Skip to main content

scx_bpfland/
stats.rs

1use std::io::Write;
2use std::sync::atomic::AtomicBool;
3use std::sync::atomic::Ordering;
4use std::sync::Arc;
5use std::time::Duration;
6
7use anyhow::Result;
8use scx_stats::prelude::*;
9use scx_stats_derive::stat_doc;
10use scx_stats_derive::Stats;
11use serde::Deserialize;
12use serde::Serialize;
13
14#[stat_doc]
15#[derive(Clone, Debug, Default, Serialize, Deserialize, Stats)]
16#[stat(top)]
17pub struct Metrics {
18    #[stat(desc = "Number of running tasks")]
19    pub nr_running: u64,
20    #[stat(desc = "Number of online CPUs")]
21    pub nr_cpus: u64,
22    #[stat(desc = "Number of kthread direct dispatches")]
23    pub nr_kthread_dispatches: u64,
24    #[stat(desc = "Number of task direct dispatches")]
25    pub nr_direct_dispatches: u64,
26    #[stat(desc = "Number of regular task dispatches")]
27    pub nr_shared_dispatches: u64,
28    // TIMELY stats (zero when timely mode is disabled)
29    #[stat(desc = "Number of delay recovery dispatches")]
30    pub nr_delay_recovery_dispatches: u64,
31    #[stat(desc = "Number of delay middle add dispatches")]
32    pub nr_delay_middle_add_dispatches: u64,
33    #[stat(desc = "Number of delay fast recovery dispatches")]
34    pub nr_delay_fast_recovery_dispatches: u64,
35    #[stat(desc = "Number of delay rate-limited dispatches")]
36    pub nr_delay_rate_limited_dispatches: u64,
37    #[stat(desc = "Number of gain floor dispatches")]
38    pub nr_gain_floor_dispatches: u64,
39    #[stat(desc = "Number of gain ceiling dispatches")]
40    pub nr_gain_ceiling_dispatches: u64,
41    #[stat(desc = "Number of delay low region samples")]
42    pub nr_delay_low_region_samples: u64,
43    #[stat(desc = "Number of delay mid region samples")]
44    pub nr_delay_mid_region_samples: u64,
45    #[stat(desc = "Number of delay high region samples")]
46    pub nr_delay_high_region_samples: u64,
47    #[stat(desc = "Number of gain floor resident samples")]
48    pub nr_gain_floor_resident_samples: u64,
49    #[stat(desc = "Number of gain mid resident samples")]
50    pub nr_gain_mid_resident_samples: u64,
51    #[stat(desc = "Number of gain ceiling resident samples")]
52    pub nr_gain_ceiling_resident_samples: u64,
53    #[stat(desc = "Number of idle select path picks")]
54    pub nr_idle_select_path_picks: u64,
55    #[stat(desc = "Number of idle enqueue path picks")]
56    pub nr_idle_enqueue_path_picks: u64,
57    #[stat(desc = "Number of idle prev CPU picks")]
58    pub nr_idle_prev_cpu_picks: u64,
59    #[stat(desc = "Number of idle primary picks")]
60    pub nr_idle_primary_picks: u64,
61    #[stat(desc = "Number of idle spill picks")]
62    pub nr_idle_spill_picks: u64,
63    #[stat(desc = "Number of idle pick failures")]
64    pub nr_idle_pick_failures: u64,
65    #[stat(desc = "Number of idle primary domain misses")]
66    pub nr_idle_primary_domain_misses: u64,
67    #[stat(desc = "Number of idle global misses")]
68    pub nr_idle_global_misses: u64,
69    #[stat(desc = "Number of waker CPU biases")]
70    pub nr_waker_cpu_biases: u64,
71    #[stat(desc = "Number of keep running reuses")]
72    pub nr_keep_running_reuses: u64,
73    #[stat(desc = "Number of keep running queue empty")]
74    pub nr_keep_running_queue_empty: u64,
75    #[stat(desc = "Number of keep running SMT blocked")]
76    pub nr_keep_running_smt_blocked: u64,
77    #[stat(desc = "Number of keep running queued work")]
78    pub nr_keep_running_queued_work: u64,
79    #[stat(desc = "Number of dispatch CPU DSQ consumes")]
80    pub nr_dispatch_cpu_dsq_consumes: u64,
81    #[stat(desc = "Number of dispatch node DSQ consumes")]
82    pub nr_dispatch_node_dsq_consumes: u64,
83}
84
85impl Metrics {
86    fn format<W: Write>(&self, w: &mut W) -> Result<()> {
87        writeln!(
88            w,
89            "[{}] tasks -> r: {:>2}/{:<2} | dispatch -> k: {:<5} d: {:<5} s: {:<5} | timely -> rec: {:<5} mid: {:<5} rl: {:<5} min: {:<5} max: {:<5}",
90            crate::SCHEDULER_NAME,
91            self.nr_running,
92            self.nr_cpus,
93            self.nr_kthread_dispatches,
94            self.nr_direct_dispatches,
95            self.nr_shared_dispatches,
96            self.nr_delay_recovery_dispatches,
97            self.nr_delay_middle_add_dispatches,
98            self.nr_delay_rate_limited_dispatches,
99            self.nr_gain_floor_dispatches,
100            self.nr_gain_ceiling_dispatches
101        )?;
102        Ok(())
103    }
104
105    fn delta(&self, rhs: &Self) -> Self {
106        Self {
107            nr_kthread_dispatches: self.nr_kthread_dispatches - rhs.nr_kthread_dispatches,
108            nr_direct_dispatches: self.nr_direct_dispatches - rhs.nr_direct_dispatches,
109            nr_shared_dispatches: self.nr_shared_dispatches - rhs.nr_shared_dispatches,
110            nr_delay_recovery_dispatches: self.nr_delay_recovery_dispatches
111                - rhs.nr_delay_recovery_dispatches,
112            nr_delay_middle_add_dispatches: self.nr_delay_middle_add_dispatches
113                - rhs.nr_delay_middle_add_dispatches,
114            nr_delay_fast_recovery_dispatches: self.nr_delay_fast_recovery_dispatches
115                - rhs.nr_delay_fast_recovery_dispatches,
116            nr_delay_rate_limited_dispatches: self.nr_delay_rate_limited_dispatches
117                - rhs.nr_delay_rate_limited_dispatches,
118            nr_gain_floor_dispatches: self.nr_gain_floor_dispatches - rhs.nr_gain_floor_dispatches,
119            nr_gain_ceiling_dispatches: self.nr_gain_ceiling_dispatches
120                - rhs.nr_gain_ceiling_dispatches,
121            nr_delay_low_region_samples: self.nr_delay_low_region_samples
122                - rhs.nr_delay_low_region_samples,
123            nr_delay_mid_region_samples: self.nr_delay_mid_region_samples
124                - rhs.nr_delay_mid_region_samples,
125            nr_delay_high_region_samples: self.nr_delay_high_region_samples
126                - rhs.nr_delay_high_region_samples,
127            nr_gain_floor_resident_samples: self.nr_gain_floor_resident_samples
128                - rhs.nr_gain_floor_resident_samples,
129            nr_gain_mid_resident_samples: self.nr_gain_mid_resident_samples
130                - rhs.nr_gain_mid_resident_samples,
131            nr_gain_ceiling_resident_samples: self.nr_gain_ceiling_resident_samples
132                - rhs.nr_gain_ceiling_resident_samples,
133            nr_idle_select_path_picks: self.nr_idle_select_path_picks
134                - rhs.nr_idle_select_path_picks,
135            nr_idle_enqueue_path_picks: self.nr_idle_enqueue_path_picks
136                - rhs.nr_idle_enqueue_path_picks,
137            nr_idle_prev_cpu_picks: self.nr_idle_prev_cpu_picks - rhs.nr_idle_prev_cpu_picks,
138            nr_idle_primary_picks: self.nr_idle_primary_picks - rhs.nr_idle_primary_picks,
139            nr_idle_spill_picks: self.nr_idle_spill_picks - rhs.nr_idle_spill_picks,
140            nr_idle_pick_failures: self.nr_idle_pick_failures - rhs.nr_idle_pick_failures,
141            nr_idle_primary_domain_misses: self.nr_idle_primary_domain_misses
142                - rhs.nr_idle_primary_domain_misses,
143            nr_idle_global_misses: self.nr_idle_global_misses - rhs.nr_idle_global_misses,
144            nr_waker_cpu_biases: self.nr_waker_cpu_biases - rhs.nr_waker_cpu_biases,
145            nr_keep_running_reuses: self.nr_keep_running_reuses - rhs.nr_keep_running_reuses,
146            nr_keep_running_queue_empty: self.nr_keep_running_queue_empty
147                - rhs.nr_keep_running_queue_empty,
148            nr_keep_running_smt_blocked: self.nr_keep_running_smt_blocked
149                - rhs.nr_keep_running_smt_blocked,
150            nr_keep_running_queued_work: self.nr_keep_running_queued_work
151                - rhs.nr_keep_running_queued_work,
152            nr_dispatch_cpu_dsq_consumes: self.nr_dispatch_cpu_dsq_consumes
153                - rhs.nr_dispatch_cpu_dsq_consumes,
154            nr_dispatch_node_dsq_consumes: self.nr_dispatch_node_dsq_consumes
155                - rhs.nr_dispatch_node_dsq_consumes,
156            ..self.clone()
157        }
158    }
159}
160
161pub fn server_data() -> StatsServerData<(), Metrics> {
162    let open: Box<dyn StatsOpener<(), Metrics>> = Box::new(move |(req_ch, res_ch)| {
163        req_ch.send(())?;
164        let mut prev = res_ch.recv()?;
165
166        let read: Box<dyn StatsReader<(), Metrics>> = Box::new(move |_args, (req_ch, res_ch)| {
167            req_ch.send(())?;
168            let cur = res_ch.recv()?;
169            let delta = cur.delta(&prev);
170            prev = cur;
171            delta.to_json()
172        });
173
174        Ok(read)
175    });
176
177    StatsServerData::new()
178        .add_meta(Metrics::meta())
179        .add_ops("top", StatsOps { open, close: None })
180}
181
182pub fn monitor(intv: Duration, shutdown: Arc<AtomicBool>) -> Result<()> {
183    scx_utils::monitor_stats::<Metrics>(
184        &[],
185        intv,
186        || shutdown.load(Ordering::Relaxed),
187        |metrics| metrics.format(&mut std::io::stdout()),
188    )
189}