Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Socktop Connector Library

The socktop_connector library provides a high-level interface for connecting to socktop agents programmatically.

Overview

The connector library allows you to:

  • Build custom monitoring tools - Create your own dashboards and UIs
  • Integrate with existing systems - Add socktop metrics to your applications
  • Automate monitoring - Script-based system checks and alerts
  • WASM support - Use in browser-based applications

Installation

Add to your Cargo.toml:

[dependencies]
socktop_connector = "1.60"
tokio = { version = "1", features = ["full"] }

Quick Start

Basic Connection

use socktop_connector::{connect_to_socktop_agent, AgentRequest, AgentResponse};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Connect to agent
    let mut connector = connect_to_socktop_agent("ws://localhost:3000/ws").await?;
    
    // Request metrics
    if let Ok(AgentResponse::Metrics(metrics)) = connector.request(AgentRequest::Metrics).await {
        println!("Hostname: {}", metrics.hostname);
        println!("CPU Usage: {:.1}%", metrics.cpu_total);
        println!("Memory: {:.1} GB / {:.1} GB",
                 metrics.mem_used as f64 / 1_000_000_000.0,
                 metrics.mem_total as f64 / 1_000_000_000.0);
    }
    
    Ok(())
}

With TLS

use socktop_connector::connect_to_socktop_agent_with_tls;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let connector = connect_to_socktop_agent_with_tls(
        "wss://secure-host:8443/ws",
        "/path/to/cert.pem",
        false  // verify_hostname: false = pin the certificate (default socktop behavior)
    ).await?;
    
    // Use connector...
    
    Ok(())
}

Request Types

The connector supports several request types:

Metrics Request

Get comprehensive system metrics:

use socktop_connector::{AgentRequest, AgentResponse};

match connector.request(AgentRequest::Metrics).await {
    Ok(AgentResponse::Metrics(metrics)) => {
        println!("CPU Total: {:.1}%", metrics.cpu_total);
        
        // Per-core usage
        for (i, usage) in metrics.cpu_per_core.iter().enumerate() {
            println!("Core {}: {:.1}%", i, usage);
        }
        
        // CPU temperature
        if let Some(temp) = metrics.cpu_temp_c {
            println!("CPU Temperature: {:.1}°C", temp);
        }
        
        // Memory
        println!("Memory Used: {} bytes", metrics.mem_used);
        println!("Memory Total: {} bytes", metrics.mem_total);
        
        // Swap
        println!("Swap Used: {} bytes", metrics.swap_used);
        println!("Swap Total: {} bytes", metrics.swap_total);
        
        // Network interfaces
        for net in &metrics.networks {
            println!("Interface {}: ↓{} ↑{}", 
                     net.name, net.received, net.transmitted);
        }
        
        // GPU information
        if let Some(gpus) = &metrics.gpus {
            for gpu in gpus {
                if let Some(name) = &gpu.name {
                    println!("GPU: {}", name);
                    println!("  Utilization: {:.1}%", gpu.utilization.unwrap_or(0.0));
                    if let Some(temp) = gpu.temp {
                        println!("  Temperature: {:.1}°C", temp);
                    }
                }
            }
        }
    }
    Err(e) => eprintln!("Error: {}", e),
    _ => unreachable!(),
}

Process Request

Get process information:

match connector.request(AgentRequest::Processes).await {
    Ok(AgentResponse::Processes(processes)) => {
        println!("Total processes: {}", processes.process_count);
        
        for proc in &processes.top_processes {
            println!("PID {}: {} - CPU: {:.1}%, Mem: {} MB",
                     proc.pid,
                     proc.name,
                     proc.cpu_usage,
                     proc.mem_bytes / 1_000_000);
        }
    }
    Err(e) => eprintln!("Error: {}", e),
    _ => unreachable!(),
}

Disk Request

Get disk information:

match connector.request(AgentRequest::Disks).await {
    Ok(AgentResponse::Disks(disks)) => {
        for disk in disks {
            let used = disk.total - disk.available;
            let used_gb = used as f64 / 1_000_000_000.0;
            let total_gb = disk.total as f64 / 1_000_000_000.0;
            let percent = (used as f64 / disk.total as f64) * 100.0;
            
            println!("Disk {}: {:.1} GB / {:.1} GB ({:.1}%)",
                     disk.name, used_gb, total_gb, percent);
        }
    }
    Err(e) => eprintln!("Error: {}", e),
    _ => unreachable!(),
}

Continuous Monitoring

Monitor metrics in a loop:

use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut connector = connect_to_socktop_agent("ws://localhost:3000/ws").await?;
    
    loop {
        match connector.request(AgentRequest::Metrics).await {
            Ok(AgentResponse::Metrics(metrics)) => {
                println!("CPU: {:.1}%, Memory: {:.1}%",
                    metrics.cpu_total,
                    (metrics.mem_used as f64 / metrics.mem_total as f64) * 100.0
                );
            }
            Err(e) => {
                eprintln!("Connection error: {}", e);
                break;
            }
            _ => unreachable!(),
        }
        
        sleep(Duration::from_secs(2)).await;
    }
    
    Ok(())
}

Advanced Usage

Custom Configuration

ConnectorConfig uses a builder pattern:

use socktop_connector::{ConnectorConfig, SocktopConnector};

let config = ConnectorConfig::new("wss://server:8443/ws?token=secret-token")
    .with_tls_ca("/path/to/cert.pem")
    .with_hostname_verification(false);

let mut connector = SocktopConnector::new(config);
connector.connect().await?;

An authentication token is passed as a token query parameter in the URL (there is no separate token field).

Error Handling

ConnectorError variants carry structured context:

