summaryrefslogtreecommitdiff
path: root/base/android/java/src/org/chromium/base/PerfTraceEvent.java
blob: c0e4b2125f06393a41b08e88abcae87ae7c77935 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
// Copyright 2014 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.Debug;
import android.os.Debug.MemoryInfo;
import android.util.Log;

import org.chromium.base.annotations.SuppressFBWarnings;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.LinkedList;
import java.util.List;

/**
 * PerfTraceEvent can be used like TraceEvent, but is intended for
 * performance measurement.  By limiting the types of tracing we hope
 * to minimize impact on measurement.
 *
 * All PerfTraceEvent events funnel into TraceEvent. When not doing
 * performance measurements, they act the same.  However,
 * PerfTraceEvents can be enabled even when TraceEvent is not.
 *
 * Unlike TraceEvent, PerfTraceEvent data is sent to the system log,
 * not to a trace file.
 *
 * Performance events need to have very specific names so we find
 * the right ones.  For example, we specify the name exactly in
 * the @TracePerf annotation.  Thus, unlike TraceEvent, we do not
 * support an implicit trace name based on the callstack.
 */
@SuppressFBWarnings("CHROMIUM_SYNCHRONIZED_METHOD")
public class PerfTraceEvent {
    private static final int MAX_NAME_LENGTH = 40;
    private static final String MEMORY_TRACE_NAME_SUFFIX = "_BZR_PSS";
    private static File sOutputFile = null;

    /** The event types understood by the perf trace scripts. */
    private enum EventType {
        START("S"),
        FINISH("F"),
        INSTANT("I");

        // The string understood by the trace scripts.
        private final String mTypeStr;

        EventType(String typeStr) {
            mTypeStr = typeStr;
        }

        @Override
        public String toString() {
            return mTypeStr;
        }
    }

    private static boolean sEnabled = false;
    private static boolean sTrackTiming = true;
    private static boolean sTrackMemory = false;

    // A list of performance trace event strings.
    // Events are stored as a JSON dict much like TraceEvent.
    // E.g. timestamp is in microseconds.
    private static JSONArray sPerfTraceStrings;

    // A filter for performance tracing.  Only events that match a
    // string in the list are saved.  Presence of a filter does not
    // necessarily mean perf tracing is enabled.
    private static List<String> sFilter;

    // Nanosecond start time of performance tracing.
    private static long sBeginNanoTime;

    /**
     * Specifies what event names will be tracked.
     *
     * @param strings Event names we will record.
     */
    @VisibleForTesting
    public static synchronized void setFilter(List<String> strings) {
        sFilter = new LinkedList<String>(strings);
    }

    /**
     * Enable or disable perf tracing.
     * Disabling of perf tracing will dump trace data to the system log.
     */
    @VisibleForTesting
    public static synchronized void setEnabled(boolean enabled) {
        if (sEnabled == enabled) {
            return;
        }
        if (enabled) {
            sBeginNanoTime = System.nanoTime();
            sPerfTraceStrings = new JSONArray();
        } else {
            dumpPerf();
            sPerfTraceStrings = null;
            sFilter = null;
        }
        sEnabled = enabled;
    }

    /**
     * Enables memory tracking for all timing perf events tracked.
     *
     * <p>
     * Only works when called in combination with {@link #setEnabled(boolean)}.
     *
     * <p>
     * By enabling this feature, an additional perf event containing the memory usage will be
     * logged whenever {@link #instant(String)}, {@link #begin(String)}, or {@link #end(String)}
     * is called.
     *
     * @param enabled Whether to enable memory tracking for all perf events.
     */
    @VisibleForTesting
    public static synchronized void setMemoryTrackingEnabled(boolean enabled) {
        sTrackMemory = enabled;
    }

    /**
     * Enables timing tracking for all perf events tracked.
     *
     * <p>
     * Only works when called in combination with {@link #setEnabled(boolean)}.
     *
     * <p>
     * If this feature is enabled, whenever {@link #instant(String)}, {@link #begin(String)},
     * or {@link #end(String)} is called the time since start of tracking will be logged.
     *
     * @param enabled Whether to enable timing tracking for all perf events.
     */
    @VisibleForTesting
    public static synchronized void setTimingTrackingEnabled(boolean enabled) {
        sTrackTiming = enabled;
    }

