root/base/android/java/src/org/chromium/base/PerfTraceEvent.java

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. START
  2. FINISH
  3. INSTANT
  4. toString
  5. setFilter
  6. setEnabled
  7. setMemoryTrackingEnabled
  8. setTimingTrackingEnabled
  9. enabled
  10. instant
  11. begin
  12. end
  13. begin
  14. end
  15. matchesFilter
  16. savePerfString
  17. savePerfString
  18. makeMemoryTraceNameFromTimingName
  19. makeSafeTraceName
  20. setOutputFile
  21. dumpPerf

// 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.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.
 */
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.
     */
    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.
     */
    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.
     */
    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.
     */
    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.
     */
    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.
     */
    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.
     */
    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.
     */
    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.
     */
    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.
     */
    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.");
            }
        }
    }
}

/* [<][>][^][v][top][bottom][index][help] */