update dep version and fix
This commit is contained in:
825
Cargo.lock
generated
825
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
12
Cargo.toml
12
Cargo.toml
@@ -9,17 +9,17 @@ repository = "https://github.com/battilo/symon"
|
||||
|
||||
[dependencies]
|
||||
# OpenTelemetry
|
||||
opentelemetry = { version = "0.26", features = ["metrics"] }
|
||||
opentelemetry-otlp = { version = "0.26", features = ["metrics", "grpc-tonic"] }
|
||||
opentelemetry_sdk = { version = "0.26", features = ["metrics", "rt-tokio"] }
|
||||
opentelemetry-semantic-conventions = "0.26"
|
||||
opentelemetry = { version = "0.31", features = ["metrics"] }
|
||||
opentelemetry-otlp = { version = "0.31", features = ["metrics", "grpc-tonic"] }
|
||||
opentelemetry_sdk = { version = "0.31", features = ["metrics", "rt-tokio"] }
|
||||
opentelemetry-semantic-conventions = "0.31"
|
||||
|
||||
# Async runtime
|
||||
tokio = { version = "1.48", features = ["rt-multi-thread", "macros", "sync", "time", "signal"] }
|
||||
tonic = "0.11"
|
||||
tonic = "0.12"
|
||||
|
||||
# System metrics collection
|
||||
sysinfo = "0.31"
|
||||
sysinfo = "0.33"
|
||||
|
||||
# Configuration
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
141
src/collector.rs
141
src/collector.rs
@@ -2,7 +2,7 @@ use crate::config::MetricsConfig;
|
||||
use anyhow::Result;
|
||||
use std::collections::HashMap;
|
||||
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
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -115,10 +115,8 @@ struct DiskIoStats {
|
||||
impl MetricsCollector {
|
||||
pub fn new(config: MetricsConfig) -> Self {
|
||||
// Initialize with minimal data - we'll refresh on-demand
|
||||
let refresh_kind = RefreshKind::new();
|
||||
|
||||
Self {
|
||||
system: System::new_with_specifics(refresh_kind),
|
||||
system: System::new(),
|
||||
networks: Networks::new_with_refreshed_list(),
|
||||
disks: Disks::new_with_refreshed_list(),
|
||||
config,
|
||||
@@ -141,15 +139,15 @@ impl MetricsCollector {
|
||||
}
|
||||
|
||||
if self.config.processes {
|
||||
self.system.refresh_processes(ProcessesToUpdate::All);
|
||||
self.system.refresh_processes(ProcessesToUpdate::All, true);
|
||||
}
|
||||
|
||||
if self.config.network {
|
||||
self.networks.refresh();
|
||||
self.networks.refresh(true);
|
||||
}
|
||||
|
||||
if self.config.disk {
|
||||
self.disks.refresh();
|
||||
self.disks.refresh(true);
|
||||
}
|
||||
|
||||
// Note: Temperature metrics are currently not implemented
|
||||
@@ -384,10 +382,129 @@ impl MetricsCollector {
|
||||
}
|
||||
|
||||
fn collect_disk_io(&mut self) -> Vec<DiskIoMetric> {
|
||||
// Note: sysinfo 0.31 doesn't provide disk I/O stats directly
|
||||
// This would require reading /proc/diskstats on Linux or using platform-specific APIs
|
||||
// For now, return empty vector
|
||||
// TODO: Implement platform-specific disk I/O collection
|
||||
vec![]
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
self.collect_disk_io_linux()
|
||||
}
|
||||
|
||||
#[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 ¤t_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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,20 +51,20 @@ impl MetricsExporter {
|
||||
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
|
||||
let exporter = opentelemetry_otlp::new_exporter()
|
||||
.tonic()
|
||||
let exporter = opentelemetry_otlp::MetricExporter::builder()
|
||||
.with_tonic()
|
||||
.with_endpoint(&config.endpoint)
|
||||
.with_timeout(config.export_timeout())
|
||||
.build_metrics_exporter(
|
||||
Box::new(opentelemetry_sdk::metrics::reader::DefaultTemporalitySelector::default())
|
||||
)
|
||||
.build()
|
||||
.context("Failed to build OTLP metrics exporter")?;
|
||||
|
||||
// Build meter provider
|
||||
let reader = PeriodicReader::builder(exporter, opentelemetry_sdk::runtime::Tokio)
|
||||
let reader = PeriodicReader::builder(exporter)
|
||||
.with_interval(config.export_interval())
|
||||
.build();
|
||||
|
||||
@@ -80,95 +80,95 @@ impl MetricsExporter {
|
||||
cpu_usage: meter
|
||||
.f64_gauge("system_cpu_usage_percent")
|
||||
.with_description("CPU usage percentage per core")
|
||||
.init(),
|
||||
.build(),
|
||||
memory_usage: meter
|
||||
.u64_gauge("system_memory_usage_bytes")
|
||||
.with_description("Memory usage in bytes")
|
||||
.init(),
|
||||
.build(),
|
||||
memory_total: meter
|
||||
.u64_gauge("system_memory_total_bytes")
|
||||
.with_description("Total memory in bytes")
|
||||
.init(),
|
||||
.build(),
|
||||
swap_usage: meter
|
||||
.u64_gauge("system_swap_usage_bytes")
|
||||
.with_description("Swap usage in bytes")
|
||||
.init(),
|
||||
.build(),
|
||||
swap_total: meter
|
||||
.u64_gauge("system_swap_total_bytes")
|
||||
.with_description("Total swap in bytes")
|
||||
.init(),
|
||||
.build(),
|
||||
network_rx: meter
|
||||
.u64_gauge("system_network_rx_bytes_per_sec")
|
||||
.with_description("Bytes received per second")
|
||||
.init(),
|
||||
.build(),
|
||||
network_tx: meter
|
||||
.u64_gauge("system_network_tx_bytes_per_sec")
|
||||
.with_description("Bytes transmitted per second")
|
||||
.init(),
|
||||
.build(),
|
||||
network_rx_packets: meter
|
||||
.u64_gauge("system_network_rx_packets_per_sec")
|
||||
.with_description("Packets received per second")
|
||||
.init(),
|
||||
.build(),
|
||||
network_tx_packets: meter
|
||||
.u64_gauge("system_network_tx_packets_per_sec")
|
||||
.with_description("Packets transmitted per second")
|
||||
.init(),
|
||||
.build(),
|
||||
network_rx_errors: meter
|
||||
.u64_gauge("system_network_rx_errors_per_sec")
|
||||
.with_description("Receive errors per second")
|
||||
.init(),
|
||||
.build(),
|
||||
network_tx_errors: meter
|
||||
.u64_gauge("system_network_tx_errors_per_sec")
|
||||
.with_description("Transmit errors per second")
|
||||
.init(),
|
||||
.build(),
|
||||
disk_usage: meter
|
||||
.u64_gauge("system_disk_usage_bytes")
|
||||
.with_description("Disk usage in bytes")
|
||||
.init(),
|
||||
.build(),
|
||||
disk_total: meter
|
||||
.u64_gauge("system_disk_total_bytes")
|
||||
.with_description("Total disk space in bytes")
|
||||
.init(),
|
||||
.build(),
|
||||
process_cpu: meter
|
||||
.f64_gauge("system_process_cpu_usage_percent")
|
||||
.with_description("Process CPU usage percentage")
|
||||
.init(),
|
||||
.build(),
|
||||
process_memory: meter
|
||||
.u64_gauge("system_process_memory_usage_bytes")
|
||||
.with_description("Process memory usage in bytes")
|
||||
.init(),
|
||||
.build(),
|
||||
temperature: meter
|
||||
.f64_gauge("system_temperature_celsius")
|
||||
.with_description("Temperature in Celsius")
|
||||
.init(),
|
||||
.build(),
|
||||
disk_io_read_bytes: meter
|
||||
.u64_gauge("system_disk_io_read_bytes_per_sec")
|
||||
.with_description("Disk read bytes per second")
|
||||
.init(),
|
||||
.build(),
|
||||
disk_io_write_bytes: meter
|
||||
.u64_gauge("system_disk_io_write_bytes_per_sec")
|
||||
.with_description("Disk write bytes per second")
|
||||
.init(),
|
||||
.build(),
|
||||
disk_io_read_ops: meter
|
||||
.u64_gauge("system_disk_io_read_ops_per_sec")
|
||||
.with_description("Disk read operations per second")
|
||||
.init(),
|
||||
.build(),
|
||||
disk_io_write_ops: meter
|
||||
.u64_gauge("system_disk_io_write_ops_per_sec")
|
||||
.with_description("Disk write operations per second")
|
||||
.init(),
|
||||
.build(),
|
||||
load_avg_1: meter
|
||||
.f64_gauge("system_load_average_1m")
|
||||
.with_description("System load average over 1 minute")
|
||||
.init(),
|
||||
.build(),
|
||||
load_avg_5: meter
|
||||
.f64_gauge("system_load_average_5m")
|
||||
.with_description("System load average over 5 minutes")
|
||||
.init(),
|
||||
.build(),
|
||||
load_avg_15: meter
|
||||
.f64_gauge("system_load_average_15m")
|
||||
.with_description("System load average over 15 minutes")
|
||||
.init(),
|
||||
.build(),
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
|
||||
Reference in New Issue
Block a user