query_runtime/metrics/
graph.rs1use 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#[derive(Debug, Clone, Default, PartialEq, Eq)]
13pub struct GraphAlgorithmRuntimeMetrics {
14 pub query_id: Option<QueryId>,
16 pub node_id: String,
18 pub algorithm: String,
20 pub runtime_outcome: String,
22 pub input_boundary: String,
24 pub output_boundary: String,
26 pub construction_policy: String,
28 pub source_column: String,
30 pub destination_column: String,
32 pub weight_column: Option<String>,
34 pub directed: bool,
36 pub renumber: bool,
38 pub algorithm_parameters: String,
40 pub algorithm_metadata: String,
42 pub input_rows: u64,
44 pub input_columns: u64,
46 pub output_rows: u64,
48 pub output_columns: u64,
50 pub output_device_bytes: Option<u64>,
52 pub graph_peak_bytes: Option<u64>,
56 pub elapsed_ns: u64,
58 pub gpu_resident_input_handoffs: u64,
60 pub host_to_device_uploads: u64,
62 pub device_to_host_materializations: u64,
64}
65
66impl QueryMetrics {
67 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 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 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 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 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 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}