1use std::io::Write;
6use std::sync::atomic::AtomicBool;
7use std::sync::atomic::Ordering;
8use std::sync::Arc;
9use std::time::Duration;
10
11use anyhow::Result;
12use scx_stats::prelude::*;
13use scx_stats_derive::stat_doc;
14use scx_stats_derive::Stats;
15use serde::Deserialize;
16use serde::Serialize;
17
18#[derive(Clone, Debug, Default, Serialize, Deserialize)]
19pub struct PerCpuMetrics {
20 pub id: u32,
21 pub freq_khz: u64,
22 pub llc_id: u32,
23 pub smt: bool,
24}
25
26#[derive(Clone, Debug, Default, Serialize, Deserialize)]
27pub struct WebMetrics {
28 pub stats: Metrics,
29 pub per_cpu: Vec<PerCpuMetrics>,
30 pub carriage_filling_count: u64,
31}
32
33#[stat_doc]
34#[derive(Clone, Debug, Default, Serialize, Deserialize, Stats)]
35#[stat(top)]
36pub struct Metrics {
37 #[stat(desc = "Tasks currently executing on a CPU")]
38 pub on_cpu: u64,
39 #[stat(desc = "Total CPU runtime in ns")]
40 pub total_runtime: u64,
41 #[stat(desc = "Scheduler uptime (wall clock since attach)")]
42 pub uptime_ns: u64,
43 #[stat(desc = "Tasks dispatched via the wakeup fast path")]
44 pub prio_dispatches: u64,
45 #[stat(desc = "Tasks dispatched from the per-CPU pinned DSQ")]
46 pub pinned_dispatches: u64,
47
48 #[stat(desc = "Carriage pool slot index")]
49 pub carriage_producer: u64,
50
51 #[stat(desc = "Times a task ran its budget down to zero or below")]
52 pub budget_exhaustions: u64,
53 #[stat(desc = "Runnable wakeups observed")]
54 pub runnable_wakeups: u64,
55 #[stat(desc = "Observed task migrations")]
56 pub cpu_migrations: u64,
57}
58
59impl Metrics {
60 fn format<W: Write>(&self, w: &mut W) -> Result<()> {
61 writeln!(
62 w,
63 "[{}] run={} runtime_ns={} uptime_ns={} quick_disp={} pinned_disp={} \
64 pool: slot={} \
65 exhaust={} runnable={} migrations={}",
66 crate::SCHEDULER_NAME,
67 self.on_cpu,
68 self.total_runtime,
69 self.uptime_ns,
70 self.prio_dispatches,
71 self.pinned_dispatches,
72 self.carriage_producer & 63,
73 self.budget_exhaustions,
74 self.runnable_wakeups,
75 self.cpu_migrations,
76 )?;
77 Ok(())
78 }
79
80 pub fn delta(&self, rhs: &Self) -> Self {
81 Self {
82 on_cpu: self.on_cpu,
83 total_runtime: self.total_runtime.wrapping_sub(rhs.total_runtime),
84 uptime_ns: self.uptime_ns,
85 prio_dispatches: self.prio_dispatches.wrapping_sub(rhs.prio_dispatches),
86 pinned_dispatches: self.pinned_dispatches.wrapping_sub(rhs.pinned_dispatches),
87 carriage_producer: self.carriage_producer,
88
89 budget_exhaustions: self.budget_exhaustions.wrapping_sub(rhs.budget_exhaustions),
90 runnable_wakeups: self.runnable_wakeups.wrapping_sub(rhs.runnable_wakeups),
91 cpu_migrations: self.cpu_migrations.wrapping_sub(rhs.cpu_migrations),
92 }
93 }
94}
95
96pub fn server_data() -> StatsServerData<(), Metrics> {
97 let open: Box<dyn StatsOpener<(), Metrics>> = Box::new(move |(req_ch, res_ch)| {
98 req_ch.send(())?;
99 let mut prev = res_ch.recv()?;
100
101 let read: Box<dyn StatsReader<(), Metrics>> = Box::new(move |_args, (req_ch, res_ch)| {
102 req_ch.send(())?;
103 let cur = res_ch.recv()?;
104 let delta = cur.delta(&prev);
105 prev = cur;
106 delta.to_json()
107 });
108
109 Ok(read)
110 });
111
112 StatsServerData::new()
113 .add_meta(Metrics::meta())
114 .add_ops("top", StatsOps { open, close: None })
115}
116
117pub fn monitor(intv: Duration, shutdown: Arc<AtomicBool>) -> Result<()> {
118 scx_utils::monitor_stats::<Metrics>(
119 &[],
120 intv,
121 || shutdown.load(Ordering::Relaxed),
122 |metrics| metrics.format(&mut std::io::stdout()),
123 )
124}