use serde::Serialize;
#[derive(Serialize, Clone, Debug, Default)]
pub struct Metrics {
pub timestamp: i64,
pub datetime: String,
pub io_read_mb_per_sec: Option<f64>,
pub io_write_mb_per_sec: Option<f64>,
pub sha256_duration_ms: Option<f64>,
pub memory_alloc_duration_ms: f64,
pub compute_duration_ms: f64,
pub mem_total_mb: u64,
pub mem_used_mb: u64,
pub mem_free_mb: u64,
pub mem_available_mb: u64,
pub swap_total_mb: u64,
pub swap_used_mb: u64,
pub mem_buffers_mb: u64,
pub mem_cached_mb: u64,
pub cpu_usage_percent: f32,
pub cpu_count: usize,
pub load_avg_1: f64,
pub load_avg_5: f64,
pub load_avg_15: f64,
pub process_count: usize,
pub thread_count: u64,
pub procs_running: u64,
pub procs_blocked: u64,
pub cpu_user: u64,
pub cpu_nice: u64,
pub cpu_system: u64,
pub cpu_idle: u64,
pub cpu_iowait: u64,
pub cpu_irq: u64,
pub cpu_softirq: u64,
pub cpu_steal: u64,
pub disk_reads_completed: u64,
pub disk_reads_merged: u64,
pub disk_sectors_read: u64,
pub disk_read_time_ms: u64,
pub disk_writes_completed: u64,
pub disk_writes_merged: u64,
pub disk_sectors_written: u64,
pub disk_write_time_ms: u64,
pub disk_io_in_progress: u64,
pub disk_io_time_ms: u64,
pub disk_weighted_io_time_ms: u64,
pub net_rx_bytes: u64,
pub net_tx_bytes: u64,
pub net_rx_packets: u64,
pub net_tx_packets: u64,
pub net_rx_errors: u64,
pub net_tx_errors: u64,
pub cpu_pressure_some_avg10: Option<f64>,
pub cpu_pressure_some_avg60: Option<f64>,
pub cpu_pressure_some_avg300: Option<f64>,
pub mem_pressure_some_avg10: Option<f64>,
pub mem_pressure_some_avg60: Option<f64>,
pub mem_pressure_full_avg10: Option<f64>,
pub io_pressure_some_avg10: Option<f64>,
pub io_pressure_some_avg60: Option<f64>,
pub io_pressure_full_avg10: Option<f64>,
pub io_pressure_full_avg60: Option<f64>,
pub cpu_temp_celsius: Option<f64>,
pub cpu_temp_source: Option<String>,
pub max_temp_celsius: Option<f64>,
pub dimm_temps: Option<String>,
pub dimm_temp_source: Option<String>,
pub dimm_temp_avg: Option<f64>,
pub dimm_temp_max: Option<f64>,
pub disk_temps: Option<String>,
pub disk_temp_source: Option<String>,
pub disk_temp_max: Option<f64>,
#[serde(skip_serializing)]
pub disk_temp_readings: Vec<DiskTempReading>,
pub context_switches: u64,
pub interrupts: u64,
pub dirty_mb: u64,
pub writeback_mb: u64,
pub anon_pages_mb: u64,
pub mapped_mb: u64,
pub shmem_mb: u64,
pub slab_mb: u64,
pub page_tables_mb: u64,
pub pgfault: u64,
pub pgmajfault: u64,
pub pgpgin: u64,
pub pgpgout: u64,
pub pswpin: u64,
pub pswpout: u64,
pub fd_allocated: u64,
pub fd_max: u64,
pub uptime_secs: f64,
pub smart_available: Option<bool>,
pub smart_health_all_passed: Option<bool>,
pub smart_reallocated_sectors_total: Option<u64>,
pub smart_pending_sectors_total: Option<u64>,
pub smart_unsafe_shutdowns_total: Option<u64>,
pub smart_unsafe_shutdowns: Option<String>,
pub ipmi_available: Option<bool>,
pub ipmi_dimm_temp_max: Option<f64>,
pub ipmi_dimm_status: Option<String>,
pub ipmi_dimm_details: Option<String>,
#[serde(skip_serializing)]
pub ipmi_dimm_temps: Vec<IpmiDimmTemp>,
}
#[derive(Serialize, Clone, Debug, Default)]
pub struct IpmiDimmTemp {
pub name: String,
pub temp_celsius: f64,
pub status: String,
}
#[derive(Serialize, Clone, Debug, Default)]
pub struct DiskTempReading {
pub name: String,
pub temp_celsius: f64,
}
#[cfg(test)]
mod tests {
use super::{DiskTempReading, IpmiDimmTemp, Metrics};
#[test]
fn csv_serialization_keeps_optional_columns_stable() {
let mut writer = csv::Writer::from_writer(Vec::new());
writer.serialize(Metrics::default()).unwrap();
let with_optional_values = Metrics {
smart_available: Some(true),
smart_health_all_passed: Some(false),
smart_reallocated_sectors_total: Some(7),
smart_pending_sectors_total: Some(2),
ipmi_available: Some(true),
ipmi_dimm_temp_max: Some(64.0),
ipmi_dimm_status: Some("ok".to_string()),
ipmi_dimm_details: Some("DIMMA1:64C[ok]".to_string()),
..Metrics::default()
};
writer.serialize(with_optional_values).unwrap();
let csv = String::from_utf8(writer.into_inner().unwrap()).unwrap();
let mut rows = csv.lines();
let header_len = rows.next().unwrap().split(',').count();
for row in rows {
assert_eq!(row.split(',').count(), header_len);
}
}
#[test]
fn csv_serialization_skips_in_memory_ipmi_vectors() {
let metrics = Metrics {
ipmi_dimm_details: Some("DIMMA1:64C[ok]".to_string()),
ipmi_dimm_temps: vec![IpmiDimmTemp {
name: "DIMMA1".to_string(),
temp_celsius: 64.0,
status: "ok".to_string(),
}],
..Metrics::default()
};
let mut writer = csv::Writer::from_writer(Vec::new());
writer.serialize(metrics).unwrap();
let csv = String::from_utf8(writer.into_inner().unwrap()).unwrap();
assert!(csv.contains("ipmi_dimm_details"));
assert!(csv.contains("DIMMA1:64C[ok]"));
assert!(!csv.contains("ipmi_dimm_temps"));
}
#[test]
fn csv_serialization_skips_in_memory_disk_temperature_vectors() {
let metrics = Metrics {
disk_temps: Some("nvme0:38.0,nvme1:42.0".to_string()),
disk_temp_readings: vec![
DiskTempReading {
name: "nvme0".to_string(),
temp_celsius: 38.0,
},
DiskTempReading {
name: "nvme1".to_string(),
temp_celsius: 42.0,
},
],
..Metrics::default()
};
let mut writer = csv::Writer::from_writer(Vec::new());
writer.serialize(metrics).unwrap();
let csv = String::from_utf8(writer.into_inner().unwrap()).unwrap();
assert!(csv.contains("disk_temps"));
assert!(csv.contains("nvme0:38.0"));
assert!(!csv.contains("disk_temp_readings"));
}
}