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]
|
[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"] }
|
||||||
|
|||||||
139
src/collector.rs
139
src/collector.rs
@@ -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
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
{
|
||||||
|
// Disk I/O metrics only supported on Linux for now
|
||||||
vec![]
|
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()));
|
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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user