use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::time::{Duration, Instant};
#[derive(Debug, Default)]
pub struct SessionMetrics {
pub navigation_count: AtomicUsize,
pub expansion_count: AtomicUsize,
pub search_count: AtomicUsize,
pub context_add_count: AtomicUsize,
pub context_remove_count: AtomicUsize,
pub blocks_visited: AtomicUsize,
pub edges_followed: AtomicUsize,
pub total_execution_time_us: AtomicU64,
pub error_count: AtomicUsize,
pub budget_warnings: AtomicUsize,
}
impl SessionMetrics {
pub fn new() -> Self {
Self::default()
}
pub fn record_navigation(&self) {
self.navigation_count.fetch_add(1, Ordering::Relaxed);
}
pub fn record_expansion(&self, blocks_count: usize) {
self.expansion_count.fetch_add(1, Ordering::Relaxed);
self.blocks_visited
.fetch_add(blocks_count, Ordering::Relaxed);
}
pub fn record_search(&self) {
self.search_count.fetch_add(1, Ordering::Relaxed);
}
pub fn record_context_add(&self, count: usize) {
self.context_add_count.fetch_add(count, Ordering::Relaxed);
}
pub fn record_context_remove(&self) {
self.context_remove_count.fetch_add(1, Ordering::Relaxed);
}
pub fn record_traversal(&self) {
self.navigation_count.fetch_add(1, Ordering::Relaxed);
}
pub fn record_edges_followed(&self, count: usize) {
self.edges_followed.fetch_add(count, Ordering::Relaxed);
}
pub fn record_execution_time(&self, duration: Duration) {
self.total_execution_time_us
.fetch_add(duration.as_micros() as u64, Ordering::Relaxed);
}
pub fn record_error(&self) {
self.error_count.fetch_add(1, Ordering::Relaxed);
}
pub fn record_budget_warning(&self) {
self.budget_warnings.fetch_add(1, Ordering::Relaxed);
}
pub fn snapshot(&self) -> MetricsSnapshot {
MetricsSnapshot {
navigation_count: self.navigation_count.load(Ordering::Relaxed),
expansion_count: self.expansion_count.load(Ordering::Relaxed),
search_count: self.search_count.load(Ordering::Relaxed),
context_add_count: self.context_add_count.load(Ordering::Relaxed),
context_remove_count: self.context_remove_count.load(Ordering::Relaxed),
blocks_visited: self.blocks_visited.load(Ordering::Relaxed),
edges_followed: self.edges_followed.load(Ordering::Relaxed),
total_execution_time_us: self.total_execution_time_us.load(Ordering::Relaxed),
error_count: self.error_count.load(Ordering::Relaxed),
budget_warnings: self.budget_warnings.load(Ordering::Relaxed),
captured_at: Utc::now(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetricsSnapshot {
pub navigation_count: usize,
pub expansion_count: usize,
pub search_count: usize,
pub context_add_count: usize,
pub context_remove_count: usize,
pub blocks_visited: usize,
pub edges_followed: usize,
pub total_execution_time_us: u64,
pub error_count: usize,
pub budget_warnings: usize,
pub captured_at: DateTime<Utc>,
}
impl MetricsSnapshot {
pub fn total_operations(&self) -> usize {
self.navigation_count
+ self.expansion_count
+ self.search_count
+ self.context_add_count
+ self.context_remove_count
}
pub fn total_execution_time(&self) -> Duration {
Duration::from_micros(self.total_execution_time_us)
}
pub fn average_operation_time(&self) -> Option<Duration> {
let total = self.total_operations();
if total == 0 {
None
} else {
Some(Duration::from_micros(
self.total_execution_time_us / total as u64,
))
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OperationMetrics {
pub operation: String,
pub duration: Duration,
pub blocks_processed: usize,
pub success: bool,
pub timestamp: DateTime<Utc>,
}
impl OperationMetrics {
pub fn start(operation: &str) -> OperationMetricsBuilder {
OperationMetricsBuilder {
operation: operation.to_string(),
start: Instant::now(),
blocks_processed: 0,
}
}
}
pub struct OperationMetricsBuilder {
operation: String,
start: Instant,
blocks_processed: usize,
}
impl OperationMetricsBuilder {
pub fn blocks(mut self, count: usize) -> Self {
self.blocks_processed = count;
self
}
pub fn finish(self, success: bool) -> OperationMetrics {
OperationMetrics {
operation: self.operation,
duration: self.start.elapsed(),
blocks_processed: self.blocks_processed,
success,
timestamp: Utc::now(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_session_metrics() {
let metrics = SessionMetrics::new();
metrics.record_navigation();
metrics.record_expansion(5);
metrics.record_search();
let snapshot = metrics.snapshot();
assert_eq!(snapshot.navigation_count, 1);
assert_eq!(snapshot.expansion_count, 1);
assert_eq!(snapshot.search_count, 1);
assert_eq!(snapshot.blocks_visited, 5);
}
#[test]
fn test_operation_metrics() {
let op = OperationMetrics::start("navigate").blocks(3).finish(true);
assert_eq!(op.operation, "navigate");
assert!(op.success);
assert_eq!(op.blocks_processed, 3);
}
}