darpan 0.2.2

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 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
        }
    }
}

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