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" } else {
104 "\x1b[37m" };
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 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}