Commit 7c1a1d49 authored by Sami Kyostila's avatar Sami Kyostila Committed by Commit Bot

Reland: Redesign ATrace integration

(Reland of https://chromium-review.googlesource.com/c/chromium/src/+/2332664)

Chrome and WebView have supported basic Android ATrace tracing, but the
current approach has some shortcomings:

1) Android R changes the way atrace session activations are broadcast,
   breaking the code in WebView listening for activations
   (OnTraceEnabledChangeListener). This means you can't trace a running
   WebView app with atrace.

2) Neither Chrome nor WebView record early startup events to atrace,
   which means all events before the native library has loaded are
   lost.

3) It's not possible to specify trace categories via atrace, which means
   we need more cumbersome alternative solutions (i.e., command line
   flags) for startup tracing.

4) Writing ATrace events is only supported in WebView.

This patch reworks the ATrace integration to resolve these problems and
to align the Chrome and WebView implementations. In short, ATrace
session management is moved to the common (TraceEvent) layer, and Chrome
and WebView only differ by which trace tags they listen to (APP vs.
WEBVIEW).

We also add a way to declare trace categories through per-app tags:

$ atrace -a org.chromium.chrome,org.chromium.chrome/<category_filter>

(Note that the plain package name without any category filters must
always appear in the list on its own.)

Multiple categories can be separated with a double colon:

$ atrace -a org.chromium.chrome,org.chromium.chrome/cat1:cat2

Or by specifying the the app several times to get around the 91
character limit for each entry:

$ atrace -a org.chromium.chrome,\
            org.chromium.chrome/cat1,\
            org.chromium.chrome/cat2

Finally, to capture Java startup events into a tracing session
controlled by the system's Perfetto service instead of atrace, a
special "-atrace" category can be used to only write events into
Chrome's own tracing service instead of atrace. This way when we
connect to Perfetto later in the startup sequence and establish
the real tracing session, startup-related events can be flushed
into that session without emitting duplicate events into ATrace.

TEST=atrace -a org.chromium.chrome

TEST=atrace webview

TEST=atrace -a org.chromium.chrome

TEST=atrace webview

TEST=atrace -a org.chromium.chrome,org.chromium.chrome/-*:cc

TEST=atrace -a com.google.android.gm,com.google.android.gm/-*:cc webview

TEST=atrace -a org.chromium.chrome,org.chromium.chrome/-atrace

