TracingControllerAndroid.java revision 8bcbed890bc3ce4d7a057a8f32cab53fa534672e
1// Copyright 2013 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.content.browser; 6 7import android.content.BroadcastReceiver; 8import android.content.Context; 9import android.content.Intent; 10import android.content.IntentFilter; 11import android.os.Environment; 12import android.text.TextUtils; 13import android.util.Log; 14import android.widget.Toast; 15 16import org.chromium.base.CalledByNative; 17import org.chromium.base.JNINamespace; 18import org.chromium.content.app.LibraryLoader; 19import org.chromium.content.common.TraceEvent; 20import org.chromium.content.R; 21 22import java.io.File; 23import java.util.Date; 24import java.util.Locale; 25import java.util.TimeZone; 26import java.text.SimpleDateFormat; 27 28/** 29 * Controller for Chrome's tracing feature. 30 * 31 * We don't have any UI per se. Just call startTracing() to start and 32 * stopTracing() to stop. We'll report progress to the user with Toasts. 33 * 34 * If the host application registers this class's BroadcastReceiver, you can 35 * also start and stop the tracer with a broadcast intent, as follows: 36 * <ul> 37 * <li>To start tracing: am broadcast -a org.chromium.content_shell_apk.GPU_PROFILER_START 38 * <li>Add "-e file /foo/bar/xyzzy" to log trace data to a specific file. 39 * <li>To stop tracing: am broadcast -a org.chromium.content_shell_apk.GPU_PROFILER_STOP 40 * </ul> 41 * Note that the name of these intents change depending on which application 42 * is being traced, but the general form is [app package name].GPU_PROFILER_{START,STOP}. 43 */ 44@JNINamespace("content") 45public class TracingControllerAndroid { 46 47 private static final String TAG = "TracingControllerAndroid"; 48 49 private static final String ACTION_START = "GPU_PROFILER_START"; 50 private static final String ACTION_STOP = "GPU_PROFILER_STOP"; 51 private static final String FILE_EXTRA = "file"; 52 private static final String CATEGORIES_EXTRA = "categories"; 53 private static final String RECORD_CONTINUOUSLY_EXTRA = "continuous"; 54 private static final String DEFAULT_CHROME_CATEGORIES_PLACE_HOLDER = 55 "_DEFAULT_CHROME_CATEGORIES"; 56 57 private final Context mContext; 58 private final TracingBroadcastReceiver mBroadcastReceiver; 59 private final TracingIntentFilter mIntentFilter; 60 private boolean mIsTracing; 61 62 // We might not want to always show toasts when we start the profiler, especially if 63 // showing the toast impacts performance. This gives us the chance to disable them. 64 private boolean mShowToasts = true; 65 66 private String mFilename; 67 68 public TracingControllerAndroid(Context context) { 69 mContext = context; 70 mBroadcastReceiver = new TracingBroadcastReceiver(); 71 mIntentFilter = new TracingIntentFilter(context); 72 } 73 74 /** 75 * Get a BroadcastReceiver that can handle profiler intents. 76 */ 77 public BroadcastReceiver getBroadcastReceiver() { 78 return mBroadcastReceiver; 79 } 80 81 /** 82 * Get an IntentFilter for profiler intents. 83 */ 84 public IntentFilter getIntentFilter() { 85 return mIntentFilter; 86 } 87 88 /** 89 * Register a BroadcastReceiver in the given context. 90 */ 91 public void registerReceiver(Context context) { 92 context.registerReceiver(getBroadcastReceiver(), getIntentFilter()); 93 } 94 95 /** 96 * Unregister the GPU BroadcastReceiver in the given context. 97 * @param context 98 */ 99 public void unregisterReceiver(Context context) { 100 context.unregisterReceiver(getBroadcastReceiver()); 101 } 102 103 /** 104 * Returns true if we're currently profiling. 105 */ 106 public boolean isTracing() { 107 return mIsTracing; 108 } 109 110 /** 111 * Returns the path of the current output file. Null if isTracing() false. 112 */ 113 public String getOutputPath() { 114 return mFilename; 115 } 116 117 /** 118 * Start profiling to a new file in the Downloads directory. 119 * 120 * Calls #startTracing(String, boolean, String, boolean) with a new timestamped filename. 121 * @see #startTracing(String, boolean, String, boolean) 122 */ 123 public boolean startTracing(boolean showToasts, String categories, 124 boolean recordContinuously) { 125 mShowToasts = showToasts; 126 String state = Environment.getExternalStorageState(); 127 if (!Environment.MEDIA_MOUNTED.equals(state)) { 128 logAndToastError( 129 mContext.getString(R.string.profiler_no_storage_toast)); 130 return false; 131 } 132 133 // Generate a hopefully-unique filename using the UTC timestamp. 134 // (Not a huge problem if it isn't unique, we'll just append more data.) 135 SimpleDateFormat formatter = new SimpleDateFormat( 136 "yyyy-MM-dd-HHmmss", Locale.US); 137 formatter.setTimeZone(TimeZone.getTimeZone("UTC")); 138 File dir = Environment.getExternalStoragePublicDirectory( 139 Environment.DIRECTORY_DOWNLOADS); 140 File file = new File( 141 dir, "chrome-profile-results-" + formatter.format(new Date())); 142 143 return startTracing(file.getPath(), showToasts, categories, recordContinuously); 144 } 145 146 /** 147 * Start profiling to the specified file. Returns true on success. 148 * 149 * Only one TracingControllerAndroid can be running at the same time. If another profiler 150 * is running when this method is called, it will be cancelled. If this 151 * profiler is already running, this method does nothing and returns false. 152 * 153 * @param filename The name of the file to output the profile data to. 154 * @param showToasts Whether or not we want to show toasts during this profiling session. 155 * When we are timing the profile run we might not want to incur extra draw overhead of showing 156 * notifications about the profiling system. 157 * @param categories Which categories to trace. See TracingControllerAndroid::BeginTracing() 158 * (in content/public/browser/trace_controller.h) for the format. 159 * @param recordContinuously Record until the user ends the trace. The trace buffer is fixed 160 * size and we use it as a ring buffer during recording. 161 */ 162 public boolean startTracing(String filename, boolean showToasts, String categories, 163 boolean recordContinuously) { 164 mShowToasts = showToasts; 165 if (isTracing()) { 166 // Don't need a toast because this shouldn't happen via the UI. 167 Log.e(TAG, "Received startTracing, but we're already tracing"); 168 return false; 169 } 170 // Lazy initialize the native side, to allow construction before the library is loaded. 171 if (mNativeTracingControllerAndroid == 0) { 172 mNativeTracingControllerAndroid = nativeInit(); 173 } 174 if (!nativeStartTracing(mNativeTracingControllerAndroid, filename, categories, 175 recordContinuously)) { 176 logAndToastError(mContext.getString(R.string.profiler_error_toast)); 177 return false; 178 } 179 180 logAndToastInfo(mContext.getString(R.string.profiler_started_toast) + ": " + categories); 181 TraceEvent.setEnabledToMatchNative(); 182 mFilename = filename; 183 mIsTracing = true; 184 return true; 185 } 186 187 /** 188 * Stop profiling. This won't take effect until Chrome has flushed its file. 189 */ 190 public void stopTracing() { 191 if (isTracing()) { 192 nativeStopTracing(mNativeTracingControllerAndroid); 193 } 194 } 195 196 /** 197 * Called by native code when the profiler's output file is closed. 198 */ 199 @CalledByNative 200 protected void onTracingStopped() { 201 if (!isTracing()) { 202 // Don't need a toast because this shouldn't happen via the UI. 203 Log.e(TAG, "Received onTracingStopped, but we aren't tracing"); 204 return; 205 } 206 207 logAndToastInfo( 208 mContext.getString(R.string.profiler_stopped_toast, mFilename)); 209 TraceEvent.setEnabledToMatchNative(); 210 mIsTracing = false; 211 mFilename = null; 212 } 213 214 @Override 215 protected void finalize() { 216 if (mNativeTracingControllerAndroid != 0) { 217 nativeDestroy(mNativeTracingControllerAndroid); 218 mNativeTracingControllerAndroid = 0; 219 } 220 } 221 222 void logAndToastError(String str) { 223 Log.e(TAG, str); 224 if (mShowToasts) Toast.makeText(mContext, str, Toast.LENGTH_SHORT).show(); 225 } 226 227 void logAndToastInfo(String str) { 228 Log.i(TAG, str); 229 if (mShowToasts) Toast.makeText(mContext, str, Toast.LENGTH_SHORT).show(); 230 } 231 232 private static class TracingIntentFilter extends IntentFilter { 233 TracingIntentFilter(Context context) { 234 addAction(context.getPackageName() + "." + ACTION_START); 235 addAction(context.getPackageName() + "." + ACTION_STOP); 236 } 237 } 238 239 class TracingBroadcastReceiver extends BroadcastReceiver { 240 @Override 241 public void onReceive(Context context, Intent intent) { 242 if (intent.getAction().endsWith(ACTION_START)) { 243 String categories = intent.getStringExtra(CATEGORIES_EXTRA); 244 if (TextUtils.isEmpty(categories)) { 245 categories = nativeGetDefaultCategories(); 246 } else { 247 categories = categories.replaceFirst( 248 DEFAULT_CHROME_CATEGORIES_PLACE_HOLDER, nativeGetDefaultCategories()); 249 } 250 boolean recordContinuously = 251 intent.getStringExtra(RECORD_CONTINUOUSLY_EXTRA) != null; 252 String filename = intent.getStringExtra(FILE_EXTRA); 253 if (filename != null) { 254 startTracing(filename, true, categories, recordContinuously); 255 } else { 256 startTracing(true, categories, recordContinuously); 257 } 258 } else if (intent.getAction().endsWith(ACTION_STOP)) { 259 stopTracing(); 260 } else { 261 Log.e(TAG, "Unexpected intent: " + intent); 262 } 263 } 264 } 265 266 private int mNativeTracingControllerAndroid; 267 private native int nativeInit(); 268 private native void nativeDestroy(int nativeTracingControllerAndroid); 269 private native boolean nativeStartTracing(int nativeTracingControllerAndroid, String filename, 270 String categories, boolean recordContinuously); 271 private native void nativeStopTracing(int nativeTracingControllerAndroid); 272 private native String nativeGetDefaultCategories(); 273} 274