use std::sync::atomic::{AtomicU64, Ordering};
pub const TIER_LOW: usize = 0;
pub const TIER_HIGH: usize = 1;
pub const TIER_CRITICAL: usize = 2;
pub const TIER_LABELS: [&str; 3] = ["low", "high", "critical"];
const IO_WAIT_BUCKETS_NS: [u64; 13] = [
1_000, 5_000, 10_000, 50_000, 100_000, 500_000, 1_000_000, 5_000_000, 10_000_000, 50_000_000, 100_000_000, 500_000_000, 1_000_000_000, ];
const BUCKET_COUNT: usize = IO_WAIT_BUCKETS_NS.len();
#[derive(Debug)]
pub struct IoMetrics {
pub queue_depth: [AtomicU64; 3],
buckets: [[AtomicU64; BUCKET_COUNT]; 3],
count: [AtomicU64; 3],
sum_ns: [AtomicU64; 3],
}
impl IoMetrics {
pub fn new() -> Self {
Self {
queue_depth: std::array::from_fn(|_| AtomicU64::new(0)),
buckets: std::array::from_fn(|_| std::array::from_fn(|_| AtomicU64::new(0))),
count: std::array::from_fn(|_| AtomicU64::new(0)),
sum_ns: std::array::from_fn(|_| AtomicU64::new(0)),
}
}
#[inline]
pub fn record_queue_depth(&self, tier: usize, depth: u64) {
self.queue_depth[tier].store(depth, Ordering::Relaxed);
}
#[inline]
pub fn record_wait(&self, tier: usize, wait_ns: u64) {
self.count[tier].fetch_add(1, Ordering::Relaxed);
self.sum_ns[tier].fetch_add(wait_ns, Ordering::Relaxed);
for (i, &boundary) in IO_WAIT_BUCKETS_NS.iter().enumerate() {
if wait_ns <= boundary {
self.buckets[tier][i].fetch_add(1, Ordering::Relaxed);
return;
}
}
}
pub fn write_prometheus(&self, out: &mut String) {
use std::fmt::Write as _;
let _ = out.write_str(
"# HELP nodedb_io_queue_depth Pending IO task count per priority tier\n\
# TYPE nodedb_io_queue_depth gauge\n",
);
for (tier, label) in TIER_LABELS.iter().enumerate() {
let depth = self.queue_depth[tier].load(Ordering::Relaxed);
let _ = writeln!(
out,
r#"nodedb_io_queue_depth{{priority="{label}"}} {depth}"#
);
}
let _ = out.write_str(
"# HELP nodedb_io_wait_ns IO submission-to-completion latency per priority tier\n\
# TYPE nodedb_io_wait_ns histogram\n",
);
for (tier, label) in TIER_LABELS.iter().enumerate() {
let mut cumulative = 0u64;
for (i, &boundary_ns) in IO_WAIT_BUCKETS_NS.iter().enumerate() {
cumulative += self.buckets[tier][i].load(Ordering::Relaxed);
let le_s = boundary_ns as f64 / 1_000_000_000.0;
let _ = writeln!(
out,
r#"nodedb_io_wait_ns_bucket{{priority="{label}",le="{le_s:.9}"}} {cumulative}"#
);
}
let total = self.count[tier].load(Ordering::Relaxed);
let _ = writeln!(
out,
r#"nodedb_io_wait_ns_bucket{{priority="{label}",le="+Inf"}} {total}"#
);
let sum_s = self.sum_ns[tier].load(Ordering::Relaxed) as f64 / 1_000_000_000.0;
let _ = writeln!(
out,
r#"nodedb_io_wait_ns_sum{{priority="{label}"}} {sum_s:.9}"#
);
let _ = writeln!(
out,
r#"nodedb_io_wait_ns_count{{priority="{label}"}} {total}"#
);
}
}
}
impl Default for IoMetrics {
fn default() -> Self {
Self::new()
}
}