From 9a2df906ea62da91df2c7f3cc3e982667fe365aa Mon Sep 17 00:00:00 2001 From: Christoffer Martinsson Date: Thu, 27 Nov 2025 16:14:45 +0100 Subject: [PATCH] Add ZMQ communication statistics tracking and display --- Cargo.lock | 6 +++--- agent/Cargo.toml | 2 +- dashboard/Cargo.toml | 2 +- dashboard/src/app.rs | 2 +- dashboard/src/metrics/store.rs | 30 ++++++++++++++++++++++++++++++ dashboard/src/ui/mod.rs | 10 +++++++++- dashboard/src/ui/widgets/system.rs | 24 ++++++++++++++++++++++++ shared/Cargo.toml | 2 +- 8 files changed, 70 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9c8e0ab..4034911 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -279,7 +279,7 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "cm-dashboard" -version = "0.1.184" +version = "0.1.185" dependencies = [ "anyhow", "chrono", @@ -301,7 +301,7 @@ dependencies = [ [[package]] name = "cm-dashboard-agent" -version = "0.1.184" +version = "0.1.185" dependencies = [ "anyhow", "async-trait", @@ -324,7 +324,7 @@ dependencies = [ [[package]] name = "cm-dashboard-shared" -version = "0.1.184" +version = "0.1.185" dependencies = [ "chrono", "serde", diff --git a/agent/Cargo.toml b/agent/Cargo.toml index fb2f12f..262a366 100644 --- a/agent/Cargo.toml +++ b/agent/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cm-dashboard-agent" -version = "0.1.185" +version = "0.1.186" edition = "2021" [dependencies] diff --git a/dashboard/Cargo.toml b/dashboard/Cargo.toml index 494a115..179f8f7 100644 --- a/dashboard/Cargo.toml +++ b/dashboard/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cm-dashboard" -version = "0.1.185" +version = "0.1.186" edition = "2021" [dependencies] diff --git a/dashboard/src/app.rs b/dashboard/src/app.rs index a05b1fd..76710fe 100644 --- a/dashboard/src/app.rs +++ b/dashboard/src/app.rs @@ -215,7 +215,7 @@ impl Dashboard { // Update TUI with new metrics (only if not headless) if let Some(ref mut tui_app) = self.tui_app { - tui_app.update_metrics(&self.metric_store); + tui_app.update_metrics(&mut self.metric_store); } } diff --git a/dashboard/src/metrics/store.rs b/dashboard/src/metrics/store.rs index 20eb910..159f58e 100644 --- a/dashboard/src/metrics/store.rs +++ b/dashboard/src/metrics/store.rs @@ -5,6 +5,14 @@ use tracing::{debug, info, warn}; use super::MetricDataPoint; +/// ZMQ communication statistics per host +#[derive(Debug, Clone)] +pub struct ZmqStats { + pub packets_received: u64, + pub last_packet_time: Instant, + pub last_packet_age_secs: f64, +} + /// Central metric storage for the dashboard pub struct MetricStore { /// Current structured data: hostname -> AgentData @@ -13,6 +21,8 @@ pub struct MetricStore { historical_metrics: HashMap>, /// Last heartbeat timestamp per host last_heartbeat: HashMap, + /// ZMQ communication statistics per host + zmq_stats: HashMap, /// Configuration max_metrics_per_host: usize, history_retention: Duration, @@ -24,6 +34,7 @@ impl MetricStore { current_agent_data: HashMap::new(), historical_metrics: HashMap::new(), last_heartbeat: HashMap::new(), + zmq_stats: HashMap::new(), max_metrics_per_host, history_retention: Duration::from_secs(history_retention_hours * 3600), } @@ -44,6 +55,16 @@ impl MetricStore { self.last_heartbeat.insert(hostname.clone(), now); debug!("Updated heartbeat for host {}", hostname); + // Update ZMQ stats + let stats = self.zmq_stats.entry(hostname.clone()).or_insert(ZmqStats { + packets_received: 0, + last_packet_time: now, + last_packet_age_secs: 0.0, + }); + stats.packets_received += 1; + stats.last_packet_time = now; + stats.last_packet_age_secs = 0.0; // Just received + // Add to history let host_history = self .historical_metrics @@ -65,6 +86,15 @@ impl MetricStore { self.current_agent_data.get(hostname) } + /// Get ZMQ communication statistics for a host + pub fn get_zmq_stats(&mut self, hostname: &str) -> Option { + let now = Instant::now(); + self.zmq_stats.get_mut(hostname).map(|stats| { + // Update packet age + stats.last_packet_age_secs = now.duration_since(stats.last_packet_time).as_secs_f64(); + stats.clone() + }) + } /// Get connected hosts (hosts with recent heartbeats) pub fn get_connected_hosts(&self, timeout: Duration) -> Vec { diff --git a/dashboard/src/ui/mod.rs b/dashboard/src/ui/mod.rs index 6104cde..012c84e 100644 --- a/dashboard/src/ui/mod.rs +++ b/dashboard/src/ui/mod.rs @@ -100,7 +100,7 @@ impl TuiApp { } /// Update widgets with structured data from store (only for current host) - pub fn update_metrics(&mut self, metric_store: &MetricStore) { + pub fn update_metrics(&mut self, metric_store: &mut MetricStore) { if let Some(hostname) = self.current_host.clone() { // Get structured data for this host if let Some(agent_data) = metric_store.get_agent_data(&hostname) { @@ -110,6 +110,14 @@ impl TuiApp { host_widgets.system_widget.update_from_agent_data(agent_data); host_widgets.services_widget.update_from_agent_data(agent_data); + // Update ZMQ stats + if let Some(zmq_stats) = metric_store.get_zmq_stats(&hostname) { + host_widgets.system_widget.update_zmq_stats( + zmq_stats.packets_received, + zmq_stats.last_packet_age_secs + ); + } + host_widgets.last_update = Some(Instant::now()); } } diff --git a/dashboard/src/ui/widgets/system.rs b/dashboard/src/ui/widgets/system.rs index f2bc783..c702a5e 100644 --- a/dashboard/src/ui/widgets/system.rs +++ b/dashboard/src/ui/widgets/system.rs @@ -15,6 +15,10 @@ pub struct SystemWidget { nixos_build: Option, agent_hash: Option, + // ZMQ communication stats + zmq_packets_received: Option, + zmq_last_packet_age: Option, + // Network interfaces network_interfaces: Vec, @@ -92,6 +96,8 @@ impl SystemWidget { Self { nixos_build: None, agent_hash: None, + zmq_packets_received: None, + zmq_last_packet_age: None, network_interfaces: Vec::new(), cpu_load_1min: None, cpu_load_5min: None, @@ -154,6 +160,12 @@ impl SystemWidget { pub fn _get_agent_hash(&self) -> Option<&String> { self.agent_hash.as_ref() } + + /// Update ZMQ communication statistics + pub fn update_zmq_stats(&mut self, packets_received: u64, last_packet_age_secs: f64) { + self.zmq_packets_received = Some(packets_received); + self.zmq_last_packet_age = Some(last_packet_age_secs); + } } use super::Widget; @@ -796,6 +808,18 @@ impl SystemWidget { Span::styled(format!("Agent: {}", agent_version_text), Typography::secondary()) ])); + // ZMQ communication stats + if let (Some(packets), Some(age)) = (self.zmq_packets_received, self.zmq_last_packet_age) { + let age_text = if age < 1.0 { + format!("{:.0}ms ago", age * 1000.0) + } else { + format!("{:.1}s ago", age) + }; + lines.push(Line::from(vec![ + Span::styled(format!("ZMQ: {} pkts, last {}", packets, age_text), Typography::secondary()) + ])); + } + // CPU section lines.push(Line::from(vec![ Span::styled("CPU:", Typography::widget_title()) diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 6681447..d7f0c8a 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cm-dashboard-shared" -version = "0.1.185" +version = "0.1.186" edition = "2021" [dependencies]