blob: 9c39e78c2bb1df755d5af2dc92aaeeb4fd09bb8f [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// 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.metrics;
import android.os.SystemClock;
import org.chromium.base.library_loader.LibraryProcessType;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabObserver;
import org.chromium.chrome.browser.util.UrlUtilities;
import org.chromium.content_public.browser.BrowserStartupController;
import org.chromium.content_public.browser.NavigationHandle;
import org.chromium.content_public.browser.WebContents;
/**
* Tracks the first navigation and first contentful paint events for a tab within an activity during
* startup.
*/
public class ActivityTabStartupMetricsTracker {
private final long mActivityStartTimeMs;
private final ChromeActivity mActivity;
// Event duration recorded from the |mActivityStartTimeMs|.
private long mFirstCommitTimeMs;
private String mHistogramSuffix;
private TabModelSelectorTabObserver mTabModelSelectorTabObserver;
private PageLoadMetrics.Observer mPageLoadMetricsObserver;
private boolean mShouldTrackStartupMetrics;
public ActivityTabStartupMetricsTracker(ChromeActivity activity) {
mActivityStartTimeMs = SystemClock.uptimeMillis();
mActivity = activity;
BrowserStartupController.get(LibraryProcessType.PROCESS_BROWSER)
.addStartupCompletedObserver(new BrowserStartupController.StartupCallback() {
@Override
public void onSuccess() {
// The activity's TabModelSelector may not have been initialized yet
// causing a crash. See https://crbug.com/847580
if (!mActivity.areTabModelsInitialized()) return;
registerObservers();
}
@Override
public void onFailure() {}
});
}
private void registerObservers() {
if (!mShouldTrackStartupMetrics) return;
mTabModelSelectorTabObserver =
new TabModelSelectorTabObserver(mActivity.getTabModelSelector()) {
private boolean mIsFirstPageLoadStart = true;
@Override
public void onPageLoadStarted(Tab tab, String url) {
// Discard startup navigation measurements when the user interfered and
// started the 2nd navigation (in activity lifetime) in parallel.
if (!mIsFirstPageLoadStart) {
mShouldTrackStartupMetrics = false;
} else {
mIsFirstPageLoadStart = false;
}
}
@Override
public void onDidFinishNavigation(Tab tab, NavigationHandle navigation) {
boolean isTrackedPage = navigation.hasCommitted()
&& navigation.isInMainFrame() && !navigation.isErrorPage()
&& !navigation.isSameDocument()
&& !navigation.isFragmentNavigation()
&& UrlUtilities.isHttpOrHttps(navigation.getUrl());
registerFinishNavigation(isTrackedPage);
}
};
mPageLoadMetricsObserver = new PageLoadMetrics.Observer() {
private final static long NO_NAVIGATION_ID = -1;
private long mNavigationId = NO_NAVIGATION_ID;
private boolean mShouldRecordHistograms;
@Override
public void onNewNavigation(WebContents webContents, long navigationId,
boolean isFirstNavigationInWebContents) {
if (mNavigationId != NO_NAVIGATION_ID) return;
mNavigationId = navigationId;
mShouldRecordHistograms = mShouldTrackStartupMetrics;
}
@Override
public void onFirstContentfulPaint(WebContents webContents, long navigationId,
long navigationStartTick, long firstContentfulPaintMs) {
if (navigationId != mNavigationId || !mShouldRecordHistograms) return;
recordFirstContentfulPaint(navigationStartTick / 1000 + firstContentfulPaintMs);
}
};
PageLoadMetrics.addObserver(mPageLoadMetricsObserver);
}
/**
* Marks that startup metrics should be tracked with the |histogramSuffix|.
* Must only be called on the UI thread.
*/
public void trackStartupMetrics(String histogramSuffix) {
mHistogramSuffix = histogramSuffix;
mShouldTrackStartupMetrics = true;
}
public void destroy() {
mShouldTrackStartupMetrics = false;
if (mTabModelSelectorTabObserver != null) {
mTabModelSelectorTabObserver.destroy();
mTabModelSelectorTabObserver = null;
}
if (mPageLoadMetricsObserver != null) {
PageLoadMetrics.removeObserver(mPageLoadMetricsObserver);
mPageLoadMetricsObserver = null;
}
}
/**
* Registers the fact that a navigation has finished. Based on this fact, may discard recording
* histograms later.
*/
private void registerFinishNavigation(boolean isTrackedPage) {
if (!mShouldTrackStartupMetrics) return;
if (isTrackedPage && UmaUtils.hasComeToForeground() && !UmaUtils.hasComeToBackground()) {
mFirstCommitTimeMs = SystemClock.uptimeMillis() - mActivityStartTimeMs;
RecordHistogram.recordMediumTimesHistogram(
"Startup.Android.Cold.TimeToFirstNavigationCommit" + mHistogramSuffix,
mFirstCommitTimeMs);
}
mShouldTrackStartupMetrics = false;
}
/**
* Record the First Contentful Paint time.
*
* @param firstContentfulPaintMs timestamp in uptime millis.
*/
private void recordFirstContentfulPaint(long firstContentfulPaintMs) {
// First commit time histogram should be recorded before this one. We should discard a
// record if the first commit time wasn't recorded.
if (mFirstCommitTimeMs == 0) return;
if (UmaUtils.hasComeToForeground() && !UmaUtils.hasComeToBackground()) {
RecordHistogram.recordMediumTimesHistogram(
"Startup.Android.Cold.TimeToFirstContentfulPaint" + mHistogramSuffix,
firstContentfulPaintMs - mActivityStartTimeMs);
}
// This is the last event we track, so destroy this tracker and remove observers.
destroy();
}
}