blob: 8d070aa598e0f88771d8b65867c961fa0f181440 [file] [log] [blame]
// Copyright 2017 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/metrics_collector.h"
#include <array>
#include <optional>
#include <set>
#include <string>
#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/numerics/clamped_math.h"
#include "base/strings/strcat.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "components/performance_manager/public/graph/graph_operations.h"
#include "components/performance_manager/public/graph/node_attached_data.h"
#include "content/public/common/process_type.h"
namespace performance_manager {
namespace {
void RecordProcessLifetime(const std::string& histogram_name,
base::TimeDelta lifetime) {
base::UmaHistogramCustomTimes(histogram_name, lifetime, base::Seconds(1),
base::Days(1), 100);
}
void RecordShortProcessLifetime(const std::string& histogram_name,
base::TimeDelta lifetime) {
base::UmaHistogramLongTimes(histogram_name, lifetime);
}
void OnRendererDestroyed(const ProcessNode* process_node,
base::TimeDelta lifetime) {
RecordProcessLifetime("Renderer.ProcessLifetime3", lifetime);
ProcessNode::ContentTypes content_types =
process_node->GetHostedContentTypes();
if (content_types.Has(ProcessNode::ContentType::kExtension)) {
RecordProcessLifetime("Renderer.ProcessLifetime3.Extension", lifetime);
} else if (!content_types.Has(ProcessNode::ContentType::kNavigatedFrame)) {
if (content_types.Has(ProcessNode::ContentType::kWorker)) {
RecordProcessLifetime("Renderer.ProcessLifetime3.Worker", lifetime);
} else if (content_types.Has(ProcessNode::ContentType::kMainFrame) ||
content_types.Has(ProcessNode::ContentType::kSubframe)) {
RecordProcessLifetime("Renderer.ProcessLifetime3.Speculative", lifetime);
} else {
RecordProcessLifetime("Renderer.ProcessLifetime3.Empty", lifetime);
}
} else if (content_types.Has(ProcessNode::ContentType::kMainFrame)) {
RecordProcessLifetime("Renderer.ProcessLifetime3.MainFrame", lifetime);
} else if (content_types.Has(ProcessNode::ContentType::kAd)) {
RecordProcessLifetime("Renderer.ProcessLifetime3.Subframe_Ad", lifetime);
} else if (content_types.Has(ProcessNode::ContentType::kSubframe)) {
RecordProcessLifetime("Renderer.ProcessLifetime3.Subframe_NoAd", lifetime);
} else {
NOTREACHED();
}
}
bool LoadingStateIsQuiescent(PageNode::LoadingState loading_state) {
switch (loading_state) {
case PageNode::LoadingState::kLoadingNotStarted:
case PageNode::LoadingState::kLoadingTimedOut:
case PageNode::LoadingState::kLoadedIdle:
return true;
case PageNode::LoadingState::kLoading:
case PageNode::LoadingState::kLoadedBusy:
return false;
}
NOTREACHED();
}
} // namespace
class MetricsReportRecordHolder
: public ExternalNodeAttachedDataImpl<MetricsReportRecordHolder> {
public:
explicit MetricsReportRecordHolder(const PageNode* unused_page_node) {}
~MetricsReportRecordHolder() override = default;
MetricsCollector::MetricsReportRecord metrics_report_record;
};
class UkmCollectionStateHolder
: public ExternalNodeAttachedDataImpl<UkmCollectionStateHolder> {
public:
explicit UkmCollectionStateHolder(const PageNode* unused_page_node) {}
~UkmCollectionStateHolder() override = default;
MetricsCollector::UkmCollectionState ukm_collection_state;
};
// Delay the metrics report from for 5 minutes from when the main frame
// navigation is committed.
const base::TimeDelta kMetricsReportDelayTimeout = base::Minutes(5);
const char kTabNavigationWithSameOriginTabHistogramName[] =
"Tabs.NewNavigationWithSameOriginTab";
const int kDefaultFrequencyUkmEQTReported = 5u;
MetricsCollector::MetricsCollector() = default;
MetricsCollector::~MetricsCollector() = default;
void MetricsCollector::OnPassedToGraph(Graph* graph) {
RegisterObservers(graph);
loading_page_counts_.fill(0);
// Unretained is safe because `this` owns the timer.
page_loading_state_timer_.Start(
FROM_HERE, base::Seconds(30),
base::BindRepeating(&MetricsCollector::RecordLoadingAndQuiescentPageCount,
base::Unretained(this)));
}
void MetricsCollector::OnTakenFromGraph(Graph* graph) {
page_loading_state_timer_.Stop();
UnregisterObservers(graph);
}
void MetricsCollector::OnPageNodeAdded(const PageNode* page_node) {
// Record the initial state of the page.
DCHECK(!base::Contains(mixed_state_pages_, page_node));
const auto loading_state =
LoadingStateIsQuiescent(page_node->GetLoadingState())
? PageLoadingState::kQuiescent
: (page_node->IsVisible() ? PageLoadingState::kLoadingVisible
: PageLoadingState::kLoadingHidden);
UpdateLoadingPageCounts(std::nullopt, loading_state);
}
void MetricsCollector::OnBeforePageNodeRemoved(const PageNode* page_node) {
// Remove the page from all counts.
PageLoadingState previous_state;
if (LoadingStateIsQuiescent(page_node->GetLoadingState())) {
DCHECK(!base::Contains(mixed_state_pages_, page_node));
previous_state = PageLoadingState::kQuiescent;
} else {
size_t erased = mixed_state_pages_.erase(page_node);
previous_state =
erased ? PageLoadingState::kLoadingMixed
: (page_node->IsVisible() ? PageLoadingState::kLoadingVisible
: PageLoadingState::kLoadingHidden);
}
UpdateLoadingPageCounts(previous_state, std::nullopt);
}
void MetricsCollector::OnIsVisibleChanged(const PageNode* page_node) {
if (LoadingStateIsQuiescent(page_node->GetLoadingState())) {
return;
}
// Change of visibility makes the state kLoadingMixed if it wasn't already.
const auto [_, inserted] = mixed_state_pages_.insert(page_node);
if (inserted) {
const auto previous_state = page_node->IsVisible()
? PageLoadingState::kLoadingHidden
: PageLoadingState::kLoadingVisible;
UpdateLoadingPageCounts(previous_state, PageLoadingState::kLoadingMixed);
}
}
void MetricsCollector::OnLoadingStateChanged(
const PageNode* page_node,
PageNode::LoadingState previous_state) {
const bool is_quiescent =
LoadingStateIsQuiescent(page_node->GetLoadingState());
const bool was_quiescent = LoadingStateIsQuiescent(previous_state);
if (is_quiescent == was_quiescent) {
return;
}
PageLoadingState old_state;
PageLoadingState new_state;
if (is_quiescent) {
size_t erased = mixed_state_pages_.erase(page_node);
old_state =
erased ? PageLoadingState::kLoadingMixed
: (page_node->IsVisible() ? PageLoadingState::kLoadingVisible
: PageLoadingState::kLoadingHidden);
new_state = PageLoadingState::kQuiescent;
} else {
DCHECK(!base::Contains(mixed_state_pages_, page_node));
old_state = PageLoadingState::kQuiescent;
new_state = page_node->IsVisible() ? PageLoadingState::kLoadingVisible
: PageLoadingState::kLoadingHidden;
}
UpdateLoadingPageCounts(old_state, new_state);
}
void MetricsCollector::OnUkmSourceIdChanged(const PageNode* page_node) {
ukm::SourceId ukm_source_id = page_node->GetUkmSourceID();
UpdateUkmSourceIdForPage(page_node, ukm_source_id);
}
void MetricsCollector::OnMainFrameDocumentChanged(const PageNode* page_node) {
bool found_same_origin_page = false;
auto* record = GetMetricsReportRecord(page_node);
if (!page_node->GetMainFrameUrl().SchemeIsHTTPOrHTTPS() ||
url::IsSameOriginWith(record->previous_url,
page_node->GetMainFrameUrl())) {
record->previous_url = page_node->GetMainFrameUrl();
return;
}
for (const PageNode* page : GetOwningGraph()->GetAllPageNodes()) {
if (page != page_node) {
if (page->GetBrowserContextID() == page_node->GetBrowserContextID() &&
url::IsSameOriginWith(page->GetMainFrameUrl(),
page_node->GetMainFrameUrl())) {
found_same_origin_page = true;
break;
}
}
}
record->previous_url = page_node->GetMainFrameUrl();
base::UmaHistogramBoolean(kTabNavigationWithSameOriginTabHistogramName,
found_same_origin_page);
}
void MetricsCollector::OnProcessLifetimeChange(
const ProcessNode* process_node) {
// Ignore process creation.
if (!process_node->GetExitStatus().has_value()) {
return;
}
OnProcessDestroyed(process_node);
}
void MetricsCollector::OnBeforeProcessNodeRemoved(
const ProcessNode* process_node) {
// If the ProcessNode is destroyed with a valid process handle, consider this
// the end of the process' life.
if (process_node->GetProcess().IsValid()) {
OnProcessDestroyed(process_node);
}
}
// static
MetricsCollector::MetricsReportRecord* MetricsCollector::GetMetricsReportRecord(
const PageNode* page_node) {
auto* holder = MetricsReportRecordHolder::GetOrCreate(page_node);
return &holder->metrics_report_record;
}
// static
MetricsCollector::UkmCollectionState* MetricsCollector::GetUkmCollectionState(
const PageNode* page_node) {
auto* holder = UkmCollectionStateHolder::GetOrCreate(page_node);
return &holder->ukm_collection_state;
}
void MetricsCollector::RegisterObservers(Graph* graph) {
graph->AddFrameNodeObserver(this);
graph->AddPageNodeObserver(this);
graph->AddProcessNodeObserver(this);
}
void MetricsCollector::UnregisterObservers(Graph* graph) {
graph->RemoveFrameNodeObserver(this);
graph->RemovePageNodeObserver(this);
graph->RemoveProcessNodeObserver(this);
}
bool MetricsCollector::ShouldReportMetrics(const PageNode* page_node) {
return page_node->GetTimeSinceLastNavigation() > kMetricsReportDelayTimeout;
}
void MetricsCollector::UpdateUkmSourceIdForPage(const PageNode* page_node,
ukm::SourceId ukm_source_id) {
auto* state = GetUkmCollectionState(page_node);
state->ukm_source_id = ukm_source_id;
}
MetricsCollector::MetricsReportRecord::MetricsReportRecord() = default;
MetricsCollector::MetricsReportRecord::MetricsReportRecord(
const MetricsReportRecord& other) = default;
void MetricsCollector::OnProcessDestroyed(const ProcessNode* process_node) {
const base::TimeTicks now = base::TimeTicks::Now();
base::TimeTicks launch_time = process_node->GetLaunchTime();
if (launch_time.is_null()) {
// Terminating a process quickly after initiating its launch (for example
// with FastShutdownIfPossible()) may result in receiving
// RenderProcessHostObserver::RenderProcessExited() without a corresponding
// RenderProcessHostCreationObserver::OnRenderProcessHostCreated(). In this
// case, GetLaunchTime() won't be set. It's correct to report a lifetime of
// 0 in this case.
launch_time = now;
}
const base::TimeDelta lifetime = now - launch_time;
if (process_node->GetProcessType() == content::PROCESS_TYPE_RENDERER) {
OnRendererDestroyed(process_node, lifetime);
} else if (process_node->GetProcessType() == content::PROCESS_TYPE_UTILITY) {
// Utility processes are known to often have short lifetimes. There are
// exceptions like the network service that could be broken out later if the
// data suggests it's necessary.
RecordShortProcessLifetime("ChildProcess.ProcessLifetime.Utility",
lifetime);
}
}
void MetricsCollector::UpdateLoadingPageCounts(
std::optional<PageLoadingState> old_state,
std::optional<PageLoadingState> new_state) {
CHECK(old_state != new_state);
if (old_state.has_value()) {
loading_page_counts_.at(static_cast<size_t>(old_state.value())) -= 1;
}
if (new_state.has_value()) {
loading_page_counts_.at(static_cast<size_t>(new_state.value())) += 1;
}
}
void MetricsCollector::RecordLoadingAndQuiescentPageCount() const {
// Record all loading state counts.
constexpr char kLoadingPageCountHistogram[] =
"PerformanceManager.LoadingNotQuiescentPageCount";
base::ClampedNumeric<size_t> total_count = 0;
for (size_t i = 0; i < loading_page_counts_.size(); ++i) {
const auto loading_state = static_cast<PageLoadingState>(i);
CHECK_LE(loading_state, PageLoadingState::kMaxValue);
const auto count = loading_page_counts_.at(i);
const char* visibility_string = [loading_state]() -> const char* {
switch (loading_state) {
case PageLoadingState::kLoadingVisible:
return ".Visible";
case PageLoadingState::kLoadingHidden:
return ".Hidden";
case PageLoadingState::kLoadingMixed:
return ".MixedVisibility";
case PageLoadingState::kQuiescent:
// Don't log here, not loading.
return nullptr;
}
NOTREACHED();
}();
if (visibility_string) {
base::UmaHistogramCounts1000(
base::StrCat({kLoadingPageCountHistogram, visibility_string}), count);
total_count += count;
}
}
base::UmaHistogramCounts1000(
base::StrCat({kLoadingPageCountHistogram, ".All"}), total_count);
// Record quiescent state count.
base::UmaHistogramCounts1000("PerformanceManager.QuiescentPageCount",
loading_page_counts_.at(static_cast<size_t>(
PageLoadingState::kQuiescent)));
}
} // namespace performance_manager