Restructure into workspace with dashboard and agent

This commit is contained in:
2025-10-11 13:56:58 +02:00
parent 65d31514a1
commit 82afe3d4f1
23 changed files with 405 additions and 59 deletions

18
agent/Cargo.toml Normal file
View File

@@ -0,0 +1,18 @@
[package]
name = "cm-dashboard-agent"
version = "0.1.0"
edition = "2021"
[dependencies]
cm-dashboard-shared = { path = "../shared" }
anyhow = "1.0"
clap = { version = "4.0", features = ["derive"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter"] }
tracing-appender = "0.2"
zmq = "0.10"
tokio = { version = "1.0", features = ["full"] }
rand = "0.8"

182
agent/src/main.rs Normal file
View File

@@ -0,0 +1,182 @@
use std::thread;
use std::time::Duration;
use anyhow::{anyhow, Context, Result};
use chrono::Utc;
use clap::{ArgAction, Parser};
use cm_dashboard_shared::envelope::{AgentType, MetricsEnvelope};
use rand::Rng;
use serde_json::json;
use tracing::info;
use tracing_subscriber::EnvFilter;
use zmq::{Context as ZmqContext, SocketType};
#[derive(Parser, Debug)]
#[command(
name = "cm-dashboard-agent",
version,
about = "CM Dashboard metrics agent"
)]
struct Cli {
/// Hostname to advertise in metric envelopes
#[arg(long, value_name = "HOSTNAME")]
hostname: String,
/// Bind endpoint for PUB socket (default tcp://*:6130)
#[arg(long, default_value = "tcp://*:6130", value_name = "ENDPOINT")]
bind: String,
/// Publish interval in milliseconds
#[arg(long, default_value_t = 5000)]
interval_ms: u64,
/// Disable smart metrics publisher
#[arg(long, action = ArgAction::SetTrue)]
disable_smart: bool,
/// Disable service metrics publisher
#[arg(long, action = ArgAction::SetTrue)]
disable_service: bool,
/// Disable backup metrics publisher
#[arg(long, action = ArgAction::SetTrue)]
disable_backup: bool,
/// Increase logging verbosity (-v, -vv)
#[arg(short, long, action = ArgAction::Count)]
verbose: u8,
}
fn main() -> Result<()> {
let cli = Cli::parse();
init_tracing(cli.verbose)?;
let context = ZmqContext::new();
let socket = context
.socket(SocketType::PUB)
.context("failed to create ZMQ PUB socket")?;
socket
.bind(&cli.bind)
.with_context(|| format!("failed to bind to {}", cli.bind))?;
info!(endpoint = %cli.bind, host = %cli.hostname, "agent started");
let interval = Duration::from_millis(cli.interval_ms.max(100));
let mut rng = rand::thread_rng();
loop {
let now = Utc::now();
let timestamp = now.timestamp() as u64;
let timestamp_rfc3339 = now.to_rfc3339();
if !cli.disable_smart {
let envelope = MetricsEnvelope {
hostname: cli.hostname.clone(),
agent_type: AgentType::Smart,
timestamp,
metrics: json!({
"status": "Healthy",
"drives": [{
"name": "nvme0n1",
"temperature_c": rng.gen_range(30.0..60.0),
"wear_level": rng.gen_range(1.0..10.0),
"power_on_hours": rng.gen_range(1000..20000),
"available_spare": rng.gen_range(90.0..100.0)
}],
"summary": {
"healthy": 1,
"warning": 0,
"critical": 0,
"capacity_total_gb": 1024,
"capacity_used_gb": rng.gen_range(100.0..800.0)
},
"issues": [],
"timestamp": timestamp_rfc3339
}),
};
publish(&socket, &envelope)?;
}
if !cli.disable_service {
let envelope = MetricsEnvelope {
hostname: cli.hostname.clone(),
agent_type: AgentType::Service,
timestamp,
metrics: json!({
"summary": {
"healthy": 5,
"degraded": 0,
"failed": 0,
"memory_used_mb": rng.gen_range(512.0..2048.0),
"memory_quota_mb": 4096.0
},
"services": [
{
"name": "example",
"status": "Running",
"memory_used_mb": rng.gen_range(128.0..512.0),
"memory_quota_mb": 1024.0,
"cpu_percent": rng.gen_range(0.0..75.0),
"sandbox_limit": null
}
],
"timestamp": timestamp_rfc3339
}),
};
publish(&socket, &envelope)?;
}
if !cli.disable_backup {
let envelope = MetricsEnvelope {
hostname: cli.hostname.clone(),
agent_type: AgentType::Backup,
timestamp,
metrics: json!({
"overall_status": "Healthy",
"backup": {
"last_success": timestamp_rfc3339,
"last_failure": null,
"size_gb": rng.gen_range(100.0..500.0),
"snapshot_count": rng.gen_range(10..40)
},
"service": {
"enabled": true,
"pending_jobs": 0,
"last_message": "Backups up-to-date"
},
"timestamp": timestamp_rfc3339
}),
};
publish(&socket, &envelope)?;
}
thread::sleep(interval);
}
}
fn publish(socket: &zmq::Socket, envelope: &MetricsEnvelope) -> Result<()> {
let serialized = serde_json::to_vec(envelope)?;
socket.send(serialized, 0)?;
Ok(())
}
fn init_tracing(verbosity: u8) -> Result<()> {
let level = match verbosity {
0 => "info",
1 => "debug",
_ => "trace",
};
let env_filter = std::env::var("RUST_LOG")
.ok()
.and_then(|value| EnvFilter::try_new(value).ok())
.unwrap_or_else(|| EnvFilter::new(level));
tracing_subscriber::fmt()
.with_env_filter(env_filter)
.with_target(false)
.compact()
.try_init()
.map_err(|err| anyhow!(err))?;
Ok(())
}