Skip to content

Commit

Permalink
Introduce TabRevisitTracker
Browse files Browse the repository at this point in the history
This component listens for tab state changes and records related timing
information in UMA.

Bug: 1469337
Change-Id: If96e6b824a0572ddc3b332c07e8f63ae54a0d07c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4763494
Reviewed-by: Joe Mason <joenotcharles@google.com>
Commit-Queue: Anthony Vallée-Dubois <anthonyvd@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1184515}
  • Loading branch information
Anthony Vallee-Dubois authored and Chromium LUCI CQ committed Aug 17, 2023
1 parent 67d69c1 commit 5c7aedf
Show file tree
Hide file tree
Showing 6 changed files with 357 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include "components/performance_manager/public/decorators/tab_page_decorator.h"
#include "components/performance_manager/public/features.h"
#include "components/performance_manager/public/graph/graph.h"
#include "components/performance_manager/public/metrics/tab_revisit_tracker.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/storage_partition.h"
Expand Down Expand Up @@ -116,6 +117,8 @@ void ChromeBrowserMainExtraPartsPerformanceManager::CreatePoliciesAndDecorators(
std::make_unique<performance_manager::PageLiveStateDecorator>(
performance_manager::PageLiveStateDelegateImpl::Create()));
graph->PassToGraph(std::make_unique<performance_manager::TabPageDecorator>());
graph->PassToGraph(
std::make_unique<performance_manager::TabRevisitTracker>());

