| // 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/fit/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 { |
| constexpr char kTimestamp[] = "timestamp"; |
| constexpr char kCpuTime[] = "cpu_time"; |
| constexpr char kQueueTime[] = "queue_time"; |
| constexpr char kPreviousCpuTime[] = "previous_cpu_time"; |
| constexpr char kPreviousQueueTime[] = "previous_queue_time"; |
| constexpr char kPreviousTimestamp[] = "previous_timestamp"; |
| constexpr char kRecentCpuTime[] = "recent_cpu_time"; |
| constexpr char kRecentQueueTime[] = "recent_queue_time"; |
| constexpr char kRecentTimestamp[] = "recent_timestamp"; |
| } // namespace |
| |
| void CpuWatcher::AddTask(const InstancePath& instance_path, zx::job job) { |
| TRACE_DURATION("appmgr", "CpuWatcher::AddTask", "name", |
| instance_path.empty() ? "" : instance_path.back()); |
| |
| std::lock_guard<std::mutex> lock(mutex_); |
| |
| Task* cur_task = &root_; |
| for (const auto& part : instance_path) { |
| auto it = cur_task->children().find(part); |
| if (it == cur_task->children().end()) { |
| bool inserted; |
| std::tie(it, inserted) = |
| cur_task->children().emplace(part, std::make_unique<Task>(Task(zx::job(), max_samples_))); |
| task_count_ += 1; |
| task_count_value_.Set(task_count_); |
| } |
| cur_task = it->second.get(); |
| } |
| cur_task->job() = std::move(job); |
| // Measure tasks on creation. |
| cur_task->Measure(zx::clock::get_monotonic()); |
| } |
| |
| 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_; |
| for (const auto& part : instance_path) { |
| auto it = cur_task->children().find(part); |
| if (it == cur_task->children().end()) { |
| return; |
| } |
| |
| cur_task = it->second.get(); |
| } |
| |
| // Measure before resetting the job, so we get final runtime stats. |
| cur_task->Measure(zx::clock::get_monotonic()); |
| 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->job().reset(); |
| } |
| |
| void CpuWatcher::Measure() { |
| zx::time start = zx::clock::get_monotonic(); |
| { |
| 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_}; |
| std::vector<Task*> to_measure; |
| to_measure.push_back(&root_); |
| to_measure.reserve(task_count_); |
| |
| for (size_t current_offset = 0; current_offset < to_measure.size(); current_offset++) { |
| auto& children = to_measure[current_offset]->children(); |
| for (auto& child : children) { |
| to_measure.push_back(child.second.get()); |
| } |
| } |
| |
| auto stamp = zx::clock::get_monotonic(); |
| |
| for (auto cur_iter = to_measure.rbegin(); cur_iter != to_measure.rend(); ++cur_iter) { |
| auto* cur = *cur_iter; |
| |
| auto measurement = cur->Measure(stamp); |
| if (measurement.has_value()) { |
| overall.cpu_time += measurement.value().cpu_time; |
| overall.queue_time += measurement.value().queue_time; |
| } |
| |
| for (auto it = cur->children().begin(); it != cur->children().end();) { |
| if (!it->second->is_alive()) { |
| it = cur->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.CreateInt(kTimestamp, overall.timestamp, &value_list); |
| total_measurement.CreateInt(kCpuTime, overall.cpu_time, &value_list); |
| total_measurement.CreateInt(kQueueTime, overall.queue_time, &value_list); |
| value_list.emplace(std::move(total_measurement)); |
| total_measurements_.emplace_back(std::move(value_list)); |
| while (total_measurements_.size() > max_samples_) { |
| total_measurements_.pop_front(); |
| } |
| |
| second_most_recent_total_ = most_recent_total_; |
| most_recent_total_ = overall; |
| } |
| process_times_.Insert((zx::clock::get_monotonic() - start).get()); |
| } |
| |
| fit::optional<CpuWatcher::Measurement> CpuWatcher::Task::Measure(const zx::time& timestamp) { |
| if (job().is_valid()) { |
| TRACE_DURATION("appmgr", "CpuWatcher::Task::Measure"); |
| zx_info_task_runtime_t info; |
| if (ZX_OK == job().get_info(ZX_INFO_TASK_RUNTIME, &info, sizeof(info), nullptr, nullptr)) { |
| TRACE_DURATION("appmgr", "CpuWatcher::Task::Measure::AddMeasurement"); |
| add_measurement(timestamp.get(), info.cpu_time, info.queue_time); |
| } |
| return fit::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 fit::nullopt; |
| } |
| } |
| |
| fit::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}); |
| |
| auto stats_node = inspector.GetRoot().CreateChild("@inspect"); |
| auto size = stats_node.CreateUint("current_size", 0); |
| auto max_size = stats_node.CreateUint("maximum_size", 0); |
| auto dynamic_links = stats_node.CreateUint("dynamic_links", 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("@samples"); |
| 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 fit::make_ok_promise(std::move(inspector)); |
| } |
| |
| fit::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 fit::make_ok_promise(std::move(inspector)); |
| } |
| |
| } // namespace component |