1 // Copyright 2017 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 "chrome/browser/resource_coordinator/tab_manager_stats_collector.h"
6
7 #include <cstdint>
8 #include <memory>
9 #include <unordered_set>
10 #include <utility>
11
12 #include "base/atomic_sequence_num.h"
13 #include "base/bind.h"
14 #include "base/memory/ptr_util.h"
15 #include "base/metrics/histogram.h"
16 #include "base/metrics/histogram_macros.h"
17 #include "base/rand_util.h"
18 #include "base/stl_util.h"
19 #include "base/threading/sequenced_task_runner_handle.h"
20 #include "base/time/time.h"
21 #include "build/build_config.h"
22 #include "chrome/browser/browser_process.h"
23 #include "chrome/browser/resource_coordinator/lifecycle_unit_state.mojom.h"
24 #include "chrome/browser/resource_coordinator/tab_helper.h"
25 #include "chrome/browser/resource_coordinator/tab_lifecycle_unit_external.h"
26 #include "chrome/browser/resource_coordinator/tab_lifecycle_unit_source.h"
27 #include "chrome/browser/resource_coordinator/tab_manager_web_contents_data.h"
28 #include "chrome/browser/resource_coordinator/time.h"
29 #include "chrome/browser/sessions/session_restore.h"
30 #include "content/public/browser/browser_thread.h"
31 #include "content/public/browser/navigation_entry.h"
32 #include "content/public/browser/swap_metrics_driver.h"
33 #include "services/metrics/public/cpp/ukm_builders.h"
34 #include "services/metrics/public/cpp/ukm_recorder.h"
35
36 namespace resource_coordinator {
37
38 namespace {
39
40 using LoadingState = TabLoadTracker::LoadingState;
41
42 const char* const kSessionTypeName[] = {"SessionRestore",
43 "BackgroundTabOpening"};
44
GetUkmSourceId(content::WebContents * contents)45 ukm::SourceId GetUkmSourceId(content::WebContents* contents) {
46 resource_coordinator::ResourceCoordinatorTabHelper* observer =
47 resource_coordinator::ResourceCoordinatorTabHelper::FromWebContents(
48 contents);
49 if (!observer)
50 return ukm::kInvalidSourceId;
51
52 return observer->ukm_source_id();
53 }
54
55 } // namespace
56
Reset()57 void TabManagerStatsCollector::BackgroundTabCountStats::Reset() {
58 tab_count = 0u;
59 tab_paused_count = 0u;
60 tab_load_auto_started_count = 0u;
61 tab_load_user_initiated_count = 0u;
62 }
63
64 class TabManagerStatsCollector::SwapMetricsDelegate
65 : public content::SwapMetricsDriver::Delegate {
66 public:
SwapMetricsDelegate(TabManagerStatsCollector * tab_manager_stats_collector,SessionType type)67 explicit SwapMetricsDelegate(
68 TabManagerStatsCollector* tab_manager_stats_collector,
69 SessionType type)
70 : tab_manager_stats_collector_(tab_manager_stats_collector),
71 session_type_(type) {}
72
73 ~SwapMetricsDelegate() override = default;
74
OnSwapInCount(uint64_t count,base::TimeDelta interval)75 void OnSwapInCount(uint64_t count, base::TimeDelta interval) override {
76 tab_manager_stats_collector_->RecordSwapMetrics(
77 session_type_, "SwapInPerSecond", count, interval);
78 }
79
OnSwapOutCount(uint64_t count,base::TimeDelta interval)80 void OnSwapOutCount(uint64_t count, base::TimeDelta interval) override {
81 tab_manager_stats_collector_->RecordSwapMetrics(
82 session_type_, "SwapOutPerSecond", count, interval);
83 }
84
OnDecompressedPageCount(uint64_t count,base::TimeDelta interval)85 void OnDecompressedPageCount(uint64_t count,
86 base::TimeDelta interval) override {
87 tab_manager_stats_collector_->RecordSwapMetrics(
88 session_type_, "DecompressedPagesPerSecond", count, interval);
89 }
90
OnCompressedPageCount(uint64_t count,base::TimeDelta interval)91 void OnCompressedPageCount(uint64_t count,
92 base::TimeDelta interval) override {
93 tab_manager_stats_collector_->RecordSwapMetrics(
94 session_type_, "CompressedPagesPerSecond", count, interval);
95 }
96
OnUpdateMetricsFailed()97 void OnUpdateMetricsFailed() override {
98 tab_manager_stats_collector_->OnUpdateSwapMetricsFailed();
99 }
100
101 private:
102 TabManagerStatsCollector* tab_manager_stats_collector_;
103 const SessionType session_type_;
104 };
105
TabManagerStatsCollector()106 TabManagerStatsCollector::TabManagerStatsCollector() {
107 SessionRestore::AddObserver(this);
108 }
109
~TabManagerStatsCollector()110 TabManagerStatsCollector::~TabManagerStatsCollector() {
111 SessionRestore::RemoveObserver(this);
112 }
113
RecordSwitchToTab(content::WebContents * old_contents,content::WebContents * new_contents)114 void TabManagerStatsCollector::RecordSwitchToTab(
115 content::WebContents* old_contents,
116 content::WebContents* new_contents) {
117 if (!is_session_restore_loading_tabs_ &&
118 !is_in_background_tab_opening_session_) {
119 return;
120 }
121
122 if (IsInOverlappedSession())
123 return;
124
125 auto* new_data = TabManager::WebContentsData::FromWebContents(new_contents);
126 DCHECK(new_data);
127
128 if (is_session_restore_loading_tabs_) {
129 UMA_HISTOGRAM_ENUMERATION(
130 kHistogramSessionRestoreSwitchToTab,
131 static_cast<int32_t>(new_data->tab_loading_state()),
132 static_cast<int32_t>(LoadingState::kMaxValue) + 1);
133 }
134 if (is_in_background_tab_opening_session_) {
135 UMA_HISTOGRAM_ENUMERATION(
136 kHistogramBackgroundTabOpeningSwitchToTab,
137 static_cast<int32_t>(new_data->tab_loading_state()),
138 static_cast<int32_t>(LoadingState::kMaxValue) + 1);
139 }
140
141 if (old_contents)
142 foreground_contents_switched_to_times_.erase(old_contents);
143 DCHECK(!base::Contains(foreground_contents_switched_to_times_, new_contents));
144 if (new_data->tab_loading_state() != LoadingState::LOADED) {
145 foreground_contents_switched_to_times_.insert(
146 std::make_pair(new_contents, NowTicks()));
147 }
148 }
149
RecordBackgroundTabCount()150 void TabManagerStatsCollector::RecordBackgroundTabCount() {
151 DCHECK(is_in_background_tab_opening_session_);
152
153 if (!is_overlapping_background_tab_opening_) {
154 UMA_HISTOGRAM_COUNTS_100(kHistogramBackgroundTabOpeningTabCount,
155 background_tab_count_stats_.tab_count);
156 UMA_HISTOGRAM_COUNTS_100(kHistogramBackgroundTabOpeningTabPausedCount,
157 background_tab_count_stats_.tab_paused_count);
158 UMA_HISTOGRAM_COUNTS_100(
159 kHistogramBackgroundTabOpeningTabLoadAutoStartedCount,
160 background_tab_count_stats_.tab_load_auto_started_count);
161 UMA_HISTOGRAM_COUNTS_100(
162 kHistogramBackgroundTabOpeningTabLoadUserInitiatedCount,
163 background_tab_count_stats_.tab_load_user_initiated_count);
164 }
165 }
166
OnSessionRestoreStartedLoadingTabs()167 void TabManagerStatsCollector::OnSessionRestoreStartedLoadingTabs() {
168 DCHECK(!is_session_restore_loading_tabs_);
169 UpdateSessionAndSequence();
170
171 CreateAndInitSwapMetricsDriverIfNeeded(SessionType::kSessionRestore);
172
173 is_session_restore_loading_tabs_ = true;
174 ClearStatsWhenInOverlappedSession();
175 }
176
OnSessionRestoreFinishedLoadingTabs()177 void TabManagerStatsCollector::OnSessionRestoreFinishedLoadingTabs() {
178 DCHECK(is_session_restore_loading_tabs_);
179
180 UMA_HISTOGRAM_BOOLEAN(kHistogramSessionOverlapSessionRestore,
181 is_overlapping_session_restore_ ? true : false);
182 if (swap_metrics_driver_)
183 swap_metrics_driver_->UpdateMetrics();
184
185 is_session_restore_loading_tabs_ = false;
186 is_overlapping_session_restore_ = false;
187 }
188
OnBackgroundTabOpeningSessionStarted()189 void TabManagerStatsCollector::OnBackgroundTabOpeningSessionStarted() {
190 DCHECK(!is_in_background_tab_opening_session_);
191 UpdateSessionAndSequence();
192 background_tab_count_stats_.Reset();
193 CreateAndInitSwapMetricsDriverIfNeeded(SessionType::kBackgroundTabOpening);
194
195 is_in_background_tab_opening_session_ = true;
196 ClearStatsWhenInOverlappedSession();
197 }
198
OnBackgroundTabOpeningSessionEnded()199 void TabManagerStatsCollector::OnBackgroundTabOpeningSessionEnded() {
200 DCHECK(is_in_background_tab_opening_session_);
201
202 UMA_HISTOGRAM_BOOLEAN(kHistogramSessionOverlapBackgroundTabOpening,
203 is_overlapping_background_tab_opening_ ? true : false);
204 if (swap_metrics_driver_)
205 swap_metrics_driver_->UpdateMetrics();
206 RecordBackgroundTabCount();
207
208 is_in_background_tab_opening_session_ = false;
209 is_overlapping_background_tab_opening_ = false;
210 }
211
CreateAndInitSwapMetricsDriverIfNeeded(SessionType type)212 void TabManagerStatsCollector::CreateAndInitSwapMetricsDriverIfNeeded(
213 SessionType type) {
214 if (IsInOverlappedSession()) {
215 swap_metrics_driver_ = nullptr;
216 return;
217 }
218
219 // Always create a new instance in case there is a SessionType change because
220 // this is shared between SessionRestore and BackgroundTabOpening.
221 swap_metrics_driver_ = content::SwapMetricsDriver::Create(
222 base::WrapUnique<content::SwapMetricsDriver::Delegate>(
223 new SwapMetricsDelegate(this, type)),
224 base::TimeDelta::FromSeconds(0));
225 // The driver could still be null on a platform with no swap driver support.
226 if (swap_metrics_driver_)
227 swap_metrics_driver_->InitializeMetrics();
228 }
229
RecordSwapMetrics(SessionType type,const std::string & metric_name,uint64_t count,base::TimeDelta interval)230 void TabManagerStatsCollector::RecordSwapMetrics(SessionType type,
231 const std::string& metric_name,
232 uint64_t count,
233 base::TimeDelta interval) {
234 base::HistogramBase* histogram = base::Histogram::FactoryGet(
235 "TabManager.Experimental." + std::string(kSessionTypeName[type]) + "." +
236 metric_name,
237 1, // minimum
238 10000, // maximum
239 50, // bucket_count
240 base::HistogramBase::kUmaTargetedHistogramFlag);
241 histogram->Add(static_cast<double>(count) / interval.InSecondsF());
242 }
243
OnUpdateSwapMetricsFailed()244 void TabManagerStatsCollector::OnUpdateSwapMetricsFailed() {
245 swap_metrics_driver_ = nullptr;
246 }
247
OnDidStartMainFrameNavigation(content::WebContents * contents)248 void TabManagerStatsCollector::OnDidStartMainFrameNavigation(
249 content::WebContents* contents) {
250 foreground_contents_switched_to_times_.erase(contents);
251 }
252
OnWillLoadNextBackgroundTab(bool timeout)253 void TabManagerStatsCollector::OnWillLoadNextBackgroundTab(bool timeout) {
254 UMA_HISTOGRAM_BOOLEAN(kHistogramBackgroundTabOpeningTabLoadTimeout, timeout);
255 }
256
OnTabIsLoaded(content::WebContents * contents)257 void TabManagerStatsCollector::OnTabIsLoaded(content::WebContents* contents) {
258 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
259
260 if (!base::Contains(foreground_contents_switched_to_times_, contents))
261 return;
262
263 base::TimeDelta switch_load_time =
264 NowTicks() - foreground_contents_switched_to_times_[contents];
265 ukm::SourceId ukm_source_id = GetUkmSourceId(contents);
266 if (is_session_restore_loading_tabs_ && !IsInOverlappedSession()) {
267 UMA_HISTOGRAM_MEDIUM_TIMES(kHistogramSessionRestoreTabSwitchLoadTime,
268 switch_load_time);
269
270 if (ukm_source_id != ukm::kInvalidSourceId) {
271 ukm::builders::
272 TabManager_Experimental_SessionRestore_TabSwitchLoadStopped(
273 ukm_source_id)
274 .SetSequenceId(sequence_++)
275 .SetSessionRestoreSessionId(session_id_)
276 .SetSessionRestoreTabCount(
277 g_browser_process->GetTabManager()->restored_tab_count())
278 .SetSystemTabCount(
279 g_browser_process->GetTabManager()->GetTabCount())
280 .SetTabSwitchLoadTime(switch_load_time.InMilliseconds())
281 .Record(ukm::UkmRecorder::Get());
282 }
283 }
284 if (is_in_background_tab_opening_session_ && !IsInOverlappedSession()) {
285 UMA_HISTOGRAM_MEDIUM_TIMES(kHistogramBackgroundTabOpeningTabSwitchLoadTime,
286 switch_load_time);
287
288 if (ukm_source_id != ukm::kInvalidSourceId) {
289 ukm::builders::
290 TabManager_Experimental_BackgroundTabOpening_TabSwitchLoadStopped(
291 ukm_source_id)
292 .SetBackgroundTabLoadingCount(
293 g_browser_process->GetTabManager()
294 ->GetBackgroundTabLoadingCount())
295 .SetBackgroundTabOpeningSessionId(session_id_)
296 .SetBackgroundTabPendingCount(
297 g_browser_process->GetTabManager()
298 ->GetBackgroundTabPendingCount())
299 .SetSequenceId(sequence_++)
300 .SetSystemTabCount(
301 g_browser_process->GetTabManager()->GetTabCount())
302 .SetTabSwitchLoadTime(switch_load_time.InMilliseconds())
303 .Record(ukm::UkmRecorder::Get());
304 }
305 }
306
307 foreground_contents_switched_to_times_.erase(contents);
308 }
309
OnWebContentsDestroyed(content::WebContents * contents)310 void TabManagerStatsCollector::OnWebContentsDestroyed(
311 content::WebContents* contents) {
312 foreground_contents_switched_to_times_.erase(contents);
313 }
314
IsInOverlappedSession()315 bool TabManagerStatsCollector::IsInOverlappedSession() {
316 return is_session_restore_loading_tabs_ &&
317 is_in_background_tab_opening_session_;
318 }
319
ClearStatsWhenInOverlappedSession()320 void TabManagerStatsCollector::ClearStatsWhenInOverlappedSession() {
321 if (!IsInOverlappedSession())
322 return;
323
324 swap_metrics_driver_ = nullptr;
325 foreground_contents_switched_to_times_.clear();
326 background_tab_count_stats_.Reset();
327
328 is_overlapping_session_restore_ = true;
329 is_overlapping_background_tab_opening_ = true;
330 }
331
UpdateSessionAndSequence()332 void TabManagerStatsCollector::UpdateSessionAndSequence() {
333 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
334
335 // This function is used by both SessionRestore and BackgroundTabOpening. This
336 // is fine because we do not report any metric when those two overlap.
337 ++session_id_;
338 sequence_ = 0;
339 }
340
341 // static
RecordDecisionDetails(LifecycleUnit * lifecycle_unit,const DecisionDetails & decision_details,LifecycleUnitState target_state)342 void TabManagerStatsCollector::RecordDecisionDetails(
343 LifecycleUnit* lifecycle_unit,
344 const DecisionDetails& decision_details,
345 LifecycleUnitState target_state) {
346 ukm::SourceId ukm_source_id = lifecycle_unit->GetUkmSourceId();
347 if (ukm_source_id == ukm::kInvalidSourceId)
348 return;
349
350 // Don't log anything for invalid decision details (trivial reasons: crashed
351 // tabs, navigations not yet committed, etc).
352 if (decision_details.reasons().empty())
353 return;
354
355 ukm::builders::TabManager_LifecycleStateChange builder(ukm_source_id);
356
357 builder.SetOldLifecycleState(
358 static_cast<int64_t>(lifecycle_unit->GetState()));
359 builder.SetNewLifecycleState(static_cast<int64_t>(target_state));
360 // No LifecycleStateChangeReason is set right now, indicating that this is a
361 // theoretical state change rather than an actual one. This differentiates
362 // sampled lifecycle transitions from actual ones.
363
364 // We only currently report transitions for tabs, so this lookup should never
365 // fail. It will start failing once we add ARC processes as LifecycleUnits.
366 // TODO(chrisha): This should be time since the navigation was committed (the
367 // load started), but that information is currently only persisted inside the
368 // CU-graph. Using time since navigation finished is a cheap approximation for
369 // the time being.
370 auto* tab = lifecycle_unit->AsTabLifecycleUnitExternal();
371 auto* contents = tab->GetWebContents();
372 auto* nav_entry = contents->GetController().GetLastCommittedEntry();
373 if (nav_entry) {
374 auto timestamp = nav_entry->GetTimestamp();
375 if (!timestamp.is_null()) {
376 auto elapsed = base::Time::Now() - timestamp;
377 builder.SetTimeSinceNavigationMs(elapsed.InMilliseconds());
378 }
379 }
380
381 // Set visibility related data.
382 // |time_since_visible| is:
383 // - Zero if the LifecycleUnit is currently visible.
384 // - Time since creation if the LifecycleUnit was never visible.
385 // - Time since visible if the LifecycleUnit was visible in the past.
386 auto visibility = lifecycle_unit->GetVisibility();
387 base::TimeDelta time_since_visible; // Zero.
388 if (visibility != content::Visibility::VISIBLE)
389 time_since_visible = NowTicks() - lifecycle_unit->GetWallTimeWhenHidden();
390 builder.SetTimeSinceVisibilityStateChangeMs(
391 time_since_visible.InMilliseconds());
392 builder.SetVisibilityState(static_cast<int64_t>(visibility));
393
394 // This populates all of the relevant Success/Failure fields, as well as
395 // Outcome.
396 decision_details.Populate(&builder);
397
398 builder.Record(ukm::UkmRecorder::Get());
399 }
400
401 // static
402 const char TabManagerStatsCollector::kHistogramSessionRestoreSwitchToTab[] =
403 "TabManager.SessionRestore.SwitchToTab";
404
405 // static
406 const char
407 TabManagerStatsCollector::kHistogramBackgroundTabOpeningSwitchToTab[] =
408 "TabManager.BackgroundTabOpening.SwitchToTab";
409
410 // static
411 const char
412 TabManagerStatsCollector::kHistogramSessionRestoreTabSwitchLoadTime[] =
413 "TabManager.Experimental.SessionRestore.TabSwitchLoadTime."
414 "UntilTabIsLoaded";
415
416 // static
417 const char TabManagerStatsCollector::
418 kHistogramBackgroundTabOpeningTabSwitchLoadTime[] =
419 "TabManager.Experimental.BackgroundTabOpening.TabSwitchLoadTime."
420 "UntilTabIsLoaded";
421
422 // static
423 const char TabManagerStatsCollector::kHistogramBackgroundTabOpeningTabCount[] =
424 "TabManager.BackgroundTabOpening.TabCount";
425
426 // static
427 const char
428 TabManagerStatsCollector::kHistogramBackgroundTabOpeningTabPausedCount[] =
429 "TabManager.BackgroundTabOpening.TabPausedCount";
430
431 // static
432 const char TabManagerStatsCollector::
433 kHistogramBackgroundTabOpeningTabLoadAutoStartedCount[] =
434 "TabManager.BackgroundTabOpening.TabLoadAutoStartedCount";
435
436 // static
437 const char TabManagerStatsCollector::
438 kHistogramBackgroundTabOpeningTabLoadUserInitiatedCount[] =
439 "TabManager.BackgroundTabOpening.TabLoadUserInitiatedCount";
440
441 // static
442 const char
443 TabManagerStatsCollector::kHistogramBackgroundTabOpeningTabLoadTimeout[] =
444 "TabManager.BackgroundTabOpening.TabLoadTimeout";
445
446 // static
447 const char TabManagerStatsCollector::kHistogramSessionOverlapSessionRestore[] =
448 "TabManager.SessionOverlap.SessionRestore";
449
450 // static
451 const char
452 TabManagerStatsCollector::kHistogramSessionOverlapBackgroundTabOpening[] =
453 "TabManager.SessionOverlap.BackgroundTabOpening";
454
455 // static
456 constexpr base::TimeDelta
457 TabManagerStatsCollector::kLowFrequencySamplingInterval;
458
459 } // namespace resource_coordinator
460