OLD | NEW |
(Empty) | |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "ios/chrome/browser/metrics/tab_usage_recorder.h" |
| 6 |
| 7 #include "base/metrics/histogram.h" |
| 8 #include "ios/chrome/browser/chrome_url_constants.h" |
| 9 #import "ios/chrome/browser/metrics/previous_session_info.h" |
| 10 #import "ios/chrome/browser/tabs/tab.h" |
| 11 #import "ios/web/web_state/ui/crw_web_controller.h" |
| 12 |
| 13 const char kTabUsageHistogramPrefix[] = "Tab"; |
| 14 |
| 15 // The histogram recording the state of the tab the user switches to. |
| 16 const char kSelectedTabHistogramName[] = |
| 17 "Tab.StatusWhenSwitchedBackToForeground"; |
| 18 |
| 19 // The histogram to record the number of page loads before an evicted tab is |
| 20 // selected. |
| 21 const char kPageLoadsBeforeEvictedTabSelected[] = |
| 22 "Tab.PageLoadsSinceLastSwitchToEvictedTab"; |
| 23 |
| 24 // Records the time it takes for an evicted tab to reload. |
| 25 const char kEvictedTabReloadTime[] = "Tab.RestoreTime"; |
| 26 |
| 27 // Records success vs failure of an evicted tab's reload. |
| 28 const char kEvictedTabReloadSuccessRate[] = "Tab.RestoreResult"; |
| 29 |
| 30 // Records whether or not the user switched tabs before an evicted tab finished |
| 31 // reloading. |
| 32 const char kDidUserWaitForEvictedTabReload[] = "Tab.RestoreUserPersistence"; |
| 33 |
| 34 // The name of the histogram that records time intervals between tab restores. |
| 35 const char kTimeBetweenRestores[] = "Tabs.TimeBetweenRestores"; |
| 36 |
| 37 // The name of the histogram that records time intervals since the last restore. |
| 38 const char kTimeAfterLastRestore[] = "Tabs.TimeAfterLastRestore"; |
| 39 |
| 40 // Name of histogram to record whether a memory warning had been recently |
| 41 // received when a renderer termination occurred. |
| 42 const char kRendererTerminationSawMemoryWarning[] = |
| 43 "Tab.RendererTermination.RecentlyReceivedMemoryWarning"; |
| 44 |
| 45 // Name of histogram to record the number of alive renderers when a renderer |
| 46 // termination is received. |
| 47 const char kRendererTerminationAliveRenderers[] = |
| 48 "Tab.RendererTermination.AliveRenderersCount"; |
| 49 |
| 50 // Name of histogram to record the number of renderers that were alive shortly |
| 51 // before a renderer termination. This metric is being recorded in case the OS |
| 52 // kills renderers in batches. |
| 53 const char kRendererTerminationRecentlyAliveRenderers[] = |
| 54 "Tab.RendererTermination.RecentlyAliveRenderersCount"; |
| 55 |
| 56 // The recently alive renderer count metric counts all renderers that were alive |
| 57 // x seconds before a renderer termination. |kSecondsBeforeRendererTermination| |
| 58 // specifies x. |
| 59 const int kSecondsBeforeRendererTermination = 2; |
| 60 |
| 61 TabUsageRecorder::TabUsageRecorder(id<TabUsageRecorderDelegate> delegate) |
| 62 : page_loads_(0), evicted_tab_(NULL), recorder_delegate_(delegate) { |
| 63 restore_start_time_ = base::TimeTicks::Now(); |
| 64 } |
| 65 |
| 66 TabUsageRecorder::~TabUsageRecorder() {} |
| 67 |
| 68 void TabUsageRecorder::InitialRestoredTabs(Tab* active_tab, NSArray* tabs) { |
| 69 #if !defined(NDEBUG) |
| 70 // Debugging check to ensure this is called at most once per run. |
| 71 // Specifically, this function is called in either of two cases: |
| 72 // 1. For a normal (not post-crash launch), during the tab model's creation. |
| 73 // It assumes that the tab model will not be deleted and recreated during the |
| 74 // application's lifecycle even if the app is backgrounded/foregrounded. |
| 75 // 2. For a post-crash launch, when the session is restored. In that case, |
| 76 // the tab model will not have been created with existing tabs, so this |
| 77 // function will not have been called during its creation. |
| 78 static bool kColdStartTabsRecorded = false; |
| 79 static dispatch_once_t once = 0; |
| 80 dispatch_once(&once, ^{ |
| 81 DCHECK(kColdStartTabsRecorded == false); |
| 82 kColdStartTabsRecorded = true; |
| 83 }); |
| 84 #endif |
| 85 |
| 86 // Do not set eviction reason on active tab since it will be reloaded without |
| 87 // being processed as a switch to the foreground tab. |
| 88 for (Tab* tab in tabs) { |
| 89 if (tab != active_tab) { |
| 90 base::WeakNSObject<Tab> weak_tab(tab); |
| 91 evicted_tabs_[weak_tab] = EVICTED_DUE_TO_COLD_START; |
| 92 } |
| 93 } |
| 94 } |
| 95 |
| 96 void TabUsageRecorder::TabCreatedForSelection(Tab* tab) { |
| 97 tab_created_selected_ = tab; |
| 98 } |
| 99 |
| 100 void TabUsageRecorder::SetDelegate(id<TabUsageRecorderDelegate> delegate) { |
| 101 recorder_delegate_.reset(delegate); |
| 102 } |
| 103 |
| 104 void TabUsageRecorder::RecordTabSwitched(Tab* old_tab, Tab* new_tab) { |
| 105 // If a tab was created to be selected, and is selected shortly thereafter, |
| 106 // it should not add its state to the "kSelectedTabHistogramName" metric. |
| 107 // |tab_created_selected_| is reset at the first tab switch seen after it was |
| 108 // created, regardless of whether or not it was the tab selected. |
| 109 bool was_just_created = new_tab == tab_created_selected_; |
| 110 tab_created_selected_ = NULL; |
| 111 |
| 112 // Disregard reselecting the same tab, but only if the mode has not changed |
| 113 // since the last time this tab was selected. I.e. going to incognito and |
| 114 // back to normal mode is an event we want to track, but simply going into |
| 115 // stack view and back out, without changing modes, isn't. |
| 116 if (new_tab == old_tab && new_tab != mode_switch_tab_) |
| 117 return; |
| 118 mode_switch_tab_ = NULL; |
| 119 |
| 120 // Disregard opening a new tab with no previous tab. |
| 121 if (!old_tab) |
| 122 return; |
| 123 |
| 124 // Before knowledge of the previous tab, |old_tab|, is lost, see if it is a |
| 125 // previously-evicted tab still reloading. If it is, record that the |
| 126 // user did not wait for the evicted tab to finish reloading. |
| 127 if (old_tab == evicted_tab_ && old_tab != new_tab && |
| 128 evicted_tab_reload_start_time_ != base::TimeTicks()) { |
| 129 UMA_HISTOGRAM_ENUMERATION(kDidUserWaitForEvictedTabReload, |
| 130 USER_DID_NOT_WAIT, USER_BEHAVIOR_COUNT); |
| 131 } |
| 132 ResetEvictedTab(); |
| 133 |
| 134 if (ShouldIgnoreTab(new_tab) || was_just_created) |
| 135 return; |
| 136 |
| 137 // Should never happen. Keeping the check to ensure that the prerender logic |
| 138 // is never overlooked, should behavior at the tab_model level change. |
| 139 DCHECK(![new_tab isPrerenderTab]); |
| 140 |
| 141 TabStateWhenSelected tab_state = ExtractTabState(new_tab); |
| 142 if (tab_state != IN_MEMORY) { |
| 143 // Keep track of the current 'evicted' tab. |
| 144 evicted_tab_ = new_tab; |
| 145 evicted_tab_state_ = tab_state; |
| 146 UMA_HISTOGRAM_COUNTS(kPageLoadsBeforeEvictedTabSelected, page_loads_); |
| 147 ResetPageLoads(); |
| 148 } |
| 149 |
| 150 UMA_HISTOGRAM_ENUMERATION(kSelectedTabHistogramName, tab_state, |
| 151 TAB_STATE_COUNT); |
| 152 } |
| 153 |
| 154 void TabUsageRecorder::RecordPrimaryTabModelChange(BOOL primary_tab_model, |
| 155 Tab* active_tab) { |
| 156 if (primary_tab_model) { |
| 157 // User just came back to this tab model, so record a tab selection even |
| 158 // though the current tab was reselected. |
| 159 if (mode_switch_tab_ == active_tab) |
| 160 RecordTabSwitched(active_tab, active_tab); |
| 161 } else { |
| 162 // Keep track of the selected tab when this tab model is moved to |
| 163 // background. This way when the tab model is moved to the foreground, and |
| 164 // the current tab reselected, it is handled as a tab selection rather than |
| 165 // a no-op. |
| 166 mode_switch_tab_ = active_tab; |
| 167 } |
| 168 } |
| 169 |
| 170 void TabUsageRecorder::RecordPageLoadStart(Tab* tab) { |
| 171 if (!ShouldIgnoreTab(tab)) { |
| 172 page_loads_++; |
| 173 if (![[tab webController] isViewAlive]) { |
| 174 // On the iPad, there is no notification that a tab is being re-selected |
| 175 // after changing modes. This catches the case where the pre-incognito |
| 176 // selected tab is selected again when leaving incognito mode. |
| 177 if (mode_switch_tab_ == tab) |
| 178 RecordTabSwitched(tab, tab); |
| 179 if (evicted_tab_ == tab) |
| 180 RecordRestoreStartTime(); |
| 181 } |
| 182 } else { |
| 183 // If there is a currently-evicted tab reloading, make sure it is recorded |
| 184 // that the user did not wait for it to load. |
| 185 if (evicted_tab_ && evicted_tab_reload_start_time_ != base::TimeTicks()) { |
| 186 UMA_HISTOGRAM_ENUMERATION(kDidUserWaitForEvictedTabReload, |
| 187 USER_DID_NOT_WAIT, USER_BEHAVIOR_COUNT); |
| 188 } |
| 189 ResetEvictedTab(); |
| 190 } |
| 191 } |
| 192 |
| 193 void TabUsageRecorder::RecordPageLoadDone(Tab* tab, bool success) { |
| 194 if (!tab) |
| 195 return; |
| 196 if (tab == evicted_tab_) { |
| 197 if (success) { |
| 198 LOCAL_HISTOGRAM_TIMES( |
| 199 kEvictedTabReloadTime, |
| 200 base::TimeTicks::Now() - evicted_tab_reload_start_time_); |
| 201 } |
| 202 UMA_HISTOGRAM_ENUMERATION(kEvictedTabReloadSuccessRate, |
| 203 success ? LOAD_SUCCESS : LOAD_FAILURE, |
| 204 LOAD_DONE_STATE_COUNT); |
| 205 |
| 206 UMA_HISTOGRAM_ENUMERATION(kDidUserWaitForEvictedTabReload, USER_WAITED, |
| 207 USER_BEHAVIOR_COUNT); |
| 208 ResetEvictedTab(); |
| 209 } |
| 210 } |
| 211 |
| 212 void TabUsageRecorder::RecordReload(Tab* tab) { |
| 213 if (!ShouldIgnoreTab(tab)) { |
| 214 page_loads_++; |
| 215 } |
| 216 } |
| 217 |
| 218 void TabUsageRecorder::RendererTerminated(Tab* terminated_tab, bool visible) { |
| 219 if (!visible) { |
| 220 DCHECK(!TabAlreadyEvicted(terminated_tab)); |
| 221 base::WeakNSObject<Tab> weak_tab(terminated_tab); |
| 222 evicted_tabs_[weak_tab] = EVICTED_DUE_TO_RENDERER_TERMINATION; |
| 223 } |
| 224 base::TimeTicks now = base::TimeTicks::Now(); |
| 225 termination_timestamps_.push_back(now); |
| 226 |
| 227 // Log if a memory warning was seen recently. |
| 228 NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; |
| 229 BOOL saw_memory_warning = |
| 230 [defaults boolForKey:previous_session_info_constants:: |
| 231 kDidSeeMemoryWarningShortlyBeforeTerminating]; |
| 232 UMA_HISTOGRAM_BOOLEAN(kRendererTerminationSawMemoryWarning, |
| 233 saw_memory_warning); |
| 234 |
| 235 // Log number of live tabs after the renderer termination. This count does not |
| 236 // include |terminated_tab_|. |
| 237 NSUInteger live_tabs_count = [recorder_delegate_ liveTabsCount]; |
| 238 UMA_HISTOGRAM_COUNTS_100(kRendererTerminationAliveRenderers, live_tabs_count); |
| 239 |
| 240 // Clear |termination_timestamps_| of timestamps older than |
| 241 // |kSecondsBeforeRendererTermination| ago. |
| 242 base::TimeDelta seconds_before = |
| 243 base::TimeDelta::FromSeconds(kSecondsBeforeRendererTermination); |
| 244 base::TimeTicks timestamp_boundary = now - seconds_before; |
| 245 while (termination_timestamps_.front() < timestamp_boundary) { |
| 246 termination_timestamps_.pop_front(); |
| 247 } |
| 248 |
| 249 // Log number of recently alive tabs, where recently alive is defined to mean |
| 250 // alive within the past |kSecondsBeforeRendererTermination|. |
| 251 NSUInteger recently_live_tabs_count = |
| 252 live_tabs_count + termination_timestamps_.size(); |
| 253 UMA_HISTOGRAM_COUNTS_100(kRendererTerminationRecentlyAliveRenderers, |
| 254 recently_live_tabs_count); |
| 255 } |
| 256 |
| 257 void TabUsageRecorder::AppDidEnterBackground() { |
| 258 base::TimeTicks time_now = base::TimeTicks::Now(); |
| 259 LOCAL_HISTOGRAM_TIMES(kTimeAfterLastRestore, time_now - restore_start_time_); |
| 260 |
| 261 if (evicted_tab_ && evicted_tab_reload_start_time_ != base::TimeTicks()) { |
| 262 UMA_HISTOGRAM_ENUMERATION(kDidUserWaitForEvictedTabReload, USER_LEFT_CHROME, |
| 263 USER_BEHAVIOR_COUNT); |
| 264 ResetEvictedTab(); |
| 265 } |
| 266 ClearDeletedTabs(); |
| 267 } |
| 268 |
| 269 void TabUsageRecorder::AppWillEnterForeground() { |
| 270 ClearDeletedTabs(); |
| 271 restore_start_time_ = base::TimeTicks::Now(); |
| 272 } |
| 273 |
| 274 void TabUsageRecorder::ResetPageLoads() { |
| 275 page_loads_ = 0; |
| 276 } |
| 277 |
| 278 int TabUsageRecorder::EvictedTabsMapSize() { |
| 279 return evicted_tabs_.size(); |
| 280 } |
| 281 |
| 282 void TabUsageRecorder::ResetAll() { |
| 283 ResetEvictedTab(); |
| 284 ResetPageLoads(); |
| 285 evicted_tabs_.clear(); |
| 286 } |
| 287 |
| 288 void TabUsageRecorder::ResetEvictedTab() { |
| 289 evicted_tab_ = NULL; |
| 290 evicted_tab_state_ = IN_MEMORY; |
| 291 evicted_tab_reload_start_time_ = base::TimeTicks(); |
| 292 } |
| 293 |
| 294 bool TabUsageRecorder::ShouldIgnoreTab(Tab* tab) { |
| 295 // Do not count chrome:// urls to avoid data noise. For example, if they were |
| 296 // counted, every new tab created would add noise to the page load count. |
| 297 return [tab url].SchemeIs(kChromeUIScheme); |
| 298 } |
| 299 |
| 300 bool TabUsageRecorder::TabAlreadyEvicted(Tab* tab) { |
| 301 base::WeakNSObject<Tab> weak_tab(tab); |
| 302 auto tab_item = evicted_tabs_.find(weak_tab); |
| 303 return tab_item != evicted_tabs_.end(); |
| 304 } |
| 305 |
| 306 TabUsageRecorder::TabStateWhenSelected TabUsageRecorder::ExtractTabState( |
| 307 Tab* tab) { |
| 308 if ([[tab webController] isViewAlive]) |
| 309 return IN_MEMORY; |
| 310 |
| 311 base::WeakNSObject<Tab> weak_tab(tab); |
| 312 auto tab_item = evicted_tabs_.find(weak_tab); |
| 313 if (tab_item != evicted_tabs_.end()) { |
| 314 TabStateWhenSelected tabState = tab_item->second; |
| 315 evicted_tabs_.erase(weak_tab); |
| 316 return tabState; |
| 317 } |
| 318 return EVICTED; |
| 319 } |
| 320 |
| 321 void TabUsageRecorder::RecordRestoreStartTime() { |
| 322 base::TimeTicks time_now = base::TimeTicks::Now(); |
| 323 // Record the time delta since the last eviction reload was seen. |
| 324 LOCAL_HISTOGRAM_TIMES(kTimeBetweenRestores, time_now - restore_start_time_); |
| 325 restore_start_time_ = time_now; |
| 326 evicted_tab_reload_start_time_ = time_now; |
| 327 } |
| 328 |
| 329 void TabUsageRecorder::ClearDeletedTabs() { |
| 330 base::WeakNSObject<Tab> empty_tab(nil); |
| 331 auto tab_item = evicted_tabs_.find(empty_tab); |
| 332 while (tab_item != evicted_tabs_.end()) { |
| 333 evicted_tabs_.erase(empty_tab); |
| 334 tab_item = evicted_tabs_.find(empty_tab); |
| 335 } |
| 336 } |
OLD | NEW |