Implement agent version tracking to diagnose deployment issues: - Add get_agent_hash() method to extract Nix store hash from executable path - Collect system_agent_hash metric in NixOS collector - Display "Agent Hash" in system panel under NixOS section - Update metric filtering to include agent hash This helps identify which version of the agent is actually running when troubleshooting deployment or metric collection issues.
179 lines
6.1 KiB
Rust
179 lines
6.1 KiB
Rust
use async_trait::async_trait;
|
|
use cm_dashboard_shared::{Metric, MetricValue, Status, StatusTracker};
|
|
use std::process::Command;
|
|
use tracing::debug;
|
|
|
|
use super::{Collector, CollectorError};
|
|
use crate::config::NixOSConfig;
|
|
|
|
/// NixOS system information collector
|
|
///
|
|
/// Collects NixOS-specific system information including:
|
|
/// - NixOS version and build information
|
|
/// - Currently active/logged in users
|
|
pub struct NixOSCollector {
|
|
config: NixOSConfig,
|
|
}
|
|
|
|
impl NixOSCollector {
|
|
pub fn new(config: NixOSConfig) -> Self {
|
|
Self { config }
|
|
}
|
|
|
|
/// Get NixOS build information
|
|
fn get_nixos_build_info(&self) -> Result<String, Box<dyn std::error::Error>> {
|
|
// Get nixos-version output directly
|
|
let output = Command::new("nixos-version").output()?;
|
|
|
|
if !output.status.success() {
|
|
return Err("nixos-version command failed".into());
|
|
}
|
|
|
|
let version_line = String::from_utf8_lossy(&output.stdout);
|
|
let version = version_line.trim();
|
|
|
|
if version.is_empty() {
|
|
return Err("Empty nixos-version output".into());
|
|
}
|
|
|
|
// Remove codename part (e.g., "(Warbler)")
|
|
let clean_version = if let Some(pos) = version.find(" (") {
|
|
version[..pos].to_string()
|
|
} else {
|
|
version.to_string()
|
|
};
|
|
|
|
Ok(clean_version)
|
|
}
|
|
|
|
/// Get agent hash from binary path
|
|
fn get_agent_hash(&self) -> Result<String, Box<dyn std::error::Error>> {
|
|
// Get the path of the current executable
|
|
let exe_path = std::env::current_exe()?;
|
|
let exe_str = exe_path.to_string_lossy();
|
|
|
|
// Extract Nix store hash from path like /nix/store/fn804fh332mp8gz06qawminpj20xl25h-cm-dashboard-0.1.0/bin/cm-dashboard-agent
|
|
if let Some(store_path) = exe_str.strip_prefix("/nix/store/") {
|
|
if let Some(dash_pos) = store_path.find('-') {
|
|
return Ok(store_path[..dash_pos].to_string());
|
|
}
|
|
}
|
|
|
|
// Fallback to "unknown" if not in Nix store
|
|
Ok("unknown".to_string())
|
|
}
|
|
|
|
/// Get currently active users
|
|
fn get_active_users(&self) -> Result<Vec<String>, Box<dyn std::error::Error>> {
|
|
let output = Command::new("who").output()?;
|
|
|
|
if !output.status.success() {
|
|
return Err("who command failed".into());
|
|
}
|
|
|
|
let who_output = String::from_utf8_lossy(&output.stdout);
|
|
let mut users = std::collections::HashSet::new();
|
|
|
|
for line in who_output.lines() {
|
|
if let Some(username) = line.split_whitespace().next() {
|
|
if !username.is_empty() {
|
|
users.insert(username.to_string());
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(users.into_iter().collect())
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Collector for NixOSCollector {
|
|
fn name(&self) -> &str {
|
|
"nixos"
|
|
}
|
|
|
|
async fn collect(&self, _status_tracker: &mut StatusTracker) -> Result<Vec<Metric>, CollectorError> {
|
|
debug!("Collecting NixOS system information");
|
|
let mut metrics = Vec::new();
|
|
let timestamp = chrono::Utc::now().timestamp() as u64;
|
|
|
|
// Collect NixOS build information
|
|
match self.get_nixos_build_info() {
|
|
Ok(build_info) => {
|
|
metrics.push(Metric {
|
|
name: "system_nixos_build".to_string(),
|
|
value: MetricValue::String(build_info),
|
|
unit: None,
|
|
description: Some("NixOS build information".to_string()),
|
|
status: Status::Ok,
|
|
timestamp,
|
|
});
|
|
}
|
|
Err(e) => {
|
|
debug!("Failed to get NixOS build info: {}", e);
|
|
metrics.push(Metric {
|
|
name: "system_nixos_build".to_string(),
|
|
value: MetricValue::String("unknown".to_string()),
|
|
unit: None,
|
|
description: Some("NixOS build (failed to detect)".to_string()),
|
|
status: Status::Unknown,
|
|
timestamp,
|
|
});
|
|
}
|
|
}
|
|
|
|
// Collect active users
|
|
match self.get_active_users() {
|
|
Ok(users) => {
|
|
let users_str = users.join(", ");
|
|
metrics.push(Metric {
|
|
name: "system_active_users".to_string(),
|
|
value: MetricValue::String(users_str),
|
|
unit: None,
|
|
description: Some("Currently active users".to_string()),
|
|
status: Status::Ok,
|
|
timestamp,
|
|
});
|
|
}
|
|
Err(e) => {
|
|
debug!("Failed to get active users: {}", e);
|
|
metrics.push(Metric {
|
|
name: "system_active_users".to_string(),
|
|
value: MetricValue::String("unknown".to_string()),
|
|
unit: None,
|
|
description: Some("Active users (failed to detect)".to_string()),
|
|
status: Status::Unknown,
|
|
timestamp,
|
|
});
|
|
}
|
|
}
|
|
|
|
// Collect agent hash
|
|
match self.get_agent_hash() {
|
|
Ok(hash) => {
|
|
metrics.push(Metric {
|
|
name: "system_agent_hash".to_string(),
|
|
value: MetricValue::String(hash),
|
|
unit: None,
|
|
description: Some("Agent Nix store hash".to_string()),
|
|
status: Status::Ok,
|
|
timestamp,
|
|
});
|
|
}
|
|
Err(e) => {
|
|
debug!("Failed to get agent hash: {}", e);
|
|
metrics.push(Metric {
|
|
name: "system_agent_hash".to_string(),
|
|
value: MetricValue::String("unknown".to_string()),
|
|
unit: None,
|
|
description: Some("Agent hash (failed to detect)".to_string()),
|
|
status: Status::Unknown,
|
|
timestamp,
|
|
});
|
|
}
|
|
}
|
|
|
|
debug!("Collected {} NixOS metrics", metrics.len());
|
|
Ok(metrics)
|
|
}
|
|
} |