use std::fmt;
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone)]
pub struct MetricEvent {
pub name: &'static str,
pub value: MetricValue,
pub labels: Vec<(&'static str, String)>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum MetricValue {
Counter(u64),
Gauge(f64),
Histogram(f64),
}
impl fmt::Display for MetricValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MetricValue::Counter(v) => write!(f, "counter({v})"),
MetricValue::Gauge(v) => write!(f, "gauge({v})"),
MetricValue::Histogram(v) => write!(f, "histogram({v})"),
}
}
}
pub trait MetricsSink: Send + Sync + fmt::Debug {
fn record(&self, event: MetricEvent);
}
#[derive(Debug, Clone, Copy, Default)]
pub struct NoopSink;
impl MetricsSink for NoopSink {
#[inline]
fn record(&self, _event: MetricEvent) {
}
}
#[derive(Debug, Default)]
pub struct RecordingSink {
events: Mutex<Vec<MetricEvent>>,
}
impl RecordingSink {
pub fn new() -> Self {
Self {
events: Mutex::new(Vec::new()),
}
}
pub fn events(&self) -> Vec<MetricEvent> {
self.events
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.clone()
}
pub fn clear(&self) {
if let Ok(mut guard) = self.events.lock() {
guard.clear();
}
}
pub fn len(&self) -> usize {
self.events
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl MetricsSink for RecordingSink {
fn record(&self, event: MetricEvent) {
if let Ok(mut guard) = self.events.lock() {
guard.push(event);
}
}
}
pub mod names {
pub const HNSW_SEARCH_DURATION_MS: &str = "hnsw.search.duration_ms";
pub const HNSW_SEARCH_COUNT: &str = "hnsw.search.count";
pub const HNSW_SEARCH_RESULTS: &str = "hnsw.search.results";
pub const HNSW_INSERT_DURATION_MS: &str = "hnsw.insert.duration_ms";
pub const HNSW_INSERT_COUNT: &str = "hnsw.insert.count";
pub const HNSW_REBUILD_DURATION_MS: &str = "hnsw.rebuild.duration_ms";
pub const HNSW_REBUILD_COUNT: &str = "hnsw.rebuild.count";
pub const HNSW_REBUILD_NODES_REMOVED: &str = "hnsw.rebuild.nodes_removed";
pub const HNSW_INDEX_SIZE: &str = "hnsw.index.size";
pub const BM25_SEARCH_DURATION_MS: &str = "bm25.search.duration_ms";
pub const BM25_SEARCH_COUNT: &str = "bm25.search.count";
pub const BM25_SEARCH_RESULTS: &str = "bm25.search.results";
pub const BM25_INDEX_DURATION_MS: &str = "bm25.index_document.duration_ms";
pub const BM25_INDEX_COUNT: &str = "bm25.index_document.count";
pub const BM25_INDEX_SIZE: &str = "bm25.index.size";
}
#[inline]
pub fn emit(sink: &Option<Arc<dyn MetricsSink>>, event: MetricEvent) {
if let Some(s) = sink {
s.record(event);
}
}
#[cfg(test)]
#[allow(clippy::approx_constant)]
#[path = "metrics/tests.rs"]
mod tests;