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