Skip to main content

query_runtime/observability/
graph_observation.rs

1//! Measured graph-execution observation.
2//!
3//! These rows record what a graph attempt did on the device. They
4//! carry no predicted or budgeted bytes: the allocator enforces a query's
5//! limit, so a planning-time claim would be an estimate masquerading as a
6//! fact. Keeping only the measurement leaves future capacity decisions with
7//! evidence. No model is stored to calibrate.
8
9use query_planning::{correlation::QueryId, plan::GraphAlgorithm};
10use serde_json::{Value, json};
11
12/// Direct source for one graph attempt's measured device-memory peak.
13///
14/// Consumers must not infer a source from a missing value or an error string.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub enum GraphMeasuredPeakProvenance {
17    /// An exclusive resident-graph cache scope reported its own allocator
18    /// high-water mark after the warm graph execution had retired. This is
19    /// the only physical peak source for work that runs on the cache runtime.
20    /// Work on the admitted attempt runtime does not take this path.
21    CacheScopeHighWater,
22    /// Neither runtime measurement completed. `measured_peak_bytes` is
23    /// unavailable. No synthetic zero stands in for it.
24    Unavailable,
25}
26
27impl GraphMeasuredPeakProvenance {
28    /// Stable label used by bounded public projections.
29    pub const fn as_str(self) -> &'static str {
30        match self {
31            Self::CacheScopeHighWater => "cache_scope_high_water",
32            Self::Unavailable => "unavailable",
33        }
34    }
35}
36
37impl std::fmt::Display for GraphMeasuredPeakProvenance {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        f.write_str(self.as_str())
40    }
41}
42
43/// Engine-owned lifecycle state at the graph executor boundary.
44///
45/// A row exists before the executor is entered, so a missing terminal state is
46/// deliberately fail-closed and is not execution evidence.
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
48pub enum GraphClaimExecutionState {
49    /// The row was recorded but no exact terminal graph boundary was observed.
50    Pending,
51    /// The native graph algorithm executor was entered.
52    AlgorithmStarted,
53}
54
55impl GraphClaimExecutionState {
56    /// Stable label used by bounded public projections.
57    pub const fn as_str(self) -> &'static str {
58        match self {
59            Self::Pending => "pending",
60            Self::AlgorithmStarted => "algorithm_started",
61        }
62    }
63}
64
65impl std::fmt::Display for GraphClaimExecutionState {
66    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        f.write_str(self.as_str())
68    }
69}
70
71/// Fixed-shape measured-peak observation for one graph attempt.
72///
73/// The engine creates one of these before it enters the graph executor and
74/// fills the runtime measurement when that attempt leaves it. It is
75/// diagnostic only: the row cannot mint admission evidence or modify a device
76/// grant. `node_index` is typed executor identity; report consumers should use
77/// `node_id`.
78#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct GraphMeasuredPeakRow {
80    /// Query-wide diagnostic correlation id, when the execution context has
81    /// one. The runtime metrics owner assigns this sequence.
82    pub query_id: Option<QueryId>,
83    /// Stable native graph node id for reports.
84    pub node_id: String,
85    /// Typed graph node identity used to correlate the post-execution sample
86    /// without parsing a report string.
87    pub node_index: usize,
88    /// The graph algorithm that owned this measured peak.
89    pub algorithm: GraphAlgorithm,
90    /// Exact engine state at the graph executor boundary.
91    pub execution_state: GraphClaimExecutionState,
92    /// Measured graph-stage peak bytes when a runtime observation completed.
93    pub measured_peak_bytes: Option<u64>,
94    /// Source of [`Self::measured_peak_bytes`].
95    pub measured_peak_provenance: GraphMeasuredPeakProvenance,
96}
97
98impl GraphMeasuredPeakRow {
99    /// Opens a pending observation for one graph plan node.
100    pub fn pending(
101        query_id: Option<QueryId>,
102        node_id: impl Into<String>,
103        node_index: usize,
104        algorithm: GraphAlgorithm,
105    ) -> Self {
106        Self {
107            query_id,
108            node_id: node_id.into(),
109            node_index,
110            algorithm,
111            execution_state: GraphClaimExecutionState::Pending,
112            measured_peak_bytes: None,
113            measured_peak_provenance: GraphMeasuredPeakProvenance::Unavailable,
114        }
115    }
116
117    /// Records one terminal graph-boundary state. A later observation cannot
118    /// overwrite the first exact boundary.
119    pub(crate) fn record_execution_state(&mut self, execution_state: GraphClaimExecutionState) {
120        if self.execution_state == GraphClaimExecutionState::Pending {
121            self.execution_state = execution_state;
122        }
123    }
124
125    /// Records one terminal graph-boundary state, for tests that build rows
126    /// without an execution.
127    #[cfg(any(test, feature = "test-hooks"))]
128    #[doc(hidden)]
129    pub fn record_execution_state_for_test(&mut self, execution_state: GraphClaimExecutionState) {
130        self.record_execution_state(execution_state);
131    }
132
133    /// Records the one direct peak observation for this graph attempt.
134    ///
135    /// An unavailable source always clears the measured value. A zero
136    /// high-water mark remains an observed zero.
137    pub fn record_measured_peak(
138        &mut self,
139        measured_peak_bytes: Option<u64>,
140        measured_peak_provenance: GraphMeasuredPeakProvenance,
141    ) {
142        match measured_peak_provenance {
143            GraphMeasuredPeakProvenance::Unavailable => self.measured_peak_bytes = None,
144            GraphMeasuredPeakProvenance::CacheScopeHighWater => self.measured_peak_bytes = measured_peak_bytes,
145        }
146        self.measured_peak_provenance = measured_peak_provenance;
147    }
148
149    /// Serializes this measured-peak row as named JSON.
150    pub fn to_json(&self) -> Value {
151        json!({
152            "query_id": self.query_id.as_ref().map(QueryId::as_str),
153            "node_id": self.node_id,
154            "node_index": self.node_index,
155            "algorithm": self.algorithm.as_str(),
156            "execution_state": self.execution_state.as_str(),
157            "measured_peak_bytes": self.measured_peak_bytes,
158            "measured_peak_provenance": self.measured_peak_provenance.as_str(),
159        })
160    }
161}