Skip to main content

gregg_protocol/
snapshot.rs

1//! Snapshot and identity wire types.
2
3use serde::{Deserialize, Serialize};
4
5use crate::ValidationViolation;
6
7/// Top-level daemon snapshot returned by the status endpoint.
8///
9/// Every numeric field uses raw units. CPU and memory percentages are reported
10/// in the closed interval `0.0..=100.0`. Bytes are unsigned 64-bit counts.
11/// `observed_at_unix_ms` is the Unix epoch in milliseconds at which the
12/// underlying counters were sampled.
13///
14/// CPU percentage values are derived from sampling-interval deltas, not from
15/// instantaneous single reads, so a freshly started daemon may legitimately
16/// report a snapshot whose CPU usage is still unknown. Such snapshots surface
17/// through the [`HealthResponse`](crate::HealthResponse) instead of through
18/// this endpoint.
19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
20#[serde(rename_all = "snake_case")]
21pub struct StatusSnapshot {
22    /// Schema major version. Must equal
23    /// [`crate::SCHEMA_VERSION_V1`] for this endpoint.
24    pub schema_version: u16,
25    /// Unix epoch in milliseconds at which the snapshot was produced.
26    pub observed_at_unix_ms: u64,
27    /// Sampling cadence in milliseconds used to derive percentage metrics.
28    pub sample_interval_ms: u64,
29    /// Per-metric capability flags.
30    pub capabilities: MetricCapabilities,
31    /// Stable identity fields reported separately so clients can degrade by
32    /// width priority.
33    pub system: SystemIdentity,
34    /// CPU utilization, with optional Linux aggregate I/O wait.
35    pub cpu: CpuMetrics,
36    /// One-, five-, and fifteen-minute load averages.
37    pub load: LoadAverage,
38    /// Physical memory utilization.
39    pub memory: MemoryMetrics,
40    /// Swap utilization.
41    pub swap: SwapMetrics,
42}
43
44/// Per-metric capability flags.
45///
46/// A `false` flag means the metric is **unsupported on this platform**.
47/// Servers report `None` for unsupported values; clients render those values
48/// as absent rather than as zero.
49///
50/// A `true` flag means the metric is supported; the corresponding value must
51/// still be present in a `Ready` snapshot but may be absent during warmup.
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
53#[serde(rename_all = "snake_case")]
54pub struct MetricCapabilities {
55    /// Whether aggregate CPU I/O wait is reported.
56    ///
57    /// `false` on macOS, where no equivalent accounting state exists.
58    /// `true` on Linux.
59    pub cpu_iowait: bool,
60}
61
62/// Stable identity fields. Each field is transported separately so the TUI
63/// can degrade by width priority without parsing a combined string. Empty
64/// values are permitted when the source cannot provide an optional identity
65/// field.
66#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
67#[serde(rename_all = "snake_case")]
68pub struct SystemIdentity {
69    /// User-facing system name (typically a configured alias). Empty values
70    /// are permitted when the source cannot provide this optional identity.
71    pub name: String,
72    /// Network hostname as reported by the operating system. Empty values are
73    /// permitted when the source cannot provide this optional identity.
74    pub hostname: String,
75    /// Operating-system family name (e.g. `"linux"`, `"macos"`).
76    pub os_name: String,
77    /// Operating-system version string.
78    pub os_version: String,
79    /// Kernel name (e.g. `"Linux"`, `"Darwin"`).
80    pub kernel_name: String,
81    /// Kernel release string.
82    pub kernel_release: String,
83    /// Target architecture (e.g. `"x86_64"`, `"aarch64"`).
84    pub architecture: String,
85}
86
87/// CPU utilization snapshot.
88///
89/// `usage_pct` is total CPU busy over the most recent sampling interval.
90/// `iowait_pct` is the aggregate CPU I/O-wait time over the same interval;
91/// it is `Some(_)` only when [`MetricCapabilities::cpu_iowait`] is `true`.
92#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
93#[serde(rename_all = "snake_case")]
94pub struct CpuMetrics {
95    /// Number of logical CPU cores available to the kernel.
96    pub logical_cores: u32,
97    /// Total CPU busy percentage, `0.0..=100.0`, derived from delta samples.
98    pub usage_pct: f32,
99    /// Aggregate CPU I/O-wait percentage, `0.0..=100.0`. `None` when the
100    /// platform does not expose this state.
101    pub iowait_pct: Option<f32>,
102}
103
104/// One-, five-, and fifteen-minute load averages as reported by the
105/// operating system.
106#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
107#[serde(rename_all = "snake_case")]
108pub struct LoadAverage {
109    /// One-minute load average.
110    pub one: f32,
111    /// Five-minute load average.
112    pub five: f32,
113    /// Fifteen-minute load average.
114    pub fifteen: f32,
115}
116
117/// Physical memory utilization.
118///
119/// `usage_pct` is computed as `100.0 * used_bytes / total_bytes` and clamped
120/// to the closed interval `0.0..=100.0`.
121#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
122#[serde(rename_all = "snake_case")]
123pub struct MemoryMetrics {
124    /// Used physical memory in bytes. Never exceeds `total_bytes`.
125    pub used_bytes: u64,
126    /// Total physical memory in bytes.
127    pub total_bytes: u64,
128    /// Memory utilization percentage, `0.0..=100.0`.
129    pub usage_pct: f32,
130}
131
132/// Swap utilization.
133///
134/// When `total_bytes` is zero, `usage_pct` is `0.0` rather than `NaN`.
135#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
136#[serde(rename_all = "snake_case")]
137pub struct SwapMetrics {
138    /// Used swap in bytes. Never exceeds `total_bytes`.
139    pub used_bytes: u64,
140    /// Total swap in bytes.
141    pub total_bytes: u64,
142    /// Swap utilization percentage, `0.0..=100.0`. Zero when
143    /// `total_bytes == 0`.
144    pub usage_pct: f32,
145}
146
147impl StatusSnapshot {
148    /// Validate that every field satisfies the version-1 protocol invariants.
149    ///
150    /// The returned [`ValidationViolation`] list is structured so callers can
151    /// log individual fields and decide whether to reject the snapshot,
152    /// surface it as a warning, or fall back to a warming-up health response.
153    ///
154    /// # Invariants
155    ///
156    /// - `schema_version == SCHEMA_VERSION_V1`.
157    /// - `observed_at_unix_ms > 0` and `sample_interval_ms > 0`.
158    /// - `cpu.logical_cores > 0`.
159    /// - All percentages are finite and in `0.0..=100.0`.
160    /// - `used_bytes <= total_bytes` for memory and swap.
161    /// - When `total_bytes == 0`, the corresponding `usage_pct` is `0.0`.
162    /// - `iowait_pct` is `None` exactly when `cpu_iowait` capability is
163    ///   `false`. A `true` capability with `None` is rejected because a
164    ///   `Ready` snapshot must report every supported metric.
165    pub fn validate(&self) -> Result<(), Vec<ValidationViolation>> {
166        crate::validate::validate(self)
167    }
168}