Skip to main content

provider_agent/
heartbeat.rs

1//! Heartbeat loop. Posts a `heartbeat` JSON message every 15s. Real metrics
2//! collection lands in a later task; this scaffold sends hardcoded zeroes so
3//! the coordinator's connection-liveness logic still works end-to-end.
4
5use std::time::Duration;
6
7use serde_json::json;
8use tokio::sync::mpsc;
9use tokio_tungstenite::tungstenite::Message;
10use tracing::{debug, warn};
11
12const INTERVAL: Duration = Duration::from_secs(15);
13
14pub async fn spawn_loop(out: mpsc::Sender<Message>) {
15    let mut ticker = tokio::time::interval(INTERVAL);
16    // Skip the immediate tick — let the connection settle first.
17    ticker.tick().await;
18
19    loop {
20        ticker.tick().await;
21        let payload = json!({
22            "type": "heartbeat",
23            "queue_depth": 0_u32,
24            "tokens_per_sec_p50": 0.0_f64,
25            "p50_latency_ms": 0_u32,
26            "p95_latency_ms": 0_u32,
27            "error_rate_60s": 0.0_f64,
28            "last_error_at": serde_json::Value::Null,
29            "last_error_code": serde_json::Value::Null,
30        });
31        match out.send(Message::Text(payload.to_string().into())).await {
32            Ok(()) => debug!("heartbeat sent"),
33            Err(_) => {
34                warn!("heartbeat channel closed; exiting heartbeat loop");
35                break;
36            }
37        }
38    }
39}