feat: add real-time metrics collection with ring buffer storage (MON-01)

Implements monitoring/collector.rs that collects per-container CPU/RAM/network/disk,
system-wide metrics, RPC latency, and WebSocket connection count every 60 seconds.
Data stored in dual ring buffers: 1-min resolution (24h) and 15-min resolution (7d).
Three new RPC endpoints: monitoring.current, monitoring.history, monitoring.containers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-11 11:11:02 +00:00
parent 47c783ceac
commit baeeb72f27
8 changed files with 1285 additions and 40 deletions

View File

@@ -4,19 +4,27 @@
use anyhow::Result;
use std::net::SocketAddr;
use tracing::info;
use tokio::signal;
mod api;
mod auth;
mod backup;
mod config;
mod content_server;
mod crash_recovery;
mod credentials;
mod disk_monitor;
mod health_monitor;
mod electrs_status;
mod container;
mod port_allocator;
mod data_model;
mod federation;
mod identity;
mod identity_manager;
mod marketplace;
mod mesh;
mod monitoring;
mod node_message;
mod nostr_discovery;
mod peers;
@@ -29,6 +37,7 @@ mod names;
mod network;
mod nostr_relays;
mod update;
mod vpn;
use auth::AuthManager;
use config::Config;
@@ -39,6 +48,8 @@ const DEV_DEFAULT_PASSWORD: &str = "password123";
#[tokio::main]
async fn main() -> Result<()> {
let startup_start = std::time::Instant::now();
// Initialize tracing
tracing_subscriber::fmt()
.with_env_filter(
@@ -47,12 +58,25 @@ async fn main() -> Result<()> {
)
.init();
info!("🚀 Starting Archipelago Bitcoin Node OS");
info!("Starting Archipelago Bitcoin Node OS");
// Load configuration
let config = Config::load().await?;
info!("📁 Data directory: {}", config.data_dir.display());
// Crash recovery: check if previous instance shut down cleanly
if let Some(containers) = crash_recovery::check_for_crash(&config.data_dir).await? {
info!("🔧 Recovering {} containers from previous crash...", containers.len());
let report = crash_recovery::recover_containers(&containers).await;
info!(
"🔧 Recovery complete: {}/{} containers restarted (failed: {:?})",
report.recovered, report.total, report.failed
);
}
// Write PID marker so we can detect crashes on next startup
crash_recovery::write_pid_marker(&config.data_dir).await?;
// In dev mode, ensure a default user exists so login works without manual setup
if config.dev_mode {
let auth = AuthManager::new(config.data_dir.clone());
@@ -70,11 +94,42 @@ async fn main() -> Result<()> {
.parse()
.expect("Invalid bind address");
info!("🌐 Server listening on http://{}", addr);
info!("📡 RPC API: http://{}/rpc/v1", addr);
info!("🔌 WebSocket: ws://{}/ws", addr);
// Spawn background update scheduler
let update_data_dir = config.data_dir.clone();
tokio::spawn(async move {
update::run_update_scheduler(update_data_dir).await;
});
server.serve(addr).await?;
// Spawn periodic container snapshot (for crash recovery)
crash_recovery::spawn_snapshot_task(config.data_dir.clone());
// Spawn disk space monitor (warns at 85%, auto-cleans at 90%)
disk_monitor::spawn_disk_monitor(config.data_dir.clone());
let startup_ms = startup_start.elapsed().as_millis();
info!("Server listening on http://{} (startup: {}ms)", addr, startup_ms);
info!("RPC API: http://{}/rpc/v1", addr);
info!("WebSocket: ws://{}/ws", addr);
// Graceful shutdown: wait for SIGTERM or SIGINT
let shutdown = async {
let mut sigterm = signal::unix::signal(signal::unix::SignalKind::terminate())
.expect("Failed to register SIGTERM handler");
tokio::select! {
_ = signal::ctrl_c() => {
info!("Received SIGINT (Ctrl+C), initiating graceful shutdown...");
}
_ = sigterm.recv() => {
info!("Received SIGTERM, initiating graceful shutdown...");
}
}
};
server.serve_with_shutdown(addr, shutdown).await?;
// Clean shutdown: remove PID marker so next startup doesn't trigger recovery
crash_recovery::remove_pid_marker(&config.data_dir).await;
info!("Archipelago shut down cleanly");
Ok(())
}