OLD | NEW |
| (Empty) |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 package org.chromium.base; | |
6 | |
7 import android.os.Process; | |
8 import android.os.StrictMode; | |
9 import android.os.SystemClock; | |
10 | |
11 import org.chromium.base.annotations.JNINamespace; | |
12 import org.chromium.base.annotations.SuppressFBWarnings; | |
13 | |
14 import java.io.File; | |
15 import java.util.ArrayList; | |
16 import java.util.HashMap; | |
17 import java.util.List; | |
18 import java.util.Map; | |
19 | |
20 /** Support for early tracing, before the native library is loaded. | |
21 * | |
22 * This is limited, as: | |
23 * - Arguments are not supported | |
24 * - Thread time is not reported | |
25 * - Two events with the same name cannot be in progress at the same time. | |
26 * | |
27 * Events recorded here are buffered in Java until the native library is availab
le. Then it waits | |
28 * for the completion of pending events, and sends the events to the native side
. | |
29 * | |
30 * Locking: This class is threadsafe. It is enabled when general tracing is, and
then disabled when | |
31 * tracing is enabled from the native side. Event completions are still
processed as long | |
32 * as some are pending, then early tracing is permanently disabled afte
r dumping the | |
33 * events. This means that if any early event is still pending when tr
acing is disabled, | |
34 * all early events are dropped. | |
35 */ | |
36 @JNINamespace("base::android") | |
37 public class EarlyTraceEvent { | |
38 // Must be kept in sync with the native kAndroidTraceConfigFile. | |
39 private static final String TRACE_CONFIG_FILENAME = "/data/local/chrome-trac
e-config.json"; | |
40 | |
41 /** Single trace event. */ | |
42 @VisibleForTesting | |
43 static final class Event { | |
44 final String mName; | |
45 final int mThreadId; | |
46 final long mBeginTimeMs; | |
47 long mEndTimeMs; | |
48 | |
49 Event(String name) { | |
50 mName = name; | |
51 mThreadId = Process.myTid(); | |
52 mBeginTimeMs = SystemClock.elapsedRealtime(); | |
53 } | |
54 | |
55 void end() { | |
56 assert mEndTimeMs == 0; | |
57 mEndTimeMs = SystemClock.elapsedRealtime(); | |
58 } | |
59 } | |
60 | |
61 // State transitions are: | |
62 // - enable(): DISABLED -> ENABLED | |
63 // - disable(): ENABLED -> FINISHING | |
64 // - Once there are no pending events: FINISHING -> FINISHED. | |
65 @VisibleForTesting static final int STATE_DISABLED = 0; | |
66 @VisibleForTesting static final int STATE_ENABLED = 1; | |
67 @VisibleForTesting static final int STATE_FINISHING = 2; | |
68 @VisibleForTesting static final int STATE_FINISHED = 3; | |
69 | |
70 // Locks the fields below. | |
71 private static final Object sLock = new Object(); | |
72 | |
73 @VisibleForTesting static volatile int sState = STATE_DISABLED; | |
74 // Not final as these object are not likely to be used at all. | |
75 @VisibleForTesting static List<Event> sCompletedEvents; | |
76 @VisibleForTesting static Map<String, Event> sPendingEvents; | |
77 | |
78 /** @see TraceEvent#MaybeEnableEarlyTracing(). | |
79 */ | |
80 @SuppressFBWarnings("DMI_HARDCODED_ABSOLUTE_FILENAME") | |
81 static void maybeEnable() { | |
82 boolean shouldEnable = false; | |
83 // Checking for the trace config filename touches the disk. | |
84 StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); | |
85 try { | |
86 CommandLine commandLine = CommandLine.getInstance(); | |
87 if (commandLine != null && commandLine.hasSwitch("trace-startup")) { | |
88 shouldEnable = true; | |
89 } else { | |
90 try { | |
91 shouldEnable = (new File(TRACE_CONFIG_FILENAME)).exists(); | |
92 } catch (SecurityException e) { | |
93 // Access denied, not enabled. | |
94 } | |
95 } | |
96 } finally { | |
97 StrictMode.setThreadPolicy(oldPolicy); | |
98 } | |
99 if (shouldEnable) enable(); | |
100 } | |
101 | |
102 @VisibleForTesting | |
103 static void enable() { | |
104 synchronized (sLock) { | |
105 if (sState != STATE_DISABLED) return; | |
106 sCompletedEvents = new ArrayList<Event>(); | |
107 sPendingEvents = new HashMap<String, Event>(); | |
108 sState = STATE_ENABLED; | |
109 } | |
110 } | |
111 | |
112 /** | |
113 * Disables Early tracing. | |
114 * | |
115 * Once this is called, no new event will be registered. However, end() call
s are still recorded | |
116 * as long as there are pending events. Once there are none left, pass the e
vents to the native | |
117 * side. | |
118 */ | |
119 static void disable() { | |
120 synchronized (sLock) { | |
121 if (sState != STATE_ENABLED) return; | |
122 sState = STATE_FINISHING; | |
123 maybeFinishLocked(); | |
124 } | |
125 } | |
126 | |
127 /** @see {@link TraceEvent#begin()}. */ | |
128 public static void begin(String name) { | |
129 // begin() and end() are going to be called once per TraceEvent, this av
oids entering a | |
130 // synchronized block at each and every call. | |
131 if (sState != STATE_ENABLED) return; | |
132 Event event = new Event(name); | |
133 Event conflictingEvent; | |
134 synchronized (sLock) { | |
135 if (sState != STATE_ENABLED) return; | |
136 conflictingEvent = sPendingEvents.put(name, event); | |
137 } | |
138 if (conflictingEvent != null) { | |
139 throw new IllegalArgumentException( | |
140 "Multiple pending trace events can't have the same name"); | |
141 } | |
142 } | |
143 | |
144 /** @see {@link TraceEvent#end()}. */ | |
145 public static void end(String name) { | |
146 int state = sState; | |
147 if (state != STATE_ENABLED && state != STATE_FINISHING) return; | |
148 synchronized (sLock) { | |
149 if (sState != STATE_ENABLED && sState != STATE_FINISHING) return; | |
150 Event event = sPendingEvents.remove(name); | |
151 if (event == null) return; | |
152 event.end(); | |
153 sCompletedEvents.add(event); | |
154 if (sState == STATE_FINISHING) maybeFinishLocked(); | |
155 } | |
156 } | |
157 | |
158 private static void maybeFinishLocked() { | |
159 if (!sPendingEvents.isEmpty()) return; | |
160 sState = STATE_FINISHED; | |
161 dumpEvents(sCompletedEvents); | |
162 sCompletedEvents = null; | |
163 sPendingEvents = null; | |
164 } | |
165 | |
166 private static void dumpEvents(List<Event> events) { | |
167 long nativeNowUs = nativeGetTimeTicksNowUs(); | |
168 long javaNowUs = SystemClock.elapsedRealtime() * 1000; | |
169 long offsetMs = (nativeNowUs - javaNowUs) / 1000; | |
170 for (Event event : events) { | |
171 nativeRecordEarlyEvent(event.mName, event.mBeginTimeMs + offsetMs, | |
172 event.mEndTimeMs + offsetMs, event.mThreadId); | |
173 } | |
174 } | |
175 | |
176 private static native long nativeGetTimeTicksNowUs(); | |
177 private static native void nativeRecordEarlyEvent( | |
178 String name, long beginTimeMs, long endTimeMs, int threadId); | |
179 } | |
OLD | NEW |