Implement complete structured data architecture
All checks were successful
Build and Release / build-and-release (push) Successful in 2m10s
All checks were successful
Build and Release / build-and-release (push) Successful in 2m10s
Replace fragile string-based metrics with type-safe JSON data structures. Agent converts all metrics to structured data, dashboard processes typed fields. Changes: - Add AgentData struct with CPU, memory, storage, services, backup fields - Replace string parsing with direct field access throughout system - Maintain UI compatibility via temporary metric bridge conversion - Fix NVMe temperature display and eliminate string parsing bugs - Update protocol to support structured data transmission over ZMQ - Comprehensive metric type coverage: CPU, memory, storage, services, backup Version bump to 0.1.131
This commit is contained in:
161
shared/src/agent_data.rs
Normal file
161
shared/src/agent_data.rs
Normal file
@@ -0,0 +1,161 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Complete structured data from an agent
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AgentData {
|
||||
pub hostname: String,
|
||||
pub agent_version: String,
|
||||
pub timestamp: u64,
|
||||
pub system: SystemData,
|
||||
pub services: Vec<ServiceData>,
|
||||
pub backup: BackupData,
|
||||
}
|
||||
|
||||
/// System-level monitoring data
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SystemData {
|
||||
pub cpu: CpuData,
|
||||
pub memory: MemoryData,
|
||||
pub storage: StorageData,
|
||||
}
|
||||
|
||||
/// CPU monitoring data
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CpuData {
|
||||
pub load_1min: f32,
|
||||
pub load_5min: f32,
|
||||
pub load_15min: f32,
|
||||
pub frequency_mhz: f32,
|
||||
pub temperature_celsius: Option<f32>,
|
||||
}
|
||||
|
||||
/// Memory monitoring data
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct MemoryData {
|
||||
pub usage_percent: f32,
|
||||
pub total_gb: f32,
|
||||
pub used_gb: f32,
|
||||
pub available_gb: f32,
|
||||
pub swap_total_gb: f32,
|
||||
pub swap_used_gb: f32,
|
||||
pub tmpfs: Vec<TmpfsData>,
|
||||
}
|
||||
|
||||
/// Tmpfs filesystem data
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TmpfsData {
|
||||
pub mount: String,
|
||||
pub usage_percent: f32,
|
||||
pub used_gb: f32,
|
||||
pub total_gb: f32,
|
||||
}
|
||||
|
||||
/// Storage monitoring data
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct StorageData {
|
||||
pub drives: Vec<DriveData>,
|
||||
pub pools: Vec<PoolData>,
|
||||
}
|
||||
|
||||
/// Individual drive data
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DriveData {
|
||||
pub name: String,
|
||||
pub health: String,
|
||||
pub temperature_celsius: Option<f32>,
|
||||
pub wear_percent: Option<f32>,
|
||||
pub filesystems: Vec<FilesystemData>,
|
||||
}
|
||||
|
||||
/// Filesystem on a drive
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FilesystemData {
|
||||
pub mount: String,
|
||||
pub usage_percent: f32,
|
||||
pub used_gb: f32,
|
||||
pub total_gb: f32,
|
||||
}
|
||||
|
||||
/// Storage pool (MergerFS, RAID, etc.)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PoolData {
|
||||
pub name: String,
|
||||
pub mount: String,
|
||||
pub pool_type: String, // "mergerfs", "raid", etc.
|
||||
pub health: String,
|
||||
pub usage_percent: f32,
|
||||
pub used_gb: f32,
|
||||
pub total_gb: f32,
|
||||
pub data_drives: Vec<PoolDriveData>,
|
||||
pub parity_drives: Vec<PoolDriveData>,
|
||||
}
|
||||
|
||||
/// Drive in a storage pool
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PoolDriveData {
|
||||
pub name: String,
|
||||
pub temperature_celsius: Option<f32>,
|
||||
pub wear_percent: Option<f32>,
|
||||
pub health: String,
|
||||
}
|
||||
|
||||
/// Service monitoring data
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServiceData {
|
||||
pub name: String,
|
||||
pub status: String, // "active", "inactive", "failed"
|
||||
pub memory_mb: f32,
|
||||
pub disk_gb: f32,
|
||||
pub user_stopped: bool,
|
||||
}
|
||||
|
||||
/// Backup system data
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct BackupData {
|
||||
pub status: String,
|
||||
pub last_run: Option<u64>,
|
||||
pub next_scheduled: Option<u64>,
|
||||
pub total_size_gb: Option<f32>,
|
||||
pub repository_health: Option<String>,
|
||||
}
|
||||
|
||||
impl AgentData {
|
||||
/// Create new agent data with current timestamp
|
||||
pub fn new(hostname: String, agent_version: String) -> Self {
|
||||
Self {
|
||||
hostname,
|
||||
agent_version,
|
||||
timestamp: chrono::Utc::now().timestamp() as u64,
|
||||
system: SystemData {
|
||||
cpu: CpuData {
|
||||
load_1min: 0.0,
|
||||
load_5min: 0.0,
|
||||
load_15min: 0.0,
|
||||
frequency_mhz: 0.0,
|
||||
temperature_celsius: None,
|
||||
},
|
||||
memory: MemoryData {
|
||||
usage_percent: 0.0,
|
||||
total_gb: 0.0,
|
||||
used_gb: 0.0,
|
||||
available_gb: 0.0,
|
||||
swap_total_gb: 0.0,
|
||||
swap_used_gb: 0.0,
|
||||
tmpfs: Vec::new(),
|
||||
},
|
||||
storage: StorageData {
|
||||
drives: Vec::new(),
|
||||
pools: Vec::new(),
|
||||
},
|
||||
},
|
||||
services: Vec::new(),
|
||||
backup: BackupData {
|
||||
status: "unknown".to_string(),
|
||||
last_run: None,
|
||||
next_scheduled: None,
|
||||
total_size_gb: None,
|
||||
repository_health: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
pub mod agent_data;
|
||||
pub mod cache;
|
||||
pub mod error;
|
||||
pub mod metrics;
|
||||
pub mod protocol;
|
||||
|
||||
pub use agent_data::*;
|
||||
pub use cache::*;
|
||||
pub use error::*;
|
||||
pub use metrics::*;
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
use crate::metrics::Metric;
|
||||
use crate::agent_data::AgentData;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Message sent from agent to dashboard via ZMQ
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct MetricMessage {
|
||||
pub hostname: String,
|
||||
pub timestamp: u64,
|
||||
pub metrics: Vec<Metric>,
|
||||
}
|
||||
/// Message sent from agent to dashboard via ZMQ
|
||||
/// Always structured data - no legacy metrics support
|
||||
pub type AgentMessage = AgentData;
|
||||
|
||||
/// Command output streaming message
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -20,15 +16,6 @@ pub struct CommandOutputMessage {
|
||||
pub timestamp: u64,
|
||||
}
|
||||
|
||||
impl MetricMessage {
|
||||
pub fn new(hostname: String, metrics: Vec<Metric>) -> Self {
|
||||
Self {
|
||||
hostname,
|
||||
timestamp: chrono::Utc::now().timestamp() as u64,
|
||||
metrics,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CommandOutputMessage {
|
||||
pub fn new(hostname: String, command_id: String, command_type: String, output_line: String, is_complete: bool) -> Self {
|
||||
@@ -59,8 +46,8 @@ pub enum Command {
|
||||
pub enum CommandResponse {
|
||||
/// Acknowledgment of command
|
||||
Ack,
|
||||
/// Metrics response
|
||||
Metrics(Vec<Metric>),
|
||||
/// Agent data response
|
||||
AgentData(AgentData),
|
||||
/// Pong response to ping
|
||||
Pong,
|
||||
/// Error response
|
||||
@@ -76,7 +63,7 @@ pub struct MessageEnvelope {
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum MessageType {
|
||||
Metrics,
|
||||
AgentData,
|
||||
Command,
|
||||
CommandResponse,
|
||||
CommandOutput,
|
||||
@@ -84,10 +71,10 @@ pub enum MessageType {
|
||||
}
|
||||
|
||||
impl MessageEnvelope {
|
||||
pub fn metrics(message: MetricMessage) -> Result<Self, crate::SharedError> {
|
||||
pub fn agent_data(data: AgentData) -> Result<Self, crate::SharedError> {
|
||||
Ok(Self {
|
||||
message_type: MessageType::Metrics,
|
||||
payload: serde_json::to_vec(&message)?,
|
||||
message_type: MessageType::AgentData,
|
||||
payload: serde_json::to_vec(&data)?,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -119,11 +106,11 @@ impl MessageEnvelope {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn decode_metrics(&self) -> Result<MetricMessage, crate::SharedError> {
|
||||
pub fn decode_agent_data(&self) -> Result<AgentData, crate::SharedError> {
|
||||
match self.message_type {
|
||||
MessageType::Metrics => Ok(serde_json::from_slice(&self.payload)?),
|
||||
MessageType::AgentData => Ok(serde_json::from_slice(&self.payload)?),
|
||||
_ => Err(crate::SharedError::Protocol {
|
||||
message: "Expected metrics message".to_string(),
|
||||
message: "Expected agent data message".to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user