| // 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. |
| |
| #ifndef SRC_SYS_APPMGR_CPU_WATCHER_H_ |
| #define SRC_SYS_APPMGR_CPU_WATCHER_H_ |
| |
| #include <lib/inspect/cpp/inspect.h> |
| #include <lib/inspect/cpp/value_list.h> |
| #include <lib/inspect/cpp/vmo/types.h> |
| #include <lib/zx/job.h> |
| #include <lib/zx/vmo.h> |
| #include <zircon/threads.h> |
| |
| #include <deque> |
| #include <map> |
| #include <string> |
| #include <thread> |
| |
| #include <src/lib/fxl/macros.h> |
| #include <src/sys/appmgr/component_controller_impl.h> |
| |
| namespace component { |
| |
| // Watch CPU usage for tasks on the system. |
| // |
| // The CpuWatcher periodically samples all CPU usage registered tasks and exposes it in an Inspect |
| // hierarchy. |
| class CpuWatcher { |
| public: |
| // Create a new CpuWatcher that exposes CPU data under the given inspect node. The given job |
| // appears as the root of the hierarchy. |
| CpuWatcher(inspect::Node node, zx::job job, size_t max_samples = kDefaultMaxSamples) |
| : top_node_(std::move(node)), |
| measurements_( |
| top_node_.CreateLazyNode("measurements", [this] { return PopulateInspector(); })), |
| task_count_value_(top_node_.CreateInt("task_count", 0)), |
| process_times_(top_node_.CreateExponentialIntHistogram( |
| "process_time_ns", kProcessTimeFloor, kProcessTimeStep, kProcessTimeMultiplier, |
| kProcessTimeBuckets)), |
| root_(std::move(job), max_samples), |
| total_node_(top_node_.CreateChild("@total")), |
| recent_cpu_usage_( |
| top_node_.CreateLazyNode("recent_usage", [this] { return PopulateRecentUsage(); })), |
| max_samples_(max_samples) {} |
| |
| // Add a task to this watcher by instance path. |
| // job: The job to sample CPU runtime from. |
| void AddTask(const InstancePath& instance_path, zx::job job); |
| |
| // Remove a task by instance path. |
| void RemoveTask(const InstancePath& instance_path); |
| |
| // Execute a measurement at the current time. |
| void Measure(); |
| |
| private: |
| static constexpr int64_t kProcessTimeFloor = 1000; |
| static constexpr int64_t kProcessTimeStep = 1000; |
| static constexpr int64_t kProcessTimeMultiplier = 2; |
| static constexpr int64_t kProcessTimeBuckets = 16; |
| static constexpr size_t kDefaultMaxSamples = 60; |
| |
| fit::promise<inspect::Inspector> PopulateInspector() const; |
| fit::promise<inspect::Inspector> PopulateRecentUsage() const; |
| |
| // An individual measurement. |
| struct Measurement { |
| zx_time_t timestamp; |
| zx_duration_t cpu_time; |
| zx_duration_t queue_time; |
| }; |
| |
| // A task that can be measured. |
| class Task { |
| public: |
| Task(zx::job job, size_t max_samples) : job_(std::move(job)), max_samples_(max_samples){}; |
| |
| // Add a measurement to this task. |
| void add_measurement(zx_time_t time, zx_duration_t cpu, zx_duration_t queue) { |
| measurements_.emplace_back( |
| Measurement{.timestamp = time, .cpu_time = cpu, .queue_time = queue}); |
| while (measurements_.size() > max_samples_) { |
| measurements_.pop_front(); |
| } |
| } |
| |
| // Rotate measurements if not empty. This is used when a task is already destroyed to ensure |
| // that we still rotate measurements outside the window. |
| void rotate() { |
| if (!measurements_.empty()) { |
| measurements_.pop_front(); |
| } |
| } |
| |
| bool is_alive() const { |
| // Keep a task around if we will either take measurements from it, or we have existing |
| // measurements. |
| return job_.is_valid() || !measurements_.empty() || !children_.empty(); |
| } |
| |
| zx::job& job() { return job_; } |
| std::map<std::string, std::unique_ptr<Task>>& children() { return children_; } |
| const std::map<std::string, std::unique_ptr<Task>>& children() const { return children_; } |
| const std::deque<Measurement>& measurements() const { return measurements_; } |
| |
| // Takes and records a new measurement for this task. A copy of the measurement is returned if |
| // one was taken. |
| fit::optional<Measurement> Measure(const zx::time& timestamp); |
| |
| private: |
| // The job to sample. |
| zx::job job_; |
| |
| // The maximum number of samples to store for this task. |
| const size_t max_samples_; |
| |
| // Deque of measurements. |
| std::deque<Measurement> measurements_; |
| |
| // Map of children for this task. |
| std::map<std::string, std::unique_ptr<Task>> children_; |
| }; |
| |
| mutable std::mutex mutex_; |
| inspect::Node top_node_; |
| inspect::LazyNode measurements_; |
| inspect::IntProperty task_count_value_; |
| inspect::ExponentialIntHistogram process_times_; |
| |
| size_t task_count_ __TA_GUARDED(mutex_) = 1; // 1 for root_ |
| Task root_ __TA_GUARDED(mutex_); |
| |
| // Total CPU and queue time of exited tasks. Used to ensure those values are not lost when |
| // calculating overall CPU usage on the system. |
| zx_duration_t exited_cpu_ __TA_GUARDED(mutex_) = 0; |
| zx_duration_t exited_queue_ __TA_GUARDED(mutex_) = 0; |
| inspect::Node total_node_; |
| size_t next_total_measurement_id_ __TA_GUARDED(mutex_) = 0; |
| std::deque<inspect::ValueList> total_measurements_ __TA_GUARDED(mutex_); |
| |
| inspect::LazyNode recent_cpu_usage_; |
| Measurement most_recent_total_ = {}, second_most_recent_total_ = {}; |
| |
| const size_t max_samples_; |
| |
| FXL_DISALLOW_COPY_AND_ASSIGN(CpuWatcher); |
| }; |
| |
| } // namespace component |
| |
| #endif // SRC_SYS_APPMGR_CPU_WATCHER_H_ |