-
Notifications
You must be signed in to change notification settings - Fork 6.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
6 changed files
with
357 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
137 changes: 137 additions & 0 deletions
137
components/performance_manager/metrics/tab_revisit_tracker.cc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
112
components/performance_manager/metrics/tab_revisit_tracker_unittest.cc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
82 changes: 82 additions & 0 deletions
82
components/performance_manager/public/metrics/tab_revisit_tracker.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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_ |
Oops, something went wrong.