use serde::Deserialize;
#[derive(Debug, Clone, PartialEq, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct DeviceStatistics {
#[serde(default)]
pub uptime_sec: u64,
#[serde(default)]
pub last_heartbeat_at: Option<String>,
#[serde(default)]
pub next_heartbeat_at: Option<String>,
#[serde(default)]
pub load_average_1_min: Option<f64>,
#[serde(default)]
pub load_average_5_min: Option<f64>,
#[serde(default)]
pub load_average_15_min: Option<f64>,
#[serde(default)]
pub cpu_utilization_pct: Option<f64>,
#[serde(default)]
pub memory_utilization_pct: Option<f64>,
#[serde(default)]
pub uplink: Option<StatisticsUplink>,
#[serde(default)]
pub interfaces: Option<StatisticsInterfaces>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StatisticsUplink {
#[serde(default)]
pub tx_rate_bps: u64,
#[serde(default)]
pub rx_rate_bps: u64,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StatisticsInterfaces {
#[serde(default)]
pub radios: Vec<RadioStatistics>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RadioStatistics {
#[serde(default)]
pub frequency_ghz: Option<f64>,
#[serde(default)]
pub tx_retries_pct: Option<f64>,
}
impl DeviceStatistics {
pub fn uptime_formatted(&self) -> String {
let secs = self.uptime_sec;
let days = secs / 86400;
let hours = (secs % 86400) / 3600;
let mins = (secs % 3600) / 60;
let secs = secs % 60;
if days > 0 {
format!("{}d {}h {}m {}s", days, hours, mins, secs)
} else if hours > 0 {
format!("{}h {}m {}s", hours, mins, secs)
} else if mins > 0 {
format!("{}m {}s", mins, secs)
} else {
format!("{}s", secs)
}
}
pub fn total_uplink_bps(&self) -> Option<u64> {
self.uplink.as_ref().map(|u| u.tx_rate_bps + u.rx_rate_bps)
}
pub fn has_radios(&self) -> bool {
self.interfaces
.as_ref()
.map(|i| !i.radios.is_empty())
.unwrap_or(false)
}
}
impl StatisticsUplink {
pub fn tx_rate_mbps(&self) -> f64 {
self.tx_rate_bps as f64 / 1_000_000.0
}
pub fn rx_rate_mbps(&self) -> f64 {
self.rx_rate_bps as f64 / 1_000_000.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_uptime_formatted_days() {
let stats = DeviceStatistics {
uptime_sec: 90061, ..Default::default()
};
assert_eq!(stats.uptime_formatted(), "1d 1h 1m 1s");
}
#[test]
fn test_uptime_formatted_hours() {
let stats = DeviceStatistics {
uptime_sec: 3661, ..Default::default()
};
assert_eq!(stats.uptime_formatted(), "1h 1m 1s");
}
#[test]
fn test_uptime_formatted_minutes() {
let stats = DeviceStatistics {
uptime_sec: 61, ..Default::default()
};
assert_eq!(stats.uptime_formatted(), "1m 1s");
}
#[test]
fn test_uptime_formatted_seconds_only() {
let stats = DeviceStatistics {
uptime_sec: 45,
..Default::default()
};
assert_eq!(stats.uptime_formatted(), "45s");
}
#[test]
fn test_uptime_formatted_zero() {
let stats = DeviceStatistics {
uptime_sec: 0,
..Default::default()
};
assert_eq!(stats.uptime_formatted(), "0s");
}
#[test]
fn test_total_uplink_bps_with_uplink() {
let stats = DeviceStatistics {
uplink: Some(StatisticsUplink {
tx_rate_bps: 1_000_000,
rx_rate_bps: 2_000_000,
}),
..Default::default()
};
assert_eq!(stats.total_uplink_bps(), Some(3_000_000));
}
#[test]
fn test_total_uplink_bps_without_uplink() {
let stats = DeviceStatistics::default();
assert_eq!(stats.total_uplink_bps(), None);
}
#[test]
fn test_has_radios_true() {
let stats = DeviceStatistics {
interfaces: Some(StatisticsInterfaces {
radios: vec![RadioStatistics {
frequency_ghz: Some(5.0),
tx_retries_pct: Some(1.5),
}],
}),
..Default::default()
};
assert!(stats.has_radios());
}
#[test]
fn test_has_radios_empty() {
let stats = DeviceStatistics {
interfaces: Some(StatisticsInterfaces { radios: vec![] }),
..Default::default()
};
assert!(!stats.has_radios());
}
#[test]
fn test_has_radios_no_interfaces() {
let stats = DeviceStatistics::default();
assert!(!stats.has_radios());
}
#[test]
fn test_tx_rate_mbps() {
let uplink = StatisticsUplink {
tx_rate_bps: 100_000_000, rx_rate_bps: 0,
};
assert!((uplink.tx_rate_mbps() - 100.0).abs() < 0.001);
}
#[test]
fn test_rx_rate_mbps() {
let uplink = StatisticsUplink {
tx_rate_bps: 0,
rx_rate_bps: 50_000_000, };
assert!((uplink.rx_rate_mbps() - 50.0).abs() < 0.001);
}
}