use std::borrow::Cow;
use super::{MetricDef, metric_def};
macro_rules! builtin_metrics {
($($variant:ident => $wire:literal),* $(,)?) => {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum BuiltinMetric { $($variant),* }
impl BuiltinMetric {
pub const fn wire_name(self) -> &'static str {
match self { $(Self::$variant => $wire,)* }
}
pub fn from_wire_name(name: &str) -> Option<Self> {
match name { $($wire => Some(Self::$variant),)* _ => None }
}
pub const ALL: &'static [BuiltinMetric] = &[$(Self::$variant),*];
}
};
}
builtin_metrics! {
WorstSpread => "worst_spread",
WorstGapMs => "worst_gap_ms",
TotalMigrations => "total_migrations",
WorstMigrationRatio => "worst_migration_ratio",
MaxImbalanceRatio => "max_imbalance_ratio",
AvgImbalanceRatio => "avg_imbalance_ratio",
MaxDsqDepth => "max_dsq_depth",
AvgDsqDepth => "avg_dsq_depth",
StuckCount => "stuck_count",
TotalFallback => "total_fallback",
TotalKeepLast => "total_keep_last",
TotalRunDelay => "total_run_delay",
TotalPcount => "total_pcount",
TotalSchedCount => "total_sched_count",
TotalYldCount => "total_yld_count",
TotalSchedGoidle => "total_sched_goidle",
TotalTtwuCount => "total_ttwu_count",
TotalTtwuLocal => "total_ttwu_local",
TotalRunDelayNsPerSched => "total_run_delay_ns_per_sched",
TtwuLocalFraction => "ttwu_local_fraction",
SchedGoidleFraction => "sched_goidle_fraction",
TotalSchedstatWallSec => "total_schedstat_wall_sec",
RunDelayPerSec => "run_delay_per_sec",
PcountPerSec => "pcount_per_sec",
SchedCountPerSec => "sched_count_per_sec",
YldCountPerSec => "yld_count_per_sec",
TtwuCountPerSec => "ttwu_count_per_sec",
SchedGoidlePerSec => "sched_goidle_per_sec",
AvgNrRunning => "avg_nr_running",
WorstP99WakeLatencyUs => "worst_p99_wake_latency_us",
WorstMedianWakeLatencyUs => "worst_median_wake_latency_us",
WorstWakeLatencyCv => "worst_wake_latency_cv",
WorstP99TimerLatencyUs => "worst_p99_timer_latency_us",
WorstMedianTimerLatencyUs => "worst_median_timer_latency_us",
WorstP999TimerLatencyUs => "worst_p999_timer_latency_us",
WorstTimerLatencyUs => "worst_timer_latency_us",
IterationRate => "iteration_rate",
TotalIterations => "total_iterations",
TotalPhaseIterations => "total_phase_iterations",
TotalPhaseDurationSec => "total_phase_duration_sec",
TotalCpuTimeSec => "total_cpu_time_sec",
TotalIterationsPooled => "total_iterations_pooled",
IterationsPerCpuSec => "iterations_per_cpu_sec",
TotalTaobenchOps => "total_taobench_ops",
TotalTaobenchFastOps => "total_taobench_fast_ops",
TotalTaobenchSlowOps => "total_taobench_slow_ops",
TotalTaobenchWallSec => "total_taobench_wall_sec",
TaobenchTotalOpsPerSec => "taobench_total_ops_per_sec",
TaobenchFastOpsPerSec => "taobench_fast_ops_per_sec",
TaobenchSlowOpsPerSec => "taobench_slow_ops_per_sec",
TaobenchHitFraction => "taobench_hit_fraction",
TaobenchServeP50UsWhole => "taobench_serve_p50_us_whole",
TaobenchServeP90UsWhole => "taobench_serve_p90_us_whole",
TaobenchServeP99UsWhole => "taobench_serve_p99_us_whole",
TaobenchServeP999UsWhole => "taobench_serve_p999_us_whole",
TaobenchServeMinUsWhole => "taobench_serve_min_us_whole",
TaobenchServeMaxUsWhole => "taobench_serve_max_us_whole",
TotalTaobenchGetCmds => "total_taobench_get_cmds",
TotalTaobenchGetHits => "total_taobench_get_hits",
TaobenchCommandHitRate => "taobench_command_hit_rate",
TotalSchbenchMsgRunDelayNs => "total_schbench_msg_run_delay_ns",
TotalSchbenchMsgPcount => "total_schbench_msg_pcount",
TotalSchbenchWorkerRunDelayNs => "total_schbench_worker_run_delay_ns",
TotalSchbenchWorkerPcount => "total_schbench_worker_pcount",
TotalSchbenchLoops => "total_schbench_loops",
SchbenchMsgRunDelayNsPerSched => "schbench_msg_run_delay_ns_per_sched",
SchbenchWorkerRunDelayNsPerSched => "schbench_worker_run_delay_ns_per_sched",
WakeupP50LatencyUsWhole => "wakeup_p50_latency_us_whole",
WakeupP90LatencyUsWhole => "wakeup_p90_latency_us_whole",
WakeupP99LatencyUsWhole => "wakeup_p99_latency_us_whole",
WakeupP999LatencyUsWhole => "wakeup_p999_latency_us_whole",
WakeupMinLatencyUsWhole => "wakeup_min_latency_us_whole",
WakeupMaxLatencyUsWhole => "wakeup_max_latency_us_whole",
RequestP50LatencyUsWhole => "request_p50_latency_us_whole",
RequestP90LatencyUsWhole => "request_p90_latency_us_whole",
RequestP99LatencyUsWhole => "request_p99_latency_us_whole",
RequestP999LatencyUsWhole => "request_p999_latency_us_whole",
RequestMinLatencyUsWhole => "request_min_latency_us_whole",
RequestMaxLatencyUsWhole => "request_max_latency_us_whole",
RpsP20Whole => "rps_p20_whole",
RpsP50Whole => "rps_p50_whole",
RpsP90Whole => "rps_p90_whole",
RpsMinWhole => "rps_min_whole",
RpsMaxWhole => "rps_max_whole",
SystemTimeNs => "system_time_ns",
UserTimeNs => "user_time_ns",
WorstMeanRunDelayUs => "worst_mean_run_delay_us",
WorstRunDelayUs => "worst_run_delay_us",
WorstWakeLatencyTailRatio => "worst_wake_latency_tail_ratio",
WorstIterationsPerWorker => "worst_iterations_per_worker",
WorstIterationsPerCpuSec => "worst_iterations_per_cpu_sec",
WorstPageLocality => "worst_page_locality",
WorstCrossNodeMigrationRatio => "worst_cross_node_migration_ratio",
TotalCpuTimeNs => "total_cpu_time_ns",
TotalHardirqs => "total_hardirqs",
TotalSoftirqNetRx => "total_softirq_net_rx",
TotalSoftirqNetTx => "total_softirq_net_tx",
TotalSoftirqTimer => "total_softirq_timer",
TotalSoftirqSched => "total_softirq_sched",
TotalIrqTimeNs => "total_irq_time_ns",
TotalSoftirqTimeNs => "total_softirq_time_ns",
TotalStealTimeNs => "total_steal_time_ns",
AvgIrqUtil => "avg_irq_util",
MaxAvgIrqUtil => "max_avg_irq_util",
HardirqRate => "hardirq_rate",
NetRxSoftirqRate => "net_rx_softirq_rate",
IrqTimeFraction => "irq_time_fraction",
TotalPhaseWallSec => "total_phase_wall_sec",
TotalPhaseWallNs => "total_phase_wall_ns",
MaxCpuHardirqs => "max_cpu_hardirqs",
MaxCpuHardirqConcentration => "max_cpu_hardirq_concentration",
MaxCpuSoftirqNetRx => "max_cpu_softirq_net_rx",
MaxCpuSoftirqNetRxConcentration => "max_cpu_softirq_net_rx_concentration",
AvgCpuUtilCompScale => "avg_cpu_util_comp_scale",
AvgTaskLatCri => "avg_task_lat_cri",
MaxTaskLatCri => "max_task_lat_cri",
MaxCgroupIrqPressure => "max_cgroup_irq_pressure",
MaxCgroupIrqPressureConcentration => "max_cgroup_irq_pressure_concentration",
MaxCgroupPsiIrqAvg10 => "max_cgroup_psi_irq_avg10",
PsiIrqFullAvg10 => "psi_irq_full_avg10",
TotalIrqPressureUs => "total_irq_pressure_us",
WakeupP50LatencyUs => "wakeup_p50_latency_us",
WakeupP90LatencyUs => "wakeup_p90_latency_us",
WakeupP99LatencyUs => "wakeup_p99_latency_us",
WakeupP999LatencyUs => "wakeup_p999_latency_us",
RequestP50LatencyUs => "request_p50_latency_us",
RequestP90LatencyUs => "request_p90_latency_us",
RequestP99LatencyUs => "request_p99_latency_us",
RequestP999LatencyUs => "request_p999_latency_us",
SchedDelayMsgUs => "sched_delay_msg_us",
SchedDelayWorkerUs => "sched_delay_worker_us",
SchbenchLoopCount => "schbench_loop_count",
TaobenchTotalQps => "taobench_total_qps",
TaobenchFastQps => "taobench_fast_qps",
TaobenchSlowQps => "taobench_slow_qps",
TaobenchHitRatio => "taobench_hit_ratio",
TaobenchHitRate => "taobench_hit_rate",
TaobenchServeP50Us => "taobench_serve_p50_us",
TaobenchServeP90Us => "taobench_serve_p90_us",
TaobenchServeP99Us => "taobench_serve_p99_us",
TaobenchServeP999Us => "taobench_serve_p999_us",
TaobenchServeMinUs => "taobench_serve_min_us",
TaobenchServeMaxUs => "taobench_serve_max_us",
WakeupMinLatencyUs => "wakeup_min_latency_us",
WakeupMaxLatencyUs => "wakeup_max_latency_us",
RequestMinLatencyUs => "request_min_latency_us",
RequestMaxLatencyUs => "request_max_latency_us",
RpsP20 => "rps_p20",
RpsP50 => "rps_p50",
RpsP90 => "rps_p90",
RpsMin => "rps_min",
RpsMax => "rps_max",
P99WakeLatencyUs => "p99_wake_latency_us",
MedianWakeLatencyUs => "median_wake_latency_us",
WakeLatencyCv => "wake_latency_cv",
P99TimerLatencyUs => "p99_timer_latency_us",
MedianTimerLatencyUs => "median_timer_latency_us",
P999TimerLatencyUs => "p999_timer_latency_us",
MeanRunDelayUs => "mean_run_delay_us",
MaxRunDelayUs => "max_run_delay_us",
AvgOffCpuPct => "avg_off_cpu_pct",
MinOffCpuPct => "min_off_cpu_pct",
MaxOffCpuPct => "max_off_cpu_pct",
OffCpuSpreadPct => "off_cpu_spread_pct",
MigrationRatio => "migration_ratio",
IterationsPerWorker => "iterations_per_worker",
PageLocality => "page_locality",
CrossNodeMigrationRatio => "cross_node_migration_ratio",
}
impl BuiltinMetric {
pub fn def(self) -> &'static MetricDef {
metric_def(self.wire_name())
.expect("BuiltinMetric variant must resolve in METRICS (pinned by test)")
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum MetricId {
Builtin(BuiltinMetric),
Dynamic(Cow<'static, str>),
}
impl MetricId {
pub fn as_str(&self) -> &str {
match self {
MetricId::Builtin(b) => b.wire_name(),
MetricId::Dynamic(s) => s.as_ref(),
}
}
pub fn def(&self) -> Option<&'static MetricDef> {
match self {
MetricId::Builtin(b) => Some(b.def()),
MetricId::Dynamic(s) => metric_def(s.as_ref()),
}
}
}
impl From<BuiltinMetric> for MetricId {
fn from(b: BuiltinMetric) -> Self {
MetricId::Builtin(b)
}
}
impl From<&BuiltinMetric> for MetricId {
fn from(b: &BuiltinMetric) -> Self {
MetricId::Builtin(*b)
}
}
impl From<&str> for MetricId {
fn from(s: &str) -> Self {
match BuiltinMetric::from_wire_name(s) {
Some(b) => MetricId::Builtin(b),
None => MetricId::Dynamic(Cow::Owned(s.to_owned())),
}
}
}
impl From<String> for MetricId {
fn from(s: String) -> Self {
match BuiltinMetric::from_wire_name(&s) {
Some(b) => MetricId::Builtin(b),
None => MetricId::Dynamic(Cow::Owned(s)),
}
}
}
impl From<&String> for MetricId {
fn from(s: &String) -> Self {
Self::from(s.as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::stats::METRICS;
#[test]
fn builtin_metric_is_one_to_one_with_registry() {
for b in BuiltinMetric::ALL {
assert!(
metric_def(b.wire_name()).is_some(),
"BuiltinMetric::{b:?} (wire {:?}) is not in METRICS",
b.wire_name()
);
}
for m in METRICS {
assert!(
BuiltinMetric::from_wire_name(m.name).is_some(),
"METRICS entry {:?} has no BuiltinMetric variant",
m.name
);
}
assert_eq!(
BuiltinMetric::ALL.len(),
METRICS.len(),
"BuiltinMetric::ALL ({}) != METRICS ({})",
BuiltinMetric::ALL.len(),
METRICS.len()
);
}
#[test]
fn wire_name_roundtrips() {
for b in BuiltinMetric::ALL {
assert_eq!(BuiltinMetric::from_wire_name(b.wire_name()), Some(*b));
}
}
#[test]
fn metric_id_canonicalizes_builtin_and_quarantines_dynamic() {
let typed: MetricId = BuiltinMetric::TaobenchTotalQps.into();
let from_str: MetricId = "taobench_total_qps".into();
assert_eq!(
typed, from_str,
"a built-in string canonicalizes to Builtin"
);
assert_eq!(typed.as_str(), "taobench_total_qps");
assert!(typed.def().is_some(), "a built-in carries its registry def");
let dynamic: MetricId = "scx_layered_layer0_depth".into();
assert!(
matches!(dynamic, MetricId::Dynamic(_)),
"a non-registered key stays Dynamic"
);
assert!(
dynamic.def().is_none(),
"a Dynamic key has no def() -> caller must not guess its kind"
);
}
}