Complete atomic migration to structured data architecture
All checks were successful
Build and Release / build-and-release (push) Successful in 1m7s
All checks were successful
Build and Release / build-and-release (push) Successful in 1m7s
Implements clean structured data collection eliminating all string metric parsing bugs. Collectors now populate AgentData directly with type-safe field access. Key improvements: - Mount points preserved correctly (/ and /boot instead of root/boot) - Tmpfs discovery added to memory collector - Temperature data flows as typed f32 fields - Zero string parsing overhead - Complete removal of MetricCollectionManager bridge - Direct ZMQ transmission of structured JSON All functionality maintained: service tracking, notifications, status evaluation, and multi-host monitoring.
This commit is contained in:
@@ -1,172 +1,100 @@
|
||||
use async_trait::async_trait;
|
||||
use cm_dashboard_shared::{Metric, MetricValue, Status, StatusTracker};
|
||||
use cm_dashboard_shared::AgentData;
|
||||
use std::fs;
|
||||
use std::process::Command;
|
||||
use tracing::debug;
|
||||
|
||||
use super::{Collector, CollectorError};
|
||||
use crate::config::NixOSConfig;
|
||||
|
||||
/// NixOS system information collector
|
||||
/// NixOS system information collector with structured data output
|
||||
///
|
||||
/// Collects NixOS-specific system information including:
|
||||
/// - NixOS version and build information
|
||||
/// This collector gathers NixOS-specific information like:
|
||||
/// - System generation/build information
|
||||
/// - Version information
|
||||
/// - Agent version from Nix store path
|
||||
pub struct NixOSCollector {
|
||||
config: NixOSConfig,
|
||||
}
|
||||
|
||||
impl NixOSCollector {
|
||||
pub fn new(_config: NixOSConfig) -> Self {
|
||||
Self {}
|
||||
pub fn new(config: NixOSConfig) -> Self {
|
||||
Self { config }
|
||||
}
|
||||
|
||||
/// Collect NixOS system information and populate AgentData
|
||||
async fn collect_nixos_info(&self, agent_data: &mut AgentData) -> Result<(), CollectorError> {
|
||||
debug!("Collecting NixOS system information");
|
||||
|
||||
/// 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())
|
||||
// Set hostname (this is universal, not NixOS-specific)
|
||||
agent_data.hostname = self.get_hostname().await.unwrap_or_else(|| "unknown".to_string());
|
||||
|
||||
// Set agent version from environment or Nix store path
|
||||
agent_data.agent_version = self.get_agent_version().await;
|
||||
|
||||
// Set current timestamp
|
||||
agent_data.timestamp = chrono::Utc::now().timestamp() as u64;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get configuration hash from deployed nix store system
|
||||
/// Get git commit hash from rebuild process
|
||||
fn get_git_commit(&self) -> Result<String, Box<dyn std::error::Error>> {
|
||||
let commit_file = "/var/lib/cm-dashboard/git-commit";
|
||||
match std::fs::read_to_string(commit_file) {
|
||||
Ok(content) => {
|
||||
let commit_hash = content.trim();
|
||||
if commit_hash.len() >= 7 {
|
||||
Ok(commit_hash.to_string())
|
||||
} else {
|
||||
Err("Git commit hash too short".into())
|
||||
}
|
||||
}
|
||||
Err(e) => Err(format!("Failed to read git commit file: {}", e).into())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_config_hash(&self) -> Result<String, Box<dyn std::error::Error>> {
|
||||
// Read the symlink target of /run/current-system to get nix store path
|
||||
let output = Command::new("readlink")
|
||||
.arg("/run/current-system")
|
||||
.output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err("readlink command failed".into());
|
||||
}
|
||||
|
||||
let binding = String::from_utf8_lossy(&output.stdout);
|
||||
let store_path = binding.trim();
|
||||
|
||||
// Extract hash from nix store path
|
||||
// Format: /nix/store/HASH-nixos-system-HOSTNAME-VERSION
|
||||
if let Some(hash_part) = store_path.strip_prefix("/nix/store/") {
|
||||
if let Some(hash) = hash_part.split('-').next() {
|
||||
if hash.len() >= 8 {
|
||||
// Return first 8 characters of nix store hash
|
||||
return Ok(hash[..8].to_string());
|
||||
/// Get system hostname
|
||||
async fn get_hostname(&self) -> Option<String> {
|
||||
match fs::read_to_string("/etc/hostname") {
|
||||
Ok(hostname) => Some(hostname.trim().to_string()),
|
||||
Err(_) => {
|
||||
// Fallback to hostname command
|
||||
match Command::new("hostname").output() {
|
||||
Ok(output) => Some(String::from_utf8_lossy(&output.stdout).trim().to_string()),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err("Could not extract hash from nix store path".into())
|
||||
}
|
||||
|
||||
/// Get agent version from Nix store path or environment
|
||||
async fn get_agent_version(&self) -> String {
|
||||
// Try to extract version from the current executable path (Nix store)
|
||||
if let Ok(current_exe) = std::env::current_exe() {
|
||||
if let Some(exe_path) = current_exe.to_str() {
|
||||
if exe_path.starts_with("/nix/store/") {
|
||||
// Extract version from Nix store path
|
||||
// Path format: /nix/store/hash-cm-dashboard-agent-v0.1.138/bin/cm-dashboard-agent
|
||||
if let Some(store_part) = exe_path.strip_prefix("/nix/store/") {
|
||||
if let Some(dash_pos) = store_part.find('-') {
|
||||
let package_part = &store_part[dash_pos + 1..];
|
||||
if let Some(bin_pos) = package_part.find("/bin/") {
|
||||
let package_name = &package_part[..bin_pos];
|
||||
// Extract version from package name
|
||||
if let Some(version_start) = package_name.rfind("-v") {
|
||||
return package_name[version_start + 1..].to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to environment variable or default
|
||||
std::env::var("CM_DASHBOARD_VERSION").unwrap_or_else(|_| "unknown".to_string())
|
||||
}
|
||||
|
||||
/// Get NixOS system generation (build) information
|
||||
async fn get_nixos_generation(&self) -> Option<String> {
|
||||
match Command::new("nixos-version").output() {
|
||||
Ok(output) => {
|
||||
let version_str = String::from_utf8_lossy(&output.stdout);
|
||||
Some(version_str.trim().to_string())
|
||||
}
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Collector for NixOSCollector {
|
||||
|
||||
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 git commit information (shows what's actually deployed)
|
||||
match self.get_git_commit() {
|
||||
Ok(git_commit) => {
|
||||
metrics.push(Metric {
|
||||
name: "system_nixos_build".to_string(),
|
||||
value: MetricValue::String(git_commit),
|
||||
unit: None,
|
||||
description: Some("Git commit hash of deployed configuration".to_string()),
|
||||
status: Status::Ok,
|
||||
timestamp,
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("Failed to get git commit: {}", e);
|
||||
metrics.push(Metric {
|
||||
name: "system_nixos_build".to_string(),
|
||||
value: MetricValue::String("unknown".to_string()),
|
||||
unit: None,
|
||||
description: Some("Git commit hash (failed to detect)".to_string()),
|
||||
status: Status::Unknown,
|
||||
timestamp,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Collect config hash
|
||||
match self.get_config_hash() {
|
||||
Ok(hash) => {
|
||||
metrics.push(Metric {
|
||||
name: "system_config_hash".to_string(),
|
||||
value: MetricValue::String(hash),
|
||||
unit: None,
|
||||
description: Some("NixOS deployed configuration hash".to_string()),
|
||||
status: Status::Ok,
|
||||
timestamp,
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
debug!("Failed to get config hash: {}", e);
|
||||
metrics.push(Metric {
|
||||
name: "system_config_hash".to_string(),
|
||||
value: MetricValue::String("unknown".to_string()),
|
||||
unit: None,
|
||||
description: Some("Deployed config hash (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)
|
||||
async fn collect_structured(&self, agent_data: &mut AgentData) -> Result<(), CollectorError> {
|
||||
self.collect_nixos_info(agent_data).await
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user