webview: update constructor performance metrics.
Add new metrics to properly distinguish the *three* distinct cases we can encounter while creating a WebView instance. The existing metrics relied on "has the global Chromium initialization already run" to distinguish "cold" and "warm" cases, and these cases were being interpreted as "whether this is the first instance of WebView or not". There are actually three cases: it's possible for the global Chromium initialization to have already run *before* the first WebView was constructed. The existing metrics categorized this as "cold", but the first instance still takes longer to construct than subsequent instances even in this case, making it hard to interpret the metrics correctly. Instead, define three separate histograms for the three cases. We continue to log the old histograms as well for now to allow for cross-version comparisons. The descriptions of the existing metrics are also updated to clarify their limitations and suggest consulting the new metrics instead. Bug: b:373630656 Change-Id: Id927276f2bb6327bec3764e1fe6dd6abb2ffb1ef Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6108730 Reviewed-by: Mark Pearson <mpearson@chromium.org> Commit-Queue: Richard (Torne) Coles <torne@chromium.org> Reviewed-by: Nate Fischer <ntfschr@chromium.org> Auto-Submit: Richard (Torne) Coles <torne@chromium.org> Reviewed-by: Oksana Zhuravlova <oksamyt@chromium.org> Cr-Commit-Position: refs/heads/main@{#1420668}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
d803df3d7a
commit
dcd4117a44
android_webview/glue/java/src/com/android/webview/chromium
base/android
tools/metrics/histograms/metadata/android
@ -88,14 +88,15 @@ import java.lang.reflect.Field;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* This class is the delegate to which WebViewProxy forwards all API calls.
|
||||
*
|
||||
* Most of the actual functionality is implemented by AwContents (or WebContents within
|
||||
* it). This class also contains WebView-specific APIs that require the creation of other
|
||||
* adapters (otherwise org.chromium.content would depend on the webview.chromium package)
|
||||
* and a small set of no-op deprecated APIs.
|
||||
* <p>Most of the actual functionality is implemented by AwContents (or WebContents within it). This
|
||||
* class also contains WebView-specific APIs that require the creation of other adapters (otherwise
|
||||
* org.chromium.content would depend on the webview.chromium package) and a small set of no-op
|
||||
* deprecated APIs.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
@Lifetime.WebView
|
||||
@ -136,6 +137,8 @@ class WebViewChromium
|
||||
sRecordWholeDocumentEnabledByApi = true;
|
||||
}
|
||||
|
||||
private static final AtomicBoolean sFirstWebViewInstanceCreated = new AtomicBoolean();
|
||||
|
||||
// Used to record the UMA histogram WebView.WebViewApiCall. Since these values are persisted to
|
||||
// logs, they should never be renumbered or reused.
|
||||
// LINT.IfChange(ApiCall)
|
||||
@ -704,7 +707,8 @@ class WebViewChromium
|
||||
public void init(
|
||||
final Map<String, Object> javaScriptInterfaces, final boolean privateBrowsing) {
|
||||
long startTime = SystemClock.uptimeMillis();
|
||||
boolean isFirstWebViewInit = !mFactory.isChromiumInitialized();
|
||||
boolean wasChromiumAlreadyInitialized = mFactory.isChromiumInitialized();
|
||||
boolean isFirstWebViewInstance = !sFirstWebViewInstanceCreated.getAndSet(true);
|
||||
try (ScopedSysTraceEvent e1 = ScopedSysTraceEvent.scoped("WebViewChromium.init")) {
|
||||
if (privateBrowsing) {
|
||||
mFactory.startYourEngines(true);
|
||||
@ -750,6 +754,10 @@ class WebViewChromium
|
||||
mFactory.startYourEngines(true);
|
||||
}
|
||||
|
||||
// At this point it is guaranteed that global Chromium init has completed on the UI
|
||||
// thread, as all paths have called startYourEngines. However, this function itself is
|
||||
// *not* necessarily running on the UI thread due to the pre-JBMR2 case above.
|
||||
|
||||
final boolean isAccessFromFileUrlsGrantedByDefault =
|
||||
mAppTargetSdkVersion < Build.VERSION_CODES.JELLY_BEAN;
|
||||
final boolean areLegacyQuirksEnabled =
|
||||
@ -794,6 +802,9 @@ class WebViewChromium
|
||||
|
||||
mSharedWebViewChromium.init(mContentsClientAdapter);
|
||||
|
||||
// In the normal case where we are currently on the UI thread, this will run initForReal
|
||||
// synchronously. For pre-JBMR2 apps we might not be on the UI thread, in which case it
|
||||
// will be posted and we do not wait for it.
|
||||
mFactory.addTask(
|
||||
new Runnable() {
|
||||
@Override
|
||||
@ -811,31 +822,42 @@ class WebViewChromium
|
||||
});
|
||||
}
|
||||
|
||||
// If initialization hasn't been deferred, record a startup time histogram entry
|
||||
// and trace event(s).
|
||||
if (mFactory.isChromiumInitialized()) {
|
||||
if (isFirstWebViewInit) {
|
||||
long elapsedTime = SystemClock.uptimeMillis() - startTime;
|
||||
if (isFirstWebViewInstance) {
|
||||
if (wasChromiumAlreadyInitialized) {
|
||||
// This is the first WebView created, but global Chromium initialization happened
|
||||
// before the constructor was called.
|
||||
RecordHistogram.recordTimesHistogram(
|
||||
"Android.WebView.Startup.CreationTime.Stage2.ProviderInit.Cold",
|
||||
SystemClock.uptimeMillis() - startTime);
|
||||
|
||||
TraceEvent.webViewStartupTotalFactoryInit(
|
||||
mFactory.getInitInfo().mTotalFactoryInitStartTime,
|
||||
mFactory.getInitInfo().mTotalFactoryInitDuration);
|
||||
|
||||
TraceEvent.webViewStartupStage1(
|
||||
mFactory.getInitInfo().mStartTime, mFactory.getInitInfo().mDuration);
|
||||
|
||||
TraceEvent.webViewStartupStage2(
|
||||
startTime, SystemClock.uptimeMillis() - startTime, true);
|
||||
"Android.WebView.Startup.CreationTime.FirstInstanceAfterGlobalStartup",
|
||||
elapsedTime);
|
||||
TraceEvent.webViewStartupFirstInstance(startTime, elapsedTime, false);
|
||||
} else {
|
||||
// This is the first WebView created, and we blocked running global Chromium
|
||||
// initialization during the constructor.
|
||||
RecordHistogram.recordTimesHistogram(
|
||||
"Android.WebView.Startup.CreationTime.Stage2.ProviderInit.Warm",
|
||||
SystemClock.uptimeMillis() - startTime);
|
||||
|
||||
TraceEvent.webViewStartupStage2(
|
||||
startTime, SystemClock.uptimeMillis() - startTime, false);
|
||||
"Android.WebView.Startup.CreationTime.FirstInstanceWithGlobalStartup",
|
||||
elapsedTime);
|
||||
TraceEvent.webViewStartupFirstInstance(startTime, elapsedTime, true);
|
||||
}
|
||||
} else {
|
||||
// This is not the first WebView created; global Chromium initialization must have
|
||||
// happened beforehand.
|
||||
RecordHistogram.recordTimesHistogram(
|
||||
"Android.WebView.Startup.CreationTime.NotFirstInstance", elapsedTime);
|
||||
TraceEvent.webViewStartupNotFirstInstance(startTime, elapsedTime);
|
||||
}
|
||||
|
||||
// Record "legacy" metrics. These have suboptimal definitions because they don't allow for
|
||||
// the case where global Chromium initialization happened before the first WebView instance
|
||||
// was constructed, and just use "cold/warm" to refer to whether global Chromium
|
||||
// initialization had to be run during the constructor or not, giving the "cold" case a
|
||||
// bimodal distribution.
|
||||
if (!wasChromiumAlreadyInitialized) {
|
||||
RecordHistogram.recordTimesHistogram(
|
||||
"Android.WebView.Startup.CreationTime.Stage2.ProviderInit.Cold", elapsedTime);
|
||||
} else {
|
||||
RecordHistogram.recordTimesHistogram(
|
||||
"Android.WebView.Startup.CreationTime.Stage2.ProviderInit.Warm", elapsedTime);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -433,6 +433,13 @@ public class WebViewChromiumAwInit {
|
||||
totalTimeTaken,
|
||||
/* callSite= */ callSite,
|
||||
/* fromUIThread= */ triggeredFromUIThread);
|
||||
// Also create the trace events for the earlier WebViewChromiumFactoryProvider init, which
|
||||
// happens before tracing is ready.
|
||||
TraceEvent.webViewStartupTotalFactoryInit(
|
||||
mFactory.getInitInfo().mTotalFactoryInitStartTime,
|
||||
mFactory.getInitInfo().mTotalFactoryInitDuration);
|
||||
TraceEvent.webViewStartupStage1(
|
||||
mFactory.getInitInfo().mStartTime, mFactory.getInitInfo().mDuration);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -442,15 +442,27 @@ public class TraceEvent implements AutoCloseable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Records 'WebView.Startup.CreationTime.Stage2.ProviderInit.Warm' and
|
||||
* 'WebView.Startup.CreationTime.Stage2.ProviderInit.Cold' events depending on the value of
|
||||
* `isColdStartup` with the 'android_webview.timeline' category starting at `startTimeMs` with
|
||||
* the duration of `durationMs`.
|
||||
* Records 'WebView.Startup.CreationTime.FirstInstanceWithGlobalStartup' or
|
||||
* 'WebView.Startup.CreationTime.FirstInstanceWithoutGlobalStartup' events depending on the
|
||||
* value of `includedGlobalStartup` with the 'android_webview.timeline' category starting at
|
||||
* `startTimeMs` with the duration of `durationMs`.
|
||||
*/
|
||||
public static void webViewStartupStage2(
|
||||
long startTimeMs, long durationMs, boolean isColdStartup) {
|
||||
public static void webViewStartupFirstInstance(
|
||||
long startTimeMs, long durationMs, boolean includedGlobalStartup) {
|
||||
if (sEnabled) {
|
||||
TraceEventJni.get().webViewStartupStage2(startTimeMs, durationMs, isColdStartup);
|
||||
TraceEventJni.get()
|
||||
.webViewStartupFirstInstance(startTimeMs, durationMs, includedGlobalStartup);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Records a 'WebView.Startup.CreationTime.NotFirstInstance' event with the
|
||||
* 'android_webview.timeline' category starting at `startTimeMs` with the duration of
|
||||
* `durationMs`.
|
||||
*/
|
||||
public static void webViewStartupNotFirstInstance(long startTimeMs, long durationMs) {
|
||||
if (sEnabled) {
|
||||
TraceEventJni.get().webViewStartupNotFirstInstance(startTimeMs, durationMs);
|
||||
}
|
||||
}
|
||||
|
||||
@ -672,7 +684,10 @@ public class TraceEvent implements AutoCloseable {
|
||||
|
||||
void webViewStartupStage1(long startTimeMs, long durationMs);
|
||||
|
||||
void webViewStartupStage2(long startTimeMs, long durationMs, boolean isColdStartup);
|
||||
void webViewStartupFirstInstance(
|
||||
long startTimeMs, long durationMs, boolean includedGlobalStartup);
|
||||
|
||||
void webViewStartupNotFirstInstance(long startTimeMs, long durationMs);
|
||||
|
||||
void webViewStartupStartChromiumLocked(
|
||||
long startTimeMs, long durationMs, int callSite, boolean fromUIThread);
|
||||
|
@ -122,13 +122,25 @@ public class TraceEventTest {
|
||||
@Test
|
||||
@SmallTest
|
||||
@Feature({"Android-AppBase"})
|
||||
public void testWebViewStartupStage2() {
|
||||
public void testWebViewStartupFirstInstance() {
|
||||
TraceEvent.setEnabled(true);
|
||||
long startTime = 10;
|
||||
long duration = 50;
|
||||
boolean isCold = true;
|
||||
TraceEvent.webViewStartupStage2(startTime, duration, isCold);
|
||||
verify(mNativeMock).webViewStartupStage2(startTime, duration, isCold);
|
||||
boolean includedGlobalStartup = true;
|
||||
TraceEvent.webViewStartupFirstInstance(startTime, duration, includedGlobalStartup);
|
||||
verify(mNativeMock).webViewStartupFirstInstance(startTime, duration, includedGlobalStartup);
|
||||
TraceEvent.setEnabled(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SmallTest
|
||||
@Feature({"Android-AppBase"})
|
||||
public void testWebViewStartupNotFirstInstance() {
|
||||
TraceEvent.setEnabled(true);
|
||||
long startTime = 10;
|
||||
long duration = 50;
|
||||
TraceEvent.webViewStartupNotFirstInstance(startTime, duration);
|
||||
verify(mNativeMock).webViewStartupNotFirstInstance(startTime, duration);
|
||||
TraceEvent.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
@ -306,21 +306,24 @@ static void JNI_TraceEvent_WebViewStartupStage1(JNIEnv* env,
|
||||
#endif // BUILDFLAG(ENABLE_BASE_TRACING)
|
||||
}
|
||||
|
||||
static void JNI_TraceEvent_WebViewStartupStage2(JNIEnv* env,
|
||||
jlong start_time_ms,
|
||||
jlong duration_ms,
|
||||
jboolean is_cold_startup) {
|
||||
static void JNI_TraceEvent_WebViewStartupFirstInstance(
|
||||
JNIEnv* env,
|
||||
jlong start_time_ms,
|
||||
jlong duration_ms,
|
||||
jboolean included_global_startup) {
|
||||
#if BUILDFLAG(ENABLE_BASE_TRACING)
|
||||
auto t = perfetto::Track::ThreadScoped(
|
||||
reinterpret_cast<void*>(trace_event::GetNextGlobalTraceId()));
|
||||
if (is_cold_startup) {
|
||||
TRACE_EVENT_BEGIN("android_webview.timeline",
|
||||
"WebView.Startup.CreationTime.Stage2.ProviderInit.Cold",
|
||||
t, TimeTicks() + Milliseconds(start_time_ms));
|
||||
if (included_global_startup) {
|
||||
TRACE_EVENT_BEGIN(
|
||||
"android_webview.timeline",
|
||||
"WebView.Startup.CreationTime.FirstInstanceWithGlobalStartup", t,
|
||||
TimeTicks() + Milliseconds(start_time_ms));
|
||||
} else {
|
||||
TRACE_EVENT_BEGIN("android_webview.timeline",
|
||||
"WebView.Startup.CreationTime.Stage2.ProviderInit.Warm",
|
||||
t, TimeTicks() + Milliseconds(start_time_ms));
|
||||
TRACE_EVENT_BEGIN(
|
||||
"android_webview.timeline",
|
||||
"WebView.Startup.CreationTime.FirstInstanceWithoutGlobalStartup", t,
|
||||
TimeTicks() + Milliseconds(start_time_ms));
|
||||
}
|
||||
|
||||
TRACE_EVENT_END("android_webview.timeline", t,
|
||||
@ -328,6 +331,20 @@ static void JNI_TraceEvent_WebViewStartupStage2(JNIEnv* env,
|
||||
#endif // BUILDFLAG(ENABLE_BASE_TRACING)
|
||||
}
|
||||
|
||||
static void JNI_TraceEvent_WebViewStartupNotFirstInstance(JNIEnv* env,
|
||||
jlong start_time_ms,
|
||||
jlong duration_ms) {
|
||||
#if BUILDFLAG(ENABLE_BASE_TRACING)
|
||||
auto t = perfetto::Track::ThreadScoped(
|
||||
reinterpret_cast<void*>(trace_event::GetNextGlobalTraceId()));
|
||||
TRACE_EVENT_BEGIN("android_webview.timeline",
|
||||
"WebView.Startup.CreationTime.NotFirstInstance", t,
|
||||
TimeTicks() + Milliseconds(start_time_ms));
|
||||
TRACE_EVENT_END("android_webview.timeline", t,
|
||||
TimeTicks() + Milliseconds(start_time_ms + duration_ms));
|
||||
#endif // BUILDFLAG(ENABLE_BASE_TRACING)
|
||||
}
|
||||
|
||||
static void JNI_TraceEvent_WebViewStartupStartChromiumLocked(
|
||||
JNIEnv* env,
|
||||
jlong start_time_ms,
|
||||
|
@ -7437,6 +7437,41 @@ chromium-metrics-reviews@google.com.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram
|
||||
name="Android.WebView.Startup.CreationTime.FirstInstanceAfterGlobalStartup"
|
||||
units="ms" expires_after="2025-12-31">
|
||||
<owner>torne@chromium.org</owner>
|
||||
<owner>src/android_webview/OWNERS</owner>
|
||||
<summary>
|
||||
How long it takes to initialize the first WebViewChromium instance in the
|
||||
case where we have already finished the process-global browser startup:
|
||||
("Android.WebView.Startup.CreationTime.StartChromiumLocked") was
|
||||
already complete and did not need to be performed during the instance
|
||||
initialization.
|
||||
|
||||
This is recorded separately as it is significantly faster than the
|
||||
"FirstInstanceWithGlobalStartup" case, but slower than the
|
||||
"NotFirstInstance" case.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram
|
||||
name="Android.WebView.Startup.CreationTime.FirstInstanceWithGlobalStartup"
|
||||
units="ms" expires_after="2025-12-31">
|
||||
<owner>torne@chromium.org</owner>
|
||||
<owner>src/android_webview/OWNERS</owner>
|
||||
<summary>
|
||||
How long it takes to initialize the first WebViewChromium instance in the
|
||||
case where we had to also do the process-global browser startup:
|
||||
("Android.WebView.Startup.CreationTime.StartChromiumLocked") had
|
||||
not yet run and was performed during the instance initialization.
|
||||
|
||||
This is recorded separately as it is significantly slower than the
|
||||
"FirstInstanceAfterGlobalStartup" and "NotFirstInstance"
|
||||
cases.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram name="Android.WebView.Startup.CreationTime.InitReason"
|
||||
enum="WebViewStartupCallSite" expires_after="2025-12-31">
|
||||
<owner>torne@chromium.org</owner>
|
||||
@ -7450,13 +7485,41 @@ chromium-metrics-reviews@google.com.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram name="Android.WebView.Startup.CreationTime.NotFirstInstance"
|
||||
units="ms" expires_after="2025-12-31">
|
||||
<owner>torne@chromium.org</owner>
|
||||
<owner>src/android_webview/OWNERS</owner>
|
||||
<summary>
|
||||
How long it takes to initialize a subsequent WebViewChromium instance. This
|
||||
will be recorded once for each instance created, other than the first to be
|
||||
created in the current process.
|
||||
|
||||
This is recorded separately as it is significantly faster than the
|
||||
"FirstInstanceWithGlobalStartup" and
|
||||
"FirstInstanceAfterGlobalStartup" cases.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram name="Android.WebView.Startup.CreationTime.Stage1.FactoryInit"
|
||||
units="ms" expires_after="2025-12-31">
|
||||
<owner>torne@chromium.org</owner>
|
||||
<owner>src/android_webview/OWNERS</owner>
|
||||
<summary>
|
||||
How long it takes to initialize a WebViewChromiumFactoryProvider. This is
|
||||
the first major phase of the WebViewChromium construction.
|
||||
How long it takes to run WebViewChromiumFactoryProvider.initialize(). This
|
||||
does not count the time spent beforehand in WebViewFactory in the Android
|
||||
framework, which first has to load the WebView code and do some basic setup
|
||||
before initialize() can be called.
|
||||
|
||||
This will be recorded once per host process since the initialization is
|
||||
process-global.
|
||||
|
||||
The "Android.WebView.Startup.CreationTime.TotalFactoryInitTime"
|
||||
histogram measures the entire duration from when
|
||||
WebViewFactory.getProvider() is first called until the
|
||||
WebViewChromiumFactoryProvider is fully initialized and should be preferred
|
||||
for most purposes, but is only recorded on Android S+ devices, so this older
|
||||
metric is retained for now to allow "fair" comparisons with
|
||||
Android R and earlier.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
@ -7465,10 +7528,23 @@ chromium-metrics-reviews@google.com.
|
||||
<owner>torne@chromium.org</owner>
|
||||
<owner>src/android_webview/OWNERS</owner>
|
||||
<summary>
|
||||
How long it takes to initialize a WebViewProvider, the first time that one
|
||||
is initialized. WebViewProvider initialization is the second major phase of
|
||||
WebViewChromium construction. The first initialization is recorded
|
||||
separately because it is usually much slower than subsequent ones.
|
||||
How long it takes to initialize a WebViewChromium instance in the case where
|
||||
we had to also do the process-global browser startup:
|
||||
("Android.WebView.Startup.CreationTime.StartChromiumLocked") had
|
||||
not yet run and was performed during the instance initialization.
|
||||
|
||||
This is recorded separately as it is significantly slower than the
|
||||
"Warm" case, and can only occur the first time that a
|
||||
WebViewChromium instance is created. However, it's possible for browser
|
||||
startup to already be complete before the first WebViewChromium instance is
|
||||
created, in which case the "Cold" case will not be recorded during
|
||||
this session.
|
||||
|
||||
The newer histograms FirstInstanceAfterGlobalStartup,
|
||||
FirstInstanceWithGlobalStartup, and NotFirstInstance should be preferred for
|
||||
most purposes as they correctly reflect that there are three distinct cases
|
||||
here rather than only two, but the older histograms are retained for now to
|
||||
allow "fair" comparisons with builds that predate the newer ones.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
@ -7477,10 +7553,24 @@ chromium-metrics-reviews@google.com.
|
||||
<owner>torne@chromium.org</owner>
|
||||
<owner>src/android_webview/OWNERS</owner>
|
||||
<summary>
|
||||
How long it takes to initialize a WebViewProvider, when it is initialized
|
||||
the second or later times. WebViewProvider initialization is the second
|
||||
major phase of WebViewChromium construction. When it is not the first time,
|
||||
it is faster and thus recorded separately.
|
||||
How long it takes to initialize a WebViewChromium instance in the case where
|
||||
we have already finished the process-global browser startup:
|
||||
("Android.WebView.Startup.CreationTime.StartChromiumLocked") was
|
||||
already complete and did not need to be performed during the instance
|
||||
initialization.
|
||||
|
||||
This is recorded separately as it is significantly faster than the
|
||||
"Cold" case. However, it's possible for browser startup
|
||||
initialization to already be complete before the first WebViewChromium
|
||||
instance is created, in which case the first instance will be recorded as a
|
||||
"Warm" case, even though it is expected to be slower than
|
||||
subsequent instances, resulting in this metric being bimodal.
|
||||
|
||||
The newer histograms FirstInstanceAfterGlobalStartup,
|
||||
FirstInstanceWithGlobalStartup, and NotFirstInstance should be preferred for
|
||||
most purposes as they correctly reflect that there are three distinct cases
|
||||
here rather than only two, but the older histograms are retained for now to
|
||||
allow "fair" comparisons with builds that predate the newer ones.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
|
Reference in New Issue
Block a user