ftui_runtime/telemetry_schema.rs
1#![forbid(unsafe_code)]
2
3//! Canonical telemetry schema for the FrankenTUI runtime (bd-17ar5).
4//!
5//! Defines the unified vocabulary of tracing targets, event names, metric
6//! names, and structured field contracts used across the runtime, effect
7//! system, subscription manager, and harness infrastructure.
8//!
9//! # Purpose
10//!
11//! Without a shared schema, telemetry becomes fragmented across modules.
12//! This module provides:
13//! - Named constants for tracing targets (e.g., `TARGET_RUNTIME`)
14//! - Canonical event names for structured log correlation
15//! - Metric name constants for counter/gauge telemetry
16//! - A schema manifest for validation and documentation
17//!
18//! # Usage
19//!
20//! ```ignore
21//! use ftui_runtime::telemetry_schema::{TARGET_RUNTIME, event};
22//!
23//! tracing::info!(
24//! target: TARGET_RUNTIME,
25//! event = event::RUNTIME_STARTUP,
26//! "runtime started"
27//! );
28//! ```
29
30// ============================================================================
31// Tracing targets
32// ============================================================================
33
34/// Runtime lifecycle events (startup, shutdown, lane resolution).
35pub const TARGET_RUNTIME: &str = "ftui.runtime";
36
37/// Effect/command execution and queue telemetry.
38pub const TARGET_EFFECT: &str = "ftui.effect";
39
40/// Process subscription lifecycle (spawn, exit, restart).
41pub const TARGET_PROCESS: &str = "ftui.process";
42
43/// Resize coalescer decisions.
44pub const TARGET_RESIZE: &str = "ftui.decision.resize";
45
46/// Value-of-information sampling decisions.
47pub const TARGET_VOI: &str = "ftui.voi";
48
49/// Bayesian online change-point detection.
50pub const TARGET_BOCPD: &str = "ftui.bocpd";
51
52/// E-process throttle decisions.
53pub const TARGET_EPROCESS: &str = "ftui.eprocess";
54
55// ============================================================================
56// Canonical event names
57// ============================================================================
58
59/// Structured event names emitted by the runtime.
60///
61/// These are the `event` field values in structured logs. Using constants
62/// ensures CI can verify event coverage and dashboards can filter reliably.
63pub mod event {
64 /// Program startup with lane and rollout policy.
65 pub const RUNTIME_STARTUP: &str = "runtime.startup";
66
67 /// Effect queue shutdown completed (fast or slow path).
68 pub const EFFECT_QUEUE_SHUTDOWN: &str = "effect_queue.shutdown";
69
70 /// Spawn executor shutdown completed.
71 pub const SPAWN_EXECUTOR_SHUTDOWN: &str = "spawn_executor.shutdown";
72
73 /// Subscription manager stop_all completed.
74 pub const SUBSCRIPTION_STOP_ALL: &str = "subscription.stop_all";
75
76 /// Individual subscription stopped.
77 pub const SUBSCRIPTION_STOP: &str = "subscription.stop";
78
79 /// Command effect started/completed.
80 pub const EFFECT_COMMAND: &str = "effect.command";
81
82 /// Subscription effect started/stopped.
83 pub const EFFECT_SUBSCRIPTION: &str = "effect.subscription";
84
85 /// Effect queue task dropped (backpressure or post-shutdown).
86 pub const QUEUE_DROP: &str = "effect_queue.drop";
87
88 /// Effect timeout exceeded deadline.
89 pub const EFFECT_TIMEOUT: &str = "effect.timeout";
90
91 /// Effect panicked during execution.
92 pub const EFFECT_PANIC: &str = "effect.panic";
93}
94
95// ============================================================================
96// Metric names
97// ============================================================================
98
99/// Monotonic counter and gauge metric names.
100///
101/// These correspond to the `AtomicU64` counters in `effect_system.rs` and
102/// are the canonical names for dashboards and CI gates.
103pub mod metric {
104 /// Total command effects executed.
105 pub const EFFECTS_COMMAND_TOTAL: &str = "effects_command_total";
106
107 /// Total subscription effects started.
108 pub const EFFECTS_SUBSCRIPTION_TOTAL: &str = "effects_subscription_total";
109
110 /// Total effects executed (command + subscription).
111 pub const EFFECTS_EXECUTED_TOTAL: &str = "effects_executed_total";
112
113 /// Total tasks enqueued to the effect queue.
114 pub const EFFECTS_QUEUE_ENQUEUED: &str = "effects_queue_enqueued";
115
116 /// Total tasks processed by the effect queue.
117 pub const EFFECTS_QUEUE_PROCESSED: &str = "effects_queue_processed";
118
119 /// Total tasks dropped (backpressure or shutdown).
120 pub const EFFECTS_QUEUE_DROPPED: &str = "effects_queue_dropped";
121
122 /// Maximum queue depth observed (ratchet-only).
123 pub const EFFECTS_QUEUE_HIGH_WATER: &str = "effects_queue_high_water";
124
125 /// Current in-flight tasks (enqueued - processed - dropped).
126 pub const EFFECTS_QUEUE_IN_FLIGHT: &str = "effects_queue_in_flight";
127}
128
129// ============================================================================
130// Structured field contracts
131// ============================================================================
132
133/// Common structured field names used in tracing spans and events.
134///
135/// Using named constants prevents typos and enables grep-based schema auditing.
136pub mod field {
137 /// Elapsed time in microseconds.
138 pub const ELAPSED_US: &str = "elapsed_us";
139
140 /// Duration in microseconds (for effect timing).
141 pub const DURATION_US: &str = "duration_us";
142
143 /// Subscription or task identifier.
144 pub const SUB_ID: &str = "sub_id";
145
146 /// Command type label.
147 pub const COMMAND_TYPE: &str = "command_type";
148
149 /// Requested runtime lane (before resolution).
150 pub const REQUESTED_LANE: &str = "requested_lane";
151
152 /// Resolved runtime lane (after fallback).
153 pub const RESOLVED_LANE: &str = "resolved_lane";
154
155 /// Rollout policy label.
156 pub const ROLLOUT_POLICY: &str = "rollout_policy";
157
158 /// Timeout in milliseconds.
159 pub const TIMEOUT_MS: &str = "timeout_ms";
160
161 /// Number of pending handles at shutdown.
162 pub const PENDING_HANDLES: &str = "pending_handles";
163
164 /// Drop reason (backpressure, post_shutdown, etc.).
165 pub const REASON: &str = "reason";
166}
167
168// ============================================================================
169// Schema manifest
170// ============================================================================
171
172/// Schema version for forward compatibility.
173pub const SCHEMA_VERSION: &str = "1.0.0";
174
175/// Complete list of registered tracing targets.
176pub const ALL_TARGETS: &[&str] = &[
177 TARGET_RUNTIME,
178 TARGET_EFFECT,
179 TARGET_PROCESS,
180 TARGET_RESIZE,
181 TARGET_VOI,
182 TARGET_BOCPD,
183 TARGET_EPROCESS,
184];
185
186/// Complete list of registered event names.
187pub const ALL_EVENTS: &[&str] = &[
188 event::RUNTIME_STARTUP,
189 event::EFFECT_QUEUE_SHUTDOWN,
190 event::SPAWN_EXECUTOR_SHUTDOWN,
191 event::SUBSCRIPTION_STOP_ALL,
192 event::SUBSCRIPTION_STOP,
193 event::EFFECT_COMMAND,
194 event::EFFECT_SUBSCRIPTION,
195 event::QUEUE_DROP,
196 event::EFFECT_TIMEOUT,
197 event::EFFECT_PANIC,
198];
199
200/// Complete list of registered metric names.
201pub const ALL_METRICS: &[&str] = &[
202 metric::EFFECTS_COMMAND_TOTAL,
203 metric::EFFECTS_SUBSCRIPTION_TOTAL,
204 metric::EFFECTS_EXECUTED_TOTAL,
205 metric::EFFECTS_QUEUE_ENQUEUED,
206 metric::EFFECTS_QUEUE_PROCESSED,
207 metric::EFFECTS_QUEUE_DROPPED,
208 metric::EFFECTS_QUEUE_HIGH_WATER,
209 metric::EFFECTS_QUEUE_IN_FLIGHT,
210];
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[test]
217 fn schema_version_is_semver() {
218 let parts: Vec<&str> = SCHEMA_VERSION.split('.').collect();
219 assert_eq!(parts.len(), 3, "schema version must be semver");
220 for part in &parts {
221 assert!(
222 part.parse::<u32>().is_ok(),
223 "each semver component must be a number: {part}"
224 );
225 }
226 }
227
228 #[test]
229 fn all_targets_are_dotted() {
230 for target in ALL_TARGETS {
231 assert!(
232 target.contains('.'),
233 "target should be dotted namespace: {target}"
234 );
235 assert!(
236 target.starts_with("ftui."),
237 "target should start with ftui.: {target}"
238 );
239 }
240 }
241
242 #[test]
243 fn all_events_have_dotted_names() {
244 for event in ALL_EVENTS {
245 assert!(event.contains('.'), "event name should be dotted: {event}");
246 }
247 }
248
249 #[test]
250 fn all_metrics_are_snake_case() {
251 for metric in ALL_METRICS {
252 assert!(
253 metric.chars().all(|c| c.is_ascii_lowercase() || c == '_'),
254 "metric name should be snake_case: {metric}"
255 );
256 }
257 }
258
259 #[test]
260 fn no_duplicate_targets() {
261 let mut seen = std::collections::HashSet::new();
262 for target in ALL_TARGETS {
263 assert!(seen.insert(target), "duplicate target: {target}");
264 }
265 }
266
267 #[test]
268 fn no_duplicate_events() {
269 let mut seen = std::collections::HashSet::new();
270 for event in ALL_EVENTS {
271 assert!(seen.insert(event), "duplicate event: {event}");
272 }
273 }
274
275 #[test]
276 fn no_duplicate_metrics() {
277 let mut seen = std::collections::HashSet::new();
278 for metric in ALL_METRICS {
279 assert!(seen.insert(metric), "duplicate metric: {metric}");
280 }
281 }
282}