netwatch-sdk 0.3.2

Shared wire-format types and collectors for NetWatch Cloud — the SDK consumed by netwatch-agent and the NetWatch Cloud server. Parses /proc, ss, lsof, nettop, and libpcap events into a common Snapshot payload.
Documentation
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::collectors::connections::ConnectionDetail;
use crate::collectors::disk::{DiskIo, DiskUsage};
use crate::collectors::network_intel::{Alert, DnsAnalytics};
use crate::collectors::process_bandwidth::ProcessBandwidth;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IngestRequest {
    pub agent_version: String,
    pub host: HostInfo,
    pub snapshots: Vec<Snapshot>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HostInfo {
    pub host_id: Uuid,
    pub hostname: String,
    pub os: Option<String>,
    pub kernel: Option<String>,
    pub uptime_secs: Option<u64>,
    pub cpu_model: Option<String>,
    pub cpu_cores: Option<u32>,
    pub memory_total_bytes: Option<u64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Snapshot {
    pub timestamp: DateTime<Utc>,
    pub interfaces: Vec<InterfaceMetric>,
    pub health: Option<HealthMetric>,
    pub connection_count: Option<u32>,
    pub system: Option<SystemMetric>,
    pub disk_usage: Option<Vec<DiskUsage>>,
    pub disk_io: Option<DiskIo>,
    pub tcp_time_wait: Option<u32>,
    pub tcp_close_wait: Option<u32>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub processes: Option<Vec<ProcessBandwidth>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub connections: Option<Vec<ConnectionDetail>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub alerts: Option<Vec<Alert>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub dns_analytics: Option<DnsAnalytics>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InterfaceMetric {
    pub name: String,
    pub is_up: bool,
    pub rx_bytes: u64,
    pub tx_bytes: u64,
    pub rx_bytes_delta: u64,
    pub tx_bytes_delta: u64,
    pub rx_packets: u64,
    pub tx_packets: u64,
    pub rx_errors: u64,
    pub tx_errors: u64,
    pub rx_drops: u64,
    pub tx_drops: u64,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub rx_rate: Option<f64>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub tx_rate: Option<f64>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub rx_history: Option<Vec<u64>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub tx_history: Option<Vec<u64>>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthMetric {
    pub gateway_ip: Option<String>,
    pub gateway_rtt_ms: Option<f64>,
    pub gateway_loss_pct: Option<f64>,
    pub dns_ip: Option<String>,
    pub dns_rtt_ms: Option<f64>,
    pub dns_loss_pct: Option<f64>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub gateway_rtt_history: Option<Vec<Option<f64>>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub dns_rtt_history: Option<Vec<Option<f64>>>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SystemMetric {
    pub cpu_usage_pct: Option<f64>,
    pub memory_total_bytes: Option<u64>,
    pub memory_used_bytes: Option<u64>,
    pub memory_available_bytes: Option<u64>,
    pub load_avg_1m: Option<f64>,
    pub load_avg_5m: Option<f64>,
    pub load_avg_15m: Option<f64>,
    pub swap_total_bytes: Option<u64>,
    pub swap_used_bytes: Option<u64>,
    pub cpu_per_core: Option<Vec<f64>>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IngestResponse {
    pub accepted: u32,
    pub rejected: u32,
    pub host_id: Uuid,
    pub results: Vec<SnapshotResult>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SnapshotResult {
    pub index: usize,
    pub status: u16,
    pub message: String,
}