1use std::collections::BTreeMap;
2use std::io::Write;
3use std::sync::atomic::AtomicBool;
4use std::sync::atomic::Ordering;
5use std::sync::Arc;
6use std::time::Duration;
7
8use anyhow::Result;
9use serde::Deserialize;
10use serde::Serialize;
11
12use scx_stats::prelude::*;
13use scx_stats_derive::stat_doc;
14use scx_stats_derive::Stats;
15
16use crate::DistributionStats;
17
18#[stat_doc]
19#[derive(Clone, Debug, Default, Serialize, Deserialize, Stats)]
20#[stat(_om_prefix = "c_")]
21#[stat(top)]
22pub struct CellMetrics {
23 #[stat(desc = "Number of cpus")]
24 pub num_cpus: u32,
25 #[stat(desc = "Local queue %")]
26 pub local_q_pct: f64,
27 #[stat(desc = "CPU queue %")]
28 pub cpu_q_pct: f64,
29 #[stat(desc = "Cell queue %")]
30 pub cell_q_pct: f64,
31 #[stat(desc = "Affinity violations % of global")]
32 pub affn_violations_pct: f64,
33 #[stat(desc = "Decision share % of global")]
34 pub share_of_decisions_pct: f64,
35 #[stat(desc = "Cell scheduling decisions")]
36 total_decisions: u64,
37}
38
39impl CellMetrics {
40 pub fn update(&mut self, ds: &DistributionStats) {
41 self.local_q_pct = ds.local_q_pct;
42 self.cpu_q_pct = ds.cpu_q_pct;
43 self.cell_q_pct = ds.cell_q_pct;
44 self.affn_violations_pct = ds.affn_viol_pct;
45 self.share_of_decisions_pct = ds.share_of_decisions_pct;
46 self.total_decisions = ds.total_decisions;
47 }
48}
49
50#[stat_doc]
51#[derive(Clone, Debug, Default, Serialize, Deserialize, Stats)]
52#[stat(top)]
53pub struct Metrics {
54 #[stat(desc = "Number of cells")]
55 pub num_cells: u32,
56 #[stat(desc = "Local queue %")]
57 pub local_q_pct: f64,
58 #[stat(desc = "CPU queue %")]
59 pub cpu_q_pct: f64,
60 #[stat(desc = "Cell queue %")]
61 pub cell_q_pct: f64,
62 #[stat(desc = "Affinity violations % of global")]
63 pub affn_violations_pct: f64,
64 #[stat(desc = "Decision share % of global")]
65 pub share_of_decisions_pct: f64,
66 #[stat(desc = "Cell scheduling decisions")]
67 total_decisions: u64,
68 #[stat(desc = "Per-cell metrics")] pub cells: BTreeMap<u32, CellMetrics>,
70}
71
72impl Metrics {
73 pub fn update(&mut self, ds: &DistributionStats) {
74 self.local_q_pct = ds.local_q_pct;
75 self.cpu_q_pct = ds.cpu_q_pct;
76 self.cell_q_pct = ds.cell_q_pct;
77 self.affn_violations_pct = ds.affn_viol_pct;
78 self.share_of_decisions_pct = ds.share_of_decisions_pct;
79 self.total_decisions = ds.total_decisions;
80 }
81
82 fn delta(&self, _: &Self) -> Self {
83 Self { ..self.clone() }
84 }
85
86 fn format<W: Write>(&self, w: &mut W) -> Result<()> {
87 writeln!(w, "{}", serde_json::to_string_pretty(self)?)?;
88 Ok(())
89 }
90}
91
92pub fn server_data() -> StatsServerData<(), Metrics> {
93 let open: Box<dyn StatsOpener<(), Metrics>> = Box::new(move |(req_ch, res_ch)| {
94 req_ch.send(())?;
95 let mut prev = res_ch.recv()?;
96
97 let read: Box<dyn StatsReader<(), Metrics>> = Box::new(move |_args, (req_ch, res_ch)| {
98 req_ch.send(())?;
99 let cur = res_ch.recv()?;
100 let delta = cur.delta(&prev);
101 prev = cur;
102 delta.to_json()
103 });
104
105 Ok(read)
106 });
107
108 StatsServerData::new()
109 .add_meta(Metrics::meta())
110 .add_meta(CellMetrics::meta())
111 .add_ops("top", StatsOps { open, close: None })
112}
113
114pub fn monitor(intv: Duration, shutdown: Arc<AtomicBool>) -> Result<()> {
115 scx_utils::monitor_stats::<Metrics>(
116 &[],
117 intv,
118 || shutdown.load(Ordering::Relaxed),
119 |metrics| metrics.format(&mut std::io::stdout()),
120 )
121}