prns_runtime/runtime/
health.rs1use core::time::Duration;
2
3use crate::interfaces::{ConnectionState, InterfaceKind, InterfaceSnapshot};
4
5#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
11pub struct RuntimeHealth {
12 pub uptime_millis: u64,
13 pub interface_count: u32,
14 pub online_interface_count: u32,
15 pub local_client_count: u32,
16 pub route_count: u32,
17 pub link_count: u32,
18 pub transported_link_count: u32,
19 pub rx_bytes: u64,
20 pub tx_bytes: u64,
21 pub rx_bps: u64,
22 pub tx_bps: u64,
23}
24
25impl RuntimeHealth {
26 #[must_use]
28 pub fn from_snapshots(uptime: Duration, snapshots: &[InterfaceSnapshot]) -> Self {
29 let mut health = Self {
30 uptime_millis: millis_u64(uptime),
31 interface_count: snapshots.len() as u32,
32 ..Self::default()
33 };
34 for snapshot in snapshots {
35 if matches!(
36 snapshot.connection,
37 ConnectionState::Connected | ConnectionState::Degraded
38 ) {
39 health.online_interface_count = health.online_interface_count.saturating_add(1);
40 }
41 if snapshot.id.kind() == Some(InterfaceKind::LocalClient) {
42 health.local_client_count = health.local_client_count.saturating_add(1);
43 }
44 health.route_count = health.route_count.saturating_add(snapshot.destinations);
45 health.link_count = health.link_count.saturating_add(snapshot.links);
46 health.transported_link_count = health
47 .transported_link_count
48 .saturating_add(snapshot.transported_links);
49 health.rx_bytes = health.rx_bytes.saturating_add(snapshot.rx_bytes);
50 health.tx_bytes = health.tx_bytes.saturating_add(snapshot.tx_bytes);
51 if let Some(rates) = snapshot.transfer_rates {
52 health.rx_bps = health.rx_bps.saturating_add(u64::from(rates.rx_bps));
53 health.tx_bps = health.tx_bps.saturating_add(u64::from(rates.tx_bps));
54 }
55 }
56 health
57 }
58}
59
60fn millis_u64(duration: Duration) -> u64 {
61 u64::try_from(duration.as_millis()).unwrap_or(u64::MAX)
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67 use crate::interfaces::{InterfaceId, Membership, TransferRates};
68
69 #[test]
70 fn runtime_health_aggregates_interface_snapshots() {
71 let local_client = InterfaceSnapshot {
72 id: InterfaceId::from_channel_tag(InterfaceKind::LocalClient, b"app"),
73 mode: crate::interfaces::InterfaceMode::Full,
74 gravity: crate::interfaces::InterfaceGravity::ZERO,
75 connection: ConnectionState::Connected,
76 failure_reason: None,
77 rx_bytes: 10,
78 tx_bytes: 20,
79 transfer_rates: Some(TransferRates {
80 rx_bps: 3,
81 tx_bps: 4,
82 }),
83 destinations: 2,
84 links: 1,
85 transported_links: 0,
86 membership: Membership::Independent,
87 };
88 let wifi_peer = InterfaceSnapshot {
89 id: InterfaceId::from_channel_tag(InterfaceKind::WifiPeer, b"peer"),
90 mode: crate::interfaces::InterfaceMode::Full,
91 gravity: crate::interfaces::InterfaceGravity::ZERO,
92 connection: ConnectionState::Reconnecting,
93 failure_reason: None,
94 rx_bytes: 5,
95 tx_bytes: 7,
96 transfer_rates: None,
97 destinations: 1,
98 links: 0,
99 transported_links: 2,
100 membership: Membership::FleetMember {
101 supervisor_id: InterfaceId::from_channel_tag(InterfaceKind::AutoWifi, b"wifi"),
102 },
103 };
104
105 let health =
106 RuntimeHealth::from_snapshots(Duration::from_millis(123), &[local_client, wifi_peer]);
107
108 assert_eq!(health.uptime_millis, 123);
109 assert_eq!(health.interface_count, 2);
110 assert_eq!(health.online_interface_count, 1);
111 assert_eq!(health.local_client_count, 1);
112 assert_eq!(health.route_count, 3);
113 assert_eq!(health.link_count, 1);
114 assert_eq!(health.transported_link_count, 2);
115 assert_eq!(health.rx_bytes, 15);
116 assert_eq!(health.tx_bytes, 27);
117 assert_eq!(health.rx_bps, 3);
118 assert_eq!(health.tx_bps, 4);
119 }
120}