use crate::telemetry::TelemetryConfig;
use prometheus::{Counter, Gauge, Histogram, HistogramOpts, Opts, Registry};
use std::sync::Arc;
pub struct Metrics {
registry: Arc<Registry>,
queries_total: Counter,
errors_total: Counter,
cache_hits: Counter,
cache_misses: Counter,
indexeddb_operations_total: Counter,
sync_operations_total: Counter,
leader_elections_total: Counter,
leadership_changes_total: Counter,
blocks_allocated_total: Counter,
blocks_deallocated_total: Counter,
query_duration: Histogram,
indexeddb_duration: Histogram,
sync_duration: Histogram,
leader_election_duration: Histogram,
active_connections: Gauge,
memory_bytes: Gauge,
storage_bytes: Gauge,
cache_size_bytes: Gauge,
is_leader: Gauge,
}
impl Metrics {
pub fn new() -> Result<Self, prometheus::Error> {
let registry = Arc::new(Registry::new());
Self::with_registry(registry)
}
pub fn with_config(_config: &TelemetryConfig) -> Result<Self, prometheus::Error> {
Self::new()
}
pub fn with_registry(registry: Arc<Registry>) -> Result<Self, prometheus::Error> {
let queries_total = Counter::with_opts(Opts::new(
"absurdersql_queries_total",
"Total number of SQL queries executed",
))?;
let errors_total = Counter::with_opts(Opts::new(
"absurdersql_errors_total",
"Total number of errors encountered",
))?;
let cache_hits = Counter::with_opts(Opts::new(
"absurdersql_cache_hits_total",
"Total number of cache hits",
))?;
let cache_misses = Counter::with_opts(Opts::new(
"absurdersql_cache_misses_total",
"Total number of cache misses",
))?;
let indexeddb_operations_total = Counter::with_opts(Opts::new(
"absurdersql_indexeddb_operations_total",
"Total number of IndexedDB operations (reads + writes)",
))?;
let sync_operations_total = Counter::with_opts(Opts::new(
"absurdersql_sync_operations_total",
"Total number of VFS sync operations",
))?;
let leader_elections_total = Counter::with_opts(Opts::new(
"absurdersql_leader_elections_total",
"Total number of leader election attempts",
))?;
let leadership_changes_total = Counter::with_opts(Opts::new(
"absurdersql_leadership_changes_total",
"Total number of leadership changes",
))?;
let blocks_allocated_total = Counter::with_opts(Opts::new(
"absurdersql_blocks_allocated_total",
"Total number of blocks allocated",
))?;
let blocks_deallocated_total = Counter::with_opts(Opts::new(
"absurdersql_blocks_deallocated_total",
"Total number of blocks deallocated",
))?;
let query_duration = Histogram::with_opts(
HistogramOpts::new(
"absurdersql_query_duration_ms",
"Query execution duration in milliseconds",
)
.buckets(vec![
1.0, 5.0, 10.0, 25.0, 50.0, 100.0, 250.0, 500.0, 1000.0,
]),
)?;
let indexeddb_duration = Histogram::with_opts(
HistogramOpts::new(
"absurdersql_indexeddb_duration_ms",
"IndexedDB operation duration in milliseconds",
)
.buckets(vec![10.0, 25.0, 50.0, 100.0, 250.0, 500.0, 1000.0, 2500.0]),
)?;
let sync_duration = Histogram::with_opts(
HistogramOpts::new(
"absurdersql_sync_duration_ms",
"VFS sync operation duration in milliseconds",
)
.buckets(vec![50.0, 100.0, 200.0, 500.0, 1000.0, 2000.0, 5000.0]),
)?;
let leader_election_duration = Histogram::with_opts(
HistogramOpts::new(
"absurdersql_leader_election_duration_ms",
"Leader election duration in milliseconds",
)
.buckets(vec![10.0, 25.0, 50.0, 100.0, 200.0, 500.0, 1000.0, 2000.0]),
)?;
let active_connections = Gauge::with_opts(Opts::new(
"absurdersql_active_connections",
"Number of active database connections",
))?;
let memory_bytes = Gauge::with_opts(Opts::new(
"absurdersql_memory_bytes",
"Memory usage in bytes",
))?;
let storage_bytes = Gauge::with_opts(Opts::new(
"absurdersql_storage_bytes",
"Storage usage in bytes (IndexedDB/filesystem)",
))?;
let cache_size_bytes = Gauge::with_opts(Opts::new(
"absurdersql_cache_size_bytes",
"Current LRU cache size in bytes",
))?;
let is_leader = Gauge::with_opts(Opts::new(
"absurdersql_is_leader",
"Current leadership status (1 = leader, 0 = follower)",
))?;
registry.register(Box::new(queries_total.clone()))?;
registry.register(Box::new(errors_total.clone()))?;
registry.register(Box::new(cache_hits.clone()))?;
registry.register(Box::new(cache_misses.clone()))?;
registry.register(Box::new(query_duration.clone()))?;
registry.register(Box::new(indexeddb_duration.clone()))?;
registry.register(Box::new(sync_duration.clone()))?;
registry.register(Box::new(active_connections.clone()))?;
registry.register(Box::new(memory_bytes.clone()))?;
registry.register(Box::new(storage_bytes.clone()))?;
registry.register(Box::new(cache_size_bytes.clone()))?;
registry.register(Box::new(indexeddb_operations_total.clone()))?;
registry.register(Box::new(sync_operations_total.clone()))?;
registry.register(Box::new(leader_elections_total.clone()))?;
registry.register(Box::new(leadership_changes_total.clone()))?;
registry.register(Box::new(leader_election_duration.clone()))?;
registry.register(Box::new(is_leader.clone()))?;
registry.register(Box::new(blocks_allocated_total.clone()))?;
registry.register(Box::new(blocks_deallocated_total.clone()))?;
Ok(Self {
registry,
queries_total,
errors_total,
cache_hits,
cache_misses,
indexeddb_operations_total,
sync_operations_total,
leader_elections_total,
leadership_changes_total,
blocks_allocated_total,
blocks_deallocated_total,
query_duration,
indexeddb_duration,
sync_duration,
leader_election_duration,
active_connections,
memory_bytes,
storage_bytes,
cache_size_bytes,
is_leader,
})
}
pub fn registry(&self) -> &Registry {
&self.registry
}
pub fn queries_total(&self) -> &Counter {
&self.queries_total
}
pub fn errors_total(&self) -> &Counter {
&self.errors_total
}
pub fn cache_hits(&self) -> &Counter {
&self.cache_hits
}
pub fn cache_misses(&self) -> &Counter {
&self.cache_misses
}
pub fn indexeddb_operations_total(&self) -> &Counter {
&self.indexeddb_operations_total
}
pub fn sync_operations_total(&self) -> &Counter {
&self.sync_operations_total
}
pub fn leader_elections_total(&self) -> &Counter {
&self.leader_elections_total
}
pub fn leadership_changes_total(&self) -> &Counter {
&self.leadership_changes_total
}
pub fn query_duration(&self) -> &Histogram {
&self.query_duration
}
pub fn indexeddb_duration(&self) -> &Histogram {
&self.indexeddb_duration
}
pub fn sync_duration(&self) -> &Histogram {
&self.sync_duration
}
pub fn leader_election_duration(&self) -> &Histogram {
&self.leader_election_duration
}
pub fn active_connections(&self) -> &Gauge {
&self.active_connections
}
pub fn memory_bytes(&self) -> &Gauge {
&self.memory_bytes
}
pub fn storage_bytes(&self) -> &Gauge {
&self.storage_bytes
}
pub fn cache_size_bytes(&self) -> &Gauge {
&self.cache_size_bytes
}
pub fn is_leader(&self) -> &Gauge {
&self.is_leader
}
pub fn blocks_allocated_total(&self) -> &Counter {
&self.blocks_allocated_total
}
pub fn blocks_deallocated_total(&self) -> &Counter {
&self.blocks_deallocated_total
}
pub fn cache_hit_ratio(&self) -> f64 {
let hits = self.cache_hits.get();
let misses = self.cache_misses.get();
let total = hits + misses;
if total == 0.0 { 0.0 } else { hits / total }
}
}
impl Clone for Metrics {
fn clone(&self) -> Self {
Self {
registry: Arc::clone(&self.registry),
queries_total: self.queries_total.clone(),
errors_total: self.errors_total.clone(),
cache_hits: self.cache_hits.clone(),
cache_misses: self.cache_misses.clone(),
indexeddb_operations_total: self.indexeddb_operations_total.clone(),
sync_operations_total: self.sync_operations_total.clone(),
leader_elections_total: self.leader_elections_total.clone(),
leadership_changes_total: self.leadership_changes_total.clone(),
blocks_allocated_total: self.blocks_allocated_total.clone(),
blocks_deallocated_total: self.blocks_deallocated_total.clone(),
query_duration: self.query_duration.clone(),
indexeddb_duration: self.indexeddb_duration.clone(),
sync_duration: self.sync_duration.clone(),
leader_election_duration: self.leader_election_duration.clone(),
active_connections: self.active_connections.clone(),
memory_bytes: self.memory_bytes.clone(),
storage_bytes: self.storage_bytes.clone(),
cache_size_bytes: self.cache_size_bytes.clone(),
is_leader: self.is_leader.clone(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_metrics_creation() {
let metrics = Metrics::new().expect("Failed to create metrics");
assert_eq!(metrics.queries_total().get(), 0.0);
}
#[test]
fn test_cache_hit_ratio_empty() {
let metrics = Metrics::new().expect("Failed to create metrics");
assert_eq!(metrics.cache_hit_ratio(), 0.0);
}
#[test]
fn test_cache_hit_ratio_calculation() {
let metrics = Metrics::new().expect("Failed to create metrics");
metrics.cache_hits().inc();
metrics.cache_hits().inc();
metrics.cache_misses().inc();
assert_eq!(metrics.cache_hit_ratio(), 2.0 / 3.0);
}
}