if (performance_manager::policies::WorkingSetTrimmerPolicy::
PlatformSupportsWorkingSetTrim()) {
Expand Down
3 changes: 3 additions & 0 deletions components/performance_manager/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ static_library("performance_manager") {
"graph/worker_node_impl_describer.h",
"graph_features.cc",
"metrics/metrics_collector.cc",
"metrics/tab_revisit_tracker.cc",
"owned_objects.h",
"performance_manager.cc",
"performance_manager_feature_observer_client.cc",
Expand Down Expand Up @@ -149,6 +150,7 @@ static_library("performance_manager") {
"public/graph/system_node.h",
"public/graph/worker_node.h",
"public/metrics/metrics_collector.h",
"public/metrics/tab_revisit_tracker.h",
"public/performance_manager.h",
"public/performance_manager_main_thread_mechanism.h",
"public/performance_manager_main_thread_observer.h",
Expand Down Expand Up @@ -310,6 +312,7 @@ source_set("unit_tests") {
"graph/worker_node_impl_unittest.cc",
"graph_features_unittest.cc",
"metrics/metrics_collector_unittest.cc",
"metrics/tab_revisit_tracker_unittest.cc",
"owned_objects_unittest.cc",
"performance_manager_impl_unittest.cc",
"performance_manager_registry_impl_unittest.cc",
Expand Down
137 changes: 137 additions & 0 deletions components/performance_manager/metrics/tab_revisit_tracker.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/performance_manager/public/metrics/tab_revisit_tracker.h"

#include "base/check.h"
#include "base/metrics/histogram_functions.h"

namespace performance_manager {

namespace {

constexpr base::TimeDelta kMinTime = base::TimeDelta();
constexpr base::TimeDelta kMaxTime = base::Hours(48);

} // namespace

TabRevisitTracker::TabRevisitTracker() = default;
TabRevisitTracker::~TabRevisitTracker() = default;

void TabRevisitTracker::RecordRevisitHistograms(
const TabPageDecorator::TabHandle* tab_handle) {
TabRevisitTracker::StateBundle state_bundle = tab_states_.at(tab_handle);
CHECK(state_bundle.last_active_time.has_value());
base::UmaHistogramCustomTimes(
/*name=*/kTimeToRevisitHistogramName,
/*time=*/base::TimeTicks::Now() - state_bundle.last_active_time.value(),
/*minimum=*/kMinTime,
/*maximum=*/kMaxTime,
/*bucket_count=*/200);
}

void TabRevisitTracker::RecordCloseHistograms(
const TabPageDecorator::TabHandle* tab_handle) {
TabRevisitTracker::StateBundle state_bundle = tab_states_[tab_handle];
CHECK(state_bundle.last_active_time.has_value());
base::UmaHistogramCustomTimes(
/*name=*/kTimeToCloseHistogramName,
/*time=*/base::TimeTicks::Now() - state_bundle.last_active_time.value(),
/*minimum=*/kMinTime,
/*maximum=*/kMaxTime,
/*bucket_count=*/200);
}

void TabRevisitTracker::OnPassedToGraph(Graph* graph) {
TabPageDecorator* tab_page_decorator =
graph->GetRegisteredObjectAs<TabPageDecorator>();
CHECK(tab_page_decorator);
tab_page_decorator->AddObserver(this);
}

void TabRevisitTracker::OnTakenFromGraph(Graph* graph) {
TabPageDecorator* tab_page_decorator =
graph->GetRegisteredObjectAs<TabPageDecorator>();
if (tab_page_decorator) {
tab_page_decorator->RemoveObserver(this);
}
}

void TabRevisitTracker::OnTabAdded(TabPageDecorator::TabHandle* tab_handle) {
PageLiveStateDecorator::Data* live_state_data =
PageLiveStateDecorator::Data::GetOrCreateForPageNode(
tab_handle->page_node());
CHECK(live_state_data);

live_state_data->AddObserver(this);

if (live_state_data->IsActiveTab()) {
tab_states_[tab_handle].state = State::kActive;
tab_states_[tab_handle].last_active_time = absl::nullopt;
} else {
tab_states_[tab_handle].state = State::kBackground;
// Set the last active time to now, since it's used to measure time
// spent in the background and this tab is already in the background.
tab_states_[tab_handle].last_active_time = base::TimeTicks::Now();
}
}

void TabRevisitTracker::OnTabAboutToBeDiscarded(
const PageNode* old_page_node,
TabPageDecorator::TabHandle* tab_handle) {
PageLiveStateDecorator::Data* old_live_state_data =
PageLiveStateDecorator::Data::GetOrCreateForPageNode(old_page_node);
CHECK(old_live_state_data);

old_live_state_data->RemoveObserver(this);

PageLiveStateDecorator::Data* new_live_state_data =
PageLiveStateDecorator::Data::GetOrCreateForPageNode(
tab_handle->page_node());
CHECK(new_live_state_data);

new_live_state_data->AddObserver(this);

tab_states_[tab_handle].state = State::kDiscarded;
}

void TabRevisitTracker::OnBeforeTabRemoved(
TabPageDecorator::TabHandle* tab_handle) {
PageLiveStateDecorator::Data* live_state_data =
PageLiveStateDecorator::Data::GetOrCreateForPageNode(
tab_handle->page_node());
CHECK(live_state_data);

live_state_data->RemoveObserver(this);

// Don't record the histograms if this is the active tab. We only care about
// background tabs being closed.
if (!live_state_data->IsActiveTab()) {
RecordCloseHistograms(tab_handle);
}

tab_states_.erase(tab_handle);
}

void TabRevisitTracker::OnIsActiveTabChanged(const PageNode* page_node) {
PageLiveStateDecorator::Data* live_state_data =
PageLiveStateDecorator::Data::GetOrCreateForPageNode(page_node);
CHECK(live_state_data);

TabPageDecorator::TabHandle* tab_handle =
TabPageDecorator::FromPageNode(page_node);
CHECK(tab_handle);

if (live_state_data->IsActiveTab()) {
CHECK_NE(tab_states_[tab_handle].state, State::kActive);
tab_states_[tab_handle].state = State::kActive;
RecordRevisitHistograms(tab_handle);
} else {
CHECK_NE(tab_states_[tab_handle].state, State::kBackground);
tab_states_[tab_handle].state = State::kBackground;
tab_states_[tab_handle].last_active_time = base::TimeTicks::Now();
}
}

} // namespace performance_manager
112 changes: 112 additions & 0 deletions components/performance_manager/metrics/tab_revisit_tracker_unittest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/performance_manager/public/metrics/tab_revisit_tracker.h"

#include <memory>

#include "base/test/metrics/histogram_tester.h"
#include "base/time/time.h"
#include "components/performance_manager/public/decorators/page_live_state_decorator.h"
#include "components/performance_manager/public/graph/graph.h"
#include "components/performance_manager/public/graph/page_node.h"
#include "components/performance_manager/test_support/graph_test_harness.h"
#include "components/performance_manager/test_support/mock_graphs.h"

namespace performance_manager {

class TabRevisitTrackerTest : public GraphTestHarness {
protected:
void SetUp() override {
GraphTestHarness::SetUp();

graph()->PassToGraph(std::make_unique<TabPageDecorator>());
graph()->PassToGraph(std::make_unique<TabRevisitTracker>());
}

void SetIsActiveTab(const PageNode* page_node, bool is_active) {
PageLiveStateDecorator::Data* data =
PageLiveStateDecorator::Data::GetOrCreateForPageNode(page_node);
data->SetIsActiveTabForTesting(is_active);
}
};

TEST_F(TabRevisitTrackerTest, StartsBackgroundedThenRevisited) {
base::HistogramTester tester;
MockSinglePageInSingleProcessGraph mock_graph(graph());

// Creating the graph doesn't record anything since the page nodes are created
// as kUnknown and don't change their "active tab" status.
tester.ExpectTotalCount(TabRevisitTracker::kTimeToRevisitHistogramName, 0);

SetIsActiveTab(mock_graph.page.get(), false);
mock_graph.page->SetType(PageType::kTab);
tester.ExpectTotalCount(TabRevisitTracker::kTimeToRevisitHistogramName, 0);

AdvanceClock(base::Minutes(30));
tester.ExpectTotalCount(TabRevisitTracker::kTimeToRevisitHistogramName, 0);

SetIsActiveTab(mock_graph.page.get(), true);
// The tab became active after 30 minutes in the background, this should be
// recorded in the revisit histogram.
tester.ExpectUniqueTimeSample(TabRevisitTracker::kTimeToRevisitHistogramName,
base::Minutes(30), 1);

SetIsActiveTab(mock_graph.page.get(), false);
tester.ExpectUniqueTimeSample(TabRevisitTracker::kTimeToRevisitHistogramName,
base::Minutes(30), 1);

AdvanceClock(base::Minutes(10));
// The tab became active again after 10 minutes in the background, the revisit
// histogram should contain 2 samples: one for each revisit.
SetIsActiveTab(mock_graph.page.get(), true);
tester.ExpectTotalCount(TabRevisitTracker::kTimeToRevisitHistogramName, 2);
tester.ExpectTimeBucketCount(TabRevisitTracker::kTimeToRevisitHistogramName,
base::Minutes(10), 1);

tester.ExpectTotalCount(TabRevisitTracker::kTimeToCloseHistogramName, 0);
}

TEST_F(TabRevisitTrackerTest, CloseInBackgroundRecordsToCloseHistogram) {
base::HistogramTester tester;
MockSinglePageInSingleProcessGraph mock_graph(graph());

SetIsActiveTab(mock_graph.page.get(), false);
mock_graph.page->SetType(PageType::kTab);
tester.ExpectTotalCount(TabRevisitTracker::kTimeToRevisitHistogramName, 0);
tester.ExpectTotalCount(TabRevisitTracker::kTimeToCloseHistogramName, 0);

AdvanceClock(base::Hours(1));

// Closing the tab while it's inactive should record to the close histogram
// but not the revisit one.
mock_graph.frame.reset();
mock_graph.page.reset();

tester.ExpectTotalCount(TabRevisitTracker::kTimeToRevisitHistogramName, 0);
tester.ExpectUniqueTimeSample(TabRevisitTracker::kTimeToCloseHistogramName,
base::Hours(1), 1);
}

TEST_F(TabRevisitTrackerTest, CloseWhileActiveDoesntRecordClose) {
base::HistogramTester tester;
MockSinglePageInSingleProcessGraph mock_graph(graph());

SetIsActiveTab(mock_graph.page.get(), true);
mock_graph.page->SetType(PageType::kTab);
tester.ExpectTotalCount(TabRevisitTracker::kTimeToRevisitHistogramName, 0);
tester.ExpectTotalCount(TabRevisitTracker::kTimeToCloseHistogramName, 0);

AdvanceClock(base::Hours(1));

// Closing the tab while it's active doesn't record either histogram, since
// they are only concerned about background tabs closing or becoming active.
mock_graph.frame.reset();
mock_graph.page.reset();

tester.ExpectTotalCount(TabRevisitTracker::kTimeToRevisitHistogramName, 0);
tester.ExpectTotalCount(TabRevisitTracker::kTimeToCloseHistogramName, 0);
}

} // namespace performance_manager
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_METRICS_TAB_REVISIT_TRACKER_H_
#define COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_METRICS_TAB_REVISIT_TRACKER_H_

#include <map>

#include "base/time/time.h"
#include "components/performance_manager/public/decorators/page_live_state_decorator.h"
#include "components/performance_manager/public/decorators/tab_page_decorator.h"
#include "components/performance_manager/public/graph/graph.h"
#include "third_party/abseil-cpp/absl/types/optional.h"

namespace performance_manager {

// A GraphOwned object that tracks tab transitions to/from
// active/background/closed/discarded states and records timing information
// about these states.
class TabRevisitTracker : public GraphOwned,
public TabPageObserver,
public PageLiveStateObserver {
public:
static constexpr char kTimeToRevisitHistogramName[] =
"PerformanceManager.TabRevisitTracker.TimeToRevisit";
static constexpr char kTimeToCloseHistogramName[] =
"PerformanceManager.TabRevisitTracker.TimeToClose";

TabRevisitTracker();
~TabRevisitTracker() override;

private:
enum class State {
kActive,
kBackground,
kDiscarded,
};

struct StateBundle {
State state;
absl::optional<base::TimeTicks> last_active_time;
};

void RecordRevisitHistograms(const TabPageDecorator::TabHandle* tab_handle);
void RecordCloseHistograms(const TabPageDecorator::TabHandle* tab_handle);

// GraphOwned:
void OnPassedToGraph(Graph* graph) override;
void OnTakenFromGraph(Graph* graph) override;

// TabPageObserver:
void OnTabAdded(TabPageDecorator::TabHandle* tab_handle) override;
void OnTabAboutToBeDiscarded(
const PageNode* old_page_node,
TabPageDecorator::TabHandle* tab_handle) override;
void OnBeforeTabRemoved(TabPageDecorator::TabHandle* tab_handle) override;

// PageLiveStateObserver:
void OnIsActiveTabChanged(const PageNode* page_node) override;
// We only care about `OnIsActiveTabChanged` but these are all pure virtual so
// no-op here.
void OnIsConnectedToUSBDeviceChanged(const PageNode* page_node) override {}
void OnIsConnectedToBluetoothDeviceChanged(
const PageNode* page_node) override {}
void OnIsCapturingVideoChanged(const PageNode* page_node) override {}
void OnIsCapturingAudioChanged(const PageNode* page_node) override {}
void OnIsBeingMirroredChanged(const PageNode* page_node) override {}
void OnIsCapturingWindowChanged(const PageNode* page_node) override {}
void OnIsCapturingDisplayChanged(const PageNode* page_node) override {}
void OnIsAutoDiscardableChanged(const PageNode* page_node) override {}
void OnWasDiscardedChanged(const PageNode* page_node) override {}
void OnIsPinnedTabChanged(const PageNode* page_node) override {}
void OnContentSettingsChanged(const PageNode* page_node) override {}
void OnIsDevToolsOpenChanged(const PageNode* page_node) override {}

std::map<const TabPageDecorator::TabHandle*, StateBundle> tab_states_;
};

} // namespace performance_manager

#endif // COMPONENTS_PERFORMANCE_MANAGER_PUBLIC_METRICS_TAB_REVISIT_TRACKER_H_

0 comments on commit 5c7aedf

Please sign in to comment.