Skip to main content

scx_mitosis/
stats.rs

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 = "Steal %")]
34    pub steal_pct: f64,
35    #[stat(desc = "Decision share % of global")]
36    pub share_of_decisions_pct: f64,
37    #[stat(desc = "Cell scheduling decisions")]
38    total_decisions: u64,
39}
40
41impl CellMetrics {
42    pub fn update(&mut self, ds: &DistributionStats) {
43        self.local_q_pct = ds.local_q_pct;
44        self.cpu_q_pct = ds.cpu_q_pct;
45        self.cell_q_pct = ds.cell_q_pct;
46        self.affn_violations_pct = ds.affn_viol_pct;
47        self.steal_pct = ds.steal_pct;
48        self.share_of_decisions_pct = ds.share_of_decisions_pct;
49        self.total_decisions = ds.total_decisions;
50    }
51}
52
53#[stat_doc]
54#[derive(Clone, Debug, Default, Serialize, Deserialize, Stats)]
55#[stat(top)]
56pub struct Metrics {
57    #[stat(desc = "Number of cells")]
58    pub num_cells: u32,
59    #[stat(desc = "Local queue %")]
60    pub local_q_pct: f64,
61    #[stat(desc = "CPU queue %")]
62    pub cpu_q_pct: f64,
63    #[stat(desc = "Cell queue %")]
64    pub cell_q_pct: f64,
65    #[stat(desc = "Affinity violations % of global")]
66    pub affn_violations_pct: f64,
67    #[stat(desc = "Steal %")]
68    pub steal_pct: f64,
69    #[stat(desc = "Decision share % of global")]
70    pub share_of_decisions_pct: f64,
71    #[stat(desc = "Cell scheduling decisions")]
72    total_decisions: u64,
73    #[stat(desc = "Per-cell metrics")] // TODO: cell names
74    pub cells: BTreeMap<u32, CellMetrics>,
75}
76
77impl Metrics {
78    pub fn update(&mut self, ds: &DistributionStats) {
79        self.local_q_pct = ds.local_q_pct;
80        self.cpu_q_pct = ds.cpu_q_pct;
81        self.cell_q_pct = ds.cell_q_pct;
82        self.affn_violations_pct = ds.affn_viol_pct;
83        self.steal_pct = ds.steal_pct;
84        self.share_of_decisions_pct = ds.share_of_decisions_pct;
85        self.total_decisions = ds.total_decisions;
86    }
87
88    fn delta(&self, _: &Self) -> Self {
89        Self { ..self.clone() }
90    }
91
92    fn format<W: Write>(&self, w: &mut W) -> Result<()> {
93        writeln!(w, "{}", serde_json::to_string_pretty(self)?)?;
94        Ok(())
95    }
96}
97
98pub fn server_data() -> StatsServerData<(), Metrics> {
99    let open: Box<dyn StatsOpener<(), Metrics>> = Box::new(move |(req_ch, res_ch)| {
100        req_ch.send(())?;
101        let mut prev = res_ch.recv()?;
102
103        let read: Box<dyn StatsReader<(), Metrics>> = Box::new(move |_args, (req_ch, res_ch)| {
104            req_ch.send(())?;
105            let cur = res_ch.recv()?;
106            let delta = cur.delta(&prev);
107            prev = cur;
108            delta.to_json()
109        });
110
111        Ok(read)
112    });
113
114    StatsServerData::new()
115        .add_meta(Metrics::meta())
116        .add_meta(CellMetrics::meta())
117        .add_ops("top", StatsOps { open, close: None })
118}
119
120pub fn monitor(intv: Duration, shutdown: Arc<AtomicBool>) -> Result<()> {
121    scx_utils::monitor_stats::<Metrics>(
122        &[],
123        intv,
124        || shutdown.load(Ordering::Relaxed),
125        |metrics| metrics.format(&mut std::io::stdout()),
126    )
127}