blob: 349617999e4797dda4174b7f5e2faf7544085b06 [file] [log] [blame]
// Copyright 2018 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/graph/system_node_impl.h"
#include "chrome/browser/performance_manager/graph/frame_node_impl.h"
#include "chrome/browser/performance_manager/graph/page_node_impl.h"
#include "chrome/browser/performance_manager/graph/process_node_impl.h"
#include <algorithm>
#include <iterator>
#include "base/macros.h"
#include "base/process/process_handle.h"
namespace performance_manager {
SystemNodeImpl::SystemNodeImpl(
const resource_coordinator::CoordinationUnitID& id,
Graph* graph)
: CoordinationUnitInterface(id, graph) {}
SystemNodeImpl::~SystemNodeImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void SystemNodeImpl::OnProcessCPUUsageReady() {
SendEvent(resource_coordinator::mojom::Event::kProcessCPUUsageReady);
}
void SystemNodeImpl::DistributeMeasurementBatch(
resource_coordinator::mojom::ProcessResourceMeasurementBatchPtr
measurement_batch) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::TimeDelta time_since_last_measurement;
if (!last_measurement_end_time_.is_null()) {
// Use the end of the measurement batch as a proxy for when every
// measurement was acquired. For the purpose of estimating CPU usage
// over the duration from last measurement, it'll be near enough. The error
// will average out, and there's an inherent race in knowing when a
// measurement was actually acquired in any case.
time_since_last_measurement =
measurement_batch->batch_ended_time - last_measurement_end_time_;
DCHECK_LE(base::TimeDelta(), time_since_last_measurement);
}
// TODO(siggi): Need to decide what to do with measurements that span an
// absurd length of time, or which are missing a significant portion of the
// data wanted/required. Maybe there should be a filtering step here, or
// perhaps this should be up to the consumers, who can perhaps better
// assess whether the gaps affect them. This would require propagating more
// information through the graph. Perhaps each page could maintain the
// min/max span for all the data that went into the current estimates.
last_measurement_start_time_ = measurement_batch->batch_started_time;
last_measurement_end_time_ = measurement_batch->batch_ended_time;
// Keep track of the pages updated with CPU cost for the second pass,
// where their memory usage is updated.
std::set<PageNodeImpl*> pages;
std::vector<ProcessNodeImpl*> found_processes;
for (const auto& measurement : measurement_batch->measurements) {
ProcessNodeImpl* process = graph()->GetProcessNodeByPid(measurement->pid);
if (process) {
base::TimeDelta cumulative_cpu_delta =
measurement->cpu_usage - process->cumulative_cpu_usage();
DCHECK_LE(base::TimeDelta(), cumulative_cpu_delta);
// Distribute the CPU delta to the pages that own the frames in this
// process.
std::set<FrameNodeImpl*> frames = process->GetFrameNodes();
if (!frames.empty()) {
// To make sure we don't systemically truncate the remainder of the
// delta, simply subtract the remainder and "hold it back" from the
// measurement. Since our measurement is cumulative, we'll see that
// CPU time again in the next measurement.
cumulative_cpu_delta -=
cumulative_cpu_delta %
base::TimeDelta::FromMicroseconds(frames.size());
for (FrameNodeImpl* frame : frames) {
PageNodeImpl* page = frame->GetPageNode();
if (page) {
page->set_usage_estimate_time(last_measurement_end_time_);
page->set_cumulative_cpu_usage_estimate(
page->cumulative_cpu_usage_estimate() +
cumulative_cpu_delta / frames.size());
pages.insert(page);
}
}
} else {
// TODO(siggi): The process has zero frames, maybe this is a newly
// started renderer and if so, this might be a good place to
// estimate the process overhead. Alternatively perhaps the first
// measurement for each process, or a lower bound thereof will
// converge to a decent estimate.
}
if (process->cumulative_cpu_usage().is_zero() ||
time_since_last_measurement.is_zero()) {
// Imitate the behavior of GetPlatformIndependentCPUUsage, which
// yields zero for the initial measurement of each process.
process->SetCPUUsage(0.0);
} else {
double cpu_usage = 100.0 * cumulative_cpu_delta.InMicrosecondsF() /
time_since_last_measurement.InMicrosecondsF();
process->SetCPUUsage(cpu_usage);
}
process->set_cumulative_cpu_usage(process->cumulative_cpu_usage() +
cumulative_cpu_delta);
process->set_private_footprint_kb(measurement->private_footprint_kb);
// Note the found processes.
found_processes.push_back(process);
}
}
// Grab all the processes to see if there were any we didn't get data for.
std::vector<ProcessNodeImpl*> processes = graph_->GetAllProcessNodes();
if (found_processes.size() != processes.size()) {
// We didn't find them all, compute the difference and clear the data for
// the processes we didn't find.
std::sort(processes.begin(), processes.end());
std::sort(found_processes.begin(), found_processes.end());
std::vector<ProcessNodeImpl*> not_found_processes;
std::set_difference(
processes.begin(), processes.end(), found_processes.begin(),
found_processes.end(),
std::inserter(not_found_processes, not_found_processes.begin()));
// Clear processes we didn't get data for.
for (ProcessNodeImpl* process : not_found_processes) {
process->SetCPUUsage(0.0);
process->set_private_footprint_kb(0);
}
}
// Iterate through the pages involved to distribute the memory to them.
for (PageNodeImpl* page : pages) {
uint64_t private_footprint_kb_sum = 0;
const auto& frames = page->GetFrameNodes();
for (FrameNodeImpl* frame : frames) {
ProcessNodeImpl* process = frame->GetProcessNode();
if (process) {
private_footprint_kb_sum +=
process->private_footprint_kb() / process->GetFrameNodes().size();
}
}
page->set_private_footprint_kb_estimate(private_footprint_kb_sum);
DCHECK_EQ(last_measurement_end_time_, page->usage_estimate_time());
}
// Fire the end update signal.
OnProcessCPUUsageReady();
}
void SystemNodeImpl::OnEventReceived(resource_coordinator::mojom::Event event) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& observer : observers())
observer.OnSystemEventReceived(this, event);
}
void SystemNodeImpl::OnPropertyChanged(
resource_coordinator::mojom::PropertyType property_type,
int64_t value) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& observer : observers())
observer.OnSystemPropertyChanged(this, property_type, value);
}
} // namespace performance_manager