use socktop_connector::{AgentRequest, ConnectorError, Result, connect_to_socktop_agent};

async fn monitor() -> Result<()> {
    let mut connector = connect_to_socktop_agent("ws://server:3000/ws").await?;

    match connector.request(AgentRequest::Metrics).await {
        Ok(_response) => {
            // Handle response
            Ok(())
        }
        Err(e @ ConnectorError::ConnectionClosed { .. }) => {
            eprintln!("Connection closed, attempting reconnect...");
            Err(e)
        }
        Err(e) => {
            eprintln!("Error: {}", e);
            Err(e)
        }
    }
}

WASM Support

The connector supports WebAssembly for browser usage:

[dependencies]
socktop_connector = { version = "1.60", default-features = false, features = ["wasm"] }
use socktop_connector::connect_to_socktop_agent;
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub async fn monitor_system(url: String) -> Result<JsValue, JsValue> {
    let mut connector = connect_to_socktop_agent(&url)
        .await
        .map_err(|e| JsValue::from_str(&e.to_string()))?;
    
    match connector.request(AgentRequest::Metrics).await {
        Ok(AgentResponse::Metrics(metrics)) => {
            Ok(JsValue::from_str(&format!("CPU: {:.1}%", metrics.cpu_total)))
        }
        Err(e) => Err(JsValue::from_str(&e.to_string())),
        _ => Err(JsValue::from_str("Unexpected response")),
    }
}

Building Custom Applications

Example: Simple Dashboard

use socktop_connector::*;
use tokio::time::{interval, Duration};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let servers = vec![
        ("web", "ws://web.example.com:3000/ws"),
        ("db", "ws://db.example.com:3000/ws"),
        ("cache", "ws://cache.example.com:3000/ws"),
    ];
    
    let mut connectors = Vec::new();
    for (name, url) in servers {
        match connect_to_socktop_agent(url).await {
            Ok(conn) => connectors.push((name, conn)),
            Err(e) => eprintln!("Failed to connect to {}: {}", name, e),
        }
    }
    
    let mut tick = interval(Duration::from_secs(2));
    
    loop {
        tick.tick().await;
        
        for (name, connector) in &mut connectors {
            if let Ok(AgentResponse::Metrics(m)) = connector.request(AgentRequest::Metrics).await {
                println!("[{}] CPU: {:.1}%, Mem: {:.1}%",
                    name,
                    m.cpu_total,
                    (m.mem_used as f64 / m.mem_total as f64) * 100.0
                );
            }
        }
        
        println!("---");
    }
}

Example: Alert System

use socktop_connector::*;

async fn check_alerts(mut connector: SocktopConnector) -> Result<(), Box<dyn std::error::Error>> {
    match connector.request(AgentRequest::Metrics).await {
        Ok(AgentResponse::Metrics(metrics)) => {
            // CPU alert
            if metrics.cpu_total > 90.0 {
                eprintln!("ALERT: CPU usage at {:.1}%", metrics.cpu_total);
            }
            
            // Memory alert
            let mem_percent = (metrics.mem_used as f64 / metrics.mem_total as f64) * 100.0;
            if mem_percent > 90.0 {
                eprintln!("ALERT: Memory usage at {:.1}%", mem_percent);
            }
            
            // Disk alert
            if let Ok(AgentResponse::Disks(disks)) = connector.request(AgentRequest::Disks).await {
                for disk in disks {
                    let used_percent = ((disk.total - disk.available) as f64 / disk.total as f64) * 100.0;
                    if used_percent > 90.0 {
                        eprintln!("ALERT: Disk {} at {:.1}%", disk.name, used_percent);
                    }
                }
            }
        }
        Err(e) => eprintln!("Error fetching metrics: {}", e),
        _ => {}
    }
    
    Ok(())
}

Data Types

Key types provided by the library:

  • Metrics - System metrics (CPU, memory, network, GPU, etc.)
  • DetailedProcessInfo - Per-process detail (command, threads, …)
  • DiskInfo - Disk usage information
  • NetworkInfo - Network interface statistics
  • GpuInfo - GPU metrics
  • JournalEntry - Systemd journal entries
  • AgentRequest - Request types (Metrics, Disks, Processes, ProcessMetrics { pid }, JournalEntries { pid })
  • AgentResponse - Response types

See the crate documentation for complete API reference.

Performance Considerations

The connector is lightweight and efficient:

  • Protocol Buffers - Efficient binary serialization
  • Gzip compression - Reduced bandwidth usage
  • Async I/O - Non-blocking operations
  • Connection reuse - Single WebSocket for multiple requests

Typical resource usage:

  • Memory: ~1-5 MB per connection
  • CPU: < 0.1% during idle
  • Bandwidth: ~1-5 KB per metrics request

Troubleshooting

Connection Errors

match connect_to_socktop_agent(url).await {
    Err(ConnectorError::ConnectionFailed { source }) => {
        eprintln!("Connection failed: {}", source);
        // Retry logic here
    }
    Err(ConnectorError::InvalidUrl { url, .. }) => {
        eprintln!("Invalid URL: {}", url);
    }
    Err(e) => eprintln!("Other error: {}", e),
    Ok(conn) => { /* Success */ }
}

TLS Errors

A TlsError or CertificateError usually means the pinned certificate doesn’t match what the agent presented (or the PEM path is wrong). Re-copy cert.pem from the agent — see TLS Configuration. Hostname verification is off by default (with_hostname_verification(false)), which pins the certificate rather than skipping checks.

Examples Repository

Working examples in the socktop repository:

API Reference

Full API documentation: docs.rs/socktop_connector

Next Steps