    /**
     * @return True if tracing is enabled, false otherwise.
     * It is safe to call trace methods without checking if PerfTraceEvent
     * is enabled.
     */
    @VisibleForTesting
    public static synchronized boolean enabled() {
        return sEnabled;
    }

    /**
     * Record an "instant" perf trace event.  E.g. "screen update happened".
     */
    public static synchronized void instant(String name) {
        // Instant doesn't really need/take an event id, but this should be okay.
        final long eventId = name.hashCode();
        TraceEvent.instant(name);
        if (sEnabled && matchesFilter(name)) {
            savePerfString(name, eventId, EventType.INSTANT, false);
        }
    }


    /**
     * Record an "begin" perf trace event.
     * Begin trace events should have a matching end event.
     */
    @VisibleForTesting
    public static synchronized void begin(String name) {
        final long eventId = name.hashCode();
        TraceEvent.startAsync(name, eventId);
        if (sEnabled && matchesFilter(name)) {
            // Done before calculating the starting perf data to ensure calculating the memory usage
            // does not influence the timing data.
            if (sTrackMemory) {
                savePerfString(makeMemoryTraceNameFromTimingName(name), eventId, EventType.START,
                        true);
            }
            if (sTrackTiming) {
                savePerfString(name, eventId, EventType.START, false);
            }
        }
    }

    /**
     * Record an "end" perf trace event, to match a begin event.  The
     * time delta between begin and end is usually interesting to
     * graph code.
     */
    @VisibleForTesting
    public static synchronized void end(String name) {
        final long eventId = name.hashCode();
        TraceEvent.finishAsync(name, eventId);
        if (sEnabled && matchesFilter(name)) {
            if (sTrackTiming) {
                savePerfString(name, eventId, EventType.FINISH, false);
            }
            // Done after calculating the ending perf data to ensure calculating the memory usage
            // does not influence the timing data.
            if (sTrackMemory) {
                savePerfString(makeMemoryTraceNameFromTimingName(name), eventId, EventType.FINISH,
                        true);
            }
        }
    }

    /**
     * Record an "begin" memory trace event.
     * Begin trace events should have a matching end event.
     */
    @VisibleForTesting
    public static synchronized void begin(String name, MemoryInfo memoryInfo) {
        final long eventId = name.hashCode();
        TraceEvent.startAsync(name, eventId);
        if (sEnabled && matchesFilter(name)) {
            // Done before calculating the starting perf data to ensure calculating the memory usage
            // does not influence the timing data.
            long timestampUs = (System.nanoTime() - sBeginNanoTime) / 1000;
            savePerfString(makeMemoryTraceNameFromTimingName(name), eventId, EventType.START,
                    timestampUs, memoryInfo);
            if (sTrackTiming) {
                savePerfString(name, eventId, EventType.START, false);
            }
        }
    }

    /**
     * Record an "end" memory trace event, to match a begin event.  The
     * memory usage delta between begin and end is usually interesting to
     * graph code.
     */
    @VisibleForTesting
    public static synchronized void end(String name, MemoryInfo memoryInfo) {
        final long eventId = name.hashCode();
        TraceEvent.finishAsync(name, eventId);
        if (sEnabled && matchesFilter(name)) {
            if (sTrackTiming) {
                savePerfString(name, eventId, EventType.FINISH, false);
            }
            // Done after calculating the instant perf data to ensure calculating the memory usage
            // does not influence the timing data.
            long timestampUs = (System.nanoTime() - sBeginNanoTime) / 1000;
            savePerfString(makeMemoryTraceNameFromTimingName(name), eventId, EventType.FINISH,
                    timestampUs, memoryInfo);
        }
    }

    /**
     * Determine if we are interested in this trace event.
     * @return True if the name matches the allowed filter; else false.
     */
    private static boolean matchesFilter(String name) {
        return sFilter != null ? sFilter.contains(name) : false;
    }

