update dep version and fix

This commit is contained in:
2025-11-07 23:53:14 +01:00
parent 5cb3395694
commit 9dba844d13
4 changed files with 748 additions and 290 deletions

825
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,17 +9,17 @@ repository = "https://github.com/battilo/symon"
[dependencies] [dependencies]
# OpenTelemetry # OpenTelemetry
opentelemetry = { version = "0.26", features = ["metrics"] } opentelemetry = { version = "0.31", features = ["metrics"] }
opentelemetry-otlp = { version = "0.26", features = ["metrics", "grpc-tonic"] } opentelemetry-otlp = { version = "0.31", features = ["metrics", "grpc-tonic"] }
opentelemetry_sdk = { version = "0.26", features = ["metrics", "rt-tokio"] } opentelemetry_sdk = { version = "0.31", features = ["metrics", "rt-tokio"] }
opentelemetry-semantic-conventions = "0.26" opentelemetry-semantic-conventions = "0.31"
# Async runtime # Async runtime
tokio = { version = "1.48", features = ["rt-multi-thread", "macros", "sync", "time", "signal"] } tokio = { version = "1.48", features = ["rt-multi-thread", "macros", "sync", "time", "signal"] }
tonic = "0.11" tonic = "0.12"
# System metrics collection # System metrics collection
sysinfo = "0.31" sysinfo = "0.33"
# Configuration # Configuration
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }

View File

