prospero_core/metrics.rs
1//! Process-lifetime operational counters for prosperod.
2//!
3//! ADR-0004 specifies a failed `Store.append` is "logged **and metered**", and
4//! `caliband::stream` documents an unknown frame as something the caller should
5//! "log **+ count**". These counters meter both, plus a little surrounding
6//! signal (events appended, repos polled, active attaches), so operators aren't
7//! blind to persistence loss, protocol drift, or attach load. They are exposed
8//! over the API via [`MetricsSnapshot`].
9
10use std::sync::atomic::{AtomicU64, Ordering};
11
12use serde::Serialize;
13
14/// Monotonic operational counters, incremented from the hot paths. Cheap,
15/// lock-free, and shared (behind an `Arc`) across the manager and its tasks.
16#[derive(Debug, Default)]
17pub struct Metrics {
18 events_appended: AtomicU64,
19 append_failures: AtomicU64,
20 unknown_frames: AtomicU64,
21 repos_polled: AtomicU64,
22}
23
24impl Metrics {
25 /// A `Store.append` succeeded.
26 pub(crate) fn record_append_ok(&self) {
27 self.events_appended.fetch_add(1, Ordering::Relaxed);
28 }
29
30 /// A `Store.append` failed (durability loss; see ADR-0004).
31 pub(crate) fn record_append_failure(&self) {
32 self.append_failures.fetch_add(1, Ordering::Relaxed);
33 }
34
35 /// An unrecognized caliban stream frame was seen (protocol drift).
36 pub(crate) fn record_unknown_frame(&self) {
37 self.unknown_frames.fetch_add(1, Ordering::Relaxed);
38 }
39
40 /// One repo poll cycle ran.
41 pub(crate) fn record_repo_poll(&self) {
42 self.repos_polled.fetch_add(1, Ordering::Relaxed);
43 }
44
45 /// Read the counters into a serializable snapshot. `active_attaches` is a
46 /// gauge supplied by the caller (the current attach-task count).
47 pub fn snapshot(&self, active_attaches: u64) -> MetricsSnapshot {
48 MetricsSnapshot {
49 events_appended: self.events_appended.load(Ordering::Relaxed),
50 append_failures: self.append_failures.load(Ordering::Relaxed),
51 unknown_frames: self.unknown_frames.load(Ordering::Relaxed),
52 repos_polled: self.repos_polled.load(Ordering::Relaxed),
53 active_attaches,
54 }
55 }
56}
57
58/// A point-in-time snapshot of prosperod's operational counters, returned by
59/// `GET /api/metrics`.
60#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
61pub struct MetricsSnapshot {
62 /// Events successfully appended to the durable store.
63 pub events_appended: u64,
64 /// `Store.append` failures (durability loss).
65 pub append_failures: u64,
66 /// Unrecognized caliban stream frames (protocol drift).
67 pub unknown_frames: u64,
68 /// Repo poll cycles run.
69 pub repos_polled: u64,
70 /// Attach tasks currently running (a gauge, not a counter).
71 pub active_attaches: u64,
72}