// Copyright 2015 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.tab; import android.os.SystemClock; import org.chromium.base.metrics.RecordHistogram; import org.chromium.chrome.browser.tabmodel.TabModel.TabSelectionType; import org.chromium.net.NetError; import java.util.concurrent.TimeUnit; /** * Centralizes UMA data collection for Tab management. * This will drive our memory optimization efforts, specially tab restoring and * eviction. * All calls must be made from the UI thread. */ public class TabUma { // TabStatus defined in tools/metrics/histograms/histograms.xml. static final int TAB_STATUS_MEMORY_RESIDENT = 0; static final int TAB_STATUS_RELOAD_EVICTED = 1; static final int TAB_STATUS_RELOAD_COLD_START_FG = 6; static final int TAB_STATUS_RELOAD_COLD_START_BG = 7; static final int TAB_STATUS_LAZY_LOAD_FOR_BG_TAB = 8; static final int TAB_STATUS_LIM = 9; // TabBackgroundLoadStatus defined in tools/metrics/histograms/histograms.xml. static final int TAB_BACKGROUND_LOAD_SHOWN = 0; static final int TAB_BACKGROUND_LOAD_LOST = 1; static final int TAB_BACKGROUND_LOAD_SKIPPED = 2; static final int TAB_BACKGROUND_LOAD_LIM = 3; // The enum values for the Tab.RestoreResult histogram. The unusual order is to // keep compatibility with the previous instance of the histogram that was using // a boolean. // // Defined in tools/metrics/histograms/histograms.xml. private static final int TAB_RESTORE_RESULT_FAILURE_OTHER = 0; private static final int TAB_RESTORE_RESULT_SUCCESS = 1; private static final int TAB_RESTORE_RESULT_FAILURE_NETWORK_CONNECTIVITY = 2; private static final int TAB_RESTORE_RESULT_COUNT = 3; // TAB_STATE_* are for TabStateTransferTime and TabTransferTarget histograms. // TabState defined in tools/metrics/histograms/histograms.xml. private static final int TAB_STATE_INITIAL = 0; private static final int TAB_STATE_ACTIVE = 1; private static final int TAB_STATE_INACTIVE = 2; private static final int TAB_STATE_DETACHED = 3; private static final int TAB_STATE_CLOSED = 4; private static final int TAB_STATE_MAX = TAB_STATE_CLOSED; // Counter of tab shows (as per onShow()) for all tabs. private static long sAllTabsShowCount = 0; /** * State in which the tab was created. This can be used in metric accounting - e.g. to * distinguish reasons for a tab to be restored upon first display. */ public enum TabCreationState { LIVE_IN_FOREGROUND, LIVE_IN_BACKGROUND, FROZEN_ON_RESTORE, FROZEN_FOR_LAZY_LOAD, FROZEN_ON_RESTORE_FAILED, } private final TabCreationState mTabCreationState; // Timestamp when this tab was last shown. private long mLastShownTimestamp = -1; // Timestamp of the beginning of the current tab restore. private long mRestoreStartedAtMillis = -1; private long mLastTabStateChangeMillis = -1; private int mLastTabState = TAB_STATE_INITIAL; // The number of background tabs opened by long pressing on this tab and selecting // "Open in a new tab" from the context menu. private int mNumBackgroundTabsOpened; // Records histogram about which background tab opened from this tab the user switches to // first. private ChildBackgroundTabShowObserver mChildBackgroundTabShowObserver; /** * Constructs a new UMA tracker for a specific tab. * @param creationState In what state the tab was created. */ public TabUma(TabCreationState creationState) { mTabCreationState = creationState; mLastTabStateChangeMillis = System.currentTimeMillis(); if (mTabCreationState == TabCreationState.LIVE_IN_FOREGROUND || mTabCreationState == TabCreationState.FROZEN_ON_RESTORE) { updateTabState(TAB_STATE_ACTIVE); } else if (mTabCreationState == TabCreationState.LIVE_IN_BACKGROUND || mTabCreationState == TabCreationState.FROZEN_FOR_LAZY_LOAD) { updateTabState(TAB_STATE_INACTIVE); } else if (mTabCreationState == TabCreationState.FROZEN_ON_RESTORE_FAILED) { // A previous TabUma should have reported an active tab state. Initialize but avoid // recording this as a state change. mLastTabState = TAB_STATE_ACTIVE; updateTabState(TAB_STATE_ACTIVE); } } /** * Records the tab restore result into several UMA histograms. * @param succeeded Whether or not the tab restore succeeded. * @param time The time taken to perform the tab restore. * @param perceivedTime The perceived time taken to perform the tab restore. * @param errorCode The error code, on failure (as denoted by the |succeeded| parameter). */ private void recordTabRestoreResult(boolean succeeded, long time, long perceivedTime, int errorCode) { if (succeeded) { RecordHistogram.recordEnumeratedHistogram( "Tab.RestoreResult", TAB_RESTORE_RESULT_SUCCESS, TAB_RESTORE_RESULT_COUNT); RecordHistogram.recordCountHistogram("Tab.RestoreTime", (int) time); RecordHistogram.recordCountHistogram("Tab.PerceivedRestoreTime", (int) perceivedTime); } else { switch (errorCode) { case NetError.ERR_INTERNET_DISCONNECTED: case NetError.ERR_NAME_RESOLUTION_FAILED: case NetError.ERR_DNS_TIMED_OUT: RecordHistogram.recordEnumeratedHistogram("Tab.RestoreResult", TAB_RESTORE_RESULT_FAILURE_NETWORK_CONNECTIVITY, TAB_RESTORE_RESULT_COUNT); break; default: RecordHistogram.recordEnumeratedHistogram("Tab.RestoreResult", TAB_RESTORE_RESULT_FAILURE_OTHER, TAB_RESTORE_RESULT_COUNT); } } } /** * Records a sample in a histogram of times. This is the Java equivalent of the * UMA_HISTOGRAM_LONG_TIMES_100. */ private void recordLongTimesHistogram100(String name, long duration) { RecordHistogram.recordCustomTimesHistogram( name, TimeUnit.MILLISECONDS.toMillis(duration), TimeUnit.MILLISECONDS.toMillis(1), TimeUnit.HOURS.toMillis(1), TimeUnit.MILLISECONDS, 100); } /** * Record the tab state transition into histograms. * @param prevState Previous state of the tab. * @param newState New state of the tab. * @param delta Time elapsed from the last state transition in milliseconds. */ private void recordTabStateTransition(int prevState, int newState, long delta) { if (prevState == TAB_STATE_ACTIVE && newState == TAB_STATE_INACTIVE) { recordLongTimesHistogram100("Tabs.StateTransfer.Time_Active_Inactive", delta); } else if (prevState == TAB_STATE_ACTIVE && newState == TAB_STATE_CLOSED) { recordLongTimesHistogram100("Tabs.StateTransfer.Time_Active_Closed", delta); } else if (prevState == TAB_STATE_INACTIVE && newState == TAB_STATE_ACTIVE) { recordLongTimesHistogram100("Tabs.StateTransfer.Time_Inactive_Active", delta); } else if (prevState == TAB_STATE_INACTIVE && newState == TAB_STATE_CLOSED) { recordLongTimesHistogram100("Tabs.StateTransfer.Time_Inactive_Close", delta); } if (prevState == TAB_STATE_INITIAL) { RecordHistogram.recordEnumeratedHistogram("Tabs.StateTransfer.Target_Initial", newState, TAB_STATE_MAX); } else if (prevState == TAB_STATE_ACTIVE) { RecordHistogram.recordEnumeratedHistogram("Tabs.StateTransfer.Target_Active", newState, TAB_STATE_MAX); } else if (prevState == TAB_STATE_INACTIVE) { RecordHistogram.recordEnumeratedHistogram("Tabs.StateTransfer.Target_Inactive", newState, TAB_STATE_MAX); } } /** * Records the number of background tabs which were opened from this tab's * current URL. Does not record anything if no background tabs were opened. */ private void recordNumBackgroundTabsOpened() { if (mNumBackgroundTabsOpened > 0) { RecordHistogram.recordCount100Histogram( "Tab.BackgroundTabsOpenedViaContextMenuCount", mNumBackgroundTabsOpened); } mNumBackgroundTabsOpened = 0; mChildBackgroundTabShowObserver = null; } /** * Updates saved TabState and its timestamp. Records the state transition into the histogram. * @param newState New state of the tab. */ void updateTabState(int newState) { long now = System.currentTimeMillis(); recordTabStateTransition(mLastTabState, newState, now - mLastTabStateChangeMillis); mLastTabStateChangeMillis = now; mLastTabState = newState; } /** * Called upon tab display. * @param selectionType determines how the tab was being shown * @param previousTimestampMillis time of the previous display or creation time for the tabs * opened in background and not yet displayed * @param rank The MRU rank for this tab within the model. */ void onShow(TabSelectionType selectionType, long previousTimestampMillis, int rank) { long now = SystemClock.elapsedRealtime(); // Do not collect the tab switching data for the first switch to a tab after the cold start // and for the tab switches that were not user-originated (e.g. the user closes the last // incognito tab and the current normal mode tab is shown). if (mLastShownTimestamp != -1 && selectionType == TabSelectionType.FROM_USER) { long age = now - mLastShownTimestamp; RecordHistogram.recordCountHistogram("Tab.SwitchedToForegroundAge", (int) age); RecordHistogram.recordCountHistogram("Tab.SwitchedToForegroundMRURank", rank); } increaseTabShowCount(); boolean isOnBrowserStartup = sAllTabsShowCount == 1; boolean performsLazyLoad = mTabCreationState == TabCreationState.FROZEN_FOR_LAZY_LOAD && mLastShownTimestamp == -1; int status; if (mRestoreStartedAtMillis == -1 && !performsLazyLoad) { // The tab is *not* being restored or loaded lazily on first display. status = TAB_STATUS_MEMORY_RESIDENT; } else if (mLastShownTimestamp == -1) { // This is first display and the tab is being restored or loaded lazily. if (isOnBrowserStartup) { status = TAB_STATUS_RELOAD_COLD_START_FG; } else if (mTabCreationState == TabCreationState.FROZEN_ON_RESTORE) { status = TAB_STATUS_RELOAD_COLD_START_BG; } else if (mTabCreationState == TabCreationState.FROZEN_FOR_LAZY_LOAD) { status = TAB_STATUS_LAZY_LOAD_FOR_BG_TAB; } else { assert mTabCreationState == TabCreationState.LIVE_IN_FOREGROUND || mTabCreationState == TabCreationState.LIVE_IN_BACKGROUND; status = TAB_STATUS_RELOAD_EVICTED; } } else { // The tab is being restored and this is *not* the first time the tab is shown. status = TAB_STATUS_RELOAD_EVICTED; } // Record only user-visible switches to existing tabs. Do not record displays of newly // created tabs (FROM_NEW) or selections of the previous tab that happen when we close the // tab opened from intent while exiting Chrome (FROM_CLOSE). if (selectionType == TabSelectionType.FROM_USER) { RecordHistogram.recordEnumeratedHistogram( "Tab.StatusWhenSwitchedBackToForeground", status, TAB_STATUS_LIM); } // Record Tab.BackgroundLoadStatus. if (mLastShownTimestamp == -1) { if (mTabCreationState == TabCreationState.LIVE_IN_BACKGROUND) { if (mRestoreStartedAtMillis == -1) { RecordHistogram.recordEnumeratedHistogram("Tab.BackgroundLoadStatus", TAB_BACKGROUND_LOAD_SHOWN, TAB_BACKGROUND_LOAD_LIM); } else { RecordHistogram.recordEnumeratedHistogram("Tab.BackgroundLoadStatus", TAB_BACKGROUND_LOAD_LOST, TAB_BACKGROUND_LOAD_LIM); if (previousTimestampMillis > 0) { RecordHistogram.recordMediumTimesHistogram( "Tab.LostTabAgeWhenSwitchedToForeground", System.currentTimeMillis() - previousTimestampMillis, TimeUnit.MILLISECONDS); } } } else if (mTabCreationState == TabCreationState.FROZEN_FOR_LAZY_LOAD) { assert mRestoreStartedAtMillis == -1; RecordHistogram.recordEnumeratedHistogram("Tab.BackgroundLoadStatus", TAB_BACKGROUND_LOAD_SKIPPED, TAB_BACKGROUND_LOAD_LIM); } } // Record "tab age upon first display" metrics. previousTimestampMillis is persisted through // cold starts. if (mLastShownTimestamp == -1 && previousTimestampMillis > 0) { if (isOnBrowserStartup) { RecordHistogram.recordCountHistogram("Tabs.ForegroundTabAgeAtStartup", (int) millisecondsToMinutes(System.currentTimeMillis() - previousTimestampMillis)); } else if (selectionType == TabSelectionType.FROM_USER) { RecordHistogram.recordCountHistogram("Tab.AgeUponRestoreFromColdStart", (int) millisecondsToMinutes(System.currentTimeMillis() - previousTimestampMillis)); } } mLastShownTimestamp = now; updateTabState(TAB_STATE_ACTIVE); } void onHide() { updateTabState(TAB_STATE_INACTIVE); } /** Called when the corresponding activity is stopped. */ void onActivityHidden() { recordNumBackgroundTabsOpened(); } void onDestroy() { updateTabState(TAB_STATE_CLOSED); if (mTabCreationState == TabCreationState.LIVE_IN_BACKGROUND || mTabCreationState == TabCreationState.FROZEN_FOR_LAZY_LOAD) { RecordHistogram.recordBooleanHistogram( "Tab.BackgroundTabShown", mLastShownTimestamp != -1); } recordNumBackgroundTabsOpened(); } /** Called when restore of the corresponding tab is triggered. */ void onRestoreStarted() { mRestoreStartedAtMillis = SystemClock.elapsedRealtime(); } /** Called when the corresponding tab starts a page load. */ void onPageLoadStarted() { recordNumBackgroundTabsOpened(); } /** Called when the correspoding tab completes a page load. */ void onPageLoadFinished() { // Record only tab restores that the user became aware of. If the restore is triggered // speculatively and completes before the user switches to the tab, then this case is // reflected in Tab.StatusWhenSwitchedBackToForeground metric. if (mRestoreStartedAtMillis != -1 && mLastShownTimestamp >= mRestoreStartedAtMillis) { long now = SystemClock.elapsedRealtime(); long restoreTime = now - mRestoreStartedAtMillis; long perceivedRestoreTime = now - mLastShownTimestamp; recordTabRestoreResult(true, restoreTime, perceivedRestoreTime, -1); } mRestoreStartedAtMillis = -1; } /** Called when the correspoding tab fails a page load. */ void onLoadFailed(int errorCode) { if (mRestoreStartedAtMillis != -1 && mLastShownTimestamp >= mRestoreStartedAtMillis) { // Load time is ignored for failed loads. recordTabRestoreResult(false, -1, -1, errorCode); } mRestoreStartedAtMillis = -1; } /** Called when the renderer of the correspoding tab crashes. */ void onRendererCrashed() { if (mRestoreStartedAtMillis != -1) { // TODO(ppi): Add a bucket in Tab.RestoreResult for restores failed due to // renderer crashes and start to track that. mRestoreStartedAtMillis = -1; } } /** * Called when a user opens a background tab by long pressing and selecting "Open in a new tab" * from the context menu. * @param backgroundTab The background tab. */ void onBackgroundTabOpenedFromContextMenu(Tab backgroundTab) { ++mNumBackgroundTabsOpened; if (mChildBackgroundTabShowObserver == null) { mChildBackgroundTabShowObserver = new ChildBackgroundTabShowObserver(backgroundTab.getParentId()); } mChildBackgroundTabShowObserver.onBackgroundTabOpened(backgroundTab); } /** * @return The timestamp for when this tab was last shown. */ long getLastShownTimestamp() { return mLastShownTimestamp; } private static void increaseTabShowCount() { sAllTabsShowCount++; } private static long millisecondsToMinutes(long msec) { return msec / 1000 / 60; } }