Skip to main content

kmp_application/
observability.rs

1use std::future::Future;
2use std::pin::Pin;
3
4use kmp_domain::PortError;
5use serde::{Deserialize, Serialize};
6
7/// One renderer request for telemetry aligned to the loom's time window.
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct ObservabilityQuery {
10    pub about: Option<String>,
11    pub from_millis: u64,
12    pub to_millis: u64,
13    pub series: Vec<String>,
14    pub max_points: usize,
15}
16
17/// Application seam for persisted local observations and remote telemetry
18/// backends. Renderers compose these values; they do not reinterpret them.
19pub trait ObservabilityQueryPort: Send + Sync {
20    /// Exact series names this reader can resolve without inventing aliases.
21    /// Readers backed by a dynamic remote catalog may leave this empty until
22    /// they can expose that capability explicitly.
23    fn available_series(&self) -> Vec<String> {
24        Vec::new()
25    }
26
27    fn query<'a>(
28        &'a self,
29        query: ObservabilityQuery,
30    ) -> Pin<Box<dyn Future<Output = Result<ObservabilityProjection, PortError>> + Send + 'a>>;
31}
32
33#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
34pub struct ObservabilityProjection {
35    pub contract: String,
36    pub from_millis: u64,
37    pub to_millis: u64,
38    pub series: Vec<ObservabilitySeries>,
39    pub exemplars: Vec<ObservabilityExemplar>,
40    pub missing: Vec<String>,
41    pub truncated: bool,
42}
43
44impl ObservabilityProjection {
45    pub fn empty(query: &ObservabilityQuery) -> Self {
46        Self {
47            contract: "kmp.observability.projection.v1".to_string(),
48            from_millis: query.from_millis,
49            to_millis: query.to_millis,
50            series: Vec::new(),
51            exemplars: Vec::new(),
52            missing: query.series.clone(),
53            truncated: false,
54        }
55    }
56}
57
58#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
59pub struct ObservabilitySeries {
60    pub name: String,
61    pub unit: String,
62    pub scope: String,
63    pub points: Vec<ObservabilityMetricPoint>,
64}
65
66#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
67pub struct ObservabilityMetricPoint {
68    pub at_millis: u64,
69    pub value: f64,
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub exemplar_id: Option<String>,
72}
73
74/// A selectable observation resolving back to an operation and memory root.
75/// It deliberately does not assert that the operation caused nearby memory.
76#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
77pub struct ObservabilityExemplar {
78    pub id: String,
79    pub at_millis: u64,
80    pub operation: String,
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub about: Option<String>,
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub bundle_ref: Option<String>,
85    #[serde(skip_serializing_if = "Option::is_none")]
86    pub revision: Option<u64>,
87    pub attributes: std::collections::BTreeMap<String, String>,
88}