[Clank Startup] Adding a Clank Startup Latency Ablation Experiment
This experiment will add a fixed-amount of latency to Clank startup
during Main Intent launches to determine downstream effects.
The ablation is injected in performPostInflationStartup at an Activity level via busy-waiting.
Bug: 334143643
Change-Id: Ifc7cbe1f55cd6427b947a7a282329686f15acd61
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5818012
Reviewed-by: Yaron Friedman <yfriedman@chromium.org>
Reviewed-by: Sean Maher <spvw@chromium.org>
Commit-Queue: Nafis Abedin <nafisabedin@google.com>
Reviewed-by: Michael Thiessen <mthiesse@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1352091}
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 8c5f4893..d9280d1 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -747,6 +747,7 @@
"java/src/org/chromium/chrome/browser/instantapps/InstantAppsHandler.java",
"java/src/org/chromium/chrome/browser/invalidation/ResumableDelayedTaskRunner.java",
"java/src/org/chromium/chrome/browser/invalidation/SessionsInvalidationManager.java",
+ "java/src/org/chromium/chrome/browser/latency_injection/StartupLatencyInjector.java",
"java/src/org/chromium/chrome/browser/lens/LensDebugBridge.java",
"java/src/org/chromium/chrome/browser/lens/LensPolicyUtils.java",
"java/src/org/chromium/chrome/browser/login/ChromeHttpAuthHandler.java",
diff --git a/chrome/android/chrome_test_java_sources.gni b/chrome/android/chrome_test_java_sources.gni
index 0e9820d..00d7278 100644
--- a/chrome/android/chrome_test_java_sources.gni
+++ b/chrome/android/chrome_test_java_sources.gni
@@ -250,6 +250,7 @@
"javatests/src/org/chromium/chrome/browser/javascript/CloseWatcherTest.java",
"javatests/src/org/chromium/chrome/browser/jsdialog/JavascriptAppModalDialogTest.java",
"javatests/src/org/chromium/chrome/browser/jsdialog/JavascriptTabModalDialogTest.java",
+ "javatests/src/org/chromium/chrome/browser/latency_injection/StartupLatencyInjectorTest.java",
"javatests/src/org/chromium/chrome/browser/locale/LocaleManagerReferralTest.java",
"javatests/src/org/chromium/chrome/browser/locale/LocaleManagerTest.java",
"javatests/src/org/chromium/chrome/browser/login/ChromeHttpAuthHandlerTest.java",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index cbba0322..706f3e9c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -129,6 +129,7 @@
import org.chromium.chrome.browser.incognito.IncognitoTabbedSnapshotController;
import org.chromium.chrome.browser.incognito.IncognitoUtils;
import org.chromium.chrome.browser.init.ActivityProfileProvider;
+import org.chromium.chrome.browser.latency_injection.StartupLatencyInjector;
import org.chromium.chrome.browser.layouts.LayoutStateProvider;
import org.chromium.chrome.browser.layouts.LayoutType;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
@@ -1055,6 +1056,30 @@
}
}
+ private boolean isMainIntentLaunch() {
+ assert !mFromResumption : "Method is correct only when it's a new Activity launch.";
+
+ Intent launchIntent = getIntent();
+ if (launchIntent == null) return false;
+
+ // Also ignore if launched from recents.
+ if (0 != (launchIntent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY)) {
+ return false;
+ }
+
+ if (IntentUtils.isMainIntentFromLauncher(launchIntent)) {
+ return true;
+ }
+
+ if (IntentUtils.safeGetBooleanExtra(
+ launchIntent, IntentHandler.EXTRA_INVOKED_FROM_SHORTCUT, false)
+ && IntentHandler.wasIntentSenderChrome(launchIntent)) {
+ return true;
+ }
+
+ return false;
+ }
+
@Override
protected OneshotSupplier<ProfileProvider> createProfileProvider() {
return new ActivityProfileProvider(getLifecycleDispatcher());
@@ -1990,6 +2015,11 @@
public void performPreInflationStartup() {
super.performPreInflationStartup();
+ if (isMainIntentLaunch()) {
+ StartupLatencyInjector startupLatencyInjector = new StartupLatencyInjector();
+ startupLatencyInjector.maybeInjectLatency();
+ }
+
// Android FrameMetrics allow tracking of java views and their deadline misses (frame
// drops/janks).
if (ChromeFeatureList.sCollectAndroidFrameTimelineMetrics.isEnabled()) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java b/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java
index 05f115e..d20dbd2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/app/flags/ChromeCachedFlags.java
@@ -27,6 +27,7 @@
import org.chromium.chrome.browser.firstrun.FirstRunUtils;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.hub.HubFieldTrial;
+import org.chromium.chrome.browser.latency_injection.StartupLatencyInjector;
import org.chromium.chrome.browser.logo.LogoUtils;
import org.chromium.chrome.browser.magic_stack.HomeModulesMetricsUtils;
import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
@@ -130,6 +131,7 @@
SuggestionsNavigationDelegate.MOST_VISITED_TILES_RESELECT_LAX_QUERY,
SuggestionsNavigationDelegate.MOST_VISITED_TILES_RESELECT_LAX_REF,
SuggestionsNavigationDelegate.MOST_VISITED_TILES_RESELECT_LAX_SCHEME_HOST,
+ StartupLatencyInjector.CLANK_STARTUP_LATENCY_PARAM_MS,
TabManagementFieldTrial.DELAY_TEMP_STRIP_TIMEOUT_MS,
HomeModulesMetricsUtils.HOME_MODULES_SHOW_ALL_MODULES,
HomeModulesMetricsUtils.TAB_RESUMPTION_COMBINE_TABS,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/latency_injection/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/latency_injection/OWNERS
new file mode 100644
index 0000000..c19374d6
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/latency_injection/OWNERS
@@ -0,0 +1 @@
+mthiesse@chromium.org
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/latency_injection/StartupLatencyInjector.java b/chrome/android/java/src/org/chromium/chrome/browser/latency_injection/StartupLatencyInjector.java
new file mode 100644
index 0000000..00d03d57
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/latency_injection/StartupLatencyInjector.java
@@ -0,0 +1,56 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.latency_injection;
+
+import org.chromium.base.TimeUtils;
+import org.chromium.base.TimeUtils.UptimeMillisTimer;
+import org.chromium.base.cached_flags.IntCachedFieldTrialParameter;
+import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
+
+public final class StartupLatencyInjector {
+ private static final String LATENCY_INJECTION_PARAM = "latency_injection_amount_millis";
+
+ private static final int LATENCY_INJECTION_DEFAULT_MILLIS = 0;
+
+ private static final String HISTOGRAM_TOTAL_WAIT_TIME =
+ "Startup.Android.MainIconLaunchTotalWaitTime";
+
+ private final Long mBusyWaitDurationMillis;
+
+ /**
+ * A cached parameter representing the amount of latency to inject during Clank startup based on
+ * experiment configuration.
+ */
+ public static final IntCachedFieldTrialParameter CLANK_STARTUP_LATENCY_PARAM_MS =
+ ChromeFeatureList.newIntCachedFieldTrialParameter(
+ ChromeFeatureList.CLANK_STARTUP_LATENCY_INJECTION,
+ LATENCY_INJECTION_PARAM,
+ LATENCY_INJECTION_DEFAULT_MILLIS);
+
+ public StartupLatencyInjector() {
+ mBusyWaitDurationMillis = Long.valueOf(CLANK_STARTUP_LATENCY_PARAM_MS.getValue());
+ }
+
+ private boolean isEnabled() {
+ return ChromeFeatureList.sClankStartupLatencyInjection.isEnabled();
+ }
+
+ public void maybeInjectLatency() {
+ if (!isEnabled() || mBusyWaitDurationMillis <= 0) {
+ return;
+ }
+
+ long startTime = TimeUtils.uptimeMillis();
+ busyWait();
+ long totalWaitTime = TimeUtils.uptimeMillis() - startTime;
+ RecordHistogram.recordMediumTimesHistogram(HISTOGRAM_TOTAL_WAIT_TIME, totalWaitTime);
+ }
+
+ private void busyWait() {
+ UptimeMillisTimer timer = new UptimeMillisTimer();
+ while (mBusyWaitDurationMillis.compareTo(timer.getElapsedMillis()) >= 0);
+ }
+}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/latency_injection/OWNERS b/chrome/android/javatests/src/org/chromium/chrome/browser/latency_injection/OWNERS
new file mode 100644
index 0000000..a35fd0d
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/latency_injection/OWNERS
@@ -0,0 +1 @@
+file://chrome/android/java/src/org/chromium/chrome/browser/latency_injection/OWNERS
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/latency_injection/StartupLatencyInjectorTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/latency_injection/StartupLatencyInjectorTest.java
new file mode 100644
index 0000000..4e5d5fbe
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/latency_injection/StartupLatencyInjectorTest.java
@@ -0,0 +1,43 @@
+// Copyright 2024 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.latency_injection;
+
+import androidx.test.filters.LargeTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.base.test.util.DoNotBatch;
+import org.chromium.base.test.util.Features.EnableFeatures;
+import org.chromium.base.test.util.HistogramWatcher;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
+import org.chromium.chrome.browser.flags.ChromeSwitches;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+
+@RunWith(ChromeJUnit4ClassRunner.class)
+@EnableFeatures(ChromeFeatureList.CLANK_STARTUP_LATENCY_INJECTION)
+@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
+@DoNotBatch(reason = "Tests require cold browser start.")
+public class StartupLatencyInjectorTest {
+ @Rule
+ public ChromeTabbedActivityTestRule mTabbedActivityTestRule =
+ new ChromeTabbedActivityTestRule();
+
+ private static final String HISTOGRAM_TOTAL_WAIT_TIME =
+ "Startup.Android.MainIconLaunchTotalWaitTime";
+
+ @Test
+ @LargeTest
+ public void checkLatencyInjectedForMainIntentLaunch() throws Exception {
+ HistogramWatcher watcher =
+ HistogramWatcher.newSingleRecordWatcher(HISTOGRAM_TOTAL_WAIT_TIME);
+ StartupLatencyInjector.CLANK_STARTUP_LATENCY_PARAM_MS.setForTesting(100);
+ mTabbedActivityTestRule.startMainActivityFromLauncher();
+ watcher.assertExpected();
+ }
+}
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index 0e0c25ea..26b31c0 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -229,6 +229,7 @@
&kCacheDeprecatedSystemLocationSetting,
&kChromeSharePageInfo,
&kChromeSurveyNextAndroid,
+ &kClankStartupLatencyInjection,
&kCommandLineOnNonRooted,
&kContextMenuTranslateWithGoogleLens,
&kContextMenuSysUiMatchesActivity,
@@ -641,6 +642,10 @@
"ChromeSurveyNextAndroid",
base::FEATURE_ENABLED_BY_DEFAULT);
+BASE_FEATURE(kClankStartupLatencyInjection,
+ "ClankStartupLatencyInjection",
+ base::FEATURE_DISABLED_BY_DEFAULT);
+
BASE_FEATURE(kCommandLineOnNonRooted,
"CommandLineOnNonRooted",
base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h
index 0ddb20a..86569c8 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -78,6 +78,7 @@
BASE_DECLARE_FEATURE(kChromeShareScreenshot);
BASE_DECLARE_FEATURE(kChromeSharingHubLaunchAdjacent);
BASE_DECLARE_FEATURE(kChromeSurveyNextAndroid);
+BASE_DECLARE_FEATURE(kClankStartupLatencyInjection);
BASE_DECLARE_FEATURE(kCommandLineOnNonRooted);
BASE_DECLARE_FEATURE(kContextMenuSysUiMatchesActivity);
BASE_DECLARE_FEATURE(kContextMenuTranslateWithGoogleLens);
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index ff04119..ee0446cf 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -268,6 +268,7 @@
public static final String CCT_TAB_MODAL_DIALOG = "CCTTabModalDialog";
public static final String CHROME_SURVEY_NEXT_ANDROID = "ChromeSurveyNextAndroid";
public static final String CHROME_SHARE_PAGE_INFO = "ChromeSharePageInfo";
+ public static final String CLANK_STARTUP_LATENCY_INJECTION = "ClankStartupLatencyInjection";
public static final String COLLECT_ANDROID_FRAME_TIMELINE_METRICS =
"CollectAndroidFrameTimelineMetrics";
public static final String COMMAND_LINE_ON_NON_ROOTED = "CommandLineOnNonRooted";
@@ -600,6 +601,8 @@
public static final CachedFlag sCctNestedSecurityIcon =
newCachedFlag(CCT_NESTED_SECURITY_ICON, false);
public static final CachedFlag sCctTabModalDialog = newCachedFlag(CCT_TAB_MODAL_DIALOG, true);
+ public static final CachedFlag sClankStartupLatencyInjection =
+ newCachedFlag(CLANK_STARTUP_LATENCY_INJECTION, false);
public static final CachedFlag sCollectAndroidFrameTimelineMetrics =
newCachedFlag(COLLECT_ANDROID_FRAME_TIMELINE_METRICS, false);
public static final CachedFlag sCommandLineOnNonRooted =
@@ -755,6 +758,7 @@
sCctRevampedBranding,
sCctNestedSecurityIcon,
sCctTabModalDialog,
+ sClankStartupLatencyInjection,
sCollectAndroidFrameTimelineMetrics,
sCommandLineOnNonRooted,
sCrossDeviceTabPaneAndroid,
diff --git a/tools/metrics/histograms/metadata/startup/histograms.xml b/tools/metrics/histograms/metadata/startup/histograms.xml
index 12999057..20f567e8 100644
--- a/tools/metrics/histograms/metadata/startup/histograms.xml
+++ b/tools/metrics/histograms/metadata/startup/histograms.xml
@@ -564,6 +564,22 @@
</summary>
</histogram>
+<histogram name="Startup.Android.MainIconLaunchTotalWaitTime" units="ms"
+ expires_after="2025-03-03">
+ <owner>nafisabedin@google.com</owner>
+ <owner>yfriedman@chromium.org</owner>
+ <summary>
+ The total time a user waits for the Clank startup ablation study. The
+ enabled arms of the study prescribes an amount of time to delay the launch
+ of Clank, and this histogram records the amount of time waited to verify
+ ablation and any additional delay.
+
+ Recorded only when the main Chrome launcher icon is used to start the app
+ (i.e. LaunchCauseType is MAIN_LAUNCHER_ICON or MAIN_LAUNCHER_ICON_SHORTCUT)
+ and after the ablation occurs.
+ </summary>
+</histogram>
+
<histogram name="Startup.Android.MainIntentIsColdStart" enum="Boolean"
expires_after="2025-02-08">
<owner>nafisabedin@google.com</owner>