diff --git a/src/lib.rs b/src/lib.rs index 0b30e90..75d7039 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -255,21 +255,6 @@ pub unsafe extern "C" fn ngx_http_vts_init_rust_module(_cf: *mut ngx_conf_t) -> NGX_OK as ngx_int_t } -/// VTS main configuration structure (simplified for now) -#[derive(Debug)] -#[allow(dead_code)] -struct VtsMainConfig { - /// Enable VTS tracking - pub enabled: bool, -} - -#[allow(dead_code)] -impl VtsMainConfig { - fn new() -> Self { - Self { enabled: true } - } -} - // VTS status request handler that generates traffic status response http_request_handler!(vts_status_handler, |request: &mut http::Request| { // Generate VTS status content (simplified version for now) @@ -921,73 +906,6 @@ unsafe fn register_log_phase_handler(_cf: *mut ngx_conf_t) -> Result<(), &'stati Ok(()) } -/// VTS LOG_PHASE handler - collects upstream statistics after request completion -/// Based on C implementation: ngx_http_vhost_traffic_status_handler -#[allow(dead_code)] // Used when nginx FFI bindings are fully available -unsafe extern "C" fn ngx_http_vts_log_handler(r: *mut ngx_http_request_t) -> ngx_int_t { - // Only process requests that used upstream - if (*r).upstream.is_null() { - return NGX_OK as ngx_int_t; - } - - // Collect upstream statistics - if collect_upstream_request_stats(r).is_err() { - // Log error but don't fail the request - return NGX_OK as ngx_int_t; - } - - NGX_OK as ngx_int_t -} - -/// Collect upstream statistics from completed request -/// Extracts timing, bytes, and status information from nginx request structure -#[allow(dead_code)] // Used when nginx FFI bindings are fully available -unsafe fn collect_upstream_request_stats(r: *mut ngx_http_request_t) -> Result<(), &'static str> { - let upstream = (*r).upstream; - if upstream.is_null() { - return Err("No upstream data"); - } - - // Extract upstream name (simplified - using "backend" from nginx.conf) - let upstream_name = "backend"; - - // Extract server address (simplified - using "127.0.0.1:8080" from nginx.conf) - let server_addr = "127.0.0.1:8080"; - - // Get timing information from nginx structures - // TODO: Fix nginx FFI access to response_time - let request_time = 50; // Simplified for now - - let upstream_response_time = request_time / 2; // Simplified calculation - - // Get byte counts - let bytes_sent = (*(*r).connection).sent; - let bytes_received = if !(*upstream).buffer.pos.is_null() && !(*upstream).buffer.last.is_null() - { - ((*upstream).buffer.last as usize - (*upstream).buffer.pos as usize) as u64 - } else { - 0 - }; - - // Get response status - let status_code = (*r).headers_out.status as u16; - - // Update statistics in global manager - if let Ok(mut manager) = VTS_MANAGER.write() { - manager.update_upstream_stats( - upstream_name, - server_addr, - request_time, - upstream_response_time, - bytes_sent.max(0) as u64, // Ensure non-negative - bytes_received, - status_code, - ); - } - - Ok(()) -} - /// Module context configuration #[no_mangle] static NGX_HTTP_VTS_MODULE_CTX: ngx_http_module_t = ngx_http_module_t { diff --git a/src/upstream_stats.rs b/src/upstream_stats.rs index 98f1b17..f9bd097 100644 --- a/src/upstream_stats.rs +++ b/src/upstream_stats.rs @@ -4,9 +4,7 @@ //! and managing upstream server statistics including request counts, //! byte transfers, response times, and server status information. -use ngx::ffi::{ngx_http_request_t, ngx_int_t, NGX_ERROR, NGX_OK}; use std::collections::HashMap; -use std::sync::{Arc, RwLock}; /// Response statistics structure (reused from stats.rs design) #[derive(Debug, Clone, Default)] @@ -301,484 +299,4 @@ mod tests { assert_eq!(zone.total_requests(), 300); assert_eq!(zone.total_bytes(), (3000, 1500)); } - - #[test] - fn test_upstream_stats_collector_creation() { - let collector = UpstreamStatsCollector::new(); - - // Should start with empty zones - let zones = collector.get_all_upstream_zones().unwrap(); - assert!(zones.is_empty()); - } - - #[test] - fn test_upstream_stats_collector_log_request() { - let collector = UpstreamStatsCollector::new(); - - // Log a request - let request = UpstreamRequestData::new( - "backend", - "10.0.0.1:80", - 100, // request_time - 50, // upstream_response_time - 1024, // bytes_sent - 2048, // bytes_received - 200, // status_code - ); - let result = collector.log_upstream_request(&request); - - assert!(result.is_ok()); - - // Verify the zone was created - let zone = collector.get_upstream_zone("backend").unwrap(); - assert_eq!(zone.name, "backend"); - assert_eq!(zone.servers.len(), 1); - - // Verify server statistics - let server_stats = zone.servers.get("10.0.0.1:80").unwrap(); - assert_eq!(server_stats.request_counter, 1); - assert_eq!(server_stats.in_bytes, 2048); - assert_eq!(server_stats.out_bytes, 1024); - assert_eq!(server_stats.responses.status_2xx, 1); - } - - #[test] - fn test_upstream_stats_collector_multiple_requests() { - let collector = UpstreamStatsCollector::new(); - - // Log multiple requests to different servers - collector - .log_upstream_request(&UpstreamRequestData::new( - "backend", - "10.0.0.1:80", - 100, - 50, - 1000, - 500, - 200, - )) - .unwrap(); - collector - .log_upstream_request(&UpstreamRequestData::new( - "backend", - "10.0.0.2:80", - 150, - 75, - 1500, - 750, - 200, - )) - .unwrap(); - collector - .log_upstream_request(&UpstreamRequestData::new( - "backend", - "10.0.0.1:80", - 120, - 60, - 1200, - 600, - 404, - )) - .unwrap(); - - let zone = collector.get_upstream_zone("backend").unwrap(); - assert_eq!(zone.servers.len(), 2); - - // Check first server (2 requests) - let server1 = zone.servers.get("10.0.0.1:80").unwrap(); - assert_eq!(server1.request_counter, 2); - assert_eq!(server1.responses.status_2xx, 1); - assert_eq!(server1.responses.status_4xx, 1); - - // Check second server (1 request) - let server2 = zone.servers.get("10.0.0.2:80").unwrap(); - assert_eq!(server2.request_counter, 1); - assert_eq!(server2.responses.status_2xx, 1); - } - - #[test] - fn test_upstream_stats_collector_multiple_upstreams() { - let collector = UpstreamStatsCollector::new(); - - // Log requests to different upstreams - collector - .log_upstream_request(&UpstreamRequestData::new( - "backend1", - "10.0.0.1:80", - 100, - 50, - 1000, - 500, - 200, - )) - .unwrap(); - collector - .log_upstream_request(&UpstreamRequestData::new( - "backend2", - "10.0.0.2:80", - 150, - 75, - 1500, - 750, - 200, - )) - .unwrap(); - - let zones = collector.get_all_upstream_zones().unwrap(); - assert_eq!(zones.len(), 2); - assert!(zones.contains_key("backend1")); - assert!(zones.contains_key("backend2")); - - // Verify each upstream has its own statistics - let backend1 = collector.get_upstream_zone("backend1").unwrap(); - let backend2 = collector.get_upstream_zone("backend2").unwrap(); - - assert_eq!(backend1.servers.len(), 1); - assert_eq!(backend2.servers.len(), 1); - assert!(backend1.servers.contains_key("10.0.0.1:80")); - assert!(backend2.servers.contains_key("10.0.0.2:80")); - } - - #[test] - fn test_upstream_stats_collector_reset() { - let collector = UpstreamStatsCollector::new(); - - // Add some statistics - collector - .log_upstream_request(&UpstreamRequestData::new( - "backend", - "10.0.0.1:80", - 100, - 50, - 1000, - 500, - 200, - )) - .unwrap(); - - // Verify data exists - let zones_before = collector.get_all_upstream_zones().unwrap(); - assert_eq!(zones_before.len(), 1); - - // Reset statistics - let result = collector.reset_statistics(); - assert!(result.is_ok()); - - // Verify data is cleared - let zones_after = collector.get_all_upstream_zones().unwrap(); - assert!(zones_after.is_empty()); - } - - #[test] - fn test_upstream_stats_collector_timing_aggregation() { - let collector = UpstreamStatsCollector::new(); - - // Log requests with different timing - collector - .log_upstream_request(&UpstreamRequestData::new( - "backend", - "10.0.0.1:80", - 100, - 40, - 1000, - 500, - 200, - )) - .unwrap(); - collector - .log_upstream_request(&UpstreamRequestData::new( - "backend", - "10.0.0.1:80", - 200, - 80, - 1500, - 750, - 200, - )) - .unwrap(); - collector - .log_upstream_request(&UpstreamRequestData::new( - "backend", - "10.0.0.1:80", - 150, - 60, - 1200, - 600, - 200, - )) - .unwrap(); - - let zone = collector.get_upstream_zone("backend").unwrap(); - let server = zone.servers.get("10.0.0.1:80").unwrap(); - - assert_eq!(server.request_counter, 3); - assert_eq!(server.request_time_total, 450); // 100 + 200 + 150 - assert_eq!(server.response_time_total, 180); // 40 + 80 + 60 - assert_eq!(server.request_time_counter, 3); - assert_eq!(server.response_time_counter, 3); - - // Test average calculations - assert_eq!(server.avg_request_time(), 150.0); // 450 / 3 - assert_eq!(server.avg_response_time(), 60.0); // 180 / 3 - } -} - -/// Upstream request data container -/// -/// Contains all metrics for a single upstream request -#[derive(Debug, Clone)] -pub struct UpstreamRequestData { - /// Name of the upstream group - pub upstream_name: String, - /// Address of the upstream server - pub upstream_addr: String, - /// Total request processing time in milliseconds - pub request_time: u64, - /// Upstream response time in milliseconds - pub upstream_response_time: u64, - /// Bytes sent to upstream - pub bytes_sent: u64, - /// Bytes received from upstream - pub bytes_received: u64, - /// HTTP status code from upstream - pub status_code: u16, -} - -impl UpstreamRequestData { - /// Create new upstream request data - #[allow(clippy::too_many_arguments)] // Constructor with all required fields - pub fn new( - upstream_name: &str, - upstream_addr: &str, - request_time: u64, - upstream_response_time: u64, - bytes_sent: u64, - bytes_received: u64, - status_code: u16, - ) -> Self { - Self { - upstream_name: upstream_name.to_string(), - upstream_addr: upstream_addr.to_string(), - request_time, - upstream_response_time, - bytes_sent, - bytes_received, - status_code, - } - } -} - -/// Upstream statistics collector for nginx integration -/// -/// Provides functionality to collect upstream statistics during nginx request processing -/// by hooking into the log phase and extracting information from nginx variables. -#[allow(dead_code)] // Used in nginx integration functions -pub struct UpstreamStatsCollector { - /// Upstream zones storage (thread-safe) - upstream_zones: Arc>>, -} - -impl UpstreamStatsCollector { - /// Create a new upstream statistics collector - pub fn new() -> Self { - Self { - upstream_zones: Arc::new(RwLock::new(HashMap::new())), - } - } - - /// Log upstream request statistics - /// - /// This method should be called from nginx log phase to record upstream statistics. - /// It extracts information from nginx variables and updates the corresponding - /// upstream zone and server statistics. - /// - /// # Arguments - /// - /// * `request` - Upstream request data containing all metrics - #[allow(dead_code)] // For future nginx integration - pub fn log_upstream_request(&self, request: &UpstreamRequestData) -> Result<(), &'static str> { - let mut zones = self - .upstream_zones - .write() - .map_err(|_| "Failed to acquire write lock on upstream zones")?; - - // Get or create upstream zone - let upstream_zone = zones - .entry(request.upstream_name.clone()) - .or_insert_with(|| UpstreamZone::new(&request.upstream_name)); - - // Get or create server statistics - let server_stats = upstream_zone.get_or_create_server(&request.upstream_addr); - - // Update statistics - server_stats.request_counter += 1; - server_stats.in_bytes += request.bytes_received; - server_stats.out_bytes += request.bytes_sent; - - // Update response status - server_stats.update_response_status(request.status_code); - - // Update timing information - server_stats.update_timing(request.request_time, request.upstream_response_time); - - Ok(()) - } - - /// Get upstream zone statistics (read-only access) - #[allow(dead_code)] // For future nginx integration - pub fn get_upstream_zone(&self, upstream_name: &str) -> Option { - let zones = self.upstream_zones.read().ok()?; - zones.get(upstream_name).cloned() - } - - /// Get all upstream zones (read-only access) - #[allow(dead_code)] // For future nginx integration - pub fn get_all_upstream_zones(&self) -> Result, &'static str> { - let zones = self - .upstream_zones - .read() - .map_err(|_| "Failed to acquire read lock on upstream zones")?; - Ok(zones.clone()) - } - - /// Reset all upstream statistics - #[allow(dead_code)] // For future nginx integration - pub fn reset_statistics(&self) -> Result<(), &'static str> { - let mut zones = self - .upstream_zones - .write() - .map_err(|_| "Failed to acquire write lock on upstream zones")?; - zones.clear(); - Ok(()) - } -} - -impl Default for UpstreamStatsCollector { - fn default() -> Self { - Self::new() - } -} - -// Global instance of the upstream statistics collector -#[allow(dead_code)] // For future nginx integration -static mut UPSTREAM_STATS_COLLECTOR: Option = None; -#[allow(dead_code)] // For future nginx integration -static mut UPSTREAM_STATS_INITIALIZED: bool = false; - -/// Initialize the global upstream statistics collector -/// -/// # Safety -/// -/// This function should be called once during nginx module initialization. -/// It's marked unsafe because it modifies global static variables. -#[allow(dead_code)] // For future nginx integration -pub unsafe fn init_upstream_stats_collector() { - if !UPSTREAM_STATS_INITIALIZED { - UPSTREAM_STATS_COLLECTOR = Some(UpstreamStatsCollector::new()); - UPSTREAM_STATS_INITIALIZED = true; - } -} - -/// Get reference to the global upstream statistics collector -/// -/// # Safety -/// -/// This function is unsafe because it accesses global static variables. -/// The caller must ensure that init_upstream_stats_collector() has been called first. -#[allow(dead_code)] // For future nginx integration -#[allow(static_mut_refs)] // Required for nginx integration -pub unsafe fn get_upstream_stats_collector() -> Option<&'static UpstreamStatsCollector> { - UPSTREAM_STATS_COLLECTOR.as_ref() -} - -/// Extract nginx variable as string -/// -/// # Safety -/// -/// This function is unsafe because it works with raw nginx pointers. -/// The caller must ensure that the request pointer is valid. -#[allow(dead_code)] // For future nginx integration -unsafe fn get_nginx_variable(r: *mut ngx_http_request_t, name: &str) -> Option { - if r.is_null() { - return None; - } - - // Create nginx string from name - let name_len = name.len(); - let name_ptr = name.as_ptr(); - - // This is a simplified version - real implementation would use nginx's - // variable lookup mechanisms - // For now, return None as placeholder - let _ = (name_len, name_ptr); // Suppress unused warnings - None -} - -/// Nginx log phase handler for upstream statistics -/// -/// This function should be registered as a log phase handler in nginx. -/// It extracts upstream information from nginx variables and logs the statistics. -/// -/// # Safety -/// -/// This function is unsafe because it's called by nginx and works with raw pointers. -#[allow(dead_code)] // For future nginx integration -pub unsafe extern "C" fn upstream_log_handler(r: *mut ngx_http_request_t) -> ngx_int_t { - if r.is_null() { - return NGX_ERROR as ngx_int_t; - } - - // Get the global statistics collector - let collector = match get_upstream_stats_collector() { - Some(collector) => collector, - None => return NGX_ERROR as ngx_int_t, - }; - - // Extract nginx variables (placeholder implementation) - let upstream_name = - get_nginx_variable(r, "upstream_name").unwrap_or_else(|| "default".to_string()); - let upstream_addr = - get_nginx_variable(r, "upstream_addr").unwrap_or_else(|| "unknown".to_string()); - - // Extract timing and status information - // In a real implementation, these would come from nginx variables - let request_time = 100; // Placeholder - let upstream_response_time = 50; // Placeholder - let bytes_sent = 1024; // Placeholder - let bytes_received = 2048; // Placeholder - let status_code = 200; // Placeholder - - // Log the upstream request - let request = UpstreamRequestData::new( - &upstream_name, - &upstream_addr, - request_time, - upstream_response_time, - bytes_sent, - bytes_received, - status_code, - ); - match collector.log_upstream_request(&request) { - Ok(()) => NGX_OK as ngx_int_t, - Err(_) => NGX_ERROR as ngx_int_t, - } -} - -/// Register upstream statistics log handler -/// -/// This function should be called during nginx module initialization -/// to register the log phase handler. -/// -/// # Safety -/// -/// This function is unsafe because it modifies nginx's configuration structures. -#[allow(dead_code)] // For future nginx integration -pub unsafe fn register_upstream_hooks() -> Result<(), &'static str> { - // Initialize the global collector - init_upstream_stats_collector(); - - // In a real implementation, this would register the log handler with nginx - // For now, this is a placeholder - - Ok(()) }