Chromium Code Reviews| Index: base/android/java/src/org/chromium/base/EarlyTraceEvent.java |
| diff --git a/base/android/java/src/org/chromium/base/EarlyTraceEvent.java b/base/android/java/src/org/chromium/base/EarlyTraceEvent.java |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..01939fd08369a302c2ad3d43da213a781ccf722c |
| --- /dev/null |
| +++ b/base/android/java/src/org/chromium/base/EarlyTraceEvent.java |
| @@ -0,0 +1,166 @@ |
| +// Copyright 2015 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +package org.chromium.base; |
| + |
| +import android.os.Process; |
| +import android.os.SystemClock; |
| + |
| +import org.chromium.base.annotations.JNINamespace; |
| +import org.chromium.base.annotations.SuppressFBWarnings; |
| + |
| +import java.io.File; |
| +import java.util.ArrayList; |
| +import java.util.HashMap; |
| +import java.util.List; |
| +import java.util.Map; |
| + |
| +/** Support for early tracing, before the native library is loaded. |
| + * |
| + * This is limited, as: |
| + * - Arguments are not supported |
| + * - Thread time is not reported |
| + * - Two events with the same name cannot be in progress at the same time. |
| + * |
| + * Events recorded here are buffered in Java until the native library is available. Then it waits |
| + * for the completion of pending events, and sends the events to the native side. |
| + * |
| + * Locking: This class is threadsafe. It is enabled when general tracing is, and then disabled when |
| + * tracing is enabled from the native side. Event completions are still processed as long |
| + * as some are pending, then early tracing is permanently disabled after dumping the |
| + * events. This means that if any early event is still pending when tracing is disabled, |
| + * all early events are dropped. |
| + */ |
| +@JNINamespace("base::android") |
| +public class EarlyTraceEvent { |
| + // Must be kept in sync with the native kAndroidTraceConfigFile. |
| + private static final String TRACE_CONFIG_FILENAME = "/data/local/chrome-trace-config.json"; |
| + |
| + /** Single trace event. */ |
| + @VisibleForTesting |
| + static final class Event { |
| + final String mName; |
| + final int mThreadId; |
| + final long mBeginTimeMs; |
| + long mEndTimeMs; |
| + |
| + Event(String name) { |
| + mName = name; |
| + mThreadId = Process.myTid(); |
| + mBeginTimeMs = SystemClock.elapsedRealtime(); |
| + } |
| + |
| + void end() { |
| + assert mEndTimeMs == 0; |
| + mEndTimeMs = SystemClock.elapsedRealtime(); |
| + } |
| + } |
| + |
| + // State transitions are: |
| + // - enable(): DISABLED -> ENABLED |
| + // - disable(): ENABLED -> FINISHING |
| + // - Once there are no pending events: FINISHING -> FINISHED. |
| + @VisibleForTesting static final int STATE_DISABLED = 0; |
| + @VisibleForTesting static final int STATE_ENABLED = 1; |
| + @VisibleForTesting static final int STATE_FINISHING = 2; |
| + @VisibleForTesting static final int STATE_FINISHED = 3; |
| + |
| + // Locks the fields below. |
| + private static final Object sLock = new Object(); |
| + |
| + @VisibleForTesting static volatile int sState = STATE_DISABLED; |
| + // Not final as these object are not likely to be used at all. |
| + @VisibleForTesting static List<Event> sCompletedEvents; |
| + @VisibleForTesting static Map<String, Event> sPendingEvents; |
| + |
| + /** @see TraceEvent#MaybeEnableEarlyTracing(). |
| + */ |
| + @SuppressFBWarnings("DMI_HARDCODED_ABSOLUTE_FILENAME") |
| + static void maybeEnable() { |
| + if (!(CommandLine.getInstance().hasSwitch("trace-startup") |
|
Yaron
2016/06/29 21:27:34
so this still need a strictmode exemption, right?
Benoit L
2016/07/04 16:10:42
Done.
|
| + || (new File(TRACE_CONFIG_FILENAME)).exists())) { |
| + return; |
| + } |
| + enable(); |
| + } |
| + |
| + |
| + @VisibleForTesting |
| + static void enable() { |
| + synchronized (sLock) { |
| + if (sState != STATE_DISABLED) return; |
| + sCompletedEvents = new ArrayList<Event>(); |
| + sPendingEvents = new HashMap<String, Event>(); |
| + sState = STATE_ENABLED; |
| + } |
| + } |
| + |
| + /** |
| + * Disables Early tracing. |
| + * |
| + * Once this is called, no new event will be registered. However, end() calls are still recorded |
| + * as long as there are pending events. Once there are none left, pass the events to the native |
| + * side. |
| + */ |
| + static void disable() { |
| + synchronized (sLock) { |
| + if (sState != STATE_ENABLED) return; |
| + sState = STATE_FINISHING; |
| + maybeFinishLocked(); |
| + } |
| + } |
| + |
| + /** @see {@link TraceEvent#begin()}. */ |
| + public static void begin(String name) { |
| + // begin() and end() are going to be called once per TraceEvent, this avoids entering a |
| + // synchronized block at each and every call. |
| + if (sState != STATE_ENABLED) return; |
| + Event event = new Event(name); |
| + Event conflictingEvent; |
| + synchronized (sLock) { |
| + if (sState != STATE_ENABLED) return; |
| + conflictingEvent = sPendingEvents.put(name, event); |
| + } |
| + if (conflictingEvent != null) { |
| + throw new IllegalArgumentException( |
| + "Multiple pending trace events can't have the same name"); |
| + } |
| + } |
| + |
| + /** @see {@link TraceEvent#end()}. */ |
| + public static void end(String name) { |
| + int state = sState; |
| + if (state != STATE_ENABLED && state != STATE_FINISHING) return; |
| + synchronized (sLock) { |
| + if (sState != STATE_ENABLED && sState != STATE_FINISHING) return; |
| + Event event = sPendingEvents.remove(name); |
| + if (event == null) return; |
| + event.end(); |
| + sCompletedEvents.add(event); |
| + if (sState == STATE_FINISHING) maybeFinishLocked(); |
| + } |
| + } |
| + |
| + private static void maybeFinishLocked() { |
| + if (!sPendingEvents.isEmpty()) return; |
| + sState = STATE_FINISHED; |
| + dumpEvents(sCompletedEvents); |
| + sCompletedEvents = null; |
| + sPendingEvents = null; |
| + } |
| + |
| + private static void dumpEvents(List<Event> events) { |
| + long nativeNowUs = nativeGetTimeTicksNowUs(); |
| + long javaNowUs = SystemClock.elapsedRealtime() * 1000; |
| + long offsetMs = (nativeNowUs - javaNowUs) / 1000; |
| + for (Event event : events) { |
| + nativeRecordEarlyEvent(event.mName, event.mBeginTimeMs + offsetMs, |
| + event.mEndTimeMs + offsetMs, event.mThreadId); |
| + } |
| + } |
| + |
| + private static native long nativeGetTimeTicksNowUs(); |
| + private static native void nativeRecordEarlyEvent( |
| + String name, long beginTimeMs, long endTimeMs, int threadId); |
| +} |