    /**
     * Save a perf trace event as a JSON dict.  The format mirrors a TraceEvent dict.
     *
     * @param name The trace data
     * @param id The id of the event
     * @param type the type of trace event (I, S, F)
     * @param includeMemory Whether to include current browser process memory usage in the trace.
     */
    private static void savePerfString(String name, long id, EventType type,
            boolean includeMemory) {
        long timestampUs = (System.nanoTime() - sBeginNanoTime) / 1000;
        MemoryInfo memInfo = null;
        if (includeMemory) {
            memInfo = new MemoryInfo();
            Debug.getMemoryInfo(memInfo);
        }
        savePerfString(name, id, type, timestampUs, memInfo);
    }

    /**
     * Save a perf trace event as a JSON dict.  The format mirrors a TraceEvent dict.
     *
     * @param name The trace data
     * @param id The id of the event
     * @param type the type of trace event (I, S, F)
     * @param timestampUs The time stamp at which this event was recorded
     * @param memoryInfo Memory details to be included in this perf string, null if
     *                   no memory details are to be included.
     */
    private static void savePerfString(String name, long id, EventType type, long timestampUs,
            MemoryInfo memoryInfo) {
        try {
            JSONObject traceObj = new JSONObject();
            traceObj.put("cat", "Java");
            traceObj.put("ts", timestampUs);
            traceObj.put("ph", type);
            traceObj.put("name", name);
            traceObj.put("id", id);
            if (memoryInfo != null) {
                int pss = memoryInfo.nativePss + memoryInfo.dalvikPss + memoryInfo.otherPss;
                traceObj.put("mem", pss);
            }
            sPerfTraceStrings.put(traceObj);
        } catch (JSONException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Generating a trace name for tracking memory based on the timing name passed in.
     *
     * @param name The timing name to use as a base for the memory perf name.
     * @return The memory perf name to use.
     */
    public static String makeMemoryTraceNameFromTimingName(String name) {
        return makeSafeTraceName(name, MEMORY_TRACE_NAME_SUFFIX);
    }

    /**
     * Builds a name to be used in the perf trace framework.  The framework has length requirements
     * for names, so this ensures the generated name does not exceed the maximum (trimming the
     * base name if necessary).
     *
     * @param baseName The base name to use when generating the name.
     * @param suffix The required suffix to be appended to the name.
     * @return A name that is safe for the perf trace framework.
     */
    public static String makeSafeTraceName(String baseName, String suffix) {
        int suffixLength = suffix.length();

        if (baseName.length() + suffixLength > MAX_NAME_LENGTH) {
            baseName = baseName.substring(0, MAX_NAME_LENGTH - suffixLength);
        }
        return baseName + suffix;
    }

    /**
     * Sets a file to dump the results to.  If {@code file} is {@code null}, it will be dumped
     * to STDOUT, otherwise the JSON performance data will be appended to {@code file}.  This should
     * be called before the performance run starts.  When {@link #setEnabled(boolean)} is called
     * with {@code false}, the perf data will be dumped.
     *
     * @param file Which file to append the performance data to.  If {@code null}, the performance
     *             data will be sent to STDOUT.
     */
    @VisibleForTesting
    public static synchronized void setOutputFile(File file) {
        sOutputFile = file;
    }

    /**
     * Dump all performance data we have saved up to the log.
     * Output as JSON for parsing convenience.
     */
    private static void dumpPerf() {
        String json = sPerfTraceStrings.toString();

        if (sOutputFile == null) {
            System.out.println(json);
        } else {
            try {
                PrintStream stream = new PrintStream(new FileOutputStream(sOutputFile, true));
                try {
                    stream.print(json);
                } finally {
                    try {
                        stream.close();
                    } catch (Exception ex) {
                        Log.e("PerfTraceEvent", "Unable to close perf trace output file.");
                    }
                }
            } catch (FileNotFoundException ex) {
                Log.e("PerfTraceEvent", "Unable to dump perf trace data to output file.");
            }
        }
    }
}