Skip to main content

inspector_core/
models.rs

1use serde::{Deserialize, Serialize};
2
3use super::schema_version::SCHEMA_VERSION;
4
5/// Versioned observation envelope. Every collector output uses this.
6/// Missing is `None`, never zero. Timeouts are errors, never empty `Ok`.
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Observation<T> {
9    pub schema_version: u16,
10    pub observation_id: String,
11    /// Wall clock, for correlation with logs. NOT for duration math.
12    pub captured_at_unix_ms: u64,
13    /// Milliseconds since collector start (monotonic). Use for deltas.
14    pub monotonic_elapsed_ms: u64,
15    pub collection_interval_ms: Option<u64>,
16    pub source: SourceId,
17    pub completeness: Completeness,
18    pub data: T,
19}
20
21impl<T> Observation<T> {
22    pub fn new(
23        source: SourceId,
24        interval_ms: Option<u64>,
25        monotonic_elapsed_ms: u64,
26        data: T,
27        completeness: Completeness,
28    ) -> Self {
29        Self {
30            schema_version: SCHEMA_VERSION,
31            observation_id: uuid::Uuid::new_v4().to_string(),
32            captured_at_unix_ms: chrono::Utc::now().timestamp_millis() as u64,
33            monotonic_elapsed_ms,
34            collection_interval_ms: interval_ms,
35            source,
36            completeness,
37            data,
38        }
39    }
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct SourceId {
44    pub console_addr: String,
45    pub service_name: Option<String>,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct Completeness {
50    pub partial: bool,
51    pub missing_fields: Vec<String>,
52    pub warnings: Vec<String>,
53}
54
55impl Completeness {
56    pub fn complete() -> Self {
57        Self {
58            partial: false,
59            missing_fields: Vec::new(),
60            warnings: Vec::new(),
61        }
62    }
63
64    pub fn partial(missing_fields: Vec<String>, warnings: Vec<String>) -> Self {
65        Self {
66            partial: true,
67            missing_fields,
68            warnings,
69        }
70    }
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct RuntimeOverview {
75    pub service_name: Option<String>,
76    pub console_addr: String,
77    pub active_tasks: Option<u64>,
78    pub completed_tasks: Option<u64>,
79    pub tasks_truncated: bool,
80    pub metric_names: Vec<String>,
81}
82
83/// Metric semantics matter: counters reset, gauges fluctuate, histograms sample.
84#[derive(Debug, Clone, Serialize, Deserialize)]
85#[serde(tag = "kind")]
86pub enum MetricKind {
87    Counter {
88        total: f64,
89        reset_detected: bool,
90    },
91    Gauge {
92        value: f64,
93    },
94    Histogram {
95        count: u64,
96        sum: f64,
97        p50: Option<f64>,
98        p95: Option<f64>,
99        p99: Option<f64>,
100    },
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct MetricObservation {
105    pub name: String,
106    pub kind: MetricKind,
107    pub unit: String,
108    pub observed_at_unix_ms: u64,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct TaskRecord {
113    pub task_id: u64,
114    pub name: Option<String>,
115    pub polls: Option<u64>,
116    pub wakes: Option<u64>,
117    pub self_wakes: Option<u64>,
118    pub busy_ns: Option<u64>,
119    pub scheduled_ns: Option<u64>,
120    pub state: Option<String>,
121    pub location: Option<String>,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct ResourceRecord {
126    pub resource_id: u64,
127    pub concrete_type: Option<String>,
128    pub kind: Option<String>,
129    pub attributes: Vec<(String, String)>,
130    pub location: Option<String>,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct Snapshot {
135    pub overview: RuntimeOverview,
136    pub tasks: Vec<TaskRecord>,
137    pub resources: Vec<ResourceRecord>,
138    pub metrics: Vec<MetricObservation>,
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize)]
142pub struct SnapshotDelta {
143    pub base_id: String,
144    pub current_id: String,
145    pub latency_delta_ratio: Option<f64>,
146    pub task_growth: Option<i64>,
147    pub unreliable: bool,
148    pub reason: Option<String>,
149    pub notes: Vec<String>,
150}
151
152#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
153pub enum FindingStatus {
154    Observed,
155    Hypothesis,
156    Verified,
157    NoIssue,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct Diagnosis {
162    pub status: FindingStatus,
163    pub finding: String,
164    pub hypothesis: Option<String>,
165    /// 0.0..=1.0. Never 1.0 unless `Verified` via controlled A/B.
166    pub confidence: f32,
167    /// Observation IDs backing this finding. Empty = invalid.
168    pub evidence: Vec<String>,
169    pub verification: Option<String>,
170}
171
172impl Diagnosis {
173    pub fn is_valid(&self) -> bool {
174        !self.evidence.is_empty() && (0.0..=1.0).contains(&self.confidence)
175    }
176}