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.
5 package org.chromium.content.browser;
7 import android.content.BroadcastReceiver;
8 import android.content.Context;
9 import android.content.Intent;
10 import android.content.IntentFilter;
11 import android.os.Environment;
12 import android.text.TextUtils;
13 import android.util.Log;
14 import android.widget.Toast;
16 import org.chromium.base.CalledByNative;
17 import org.chromium.base.JNINamespace;
18 import org.chromium.base.TraceEvent;
19 import org.chromium.content.R;
22 import java.text.SimpleDateFormat;
23 import java.util.Date;
24 import java.util.Locale;
25 import java.util.TimeZone;
28 * Controller for Chrome's tracing feature.
30 * We don't have any UI per se. Just call startTracing() to start and
31 * stopTracing() to stop. We'll report progress to the user with Toasts.
33 * If the host application registers this class's BroadcastReceiver, you can
34 * also start and stop the tracer with a broadcast intent, as follows:
36 * <li>To start tracing: am broadcast -a org.chromium.content_shell_apk.GPU_PROFILER_START
37 * <li>Add "-e file /foo/bar/xyzzy" to log trace data to a specific file.
38 * <li>To stop tracing: am broadcast -a org.chromium.content_shell_apk.GPU_PROFILER_STOP
40 * Note that the name of these intents change depending on which application
41 * is being traced, but the general form is [app package name].GPU_PROFILER_{START,STOP}.
43 @JNINamespace("content")
44 public class TracingControllerAndroid {
46 private static final String TAG = "TracingControllerAndroid";
48 private static final String ACTION_START = "GPU_PROFILER_START";
49 private static final String ACTION_STOP = "GPU_PROFILER_STOP";
50 private static final String FILE_EXTRA = "file";
51 private static final String CATEGORIES_EXTRA = "categories";
52 private static final String RECORD_CONTINUOUSLY_EXTRA = "continuous";
53 private static final String DEFAULT_CHROME_CATEGORIES_PLACE_HOLDER =
54 "_DEFAULT_CHROME_CATEGORIES";
56 private final Context mContext;
57 private final TracingBroadcastReceiver mBroadcastReceiver;
58 private final TracingIntentFilter mIntentFilter;
59 private boolean mIsTracing;
61 // We might not want to always show toasts when we start the profiler, especially if
62 // showing the toast impacts performance. This gives us the chance to disable them.
63 private boolean mShowToasts = true;
65 private String mFilename;
67 public TracingControllerAndroid(Context context) {
69 mBroadcastReceiver = new TracingBroadcastReceiver();
70 mIntentFilter = new TracingIntentFilter(context);
74 * Get a BroadcastReceiver that can handle profiler intents.
76 public BroadcastReceiver getBroadcastReceiver() {
77 return mBroadcastReceiver;
81 * Get an IntentFilter for profiler intents.
83 public IntentFilter getIntentFilter() {
88 * Register a BroadcastReceiver in the given context.
90 public void registerReceiver(Context context) {
91 context.registerReceiver(getBroadcastReceiver(), getIntentFilter());
95 * Unregister the GPU BroadcastReceiver in the given context.
98 public void unregisterReceiver(Context context) {
99 context.unregisterReceiver(getBroadcastReceiver());
103 * Returns true if we're currently profiling.
105 public boolean isTracing() {
110 * Returns the path of the current output file. Null if isTracing() false.
112 public String getOutputPath() {
117 * Start profiling to a new file in the Downloads directory.
119 * Calls #startTracing(String, boolean, String, boolean) with a new timestamped filename.
120 * @see #startTracing(String, boolean, String, boolean)
122 public boolean startTracing(boolean showToasts, String categories,
123 boolean recordContinuously) {
124 mShowToasts = showToasts;
125 String state = Environment.getExternalStorageState();
126 if (!Environment.MEDIA_MOUNTED.equals(state)) {
128 mContext.getString(R.string.profiler_no_storage_toast));
132 // Generate a hopefully-unique filename using the UTC timestamp.
133 // (Not a huge problem if it isn't unique, we'll just append more data.)
134 SimpleDateFormat formatter = new SimpleDateFormat(
135 "yyyy-MM-dd-HHmmss", Locale.US);
136 formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
137 File dir = Environment.getExternalStoragePublicDirectory(
138 Environment.DIRECTORY_DOWNLOADS);
139 File file = new File(
140 dir, "chrome-profile-results-" + formatter.format(new Date()));
142 return startTracing(file.getPath(), showToasts, categories, recordContinuously);
146 * Start profiling to the specified file. Returns true on success.
148 * Only one TracingControllerAndroid can be running at the same time. If another profiler
149 * is running when this method is called, it will be cancelled. If this
150 * profiler is already running, this method does nothing and returns false.
152 * @param filename The name of the file to output the profile data to.
153 * @param showToasts Whether or not we want to show toasts during this profiling session.
154 * When we are timing the profile run we might not want to incur extra draw overhead of showing
155 * notifications about the profiling system.
156 * @param categories Which categories to trace. See TracingControllerAndroid::BeginTracing()
157 * (in content/public/browser/trace_controller.h) for the format.
158 * @param recordContinuously Record until the user ends the trace. The trace buffer is fixed
159 * size and we use it as a ring buffer during recording.
161 public boolean startTracing(String filename, boolean showToasts, String categories,
162 boolean recordContinuously) {
163 mShowToasts = showToasts;
165 // Don't need a toast because this shouldn't happen via the UI.
166 Log.e(TAG, "Received startTracing, but we're already tracing");
169 // Lazy initialize the native side, to allow construction before the library is loaded.
170 if (mNativeTracingControllerAndroid == 0) {
171 mNativeTracingControllerAndroid = nativeInit();
173 if (!nativeStartTracing(mNativeTracingControllerAndroid, filename, categories,
174 recordContinuously)) {
175 logAndToastError(mContext.getString(R.string.profiler_error_toast));
179 logAndToastInfo(mContext.getString(R.string.profiler_started_toast) + ": " + categories);
180 TraceEvent.setEnabledToMatchNative();
181 mFilename = filename;
187 * Stop profiling. This won't take effect until Chrome has flushed its file.
189 public void stopTracing() {
191 nativeStopTracing(mNativeTracingControllerAndroid);
196 * Called by native code when the profiler's output file is closed.
199 protected void onTracingStopped() {
201 // Don't need a toast because this shouldn't happen via the UI.
202 Log.e(TAG, "Received onTracingStopped, but we aren't tracing");
207 mContext.getString(R.string.profiler_stopped_toast, mFilename));
208 TraceEvent.setEnabledToMatchNative();
214 protected void finalize() {
215 if (mNativeTracingControllerAndroid != 0) {
216 nativeDestroy(mNativeTracingControllerAndroid);
217 mNativeTracingControllerAndroid = 0;
221 void logAndToastError(String str) {
223 if (mShowToasts) Toast.makeText(mContext, str, Toast.LENGTH_SHORT).show();
226 void logAndToastInfo(String str) {
228 if (mShowToasts) Toast.makeText(mContext, str, Toast.LENGTH_SHORT).show();
231 private static class TracingIntentFilter extends IntentFilter {
232 TracingIntentFilter(Context context) {
233 addAction(context.getPackageName() + "." + ACTION_START);
234 addAction(context.getPackageName() + "." + ACTION_STOP);
238 class TracingBroadcastReceiver extends BroadcastReceiver {
240 public void onReceive(Context context, Intent intent) {
241 if (intent.getAction().endsWith(ACTION_START)) {
242 String categories = intent.getStringExtra(CATEGORIES_EXTRA);
243 if (TextUtils.isEmpty(categories)) {
244 categories = nativeGetDefaultCategories();
246 categories = categories.replaceFirst(
247 DEFAULT_CHROME_CATEGORIES_PLACE_HOLDER, nativeGetDefaultCategories());
249 boolean recordContinuously =
250 intent.getStringExtra(RECORD_CONTINUOUSLY_EXTRA) != null;
251 String filename = intent.getStringExtra(FILE_EXTRA);
252 if (filename != null) {
253 startTracing(filename, true, categories, recordContinuously);
255 startTracing(true, categories, recordContinuously);
257 } else if (intent.getAction().endsWith(ACTION_STOP)) {
260 Log.e(TAG, "Unexpected intent: " + intent);
265 private long mNativeTracingControllerAndroid;
266 private native long nativeInit();
267 private native void nativeDestroy(long nativeTracingControllerAndroid);
268 private native boolean nativeStartTracing(long nativeTracingControllerAndroid, String filename,
269 String categories, boolean recordContinuously);
270 private native void nativeStopTracing(long nativeTracingControllerAndroid);
271 private native String nativeGetDefaultCategories();