blob: 2f96c0a53904ad42473386803bb93fc557e1aa1a [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 <set>
#include <string>
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "base/time/time.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_IN_MIGRATION();
}
}
} // 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) {
graph_ = graph;
RegisterObservers(graph);
}
void MetricsCollector::OnTakenFromGraph(Graph* graph) {
UnregisterObservers(graph);
graph_ = nullptr;
}
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;
}
graph_->VisitAllPageNodes([&](const PageNode* page_node_it) {
if (page_node_it != page_node) {
if (page_node_it->GetBrowserContextID() ==
page_node->GetBrowserContextID() &&
url::IsSameOriginWith(page_node_it->GetMainFrameUrl(),
page_node->GetMainFrameUrl())) {
found_same_origin_page = true;
return false;
}
}
return true;
});
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);
}
}
} // namespace performance_manager