| Index: ios/chrome/browser/metrics/tab_usage_recorder.mm
|
| diff --git a/ios/chrome/browser/metrics/tab_usage_recorder.mm b/ios/chrome/browser/metrics/tab_usage_recorder.mm
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..34733bc3d552436343252788f8c005a6f24b99aa
|
| --- /dev/null
|
| +++ b/ios/chrome/browser/metrics/tab_usage_recorder.mm
|
| @@ -0,0 +1,336 @@
|
| +// Copyright 2013 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.
|
| +
|
| +#include "ios/chrome/browser/metrics/tab_usage_recorder.h"
|
| +
|
| +#include "base/metrics/histogram.h"
|
| +#include "ios/chrome/browser/chrome_url_constants.h"
|
| +#import "ios/chrome/browser/metrics/previous_session_info.h"
|
| +#import "ios/chrome/browser/tabs/tab.h"
|
| +#import "ios/web/web_state/ui/crw_web_controller.h"
|
| +
|
| +const char kTabUsageHistogramPrefix[] = "Tab";
|
| +
|
| +// The histogram recording the state of the tab the user switches to.
|
| +const char kSelectedTabHistogramName[] =
|
| + "Tab.StatusWhenSwitchedBackToForeground";
|
| +
|
| +// The histogram to record the number of page loads before an evicted tab is
|
| +// selected.
|
| +const char kPageLoadsBeforeEvictedTabSelected[] =
|
| + "Tab.PageLoadsSinceLastSwitchToEvictedTab";
|
| +
|
| +// Records the time it takes for an evicted tab to reload.
|
| +const char kEvictedTabReloadTime[] = "Tab.RestoreTime";
|
| +
|
| +// Records success vs failure of an evicted tab's reload.
|
| +const char kEvictedTabReloadSuccessRate[] = "Tab.RestoreResult";
|
| +
|
| +// Records whether or not the user switched tabs before an evicted tab finished
|
| +// reloading.
|
| +const char kDidUserWaitForEvictedTabReload[] = "Tab.RestoreUserPersistence";
|
| +
|
| +// The name of the histogram that records time intervals between tab restores.
|
| +const char kTimeBetweenRestores[] = "Tabs.TimeBetweenRestores";
|
| +
|
| +// The name of the histogram that records time intervals since the last restore.
|
| +const char kTimeAfterLastRestore[] = "Tabs.TimeAfterLastRestore";
|
| +
|
| +// Name of histogram to record whether a memory warning had been recently
|
| +// received when a renderer termination occurred.
|
| +const char kRendererTerminationSawMemoryWarning[] =
|
| + "Tab.RendererTermination.RecentlyReceivedMemoryWarning";
|
| +
|
| +// Name of histogram to record the number of alive renderers when a renderer
|
| +// termination is received.
|
| +const char kRendererTerminationAliveRenderers[] =
|
| + "Tab.RendererTermination.AliveRenderersCount";
|
| +
|
| +// Name of histogram to record the number of renderers that were alive shortly
|
| +// before a renderer termination. This metric is being recorded in case the OS
|
| +// kills renderers in batches.
|
| +const char kRendererTerminationRecentlyAliveRenderers[] =
|
| + "Tab.RendererTermination.RecentlyAliveRenderersCount";
|
| +
|
| +// The recently alive renderer count metric counts all renderers that were alive
|
| +// x seconds before a renderer termination. |kSecondsBeforeRendererTermination|
|
| +// specifies x.
|
| +const int kSecondsBeforeRendererTermination = 2;
|
| +
|
| +TabUsageRecorder::TabUsageRecorder(id<TabUsageRecorderDelegate> delegate)
|
| + : page_loads_(0), evicted_tab_(NULL), recorder_delegate_(delegate) {
|
| + restore_start_time_ = base::TimeTicks::Now();
|
| +}
|
| +
|
| +TabUsageRecorder::~TabUsageRecorder() {}
|
| +
|
| +void TabUsageRecorder::InitialRestoredTabs(Tab* active_tab, NSArray* tabs) {
|
| +#if !defined(NDEBUG)
|
| + // Debugging check to ensure this is called at most once per run.
|
| + // Specifically, this function is called in either of two cases:
|
| + // 1. For a normal (not post-crash launch), during the tab model's creation.
|
| + // It assumes that the tab model will not be deleted and recreated during the
|
| + // application's lifecycle even if the app is backgrounded/foregrounded.
|
| + // 2. For a post-crash launch, when the session is restored. In that case,
|
| + // the tab model will not have been created with existing tabs, so this
|
| + // function will not have been called during its creation.
|
| + static bool kColdStartTabsRecorded = false;
|
| + static dispatch_once_t once = 0;
|
| + dispatch_once(&once, ^{
|
| + DCHECK(kColdStartTabsRecorded == false);
|
| + kColdStartTabsRecorded = true;
|
| + });
|
| +#endif
|
| +
|
| + // Do not set eviction reason on active tab since it will be reloaded without
|
| + // being processed as a switch to the foreground tab.
|
| + for (Tab* tab in tabs) {
|
| + if (tab != active_tab) {
|
| + base::WeakNSObject<Tab> weak_tab(tab);
|
| + evicted_tabs_[weak_tab] = EVICTED_DUE_TO_COLD_START;
|
| + }
|
| + }
|
| +}
|
| +
|
| +void TabUsageRecorder::TabCreatedForSelection(Tab* tab) {
|
| + tab_created_selected_ = tab;
|
| +}
|
| +
|
| +void TabUsageRecorder::SetDelegate(id<TabUsageRecorderDelegate> delegate) {
|
| + recorder_delegate_.reset(delegate);
|
| +}
|
| +
|
| +void TabUsageRecorder::RecordTabSwitched(Tab* old_tab, Tab* new_tab) {
|
| + // If a tab was created to be selected, and is selected shortly thereafter,
|
| + // it should not add its state to the "kSelectedTabHistogramName" metric.
|
| + // |tab_created_selected_| is reset at the first tab switch seen after it was
|
| + // created, regardless of whether or not it was the tab selected.
|
| + bool was_just_created = new_tab == tab_created_selected_;
|
| + tab_created_selected_ = NULL;
|
| +
|
| + // Disregard reselecting the same tab, but only if the mode has not changed
|
| + // since the last time this tab was selected. I.e. going to incognito and
|
| + // back to normal mode is an event we want to track, but simply going into
|
| + // stack view and back out, without changing modes, isn't.
|
| + if (new_tab == old_tab && new_tab != mode_switch_tab_)
|
| + return;
|
| + mode_switch_tab_ = NULL;
|
| +
|
| + // Disregard opening a new tab with no previous tab.
|
| + if (!old_tab)
|
| + return;
|
| +
|
| + // Before knowledge of the previous tab, |old_tab|, is lost, see if it is a
|
| + // previously-evicted tab still reloading. If it is, record that the
|
| + // user did not wait for the evicted tab to finish reloading.
|
| + if (old_tab == evicted_tab_ && old_tab != new_tab &&
|
| + evicted_tab_reload_start_time_ != base::TimeTicks()) {
|
| + UMA_HISTOGRAM_ENUMERATION(kDidUserWaitForEvictedTabReload,
|
| + USER_DID_NOT_WAIT, USER_BEHAVIOR_COUNT);
|
| + }
|
| + ResetEvictedTab();
|
| +
|
| + if (ShouldIgnoreTab(new_tab) || was_just_created)
|
| + return;
|
| +
|
| + // Should never happen. Keeping the check to ensure that the prerender logic
|
| + // is never overlooked, should behavior at the tab_model level change.
|
| + DCHECK(![new_tab isPrerenderTab]);
|
| +
|
| + TabStateWhenSelected tab_state = ExtractTabState(new_tab);
|
| + if (tab_state != IN_MEMORY) {
|
| + // Keep track of the current 'evicted' tab.
|
| + evicted_tab_ = new_tab;
|
| + evicted_tab_state_ = tab_state;
|
| + UMA_HISTOGRAM_COUNTS(kPageLoadsBeforeEvictedTabSelected, page_loads_);
|
| + ResetPageLoads();
|
| + }
|
| +
|
| + UMA_HISTOGRAM_ENUMERATION(kSelectedTabHistogramName, tab_state,
|
| + TAB_STATE_COUNT);
|
| +}
|
| +
|
| +void TabUsageRecorder::RecordPrimaryTabModelChange(BOOL primary_tab_model,
|
| + Tab* active_tab) {
|
| + if (primary_tab_model) {
|
| + // User just came back to this tab model, so record a tab selection even
|
| + // though the current tab was reselected.
|
| + if (mode_switch_tab_ == active_tab)
|
| + RecordTabSwitched(active_tab, active_tab);
|
| + } else {
|
| + // Keep track of the selected tab when this tab model is moved to
|
| + // background. This way when the tab model is moved to the foreground, and
|
| + // the current tab reselected, it is handled as a tab selection rather than
|
| + // a no-op.
|
| + mode_switch_tab_ = active_tab;
|
| + }
|
| +}
|
| +
|
| +void TabUsageRecorder::RecordPageLoadStart(Tab* tab) {
|
| + if (!ShouldIgnoreTab(tab)) {
|
| + page_loads_++;
|
| + if (![[tab webController] isViewAlive]) {
|
| + // On the iPad, there is no notification that a tab is being re-selected
|
| + // after changing modes. This catches the case where the pre-incognito
|
| + // selected tab is selected again when leaving incognito mode.
|
| + if (mode_switch_tab_ == tab)
|
| + RecordTabSwitched(tab, tab);
|
| + if (evicted_tab_ == tab)
|
| + RecordRestoreStartTime();
|
| + }
|
| + } else {
|
| + // If there is a currently-evicted tab reloading, make sure it is recorded
|
| + // that the user did not wait for it to load.
|
| + if (evicted_tab_ && evicted_tab_reload_start_time_ != base::TimeTicks()) {
|
| + UMA_HISTOGRAM_ENUMERATION(kDidUserWaitForEvictedTabReload,
|
| + USER_DID_NOT_WAIT, USER_BEHAVIOR_COUNT);
|
| + }
|
| + ResetEvictedTab();
|
| + }
|
| +}
|
| +
|
| +void TabUsageRecorder::RecordPageLoadDone(Tab* tab, bool success) {
|
| + if (!tab)
|
| + return;
|
| + if (tab == evicted_tab_) {
|
| + if (success) {
|
| + LOCAL_HISTOGRAM_TIMES(
|
| + kEvictedTabReloadTime,
|
| + base::TimeTicks::Now() - evicted_tab_reload_start_time_);
|
| + }
|
| + UMA_HISTOGRAM_ENUMERATION(kEvictedTabReloadSuccessRate,
|
| + success ? LOAD_SUCCESS : LOAD_FAILURE,
|
| + LOAD_DONE_STATE_COUNT);
|
| +
|
| + UMA_HISTOGRAM_ENUMERATION(kDidUserWaitForEvictedTabReload, USER_WAITED,
|
| + USER_BEHAVIOR_COUNT);
|
| + ResetEvictedTab();
|
| + }
|
| +}
|
| +
|
| +void TabUsageRecorder::RecordReload(Tab* tab) {
|
| + if (!ShouldIgnoreTab(tab)) {
|
| + page_loads_++;
|
| + }
|
| +}
|
| +
|
| +void TabUsageRecorder::RendererTerminated(Tab* terminated_tab, bool visible) {
|
| + if (!visible) {
|
| + DCHECK(!TabAlreadyEvicted(terminated_tab));
|
| + base::WeakNSObject<Tab> weak_tab(terminated_tab);
|
| + evicted_tabs_[weak_tab] = EVICTED_DUE_TO_RENDERER_TERMINATION;
|
| + }
|
| + base::TimeTicks now = base::TimeTicks::Now();
|
| + termination_timestamps_.push_back(now);
|
| +
|
| + // Log if a memory warning was seen recently.
|
| + NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
|
| + BOOL saw_memory_warning =
|
| + [defaults boolForKey:previous_session_info_constants::
|
| + kDidSeeMemoryWarningShortlyBeforeTerminating];
|
| + UMA_HISTOGRAM_BOOLEAN(kRendererTerminationSawMemoryWarning,
|
| + saw_memory_warning);
|
| +
|
| + // Log number of live tabs after the renderer termination. This count does not
|
| + // include |terminated_tab_|.
|
| + NSUInteger live_tabs_count = [recorder_delegate_ liveTabsCount];
|
| + UMA_HISTOGRAM_COUNTS_100(kRendererTerminationAliveRenderers, live_tabs_count);
|
| +
|
| + // Clear |termination_timestamps_| of timestamps older than
|
| + // |kSecondsBeforeRendererTermination| ago.
|
| + base::TimeDelta seconds_before =
|
| + base::TimeDelta::FromSeconds(kSecondsBeforeRendererTermination);
|
| + base::TimeTicks timestamp_boundary = now - seconds_before;
|
| + while (termination_timestamps_.front() < timestamp_boundary) {
|
| + termination_timestamps_.pop_front();
|
| + }
|
| +
|
| + // Log number of recently alive tabs, where recently alive is defined to mean
|
| + // alive within the past |kSecondsBeforeRendererTermination|.
|
| + NSUInteger recently_live_tabs_count =
|
| + live_tabs_count + termination_timestamps_.size();
|
| + UMA_HISTOGRAM_COUNTS_100(kRendererTerminationRecentlyAliveRenderers,
|
| + recently_live_tabs_count);
|
| +}
|
| +
|
| +void TabUsageRecorder::AppDidEnterBackground() {
|
| + base::TimeTicks time_now = base::TimeTicks::Now();
|
| + LOCAL_HISTOGRAM_TIMES(kTimeAfterLastRestore, time_now - restore_start_time_);
|
| +
|
| + if (evicted_tab_ && evicted_tab_reload_start_time_ != base::TimeTicks()) {
|
| + UMA_HISTOGRAM_ENUMERATION(kDidUserWaitForEvictedTabReload, USER_LEFT_CHROME,
|
| + USER_BEHAVIOR_COUNT);
|
| + ResetEvictedTab();
|
| + }
|
| + ClearDeletedTabs();
|
| +}
|
| +
|
| +void TabUsageRecorder::AppWillEnterForeground() {
|
| + ClearDeletedTabs();
|
| + restore_start_time_ = base::TimeTicks::Now();
|
| +}
|
| +
|
| +void TabUsageRecorder::ResetPageLoads() {
|
| + page_loads_ = 0;
|
| +}
|
| +
|
| +int TabUsageRecorder::EvictedTabsMapSize() {
|
| + return evicted_tabs_.size();
|
| +}
|
| +
|
| +void TabUsageRecorder::ResetAll() {
|
| + ResetEvictedTab();
|
| + ResetPageLoads();
|
| + evicted_tabs_.clear();
|
| +}
|
| +
|
| +void TabUsageRecorder::ResetEvictedTab() {
|
| + evicted_tab_ = NULL;
|
| + evicted_tab_state_ = IN_MEMORY;
|
| + evicted_tab_reload_start_time_ = base::TimeTicks();
|
| +}
|
| +
|
| +bool TabUsageRecorder::ShouldIgnoreTab(Tab* tab) {
|
| + // Do not count chrome:// urls to avoid data noise. For example, if they were
|
| + // counted, every new tab created would add noise to the page load count.
|
| + return [tab url].SchemeIs(kChromeUIScheme);
|
| +}
|
| +
|
| +bool TabUsageRecorder::TabAlreadyEvicted(Tab* tab) {
|
| + base::WeakNSObject<Tab> weak_tab(tab);
|
| + auto tab_item = evicted_tabs_.find(weak_tab);
|
| + return tab_item != evicted_tabs_.end();
|
| +}
|
| +
|
| +TabUsageRecorder::TabStateWhenSelected TabUsageRecorder::ExtractTabState(
|
| + Tab* tab) {
|
| + if ([[tab webController] isViewAlive])
|
| + return IN_MEMORY;
|
| +
|
| + base::WeakNSObject<Tab> weak_tab(tab);
|
| + auto tab_item = evicted_tabs_.find(weak_tab);
|
| + if (tab_item != evicted_tabs_.end()) {
|
| + TabStateWhenSelected tabState = tab_item->second;
|
| + evicted_tabs_.erase(weak_tab);
|
| + return tabState;
|
| + }
|
| + return EVICTED;
|
| +}
|
| +
|
| +void TabUsageRecorder::RecordRestoreStartTime() {
|
| + base::TimeTicks time_now = base::TimeTicks::Now();
|
| + // Record the time delta since the last eviction reload was seen.
|
| + LOCAL_HISTOGRAM_TIMES(kTimeBetweenRestores, time_now - restore_start_time_);
|
| + restore_start_time_ = time_now;
|
| + evicted_tab_reload_start_time_ = time_now;
|
| +}
|
| +
|
| +void TabUsageRecorder::ClearDeletedTabs() {
|
| + base::WeakNSObject<Tab> empty_tab(nil);
|
| + auto tab_item = evicted_tabs_.find(empty_tab);
|
| + while (tab_item != evicted_tabs_.end()) {
|
| + evicted_tabs_.erase(empty_tab);
|
| + tab_item = evicted_tabs_.find(empty_tab);
|
| + }
|
| +}
|
|
|