use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
pub const DURATION_BUCKETS: [f64; 8] = [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1.0, 5.0];
pub const DEPTH_BUCKETS: [u64; 6] = [1, 2, 3, 5, 10, 20];
pub const NODES_BUCKETS: [u64; 7] = [10, 50, 100, 500, 1000, 5000, 10000];
#[derive(Debug, Default)]
pub struct OperationalMetrics {
pub queries_total: AtomicU64,
pub query_errors: AtomicU64,
pub query_rate_limited: AtomicU64,
pub vector_queries: AtomicU64,
pub graph_queries: AtomicU64,
pub hybrid_queries: AtomicU64,
pub documents_total: AtomicU64,
pub index_size_bytes: AtomicU64,
pub active_connections: AtomicU64,
}
impl OperationalMetrics {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn new_arc() -> Arc<Self> {
Arc::new(Self::new())
}
#[must_use]
#[deprecated(since = "1.13.2", note = "use `OperationalMetrics::new_arc` instead")]
pub fn shared() -> Arc<Self> {
Self::new_arc()
}
pub fn inc_queries(&self) {
self.queries_total.fetch_add(1, Ordering::Relaxed);
}
pub fn inc_errors(&self) {
self.query_errors.fetch_add(1, Ordering::Relaxed);
}
pub fn inc_rate_limited(&self) {
self.query_rate_limited.fetch_add(1, Ordering::Relaxed);
}
pub fn record_vector_query(&self) {
self.inc_queries();
self.vector_queries.fetch_add(1, Ordering::Relaxed);
}
pub fn record_graph_query(&self) {
self.inc_queries();
self.graph_queries.fetch_add(1, Ordering::Relaxed);
}
pub fn record_hybrid_query(&self) {
self.inc_queries();
self.hybrid_queries.fetch_add(1, Ordering::Relaxed);
}
pub fn set_documents(&self, count: u64) {
self.documents_total.store(count, Ordering::Relaxed);
}
pub fn set_index_size(&self, bytes: u64) {
self.index_size_bytes.store(bytes, Ordering::Relaxed);
}
pub fn inc_connections(&self) {
self.active_connections.fetch_add(1, Ordering::Relaxed);
}
pub fn dec_connections(&self) {
self.active_connections
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |x| {
Some(x.saturating_sub(1))
})
.ok();
}
#[must_use]
pub fn export_prometheus(&self) -> String {
use std::fmt::Write;
let mut output = String::new();
let total = self.queries_total.load(Ordering::Relaxed);
let errors = self.query_errors.load(Ordering::Relaxed);
let rate_limited = self.query_rate_limited.load(Ordering::Relaxed);
let success = total.saturating_sub(errors).saturating_sub(rate_limited);
Self::write_metric_header(
&mut output,
"velesdb_queries_total",
"counter",
"Total number of queries executed",
);
let _ = writeln!(
output,
"velesdb_queries_total{{status=\"success\"}} {success}"
);
let _ = writeln!(output, "velesdb_queries_total{{status=\"error\"}} {errors}");
let _ = writeln!(
output,
"velesdb_queries_total{{status=\"rate_limited\"}} {rate_limited}\n"
);
Self::write_metric_header(
&mut output,
"velesdb_queries_by_type",
"counter",
"Queries by type",
);
let _ = writeln!(
output,
"velesdb_queries_by_type{{type=\"vector\"}} {}",
self.vector_queries.load(Ordering::Relaxed)
);
let _ = writeln!(
output,
"velesdb_queries_by_type{{type=\"graph\"}} {}",
self.graph_queries.load(Ordering::Relaxed)
);
let _ = writeln!(
output,
"velesdb_queries_by_type{{type=\"hybrid\"}} {}\n",
self.hybrid_queries.load(Ordering::Relaxed)
);
Self::write_gauge(
&mut output,
"velesdb_documents_total",
"Total documents in database",
self.documents_total.load(Ordering::Relaxed),
);
Self::write_gauge(
&mut output,
"velesdb_index_size_bytes",
"Total index size in bytes",
self.index_size_bytes.load(Ordering::Relaxed),
);
Self::write_gauge(
&mut output,
"velesdb_active_connections",
"Current active connections",
self.active_connections.load(Ordering::Relaxed),
);
output
}
fn write_metric_header(output: &mut String, name: &str, metric_type: &str, help: &str) {
use std::fmt::Write;
let _ = write!(
output,
"# HELP {name} {help}\n# TYPE {name} {metric_type}\n"
);
}
fn write_gauge(output: &mut String, name: &str, help: &str, value: u64) {
use std::fmt::Write;
Self::write_metric_header(output, name, "gauge", help);
let _ = writeln!(output, "{name} {value}\n");
}
}
#[cfg(test)]
#[path = "operational_tests.rs"]
mod tests;