wactorz_core/
metrics.rs

1//! Runtime telemetry for actors.
2//!
3//! [`ActorMetrics`] is a cheap `Arc`-wrapped, atomically updated counter set
4//! that actors carry internally. The registry exposes these over MQTT/REST.
5
6use std::sync::atomic::{AtomicU64, Ordering};
7
8use serde::{Deserialize, Serialize};
9
10/// Atomic runtime counters for an actor.
11///
12/// All fields use relaxed ordering because cross-thread ordering guarantees
13/// are not required for telemetry — occasional skew is acceptable.
14#[derive(Debug, Default)]
15pub struct ActorMetrics {
16    /// Total messages received since the actor started.
17    pub messages_received: AtomicU64,
18    /// Total messages successfully processed.
19    pub messages_processed: AtomicU64,
20    /// Total messages that raised an error during processing.
21    pub messages_failed: AtomicU64,
22    /// Number of heartbeat ticks emitted.
23    pub heartbeats: AtomicU64,
24    /// UNIX timestamp (seconds) of the last received message.
25    pub last_message_at: AtomicU64,
26    /// Number of supervisor-triggered restarts for this actor.
27    pub restart_count: AtomicU64,
28    /// Total LLM input tokens consumed by this actor.
29    pub llm_input_tokens: AtomicU64,
30    /// Total LLM output tokens produced by this actor.
31    pub llm_output_tokens: AtomicU64,
32    /// Total LLM cost in nano-USD (divide by 1_000_000_000 for USD).
33    pub llm_cost_nano_usd: AtomicU64,
34}
35
36impl ActorMetrics {
37    pub fn new() -> Self {
38        Self::default()
39    }
40
41    pub fn record_received(&self) {
42        self.messages_received.fetch_add(1, Ordering::Relaxed);
43        let now = std::time::SystemTime::now()
44            .duration_since(std::time::UNIX_EPOCH)
45            .unwrap_or_default()
46            .as_secs();
47        self.last_message_at.store(now, Ordering::Relaxed);
48    }
49
50    pub fn record_processed(&self) {
51        self.messages_processed.fetch_add(1, Ordering::Relaxed);
52    }
53
54    pub fn record_failed(&self) {
55        self.messages_failed.fetch_add(1, Ordering::Relaxed);
56    }
57
58    pub fn record_heartbeat(&self) {
59        self.heartbeats.fetch_add(1, Ordering::Relaxed);
60    }
61
62    pub fn record_restart(&self) {
63        self.restart_count.fetch_add(1, Ordering::Relaxed);
64    }
65
66    /// Record LLM usage: token counts and cost (in nano-USD).
67    pub fn record_llm_usage(&self, input_tokens: u64, output_tokens: u64, cost_nano_usd: u64) {
68        self.llm_input_tokens
69            .fetch_add(input_tokens, Ordering::Relaxed);
70        self.llm_output_tokens
71            .fetch_add(output_tokens, Ordering::Relaxed);
72        self.llm_cost_nano_usd
73            .fetch_add(cost_nano_usd, Ordering::Relaxed);
74    }
75
76    /// Snapshot current counters as a serializable struct.
77    pub fn snapshot(&self) -> MetricsSnapshot {
78        MetricsSnapshot {
79            messages_received: self.messages_received.load(Ordering::Relaxed),
80            messages_processed: self.messages_processed.load(Ordering::Relaxed),
81            messages_failed: self.messages_failed.load(Ordering::Relaxed),
82            heartbeats: self.heartbeats.load(Ordering::Relaxed),
83            last_message_at: self.last_message_at.load(Ordering::Relaxed),
84            restart_count: self.restart_count.load(Ordering::Relaxed),
85            llm_input_tokens: self.llm_input_tokens.load(Ordering::Relaxed),
86            llm_output_tokens: self.llm_output_tokens.load(Ordering::Relaxed),
87            llm_cost_usd: self.llm_cost_nano_usd.load(Ordering::Relaxed) as f64 / 1_000_000_000.0,
88        }
89    }
90}
91
92/// A point-in-time snapshot of [`ActorMetrics`] that is `Serialize`.
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct MetricsSnapshot {
95    pub messages_received: u64,
96    pub messages_processed: u64,
97    pub messages_failed: u64,
98    pub heartbeats: u64,
99    /// UNIX seconds of last message.
100    pub last_message_at: u64,
101    /// Number of supervisor restarts.
102    pub restart_count: u64,
103    /// Total LLM input tokens.
104    pub llm_input_tokens: u64,
105    /// Total LLM output tokens.
106    pub llm_output_tokens: u64,
107    /// Total LLM cost in USD.
108    pub llm_cost_usd: f64,
109}