use lazy_static::lazy_static;
use prometheus::{
register_counter_vec, register_gauge_vec, register_histogram_vec, CounterVec, GaugeVec,
HistogramVec, Registry,
};
use std::sync::Arc;
lazy_static! {
static ref PERFORMANCE_METRICS: Arc<PerformanceMetrics> = Arc::new(
PerformanceMetrics::new(crate::metrics::search_metrics::get_registry())
.expect("Failed to initialize performance metrics")
);
}
pub fn get_performance_metrics() -> Arc<PerformanceMetrics> {
PERFORMANCE_METRICS.clone()
}
pub struct PerformanceMetrics {
indexing_rate: HistogramVec,
indexing_latency: HistogramVec,
memory_usage: GaugeVec,
cache_hit_rate: GaugeVec,
query_throughput: CounterVec,
chunks_created: CounterVec,
files_indexed: CounterVec,
}
impl PerformanceMetrics {
pub fn new(registry: &Registry) -> Result<Self, prometheus::Error> {
let indexing_rate = register_histogram_vec!(
prometheus::HistogramOpts::new(
"maproom_indexing_rate_files_per_minute",
"File indexing throughput in files per minute"
)
.buckets(vec![
10.0, 50.0, 100.0, 150.0, 200.0, 300.0, 500.0, 750.0, 1000.0, 1500.0, 2000.0
]),
&["language"]
)?;
registry.register(Box::new(indexing_rate.clone()))?;
let indexing_latency = register_histogram_vec!(
prometheus::HistogramOpts::new(
"maproom_indexing_latency_seconds",
"Per-file indexing latency in seconds"
)
.buckets(vec![
0.001, 0.005, 0.010, 0.025, 0.050, 0.100, 0.250, 0.500, 1.0, 2.0, 5.0
]),
&["language", "phase"]
)?;
registry.register(Box::new(indexing_latency.clone()))?;
let memory_usage = register_gauge_vec!(
"maproom_memory_usage_bytes",
"Current memory usage in bytes by component",
&["component"]
)?;
registry.register(Box::new(memory_usage.clone()))?;
let cache_hit_rate = register_gauge_vec!(
"maproom_cache_hit_rate",
"Cache effectiveness as a ratio of hits to total requests (0.0-1.0)",
&["cache_type"]
)?;
registry.register(Box::new(cache_hit_rate.clone()))?;
let query_throughput = register_counter_vec!(
"maproom_query_throughput_total",
"Total number of queries processed",
&["mode", "status"]
)?;
registry.register(Box::new(query_throughput.clone()))?;
let chunks_created = register_counter_vec!(
"maproom_chunks_created_total",
"Total number of chunks created during indexing",
&["language"]
)?;
registry.register(Box::new(chunks_created.clone()))?;
let files_indexed = register_counter_vec!(
"maproom_files_indexed_total",
"Total number of files indexed",
&["language"]
)?;
registry.register(Box::new(files_indexed.clone()))?;
Ok(Self {
indexing_rate,
indexing_latency,
memory_usage,
cache_hit_rate,
query_throughput,
chunks_created,
files_indexed,
})
}
pub fn record_indexing_rate(&self, files_per_minute: f64, language: &str) {
self.indexing_rate
.with_label_values(&[language])
.observe(files_per_minute);
}
pub fn record_indexing_latency(&self, duration_seconds: f64, language: &str, phase: &str) {
self.indexing_latency
.with_label_values(&[language, phase])
.observe(duration_seconds);
}
pub fn update_memory_usage(&self, bytes: u64, component: &str) {
self.memory_usage
.with_label_values(&[component])
.set(bytes as f64);
}
pub fn update_cache_hit_rate(&self, hit_rate: f64, cache_type: &str) {
self.cache_hit_rate
.with_label_values(&[cache_type])
.set(hit_rate);
}
pub fn increment_query_throughput(&self, mode: &str, success: bool) {
let status = if success { "success" } else { "error" };
self.query_throughput
.with_label_values(&[mode, status])
.inc();
}
pub fn increment_chunks_created(&self, count: usize, language: &str) {
self.chunks_created
.with_label_values(&[language])
.inc_by(count as f64);
}
pub fn increment_files_indexed(&self, language: &str) {
self.files_indexed.with_label_values(&[language]).inc();
}
pub fn record_file_indexing(
&self,
language: &str,
parse_duration_seconds: f64,
insert_duration_seconds: f64,
total_duration_seconds: f64,
chunks_count: usize,
) {
self.record_indexing_latency(parse_duration_seconds, language, "parse");
self.record_indexing_latency(insert_duration_seconds, language, "insert");
self.record_indexing_latency(total_duration_seconds, language, "total");
self.increment_chunks_created(chunks_count, language);
self.increment_files_indexed(language);
}
pub fn record_batch_indexing(&self, file_count: usize, duration_seconds: f64, language: &str) {
let files_per_minute = (file_count as f64 / duration_seconds) * 60.0;
self.record_indexing_rate(files_per_minute, language);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_performance_metrics_recording() {
let metrics = get_performance_metrics();
metrics.record_indexing_rate(250.0, "ts");
metrics.record_indexing_latency(0.015, "rs", "parse");
metrics.record_indexing_latency(0.005, "rs", "insert");
metrics.record_indexing_latency(0.020, "rs", "total");
metrics.update_memory_usage(100_000_000, "indexer");
metrics.update_memory_usage(50_000_000, "cache");
metrics.update_memory_usage(200_000_000, "total");
metrics.update_cache_hit_rate(0.85, "embedding");
metrics.update_cache_hit_rate(0.75, "query");
metrics.increment_query_throughput("code", true);
metrics.increment_query_throughput("text", false);
metrics.increment_chunks_created(15, "py");
metrics.increment_files_indexed("py");
}
#[test]
fn test_record_file_indexing() {
let metrics = get_performance_metrics();
metrics.record_file_indexing("ts", 0.010, 0.005, 0.015, 12);
}
#[test]
fn test_record_batch_indexing() {
let metrics = get_performance_metrics();
metrics.record_batch_indexing(100, 30.0, "mixed");
}
#[test]
fn test_global_performance_metrics_instance() {
let metrics1 = get_performance_metrics();
let metrics2 = get_performance_metrics();
assert!(Arc::ptr_eq(&metrics1, &metrics2));
}
#[test]
fn test_indexing_rate_targets() {
let metrics = get_performance_metrics();
metrics.record_indexing_rate(150.0, "ts"); metrics.record_indexing_rate(500.0, "rs"); metrics.record_indexing_rate(1000.0, "py"); }
#[test]
fn test_memory_usage_components() {
let metrics = get_performance_metrics();
metrics.update_memory_usage(100_000_000, "indexer");
metrics.update_memory_usage(50_000_000, "search");
metrics.update_memory_usage(25_000_000, "cache");
metrics.update_memory_usage(200_000_000, "total");
assert!(200_000_000 < 524_288_000);
}
#[test]
fn test_cache_types() {
let metrics = get_performance_metrics();
metrics.update_cache_hit_rate(0.80, "embedding");
metrics.update_cache_hit_rate(0.70, "query");
metrics.update_cache_hit_rate(0.90, "chunk");
}
}