Skip to main content

query_runtime/metrics/
graph.rs

1use query_planning::{
2    correlation::QueryId,
3    plan::{GraphAlgorithm, NodeId},
4};
5
6use super::QueryMetrics;
7use crate::observability::graph_observation::{
8    GraphClaimExecutionState, GraphMeasuredPeakProvenance, GraphMeasuredPeakRow,
9};
10
11/// cuGraph algorithm runtime metric row.
12#[derive(Debug, Clone, Default, PartialEq, Eq)]
13pub struct GraphAlgorithmRuntimeMetrics {
14    /// Query-wide diagnostic correlation id, when assigned.
15    pub query_id: Option<QueryId>,
16    /// Native graph algorithm node id.
17    pub node_id: String,
18    /// Stable graph algorithm label.
19    pub algorithm: String,
20    /// Runtime outcome label.
21    pub runtime_outcome: String,
22    /// Input boundary label.
23    pub input_boundary: String,
24    /// Output boundary label.
25    pub output_boundary: String,
26    /// Graph construction policy label.
27    pub construction_policy: String,
28    /// Source vertex column name.
29    pub source_column: String,
30    /// Destination vertex column name.
31    pub destination_column: String,
32    /// Optional edge-weight column name.
33    pub weight_column: Option<String>,
34    /// Whether the graph was treated as directed.
35    pub directed: bool,
36    /// Whether cuGraph renumbering was enabled.
37    pub renumber: bool,
38    /// Stable serialized algorithm parameters.
39    pub algorithm_parameters: String,
40    /// Stable serialized algorithm metadata.
41    pub algorithm_metadata: String,
42    /// Input row count.
43    pub input_rows: u64,
44    /// Input column count.
45    pub input_columns: u64,
46    /// Output row count.
47    pub output_rows: u64,
48    /// Output column count.
49    pub output_columns: u64,
50    /// Output device bytes when known.
51    pub output_device_bytes: Option<u64>,
52    /// Graph-stage reservation high-water bytes, or CUDA async-pool high-water
53    /// bytes when reservation evidence is absent. Successful graph rows that do
54    /// no graph allocation report `Some(0)`.
55    pub graph_peak_bytes: Option<u64>,
56    /// Runtime elapsed nanoseconds.
57    pub elapsed_ns: u64,
58    /// GPU-resident input handoffs performed for this algorithm.
59    pub gpu_resident_input_handoffs: u64,
60    /// Host-to-device uploads performed for this algorithm.
61    pub host_to_device_uploads: u64,
62    /// Device-to-host materializations performed for this algorithm.
63    pub device_to_host_materializations: u64,
64}
65
66impl QueryMetrics {
67    /// Appends one graph algorithm runtime row.
68    pub fn record_graph_algorithm_runtime(&mut self, mut metrics: GraphAlgorithmRuntimeMetrics) {
69        if metrics.query_id.is_none() {
70            metrics.query_id = self.inherited_query_id();
71        }
72        self.graph_algorithm_runtime_rows.push(metrics);
73    }
74
75    /// Opens a pending measured-peak observation row for one graph executor
76    /// invocation.
77    ///
78    /// The caller supplies the plan [`NodeId`] that owns this graph node. The
79    /// completion sample then correlates by typed node identity without parsing
80    /// the rendered `node_id` string.
81    pub fn record_graph_claim_calibration(&mut self, node_id: NodeId, algorithm: GraphAlgorithm) {
82        self.graph_claim_calibration_rows.push(GraphMeasuredPeakRow::pending(
83            self.inherited_query_id(),
84            node_id.to_string(),
85            node_id.index(),
86            algorithm,
87        ));
88    }
89
90    /// Completes the most recent pending measured-peak row for the exact
91    /// graph node/algorithm invocation.
92    ///
93    /// The node index and enum are carried directly from the executor. No
94    /// peak provenance is inferred from a rendered error, a label, or a
95    /// missing numeric value.
96    pub fn record_graph_claim_calibration_measurement(
97        &mut self,
98        node_id: NodeId,
99        algorithm: GraphAlgorithm,
100        measured_peak_bytes: Option<u64>,
101        measured_peak_provenance: GraphMeasuredPeakProvenance,
102    ) {
103        // The query peak is a total; it must not depend on finding the row.
104        self.max_task_observed_peak_bytes = self.max_task_observed_peak_bytes.max(measured_peak_bytes);
105        let Some(row) = self
106            .graph_claim_calibration_rows
107            .iter_mut()
108            .rev()
109            .find(|row| row.node_index == node_id.index() && row.algorithm == algorithm)
110        else {
111            return;
112        };
113        row.record_measured_peak(measured_peak_bytes, measured_peak_provenance);
114    }
115
116    /// Records the exact transition into the native graph algorithm executor.
117    /// A claim that remains pending never becomes physical by implication.
118    pub fn record_graph_claim_calibration_algorithm_started(&mut self, node_id: NodeId, algorithm: GraphAlgorithm) {
119        let Some(row) = self
120            .graph_claim_calibration_rows
121            .iter_mut()
122            .rev()
123            .find(|row| row.node_index == node_id.index() && row.algorithm == algorithm)
124        else {
125            return;
126        };
127        row.record_execution_state(GraphClaimExecutionState::AlgorithmStarted);
128    }
129
130    /// Returns the fixed-shape graph measured-peak rows collected for this
131    /// execution. The returned projection is read-only and cannot alter
132    /// admission state or memory accounting.
133    pub fn graph_claim_calibration_rows(&self) -> &[GraphMeasuredPeakRow] {
134        &self.graph_claim_calibration_rows
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use query_planning::correlation::QueryId;
141
142    use super::*;
143    use crate::observability::graph_observation::{GraphClaimExecutionState, GraphMeasuredPeakProvenance};
144
145    #[test]
146    fn graph_claim_measurement_correlates_by_typed_node_and_algorithm() {
147        let node = NodeId::from_index_for_test(7);
148        let mut metrics = QueryMetrics::new("query-a");
149        metrics.graph_claim_calibration_rows.push(GraphMeasuredPeakRow::pending(
150            Some(QueryId::new("query-a")),
151            "n7",
152            node.index(),
153            GraphAlgorithm::Bfs,
154        ));
155        metrics.graph_claim_calibration_rows.push(GraphMeasuredPeakRow::pending(
156            Some(QueryId::new("query-b")),
157            "n7",
158            node.index(),
159            GraphAlgorithm::PageRank,
160        ));
161
162        metrics.record_graph_claim_calibration_measurement(
163            node,
164            GraphAlgorithm::PageRank,
165            Some(1_600),
166            GraphMeasuredPeakProvenance::CacheScopeHighWater,
167        );
168
169        let bfs = &metrics.graph_claim_calibration_rows[0];
170        assert_eq!(bfs.query_id.as_ref().map(QueryId::as_str), Some("query-a"));
171        assert_eq!(bfs.measured_peak_bytes, None, "same node but different algorithm must not correlate");
172        assert_eq!(bfs.measured_peak_provenance, GraphMeasuredPeakProvenance::Unavailable);
173
174        let pagerank = &metrics.graph_claim_calibration_rows[1];
175        assert_eq!(pagerank.query_id.as_ref().map(QueryId::as_str), Some("query-b"));
176        assert_eq!(pagerank.measured_peak_bytes, Some(1_600));
177        assert_eq!(pagerank.measured_peak_provenance, GraphMeasuredPeakProvenance::CacheScopeHighWater);
178    }
179
180    #[test]
181    fn graph_claim_execution_state_records_only_the_first_exact_boundary() {
182        let node = NodeId::from_index_for_test(7);
183        let mut metrics = QueryMetrics::new("query-a");
184        metrics.graph_claim_calibration_rows.push(GraphMeasuredPeakRow::pending(
185            Some(QueryId::new("query-a")),
186            "n7",
187            node.index(),
188            GraphAlgorithm::Bfs,
189        ));
190
191        metrics.record_graph_claim_calibration_algorithm_started(node, GraphAlgorithm::Bfs);
192        metrics.record_graph_claim_calibration_algorithm_started(node, GraphAlgorithm::Bfs);
193
194        assert_eq!(
195            metrics.graph_claim_calibration_rows[0].execution_state,
196            GraphClaimExecutionState::AlgorithmStarted,
197            "an exact boundary must not be rewritten by a later call"
198        );
199    }
200}