use super::unified::{
CacheOpResult, CacheOpType, CacheOperation, GLOBAL_UNIFIED_METRICS, UnifiedMetrics,
};
use crate::core::CacheLayer;
use std::sync::Arc;
use std::time::Duration;
pub trait MetricsRecorder: Send + Sync + 'static {
fn record_hit(&self, _layer: CacheLayer, _latency: Duration) {}
fn record_miss(&self, _layer: CacheLayer, _latency: Duration) {}
fn record_set(&self, _layer: CacheLayer, _latency: Duration) {}
fn record_delete(&self, _layer: CacheLayer, _latency: Duration) {}
fn record_eviction(&self, _count: u64) {}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct NoOpMetricsRecorder;
impl MetricsRecorder for NoOpMetricsRecorder {}
#[derive(Clone, Debug)]
pub struct UnifiedMetricsRecorder {
metrics: UnifiedMetrics,
service: Option<Arc<str>>,
}
impl UnifiedMetricsRecorder {
pub fn new() -> Self {
Self {
metrics: UnifiedMetrics::new(),
service: None,
}
}
pub fn global() -> Self {
Self {
metrics: GLOBAL_UNIFIED_METRICS.clone(),
service: None,
}
}
pub fn global_tagged(service: impl Into<Arc<str>>) -> Self {
Self {
metrics: GLOBAL_UNIFIED_METRICS.clone(),
service: Some(service.into()),
}
}
pub fn metrics(&self) -> &UnifiedMetrics {
&self.metrics
}
}
impl Default for UnifiedMetricsRecorder {
fn default() -> Self {
Self::new()
}
}
fn record_with_latency(
metrics: &UnifiedMetrics,
op: CacheOperation,
latency: Duration,
service: Option<&str>,
) {
match service {
Some(service) => metrics.record_operation_with_service(&op, service),
None => metrics.record_operation(op.clone()),
}
metrics.record_duration(&op, latency);
metrics.record_latency_seconds(latency.as_secs_f64());
}
impl MetricsRecorder for UnifiedMetricsRecorder {
fn record_hit(&self, layer: CacheLayer, latency: Duration) {
record_with_latency(
&self.metrics,
CacheOperation {
layer,
op_type: CacheOpType::Get,
result: CacheOpResult::Hit,
},
latency,
self.service.as_deref(),
);
}
fn record_miss(&self, layer: CacheLayer, latency: Duration) {
record_with_latency(
&self.metrics,
CacheOperation {
layer,
op_type: CacheOpType::Get,
result: CacheOpResult::Miss,
},
latency,
self.service.as_deref(),
);
}
fn record_set(&self, layer: CacheLayer, latency: Duration) {
record_with_latency(
&self.metrics,
CacheOperation {
layer,
op_type: CacheOpType::Set,
result: CacheOpResult::Success,
},
latency,
self.service.as_deref(),
);
}
fn record_delete(&self, layer: CacheLayer, latency: Duration) {
record_with_latency(
&self.metrics,
CacheOperation {
layer,
op_type: CacheOpType::Delete,
result: CacheOpResult::Success,
},
latency,
self.service.as_deref(),
);
}
fn record_eviction(&self, count: u64) {
self.metrics.record_eviction(count);
}
}
pub fn noop_recorder() -> Arc<dyn MetricsRecorder> {
Arc::new(NoOpMetricsRecorder)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn noop_recorder_is_zero_cost() {
let recorder = NoOpMetricsRecorder;
recorder.record_hit(CacheLayer::L1, Duration::from_micros(10));
recorder.record_miss(CacheLayer::L1, Duration::from_micros(10));
recorder.record_set(CacheLayer::L1, Duration::from_micros(10));
recorder.record_delete(CacheLayer::L1, Duration::from_micros(10));
recorder.record_eviction(3);
}
#[test]
fn unified_recorder_counts_hits_misses_and_latency_samples() {
let recorder = UnifiedMetricsRecorder::new();
let counters = recorder.metrics().get_counters();
assert_eq!(counters.l1_hits, 0);
assert_eq!(counters.l1_misses, 0);
recorder.record_hit(CacheLayer::L1, Duration::from_micros(120));
recorder.record_hit(CacheLayer::L1, Duration::from_micros(80));
recorder.record_miss(CacheLayer::L1, Duration::from_micros(200));
let counters = recorder.metrics().get_counters();
assert_eq!(counters.l1_hits, 2, "命中计数应被记录");
assert_eq!(counters.l1_misses, 1, "未命中计数应被记录");
assert_eq!(counters.l1_sets, 0);
let dynamic = recorder.metrics().get_dynamic_metrics();
assert!(
dynamic.contains_key(super::super::unified::OPERATION_LATENCY_HISTOGRAM),
"延迟直方图样本应被记录"
);
}
#[test]
fn unified_recorder_counts_sets_deletes_and_evictions() {
let recorder = UnifiedMetricsRecorder::new();
recorder.record_set(CacheLayer::L1, Duration::from_micros(50));
recorder.record_delete(CacheLayer::L1, Duration::from_micros(30));
recorder.record_eviction(4);
let counters = recorder.metrics().get_counters();
assert_eq!(counters.l1_sets, 1);
assert_eq!(counters.l1_deletes, 1);
assert_eq!(counters.evictions, 4, "淘汰计数应被记录");
}
#[test]
fn recorder_is_object_safe_and_injectable() {
let recorder: Arc<dyn MetricsRecorder> = Arc::new(UnifiedMetricsRecorder::new());
recorder.record_hit(CacheLayer::L1, Duration::from_micros(5));
let noop: Arc<dyn MetricsRecorder> = noop_recorder();
noop.record_eviction(1);
}
}