1// Copyright 2014 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 5package org.chromium.base; 6 7import android.os.Debug; 8import android.os.Debug.MemoryInfo; 9import android.util.Log; 10 11import org.chromium.base.annotations.SuppressFBWarnings; 12 13import org.json.JSONArray; 14import org.json.JSONException; 15import org.json.JSONObject; 16 17import java.io.File; 18import java.io.FileNotFoundException; 19import java.io.FileOutputStream; 20import java.io.PrintStream; 21import java.util.LinkedList; 22import java.util.List; 23 24/** 25 * PerfTraceEvent can be used like TraceEvent, but is intended for 26 * performance measurement. By limiting the types of tracing we hope 27 * to minimize impact on measurement. 28 * 29 * All PerfTraceEvent events funnel into TraceEvent. When not doing 30 * performance measurements, they act the same. However, 31 * PerfTraceEvents can be enabled even when TraceEvent is not. 32 * 33 * Unlike TraceEvent, PerfTraceEvent data is sent to the system log, 34 * not to a trace file. 35 * 36 * Performance events need to have very specific names so we find 37 * the right ones. For example, we specify the name exactly in 38 * the @TracePerf annotation. Thus, unlike TraceEvent, we do not 39 * support an implicit trace name based on the callstack. 40 */ 41@SuppressFBWarnings("CHROMIUM_SYNCHRONIZED_METHOD") 42public class PerfTraceEvent { 43 private static final int MAX_NAME_LENGTH = 40; 44 private static final String MEMORY_TRACE_NAME_SUFFIX = "_BZR_PSS"; 45 private static File sOutputFile = null; 46 47 /** The event types understood by the perf trace scripts. */ 48 private enum EventType { 49 START("S"), 50 FINISH("F"), 51 INSTANT("I"); 52 53 // The string understood by the trace scripts. 54 private final String mTypeStr; 55 56 EventType(String typeStr) { 57 mTypeStr = typeStr; 58 } 59 60 @Override 61 public String toString() { 62 return mTypeStr; 63 } 64 } 65 66 private static boolean sEnabled = false; 67 private static boolean sTrackTiming = true; 68 private static boolean sTrackMemory = false; 69 70 // A list of performance trace event strings. 71 // Events are stored as a JSON dict much like TraceEvent. 72 // E.g. timestamp is in microseconds. 73 private static JSONArray sPerfTraceStrings; 74 75 // A filter for performance tracing. Only events that match a 76 // string in the list are saved. Presence of a filter does not 77 // necessarily mean perf tracing is enabled. 78 private static List<String> sFilter; 79 80 // Nanosecond start time of performance tracing. 81 private static long sBeginNanoTime; 82 83 /** 84 * Specifies what event names will be tracked. 85 * 86 * @param strings Event names we will record. 87 */ 88 @VisibleForTesting 89 public static synchronized void setFilter(List<String> strings) { 90 sFilter = new LinkedList<String>(strings); 91 } 92 93 /** 94 * Enable or disable perf tracing. 95 * Disabling of perf tracing will dump trace data to the system log. 96 */ 97 @VisibleForTesting 98 public static synchronized void setEnabled(boolean enabled) { 99 if (sEnabled == enabled) { 100 return; 101 } 102 if (enabled) { 103 sBeginNanoTime = System.nanoTime(); 104 sPerfTraceStrings = new JSONArray(); 105 } else { 106 dumpPerf(); 107 sPerfTraceStrings = null; 108 sFilter = null; 109 } 110 sEnabled = enabled; 111 } 112 113 /** 114 * Enables memory tracking for all timing perf events tracked. 115 * 116 * <p> 117 * Only works when called in combination with {@link #setEnabled(boolean)}. 118 * 119 * <p> 120 * By enabling this feature, an additional perf event containing the memory usage will be 121 * logged whenever {@link #instant(String)}, {@link #begin(String)}, or {@link #end(String)} 122 * is called. 123 * 124 * @param enabled Whether to enable memory tracking for all perf events. 125 */ 126 @VisibleForTesting 127 public static synchronized void setMemoryTrackingEnabled(boolean enabled) { 128 sTrackMemory = enabled; 129 } 130 131 /** 132 * Enables timing tracking for all perf events tracked. 133 * 134 * <p> 135 * Only works when called in combination with {@link #setEnabled(boolean)}. 136 * 137 * <p> 138 * If this feature is enabled, whenever {@link #instant(String)}, {@link #begin(String)}, 139 * or {@link #end(String)} is called the time since start of tracking will be logged. 140 * 141 * @param enabled Whether to enable timing tracking for all perf events. 142 */ 143 @VisibleForTesting 144 public static synchronized void setTimingTrackingEnabled(boolean enabled) { 145 sTrackTiming = enabled; 146 } 147 148 /** 149 * @return True if tracing is enabled, false otherwise. 150 * It is safe to call trace methods without checking if PerfTraceEvent 151 * is enabled. 152 */ 153 @VisibleForTesting 154 public static synchronized boolean enabled() { 155 return sEnabled; 156 } 157 158 /** 159 * Record an "instant" perf trace event. E.g. "screen update happened". 160 */ 161 public static synchronized void instant(String name) { 162 // Instant doesn't really need/take an event id, but this should be okay. 163 final long eventId = name.hashCode(); 164 TraceEvent.instant(name); 165 if (sEnabled && matchesFilter(name)) { 166 savePerfString(name, eventId, EventType.INSTANT, false); 167 } 168 } 169 170 171 /** 172 * Record an "begin" perf trace event. 173 * Begin trace events should have a matching end event. 174 */ 175 @VisibleForTesting 176 public static synchronized void begin(String name) { 177 final long eventId = name.hashCode(); 178 TraceEvent.startAsync(name, eventId); 179 if (sEnabled && matchesFilter(name)) { 180 // Done before calculating the starting perf data to ensure calculating the memory usage 181 // does not influence the timing data. 182 if (sTrackMemory) { 183 savePerfString(makeMemoryTraceNameFromTimingName(name), eventId, EventType.START, 184 true); 185 } 186 if (sTrackTiming) { 187 savePerfString(name, eventId, EventType.START, false); 188 } 189 } 190 } 191 192 /** 193 * Record an "end" perf trace event, to match a begin event. The 194 * time delta between begin and end is usually interesting to 195 * graph code. 196 */ 197 @VisibleForTesting 198 public static synchronized void end(String name) { 199 final long eventId = name.hashCode(); 200 TraceEvent.finishAsync(name, eventId); 201 if (sEnabled && matchesFilter(name)) { 202 if (sTrackTiming) { 203 savePerfString(name, eventId, EventType.FINISH, false); 204 } 205 // Done after calculating the ending perf data to ensure calculating the memory usage 206 // does not influence the timing data. 207 if (sTrackMemory) { 208 savePerfString(makeMemoryTraceNameFromTimingName(name), eventId, EventType.FINISH, 209 true); 210 } 211 } 212 } 213 214 /** 215 * Record an "begin" memory trace event. 216 * Begin trace events should have a matching end event. 217 */ 218 @VisibleForTesting 219 public static synchronized void begin(String name, MemoryInfo memoryInfo) { 220 final long eventId = name.hashCode(); 221 TraceEvent.startAsync(name, eventId); 222 if (sEnabled && matchesFilter(name)) { 223 // Done before calculating the starting perf data to ensure calculating the memory usage 224 // does not influence the timing data. 225 long timestampUs = (System.nanoTime() - sBeginNanoTime) / 1000; 226 savePerfString(makeMemoryTraceNameFromTimingName(name), eventId, EventType.START, 227 timestampUs, memoryInfo); 228 if (sTrackTiming) { 229 savePerfString(name, eventId, EventType.START, false); 230 } 231 } 232 } 233 234 /** 235 * Record an "end" memory trace event, to match a begin event. The 236 * memory usage delta between begin and end is usually interesting to 237 * graph code. 238 */ 239 @VisibleForTesting 240 public static synchronized void end(String name, MemoryInfo memoryInfo) { 241 final long eventId = name.hashCode(); 242 TraceEvent.finishAsync(name, eventId); 243 if (sEnabled && matchesFilter(name)) { 244 if (sTrackTiming) { 245 savePerfString(name, eventId, EventType.FINISH, false); 246 } 247 // Done after calculating the instant perf data to ensure calculating the memory usage 248 // does not influence the timing data. 249 long timestampUs = (System.nanoTime() - sBeginNanoTime) / 1000; 250 savePerfString(makeMemoryTraceNameFromTimingName(name), eventId, EventType.FINISH, 251 timestampUs, memoryInfo); 252 } 253 } 254 255 /** 256 * Determine if we are interested in this trace event. 257 * @return True if the name matches the allowed filter; else false. 258 */ 259 private static boolean matchesFilter(String name) { 260 return sFilter != null ? sFilter.contains(name) : false; 261 } 262 263 /** 264 * Save a perf trace event as a JSON dict. The format mirrors a TraceEvent dict. 265 * 266 * @param name The trace data 267 * @param id The id of the event 268 * @param type the type of trace event (I, S, F) 269 * @param includeMemory Whether to include current browser process memory usage in the trace. 270 */ 271 private static void savePerfString(String name, long id, EventType type, 272 boolean includeMemory) { 273 long timestampUs = (System.nanoTime() - sBeginNanoTime) / 1000; 274 MemoryInfo memInfo = null; 275 if (includeMemory) { 276 memInfo = new MemoryInfo(); 277 Debug.getMemoryInfo(memInfo); 278 } 279 savePerfString(name, id, type, timestampUs, memInfo); 280 } 281 282 /** 283 * Save a perf trace event as a JSON dict. The format mirrors a TraceEvent dict. 284 * 285 * @param name The trace data 286 * @param id The id of the event 287 * @param type the type of trace event (I, S, F) 288 * @param timestampUs The time stamp at which this event was recorded 289 * @param memoryInfo Memory details to be included in this perf string, null if 290 * no memory details are to be included. 291 */ 292 private static void savePerfString(String name, long id, EventType type, long timestampUs, 293 MemoryInfo memoryInfo) { 294 try { 295 JSONObject traceObj = new JSONObject(); 296 traceObj.put("cat", "Java"); 297 traceObj.put("ts", timestampUs); 298 traceObj.put("ph", type); 299 traceObj.put("name", name); 300 traceObj.put("id", id); 301 if (memoryInfo != null) { 302 int pss = memoryInfo.nativePss + memoryInfo.dalvikPss + memoryInfo.otherPss; 303 traceObj.put("mem", pss); 304 } 305 sPerfTraceStrings.put(traceObj); 306 } catch (JSONException e) { 307 throw new RuntimeException(e); 308 } 309 } 310 311 /** 312 * Generating a trace name for tracking memory based on the timing name passed in. 313 * 314 * @param name The timing name to use as a base for the memory perf name. 315 * @return The memory perf name to use. 316 */ 317 public static String makeMemoryTraceNameFromTimingName(String name) { 318 return makeSafeTraceName(name, MEMORY_TRACE_NAME_SUFFIX); 319 } 320 321 /** 322 * Builds a name to be used in the perf trace framework. The framework has length requirements 323 * for names, so this ensures the generated name does not exceed the maximum (trimming the 324 * base name if necessary). 325 * 326 * @param baseName The base name to use when generating the name. 327 * @param suffix The required suffix to be appended to the name. 328 * @return A name that is safe for the perf trace framework. 329 */ 330 public static String makeSafeTraceName(String baseName, String suffix) { 331 int suffixLength = suffix.length(); 332 333 if (baseName.length() + suffixLength > MAX_NAME_LENGTH) { 334 baseName = baseName.substring(0, MAX_NAME_LENGTH - suffixLength); 335 } 336 return baseName + suffix; 337 } 338 339 /** 340 * Sets a file to dump the results to. If {@code file} is {@code null}, it will be dumped 341 * to STDOUT, otherwise the JSON performance data will be appended to {@code file}. This should 342 * be called before the performance run starts. When {@link #setEnabled(boolean)} is called 343 * with {@code false}, the perf data will be dumped. 344 * 345 * @param file Which file to append the performance data to. If {@code null}, the performance 346 * data will be sent to STDOUT. 347 */ 348 @VisibleForTesting 349 public static synchronized void setOutputFile(File file) { 350 sOutputFile = file; 351 } 352 353 /** 354 * Dump all performance data we have saved up to the log. 355 * Output as JSON for parsing convenience. 356 */ 357 private static void dumpPerf() { 358 String json = sPerfTraceStrings.toString(); 359 360 if (sOutputFile == null) { 361 System.out.println(json); 362 } else { 363 try { 364 PrintStream stream = new PrintStream(new FileOutputStream(sOutputFile, true)); 365 try { 366 stream.print(json); 367 } finally { 368 try { 369 stream.close(); 370 } catch (Exception ex) { 371 Log.e("PerfTraceEvent", "Unable to close perf trace output file."); 372 } 373 } 374 } catch (FileNotFoundException ex) { 375 Log.e("PerfTraceEvent", "Unable to dump perf trace data to output file."); 376 } 377 } 378 } 379} 380