scx_lavd/
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::thread::ThreadId;
7use std::time::Duration;
8
9use anyhow::bail;
10use anyhow::{Context, Result};
11use gpoint::GPoint;
12use scx_stats::prelude::*;
13use scx_stats_derive::stat_doc;
14use scx_stats_derive::Stats;
15use serde::Deserialize;
16use serde::Serialize;
17
18#[stat_doc]
19#[derive(Clone, Debug, Default, Serialize, Deserialize, Stats)]
20#[stat(top)]
21pub struct SysStats {
22    #[stat(desc = "Sequence ID of this message")]
23    pub mseq: u64,
24
25    #[stat(desc = "Number of runnable tasks in runqueues")]
26    pub nr_queued_task: u64,
27
28    #[stat(desc = "Number of active CPUs when core compaction is enabled")]
29    pub nr_active: u32,
30
31    #[stat(desc = "Number of context switches")]
32    pub nr_sched: u64,
33
34    #[stat(desc = "Number of task preemption triggered")]
35    pub nr_preempt: u64,
36
37    #[stat(desc = "% of performance-critical tasks")]
38    pub pc_pc: f64,
39
40    #[stat(desc = "% of latency-critical tasks")]
41    pub pc_lc: f64,
42
43    #[stat(desc = "% of cross domain task migration")]
44    pub pc_x_migration: f64,
45
46    #[stat(desc = "Number of stealee domains")]
47    pub nr_stealee: u32,
48
49    #[stat(desc = "% of tasks scheduled on big cores")]
50    pub pc_big: f64,
51
52    #[stat(desc = "% of performance-critical tasks scheduled on big cores")]
53    pub pc_pc_on_big: f64,
54
55    #[stat(desc = "% of latency-critical tasks scheduled on big cores")]
56    pub pc_lc_on_big: f64,
57
58    #[stat(desc = "Current power mode")]
59    pub power_mode: String,
60
61    #[stat(desc = "% of performance mode")]
62    pub pc_performance: f64,
63
64    #[stat(desc = "% of balanced mode")]
65    pub pc_balanced: f64,
66
67    #[stat(desc = "% of powersave mode")]
68    pub pc_powersave: f64,
69}
70
71impl SysStats {
72    pub fn format_header<W: Write>(w: &mut W) -> Result<()> {
73        writeln!(
74            w,
75            "\x1b[93m| {:8} | {:9} | {:9} | {:8} | {:9} | {:8} | {:8} | {:8} | {:8} | {:8} | {:8} | {:8} | {:11} | {:12} | {:12} | {:12} |\x1b[0m",
76            "MSEQ",
77            "# Q TASK",
78            "# ACT CPU",
79            "# SCHED",
80            "# PREEMPT",
81            "PERF-CR%",
82            "LAT-CR%",
83            "X-MIG%",
84            "# STLEE",
85            "BIG%",
86            "PC/BIG%",
87            "LC/BIG%",
88            "POWER MODE",
89            "PERFORMANCE%",
90            "BALANCED%",
91            "POWERSAVE%",
92        )?;
93        Ok(())
94    }
95
96    fn format<W: Write>(&self, w: &mut W) -> Result<()> {
97        if self.mseq % 10 == 1 {
98            Self::format_header(w)?;
99        }
100
101        let color = if self.mseq % 2 == 0 {
102            "\x1b[90m" // Dark gray for even mseq
103        } else {
104            "\x1b[37m" // white for odd mseq
105        };
106
107        writeln!(
108            w,
109            "{color}| {:8} | {:9} | {:9} | {:8} | {:9} | {:8} | {:8} | {:8} | {:8} | {:8} | {:8} | {:8} | {:11} | {:12} | {:12} | {:12} |\x1b[0m",
110            self.mseq,
111            self.nr_queued_task,
112            self.nr_active,
113            self.nr_sched,
114            self.nr_preempt,
115            GPoint(self.pc_pc),
116            GPoint(self.pc_lc),
117            GPoint(self.pc_x_migration),
118            self.nr_stealee,
119            GPoint(self.pc_big),
120            GPoint(self.pc_pc_on_big),
121            GPoint(self.pc_lc_on_big),
122            self.power_mode,
123            GPoint(self.pc_performance),
124            GPoint(self.pc_balanced),
125            GPoint(self.pc_powersave),
126        )?;
127        Ok(())
128    }
129}
130
131#[stat_doc]
132#[derive(Clone, Debug, Default, Serialize, Deserialize, Stats)]
133#[stat(top, _om_prefix = "s_", _om_label = "sched_sample")]
134pub struct SchedSample {
135    #[stat(desc = "Sequence ID of this message")]
136    pub mseq: u64,
137    #[stat(desc = "Process ID")]
138    pub pid: i32,
139    #[stat(desc = "Task name")]
140    pub comm: String,
141    #[stat(
142        desc = "LR: 'L'atency-critical or 'R'egular, HI: performance-'H'ungry or performance-'I'nsensitive, BT: 'B'ig or li'T'tle, EG: 'E'ligible or 'G'reedy, PN: 'P'reempting or 'N'ot"
143    )]
144    pub stat: String,
145    #[stat(desc = "CPU id where this task is scheduled on")]
146    pub cpu_id: u32,
147    #[stat(desc = "Assigned time slice")]
148    pub slice_ns: u32,
149    #[stat(desc = "Latency criticality of this task")]
150    pub lat_cri: u32,
151    #[stat(desc = "Average latency criticality in a system")]
152    pub avg_lat_cri: u32,
153    #[stat(desc = "Static priority (20 == nice 0)")]
154    pub static_prio: u16,
155    #[stat(desc = "How often this task is scheduled per second")]
156    pub run_freq: u64,
157    #[stat(desc = "Average runtime per schedule")]
158    pub avg_runtime: u64,
159    #[stat(desc = "How frequently this task waits for other tasks")]
160    pub wait_freq: u64,
161    #[stat(desc = "How frequently this task wakes other tasks")]
162    pub wake_freq: u64,
163    #[stat(desc = "Performance criticality of this task")]
164    pub perf_cri: u32,
165    #[stat(desc = "Performance criticality threshold")]
166    pub thr_perf_cri: u32,
167    #[stat(desc = "Target performance level of this CPU")]
168    pub cpuperf_cur: u32,
169    #[stat(desc = "CPU utilization of this CPU")]
170    pub cpu_util: u64,
171    #[stat(desc = "Scaled CPU utilization of this CPU")]
172    pub cpu_sutil: u64,
173    #[stat(desc = "Number of active CPUs when core compaction is enabled")]
174    pub nr_active: u32,
175}
176
177impl SchedSample {
178    pub fn format_header<W: Write>(w: &mut W) -> Result<()> {
179        writeln!(
180            w,
181            "\x1b[93m| {:6} | {:7} | {:17} | {:5} | {:4} | {:8} | {:8} | {:7} | {:8} | {:9} | {:9} | {:9} | {:9} | {:8} | {:8} | {:8} | {:8} | {:8} | {:6} |\x1b[0m",
182            "MSEQ",
183            "PID",
184            "COMM",
185            "STAT",
186            "CPU",
187            "SLC_NS",
188            "LAT_CRI",
189            "AVG_LC",
190            "ST_PRIO",
191            "RUN_FREQ",
192            "RUN_TM_NS",
193            "WAIT_FREQ",
194            "WAKE_FREQ",
195            "PERF_CRI",
196            "THR_PC",
197            "CPUFREQ",
198            "CPU_UTIL",
199            "CPU_SUTIL",
200            "NR_ACT",
201        )?;
202        Ok(())
203    }
204
205    pub fn format<W: Write>(&self, w: &mut W) -> Result<()> {
206        if self.mseq % 10 == 1 {
207            Self::format_header(w)?;
208        }
209
210        writeln!(
211            w,
212            "| {:6} | {:7} | {:17} | {:5} | {:4} | {:8} | {:8} | {:7} | {:8} | {:9} | {:9} | {:9} | {:9} | {:8} | {:8} | {:8} | {:8} | {:8} | {:6} |",
213            self.mseq,
214            self.pid,
215            self.comm,
216            self.stat,
217            self.cpu_id,
218            self.slice_ns,
219            self.lat_cri,
220            self.avg_lat_cri,
221            self.static_prio,
222            self.run_freq,
223            self.avg_runtime,
224            self.wait_freq,
225            self.wake_freq,
226            self.perf_cri,
227            self.thr_perf_cri,
228            self.cpuperf_cur,
229            self.cpu_util,
230            self.cpu_sutil,
231            self.nr_active,
232        )?;
233        Ok(())
234    }
235}
236
237#[derive(Clone, Debug, Default, Serialize, Deserialize, Stats)]
238pub struct SchedSamples {
239    pub samples: Vec<SchedSample>,
240}
241
242#[derive(Debug)]
243pub enum StatsReq {
244    NewSampler(ThreadId),
245    SysStatsReq {
246        tid: ThreadId,
247    },
248    SchedSamplesNr {
249        tid: ThreadId,
250        nr_samples: u64,
251        interval_ms: u64,
252    },
253}
254
255impl StatsReq {
256    fn from_args_stats(tid: ThreadId) -> Result<Self> {
257        Ok(Self::SysStatsReq { tid })
258    }
259
260    fn from_args_samples(
261        tid: ThreadId,
262        nr_cpus_onln: u64,
263        args: &BTreeMap<String, String>,
264    ) -> Result<Self> {
265        let mut nr_samples = 1;
266
267        if let Some(arg) = args.get("nr_samples") {
268            nr_samples = arg.trim().parse()?;
269        }
270
271        let mut interval_ms = 1000;
272        if nr_samples > nr_cpus_onln {
273            // More samples, shorter sampling interval.
274            let f = nr_samples / nr_cpus_onln * 2;
275            interval_ms /= f;
276        }
277
278        Ok(Self::SchedSamplesNr {
279            tid,
280            nr_samples,
281            interval_ms,
282        })
283    }
284}
285
286#[derive(Debug)]
287pub enum StatsRes {
288    Ack,
289    Bye,
290    SysStats(SysStats),
291    SchedSamples(SchedSamples),
292}
293
294pub fn server_data(nr_cpus_onln: u64) -> StatsServerData<StatsReq, StatsRes> {
295    let open: Box<dyn StatsOpener<StatsReq, StatsRes>> = Box::new(move |(req_ch, res_ch)| {
296        let tid = std::thread::current().id();
297        req_ch.send(StatsReq::NewSampler(tid))?;
298        match res_ch.recv()? {
299            StatsRes::Ack => {}
300            res => bail!("invalid response: {:?}", res),
301        }
302
303        let read: Box<dyn StatsReader<StatsReq, StatsRes>> =
304            Box::new(move |_args, (req_ch, res_ch)| {
305                let req = StatsReq::from_args_stats(tid)?;
306                req_ch.send(req)?;
307
308                let stats = match res_ch.recv()? {
309                    StatsRes::SysStats(v) => v,
310                    StatsRes::Bye => bail!("preempted by another sampler"),
311                    res => bail!("invalid response: {:?}", res),
312                };
313
314                stats.to_json()
315            });
316        Ok(read)
317    });
318
319    let samples_open: Box<dyn StatsOpener<StatsReq, StatsRes>> =
320        Box::new(move |(req_ch, res_ch)| {
321            let tid = std::thread::current().id();
322            req_ch.send(StatsReq::NewSampler(tid))?;
323            match res_ch.recv()? {
324                StatsRes::Ack => {}
325                res => bail!("invalid response: {:?}", res),
326            }
327
328            let read: Box<dyn StatsReader<StatsReq, StatsRes>> =
329                Box::new(move |args, (req_ch, res_ch)| {
330                    let req = StatsReq::from_args_samples(tid, nr_cpus_onln, args)?;
331                    req_ch.send(req)?;
332
333                    let samples = match res_ch.recv()? {
334                        StatsRes::SchedSamples(v) => v,
335                        StatsRes::Bye => bail!("preempted by another sampler"),
336                        res => bail!("invalid response: {:?}", res),
337                    };
338
339                    samples.to_json()
340                });
341            Ok(read)
342        });
343
344    StatsServerData::new()
345        .add_meta(SysStats::meta())
346        .add_ops("top", StatsOps { open, close: None })
347        .add_meta(SchedSample::meta())
348        .add_ops(
349            "sched_samples",
350            StatsOps {
351                open: samples_open,
352                close: None,
353            },
354        )
355}
356
357pub fn monitor_sched_samples(nr_samples: u64, shutdown: Arc<AtomicBool>) -> Result<()> {
358    scx_utils::monitor_stats::<SchedSamples>(
359        &vec![
360            ("target".into(), "sched_samples".into()),
361            ("nr_samples".into(), nr_samples.to_string()),
362        ],
363        Duration::from_secs(0),
364        || shutdown.load(Ordering::Relaxed),
365        |ts| {
366            let mut stdout = std::io::stdout();
367            for sample in ts.samples.iter() {
368                sample.format(&mut stdout)?;
369            }
370            Ok(())
371        },
372    )
373}
374
375pub fn monitor(intv: Duration, shutdown: Arc<AtomicBool>) -> Result<()> {
376    scx_utils::monitor_stats::<SysStats>(
377        &vec![],
378        intv,
379        || shutdown.load(Ordering::Relaxed),
380        |sysstats| {
381            sysstats
382                .format(&mut std::io::stdout())
383                .context("failed to format sysstats")?;
384            Ok(())
385        },
386    )
387}