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); |
+ } |
+} |