Skip to main content

saluki_core/runtime/tree/
snapshot.rs

1//! Serialized form of a supervision-tree snapshot.
2//!
3//! These are the wire types: the stable shape that [`SupervisionTreeHandle::snapshot`][super::SupervisionTreeHandle]
4//! produces, the API route serves, and the CLI decodes. They are deliberately separate from the live bookkeeping in
5//! the parent module -- a rename here changes a payload that shipped binaries parse, which is a very different kind of
6//! change from adjusting how the tree is tracked at runtime.
7
8use saluki_common::resource_tracking::ResourceStatsSnapshot;
9use serde::{Deserialize, Serialize};
10
11use crate::runtime::{restart::RestartType, supervisor::AutoShutdown, RestartMode};
12
13/// A point in time, as milliseconds since the Unix epoch.
14///
15/// Serialized as a plain integer. The standard library's own representation of a timestamp serializes as a pair of
16/// fields, which is awkward for a consumer that just wants to render a time.
17#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
18#[serde(transparent)]
19pub struct UnixMillis(pub u64);
20
21/// A point-in-time view of a supervision tree.
22#[derive(Clone, Debug, Deserialize, Serialize)]
23pub struct TreeSnapshot {
24    /// When the snapshot was taken.
25    pub captured_at: UnixMillis,
26
27    /// Whether allocations are being tracked at all.
28    ///
29    /// When false, every byte count in the snapshot reads zero because nothing is measuring, rather than because
30    /// nothing has been allocated. Distinguishing the two matters: the tracking allocator has to be installed as the
31    /// process's global allocator, which not every embedding does.
32    pub resource_tracking_enabled: bool,
33
34    /// Aggregate counts across the whole tree.
35    pub totals: TreeTotals,
36
37    /// The supervisor the snapshot was taken from, and everything beneath it.
38    pub root: NodeSnapshot,
39}
40
41/// Aggregate counts across a whole [`TreeSnapshot`].
42#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)]
43pub struct TreeTotals {
44    /// Number of supervisors in the tree.
45    pub supervisors: usize,
46
47    /// Number of leaf workers in the tree.
48    pub workers: usize,
49
50    /// Number of nodes currently running.
51    pub running: usize,
52
53    /// Number of nodes that ran and have since exited without being restarted.
54    pub exited: usize,
55
56    /// Number of nodes that are declared but have never run.
57    pub registered: usize,
58
59    /// Total restarts across every node in the tree.
60    pub restarts: u64,
61
62    /// Total live bytes across every distinct resource group in the tree.
63    ///
64    /// Summed over groups rather than over nodes: several nodes can share one group, and their usage is one figure
65    /// rather than one per node.
66    pub live_bytes: u64,
67
68    /// Total CPU time across every distinct resource group in the tree, in nanoseconds.
69    pub cpu_time_nanos: u64,
70
71    /// Depth of the deepest node, counting the root as 1.
72    pub max_depth: usize,
73}
74
75/// Whether a node supervises other nodes or performs work itself.
76#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
77#[serde(rename_all = "snake_case")]
78pub enum NodeKind {
79    /// A supervisor, which manages other nodes.
80    Supervisor,
81
82    /// A worker, which performs work itself.
83    ///
84    /// The children of a worker, if any, are the children that it spawned into its own scope. The children can also
85    /// include a supervisor that the worker drives inside its own future.
86    Worker,
87}
88
89/// Where a node is in its lifecycle.
90#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
91#[serde(rename_all = "snake_case")]
92pub enum NodeState {
93    /// Declared, but not currently running.
94    ///
95    /// Either it has never run, or -- for a supervisor being restarted -- it has stopped and its next generation has
96    /// not yet started.
97    Registered,
98
99    /// Currently running.
100    Running,
101
102    /// Ran, exited, and was not restarted.
103    Exited,
104}
105
106/// One node -- a supervisor or a worker -- in a [`TreeSnapshot`].
107#[derive(Clone, Debug, Deserialize, Serialize)]
108pub struct NodeSnapshot {
109    /// The node's bare name, as registered with its supervisor.
110    pub name: String,
111
112    /// Whether the node is a supervisor or a worker.
113    pub kind: NodeKind,
114
115    /// The node's fully qualified, dot-scoped process name. `None` if the node has never run.
116    pub process_name: Option<String>,
117
118    /// Identifier of the node's most recent process. `None` if the node has never run.
119    ///
120    /// A restart gives the node a new process, and so a new identifier. A node that has stopped keeps the identifier
121    /// it last ran under, which is what `state` is for: this says what the node ran as, and `state` says whether it
122    /// still is.
123    pub process_id: Option<u64>,
124
125    /// Where the node is in its lifecycle.
126    pub state: NodeState,
127
128    /// The node's restart policy.
129    pub restart: RestartType,
130
131    /// Whether the node's termination can drive its supervisor to shut down.
132    pub significant: bool,
133
134    /// When the node first became part of the tree.
135    ///
136    /// Constant across restarts, so the difference between this and `started_at` is the time the node has spent not
137    /// running since it was created.
138    pub created_at: UnixMillis,
139
140    /// When the node's most recent process started. `None` if the node has never run.
141    pub started_at: Option<UnixMillis>,
142
143    /// How long the node's current process has been running, in milliseconds. `None` unless it is running.
144    pub uptime_ms: Option<u64>,
145
146    /// How many times the node has been restarted since it was created.
147    ///
148    /// Counts every restart that gave the node a new process, whether it was restarted on its own account or brought
149    /// back as part of its supervisor restarting -- either by a group restart or by the supervisor itself being
150    /// restarted from above. Only statically declared nodes accumulate this across a supervisor restart, since only
151    /// they have an identity that survives one; a dynamically spawned node is never restored.
152    pub restart_count: u32,
153
154    /// When the node exited without being restarted. `None` unless it has exited.
155    pub exited_at: Option<UnixMillis>,
156
157    /// The resource group the node's allocations are attributed to. `None` if the node has never run.
158    ///
159    /// For a supervisor this is its own group. For a worker it is its supervisor's, since a worker inherits its
160    /// supervisor's group rather than owning one.
161    pub resource_group: Option<String>,
162
163    /// Resource usage attributed to this node.
164    ///
165    /// Populated for a supervisor, which owns a resource group covering itself and its workers. Always absent for a
166    /// worker, whose usage is counted against the supervisor named by `resource_group`.
167    #[serde(default, skip_serializing_if = "Option::is_none")]
168    pub resources: Option<ResourceUsage>,
169
170    /// How the node supervises its children. Absent for a worker.
171    #[serde(default, skip_serializing_if = "Option::is_none")]
172    pub supervision: Option<SupervisionSettings>,
173
174    /// The node's children.
175    ///
176    /// For a worker, this holds the children that it spawned into its own scope, and any supervisor that it drives.
177    /// For most workers, it is empty.
178    pub children: Vec<NodeSnapshot>,
179}
180
181/// Cumulative resource usage for one resource group.
182///
183/// Counts are since the process started. Both allocation counts and CPU time depend on process-wide facilities that
184/// may not be available: see [`TreeSnapshot::resource_tracking_enabled`].
185#[derive(Clone, Debug, Default, Deserialize, Serialize)]
186pub struct ResourceUsage {
187    /// Bytes allocated.
188    pub allocated_bytes: u64,
189
190    /// Objects allocated.
191    pub allocated_objects: u64,
192
193    /// Bytes deallocated.
194    pub deallocated_bytes: u64,
195
196    /// Objects deallocated.
197    pub deallocated_objects: u64,
198
199    /// Bytes allocated and not yet deallocated.
200    pub live_bytes: u64,
201
202    /// Objects allocated and not yet deallocated.
203    pub live_objects: u64,
204
205    /// CPU time consumed, in nanoseconds.
206    ///
207    /// Always zero where per-thread CPU time is unavailable.
208    pub cpu_time_nanos: u64,
209}
210
211impl From<&ResourceStatsSnapshot> for ResourceUsage {
212    fn from(stats: &ResourceStatsSnapshot) -> Self {
213        Self {
214            allocated_bytes: stats.allocated_bytes as u64,
215            allocated_objects: stats.allocated_objects as u64,
216            deallocated_bytes: stats.deallocated_bytes as u64,
217            deallocated_objects: stats.deallocated_objects as u64,
218            live_bytes: stats.live_bytes() as u64,
219            live_objects: stats.live_objects() as u64,
220            cpu_time_nanos: stats.cpu_time_nanos,
221        }
222    }
223}
224
225/// How a supervisor supervises its children, and how it has fared.
226#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
227pub struct SupervisionSettings {
228    /// Whether a failing child is restarted alone or together with its siblings.
229    pub restart_mode: RestartMode,
230
231    /// How many restarts the supervisor tolerates within `restart_period_ms` before giving up.
232    pub restart_intensity: usize,
233
234    /// The window over which `restart_intensity` is measured, in milliseconds.
235    pub restart_period_ms: u64,
236
237    /// Whether the termination of a significant child drives the supervisor to shut down.
238    pub auto_shutdown: AutoShutdown,
239
240    /// Worker threads on the supervisor's own runtime. `None` if it runs on its parent's runtime.
241    pub dedicated_threads: Option<usize>,
242
243    /// How many child restarts the supervisor has performed, across all of its own generations.
244    ///
245    /// A group restart counts once here however many children it brought back, which is what distinguishes a
246    /// supervisor restarting its whole group repeatedly from a single child restarting repeatedly.
247    pub restarts_performed: u64,
248
249    /// How many times the supervisor has started running.
250    pub generation: u64,
251}