Readur/src/services/webdav_metrics_integration.rs

259 lines
8.5 KiB
Rust

use std::sync::Arc;
use std::time::Instant;
use uuid::Uuid;
use tracing::{debug, warn};
use crate::services::webdav_metrics_simple::WebDAVMetrics;
/// Integration layer for WebDAV metrics
/// This provides a clean interface for WebDAV services to record metrics
/// without coupling them tightly to the metrics implementation
pub struct WebDAVMetricsCollector {
metrics: Arc<WebDAVMetrics>,
}
impl WebDAVMetricsCollector {
pub fn new(metrics: Arc<WebDAVMetrics>) -> Self {
Self { metrics }
}
/// Create a new session tracker
pub fn start_session(&self, user_id: Uuid, source_id: Option<Uuid>) -> SessionTracker {
SessionTracker {
metrics: Arc::clone(&self.metrics),
user_id,
source_id,
start_time: Instant::now(),
files_processed: 0,
bytes_processed: 0,
requests_made: 0,
successful_requests: 0,
}
}
/// Record a standalone request (not part of a session)
pub async fn record_standalone_request(&self, success: bool, duration_ms: u64) {
self.metrics.record_request(success, duration_ms).await;
}
/// Get metrics for Prometheus export
pub async fn get_prometheus_metrics(&self) -> crate::services::webdav_metrics_simple::PrometheusMetrics {
self.metrics.get_prometheus_metrics().await
}
}
/// Tracks metrics for a single WebDAV sync session
/// This replaces the complex database-backed session tracking
pub struct SessionTracker {
metrics: Arc<WebDAVMetrics>,
user_id: Uuid,
source_id: Option<Uuid>,
start_time: Instant,
files_processed: u32,
bytes_processed: u64,
requests_made: u32,
successful_requests: u32,
}
impl SessionTracker {
/// Record that files were processed in this session
pub fn record_files_processed(&mut self, count: u32, bytes: u64) {
self.files_processed += count;
self.bytes_processed += bytes;
debug!("Session {}: processed {} files, {} bytes total",
self.user_id, self.files_processed, self.bytes_processed);
}
/// Record an HTTP request made during this session
pub async fn record_request(&mut self, success: bool, duration_ms: u64) {
self.requests_made += 1;
if success {
self.successful_requests += 1;
}
// Record in global metrics
self.metrics.record_request(success, duration_ms).await;
debug!("Session {}: request {} (success: {}, duration: {}ms)",
self.user_id, self.requests_made, success, duration_ms);
}
/// Complete the session successfully
pub async fn complete_success(self) {
let duration_ms = self.start_time.elapsed().as_millis() as u64;
self.metrics.record_session(
true,
duration_ms,
self.files_processed,
self.bytes_processed,
).await;
debug!("Session {} completed successfully: {}ms, {} files, {} bytes, {}/{} requests successful",
self.user_id, duration_ms, self.files_processed, self.bytes_processed,
self.successful_requests, self.requests_made);
}
/// Complete the session with failure
pub async fn complete_failure(self, _error: &str) {
let duration_ms = self.start_time.elapsed().as_millis() as u64;
self.metrics.record_session(
false,
duration_ms,
self.files_processed,
self.bytes_processed,
).await;
warn!("Session {} failed after {}ms: {} files, {} bytes, {}/{} requests successful",
self.user_id, duration_ms, self.files_processed, self.bytes_processed,
self.successful_requests, self.requests_made);
}
/// Get current session stats (for debugging/logging)
pub fn current_stats(&self) -> SessionStats {
SessionStats {
user_id: self.user_id,
source_id: self.source_id,
duration_ms: self.start_time.elapsed().as_millis() as u64,
files_processed: self.files_processed,
bytes_processed: self.bytes_processed,
requests_made: self.requests_made,
successful_requests: self.successful_requests,
}
}
}
/// Simple session statistics for logging/debugging
#[derive(Debug, Clone)]
pub struct SessionStats {
pub user_id: Uuid,
pub source_id: Option<Uuid>,
pub duration_ms: u64,
pub files_processed: u32,
pub bytes_processed: u64,
pub requests_made: u32,
pub successful_requests: u32,
}
/// Request timing helper for easy request measurement
pub struct RequestTimer {
start_time: Instant,
}
impl RequestTimer {
pub fn new() -> Self {
Self {
start_time: Instant::now(),
}
}
pub fn elapsed_ms(&self) -> u64 {
self.start_time.elapsed().as_millis() as u64
}
/// Complete and record the request
pub async fn complete(self, session: &mut SessionTracker, success: bool) {
let duration_ms = self.elapsed_ms();
session.record_request(success, duration_ms).await;
}
}
impl Default for RequestTimer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use tokio::time::{sleep, Duration};
#[tokio::test]
async fn test_session_tracking() {
let metrics = Arc::new(WebDAVMetrics::new());
let collector = WebDAVMetricsCollector::new(metrics);
let user_id = Uuid::new_v4();
let source_id = Some(Uuid::new_v4());
// Start session
let mut session = collector.start_session(user_id, source_id);
// Simulate processing files
session.record_files_processed(5, 1024000);
session.record_files_processed(3, 512000);
// Simulate HTTP requests
session.record_request(true, 100).await;
session.record_request(true, 150).await;
session.record_request(false, 5000).await; // Failed request
let stats = session.current_stats();
assert_eq!(stats.files_processed, 8);
assert_eq!(stats.bytes_processed, 1536000);
assert_eq!(stats.requests_made, 3);
assert_eq!(stats.successful_requests, 2);
// Complete successfully
session.complete_success().await;
// Check metrics
let prometheus_metrics = collector.get_prometheus_metrics().await;
assert_eq!(prometheus_metrics.total_sessions, 1);
assert_eq!(prometheus_metrics.successful_sessions, 1);
assert_eq!(prometheus_metrics.total_files_processed, 8);
assert_eq!(prometheus_metrics.total_bytes_processed, 1536000);
assert_eq!(prometheus_metrics.total_http_requests, 3);
}
#[tokio::test]
async fn test_request_timer() {
let metrics = Arc::new(WebDAVMetrics::new());
let collector = WebDAVMetricsCollector::new(metrics);
let user_id = Uuid::new_v4();
let mut session = collector.start_session(user_id, None);
// Test request timing
let timer = RequestTimer::new();
sleep(Duration::from_millis(10)).await; // Simulate work
let duration_before = timer.elapsed_ms();
timer.complete(&mut session, true).await;
// Should have recorded a request with reasonable duration
assert!(duration_before >= 10);
let stats = session.current_stats();
assert_eq!(stats.requests_made, 1);
assert_eq!(stats.successful_requests, 1);
}
#[tokio::test]
async fn test_failed_session() {
let metrics = Arc::new(WebDAVMetrics::new());
let collector = WebDAVMetricsCollector::new(metrics);
let user_id = Uuid::new_v4();
let mut session = collector.start_session(user_id, None);
// Process some data before failure
session.record_files_processed(2, 100000);
session.record_request(true, 100).await;
session.record_request(false, 200).await;
// Complete with failure
session.complete_failure("Connection error").await;
// Check metrics
let prometheus_metrics = collector.get_prometheus_metrics().await;
assert_eq!(prometheus_metrics.total_sessions, 1);
assert_eq!(prometheus_metrics.successful_sessions, 0);
assert_eq!(prometheus_metrics.failed_sessions, 1);
assert_eq!(prometheus_metrics.total_files_processed, 2);
assert_eq!(prometheus_metrics.total_http_requests, 2);
}
}