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 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}