blob: 5cfaadadd5c631903594ad803a04494dd39aee54 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/performance_manager/observers/isolation_context_metrics.h"
#include <cmath>
#include <utility>
#include "base/bind.h"
#include "base/metrics/histogram_macros.h"
#include "chrome/browser/performance_manager/public/graph/node_attached_data.h"
namespace performance_manager {
namespace {
// Given |data|, an instance of DataType that contains a |last_reported| field,
// calculates and returns the amount of time (in seconds) that has elapsed since
// the last report was filed, and updates the last report time to |now|.
template <typename DataType>
int GetSecondsSinceLastReportAndUpdate(const base::TimeTicks now,
const base::TimeDelta reporting_interval,
DataType* data) {
auto elapsed = now - data->last_reported;
data->last_reported = now;
// It's entirely possible for time to advance by extremely large jumps if the
// machine is put to sleep, for example. In this case, the data won't
// meaningfully contribute to metrics. If the amount of time elapsed greatly
// surpasses our reporting interval, silently drop the report by returning
// that no time has elapsed.
if (elapsed >= 2 * reporting_interval)
return 0;
return static_cast<int>(std::round(elapsed.InSecondsF()));
}
// Adds |count| samples of the given |value| to the linear histogram with the
// provided |name|. Assumes the enum starts at 0, and ends at
// EnumType::kMaxValue inclusively. This is templated on the histogram name so
// that each histogram gets a distinct static histogram pointer.
template <const char* kName, typename EnumType>
void AddCountsToHistogram(EnumType value, int count) {
static constexpr int32_t kMaxValue =
static_cast<int32_t>(EnumType::kMaxValue);
STATIC_HISTOGRAM_POINTER_BLOCK(
kName, AddCount(static_cast<int32_t>(value), count),
base::LinearHistogram::FactoryGet(
kName, 0, kMaxValue, kMaxValue + 1,
base::HistogramBase::kUmaTargetedHistogramFlag));
}
} // namespace
// Wrapper around ProcessData providing storage. Keeps the impl details out of
// the header.
struct IsolationContextMetricsProcessDataImpl
: public ExternalNodeAttachedDataImpl<
IsolationContextMetricsProcessDataImpl> {
explicit IsolationContextMetricsProcessDataImpl(
const ProcessNode* process_node) {}
~IsolationContextMetricsProcessDataImpl() override = default;
IsolationContextMetrics::ProcessData process_data;
DISALLOW_COPY_AND_ASSIGN(IsolationContextMetricsProcessDataImpl);
};
// static
constexpr base::TimeDelta IsolationContextMetrics::kReportingInterval;
// static
const char IsolationContextMetrics::kProcessDataByTimeHistogramName[] =
"PerformanceManager.FrameSiteInstanceProcessRelationship.ByTime2";
// static
const char IsolationContextMetrics::kProcessDataByProcessHistogramName[] =
"PerformanceManager.FrameSiteInstanceProcessRelationship.ByProcess2";
// static
const char
IsolationContextMetrics::kBrowsingInstanceDataByPageTimeHistogramName[] =
"PerformanceManager.BrowsingInstancePluralityVisibilityState."
"ByPageTime";
// static
const char IsolationContextMetrics::kBrowsingInstanceDataByTimeHistogramName[] =
"PerformanceManager.BrowsingInstancePluralityVisibilityState.ByTime";
// static
const char IsolationContextMetrics::kFramesPerRendererByTimeHistogram[] =
"PerformanceManager.FramesPerRendererByTime";
// static
const char IsolationContextMetrics::kSiteInstancesPerRendererByTimeHistogram[] =
"PerformanceManager.SiteInstancesPerRendererByTime";
IsolationContextMetrics::IsolationContextMetrics() = default;
IsolationContextMetrics::~IsolationContextMetrics() {}
void IsolationContextMetrics::StartTimer() {
// The timer cancels itself when it goes out of scope, so base::Unretained is
// safe here.
reporting_timer_.Start(
FROM_HERE, kReportingInterval,
base::BindRepeating(&IsolationContextMetrics::OnReportingTimerFired,
base::Unretained(this)));
}
void IsolationContextMetrics::OnFrameNodeAdded(const FrameNode* frame_node) {
// Track frame node births and use that to keep ProcessData up to date.
ChangeFrameCount(frame_node, 1);
// This should be impossible, as frame nodes are created not current, and
// are added to the graph before |IsCurrent| is set.
DCHECK(!frame_node->IsCurrent());
}
void IsolationContextMetrics::OnBeforeFrameNodeRemoved(
const FrameNode* frame_node) {
// Track frame node deaths and use that to keep ProcessData up to date.
ChangeFrameCount(frame_node, -1);
// If the frame is the current main frame of a page, then remove the page
// from the browsing instance as well.
if (frame_node->IsMainFrame() && frame_node->IsCurrent()) {
ChangePageCount(frame_node->GetPageNode(),
frame_node->GetBrowsingInstanceId(), -1);
}
}
void IsolationContextMetrics::OnIsCurrentChanged(const FrameNode* frame_node) {
if (!frame_node->IsMainFrame())
return;
const auto* page_node = frame_node->GetPageNode();
DCHECK(page_node);
const int32_t browsing_instance_id = frame_node->GetBrowsingInstanceId();
const int delta = frame_node->IsCurrent() ? 1 : -1;
ChangePageCount(page_node, browsing_instance_id, delta);
}
void IsolationContextMetrics::OnPassedToGraph(Graph* graph) {
graph_ = graph;
RegisterObservers(graph);
}
void IsolationContextMetrics::OnTakenFromGraph(Graph* graph) {
UnregisterObservers(graph);
graph_ = nullptr;
}
void IsolationContextMetrics::OnIsVisibleChanged(const PageNode* page_node) {
// If there is no current main frame node associated with the page, we will
// capture the visibility event when a node is added and made current via
// "OnIsCurrentChanged".
const auto* frame_node = page_node->GetMainFrameNode();
if (!frame_node || !frame_node->IsCurrent())
return;
// Get the data related to this browsing instance. Since there is a current
// main frame it must already have existed.
DCHECK(base::Contains(browsing_instance_data_,
frame_node->GetBrowsingInstanceId()));
auto* data = &browsing_instance_data_[frame_node->GetBrowsingInstanceId()];
const BrowsingInstanceDataState old_state =
GetBrowsingInstanceDataState(data);
const int old_page_count = data->page_count;
DCHECK_NE(BrowsingInstanceDataState::kUndefined, old_state);
DCHECK_LT(0, data->page_count);
if (page_node->IsVisible()) {
++data->visible_page_count;
DCHECK_LE(data->visible_page_count, data->page_count);
} else {
DCHECK_LT(0, data->visible_page_count);
--data->visible_page_count;
}
// Report the data if the state has changed.
const BrowsingInstanceDataState new_state =
GetBrowsingInstanceDataState(data);
if (old_state != new_state) {
// Report the state change. Flush all other related data so as not to
// introduce bias into the metrics when only a partial reporting cycle
// occurs.
const auto now = base::TimeTicks::Now();
ReportBrowsingInstanceData(data, old_page_count, old_state, now);
ReportAllBrowsingInstanceData(now);
}
}
void IsolationContextMetrics::OnBeforeProcessNodeRemoved(
const ProcessNode* process_node) {
// Track process death and use that to report whether or not the process
// ever hosted frames from the same site instance. The ProcessData will only
// exist for renderer processes that ever actually hosted frames.
if (auto* process_data = ProcessData::Get(process_node)) {
auto state = ProcessDataState::kOnlyOneFrameExists;
if (process_data->has_hosted_multiple_frames) {
state = process_data->has_hosted_multiple_frames_with_same_site_instance
? ProcessDataState::kSomeFramesHaveSameSiteInstance
: ProcessDataState::kAllFramesHaveDistinctSiteInstances;
}
UMA_HISTOGRAM_ENUMERATION(kProcessDataByProcessHistogramName, state);
}
}
void IsolationContextMetrics::RegisterObservers(Graph* graph) {
graph->AddFrameNodeObserver(this);
graph->AddPageNodeObserver(this);
graph->AddProcessNodeObserver(this);
StartTimer();
}
void IsolationContextMetrics::UnregisterObservers(Graph* graph) {
graph->RemoveFrameNodeObserver(this);
graph->RemovePageNodeObserver(this);
graph->RemoveProcessNodeObserver(this);
// Drain all metrics on shutdown to avoid losing the tail.
reporting_timer_.Stop();
OnReportingTimerFired();
}
// static
IsolationContextMetrics::ProcessDataState
IsolationContextMetrics::GetProcessDataState(const ProcessData* process_data) {
if (process_data->site_instance_frame_count.empty())
return ProcessDataState::kUndefined;
if (process_data->multi_frame_site_instance_count == 0) {
if (process_data->site_instance_frame_count.size() == 1)
return ProcessDataState::kOnlyOneFrameExists;
return ProcessDataState::kAllFramesHaveDistinctSiteInstances;
}
return ProcessDataState::kSomeFramesHaveSameSiteInstance;
}
// static
void IsolationContextMetrics::ReportProcessData(ProcessData* process_data,
ProcessDataState state,
base::TimeTicks now) {
const int seconds =
GetSecondsSinceLastReportAndUpdate(now, kReportingInterval, process_data);
if (seconds) {
AddCountsToHistogram<kProcessDataByTimeHistogramName>(state, seconds);
// The following are effectively UMA_HISTOGRAM_COUNTS_100, but using
// AddCount rather than Add.
STATIC_HISTOGRAM_POINTER_BLOCK(
kFramesPerRendererByTimeHistogram,
AddCount(process_data->frame_count, seconds),
base::Histogram::FactoryGet(
kFramesPerRendererByTimeHistogram, 1, 100, 50,
base::HistogramBase::kUmaTargetedHistogramFlag));
STATIC_HISTOGRAM_POINTER_BLOCK(
kSiteInstancesPerRendererByTimeHistogram,
AddCount(process_data->site_instance_frame_count.size(), seconds),
base::Histogram::FactoryGet(
kSiteInstancesPerRendererByTimeHistogram, 1, 100, 50,
base::HistogramBase::kUmaTargetedHistogramFlag));
}
}
void IsolationContextMetrics::ReportAllProcessData(base::TimeTicks now) {
for (const auto* process_node : graph_->GetAllProcessNodes()) {
auto* process_data = ProcessData::Get(process_node);
if (process_data)
ReportProcessData(process_data, GetProcessDataState(process_data), now);
}
}
void IsolationContextMetrics::ChangeFrameCount(const FrameNode* frame_node,
int delta) {
DCHECK(delta == -1 || delta == 1);
const auto* process_node = frame_node->GetProcessNode();
auto* data = ProcessData::GetOrCreate(process_node);
const auto old_state = GetProcessDataState(data);
if (delta == 1) {
if (++data->frame_count > 1)
data->has_hosted_multiple_frames = true;
} else {
--data->frame_count;
}
auto iter = data->site_instance_frame_count
.insert(std::make_pair(frame_node->GetSiteInstanceId(), 0))
.first;
DCHECK_LE(0, iter->second);
const int frame_count = iter->second += delta;
DCHECK_LE(0, iter->second);
if (delta == 1 && frame_count == 2) {
++data->multi_frame_site_instance_count;
data->has_hosted_multiple_frames_with_same_site_instance = true;
} else if (delta == -1 && frame_count == 1) {
--data->multi_frame_site_instance_count;
}
if (frame_count == 0)
data->site_instance_frame_count.erase(iter);
const auto new_state = GetProcessDataState(data);
if (old_state == ProcessDataState::kUndefined || old_state == new_state)
return;
// Report the state change. Flush all other related data so as not to
// introduce bias into the metrics when only a partial reporting cycle occurs.
const auto now = base::TimeTicks::Now();
ReportProcessData(data, old_state, now);
ReportAllProcessData(now);
}
// static
IsolationContextMetrics::BrowsingInstanceDataState
IsolationContextMetrics::GetBrowsingInstanceDataState(
const BrowsingInstanceData* browsing_instance_data) {
if (browsing_instance_data->page_count == 0)
return BrowsingInstanceDataState::kUndefined;
if (browsing_instance_data->page_count == 1) {
if (browsing_instance_data->visible_page_count == 1)
return BrowsingInstanceDataState::kSinglePageForeground;
return BrowsingInstanceDataState::kSinglePageBackground;
}
if (browsing_instance_data->visible_page_count > 0)
return BrowsingInstanceDataState::kMultiPageSomeForeground;
return BrowsingInstanceDataState::kMultiPageBackground;
}
// static
void IsolationContextMetrics::ReportBrowsingInstanceData(
BrowsingInstanceData* browsing_instance_data,
int page_count,
BrowsingInstanceDataState state,
base::TimeTicks now) {
const int seconds = GetSecondsSinceLastReportAndUpdate(
now, kReportingInterval, browsing_instance_data);
if (seconds) {
DCHECK_LT(0, page_count);
AddCountsToHistogram<kBrowsingInstanceDataByPageTimeHistogramName>(
state, seconds * page_count);
AddCountsToHistogram<kBrowsingInstanceDataByTimeHistogramName>(state,
seconds);
}
}
void IsolationContextMetrics::ReportAllBrowsingInstanceData(
base::TimeTicks now) {
for (auto& id_data_pair : browsing_instance_data_) {
auto* data = &id_data_pair.second;
ReportBrowsingInstanceData(data, data->page_count,
GetBrowsingInstanceDataState(data), now);
}
}
void IsolationContextMetrics::ChangePageCount(const PageNode* page_node,
int32_t browsing_instance_id,
int delta) {
DCHECK(delta == -1 || delta == 1);
auto iter =
browsing_instance_data_
.insert(std::make_pair(browsing_instance_id, BrowsingInstanceData()))
.first;
auto* data = &iter->second;
BrowsingInstanceDataState old_state = GetBrowsingInstanceDataState(data);
int old_page_count = data->page_count;
// Modify the page counts, checking invariants before and after.
DCHECK_LE(0, data->page_count);
DCHECK_LE(0, data->visible_page_count);
DCHECK_LE(data->visible_page_count, data->page_count);
data->page_count += delta;
if (page_node->IsVisible())
data->visible_page_count += delta;
DCHECK_LE(0, data->page_count);
DCHECK_LE(0, data->visible_page_count);
DCHECK_LE(data->visible_page_count, data->page_count);
BrowsingInstanceDataState new_state = GetBrowsingInstanceDataState(data);
// No point reporting anything if this is newly created, or if the state
// hasn't changed.
if (old_state != BrowsingInstanceDataState::kUndefined &&
old_state != new_state) {
// Report the state change. Flush all other related data so as not to
// introduce bias into the metrics when only a partial reporting cycle
// occurs.
const auto now = base::TimeTicks::Now();
ReportBrowsingInstanceData(data, old_page_count, old_state, now);
ReportAllBrowsingInstanceData(now);
}
// If the page count is down to zero then the browsing instance can be
// erased. Note that |data| becomes an invalid pointer after this point.
if (data->page_count == 0)
browsing_instance_data_.erase(iter);
}
void IsolationContextMetrics::OnReportingTimerFired() {
const auto now = base::TimeTicks::Now();
ReportAllProcessData(now);
ReportAllBrowsingInstanceData(now);
}
IsolationContextMetrics::ProcessData::ProcessData()
: last_reported(base::TimeTicks::Now()) {}
IsolationContextMetrics::ProcessData::~ProcessData() = default;
// static
IsolationContextMetrics::ProcessData* IsolationContextMetrics::ProcessData::Get(
const ProcessNode* process_node) {
auto* impl = IsolationContextMetricsProcessDataImpl::Get(process_node);
if (!impl)
return nullptr;
return &impl->process_data;
}
// static
IsolationContextMetrics::ProcessData*
IsolationContextMetrics::ProcessData::GetOrCreate(
const ProcessNode* process_node) {
return &IsolationContextMetricsProcessDataImpl::GetOrCreate(process_node)
->process_data;
}
IsolationContextMetrics::BrowsingInstanceData::BrowsingInstanceData()
: last_reported(base::TimeTicks::Now()) {}
IsolationContextMetrics::BrowsingInstanceData::~BrowsingInstanceData() =
default;
} // namespace performance_manager