1use std::io::Write;
2use std::sync::atomic::AtomicBool;
3use std::sync::atomic::Ordering;
4use std::sync::Arc;
5use std::time::Duration;
6
7use anyhow::Result;
8use scx_p2dq::TOPO;
9use scx_stats::prelude::*;
10use scx_stats_derive::stat_doc;
11use scx_stats_derive::Stats;
12use serde::Deserialize;
13use serde::Serialize;
14
15static THERMAL_TRACKING_ENABLED: AtomicBool = AtomicBool::new(false);
17
18static EAS_ENABLED: AtomicBool = AtomicBool::new(false);
20
21static ATQ_ENABLED: AtomicBool = AtomicBool::new(false);
22
23pub fn set_thermal_tracking_enabled(enabled: bool) {
24 THERMAL_TRACKING_ENABLED.store(enabled, Ordering::Relaxed);
25}
26
27pub fn is_thermal_tracking_enabled() -> bool {
28 THERMAL_TRACKING_ENABLED.load(Ordering::Relaxed)
29}
30
31pub fn set_eas_enabled(enabled: bool) {
32 EAS_ENABLED.store(enabled, Ordering::Relaxed);
33}
34
35pub fn is_eas_enabled() -> bool {
36 EAS_ENABLED.load(Ordering::Relaxed)
37}
38
39pub fn set_atq_enabled(enabled: bool) {
40 ATQ_ENABLED.store(enabled, Ordering::Relaxed);
41}
42
43pub fn is_atq_enabled() -> bool {
44 ATQ_ENABLED.load(Ordering::Relaxed)
45}
46
47#[stat_doc]
48#[derive(Clone, Debug, Default, Serialize, Deserialize, Stats)]
49#[stat(top)]
50pub struct Metrics {
51 #[stat(desc = "Number of times a task was enqueued to a ATQ")]
52 pub atq_enq: u64,
53 #[stat(desc = "Number of times a task was re-enqueued to a ATQ")]
54 pub atq_reenq: u64,
55 #[stat(desc = "Number of times tasks have switched DSQs")]
56 pub dsq_change: u64,
57 #[stat(desc = "Number of times tasks have stayed on the same DSQ")]
58 pub same_dsq: u64,
59 #[stat(desc = "Number of times a task kept running")]
60 pub keep: u64,
61 #[stat(desc = "Number of times a task was enqueued to CPUC DSQ")]
62 pub enq_cpu: u64,
63 #[stat(desc = "Number of times a task was enqueued to LLC DSQ")]
64 pub enq_llc: u64,
65 #[stat(desc = "Number of times a task was enqueued to interactive DSQ")]
66 pub enq_intr: u64,
67 #[stat(desc = "Number of times a task was enqueued to migration DSQ")]
68 pub enq_mig: u64,
69 #[stat(desc = "Number of times a select_cpu pick 2 load balancing occurred")]
70 pub select_pick2: u64,
71 #[stat(desc = "Number of times a dispatch pick 2 load balancing occurred")]
72 pub dispatch_pick2: u64,
73 #[stat(desc = "Number of times a task migrated LLCs")]
74 pub llc_migrations: u64,
75 #[stat(desc = "Number of times a task migrated NUMA nodes")]
76 pub node_migrations: u64,
77 #[stat(desc = "Number of times tasks have directly been dispatched to local per CPU DSQs")]
78 pub direct: u64,
79 #[stat(desc = "Number of times tasks have dispatched to an idle local per CPU DSQs")]
80 pub idle: u64,
81 #[stat(desc = "Number of times tasks have been woken to the previous CPU")]
82 pub wake_prev: u64,
83 #[stat(desc = "Number of times tasks have been woken to the previous llc")]
84 pub wake_llc: u64,
85 #[stat(desc = "Number of times tasks have been woken and migrated llc")]
86 pub wake_mig: u64,
87 #[stat(desc = "Number of times fork balancing migrated to different LLC")]
88 pub fork_balance: u64,
89 #[stat(desc = "Number of times exec balancing migrated to different LLC")]
90 pub exec_balance: u64,
91 #[stat(desc = "Number of times fork stayed on same LLC")]
92 pub fork_same_llc: u64,
93 #[stat(desc = "Number of times exec stayed on same LLC")]
94 pub exec_same_llc: u64,
95 #[stat(desc = "Number of CPU kicks due to thermal pressure")]
96 pub thermal_kick: u64,
97 #[stat(desc = "Number of times throttled CPUs were avoided")]
98 pub thermal_avoid: u64,
99 #[stat(desc = "Number of times EAS placed task on little core")]
100 pub eas_little_select: u64,
101 #[stat(desc = "Number of times EAS placed task on big core")]
102 pub eas_big_select: u64,
103 #[stat(desc = "Number of times EAS fell back to non-preferred core type")]
104 pub eas_fallback: u64,
105}
106
107impl Metrics {
108 fn format<W: Write>(&self, w: &mut W) -> Result<()> {
109 let multi_llc = TOPO.all_llcs.len() > 1;
110 let atq = is_atq_enabled();
111
112 write!(
113 w,
114 "direct/idle/keep {}/{}/{}\n\tdsq same/migrate {}/{}",
115 self.direct, self.idle, self.keep, self.same_dsq, self.dsq_change,
116 )?;
117
118 if atq {
119 write!(w, "\n\tatq enq/reenq {}/{}", self.atq_enq, self.atq_reenq)?;
120 }
121
122 if multi_llc {
123 writeln!(
124 w,
125 "\n\tenq cpu/llc/intr/mig {}/{}/{}/{}",
126 self.enq_cpu, self.enq_llc, self.enq_intr, self.enq_mig,
127 )?;
128 } else {
129 writeln!(
130 w,
131 "\n\tenq cpu/llc/intr {}/{}/{}",
132 self.enq_cpu, self.enq_llc, self.enq_intr,
133 )?;
134 }
135
136 let mut stats_line = format!("\twake prev {}", self.wake_prev);
137
138 if multi_llc {
139 stats_line.push_str(&format!(
140 "/llc/mig {}/{}\n\tpick2 select/dispatch {}/{}\n\tmigrations llc/node {}/{}\n\tfork balance/same {}/{}\n\texec balance/same {}/{}",
141 self.wake_llc,
142 self.wake_mig,
143 self.select_pick2,
144 self.dispatch_pick2,
145 self.llc_migrations,
146 self.node_migrations,
147 self.fork_balance,
148 self.fork_same_llc,
149 self.exec_balance,
150 self.exec_same_llc,
151 ));
152 }
153
154 if is_thermal_tracking_enabled() {
155 stats_line.push_str(&format!(
156 "\n\tthermal kick/avoid {}/{}",
157 self.thermal_kick, self.thermal_avoid,
158 ));
159 }
160
161 if is_eas_enabled() {
162 stats_line.push_str(&format!(
163 "\n\tEAS little/big/fallback {}/{}/{}",
164 self.eas_little_select, self.eas_big_select, self.eas_fallback,
165 ));
166 }
167
168 writeln!(w, "{}", stats_line)?;
169 Ok(())
170 }
171
172 fn delta(&self, rhs: &Self) -> Self {
173 Self {
174 atq_enq: self.atq_enq - rhs.atq_enq,
175 atq_reenq: self.atq_reenq - rhs.atq_reenq,
176 direct: self.direct - rhs.direct,
177 idle: self.idle - rhs.idle,
178 dsq_change: self.dsq_change - rhs.dsq_change,
179 same_dsq: self.same_dsq - rhs.same_dsq,
180 keep: self.keep - rhs.keep,
181 enq_cpu: self.enq_cpu - rhs.enq_cpu,
182 enq_llc: self.enq_llc - rhs.enq_llc,
183 enq_intr: self.enq_intr - rhs.enq_intr,
184 enq_mig: self.enq_mig - rhs.enq_mig,
185 select_pick2: self.select_pick2 - rhs.select_pick2,
186 dispatch_pick2: self.dispatch_pick2 - rhs.dispatch_pick2,
187 llc_migrations: self.llc_migrations - rhs.llc_migrations,
188 node_migrations: self.node_migrations - rhs.node_migrations,
189 wake_prev: self.wake_prev - rhs.wake_prev,
190 wake_llc: self.wake_llc - rhs.wake_llc,
191 wake_mig: self.wake_mig - rhs.wake_mig,
192 fork_balance: self.fork_balance - rhs.fork_balance,
193 exec_balance: self.exec_balance - rhs.exec_balance,
194 fork_same_llc: self.fork_same_llc - rhs.fork_same_llc,
195 exec_same_llc: self.exec_same_llc - rhs.exec_same_llc,
196 thermal_kick: self.thermal_kick - rhs.thermal_kick,
197 thermal_avoid: self.thermal_avoid - rhs.thermal_avoid,
198 eas_little_select: self.eas_little_select - rhs.eas_little_select,
199 eas_big_select: self.eas_big_select - rhs.eas_big_select,
200 eas_fallback: self.eas_fallback - rhs.eas_fallback,
201 }
202 }
203}
204pub fn server_data() -> StatsServerData<(), Metrics> {
205 let open: Box<dyn StatsOpener<(), Metrics>> = Box::new(move |(req_ch, res_ch)| {
206 req_ch.send(())?;
207 let mut prev = res_ch.recv()?;
208
209 let read: Box<dyn StatsReader<(), Metrics>> = Box::new(move |_args, (req_ch, res_ch)| {
210 req_ch.send(())?;
211 let cur = res_ch.recv()?;
212 let delta = cur.delta(&prev);
213 prev = cur;
214 delta.to_json()
215 });
216
217 Ok(read)
218 });
219
220 StatsServerData::new()
221 .add_meta(Metrics::meta())
222 .add_ops("top", StatsOps { open, close: None })
223}
224
225pub fn monitor(intv: Duration, shutdown: Arc<AtomicBool>) -> Result<()> {
226 scx_utils::monitor_stats::<Metrics>(
227 &[],
228 intv,
229 || shutdown.load(Ordering::Relaxed),
230 |metrics| metrics.format(&mut std::io::stdout()),
231 )
232}