scx_p2dq/
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// Global flag to track if thermal pressure tracking is enabled
15static THERMAL_TRACKING_ENABLED: AtomicBool = AtomicBool::new(false);
16
17// Global flag to track if energy-aware scheduling is enabled
18static EAS_ENABLED: AtomicBool = AtomicBool::new(false);
19
20pub fn set_thermal_tracking_enabled(enabled: bool) {
21    THERMAL_TRACKING_ENABLED.store(enabled, Ordering::Relaxed);
22}
23
24pub fn is_thermal_tracking_enabled() -> bool {
25    THERMAL_TRACKING_ENABLED.load(Ordering::Relaxed)
26}
27
28pub fn set_eas_enabled(enabled: bool) {
29    EAS_ENABLED.store(enabled, Ordering::Relaxed);
30}
31
32pub fn is_eas_enabled() -> bool {
33    EAS_ENABLED.load(Ordering::Relaxed)
34}
35
36#[stat_doc]
37#[derive(Clone, Debug, Default, Serialize, Deserialize, Stats)]
38#[stat(top)]
39pub struct Metrics {
40    #[stat(desc = "Number of times a task was enqueued to a ATQ")]
41    pub atq_enq: u64,
42    #[stat(desc = "Number of times a task was re-enqueued to a ATQ")]
43    pub atq_reenq: u64,
44    #[stat(desc = "Number of times tasks have switched DSQs")]
45    pub dsq_change: u64,
46    #[stat(desc = "Number of times tasks have stayed on the same DSQ")]
47    pub same_dsq: u64,
48    #[stat(desc = "Number of times a task kept running")]
49    pub keep: u64,
50    #[stat(desc = "Number of times a task was enqueued to CPUC DSQ")]
51    pub enq_cpu: u64,
52    #[stat(desc = "Number of times a task was enqueued to LLC DSQ")]
53    pub enq_llc: u64,
54    #[stat(desc = "Number of times a task was enqueued to interactive DSQ")]
55    pub enq_intr: u64,
56    #[stat(desc = "Number of times a task was enqueued to migration DSQ")]
57    pub enq_mig: u64,
58    #[stat(desc = "Number of times a select_cpu pick 2 load balancing occured")]
59    pub select_pick2: u64,
60    #[stat(desc = "Number of times a dispatch pick 2 load balancing occured")]
61    pub dispatch_pick2: u64,
62    #[stat(desc = "Number of times a task migrated LLCs")]
63    pub llc_migrations: u64,
64    #[stat(desc = "Number of times a task migrated NUMA nodes")]
65    pub node_migrations: u64,
66    #[stat(desc = "Number of times tasks have directly been dispatched to local per CPU DSQs")]
67    pub direct: u64,
68    #[stat(desc = "Number of times tasks have dispatched to an idle local per CPU DSQs")]
69    pub idle: u64,
70    #[stat(desc = "Number of times tasks have been woken to the previous CPU")]
71    pub wake_prev: u64,
72    #[stat(desc = "Number of times tasks have been woken to the previous llc")]
73    pub wake_llc: u64,
74    #[stat(desc = "Number of times tasks have been woken and migrated llc")]
75    pub wake_mig: u64,
76    #[stat(desc = "Number of times fork balancing migrated to different LLC")]
77    pub fork_balance: u64,
78    #[stat(desc = "Number of times exec balancing migrated to different LLC")]
79    pub exec_balance: u64,
80    #[stat(desc = "Number of times fork stayed on same LLC")]
81    pub fork_same_llc: u64,
82    #[stat(desc = "Number of times exec stayed on same LLC")]
83    pub exec_same_llc: u64,
84    #[stat(desc = "Number of CPU kicks due to thermal pressure")]
85    pub thermal_kick: u64,
86    #[stat(desc = "Number of times throttled CPUs were avoided")]
87    pub thermal_avoid: u64,
88    #[stat(desc = "Number of times EAS placed task on little core")]
89    pub eas_little_select: u64,
90    #[stat(desc = "Number of times EAS placed task on big core")]
91    pub eas_big_select: u64,
92    #[stat(desc = "Number of times EAS fell back to non-preferred core type")]
93    pub eas_fallback: u64,
94}
95
96impl Metrics {
97    fn format<W: Write>(&self, w: &mut W) -> Result<()> {
98        writeln!(
99            w,
100            "direct/idle/keep {}/{}/{}\n\tdsq same/migrate {}/{}\n\tatq enq/reenq {}/{}\n\tenq cpu/llc/intr/mig {}/{}/{}/{}",
101            self.direct,
102            self.idle,
103            self.keep,
104            self.same_dsq,
105            self.dsq_change,
106            self.atq_enq,
107            self.atq_reenq,
108            self.enq_cpu,
109            self.enq_llc,
110            self.enq_intr,
111            self.enq_mig,
112        )?;
113
114        // Build the stats line conditionally based on thermal tracking availability
115        let mut stats_line = format!(
116            "\twake prev/llc/mig {}/{}/{}\n\tpick2 select/dispatch {}/{}\n\tmigrations llc/node: {}/{}\n\tfork balance/same {}/{}\n\texec balance/same {}/{}",
117            self.wake_prev,
118            self.wake_llc,
119            self.wake_mig,
120            self.select_pick2,
121            self.dispatch_pick2,
122            self.llc_migrations,
123            self.node_migrations,
124            self.fork_balance,
125            self.fork_same_llc,
126            self.exec_balance,
127            self.exec_same_llc,
128        );
129
130        // Only show thermal stats if thermal tracking is enabled
131        if is_thermal_tracking_enabled() {
132            stats_line.push_str(&format!(
133                "\n\tthermal kick/avoid {}/{}",
134                self.thermal_kick, self.thermal_avoid,
135            ));
136        }
137
138        // Only show EAS stats if energy-aware scheduling is enabled
139        if is_eas_enabled() {
140            stats_line.push_str(&format!(
141                "\n\tEAS little/big/fallback {}/{}/{}",
142                self.eas_little_select, self.eas_big_select, self.eas_fallback,
143            ));
144        }
145
146        writeln!(w, "{}", stats_line)?;
147        Ok(())
148    }
149
150    fn delta(&self, rhs: &Self) -> Self {
151        Self {
152            atq_enq: self.atq_enq - rhs.atq_enq,
153            atq_reenq: self.atq_reenq - rhs.atq_reenq,
154            direct: self.direct - rhs.direct,
155            idle: self.idle - rhs.idle,
156            dsq_change: self.dsq_change - rhs.dsq_change,
157            same_dsq: self.same_dsq - rhs.same_dsq,
158            keep: self.keep - rhs.keep,
159            enq_cpu: self.enq_cpu - rhs.enq_cpu,
160            enq_llc: self.enq_llc - rhs.enq_llc,
161            enq_intr: self.enq_intr - rhs.enq_intr,
162            enq_mig: self.enq_mig - rhs.enq_mig,
163            select_pick2: self.select_pick2 - rhs.select_pick2,
164            dispatch_pick2: self.dispatch_pick2 - rhs.dispatch_pick2,
165            llc_migrations: self.llc_migrations - rhs.llc_migrations,
166            node_migrations: self.node_migrations - rhs.node_migrations,
167            wake_prev: self.wake_prev - rhs.wake_prev,
168            wake_llc: self.wake_llc - rhs.wake_llc,
169            wake_mig: self.wake_mig - rhs.wake_mig,
170            fork_balance: self.fork_balance - rhs.fork_balance,
171            exec_balance: self.exec_balance - rhs.exec_balance,
172            fork_same_llc: self.fork_same_llc - rhs.fork_same_llc,
173            exec_same_llc: self.exec_same_llc - rhs.exec_same_llc,
174            thermal_kick: self.thermal_kick - rhs.thermal_kick,
175            thermal_avoid: self.thermal_avoid - rhs.thermal_avoid,
176            eas_little_select: self.eas_little_select - rhs.eas_little_select,
177            eas_big_select: self.eas_big_select - rhs.eas_big_select,
178            eas_fallback: self.eas_fallback - rhs.eas_fallback,
179        }
180    }
181}
182pub fn server_data() -> StatsServerData<(), Metrics> {
183    let open: Box<dyn StatsOpener<(), Metrics>> = Box::new(move |(req_ch, res_ch)| {
184        req_ch.send(())?;
185        let mut prev = res_ch.recv()?;
186
187        let read: Box<dyn StatsReader<(), Metrics>> = Box::new(move |_args, (req_ch, res_ch)| {
188            req_ch.send(())?;
189            let cur = res_ch.recv()?;
190            let delta = cur.delta(&prev);
191            prev = cur;
192            delta.to_json()
193        });
194
195        Ok(read)
196    });
197
198    StatsServerData::new()
199        .add_meta(Metrics::meta())
200        .add_ops("top", StatsOps { open, close: None })
201}
202
203pub fn monitor(intv: Duration, shutdown: Arc<AtomicBool>) -> Result<()> {
204    scx_utils::monitor_stats::<Metrics>(
205        &[],
206        intv,
207        || shutdown.load(Ordering::Relaxed),
208        |metrics| metrics.format(&mut std::io::stdout()),
209    )
210}