1use std::io::Write;
11use std::sync::Arc;
12use std::sync::atomic::AtomicBool;
13use std::sync::atomic::Ordering;
14use std::time::Duration;
15
16use anyhow::Result;
17use scx_stats::prelude::*;
18use scx_stats_derive::Stats;
19use scx_stats_derive::stat_doc;
20use serde::Deserialize;
21use serde::Serialize;
22
23#[stat_doc]
24#[derive(Clone, Debug, Default, Serialize, Deserialize, Stats)]
25#[stat(top)]
26pub struct Metrics {
29 #[stat(desc = "Tasks now on a CPU")]
30 #[serde(default)]
31 pub on_cpu: u64,
32 #[stat(desc = "Total runtime in nanoseconds")]
33 #[serde(default)]
34 pub total_runtime: u64,
35 #[stat(desc = "Uptime since attach in nanoseconds")]
38 #[serde(default)]
39 pub uptime_ns: u64,
40 #[stat(desc = "Fresh deadline joins")]
41 #[serde(default)]
42 pub inserts: u64,
43 #[stat(desc = "Runnable slice ends with requeue")]
44 #[serde(default)]
45 pub requeues: u64,
46 #[stat(desc = "Blocks and exits")]
47 #[serde(default)]
48 pub completions: u64,
49 #[stat(desc = "Moves from the local tier")]
50 #[serde(default)]
51 pub local_moves: u64,
52 #[stat(desc = "Moves from the node tier")]
53 #[serde(default)]
54 pub node_moves: u64,
55 #[stat(desc = "Moves from the machine tier")]
56 #[serde(default)]
57 pub machine_moves: u64,
58 #[stat(desc = "Idle wakeup kicks sent after insert")]
59 #[serde(default)]
60 pub kicks: u64,
61 #[stat(desc = "Tasks admitted with no bound")]
62 #[serde(default)]
63 pub admits: u64,
64 #[stat(
65 desc = "Rejects dead at zero whitelisted for wire compat with no writer; real rejects count in gate_rejects"
66 )]
67 #[serde(default)]
68 pub rejects: u64,
69 #[stat(desc = "Wall completions past deadline")]
70 #[serde(default)]
71 pub misses: u64,
72 #[stat(desc = "Closed gate rejects on stale CPUs plus tasks")]
73 #[serde(default)]
74 pub gate_rejects: u64,
75 #[stat(desc = "Busy preempts sent with margin plus tail")]
76 #[serde(default)]
77 pub preempt_kicks: u64,
78 #[stat(desc = "Preempts held by margin plus tail plus eligibility")]
79 #[serde(default)]
80 pub preempt_skipped: u64,
81 #[stat(desc = "RED overload rejects to the value ordered queue")]
82 #[serde(default)]
83 pub red_rejects: u64,
84 #[stat(desc = "RED reclaims from the value ordered queue")]
85 #[serde(default)]
86 pub red_reclaims: u64,
87}
88
89#[derive(Clone, Debug, Default, Serialize, Deserialize)]
95pub struct PerCpuMetrics {
96 #[serde(default)]
98 pub id: u32,
99 #[serde(default)]
102 pub smt: bool,
103 #[serde(default)]
105 pub running_pid: u32,
106 #[serde(default)]
108 pub slice_ns: u64,
109}
110
111#[derive(Clone, Debug, Default, Serialize, Deserialize)]
116pub struct WebMetrics {
117 pub stats: Metrics,
119 #[serde(default)]
121 pub per_cpu: Vec<PerCpuMetrics>,
122 #[serde(default)]
124 pub version: String,
125 #[serde(default)]
127 pub timestamp_ns: u64,
128 #[serde(default)]
130 pub topology: String,
131}
132
133pub fn monitor(intv: Duration, shutdown: Arc<AtomicBool>) -> Result<()> {
136 scx_utils::monitor_stats::<Metrics>(
137 &[],
138 intv,
139 || shutdown.load(Ordering::Relaxed),
140 |metrics| metrics.format(&mut std::io::stdout()),
141 )
142}
143
144pub fn server_data() -> StatsServerData<(), Metrics> {
147 let open: Box<dyn StatsOpener<(), Metrics>> = Box::new(move |(req_ch, res_ch)| {
148 req_ch.send(())?;
149 let mut prev = res_ch.recv()?;
150 let read: Box<dyn StatsReader<(), Metrics>> = Box::new(move |_args, (req_ch, res_ch)| {
151 req_ch.send(())?;
152 let cur = res_ch.recv()?;
153 let delta = cur.delta(&prev);
154 prev = cur;
155 delta.to_json()
156 });
157 Ok(read)
158 });
159 StatsServerData::new()
160 .add_meta(Metrics::meta())
161 .add_ops("top", StatsOps { open, close: None })
162}
163
164impl Metrics {
165 fn format<W: Write>(&self, w: &mut W) -> Result<()> {
166 writeln!(
167 w,
168 "[{}] run={} runtime_ns={} uptime_ns={} ins={} req={} done={} \
169 local={} node={} machine={} kick={} adm={} rej={} \
170 miss={} gate={} pkick={} pskip={} redrej={} redrec={}",
171 crate::SCHEDULER_NAME,
172 self.on_cpu,
173 self.total_runtime,
174 self.uptime_ns,
175 self.inserts,
176 self.requeues,
177 self.completions,
178 self.local_moves,
179 self.node_moves,
180 self.machine_moves,
181 self.kicks,
182 self.admits,
183 self.rejects,
184 self.misses,
185 self.gate_rejects,
186 self.preempt_kicks,
187 self.preempt_skipped,
188 self.red_rejects,
189 self.red_reclaims,
190 )?;
191 Ok(())
192 }
193
194 pub fn delta(&self, rhs: &Self) -> Self {
197 Self {
198 on_cpu: self.on_cpu,
199 total_runtime: self.total_runtime.wrapping_sub(rhs.total_runtime),
200 uptime_ns: self.uptime_ns,
201 inserts: self.inserts.wrapping_sub(rhs.inserts),
202 requeues: self.requeues.wrapping_sub(rhs.requeues),
203 completions: self.completions.wrapping_sub(rhs.completions),
204 local_moves: self.local_moves.wrapping_sub(rhs.local_moves),
205 node_moves: self.node_moves.wrapping_sub(rhs.node_moves),
206 machine_moves: self.machine_moves.wrapping_sub(rhs.machine_moves),
207 kicks: self.kicks.wrapping_sub(rhs.kicks),
208 admits: self.admits.wrapping_sub(rhs.admits),
209 rejects: self.rejects.wrapping_sub(rhs.rejects),
210 misses: self.misses.wrapping_sub(rhs.misses),
211 gate_rejects: self.gate_rejects.wrapping_sub(rhs.gate_rejects),
212 preempt_kicks: self.preempt_kicks.wrapping_sub(rhs.preempt_kicks),
213 preempt_skipped: self.preempt_skipped.wrapping_sub(rhs.preempt_skipped),
214 red_rejects: self.red_rejects.wrapping_sub(rhs.red_rejects),
215 red_reclaims: self.red_reclaims.wrapping_sub(rhs.red_reclaims),
216 }
217 }
218}