Skip to main content

scx_flow/
stats.rs

1// SPDX-License-Identifier: GPL-2.0
2//
3// Copyright (c) 2026 Galih Tama <galpt@v.recipes>
4
5use std::io::Write;
6use std::sync::atomic::AtomicBool;
7use std::sync::atomic::Ordering;
8use std::sync::Arc;
9use std::time::Duration;
10
11use anyhow::Result;
12use scx_stats::prelude::*;
13use scx_stats_derive::stat_doc;
14use scx_stats_derive::Stats;
15use serde::Deserialize;
16use serde::Serialize;
17
18#[derive(Clone, Debug, Default, Serialize, Deserialize)]
19pub struct PerCpuMetrics {
20    pub id: u32,
21    pub freq_khz: u64,
22    pub llc_id: u32,
23    pub smt: bool,
24}
25
26#[derive(Clone, Debug, Default, Serialize, Deserialize)]
27pub struct WebMetrics {
28    pub stats: Metrics,
29    pub per_cpu: Vec<PerCpuMetrics>,
30    pub carriage_filling_count: u64,
31}
32
33#[stat_doc]
34#[derive(Clone, Debug, Default, Serialize, Deserialize, Stats)]
35#[stat(top)]
36pub struct Metrics {
37    #[stat(desc = "Tasks currently executing on a CPU")]
38    pub on_cpu: u64,
39    #[stat(desc = "Total CPU runtime in ns")]
40    pub total_runtime: u64,
41    #[stat(desc = "Scheduler uptime (wall clock since attach)")]
42    pub uptime_ns: u64,
43    #[stat(desc = "Tasks dispatched via the wakeup fast path")]
44    pub prio_dispatches: u64,
45    #[stat(desc = "Tasks dispatched from the per-CPU pinned DSQ")]
46    pub pinned_dispatches: u64,
47
48    #[stat(desc = "Carriage pool slot index")]
49    pub carriage_producer: u64,
50
51    #[stat(desc = "Times a task ran its budget down to zero or below")]
52    pub budget_exhaustions: u64,
53    #[stat(desc = "Runnable wakeups observed")]
54    pub runnable_wakeups: u64,
55    #[stat(desc = "Observed task migrations")]
56    pub cpu_migrations: u64,
57}
58
59impl Metrics {
60    fn format<W: Write>(&self, w: &mut W) -> Result<()> {
61        writeln!(
62            w,
63            "[{}] run={} runtime_ns={} uptime_ns={} quick_disp={} pinned_disp={} \
64             pool: slot={} \
65              exhaust={} runnable={} migrations={}",
66            crate::SCHEDULER_NAME,
67            self.on_cpu,
68            self.total_runtime,
69            self.uptime_ns,
70            self.prio_dispatches,
71            self.pinned_dispatches,
72            self.carriage_producer & 63,
73            self.budget_exhaustions,
74            self.runnable_wakeups,
75            self.cpu_migrations,
76        )?;
77        Ok(())
78    }
79
80    pub fn delta(&self, rhs: &Self) -> Self {
81        Self {
82            on_cpu: self.on_cpu,
83            total_runtime: self.total_runtime.wrapping_sub(rhs.total_runtime),
84            uptime_ns: self.uptime_ns,
85            prio_dispatches: self.prio_dispatches.wrapping_sub(rhs.prio_dispatches),
86            pinned_dispatches: self.pinned_dispatches.wrapping_sub(rhs.pinned_dispatches),
87            carriage_producer: self.carriage_producer,
88
89            budget_exhaustions: self.budget_exhaustions.wrapping_sub(rhs.budget_exhaustions),
90            runnable_wakeups: self.runnable_wakeups.wrapping_sub(rhs.runnable_wakeups),
91            cpu_migrations: self.cpu_migrations.wrapping_sub(rhs.cpu_migrations),
92        }
93    }
94}
95
96pub fn server_data() -> StatsServerData<(), Metrics> {
97    let open: Box<dyn StatsOpener<(), Metrics>> = Box::new(move |(req_ch, res_ch)| {
98        req_ch.send(())?;
99        let mut prev = res_ch.recv()?;
100
101        let read: Box<dyn StatsReader<(), Metrics>> = Box::new(move |_args, (req_ch, res_ch)| {
102            req_ch.send(())?;
103            let cur = res_ch.recv()?;
104            let delta = cur.delta(&prev);
105            prev = cur;
106            delta.to_json()
107        });
108
109        Ok(read)
110    });
111
112    StatsServerData::new()
113        .add_meta(Metrics::meta())
114        .add_ops("top", StatsOps { open, close: None })
115}
116
117pub fn monitor(intv: Duration, shutdown: Arc<AtomicBool>) -> Result<()> {
118    scx_utils::monitor_stats::<Metrics>(
119        &[],
120        intv,
121        || shutdown.load(Ordering::Relaxed),
122        |metrics| metrics.format(&mut std::io::stdout()),
123    )
124}