darpan 0.2.5

Linux developer service monitoring utility with auto-detection, real-time health checks, and interactive TUI for databases, APIs, Docker containers, and more
Documentation
use crate::models::Service;
use chrono::{DateTime, Utc};
use std::process::Command;
use tracing::trace;

/// Get network statistics for a service
pub struct NetworkMonitor;

impl NetworkMonitor {
    /// Get count of active connections for a service port
    pub fn get_active_connections(service: &Service) -> Option<usize> {
        // Use ss (socket statistics) to count connections
        let output = Command::new("ss")
            .args(&["-tn", &format!("sport = :{}", service.port)])
            .output()
            .ok()?;

        if output.status.success() {
            let stdout = String::from_utf8_lossy(&output.stdout);
            // Count non-header lines
            let count = stdout.lines().skip(1).count();
            trace!("Service {} on port {} has {} active connections", service.name, service.port, count);
            Some(count)
        } else {
            None
        }
    }

    /// Get count of listening sockets for a service port
    pub fn is_port_listening(port: u16) -> bool {
        let output = Command::new("ss")
            .args(&["-tln", &format!("sport = :{}", port)])
            .output();

        if let Ok(output) = output {
            if output.status.success() {
                let stdout = String::from_utf8_lossy(&output.stdout);
                let has_listener = stdout.lines().skip(1).any(|line| {
                    line.contains(&format!(":{}", port)) && line.contains("LISTEN")
                });
                trace!("Port {} listening: {}", port, has_listener);
                return has_listener;
            }
        }
        false
    }

    /// Get process network activity statistics
    pub fn get_process_stats(pid: u32) -> Option<ProcessNetStats> {
        // Read from /proc/<pid>/net/tcp and /proc/<pid>/net/tcp6
        let tcp_path = format!("/proc/{}/net/tcp", pid);
        let tcp6_path = format!("/proc/{}/net/tcp6", pid);

        let mut established = 0;
        let mut listen = 0;

        // Parse TCP connections
        if let Ok(content) = std::fs::read_to_string(&tcp_path) {
            for line in content.lines().skip(1) {
                let parts: Vec<&str> = line.split_whitespace().collect();
                if parts.len() > 3 {
                    match parts[3] {
                        "01" => established += 1, // ESTABLISHED
                        "0A" => listen += 1,      // LISTEN
                        _ => {}
                    }
                }
            }
        }

        // Parse TCP6 connections
        if let Ok(content) = std::fs::read_to_string(&tcp6_path) {
            for line in content.lines().skip(1) {
                let parts: Vec<&str> = line.split_whitespace().collect();
                if parts.len() > 3 {
                    match parts[3] {
                        "01" => established += 1,
                        "0A" => listen += 1,
                        _ => {}
                    }
                }
            }
        }

        if established > 0 || listen > 0 {
            Some(ProcessNetStats {
                established_connections: established,
                listening_sockets: listen,
            })
        } else {
            None
        }
    }

    /// Get comprehensive traffic statistics for a service
    pub fn get_traffic_stats(service: &Service) -> TrafficStats {
        let active_connections = Self::get_active_connections(service).unwrap_or(0);
        
        // For now, we'll use a simplified approach:
        // - Active connections indicate traffic
        // - If port is listening and has connections, service is active
        let is_active = active_connections > 0 && Self::is_port_listening(service.port);
        
        // Try to get traffic volume from process if we have a PID
        let (bytes_in, bytes_out) = if let Some(pid) = service.pid {
            Self::get_process_traffic_volume(pid).unwrap_or((0, 0))
        } else {
            (0, 0)
        };
        
        TrafficStats {
            active_connections,
            total_connections_today: 0, // Would need persistent storage to track
            bytes_in,
            bytes_out,
            connections_per_minute: 0.0, // Would need time-based tracking
            last_connection_time: if is_active { Some(Utc::now()) } else { None },
            is_active,
        }
    }

    /// Get traffic volume (bytes in/out) for a process
    /// This reads from /proc/<pid>/net/sockstat or uses ss -i
    fn get_process_traffic_volume(_pid: u32) -> Option<(u64, u64)> {
        // Try reading from /proc/<pid>/net/sockstat
        // Note: This is a simplified implementation
        // Real traffic volume tracking would require more sophisticated monitoring
        // For now, we focus on connection-based activity detection
        None
    }
}

#[derive(Debug, Clone)]
pub struct ProcessNetStats {
    pub established_connections: usize,
    pub listening_sockets: usize,
}

/// Traffic statistics for a service
#[derive(Debug, Clone)]
pub struct TrafficStats {
    pub active_connections: usize,
    pub total_connections_today: u64,
    pub bytes_in: u64,
    pub bytes_out: u64,
    pub connections_per_minute: f64,
    pub last_connection_time: Option<DateTime<Utc>>,
    pub is_active: bool, // Has traffic in last N seconds
}

impl Default for TrafficStats {
    fn default() -> Self {
        Self {
            active_connections: 0,
            total_connections_today: 0,
            bytes_in: 0,
            bytes_out: 0,
            connections_per_minute: 0.0,
            last_connection_time: None,
            is_active: false,
        }
    }
}