cm-dashboard/shared/src/protocol.rs
Christoffer Martinsson 00a8ed3da2 Implement hysteresis for metric status changes to prevent flapping
Add comprehensive hysteresis support to prevent status oscillation near
threshold boundaries while maintaining responsive alerting.

Key Features:
- HysteresisThresholds with configurable upper/lower limits
- StatusTracker for per-metric status history
- Default gaps: CPU load 10%, memory 5%, disk temp 5°C

Updated Components:
- CPU load collector (5-minute average with hysteresis)
- Memory usage collector (percentage-based thresholds)
- Disk temperature collector (SMART data monitoring)
- All collectors updated to support StatusTracker interface

Cache Interval Adjustments:
- Service status: 60s → 10s (faster response)
- Disk usage: 300s → 60s (more frequent checks)
- Backup status: 900s → 60s (quicker updates)
- SMART data: moved to 600s tier (10 minutes)

Architecture:
- Individual metric status calculation in collectors
- Centralized StatusTracker in MetricCollectionManager
- Status aggregation preserved in dashboard widgets
2025-10-20 18:45:41 +02:00

117 lines
3.3 KiB
Rust

use crate::metrics::Metric;
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>,
}
impl MetricMessage {
pub fn new(hostname: String, metrics: Vec<Metric>) -> Self {
Self {
hostname,
timestamp: chrono::Utc::now().timestamp() as u64,
metrics,
}
}
}
/// Commands that can be sent from dashboard to agent
#[derive(Debug, Serialize, Deserialize)]
pub enum Command {
/// Request immediate metric refresh
RefreshMetrics,
/// Request specific metrics by name
RequestMetrics { metric_names: Vec<String> },
/// Ping command for connection testing
Ping,
}
/// Response from agent to dashboard commands
#[derive(Debug, Serialize, Deserialize)]
pub enum CommandResponse {
/// Acknowledgment of command
Ack,
/// Metrics response
Metrics(Vec<Metric>),
/// Pong response to ping
Pong,
/// Error response
Error { message: String },
}
/// ZMQ message envelope for routing
#[derive(Debug, Serialize, Deserialize)]
pub struct MessageEnvelope {
pub message_type: MessageType,
pub payload: Vec<u8>,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum MessageType {
Metrics,
Command,
CommandResponse,
Heartbeat,
}
impl MessageEnvelope {
pub fn metrics(message: MetricMessage) -> Result<Self, crate::SharedError> {
Ok(Self {
message_type: MessageType::Metrics,
payload: serde_json::to_vec(&message)?,
})
}
pub fn command(command: Command) -> Result<Self, crate::SharedError> {
Ok(Self {
message_type: MessageType::Command,
payload: serde_json::to_vec(&command)?,
})
}
pub fn command_response(response: CommandResponse) -> Result<Self, crate::SharedError> {
Ok(Self {
message_type: MessageType::CommandResponse,
payload: serde_json::to_vec(&response)?,
})
}
pub fn heartbeat() -> Result<Self, crate::SharedError> {
Ok(Self {
message_type: MessageType::Heartbeat,
payload: Vec::new(),
})
}
pub fn decode_metrics(&self) -> Result<MetricMessage, crate::SharedError> {
match self.message_type {
MessageType::Metrics => Ok(serde_json::from_slice(&self.payload)?),
_ => Err(crate::SharedError::Protocol {
message: "Expected metrics message".to_string(),
}),
}
}
pub fn decode_command(&self) -> Result<Command, crate::SharedError> {
match self.message_type {
MessageType::Command => Ok(serde_json::from_slice(&self.payload)?),
_ => Err(crate::SharedError::Protocol {
message: "Expected command message".to_string(),
}),
}
}
pub fn decode_command_response(&self) -> Result<CommandResponse, crate::SharedError> {
match self.message_type {
MessageType::CommandResponse => Ok(serde_json::from_slice(&self.payload)?),
_ => Err(crate::SharedError::Protocol {
message: "Expected command response message".to_string(),
}),
}
}
}