1use std::borrow::Cow;
30
31use super::{MetricDef, metric_def};
32
33macro_rules! builtin_metrics {
38 ($($variant:ident => $wire:literal),* $(,)?) => {
39 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
44 pub enum BuiltinMetric { $($variant),* }
45
46 impl BuiltinMetric {
47 pub const fn wire_name(self) -> &'static str {
50 match self { $(Self::$variant => $wire,)* }
51 }
52
53 pub fn from_wire_name(name: &str) -> Option<Self> {
56 match name { $($wire => Some(Self::$variant),)* _ => None }
57 }
58
59 pub const ALL: &'static [BuiltinMetric] = &[$(Self::$variant),*];
62 }
63 };
64}
65
66builtin_metrics! {
67 WorstSpread => "worst_spread",
70 WorstGapMs => "worst_gap_ms",
71 TotalMigrations => "total_migrations",
72 WorstMigrationRatio => "worst_migration_ratio",
73 MaxImbalanceRatio => "max_imbalance_ratio",
74 AvgImbalanceRatio => "avg_imbalance_ratio",
75 MaxDsqDepth => "max_dsq_depth",
76 AvgDsqDepth => "avg_dsq_depth",
77 StuckCount => "stuck_count",
78 TotalFallback => "total_fallback",
79 TotalKeepLast => "total_keep_last",
80 TotalRunDelay => "total_run_delay",
81 TotalPcount => "total_pcount",
82 TotalSchedCount => "total_sched_count",
83 TotalYldCount => "total_yld_count",
84 TotalSchedGoidle => "total_sched_goidle",
85 TotalTtwuCount => "total_ttwu_count",
86 TotalTtwuLocal => "total_ttwu_local",
87 TotalRunDelayNsPerSched => "total_run_delay_ns_per_sched",
88 TtwuLocalFraction => "ttwu_local_fraction",
89 SchedGoidleFraction => "sched_goidle_fraction",
90 TotalSchedstatWallSec => "total_schedstat_wall_sec",
93 RunDelayPerSec => "run_delay_per_sec",
94 PcountPerSec => "pcount_per_sec",
95 SchedCountPerSec => "sched_count_per_sec",
96 YldCountPerSec => "yld_count_per_sec",
97 TtwuCountPerSec => "ttwu_count_per_sec",
98 SchedGoidlePerSec => "sched_goidle_per_sec",
99 AvgNrRunning => "avg_nr_running",
100 WorstP99WakeLatencyUs => "worst_p99_wake_latency_us",
101 WorstMedianWakeLatencyUs => "worst_median_wake_latency_us",
102 WorstWakeLatencyCv => "worst_wake_latency_cv",
103 WorstP99TimerLatencyUs => "worst_p99_timer_latency_us",
104 WorstMedianTimerLatencyUs => "worst_median_timer_latency_us",
105 WorstP999TimerLatencyUs => "worst_p999_timer_latency_us",
106 WorstTimerLatencyUs => "worst_timer_latency_us",
107 IterationRate => "iteration_rate",
108 TotalIterations => "total_iterations",
109 TotalPhaseIterations => "total_phase_iterations",
110 TotalPhaseDurationSec => "total_phase_duration_sec",
111 TotalCpuTimeSec => "total_cpu_time_sec",
112 TotalIterationsPooled => "total_iterations_pooled",
113 IterationsPerCpuSec => "iterations_per_cpu_sec",
114 TotalTaobenchOps => "total_taobench_ops",
119 TotalTaobenchFastOps => "total_taobench_fast_ops",
120 TotalTaobenchSlowOps => "total_taobench_slow_ops",
121 TotalTaobenchWallSec => "total_taobench_wall_sec",
122 TaobenchTotalOpsPerSec => "taobench_total_ops_per_sec",
123 TaobenchFastOpsPerSec => "taobench_fast_ops_per_sec",
124 TaobenchSlowOpsPerSec => "taobench_slow_ops_per_sec",
125 TaobenchHitFraction => "taobench_hit_fraction",
126 TaobenchServeP50UsWhole => "taobench_serve_p50_us_whole",
128 TaobenchServeP90UsWhole => "taobench_serve_p90_us_whole",
129 TaobenchServeP99UsWhole => "taobench_serve_p99_us_whole",
130 TaobenchServeP999UsWhole => "taobench_serve_p999_us_whole",
131 TaobenchServeMinUsWhole => "taobench_serve_min_us_whole",
132 TaobenchServeMaxUsWhole => "taobench_serve_max_us_whole",
133 TotalTaobenchGetCmds => "total_taobench_get_cmds",
136 TotalTaobenchGetHits => "total_taobench_get_hits",
137 TaobenchCommandHitRate => "taobench_command_hit_rate",
138 TotalSchbenchMsgRunDelayNs => "total_schbench_msg_run_delay_ns",
141 TotalSchbenchMsgPcount => "total_schbench_msg_pcount",
142 TotalSchbenchWorkerRunDelayNs => "total_schbench_worker_run_delay_ns",
143 TotalSchbenchWorkerPcount => "total_schbench_worker_pcount",
144 TotalSchbenchLoops => "total_schbench_loops",
145 SchbenchMsgRunDelayNsPerSched => "schbench_msg_run_delay_ns_per_sched",
146 SchbenchWorkerRunDelayNsPerSched => "schbench_worker_run_delay_ns_per_sched",
147 WakeupP50LatencyUsWhole => "wakeup_p50_latency_us_whole",
151 WakeupP90LatencyUsWhole => "wakeup_p90_latency_us_whole",
152 WakeupP99LatencyUsWhole => "wakeup_p99_latency_us_whole",
153 WakeupP999LatencyUsWhole => "wakeup_p999_latency_us_whole",
154 WakeupMinLatencyUsWhole => "wakeup_min_latency_us_whole",
155 WakeupMaxLatencyUsWhole => "wakeup_max_latency_us_whole",
156 RequestP50LatencyUsWhole => "request_p50_latency_us_whole",
157 RequestP90LatencyUsWhole => "request_p90_latency_us_whole",
158 RequestP99LatencyUsWhole => "request_p99_latency_us_whole",
159 RequestP999LatencyUsWhole => "request_p999_latency_us_whole",
160 RequestMinLatencyUsWhole => "request_min_latency_us_whole",
161 RequestMaxLatencyUsWhole => "request_max_latency_us_whole",
162 RpsP20Whole => "rps_p20_whole",
163 RpsP50Whole => "rps_p50_whole",
164 RpsP90Whole => "rps_p90_whole",
165 RpsMinWhole => "rps_min_whole",
166 RpsMaxWhole => "rps_max_whole",
167 SystemTimeNs => "system_time_ns",
168 UserTimeNs => "user_time_ns",
169 WorstMeanRunDelayUs => "worst_mean_run_delay_us",
170 WorstRunDelayUs => "worst_run_delay_us",
171 WorstWakeLatencyTailRatio => "worst_wake_latency_tail_ratio",
172 WorstIterationsPerWorker => "worst_iterations_per_worker",
173 WorstIterationsPerCpuSec => "worst_iterations_per_cpu_sec",
174 WorstPageLocality => "worst_page_locality",
175 WorstCrossNodeMigrationRatio => "worst_cross_node_migration_ratio",
176 TotalCpuTimeNs => "total_cpu_time_ns",
177
178 TotalHardirqs => "total_hardirqs",
188 TotalSoftirqNetRx => "total_softirq_net_rx",
189 TotalSoftirqNetTx => "total_softirq_net_tx",
190 TotalSoftirqTimer => "total_softirq_timer",
191 TotalSoftirqSched => "total_softirq_sched",
192 TotalIrqTimeNs => "total_irq_time_ns",
193 TotalSoftirqTimeNs => "total_softirq_time_ns",
194 TotalStealTimeNs => "total_steal_time_ns",
195 AvgIrqUtil => "avg_irq_util",
196 MaxAvgIrqUtil => "max_avg_irq_util",
197 HardirqRate => "hardirq_rate",
198 NetRxSoftirqRate => "net_rx_softirq_rate",
199 IrqTimeFraction => "irq_time_fraction",
200 TotalPhaseWallSec => "total_phase_wall_sec",
204 TotalPhaseWallNs => "total_phase_wall_ns",
205
206 MaxCpuHardirqs => "max_cpu_hardirqs",
212 MaxCpuHardirqConcentration => "max_cpu_hardirq_concentration",
213 MaxCpuSoftirqNetRx => "max_cpu_softirq_net_rx",
217 MaxCpuSoftirqNetRxConcentration => "max_cpu_softirq_net_rx_concentration",
218 AvgCpuUtilCompScale => "avg_cpu_util_comp_scale",
223
224 AvgTaskLatCri => "avg_task_lat_cri",
231 MaxTaskLatCri => "max_task_lat_cri",
232
233 MaxCgroupIrqPressure => "max_cgroup_irq_pressure",
240 MaxCgroupIrqPressureConcentration => "max_cgroup_irq_pressure_concentration",
241 MaxCgroupPsiIrqAvg10 => "max_cgroup_psi_irq_avg10",
242
243 PsiIrqFullAvg10 => "psi_irq_full_avg10",
249 TotalIrqPressureUs => "total_irq_pressure_us",
250
251 WakeupP50LatencyUs => "wakeup_p50_latency_us",
254 WakeupP90LatencyUs => "wakeup_p90_latency_us",
255 WakeupP99LatencyUs => "wakeup_p99_latency_us",
256 WakeupP999LatencyUs => "wakeup_p999_latency_us",
257 RequestP50LatencyUs => "request_p50_latency_us",
258 RequestP90LatencyUs => "request_p90_latency_us",
259 RequestP99LatencyUs => "request_p99_latency_us",
260 RequestP999LatencyUs => "request_p999_latency_us",
261 SchedDelayMsgUs => "sched_delay_msg_us",
262 SchedDelayWorkerUs => "sched_delay_worker_us",
263 SchbenchLoopCount => "schbench_loop_count",
264 TaobenchTotalQps => "taobench_total_qps",
265 TaobenchFastQps => "taobench_fast_qps",
266 TaobenchSlowQps => "taobench_slow_qps",
267 TaobenchHitRatio => "taobench_hit_ratio",
268 TaobenchHitRate => "taobench_hit_rate",
269 TaobenchServeP50Us => "taobench_serve_p50_us",
271 TaobenchServeP90Us => "taobench_serve_p90_us",
272 TaobenchServeP99Us => "taobench_serve_p99_us",
273 TaobenchServeP999Us => "taobench_serve_p999_us",
274 TaobenchServeMinUs => "taobench_serve_min_us",
275 TaobenchServeMaxUs => "taobench_serve_max_us",
276 WakeupMinLatencyUs => "wakeup_min_latency_us",
277 WakeupMaxLatencyUs => "wakeup_max_latency_us",
278 RequestMinLatencyUs => "request_min_latency_us",
279 RequestMaxLatencyUs => "request_max_latency_us",
280 RpsP20 => "rps_p20",
281 RpsP50 => "rps_p50",
282 RpsP90 => "rps_p90",
283 RpsMin => "rps_min",
284 RpsMax => "rps_max",
285
286 P99WakeLatencyUs => "p99_wake_latency_us",
290 MedianWakeLatencyUs => "median_wake_latency_us",
291 WakeLatencyCv => "wake_latency_cv",
292 P99TimerLatencyUs => "p99_timer_latency_us",
293 MedianTimerLatencyUs => "median_timer_latency_us",
294 P999TimerLatencyUs => "p999_timer_latency_us",
295 MeanRunDelayUs => "mean_run_delay_us",
296 MaxRunDelayUs => "max_run_delay_us",
297 AvgOffCpuPct => "avg_off_cpu_pct",
298 MinOffCpuPct => "min_off_cpu_pct",
299 MaxOffCpuPct => "max_off_cpu_pct",
300 OffCpuSpreadPct => "off_cpu_spread_pct",
301 MigrationRatio => "migration_ratio",
302 IterationsPerWorker => "iterations_per_worker",
303 PageLocality => "page_locality",
304 CrossNodeMigrationRatio => "cross_node_migration_ratio",
305}
306
307impl BuiltinMetric {
308 pub fn def(self) -> &'static MetricDef {
312 metric_def(self.wire_name())
313 .expect("BuiltinMetric variant must resolve in METRICS (pinned by test)")
314 }
315}
316
317#[derive(Debug, Clone, PartialEq, Eq, Hash)]
322pub enum MetricId {
323 Builtin(BuiltinMetric),
326 Dynamic(Cow<'static, str>),
328}
329
330impl MetricId {
331 pub fn as_str(&self) -> &str {
335 match self {
336 MetricId::Builtin(b) => b.wire_name(),
337 MetricId::Dynamic(s) => s.as_ref(),
338 }
339 }
340
341 pub fn def(&self) -> Option<&'static MetricDef> {
350 match self {
351 MetricId::Builtin(b) => Some(b.def()),
352 MetricId::Dynamic(s) => metric_def(s.as_ref()),
353 }
354 }
355}
356
357impl From<BuiltinMetric> for MetricId {
358 fn from(b: BuiltinMetric) -> Self {
359 MetricId::Builtin(b)
360 }
361}
362
363impl From<&BuiltinMetric> for MetricId {
364 fn from(b: &BuiltinMetric) -> Self {
367 MetricId::Builtin(*b)
368 }
369}
370
371impl From<&str> for MetricId {
372 fn from(s: &str) -> Self {
377 match BuiltinMetric::from_wire_name(s) {
378 Some(b) => MetricId::Builtin(b),
379 None => MetricId::Dynamic(Cow::Owned(s.to_owned())),
380 }
381 }
382}
383
384impl From<String> for MetricId {
385 fn from(s: String) -> Self {
386 match BuiltinMetric::from_wire_name(&s) {
387 Some(b) => MetricId::Builtin(b),
388 None => MetricId::Dynamic(Cow::Owned(s)),
389 }
390 }
391}
392
393impl From<&String> for MetricId {
394 fn from(s: &String) -> Self {
398 Self::from(s.as_str())
399 }
400}
401
402#[cfg(test)]
403mod tests {
404 use super::*;
405 use crate::stats::METRICS;
406
407 #[test]
410 fn builtin_metric_is_one_to_one_with_registry() {
411 for b in BuiltinMetric::ALL {
413 assert!(
414 metric_def(b.wire_name()).is_some(),
415 "BuiltinMetric::{b:?} (wire {:?}) is not in METRICS",
416 b.wire_name()
417 );
418 }
419 for m in METRICS {
421 assert!(
422 BuiltinMetric::from_wire_name(m.name).is_some(),
423 "METRICS entry {:?} has no BuiltinMetric variant",
424 m.name
425 );
426 }
427 assert_eq!(
430 BuiltinMetric::ALL.len(),
431 METRICS.len(),
432 "BuiltinMetric::ALL ({}) != METRICS ({})",
433 BuiltinMetric::ALL.len(),
434 METRICS.len()
435 );
436 }
437
438 #[test]
440 fn wire_name_roundtrips() {
441 for b in BuiltinMetric::ALL {
442 assert_eq!(BuiltinMetric::from_wire_name(b.wire_name()), Some(*b));
443 }
444 }
445
446 #[test]
450 fn metric_id_canonicalizes_builtin_and_quarantines_dynamic() {
451 let typed: MetricId = BuiltinMetric::TaobenchTotalQps.into();
452 let from_str: MetricId = "taobench_total_qps".into();
453 assert_eq!(
454 typed, from_str,
455 "a built-in string canonicalizes to Builtin"
456 );
457 assert_eq!(typed.as_str(), "taobench_total_qps");
458 assert!(typed.def().is_some(), "a built-in carries its registry def");
459
460 let dynamic: MetricId = "scx_layered_layer0_depth".into();
461 assert!(
462 matches!(dynamic, MetricId::Dynamic(_)),
463 "a non-registered key stays Dynamic"
464 );
465 assert!(
466 dynamic.def().is_none(),
467 "a Dynamic key has no def() -> caller must not guess its kind"
468 );
469 }
470}