blob: 21cc9acc0c15845cf3137d16db80f672cf35ba91 [file] [log] [blame]
// Copyright 2020 The Fuchsia 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 "cpu_watcher.h"
#include <lib/inspect/cpp/vmo/types.h>
#include <lib/stdcompat/optional.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/trace/event.h>
#include <lib/zx/clock.h>
#include <memory>
#include <mutex>
#include <type_traits>
namespace component {
namespace {
using inspect::StringReference;
const StringReference kSamples("@samples");
const StringReference kInspect("@inspect");
const StringReference kCurrentSize("current_size");
const StringReference kMaximumSize("maximum_size");
const StringReference kDynamicLinks("dynamic_links");
const StringReference kTimestamp("timestamp");
const StringReference kCpuTime("cpu_time");
const StringReference kQueueTime("queue_time");
const StringReference kPreviousCpuTime("previous_cpu_time");
const StringReference kPreviousQueueTime("previous_queue_time");
const StringReference kPreviousTimestamp("previous_timestamp");
const StringReference kRecentCpuTime("recent_cpu_time");
const StringReference kRecentQueueTime("recent_queue_time");
const StringReference kRecentTimestamp("recent_timestamp");
} // namespace
void CpuWatcher::AddTask(const InstancePath& instance_path,
std::unique_ptr<StatsReader> stats_reader) {
TRACE_DURATION("appmgr", "CpuWatcher::AddTask", "name",
instance_path.empty() ? "" : instance_path.back());
std::lock_guard<std::mutex> lock(mutex_);
Task* cur_task = &root_;
Task* parent_task = &root_;
unsigned long path_length = instance_path.size(), path_position = 0;
std::ostringstream histogram_name;
for (const auto& part : instance_path) {
histogram_name << part;
auto it = cur_task->children().find(part);
if (it == cur_task->children().end()) {
bool inserted;
cpp17::optional<inspect::LinearUintHistogram> histogram;
// The leaves of this tree are koids. We want one histogram for all koids that may occur
// (for example if a component is restarted) so we'll create that at the parent-of-leaf Task.
if (path_position == path_length - 2) {
histogram = cpp17::make_optional(histograms_node_.CreateLinearUintHistogram(
histogram_name.str(), 1 /*floor*/, 1 /*step_size*/, 99 /*buckets*/));
} else {
histogram = cpp17::nullopt;
}
std::tie(it, inserted) = cur_task->children().emplace(
part, std::make_unique<Task>(Task(nullptr /* stats_reader */, max_samples_,
std::move(histogram), parameters_.get_time())));
task_count_ += 1;
task_count_value_.Set(task_count_);
}
parent_task = cur_task;
cur_task = it->second.get();
histogram_name << '/';
path_position++;
}
cur_task->stats_reader() = std::move(stats_reader);
// Measure tasks on creation.
cur_task->Measure(parameters_.get_time(), parameters_, parent_task);
}
void CpuWatcher::RemoveTask(const InstancePath& instance_path) {
TRACE_DURATION("appmgr", "CpuWatcher::RemoveTask", "name",
instance_path.empty() ? "" : instance_path.back());
std::lock_guard<std::mutex> lock(mutex_);
Task* cur_task = &root_;
Task* parent_task = &root_;
for (const auto& part : instance_path) {
auto it = cur_task->children().find(part);
if (it == cur_task->children().end()) {
return;
}
parent_task = cur_task;
cur_task = it->second.get();
}
// Measure before resetting the job, so we get final runtime stats.
cur_task->Measure(parameters_.get_time(), parameters_, parent_task);
const auto& measurements = cur_task->measurements();
auto it = measurements.rbegin();
if (it != measurements.rend()) {
exited_cpu_ += it->cpu_time;
exited_queue_ += it->queue_time;
}
cur_task->stats_reader().reset();
}
void CpuWatcher::Measure() {
zx::time start = parameters_.get_time();
{
TRACE_DURATION("appmgr", "CpuWatcher::Measure", "num_tasks", task_count_);
std::lock_guard<std::mutex> lock(mutex_);
Measurement overall{
.timestamp = start.get(), .cpu_time = exited_cpu_, .queue_time = exited_queue_};
// We store measurement-lists in the leaves, but histograms in the parents of the leaves.
// to_measure is pair <parent, child> treating root as the parent of itself.
std::vector<std::pair<CpuWatcher::Task*, CpuWatcher::Task*>> to_measure;
to_measure.reserve(task_count_);
to_measure.push_back(std::make_pair(&root_, &root_));
for (size_t current_offset = 0; current_offset < to_measure.size(); current_offset++) {
auto& children = to_measure[current_offset].second->children();
for (auto& child : children) {
to_measure.push_back(std::make_pair(to_measure[current_offset].second, child.second.get()));
}
}
auto stamp = parameters_.get_time();
for (auto cur_iter = to_measure.rbegin(); cur_iter != to_measure.rend(); ++cur_iter) {
auto cur = *cur_iter;
auto measurement = cur.second->Measure(stamp, parameters_, cur.first);
if (measurement.has_value()) {
overall.cpu_time += measurement.value().cpu_time;
overall.queue_time += measurement.value().queue_time;
}
for (auto it = cur.second->children().begin(); it != cur.second->children().end();) {
if (!it->second->is_alive()) {
it = cur.second->children().erase(it);
task_count_ -= 1;
task_count_value_.Set(task_count_);
} else {
++it;
}
}
}
inspect::ValueList value_list;
inspect::Node total_measurement =
total_node_.CreateChild(std::to_string(next_total_measurement_id_++));
total_measurement.RecordInt(kTimestamp, overall.timestamp);
total_measurement.RecordInt(kCpuTime, overall.cpu_time);
total_measurement.RecordInt(kQueueTime, overall.queue_time);
total_measurements_.emplace_back(std::move(total_measurement));
while (total_measurements_.size() > max_samples_) {
total_measurements_.pop_front();
}
second_most_recent_total_ = most_recent_total_;
most_recent_total_ = overall;
}
process_times_.Insert((parameters_.get_time() - start).get());
}
cpp17::optional<CpuWatcher::Measurement> CpuWatcher::Task::Measure(
const zx::time& timestamp, const CpuWatcherParameters& parameters, Task* parent) {
if (stats_reader_) {
TRACE_DURATION("appmgr", "CpuWatcher::Task::Measure");
zx_info_task_runtime_t info;
if (ZX_OK == stats_reader_->GetCpuStats(&info)) {
TRACE_DURATION("appmgr", "CpuWatcher::Task::Measure::AddMeasurement");
AddMeasurementToList(timestamp, info.cpu_time, info.queue_time);
ZX_DEBUG_ASSERT(parent != nullptr);
if (parent != nullptr) {
auto histogram = parent->histogram();
if (histogram) {
AddMeasurementToHistogram(timestamp, info.cpu_time - previous_cpu_, parameters,
histogram);
}
}
previous_cpu_ = info.cpu_time;
}
return cpp17::make_optional(Measurement{
.timestamp = timestamp.get(), .cpu_time = info.cpu_time, .queue_time = info.queue_time});
} else {
TRACE_DURATION("appmgr", "CpuWatcher::Task::Measure:Rotate");
rotate();
return cpp17::nullopt;
}
}
fpromise::promise<inspect::Inspector> CpuWatcher::PopulateInspector() const {
TRACE_DURATION("appmgr", "CpuWatcher::PopulateInspector");
std::lock_guard<std::mutex> lock(mutex_);
inspect::Inspector inspector(inspect::InspectSettings{.maximum_size = 2 * 1024 * 1024});
// TODO(fxbug.dev/75726) -- move this to standard place
auto stats_node = inspector.GetRoot().CreateChild(kInspect);
auto size = stats_node.CreateUint(kCurrentSize, 0);
auto max_size = stats_node.CreateUint(kMaximumSize, 0);
auto dynamic_links = stats_node.CreateUint(kDynamicLinks, 0);
struct WorkEntry {
const char* name;
const Task* task;
inspect::Node* parent;
};
std::vector<WorkEntry> work_stack;
work_stack.push_back(WorkEntry{.name = "root", .task = &root_, .parent = &inspector.GetRoot()});
while (!work_stack.empty()) {
auto entry = work_stack.back();
work_stack.pop_back();
auto node = std::make_unique<inspect::Node>(entry.parent->CreateChild(entry.name));
inspect::Node* node_ptr = node.get();
inspector.emplace(std::move(node));
if (!entry.task->measurements().empty()) {
inspect::Node samples = node_ptr->CreateChild(kSamples);
size_t next_id = 0;
for (const auto& measurement : entry.task->measurements()) {
auto sample = samples.CreateChild(std::to_string(next_id++));
sample.CreateInt(kTimestamp, measurement.timestamp, &inspector);
sample.CreateInt(kCpuTime, measurement.cpu_time, &inspector);
sample.CreateInt(kQueueTime, measurement.queue_time, &inspector);
inspector.emplace(std::move(sample));
}
inspector.emplace(std::move(samples));
}
for (const auto& child : entry.task->children()) {
work_stack.push_back(
WorkEntry{.name = child.first.c_str(), .task = &*child.second, .parent = node_ptr});
}
}
// Include stats about the Inspector that is being exposed.
// This data can be used to determine if the measurement inspector is full.
auto stats = inspector.GetStats();
size.Set(stats.size);
max_size.Set(stats.maximum_size);
dynamic_links.Set(stats.dynamic_child_count);
inspector.emplace(std::move(stats_node));
inspector.emplace(std::move(size));
inspector.emplace(std::move(max_size));
inspector.emplace(std::move(dynamic_links));
return fpromise::make_ok_promise(std::move(inspector));
}
fpromise::promise<inspect::Inspector> CpuWatcher::PopulateRecentUsage() const {
TRACE_DURATION("appmgr", "CpuWatcher::PopulateRecentUsage");
std::lock_guard<std::mutex> lock(mutex_);
inspect::Inspector inspector(inspect::InspectSettings{.maximum_size = 4096});
inspector.GetRoot().CreateInt(kPreviousCpuTime, second_most_recent_total_.cpu_time, &inspector);
inspector.GetRoot().CreateInt(kPreviousQueueTime, second_most_recent_total_.queue_time,
&inspector);
inspector.GetRoot().CreateInt(kPreviousTimestamp, second_most_recent_total_.timestamp,
&inspector);
inspector.GetRoot().CreateInt(kRecentCpuTime, most_recent_total_.cpu_time, &inspector);
inspector.GetRoot().CreateInt(kRecentQueueTime, most_recent_total_.queue_time, &inspector);
inspector.GetRoot().CreateInt(kRecentTimestamp, most_recent_total_.timestamp, &inspector);
return fpromise::make_ok_promise(std::move(inspector));
}
} // namespace component