@@ -2,7 +2,7 @@ use crate::config::MetricsConfig;
use anyhow::Result; use anyhow::Result;
use std::collections::HashMap; use std::collections::HashMap;
use std::time::Instant; use std::time::Instant;
use sysinfo::{Disks, Networks, ProcessesToUpdate, RefreshKind, System}; use sysinfo::{Disks, Networks, ProcessesToUpdate, System};
/// System metrics collected at a point in time /// System metrics collected at a point in time
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -115,10 +115,8 @@ struct DiskIoStats {
impl MetricsCollector { impl MetricsCollector {
pub fn new(config: MetricsConfig) -> Self { pub fn new(config: MetricsConfig) -> Self {
// Initialize with minimal data - we'll refresh on-demand // Initialize with minimal data - we'll refresh on-demand
let refresh_kind = RefreshKind::new();
Self { Self {
system: System::new_with_specifics(refresh_kind), system: System::new(),
networks: Networks::new_with_refreshed_list(), networks: Networks::new_with_refreshed_list(),
disks: Disks::new_with_refreshed_list(), disks: Disks::new_with_refreshed_list(),
config, config,
@@ -141,15 +139,15 @@ impl MetricsCollector {
} }
if self.config.processes { if self.config.processes {
self.system.refresh_processes(ProcessesToUpdate::All); self.system.refresh_processes(ProcessesToUpdate::All, true);
} }
if self.config.network { if self.config.network {
self.networks.refresh(); self.networks.refresh(true);
} }
if self.config.disk { if self.config.disk {
self.disks.refresh(); self.disks.refresh(true);
} }
// Note: Temperature metrics are currently not implemented // Note: Temperature metrics are currently not implemented
@@ -384,10 +382,129 @@ impl MetricsCollector {
} }
fn collect_disk_io(&mut self) -> Vec<DiskIoMetric> { fn collect_disk_io(&mut self) -> Vec<DiskIoMetric> {
// Note: sysinfo 0.31 doesn't provide disk I/O stats directly #[cfg(target_os = "linux")]
// This would require reading /proc/diskstats on Linux or using platform-specific APIs {
// For now, return empty vector self.collect_disk_io_linux()
// TODO: Implement platform-specific disk I/O collection }
vec![]
#[cfg(not(target_os = "linux"))]
{
// Disk I/O metrics only supported on Linux for now
vec![]
}
}
#[cfg(target_os = "linux")]
fn collect_disk_io_linux(&mut self) -> Vec<DiskIoMetric> {
let now = Instant::now();
let mut metrics = Vec::new();
// Calculate time delta
let time_delta_secs = if let Some(last_time) = self.last_disk_io_time {
now.duration_since(last_time).as_secs_f64()
} else {
// First collection, just store the values
self.last_disk_io_time = Some(now);
if let Ok(stats) = Self::read_diskstats() {
self.last_disk_io_stats = stats;
}
return metrics; // Return empty on first run
};
// Update timestamp
self.last_disk_io_time = Some(now);
// Read current disk stats
let current_stats = match Self::read_diskstats() {
Ok(stats) => stats,
Err(_) => return metrics,
};
// Calculate rates for each disk
for (device_name, current) in &current_stats {
if let Some(last) = self.last_disk_io_stats.get(device_name) {
// Skip if device is a partition (has digits at the end) - we only want whole disks
// unless it's nvme, loop, or similar devices
if Self::should_include_device(device_name) {
metrics.push(DiskIoMetric {
device_name: device_name.clone(),
read_bytes_per_sec: Self::calculate_rate(current.read_bytes, last.read_bytes, time_delta_secs),
write_bytes_per_sec: Self::calculate_rate(current.write_bytes, last.write_bytes, time_delta_secs),
read_ops_per_sec: Self::calculate_rate(current.read_count, last.read_count, time_delta_secs),
write_ops_per_sec: Self::calculate_rate(current.write_count, last.write_count, time_delta_secs),
});
}
}
}
// Update last stats
self.last_disk_io_stats = current_stats;
metrics
}
#[cfg(target_os = "linux")]
fn read_diskstats() -> std::io::Result<HashMap<String, DiskIoStats>> {
use std::fs::File;
use std::io::{BufRead, BufReader};
let file = File::open("/proc/diskstats")?;
let reader = BufReader::new(file);
let mut stats = HashMap::new();
for line in reader.lines() {
let line = line?;
let fields: Vec<&str> = line.split_whitespace().collect();
// /proc/diskstats format:
// major minor name reads reads_merged sectors_read time_reading writes writes_merged sectors_written time_writing ...
// We need: name (field 2), reads (field 3), sectors_read (field 5), writes (field 7), sectors_written (field 9)
if fields.len() >= 14 {
let device_name = fields[2].to_string();
// Parse fields (with error handling)
let reads = fields[3].parse::<u64>().unwrap_or(0);
let sectors_read = fields[5].parse::<u64>().unwrap_or(0);
let writes = fields[7].parse::<u64>().unwrap_or(0);
let sectors_written = fields[9].parse::<u64>().unwrap_or(0);
// Sector size is typically 512 bytes
let read_bytes = sectors_read * 512;
let write_bytes = sectors_written * 512;
stats.insert(
device_name,
DiskIoStats {
read_bytes,
write_bytes,
read_count: reads,
write_count: writes,
},
);
}
}
Ok(stats)
}
#[cfg(target_os = "linux")]
fn should_include_device(device_name: &str) -> bool {
// Include whole disks: sda, nvme0n1, vda, hda, etc.
// Exclude partitions: sda1, nvme0n1p1, vda1, etc.
// Also exclude loop devices, ram devices, and other virtual devices
if device_name.starts_with("loop") ||
device_name.starts_with("ram") ||
device_name.starts_with("dm-") {
return false;
}
// For nvme devices: include nvme0n1 but exclude nvme0n1p1
if device_name.starts_with("nvme") {
return !device_name.contains('p');
}
// For standard devices (sd*, vd*, hd*): check if last char is a digit
!device_name.chars().last().map(|c| c.is_ascii_digit()).unwrap_or(false)
} }
} }

View File

@@ -51,20 +51,20 @@ impl MetricsExporter {
resource_kvs.push(KeyValue::new(key.clone(), value.clone())); resource_kvs.push(KeyValue::new(key.clone(), value.clone()));
} }
let resource = Resource::new(resource_kvs); let resource = Resource::builder_empty()
.with_attributes(resource_kvs)
.build();
// Build OTLP exporter using new pipeline API // Build OTLP exporter using new pipeline API
let exporter = opentelemetry_otlp::new_exporter() let exporter = opentelemetry_otlp::MetricExporter::builder()
.tonic() .with_tonic()
.with_endpoint(&config.endpoint) .with_endpoint(&config.endpoint)
.with_timeout(config.export_timeout()) .with_timeout(config.export_timeout())
.build_metrics_exporter( .build()
Box::new(opentelemetry_sdk::metrics::reader::DefaultTemporalitySelector::default())
)
.context("Failed to build OTLP metrics exporter")?; .context("Failed to build OTLP metrics exporter")?;
// Build meter provider // Build meter provider
let reader = PeriodicReader::builder(exporter, opentelemetry_sdk::runtime::Tokio) let reader = PeriodicReader::builder(exporter)
.with_interval(config.export_interval()) .with_interval(config.export_interval())
.build(); .build();
@@ -80,95 +80,95 @@ impl MetricsExporter {
cpu_usage: meter cpu_usage: meter
.f64_gauge("system_cpu_usage_percent") .f64_gauge("system_cpu_usage_percent")
.with_description("CPU usage percentage per core") .with_description("CPU usage percentage per core")
.init(), .build(),
memory_usage: meter memory_usage: meter
.u64_gauge("system_memory_usage_bytes") .u64_gauge("system_memory_usage_bytes")
.with_description("Memory usage in bytes") .with_description("Memory usage in bytes")
.init(), .build(),
memory_total: meter memory_total: meter
.u64_gauge("system_memory_total_bytes") .u64_gauge("system_memory_total_bytes")
.with_description("Total memory in bytes") .with_description("Total memory in bytes")
.init(), .build(),
swap_usage: meter swap_usage: meter
.u64_gauge("system_swap_usage_bytes") .u64_gauge("system_swap_usage_bytes")
.with_description("Swap usage in bytes") .with_description("Swap usage in bytes")
.init(), .build(),
swap_total: meter swap_total: meter
.u64_gauge("system_swap_total_bytes") .u64_gauge("system_swap_total_bytes")
.with_description("Total swap in bytes") .with_description("Total swap in bytes")
.init(), .build(),
network_rx: meter network_rx: meter
.u64_gauge("system_network_rx_bytes_per_sec") .u64_gauge("system_network_rx_bytes_per_sec")
.with_description("Bytes received per second") .with_description("Bytes received per second")
.init(), .build(),
network_tx: meter network_tx: meter
.u64_gauge("system_network_tx_bytes_per_sec") .u64_gauge("system_network_tx_bytes_per_sec")
.with_description("Bytes transmitted per second") .with_description("Bytes transmitted per second")
.init(), .build(),
network_rx_packets: meter network_rx_packets: meter
.u64_gauge("system_network_rx_packets_per_sec") .u64_gauge("system_network_rx_packets_per_sec")
.with_description("Packets received per second") .with_description("Packets received per second")
.init(), .build(),
network_tx_packets: meter network_tx_packets: meter
.u64_gauge("system_network_tx_packets_per_sec") .u64_gauge("system_network_tx_packets_per_sec")
.with_description("Packets transmitted per second") .with_description("Packets transmitted per second")
.init(), .build(),
network_rx_errors: meter network_rx_errors: meter
.u64_gauge("system_network_rx_errors_per_sec") .u64_gauge("system_network_rx_errors_per_sec")
.with_description("Receive errors per second") .with_description("Receive errors per second")
.init(), .build(),
network_tx_errors: meter network_tx_errors: meter
.u64_gauge("system_network_tx_errors_per_sec") .u64_gauge("system_network_tx_errors_per_sec")
.with_description("Transmit errors per second") .with_description("Transmit errors per second")
.init(), .build(),
disk_usage: meter disk_usage: meter
.u64_gauge("system_disk_usage_bytes") .u64_gauge("system_disk_usage_bytes")
.with_description("Disk usage in bytes") .with_description("Disk usage in bytes")
.init(), .build(),
disk_total: meter disk_total: meter
.u64_gauge("system_disk_total_bytes") .u64_gauge("system_disk_total_bytes")
.with_description("Total disk space in bytes") .with_description("Total disk space in bytes")
.init(), .build(),
process_cpu: meter process_cpu: meter
.f64_gauge("system_process_cpu_usage_percent") .f64_gauge("system_process_cpu_usage_percent")
.with_description("Process CPU usage percentage") .with_description("Process CPU usage percentage")
.init(), .build(),
process_memory: meter process_memory: meter
.u64_gauge("system_process_memory_usage_bytes") .u64_gauge("system_process_memory_usage_bytes")
.with_description("Process memory usage in bytes") .with_description("Process memory usage in bytes")
.init(), .build(),
temperature: meter temperature: meter
.f64_gauge("system_temperature_celsius") .f64_gauge("system_temperature_celsius")
.with_description("Temperature in Celsius") .with_description("Temperature in Celsius")
.init(), .build(),
disk_io_read_bytes: meter disk_io_read_bytes: meter
.u64_gauge("system_disk_io_read_bytes_per_sec") .u64_gauge("system_disk_io_read_bytes_per_sec")
.with_description("Disk read bytes per second") .with_description("Disk read bytes per second")
.init(), .build(),
disk_io_write_bytes: meter disk_io_write_bytes: meter
.u64_gauge("system_disk_io_write_bytes_per_sec") .u64_gauge("system_disk_io_write_bytes_per_sec")
.with_description("Disk write bytes per second") .with_description("Disk write bytes per second")
.init(), .build(),
disk_io_read_ops: meter disk_io_read_ops: meter
.u64_gauge("system_disk_io_read_ops_per_sec") .u64_gauge("system_disk_io_read_ops_per_sec")
.with_description("Disk read operations per second") .with_description("Disk read operations per second")
.init(), .build(),
disk_io_write_ops: meter disk_io_write_ops: meter
.u64_gauge("system_disk_io_write_ops_per_sec") .u64_gauge("system_disk_io_write_ops_per_sec")
.with_description("Disk write operations per second") .with_description("Disk write operations per second")
.init(), .build(),
load_avg_1: meter load_avg_1: meter
.f64_gauge("system_load_average_1m") .f64_gauge("system_load_average_1m")
.with_description("System load average over 1 minute") .with_description("System load average over 1 minute")
.init(), .build(),
load_avg_5: meter load_avg_5: meter
.f64_gauge("system_load_average_5m") .f64_gauge("system_load_average_5m")
.with_description("System load average over 5 minutes") .with_description("System load average over 5 minutes")
.init(), .build(),
load_avg_15: meter load_avg_15: meter
.f64_gauge("system_load_average_15m") .f64_gauge("system_load_average_15m")
.with_description("System load average over 15 minutes") .with_description("System load average over 15 minutes")
.init(), .build(),
}; };
Ok(Self { Ok(Self {