Skip to main content

thunder/server/
metrics.rs

1//! Server metrics as plain atomics (SRV-030) — snapshot-friendly for any
2//! exporter, no metrics-framework dependency. Every series records **after**
3//! a successful socket write, per the writer contract; byte counts
4//! come from the decoder's frame size (in) and the single encoded response
5//! buffer (out) — nothing is ever re-encoded to be measured (SRV-007).
6
7use std::sync::atomic::{AtomicU64, Ordering};
8use std::time::Duration;
9
10/// The seven atomic series of SRV-030. Interior to the listener; consumers
11/// read it through [`MetricsSnapshot`].
12#[derive(Debug, Default)]
13pub(crate) struct Metrics {
14    connections: AtomicU64,
15    commands_total: AtomicU64,
16    commands_error_total: AtomicU64,
17    command_duration_microseconds_total: AtomicU64,
18    frame_bytes_in_total: AtomicU64,
19    frame_bytes_out_total: AtomicU64,
20    slow_commands_total: AtomicU64,
21}
22
23impl Metrics {
24    /// Gauge up: one connection accepted.
25    pub(crate) fn connection_opened(&self) {
26        self.connections.fetch_add(1, Ordering::Relaxed);
27    }
28
29    /// Gauge down: one connection fully drained and closed.
30    pub(crate) fn connection_closed(&self) {
31        self.connections.fetch_sub(1, Ordering::Relaxed);
32    }
33
34    /// Record one completed command — called by the writer task after the
35    /// response left the socket (SRV-030). A zero `slow_threshold`
36    /// disables the slow counter.
37    pub(crate) fn record_command(
38        &self,
39        in_bytes: usize,
40        out_bytes: usize,
41        duration: Duration,
42        is_error: bool,
43        slow_threshold: Duration,
44    ) {
45        self.commands_total.fetch_add(1, Ordering::Relaxed);
46        if is_error {
47            self.commands_error_total.fetch_add(1, Ordering::Relaxed);
48        }
49        self.command_duration_microseconds_total
50            .fetch_add(duration.as_micros() as u64, Ordering::Relaxed);
51        self.frame_bytes_in_total
52            .fetch_add(in_bytes as u64, Ordering::Relaxed);
53        self.frame_bytes_out_total
54            .fetch_add(out_bytes as u64, Ordering::Relaxed);
55        if !slow_threshold.is_zero() && duration >= slow_threshold {
56            self.slow_commands_total.fetch_add(1, Ordering::Relaxed);
57        }
58    }
59
60    /// Record one push frame (SRV-013): only out-bytes — pushes are not
61    /// commands.
62    pub(crate) fn record_push(&self, out_bytes: usize) {
63        self.frame_bytes_out_total
64            .fetch_add(out_bytes as u64, Ordering::Relaxed);
65    }
66
67    /// Point-in-time copy of every series.
68    pub(crate) fn snapshot(&self) -> MetricsSnapshot {
69        MetricsSnapshot {
70            connections: self.connections.load(Ordering::Relaxed),
71            commands_total: self.commands_total.load(Ordering::Relaxed),
72            commands_error_total: self.commands_error_total.load(Ordering::Relaxed),
73            command_duration_microseconds_total: self
74                .command_duration_microseconds_total
75                .load(Ordering::Relaxed),
76            frame_bytes_in_total: self.frame_bytes_in_total.load(Ordering::Relaxed),
77            frame_bytes_out_total: self.frame_bytes_out_total.load(Ordering::Relaxed),
78            slow_commands_total: self.slow_commands_total.load(Ordering::Relaxed),
79        }
80    }
81}
82
83/// One consistent-enough read of the listener's counters (SRV-030),
84/// exporter-agnostic by design.
85#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
86pub struct MetricsSnapshot {
87    /// Currently open connections (gauge).
88    pub connections: u64,
89    /// Responses written, success or error.
90    pub commands_total: u64,
91    /// Error responses written.
92    pub commands_error_total: u64,
93    /// Total dispatch time across all commands, microseconds.
94    pub command_duration_microseconds_total: u64,
95    /// Request bytes as counted by the decoder's length prefix (SRV-007).
96    pub frame_bytes_in_total: u64,
97    /// Response/push bytes as counted from the encoded buffers (SRV-007).
98    pub frame_bytes_out_total: u64,
99    /// Commands slower than the configured threshold (SRV-030).
100    pub slow_commands_total: u64,
101}