scx_lavd/
stats.rs

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