Bug: 1095587, b/160768681
Change-Id: Ifc1488cc1a045bb79a804f13596935234fc7a163
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2364635
Commit-Queue: Sami Kyöstilä <skyostil@chromium.org>
Reviewed-by: default avatarBo <boliu@chromium.org>
Reviewed-by: default avatarAndrew Grieve <agrieve@chromium.org>
Reviewed-by: default avatarEric Seckler <eseckler@chromium.org>
Cr-Commit-Position: refs/heads/master@{#800611}
parent 407f4938
...@@ -16,8 +16,6 @@ import android.webkit.GeolocationPermissions; ...@@ -16,8 +16,6 @@ import android.webkit.GeolocationPermissions;
import android.webkit.WebStorage; import android.webkit.WebStorage;
import android.webkit.WebViewDatabase; import android.webkit.WebViewDatabase;
import com.android.webview.chromium.WebViewDelegateFactory.WebViewDelegate;
import org.chromium.android_webview.AwBrowserContext; import org.chromium.android_webview.AwBrowserContext;
import org.chromium.android_webview.AwBrowserProcess; import org.chromium.android_webview.AwBrowserProcess;
import org.chromium.android_webview.AwContents; import org.chromium.android_webview.AwContents;
...@@ -97,6 +95,8 @@ public class WebViewChromiumAwInit { ...@@ -97,6 +95,8 @@ public class WebViewChromiumAwInit {
mFactory = factory; mFactory = factory;
// Do not make calls into 'factory' in this ctor - this ctor is called from the // Do not make calls into 'factory' in this ctor - this ctor is called from the
// WebViewChromiumFactoryProvider ctor, so 'factory' is not properly initialized yet. // WebViewChromiumFactoryProvider ctor, so 'factory' is not properly initialized yet.
TraceEvent.maybeEnableEarlyTracing(
TraceEvent.ATRACE_TAG_WEBVIEW, /*readCommandLine=*/false);
} }
public AwTracingController getAwTracingController() { public AwTracingController getAwTracingController() {
...@@ -126,7 +126,6 @@ public class WebViewChromiumAwInit { ...@@ -126,7 +126,6 @@ public class WebViewChromiumAwInit {
protected void startChromiumLocked() { protected void startChromiumLocked() {
try (ScopedSysTraceEvent event = try (ScopedSysTraceEvent event =
ScopedSysTraceEvent.scoped("WebViewChromiumAwInit.startChromiumLocked")) { ScopedSysTraceEvent.scoped("WebViewChromiumAwInit.startChromiumLocked")) {
TraceEvent.setATraceEnabled(mFactory.getWebViewDelegate().isTraceTagEnabled());
assert Thread.holdsLock(mLock) && ThreadUtils.runningOnUiThread(); assert Thread.holdsLock(mLock) && ThreadUtils.runningOnUiThread();
// The post-condition of this method is everything is ready, so notify now to cover all // The post-condition of this method is everything is ready, so notify now to cover all
...@@ -178,14 +177,6 @@ public class WebViewChromiumAwInit { ...@@ -178,14 +177,6 @@ public class WebViewChromiumAwInit {
mSharedStatics.setWebContentsDebuggingEnabledUnconditionally(true); mSharedStatics.setWebContentsDebuggingEnabledUnconditionally(true);
} }
mFactory.getWebViewDelegate().setOnTraceEnabledChangeListener(
new WebViewDelegate.OnTraceEnabledChangeListener() {
@Override
public void onTraceEnabledChange(boolean enabled) {
TraceEvent.setATraceEnabled(enabled);
}
});
mStarted = true; mStarted = true;
RecordHistogram.recordSparseHistogram("Android.WebView.TargetSdkVersion", RecordHistogram.recordSparseHistogram("Android.WebView.TargetSdkVersion",
......
...@@ -11,7 +11,6 @@ import android.content.pm.PackageInfo; ...@@ -11,7 +11,6 @@ import android.content.pm.PackageInfo;
import android.content.res.AssetManager; import android.content.res.AssetManager;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Canvas; import android.graphics.Canvas;
import android.os.Trace;
import android.util.SparseArray; import android.util.SparseArray;
import android.view.View; import android.view.View;
...@@ -41,17 +40,6 @@ class WebViewDelegateFactory { ...@@ -41,17 +40,6 @@ class WebViewDelegateFactory {
* See {@link WebViewDelegateFactory} for the reasons why this copy is needed. * See {@link WebViewDelegateFactory} for the reasons why this copy is needed.
*/ */
interface WebViewDelegate extends AwDrawFnImpl.DrawFnAccess { interface WebViewDelegate extends AwDrawFnImpl.DrawFnAccess {
/** @see android.webkit.WebViewDelegate.OnTraceEnabledChangeListener */
interface OnTraceEnabledChangeListener {
void onTraceEnabledChange(boolean enabled);
}
/** @see android.webkit.WebViewDelegate#setOnTraceEnabledChangeListener */
void setOnTraceEnabledChangeListener(final OnTraceEnabledChangeListener listener);
/** @see android.webkit.WebViewDelegate#isTraceTagEnabled */
boolean isTraceTagEnabled();
/** @see android.webkit.WebViewDelegate#canInvokeDrawGlFunctor */ /** @see android.webkit.WebViewDelegate#canInvokeDrawGlFunctor */
boolean canInvokeDrawGlFunctor(View containerView); boolean canInvokeDrawGlFunctor(View containerView);
...@@ -123,22 +111,6 @@ class WebViewDelegateFactory { ...@@ -123,22 +111,6 @@ class WebViewDelegateFactory {
mDelegate = delegate; mDelegate = delegate;
} }
@Override
public void setOnTraceEnabledChangeListener(final OnTraceEnabledChangeListener listener) {
mDelegate.setOnTraceEnabledChangeListener(
new android.webkit.WebViewDelegate.OnTraceEnabledChangeListener() {
@Override
public void onTraceEnabledChange(boolean enabled) {
listener.onTraceEnabledChange(enabled);
}
});
}
@Override
public boolean isTraceTagEnabled() {
return mDelegate.isTraceTagEnabled();
}
@Override @Override
public boolean canInvokeDrawGlFunctor(View containerView) { public boolean canInvokeDrawGlFunctor(View containerView) {
return mDelegate.canInvokeDrawGlFunctor(containerView); return mDelegate.canInvokeDrawGlFunctor(containerView);
...@@ -234,12 +206,7 @@ class WebViewDelegateFactory { ...@@ -234,12 +206,7 @@ class WebViewDelegateFactory {
* framework. * framework.
*/ */
private static class Api21CompatibilityDelegate implements WebViewDelegate { private static class Api21CompatibilityDelegate implements WebViewDelegate {
/** Copy of Trace.TRACE_TAG_WEBVIEW */
private static final long TRACE_TAG_WEBVIEW = 1L << 4;
/** Hidden APIs released in the API 21 version of the framework */ /** Hidden APIs released in the API 21 version of the framework */
private final Method mIsTagEnabledMethod;
private final Method mAddChangeCallbackMethod;
private final Method mGetViewRootImplMethod; private final Method mGetViewRootImplMethod;
private final Method mInvokeFunctorMethod; private final Method mInvokeFunctorMethod;
private final Method mCallDrawGLFunctionMethod; private final Method mCallDrawGLFunctionMethod;
...@@ -255,9 +222,6 @@ class WebViewDelegateFactory { ...@@ -255,9 +222,6 @@ class WebViewDelegateFactory {
// Important: This reflection essentially defines a snapshot of some hidden APIs // Important: This reflection essentially defines a snapshot of some hidden APIs
// at version 21 of the framework for compatibility reasons, and the reflection // at version 21 of the framework for compatibility reasons, and the reflection
// should not be changed even if those hidden APIs change in future releases. // should not be changed even if those hidden APIs change in future releases.
mIsTagEnabledMethod = Trace.class.getMethod("isTagEnabled", long.class);
mAddChangeCallbackMethod = Class.forName("android.os.SystemProperties")
.getMethod("addChangeCallback", Runnable.class);
mGetViewRootImplMethod = View.class.getMethod("getViewRootImpl"); mGetViewRootImplMethod = View.class.getMethod("getViewRootImpl");
mInvokeFunctorMethod = mInvokeFunctorMethod =
Class.forName("android.view.ViewRootImpl") Class.forName("android.view.ViewRootImpl")
...@@ -280,29 +244,6 @@ class WebViewDelegateFactory { ...@@ -280,29 +244,6 @@ class WebViewDelegateFactory {
} }
} }
@Override
public void setOnTraceEnabledChangeListener(final OnTraceEnabledChangeListener listener) {
try {
mAddChangeCallbackMethod.invoke(null, new Runnable() {
@Override
public void run() {
listener.onTraceEnabledChange(isTraceTagEnabled());
}
});
} catch (Exception e) {
throw new RuntimeException("Invalid reflection", e);
}
}
@Override
public boolean isTraceTagEnabled() {
try {
return ((Boolean) mIsTagEnabledMethod.invoke(null, TRACE_TAG_WEBVIEW));
} catch (Exception e) {
throw new RuntimeException("Invalid reflection", e);
}
}
@Override @Override
public boolean canInvokeDrawGlFunctor(View containerView) { public boolean canInvokeDrawGlFunctor(View containerView) {
try { try {
......
...@@ -71,10 +71,12 @@ public class AwShellActivity extends Activity { ...@@ -71,10 +71,12 @@ public class AwShellActivity extends Activity {
AwBrowserProcess.loadLibrary(null); AwBrowserProcess.loadLibrary(null);
// This flag is deprecated. Print a hint instead.
if (CommandLine.getInstance().hasSwitch(AwShellSwitches.ENABLE_ATRACE)) { if (CommandLine.getInstance().hasSwitch(AwShellSwitches.ENABLE_ATRACE)) {
Log.e(TAG, "Enabling Android trace."); Log.e(TAG, "To trace the test shell, run \"atrace webview\"");
TraceEvent.setATraceEnabled(true);
} }
TraceEvent.maybeEnableEarlyTracing(
TraceEvent.ATRACE_TAG_WEBVIEW, /*readCommandLine=*/false);
setContentView(R.layout.testshell_activity); setContentView(R.layout.testshell_activity);
......
...@@ -9,7 +9,7 @@ package org.chromium.android_webview.shell; ...@@ -9,7 +9,7 @@ package org.chromium.android_webview.shell;
* the android_webview glue layer. * the android_webview glue layer.
*/ */
public abstract class AwShellSwitches { public abstract class AwShellSwitches {
// Enables Android systrace path for Chrome traces. // Deprecated: instead, run "atrace webview".
public static final String ENABLE_ATRACE = "enable-atrace"; public static final String ENABLE_ATRACE = "enable-atrace";
// Prevent instantiation. // Prevent instantiation.
......
...@@ -148,7 +148,6 @@ public class EarlyTraceEvent { ...@@ -148,7 +148,6 @@ public class EarlyTraceEvent {
if (shouldEnable) enable(); if (shouldEnable) enable();
} }
@VisibleForTesting
static void enable() { static void enable() {
synchronized (sLock) { synchronized (sLock) {
if (sState != STATE_DISABLED) return; if (sState != STATE_DISABLED) return;
......
...@@ -118,18 +118,24 @@ public class ThreadUtils { ...@@ -118,18 +118,24 @@ public class ThreadUtils {
sUiThreadHandler = new Handler(looper); sUiThreadHandler = new Handler(looper);
} }
} }
TraceEvent.onUiThreadReady();
} }
public static Handler getUiThreadHandler() { public static Handler getUiThreadHandler() {
boolean createdHandler = false;
synchronized (sLock) { synchronized (sLock) {
if (sUiThreadHandler == null) { if (sUiThreadHandler == null) {
if (sWillOverride) { if (sWillOverride) {
throw new RuntimeException("Did not yet override the UI thread"); throw new RuntimeException("Did not yet override the UI thread");
} }
sUiThreadHandler = new Handler(Looper.getMainLooper()); sUiThreadHandler = new Handler(Looper.getMainLooper());
createdHandler = true;
} }
return sUiThreadHandler;
} }
if (createdHandler) {
TraceEvent.onUiThreadReady();
}
return sUiThreadHandler;
} }
/** /**
......
...@@ -10,10 +10,18 @@ import android.os.SystemClock; ...@@ -10,10 +10,18 @@ import android.os.SystemClock;
import android.util.Log; import android.util.Log;
import android.util.Printer; import android.util.Printer;
import androidx.annotation.AnyThread;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import org.chromium.base.annotations.CalledByNative; import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace; import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.MainDex; import org.chromium.base.annotations.MainDex;
import org.chromium.base.annotations.NativeMethods; import org.chromium.base.annotations.NativeMethods;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicBoolean;
/** /**
* Java mirror of Chrome trace event API. See base/trace_event/trace_event.h. * Java mirror of Chrome trace event API. See base/trace_event/trace_event.h.
* *
...@@ -33,8 +41,335 @@ import org.chromium.base.annotations.NativeMethods; ...@@ -33,8 +41,335 @@ import org.chromium.base.annotations.NativeMethods;
@JNINamespace("base::android") @JNINamespace("base::android")
@MainDex @MainDex
public class TraceEvent implements AutoCloseable { public class TraceEvent implements AutoCloseable {
private static volatile boolean sEnabled; private static volatile boolean sEnabled; // True when tracing into Chrome's tracing service.
private static volatile boolean sATraceEnabled; // True when taking an Android systrace.
// Trace tags replicated from android.os.Trace.
public static final long ATRACE_TAG_WEBVIEW = 1L << 4;
public static final long ATRACE_TAG_APP = 1L << 12;
/**
* Watches for active ATrace sessions and accordingly enables or disables
* tracing in Chrome/WebView.
*/
private static class ATrace implements MessageQueue.IdleHandler {
private static final String TAG = "ATrace";
private Class<?> mTraceClass;
private Method mIsTraceTagEnabledMethod;
private Method mTraceBeginMethod;
private Method mTraceEndMethod;
private Method mAsyncTraceBeginMethod;
private Method mAsyncTraceEndMethod;
private Class<?> mSystemPropertiesClass;
private Method mGetSystemPropertyMethod;
private final AtomicBoolean mNativeTracingReady = new AtomicBoolean();
private final AtomicBoolean mUiThreadReady = new AtomicBoolean();
private final AtomicBoolean mTraceTagActive = new AtomicBoolean();
private final long mTraceTag;
private boolean mShouldWriteToSystemTrace;
private static class CategoryConfig {
public String filter = "";
public boolean shouldWriteToATrace = true;
}
public ATrace(long traceTag) {
// Look up hidden ATrace APIs.
try {
mTraceClass = Class.forName("android.os.Trace");
mIsTraceTagEnabledMethod = mTraceClass.getMethod("isTagEnabled", long.class);
mTraceBeginMethod = mTraceClass.getMethod("traceBegin", long.class, String.class);
mTraceEndMethod = mTraceClass.getMethod("traceEnd", long.class);
mAsyncTraceBeginMethod = mTraceClass.getMethod(
"asyncTraceBegin", long.class, String.class, int.class);
mAsyncTraceEndMethod =
mTraceClass.getMethod("asyncTraceEnd", long.class, String.class, int.class);
mSystemPropertiesClass = Class.forName("android.os.SystemProperties");
mGetSystemPropertyMethod = mSystemPropertiesClass.getMethod("get", String.class);
} catch (Exception e) {
// If we hit reflection errors, just disable atrace support.
org.chromium.base.Log.w(TAG, "Reflection error", e);
mIsTraceTagEnabledMethod = null;
}
// If there's an active atrace session, also start collecting early trace events.
mTraceTag = traceTag;
pollConfig();
}
/**
* Reads a system property and returns its string value.
*
* @param name the name of the system property
* @return the result string or null if an exception occurred
*/
@Nullable
private String getSystemProperty(String name) {
try {
return (String) mGetSystemPropertyMethod.invoke(mSystemPropertiesClass, name);
} catch (Exception e) {
return null;
}
}
/**
* Reads a system property and returns its value as an integer.
*
* @param name the name of the system property
* @return the result integer or null if an exception occurred
*/
private Integer getIntegerSystemProperty(String name) {
String property = getSystemProperty(name);
if (property == null) return null;
try {
return Integer.decode(property);
} catch (NumberFormatException e) {
return null;
}
}
private boolean isTraceTagEnabled(long traceTag) {
try {
return (boolean) mIsTraceTagEnabledMethod.invoke(mTraceClass, traceTag);
} catch (Exception e) {
return false;
}
}
/**
* @return true if Chrome/WebView is part of an active ATrace session.
*/
public boolean hasActiveSession() {
return mTraceTagActive.get();
}
/**
* Checks whether ATrace has started or stopped tracing since the last
* call to this function and parses the changed config if necessary.
*
* @return true if a session has started or stopped.
*/
@UiThread
private boolean pollConfig() {
// ATrace's tracing configuration consists of the following system
// properties:
// - debug.atrace.tags.enableflags: A hex mask of the enabled system
// tracing categories (e.g, "0x10").
// - debug.atrace.app_number: The number of per-app config entries
// (e.g., "1").
// - debug.atrace.app_0: Config for app 0 (up to
// app_number-1).
//
// Normally the per-app config entry is just the package name, but we
// also support setting the trace config with additional parameters,
// e.g., assuming "com.android.chrome" as the package name:
//
// - Enable default categories: "com.android.chrome"
// - Enable specific categories: "com.android.chrome/cat1:cat2"
// - Disable specific categories: "com.android.chrome/*:-cat1"
//
// Since each app-specific config is limited to 91 characters, multiple
// entries can be used to work around the limit.
//
// If either the "webview" trace tag (0x10) is enabled (for WebView)
// or our package name is found in the list of configs, trace events
// will be written into ATrace. However, if "-atrace" appears as a
// category in any of the app-specific configs, events will only be
// written into Chrome's own startup tracing buffer to avoid
// duplicate events.
boolean traceTagWasActive = mTraceTagActive.get();
boolean traceTagIsActive = isTraceTagEnabled(mTraceTag);
if (traceTagWasActive == traceTagIsActive) return false;
mTraceTagActive.set(traceTagIsActive);
if (!traceTagIsActive) {
// A previously active atrace session ended.
EarlyTraceEvent.disable();
disableNativeATrace();
mShouldWriteToSystemTrace = false;
ThreadUtils.getUiThreadLooper().setMessageLogging(null);
return true;
}
CategoryConfig config = getCategoryConfigFromATrace();
// There is an active atrace session. We can output events into one
// of the following sinks:
//
// - To ATrace:
// ...via TraceLog if native has finished loading.
// ...via android.os.Trace otherwise.
// - To Chrome's own tracing service (for startup tracing):
// ...via TraceLog if native has finished loading.
// ...via EarlyTraceEvent otherwise.
mShouldWriteToSystemTrace = false;
if (mNativeTracingReady.get()) {
// Native is loaded; start writing to atrace via TraceLog, or in
// the case of a Chrome-only trace, setup a startup tracing
// session.
if (config.shouldWriteToATrace) {
enableNativeATrace(config.filter);
} else {
setupATraceStartupTrace(config.filter);
}
} else {
// Native isn't there yet; fall back to android.os.Trace or
// EarlyTraceEvent. We can't use the category filter in this
// case because Java events don't have categories.
if (config.shouldWriteToATrace) {
mShouldWriteToSystemTrace = true;
} else {
EarlyTraceEvent.enable();
}
}
// For Chrome-only traces, also capture Looper messages. In other
// cases, they are logged by the system.
if (!config.shouldWriteToATrace) {
ThreadUtils.getUiThreadLooper().setMessageLogging(LooperMonitorHolder.sInstance);
}
return true;
}
private CategoryConfig getCategoryConfigFromATrace() {
CategoryConfig config = new CategoryConfig();
boolean shouldWriteToATrace = true;
Integer appCount = getIntegerSystemProperty("debug.atrace.app_number");
// In the case of WebView, the application context may not have been
// attached yet. Ignore per-app category settings in that case; they
// will be applied when the native library finishes loading.
if (appCount != null && appCount > 0 && ContextUtils.getApplicationContext() != null) {
// Look for tracing category settings meant for this activity.
// For Chrome this is the package name of the browser, while for
// WebView this is the package name of the hosting application
// (e.g., GMail).
String packageName = ContextUtils.getApplicationContext().getPackageName();
for (int i = 0; i < appCount; i++) {
String appConfig = getSystemProperty("debug.atrace.app_" + i);
if (appConfig == null || !appConfig.startsWith(packageName)) continue;
String extra = appConfig.substring(packageName.length());
if (!extra.startsWith("/")) continue;
for (String category : extra.substring(1).split(":")) {
if (category.equals("-atrace")) {
config.shouldWriteToATrace = false;
continue;
}
if (config.filter.length() > 0) config.filter += ",";
config.filter += category;
}
}
}
return config;
}
@AnyThread
public void onNativeTracingReady() {
mNativeTracingReady.set(true);
// If there already was an active atrace session, we should transfer
// it over to native. If the UI thread was already registered, post
// a task to move the session over as soon as possible. Otherwise
// we'll wait until the UI thread activates.
mTraceTagActive.set(false);
if (mUiThreadReady.get()) {
ThreadUtils.postOnUiThread(() -> { pollConfig(); });
}
}
@AnyThread
public void onUiThreadReady() {
mUiThreadReady.set(true);
if (!ThreadUtils.runningOnUiThread()) {
ThreadUtils.postOnUiThread(() -> { startPolling(); });
return;
}
startPolling();
}
private void startPolling() {
ThreadUtils.assertOnUiThread();
// Since Android R there's no way for an app to be notified of
// atrace activations. To work around this, we poll for the latest
// state whenever the main run loop becomes idle. Since the check
// amounts to one JNI call, the overhead of doing this is
// negligible. See queueIdle().
Looper.myQueue().addIdleHandler(this);
pollConfig();
}
@Override
public final boolean queueIdle() {
pollConfig();
return true;
}
/**
* Instructs Chrome's tracing service to start tracing.
*
* @param categoryFilter Set of trace categories to enable.
*/
private void enableNativeATrace(String categoryFilter) {
assert mNativeTracingReady.get();
TraceEventJni.get().startATrace(categoryFilter);
}
/**
* Stop a previously started tracing session and flush remaining events
* to ATrace (if enabled).
*/
private void disableNativeATrace() {
assert mNativeTracingReady.get();
TraceEventJni.get().stopATrace();
}
/**
* Begins a startup tracing session which will be later taken over by a
* system tracing session.
*
* @param categoryFilter Set of trace categories to enable.
*/
private void setupATraceStartupTrace(String categoryFilter) {
assert mNativeTracingReady.get();
TraceEventJni.get().setupATraceStartupTrace(categoryFilter);
}
public void traceBegin(String name) {
if (!mShouldWriteToSystemTrace) return;
try {
mTraceBeginMethod.invoke(mTraceClass, mTraceTag, name);
} catch (Exception e) {
// No-op.
}
}
public void traceEnd() {
if (!mShouldWriteToSystemTrace) return;
try {
mTraceEndMethod.invoke(mTraceClass, mTraceTag);
} catch (Exception e) {
// No-op.
}
}
public void asyncTraceBegin(String name, int cookie) {
if (!mShouldWriteToSystemTrace) return;
try {
mAsyncTraceBeginMethod.invoke(mTraceClass, mTraceTag, name, cookie);
} catch (Exception e) {
// No-op.
}
}
public void asyncTraceEnd(String name, int cookie) {
if (!mShouldWriteToSystemTrace) return;
try {
mAsyncTraceEndMethod.invoke(mTraceClass, mTraceTag, name, cookie);
} catch (Exception e) {
// No-op.
}
}
}
private static ATrace sATrace;
private static class BasicLooperMonitor implements Printer { private static class BasicLooperMonitor implements Printer {
private static final String LOOPER_TASK_PREFIX = "Looper.dispatch: "; private static final String LOOPER_TASK_PREFIX = "Looper.dispatch: ";
...@@ -56,6 +391,8 @@ public class TraceEvent implements AutoCloseable { ...@@ -56,6 +391,8 @@ public class TraceEvent implements AutoCloseable {
// will filter the event in this case. // will filter the event in this case.
boolean earlyTracingActive = EarlyTraceEvent.enabled(); boolean earlyTracingActive = EarlyTraceEvent.enabled();
if (sEnabled || earlyTracingActive) { if (sEnabled || earlyTracingActive) {
// Note that we don't need to log ATrace events here because the
// framework does that for us (M+).
mCurrentTarget = getTraceEventName(line); mCurrentTarget = getTraceEventName(line);
if (sEnabled) { if (sEnabled) {
TraceEventJni.get().beginToplevel(mCurrentTarget); TraceEventJni.get().beginToplevel(mCurrentTarget);
...@@ -267,13 +604,6 @@ public class TraceEvent implements AutoCloseable { ...@@ -267,13 +604,6 @@ public class TraceEvent implements AutoCloseable {
return scoped(name, null); return scoped(name, null);
} }
/**
* Register an enabled observer, such that java traces are always enabled with native.
*/
public static void registerNativeEnabledObserver() {
TraceEventJni.get().registerEnabledObserver();
}
/** /**
* Notification from native that tracing is enabled/disabled. * Notification from native that tracing is enabled/disabled.
*/ */
...@@ -286,40 +616,48 @@ public class TraceEvent implements AutoCloseable { ...@@ -286,40 +616,48 @@ public class TraceEvent implements AutoCloseable {
sEnabled = enabled; sEnabled = enabled;
// Android M+ systrace logs this on its own. Only log it if not writing to Android // Android M+ systrace logs this on its own. Only log it if not writing to Android
// systrace. // systrace.
if (sATraceEnabled) return; if (sATrace != null && !sATrace.hasActiveSession()) {
ThreadUtils.getUiThreadLooper().setMessageLogging( ThreadUtils.getUiThreadLooper().setMessageLogging(
enabled ? LooperMonitorHolder.sInstance : null); enabled ? LooperMonitorHolder.sInstance : null);
} }
} }
}
/** /**
* May enable early tracing depending on the environment. * May enable early tracing depending on the environment.
* *
* Must be called after the command-line has been read. * @param traceTag If non-zero, start watching for ATrace sessions on the given tag.
* @param readCommandLine If true, also check command line flags to see
* whether tracing should be turned on.
*/ */
public static void maybeEnableEarlyTracing() { public static void maybeEnableEarlyTracing(long traceTag, boolean readCommandLine) {
// Enable early trace events based on command line flags. This is only
// done for Chrome since WebView tracing isn't controlled with command
// line flags.
if (readCommandLine) {
EarlyTraceEvent.maybeEnable(); EarlyTraceEvent.maybeEnable();
if (EarlyTraceEvent.enabled()) { }
if (traceTag != 0) {
sATrace = new ATrace(traceTag);
}
if (EarlyTraceEvent.enabled() && (sATrace == null || !sATrace.hasActiveSession())) {
ThreadUtils.getUiThreadLooper().setMessageLogging(LooperMonitorHolder.sInstance); ThreadUtils.getUiThreadLooper().setMessageLogging(LooperMonitorHolder.sInstance);
} }
} }
/** public static void onNativeTracingReady() {
* Enables or disabled Android systrace path of Chrome tracing. If enabled, all Chrome // Register an enabled observer, such that java traces are always
* traces will be also output to Android systrace. Because of the overhead of Android // enabled with native.
* systrace, this is for WebView only. TraceEventJni.get().registerEnabledObserver();
*/ if (sATrace != null) {
public static void setATraceEnabled(boolean enabled) { sATrace.onNativeTracingReady();
if (sATraceEnabled == enabled) return; }
sATraceEnabled = enabled; }
if (enabled) {
// Calls TraceEvent.setEnabled(true) via // Called by ThreadUtils.
// TraceLog::EnabledStateObserver::OnTraceLogEnabled static void onUiThreadReady() {
TraceEventJni.get().startATrace(); if (sATrace != null) {
} else { sATrace.onUiThreadReady();
// Calls TraceEvent.setEnabled(false) via
// TraceLog::EnabledStateObserver::OnTraceLogDisabled
TraceEventJni.get().stopATrace();
} }
} }
...@@ -356,7 +694,11 @@ public class TraceEvent implements AutoCloseable { ...@@ -356,7 +694,11 @@ public class TraceEvent implements AutoCloseable {
*/ */
public static void startAsync(String name, long id) { public static void startAsync(String name, long id) {
EarlyTraceEvent.startAsync(name, id); EarlyTraceEvent.startAsync(name, id);
if (sEnabled) TraceEventJni.get().startAsync(name, id); if (sEnabled) {
TraceEventJni.get().startAsync(name, id);
} else if (sATrace != null) {
sATrace.asyncTraceBegin(name, (int) id);
}
} }
/** /**
...@@ -366,7 +708,11 @@ public class TraceEvent implements AutoCloseable { ...@@ -366,7 +708,11 @@ public class TraceEvent implements AutoCloseable {
*/ */
public static void finishAsync(String name, long id) { public static void finishAsync(String name, long id) {
EarlyTraceEvent.finishAsync(name, id); EarlyTraceEvent.finishAsync(name, id);
if (sEnabled) TraceEventJni.get().finishAsync(name, id); if (sEnabled) {
TraceEventJni.get().finishAsync(name, id);
} else if (sATrace != null) {
sATrace.asyncTraceEnd(name, (int) id);
}
} }
/** /**
...@@ -384,7 +730,11 @@ public class TraceEvent implements AutoCloseable { ...@@ -384,7 +730,11 @@ public class TraceEvent implements AutoCloseable {
*/ */
public static void begin(String name, String arg) { public static void begin(String name, String arg) {
EarlyTraceEvent.begin(name, false /*isToplevel*/); EarlyTraceEvent.begin(name, false /*isToplevel*/);
if (sEnabled) TraceEventJni.get().begin(name, arg); if (sEnabled) {
TraceEventJni.get().begin(name, arg);
} else if (sATrace != null) {
sATrace.traceBegin(name);
}
} }
/** /**
...@@ -402,14 +752,19 @@ public class TraceEvent implements AutoCloseable { ...@@ -402,14 +752,19 @@ public class TraceEvent implements AutoCloseable {
*/ */
public static void end(String name, String arg) { public static void end(String name, String arg) {
EarlyTraceEvent.end(name, false /*isToplevel*/); EarlyTraceEvent.end(name, false /*isToplevel*/);
if (sEnabled) TraceEventJni.get().end(name, arg); if (sEnabled) {
TraceEventJni.get().end(name, arg);
} else if (sATrace != null) {
sATrace.traceEnd();
}
} }
@NativeMethods @NativeMethods
interface Natives { interface Natives {
void registerEnabledObserver(); void registerEnabledObserver();
void startATrace(); void startATrace(String categoryFilter);
void stopATrace(); void stopATrace();
void setupATraceStartupTrace(String categoryFilter);
void instant(String name, String arg); void instant(String name, String arg);
void begin(String name, String arg); void begin(String name, String arg);
void end(String name, String arg); void end(String name, String arg);
......
...@@ -633,7 +633,7 @@ public class LibraryLoader { ...@@ -633,7 +633,7 @@ public class LibraryLoader {
UmaRecorderHolder.onLibraryLoaded(); UmaRecorderHolder.onLibraryLoaded();
// From now on, keep tracing in sync with native. // From now on, keep tracing in sync with native.
TraceEvent.registerNativeEnabledObserver(); TraceEvent.onNativeTracingReady();
// From this point on, native code is ready to use, but non-MainDex JNI may not yet have // From this point on, native code is ready to use, but non-MainDex JNI may not yet have
// been registered. Check isInitialized() to be sure that initialization is fully complete. // been registered. Check isInitialized() to be sure that initialization is fully complete.
......
...@@ -49,22 +49,39 @@ static void JNI_TraceEvent_RegisterEnabledObserver(JNIEnv* env) { ...@@ -49,22 +49,39 @@ static void JNI_TraceEvent_RegisterEnabledObserver(JNIEnv* env) {
std::make_unique<TraceEnabledObserver>()); std::make_unique<TraceEnabledObserver>());
} }
static void JNI_TraceEvent_StartATrace(JNIEnv* env) { static void JNI_TraceEvent_StartATrace(
base::trace_event::TraceLog::GetInstance()->StartATrace(); JNIEnv* env,
const JavaParamRef<jstring>& category_filter) {
std::string category_filter_utf8 =
ConvertJavaStringToUTF8(env, category_filter);
base::trace_event::TraceLog::GetInstance()->StartATrace(category_filter_utf8);
} }
static void JNI_TraceEvent_StopATrace(JNIEnv* env) { static void JNI_TraceEvent_StopATrace(JNIEnv* env) {
base::trace_event::TraceLog::GetInstance()->StopATrace(); base::trace_event::TraceLog::GetInstance()->StopATrace();
} }
static void JNI_TraceEvent_SetupATraceStartupTrace(
JNIEnv* env,
const JavaParamRef<jstring>& category_filter) {
std::string category_filter_utf8 =
ConvertJavaStringToUTF8(env, category_filter);
base::trace_event::TraceLog::GetInstance()->SetupATraceStartupTrace(
category_filter_utf8);
}
#else // BUILDFLAG(ENABLE_BASE_TRACING) #else // BUILDFLAG(ENABLE_BASE_TRACING)
// Empty implementations when TraceLog isn't available. // Empty implementations when TraceLog isn't available.
static void JNI_TraceEvent_RegisterEnabledObserver(JNIEnv* env) { static void JNI_TraceEvent_RegisterEnabledObserver(JNIEnv* env) {
base::android::Java_TraceEvent_setEnabled(env, false); base::android::Java_TraceEvent_setEnabled(env, false);
} }
static void JNI_TraceEvent_StartATrace(JNIEnv* env) {} static void JNI_TraceEvent_StartATrace(JNIEnv* env,
base::android::JavaParamRef<jstring>&) {}
static void JNI_TraceEvent_StopATrace(JNIEnv* env) {} static void JNI_TraceEvent_StopATrace(JNIEnv* env) {}
static void JNI_TraceEvent_SetupATraceStartupTrace(
JNIEnv* env,
base::android::JavaParamRef<jstring>&) {}
#endif // BUILDFLAG(ENABLE_BASE_TRACING) #endif // BUILDFLAG(ENABLE_BASE_TRACING)
......
...@@ -34,7 +34,10 @@ void WriteToATrace(int fd, const char* buffer, size_t size) { ...@@ -34,7 +34,10 @@ void WriteToATrace(int fd, const char* buffer, size_t size) {
break; break;
total_written += written; total_written += written;
} }
if (total_written < size) { // Tracing might have been disabled before we were notified about it, which
// triggers EBADF. Since enabling and disabling atrace is racy, ignore the
// error in that case to avoid logging an error for every trace event.
if (total_written < size && errno != EBADF) {
PLOG(WARNING) << "Failed to write buffer '" << std::string(buffer, size) PLOG(WARNING) << "Failed to write buffer '" << std::string(buffer, size)
<< "' to " << kATraceMarkerFile; << "' to " << kATraceMarkerFile;
} }
...@@ -73,20 +76,6 @@ void WriteEvent(char phase, ...@@ -73,20 +76,6 @@ void WriteEvent(char phase,
WriteToATrace(g_atrace_fd, out.c_str(), out.size()); WriteToATrace(g_atrace_fd, out.c_str(), out.size());
} }
void NoOpOutputCallback(WaitableEvent* complete_event,
const scoped_refptr<RefCountedString>&,
bool has_more_events) {
if (!has_more_events)
complete_event->Signal();
}
void EndChromeTracing(TraceLog* trace_log,
WaitableEvent* complete_event) {
trace_log->SetDisabled();
// Delete the buffered trace events as they have been sent to atrace.
trace_log->Flush(BindRepeating(&NoOpOutputCallback, complete_event));
}
} // namespace } // namespace
// These functions support Android systrace.py when 'webview' category is // These functions support Android systrace.py when 'webview' category is
...@@ -99,7 +88,7 @@ void EndChromeTracing(TraceLog* trace_log, ...@@ -99,7 +88,7 @@ void EndChromeTracing(TraceLog* trace_log,
// StartATrace, StopATrace and SendToATrace, and perhaps send Java traces // StartATrace, StopATrace and SendToATrace, and perhaps send Java traces
// directly to atrace in trace_event_binding.cc. // directly to atrace in trace_event_binding.cc.
void TraceLog::StartATrace() { void TraceLog::StartATrace(const std::string& category_filter) {
if (g_atrace_fd != -1) if (g_atrace_fd != -1)
return; return;
...@@ -108,28 +97,17 @@ void TraceLog::StartATrace() { ...@@ -108,28 +97,17 @@ void TraceLog::StartATrace() {
PLOG(WARNING) << "Couldn't open " << kATraceMarkerFile; PLOG(WARNING) << "Couldn't open " << kATraceMarkerFile;
return; return;
} }
TraceConfig trace_config; TraceConfig trace_config(category_filter);
trace_config.SetTraceRecordMode(RECORD_CONTINUOUSLY); trace_config.SetTraceRecordMode(RECORD_CONTINUOUSLY);
SetEnabled(trace_config, TraceLog::RECORDING_MODE); SetEnabled(trace_config, TraceLog::RECORDING_MODE);
} }
void TraceLog::StopATrace() { void TraceLog::StopATrace() {
if (g_atrace_fd == -1) if (g_atrace_fd != -1) {
return;
close(g_atrace_fd); close(g_atrace_fd);
g_atrace_fd = -1; g_atrace_fd = -1;
}
// TraceLog::Flush() requires the current thread to have a message loop, but SetDisabled();
// this thread called from Java may not have one, so flush in another thread.
Thread end_chrome_tracing_thread("end_chrome_tracing");
WaitableEvent complete_event(WaitableEvent::ResetPolicy::AUTOMATIC,
WaitableEvent::InitialState::NOT_SIGNALED);
end_chrome_tracing_thread.Start();
end_chrome_tracing_thread.task_runner()->PostTask(
FROM_HERE, base::BindOnce(&EndChromeTracing, Unretained(this),
Unretained(&complete_event)));
complete_event.Wait();
} }
void TraceEvent::SendToATrace() { void TraceEvent::SendToATrace() {
...@@ -198,5 +176,13 @@ void TraceLog::AddClockSyncMetadataEvent() { ...@@ -198,5 +176,13 @@ void TraceLog::AddClockSyncMetadataEvent() {
close(atrace_fd); close(atrace_fd);
} }
void TraceLog::SetupATraceStartupTrace(const std::string& category_filter) {
atrace_startup_config_ = TraceConfig(category_filter);
}
Optional<TraceConfig> TraceLog::TakeATraceStartupConfig() {
return std::move(atrace_startup_config_);
}
} // namespace trace_event } // namespace trace_event
} // namespace base } // namespace base
...@@ -12,11 +12,20 @@ namespace trace_event { ...@@ -12,11 +12,20 @@ namespace trace_event {
TEST(TraceEventAndroidTest, WriteToATrace) { TEST(TraceEventAndroidTest, WriteToATrace) {
// Just a smoke test to ensure no crash. // Just a smoke test to ensure no crash.
TraceLog* trace_log = TraceLog::GetInstance(); TraceLog* trace_log = TraceLog::GetInstance();
trace_log->StartATrace(); trace_log->StartATrace("test");
TRACE_EVENT0("test", "test-event"); TRACE_EVENT0("test", "test-event");
trace_log->StopATrace(); trace_log->StopATrace();
trace_log->AddClockSyncMetadataEvent(); trace_log->AddClockSyncMetadataEvent();
} }
TEST(TraceEventAndroidTest, ATraceStartup) {
TraceLog* trace_log = TraceLog::GetInstance();
EXPECT_FALSE(trace_log->TakeATraceStartupConfig());
trace_log->SetupATraceStartupTrace("cat");
auto config = trace_log->TakeATraceStartupConfig();
EXPECT_TRUE(config);
EXPECT_TRUE(config->IsCategoryGroupEnabled("cat"));
}
} // namespace trace_event } // namespace trace_event
} // namespace base } // namespace base
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include "base/gtest_prod_util.h" #include "base/gtest_prod_util.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/scoped_refptr.h" #include "base/memory/scoped_refptr.h"
#include "base/optional.h"
#include "base/single_thread_task_runner.h" #include "base/single_thread_task_runner.h"
#include "base/time/time_override.h" #include "base/time/time_override.h"
#include "base/trace_event/category_registry.h" #include "base/trace_event/category_registry.h"
...@@ -106,10 +107,12 @@ class BASE_EXPORT TraceLog : public MemoryDumpProvider { ...@@ -106,10 +107,12 @@ class BASE_EXPORT TraceLog : public MemoryDumpProvider {
int GetNumTracesRecorded(); int GetNumTracesRecorded();
#if defined(OS_ANDROID) #if defined(OS_ANDROID)
void StartATrace(); void StartATrace(const std::string& category_filter);
void StopATrace(); void StopATrace();
void AddClockSyncMetadataEvent(); void AddClockSyncMetadataEvent();
#endif void SetupATraceStartupTrace(const std::string& category_filter);
Optional<TraceConfig> TakeATraceStartupConfig();
#endif // defined(OS_ANDROID)
// Enabled state listeners give a callback when tracing is enabled or // Enabled state listeners give a callback when tracing is enabled or
// disabled. This can be used to tie into other library's tracing systems // disabled. This can be used to tie into other library's tracing systems
...@@ -576,6 +579,10 @@ class BASE_EXPORT TraceLog : public MemoryDumpProvider { ...@@ -576,6 +579,10 @@ class BASE_EXPORT TraceLog : public MemoryDumpProvider {
FilterFactoryForTesting filter_factory_for_testing_; FilterFactoryForTesting filter_factory_for_testing_;
#if defined(OS_ANDROID)
base::Optional<TraceConfig> atrace_startup_config_;
#endif
DISALLOW_COPY_AND_ASSIGN(TraceLog); DISALLOW_COPY_AND_ASSIGN(TraceLog);
}; };
......
...@@ -7,6 +7,7 @@ package org.chromium.chrome.browser; ...@@ -7,6 +7,7 @@ package org.chromium.chrome.browser;
import android.app.Application; import android.app.Application;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.os.Bundle; import android.os.Bundle;
...@@ -92,8 +93,14 @@ public class ChromeApplication extends Application { ...@@ -92,8 +93,14 @@ public class ChromeApplication extends Application {
CommandLineInitUtil.initCommandLine( CommandLineInitUtil.initCommandLine(
COMMAND_LINE_FILE, ChromeApplication::shouldUseDebugFlags); COMMAND_LINE_FILE, ChromeApplication::shouldUseDebugFlags);
// Enable ATrace on debug OS or app builds.
int applicationFlags = context.getApplicationInfo().flags;
boolean isAppDebuggable = (applicationFlags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
boolean isOsDebuggable = BuildInfo.isDebugAndroid();
// Requires command-line flags. // Requires command-line flags.
TraceEvent.maybeEnableEarlyTracing(); TraceEvent.maybeEnableEarlyTracing(
(isAppDebuggable || isOsDebuggable) ? TraceEvent.ATRACE_TAG_APP : 0,
/*readCommandLine=*/true);
TraceEvent.begin("ChromeApplication.attachBaseContext"); TraceEvent.begin("ChromeApplication.attachBaseContext");
// Register for activity lifecycle callbacks. Must be done before any activities are // Register for activity lifecycle callbacks. Must be done before any activities are
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include "base/logging.h" #include "base/logging.h"
#include "base/memory/singleton.h" #include "base/memory/singleton.h"
#include "base/strings/string_number_conversions.h" #include "base/strings/string_number_conversions.h"
#include "base/trace_event/trace_log.h"
#include "base/values.h" #include "base/values.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "components/tracing/common/tracing_switches.h" #include "components/tracing/common/tracing_switches.h"
...@@ -95,6 +96,10 @@ TraceStartupConfig::TraceStartupConfig() { ...@@ -95,6 +96,10 @@ TraceStartupConfig::TraceStartupConfig() {
DCHECK(!IsTracingStartupForDuration()); DCHECK(!IsTracingStartupForDuration());
DCHECK_EQ(SessionOwner::kBackgroundTracing, session_owner_); DCHECK_EQ(SessionOwner::kBackgroundTracing, session_owner_);
CHECK(!ShouldTraceToResultFile()); CHECK(!ShouldTraceToResultFile());
} else if (EnableFromATrace()) {
DCHECK(IsEnabled());
DCHECK_EQ(SessionOwner::kSystemTracing, session_owner_);
CHECK(!ShouldTraceToResultFile());
} }
} }
...@@ -196,6 +201,24 @@ bool TraceStartupConfig::EnableFromCommandLine() { ...@@ -196,6 +201,24 @@ bool TraceStartupConfig::EnableFromCommandLine() {
return true; return true;
} }
bool TraceStartupConfig::EnableFromATrace() {
#if defined(OS_ANDROID)
auto atrace_config =
base::trace_event::TraceLog::GetInstance()->TakeATraceStartupConfig();
if (!atrace_config)
return false;
trace_config_ = *atrace_config;
is_enabled_ = true;
// We only support ATrace-initiated startup tracing together with the system
// service, because DevTools and background tracing generally use Chrome
// command line flags to control startup tracing instead of ATrace.
session_owner_ = SessionOwner::kSystemTracing;
return true;
#else // defined(OS_ANDROID)
return false;
#endif // !defined(OS_ANDROID)
}
bool TraceStartupConfig::EnableFromConfigFile() { bool TraceStartupConfig::EnableFromConfigFile() {
#if defined(OS_ANDROID) #if defined(OS_ANDROID)
base::FilePath trace_config_file(kAndroidTraceConfigFile); base::FilePath trace_config_file(kAndroidTraceConfigFile);
......
...@@ -157,6 +157,7 @@ class TRACING_EXPORT TraceStartupConfig { ...@@ -157,6 +157,7 @@ class TRACING_EXPORT TraceStartupConfig {
bool EnableFromCommandLine(); bool EnableFromCommandLine();
bool EnableFromConfigFile(); bool EnableFromConfigFile();
bool EnableFromBackgroundTracing(); bool EnableFromBackgroundTracing();
bool EnableFromATrace();
bool ParseTraceConfigFileContent(const std::string& content); bool ParseTraceConfigFileContent(const std::string& content);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment