Skip to main content

scx_bpfland/
stats.rs

1use std::io::Write;
2use std::sync::Arc;
3use std::sync::atomic::AtomicBool;
4use std::sync::atomic::Ordering;
5use std::time::Duration;
6
7use anyhow::Result;
8use scx_stats::prelude::*;
9use scx_stats_derive::Stats;
10use scx_stats_derive::stat_doc;
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    #[stat(desc = "Number of idle select path picks")]
29    pub nr_idle_select_path_picks: u64,
30    #[stat(desc = "Number of idle enqueue path picks")]
31    pub nr_idle_enqueue_path_picks: u64,
32    #[stat(desc = "Number of idle prev CPU picks")]
33    pub nr_idle_prev_cpu_picks: u64,
34    #[stat(desc = "Number of idle primary picks")]
35    pub nr_idle_primary_picks: u64,
36    #[stat(desc = "Number of idle spill picks")]
37    pub nr_idle_spill_picks: u64,
38    #[stat(desc = "Number of idle pick failures")]
39    pub nr_idle_pick_failures: u64,
40    #[stat(desc = "Number of idle primary domain misses")]
41    pub nr_idle_primary_domain_misses: u64,
42    #[stat(desc = "Number of idle global misses")]
43    pub nr_idle_global_misses: u64,
44    #[stat(desc = "Number of waker CPU biases")]
45    pub nr_waker_cpu_biases: u64,
46    #[stat(desc = "Number of keep running reuses")]
47    pub nr_keep_running_reuses: u64,
48    #[stat(desc = "Number of keep running queue empty")]
49    pub nr_keep_running_queue_empty: u64,
50    #[stat(desc = "Number of keep running SMT blocked")]
51    pub nr_keep_running_smt_blocked: u64,
52    #[stat(desc = "Number of keep running queued work")]
53    pub nr_keep_running_queued_work: u64,
54    #[stat(desc = "Number of dispatch CPU DSQ consumes")]
55    pub nr_dispatch_cpu_dsq_consumes: u64,
56    #[stat(desc = "Number of dispatch node DSQ consumes")]
57    pub nr_dispatch_node_dsq_consumes: u64,
58}
59
60impl Metrics {
61    fn format<W: Write>(&self, w: &mut W) -> Result<()> {
62        writeln!(
63            w,
64            "[{}] tasks -> r: {:>2}/{:<2} | dispatch -> k: {:<5} d: {:<5} s: {:<5}",
65            crate::SCHEDULER_NAME,
66            self.nr_running,
67            self.nr_cpus,
68            self.nr_kthread_dispatches,
69            self.nr_direct_dispatches,
70            self.nr_shared_dispatches
71        )?;
72        Ok(())
73    }
74
75    fn delta(&self, rhs: &Self) -> Self {
76        Self {
77            nr_kthread_dispatches: self.nr_kthread_dispatches - rhs.nr_kthread_dispatches,
78            nr_direct_dispatches: self.nr_direct_dispatches - rhs.nr_direct_dispatches,
79            nr_shared_dispatches: self.nr_shared_dispatches - rhs.nr_shared_dispatches,
80            nr_idle_select_path_picks: self.nr_idle_select_path_picks
81                - rhs.nr_idle_select_path_picks,
82            nr_idle_enqueue_path_picks: self.nr_idle_enqueue_path_picks
83                - rhs.nr_idle_enqueue_path_picks,
84            nr_idle_prev_cpu_picks: self.nr_idle_prev_cpu_picks - rhs.nr_idle_prev_cpu_picks,
85            nr_idle_primary_picks: self.nr_idle_primary_picks - rhs.nr_idle_primary_picks,
86            nr_idle_spill_picks: self.nr_idle_spill_picks - rhs.nr_idle_spill_picks,
87            nr_idle_pick_failures: self.nr_idle_pick_failures - rhs.nr_idle_pick_failures,
88            nr_idle_primary_domain_misses: self.nr_idle_primary_domain_misses
89                - rhs.nr_idle_primary_domain_misses,
90            nr_idle_global_misses: self.nr_idle_global_misses - rhs.nr_idle_global_misses,
91            nr_waker_cpu_biases: self.nr_waker_cpu_biases - rhs.nr_waker_cpu_biases,
92            nr_keep_running_reuses: self.nr_keep_running_reuses - rhs.nr_keep_running_reuses,
93            nr_keep_running_queue_empty: self.nr_keep_running_queue_empty
94                - rhs.nr_keep_running_queue_empty,
95            nr_keep_running_smt_blocked: self.nr_keep_running_smt_blocked
96                - rhs.nr_keep_running_smt_blocked,
97            nr_keep_running_queued_work: self.nr_keep_running_queued_work
98                - rhs.nr_keep_running_queued_work,
99            nr_dispatch_cpu_dsq_consumes: self.nr_dispatch_cpu_dsq_consumes
100                - rhs.nr_dispatch_cpu_dsq_consumes,
101            nr_dispatch_node_dsq_consumes: self.nr_dispatch_node_dsq_consumes
102                - rhs.nr_dispatch_node_dsq_consumes,
103            ..self.clone()
104        }
105    }
106}
107
108pub fn server_data() -> StatsServerData<(), Metrics> {
109    let open: Box<dyn StatsOpener<(), Metrics>> = Box::new(move |(req_ch, res_ch)| {
110        req_ch.send(())?;
111        let mut prev = res_ch.recv()?;
112
113        let read: Box<dyn StatsReader<(), Metrics>> = Box::new(move |_args, (req_ch, res_ch)| {
114            req_ch.send(())?;
115            let cur = res_ch.recv()?;
116            let delta = cur.delta(&prev);
117            prev = cur;
118            delta.to_json()
119        });
120
121        Ok(read)
122    });
123
124    StatsServerData::new()
125        .add_meta(Metrics::meta())
126        .add_ops("top", StatsOps { open, close: None })
127}
128
129pub fn monitor(intv: Duration, shutdown: Arc<AtomicBool>) -> Result<()> {
130    scx_utils::monitor_stats::<Metrics>(
131        &[],
132        intv,
133        || shutdown.load(Ordering::Relaxed),
134        |metrics| metrics.format(&mut std::io::stdout()),
135    )
136}