1use query_planning::{
2 correlation::QueryId,
3 dynamic_filter::{DynamicFilter, DynamicFilterConsumerReductionStage, DynamicFilterRoute},
4};
5
6use super::{QueryMetrics, optional_i64, optional_str, optional_u64};
7
8#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
10pub struct DynamicFilterSourceSnapshot {
11 pub row_groups: u64,
13 pub source_bytes: Option<u64>,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct DynamicFilterRuntimeMetrics {
20 pub(crate) query_id: Option<QueryId>,
22 pub dynamic_filter_id: String,
24 pub event: String,
27 pub producer_kind: String,
29 pub producer_node_id: String,
31 pub consumer_kind: String,
33 pub consumer_node_id: String,
35 pub consumer_lowering: String,
37 pub consumer_reduction_stage: String,
39 pub filter_kind: String,
41 pub payload_value_count: Option<u64>,
43 pub payload_min_i64: Option<i64>,
45 pub payload_max_i64: Option<i64>,
47 pub producer_rows: Option<u64>,
49 pub skipped_reason: Option<String>,
51 pub selected_row_groups_before: Option<u64>,
53 pub selected_row_groups_after: Option<u64>,
55 pub selected_source_bytes_before: Option<u64>,
57 pub selected_source_bytes_after: Option<u64>,
59 pub source_file_index: Option<u64>,
61 pub source_chunk_index: Option<u64>,
63 pub reader_predicate_rows_before: Option<u64>,
65 pub reader_predicate_rows_after: Option<u64>,
67 pub reader_predicate_rows_filtered: u64,
69 pub post_decode_rows_before: Option<u64>,
71 pub post_decode_rows_after: Option<u64>,
73 pub post_decode_rows_filtered: u64,
75 pub row_groups_pruned: u64,
77 pub source_bytes_pruned: u64,
79}
80
81impl DynamicFilterRuntimeMetrics {
82 fn produced(route: &DynamicFilterRoute, filter: &DynamicFilter, producer_rows: u64) -> Self {
83 let (payload_min_i64, payload_max_i64) =
84 filter.i64_range().map_or((None, None), |(min, max)| (Some(min), Some(max)));
85 Self {
86 query_id: None,
87 dynamic_filter_id: route.id.to_string(),
88 event: "produced".to_string(),
89 producer_kind: route.producer.kind().to_string(),
90 producer_node_id: route.producer.node_id().to_string(),
91 consumer_kind: route.consumer.kind().to_string(),
92 consumer_node_id: route.consumer.node_id().to_string(),
93 consumer_lowering: route.consumer_lowering.to_string(),
94 consumer_reduction_stage: route.consumer_reduction_stage().to_string(),
95 filter_kind: filter.kind().to_string(),
96 payload_value_count: filter.value_count().and_then(|value| u64::try_from(value).ok()),
97 payload_min_i64,
98 payload_max_i64,
99 producer_rows: Some(producer_rows),
100 skipped_reason: None,
101 selected_row_groups_before: None,
102 selected_row_groups_after: None,
103 selected_source_bytes_before: None,
104 selected_source_bytes_after: None,
105 source_file_index: None,
106 source_chunk_index: None,
107 reader_predicate_rows_before: None,
108 reader_predicate_rows_after: None,
109 reader_predicate_rows_filtered: 0,
110 post_decode_rows_before: None,
111 post_decode_rows_after: None,
112 post_decode_rows_filtered: 0,
113 row_groups_pruned: 0,
114 source_bytes_pruned: 0,
115 }
116 }
117
118 fn skipped(route: &DynamicFilterRoute, reason: impl Into<String>) -> Self {
119 Self {
120 query_id: None,
121 dynamic_filter_id: route.id.to_string(),
122 event: "skipped".to_string(),
123 producer_kind: route.producer.kind().to_string(),
124 producer_node_id: route.producer.node_id().to_string(),
125 consumer_kind: route.consumer.kind().to_string(),
126 consumer_node_id: route.consumer.node_id().to_string(),
127 consumer_lowering: route.consumer_lowering.to_string(),
128 consumer_reduction_stage: route.consumer_reduction_stage().to_string(),
129 filter_kind: "none".to_string(),
130 payload_value_count: None,
131 payload_min_i64: None,
132 payload_max_i64: None,
133 producer_rows: None,
134 skipped_reason: Some(reason.into()),
135 selected_row_groups_before: None,
136 selected_row_groups_after: None,
137 selected_source_bytes_before: None,
138 selected_source_bytes_after: None,
139 source_file_index: None,
140 source_chunk_index: None,
141 reader_predicate_rows_before: None,
142 reader_predicate_rows_after: None,
143 reader_predicate_rows_filtered: 0,
144 post_decode_rows_before: None,
145 post_decode_rows_after: None,
146 post_decode_rows_filtered: 0,
147 row_groups_pruned: 0,
148 source_bytes_pruned: 0,
149 }
150 }
151
152 fn applied(
153 route: &DynamicFilterRoute,
154 filter: &DynamicFilter,
155 before: DynamicFilterSourceSnapshot,
156 after: DynamicFilterSourceSnapshot,
157 row_groups_pruned: u64,
158 source_bytes_pruned: u64,
159 ) -> Self {
160 let (payload_min_i64, payload_max_i64) =
161 filter.i64_range().map_or((None, None), |(min, max)| (Some(min), Some(max)));
162 Self {
163 query_id: None,
164 dynamic_filter_id: route.id.to_string(),
165 event: "applied".to_string(),
166 producer_kind: route.producer.kind().to_string(),
167 producer_node_id: route.producer.node_id().to_string(),
168 consumer_kind: route.consumer.kind().to_string(),
169 consumer_node_id: route.consumer.node_id().to_string(),
170 consumer_lowering: route.consumer_lowering.to_string(),
171 consumer_reduction_stage: route.consumer_reduction_stage().to_string(),
172 filter_kind: filter.kind().to_string(),
173 payload_value_count: filter.value_count().and_then(|value| u64::try_from(value).ok()),
174 payload_min_i64,
175 payload_max_i64,
176 producer_rows: None,
177 skipped_reason: None,
178 selected_row_groups_before: Some(before.row_groups),
179 selected_row_groups_after: Some(after.row_groups),
180 selected_source_bytes_before: before.source_bytes,
181 selected_source_bytes_after: after.source_bytes,
182 source_file_index: None,
183 source_chunk_index: None,
184 reader_predicate_rows_before: None,
185 reader_predicate_rows_after: None,
186 reader_predicate_rows_filtered: 0,
187 post_decode_rows_before: None,
188 post_decode_rows_after: None,
189 post_decode_rows_filtered: 0,
190 row_groups_pruned,
191 source_bytes_pruned,
192 }
193 }
194
195 fn source_reader_predicate_rows(
196 route: &DynamicFilterRoute,
197 filter: &DynamicFilter,
198 source_file_index: Option<usize>,
199 source_chunk_index: Option<usize>,
200 rows_before: Option<u64>,
201 rows_after: Option<u64>,
202 ) -> Self {
203 let (payload_min_i64, payload_max_i64) =
204 filter.i64_range().map_or((None, None), |(min, max)| (Some(min), Some(max)));
205 let rows_filtered = rows_before.zip(rows_after).map_or(0, |(before, after)| before.saturating_sub(after));
206 Self {
207 query_id: None,
208 dynamic_filter_id: route.id.to_string(),
209 event: "reader_predicate_rows".to_string(),
210 producer_kind: route.producer.kind().to_string(),
211 producer_node_id: route.producer.node_id().to_string(),
212 consumer_kind: route.consumer.kind().to_string(),
213 consumer_node_id: route.consumer.node_id().to_string(),
214 consumer_lowering: route.consumer_lowering.as_str().to_string(),
215 consumer_reduction_stage: route.consumer_reduction_stage().to_string(),
216 filter_kind: filter.kind().as_str().to_string(),
217 payload_value_count: filter.value_count().map(|value| value as u64),
218 payload_min_i64,
219 payload_max_i64,
220 producer_rows: None,
221 skipped_reason: None,
222 selected_row_groups_before: None,
223 selected_row_groups_after: None,
224 selected_source_bytes_before: None,
225 selected_source_bytes_after: None,
226 source_file_index: source_file_index.map(|index| index as u64),
227 source_chunk_index: source_chunk_index.map(|index| index as u64),
228 reader_predicate_rows_before: rows_before,
229 reader_predicate_rows_after: rows_after,
230 reader_predicate_rows_filtered: rows_filtered,
231 post_decode_rows_before: None,
232 post_decode_rows_after: None,
233 post_decode_rows_filtered: 0,
234 row_groups_pruned: 0,
235 source_bytes_pruned: 0,
236 }
237 }
238
239 fn applied_post_decode(
240 route: &DynamicFilterRoute,
241 filter: &DynamicFilter,
242 rows_before: Option<u64>,
243 rows_after: Option<u64>,
244 ) -> Self {
245 Self::post_decode_event(route, filter, "applied_post_decode", rows_before, rows_after)
246 }
247
248 fn source_post_decode_stage(
249 route: &DynamicFilterRoute,
250 filter: &DynamicFilter,
251 rows_before: Option<u64>,
252 rows_after: Option<u64>,
253 ) -> Self {
254 Self::post_decode_event_with_stage(
255 route,
256 filter,
257 "applied_post_decode",
258 DynamicFilterConsumerReductionStage::SourcePostDecode,
259 rows_before,
260 rows_after,
261 )
262 }
263
264 fn post_decode_rows(
265 route: &DynamicFilterRoute,
266 filter: &DynamicFilter,
267 rows_before: Option<u64>,
268 rows_after: Option<u64>,
269 ) -> Self {
270 Self::post_decode_event(route, filter, "post_decode_rows", rows_before, rows_after)
271 }
272
273 fn post_decode_event(
274 route: &DynamicFilterRoute,
275 filter: &DynamicFilter,
276 event: &'static str,
277 rows_before: Option<u64>,
278 rows_after: Option<u64>,
279 ) -> Self {
280 Self::post_decode_event_with_stage(
281 route,
282 filter,
283 event,
284 route.consumer_reduction_stage(),
285 rows_before,
286 rows_after,
287 )
288 }
289
290 fn post_decode_event_with_stage(
291 route: &DynamicFilterRoute,
292 filter: &DynamicFilter,
293 event: &'static str,
294 consumer_reduction_stage: DynamicFilterConsumerReductionStage,
295 rows_before: Option<u64>,
296 rows_after: Option<u64>,
297 ) -> Self {
298 let (payload_min_i64, payload_max_i64) =
299 filter.i64_range().map_or((None, None), |(min, max)| (Some(min), Some(max)));
300 let rows_filtered = rows_before.zip(rows_after).map_or(0, |(before, after)| before.saturating_sub(after));
301 Self {
302 query_id: None,
303 dynamic_filter_id: route.id.to_string(),
304 event: event.to_string(),
305 producer_kind: route.producer.kind().to_string(),
306 producer_node_id: route.producer.node_id().to_string(),
307 consumer_kind: route.consumer.kind().to_string(),
308 consumer_node_id: route.consumer.node_id().to_string(),
309 consumer_lowering: route.consumer_lowering.to_string(),
310 consumer_reduction_stage: consumer_reduction_stage.to_string(),
311 filter_kind: filter.kind().to_string(),
312 payload_value_count: filter.value_count().and_then(|value| u64::try_from(value).ok()),
313 payload_min_i64,
314 payload_max_i64,
315 producer_rows: None,
316 skipped_reason: None,
317 selected_row_groups_before: None,
318 selected_row_groups_after: None,
319 selected_source_bytes_before: None,
320 selected_source_bytes_after: None,
321 source_file_index: None,
322 source_chunk_index: None,
323 reader_predicate_rows_before: None,
324 reader_predicate_rows_after: None,
325 reader_predicate_rows_filtered: 0,
326 post_decode_rows_before: rows_before,
327 post_decode_rows_after: rows_after,
328 post_decode_rows_filtered: rows_filtered,
329 row_groups_pruned: 0,
330 source_bytes_pruned: 0,
331 }
332 }
333
334 pub fn to_stable_kv_row(&self) -> String {
336 format!(
337 "dynamic_filter_id={},event={},producer_kind={},producer_node_id={},consumer_kind={},consumer_node_id={},consumer_lowering={},consumer_reduction_stage={},filter_kind={},payload_value_count={},payload_min_i64={},payload_max_i64={},producer_rows={},skipped_reason={},selected_row_groups_before={},selected_row_groups_after={},selected_source_bytes_before={},selected_source_bytes_after={},source_file_index={},source_chunk_index={},reader_predicate_rows_before={},reader_predicate_rows_after={},reader_predicate_rows_filtered={},post_decode_rows_before={},post_decode_rows_after={},post_decode_rows_filtered={},row_groups_pruned={},source_bytes_pruned={}",
338 self.dynamic_filter_id,
339 self.event,
340 self.producer_kind,
341 self.producer_node_id,
342 self.consumer_kind,
343 self.consumer_node_id,
344 self.consumer_lowering,
345 self.consumer_reduction_stage,
346 self.filter_kind,
347 optional_u64(self.payload_value_count),
348 optional_i64(self.payload_min_i64),
349 optional_i64(self.payload_max_i64),
350 optional_u64(self.producer_rows),
351 optional_str(self.skipped_reason.as_deref()),
352 optional_u64(self.selected_row_groups_before),
353 optional_u64(self.selected_row_groups_after),
354 optional_u64(self.selected_source_bytes_before),
355 optional_u64(self.selected_source_bytes_after),
356 optional_u64(self.source_file_index),
357 optional_u64(self.source_chunk_index),
358 optional_u64(self.reader_predicate_rows_before),
359 optional_u64(self.reader_predicate_rows_after),
360 self.reader_predicate_rows_filtered,
361 optional_u64(self.post_decode_rows_before),
362 optional_u64(self.post_decode_rows_after),
363 self.post_decode_rows_filtered,
364 self.row_groups_pruned,
365 self.source_bytes_pruned,
366 )
367 }
368}
369
370impl QueryMetrics {
371 fn record_dynamic_filter_runtime_row(&mut self, mut row: DynamicFilterRuntimeMetrics) {
372 if row.query_id.is_none() {
373 row.query_id = self.inherited_query_id();
374 }
375 self.dynamic_filter_runtime_rows.push(row);
376 }
377
378 pub fn record_dynamic_filter_produced(
380 &mut self,
381 route: &DynamicFilterRoute,
382 filter: &DynamicFilter,
383 producer_rows: u64,
384 ) {
385 self.dynamic_filter_produced_count = self.dynamic_filter_produced_count.saturating_add(1);
386 self.record_dynamic_filter_runtime_row(DynamicFilterRuntimeMetrics::produced(route, filter, producer_rows));
387 }
388
389 pub fn record_dynamic_filter_skipped(&mut self, route: &DynamicFilterRoute, reason: impl Into<String>) {
391 self.dynamic_filter_skipped_count = self.dynamic_filter_skipped_count.saturating_add(1);
392 self.record_dynamic_filter_runtime_row(DynamicFilterRuntimeMetrics::skipped(route, reason));
393 }
394
395 pub fn record_dynamic_filter_applied(
397 &mut self,
398 route: &DynamicFilterRoute,
399 filter: &DynamicFilter,
400 before: DynamicFilterSourceSnapshot,
401 after: DynamicFilterSourceSnapshot,
402 ) {
403 let row_groups_pruned = before.row_groups.saturating_sub(after.row_groups);
404 let source_bytes_pruned = before.source_bytes.unwrap_or(0).saturating_sub(after.source_bytes.unwrap_or(0));
405 self.dynamic_filter_applied_count = self.dynamic_filter_applied_count.saturating_add(1);
406 self.dynamic_filter_row_groups_pruned = self.dynamic_filter_row_groups_pruned.saturating_add(row_groups_pruned);
407 self.dynamic_filter_source_bytes_pruned =
408 self.dynamic_filter_source_bytes_pruned.saturating_add(source_bytes_pruned);
409 self.record_dynamic_filter_runtime_row(DynamicFilterRuntimeMetrics::applied(
410 route,
411 filter,
412 before,
413 after,
414 row_groups_pruned,
415 source_bytes_pruned,
416 ));
417 }
418
419 pub fn record_dynamic_filter_applied_post_decode(
421 &mut self,
422 route: &DynamicFilterRoute,
423 filter: &DynamicFilter,
424 rows_before: Option<u64>,
425 rows_after: Option<u64>,
426 ) {
427 self.dynamic_filter_applied_count = self.dynamic_filter_applied_count.saturating_add(1);
428 self.record_dynamic_filter_post_decode_reduction(route.consumer_reduction_stage(), rows_before, rows_after);
429 self.record_dynamic_filter_runtime_row(DynamicFilterRuntimeMetrics::applied_post_decode(
430 route,
431 filter,
432 rows_before,
433 rows_after,
434 ));
435 }
436
437 pub fn record_dynamic_filter_source_post_decode_stage(
440 &mut self,
441 route: &DynamicFilterRoute,
442 filter: &DynamicFilter,
443 rows_before: Option<u64>,
444 rows_after: Option<u64>,
445 ) {
446 self.record_dynamic_filter_post_decode_reduction(
447 DynamicFilterConsumerReductionStage::SourcePostDecode,
448 rows_before,
449 rows_after,
450 );
451 self.record_dynamic_filter_runtime_row(DynamicFilterRuntimeMetrics::source_post_decode_stage(
452 route,
453 filter,
454 rows_before,
455 rows_after,
456 ));
457 }
458
459 pub fn record_dynamic_filter_post_decode_rows(
464 &mut self,
465 route: &DynamicFilterRoute,
466 filter: &DynamicFilter,
467 rows_before: Option<u64>,
468 rows_after: Option<u64>,
469 ) {
470 self.record_dynamic_filter_post_decode_reduction(route.consumer_reduction_stage(), rows_before, rows_after);
471 self.record_dynamic_filter_runtime_row(DynamicFilterRuntimeMetrics::post_decode_rows(
472 route,
473 filter,
474 rows_before,
475 rows_after,
476 ));
477 }
478
479 pub fn record_dynamic_filter_source_reader_predicate_rows(
481 &mut self,
482 route: &DynamicFilterRoute,
483 filter: &DynamicFilter,
484 source_file_index: usize,
485 source_chunk_index: usize,
486 rows_before: Option<u64>,
487 rows_after: Option<u64>,
488 ) {
489 self.record_dynamic_filter_runtime_row(DynamicFilterRuntimeMetrics::source_reader_predicate_rows(
490 route,
491 filter,
492 Some(source_file_index),
493 Some(source_chunk_index),
494 rows_before,
495 rows_after,
496 ));
497 }
498
499 pub fn record_dynamic_filter_source_reader_predicate_batch_rows(
501 &mut self,
502 route: &DynamicFilterRoute,
503 filter: &DynamicFilter,
504 rows_before: Option<u64>,
505 rows_after: Option<u64>,
506 ) {
507 self.record_dynamic_filter_runtime_row(DynamicFilterRuntimeMetrics::source_reader_predicate_rows(
508 route,
509 filter,
510 None,
511 None,
512 rows_before,
513 rows_after,
514 ));
515 }
516
517 pub fn record_dynamic_filter_reader_predicate_source_reduction(
519 &mut self,
520 rows_before: Option<u64>,
521 rows_after: Option<u64>,
522 ) {
523 if let Some(rows_before) = rows_before {
524 self.dynamic_filter_reader_predicate_rows_before =
525 self.dynamic_filter_reader_predicate_rows_before.saturating_add(rows_before);
526 }
527 if let Some(rows_after) = rows_after {
528 self.dynamic_filter_reader_predicate_rows_after =
529 self.dynamic_filter_reader_predicate_rows_after.saturating_add(rows_after);
530 }
531 if let Some(rows_filtered) = rows_before.zip(rows_after).map(|(before, after)| before.saturating_sub(after)) {
532 self.dynamic_filter_reader_predicate_rows_filtered =
533 self.dynamic_filter_reader_predicate_rows_filtered.saturating_add(rows_filtered);
534 }
535 }
536
537 fn record_dynamic_filter_post_decode_reduction(
538 &mut self,
539 stage: DynamicFilterConsumerReductionStage,
540 rows_before: Option<u64>,
541 rows_after: Option<u64>,
542 ) {
543 if let Some(rows_before) = rows_before {
544 self.dynamic_filter_post_decode_rows_before =
545 self.dynamic_filter_post_decode_rows_before.saturating_add(rows_before);
546 }
547 if let Some(rows_after) = rows_after {
548 self.dynamic_filter_post_decode_rows_after =
549 self.dynamic_filter_post_decode_rows_after.saturating_add(rows_after);
550 }
551 if let Some(rows_filtered) = rows_before.zip(rows_after).map(|(before, after)| before.saturating_sub(after)) {
552 self.dynamic_filter_post_decode_rows_filtered =
553 self.dynamic_filter_post_decode_rows_filtered.saturating_add(rows_filtered);
554 match stage {
555 DynamicFilterConsumerReductionStage::SourcePostDecode => {
556 self.dynamic_filter_source_post_decode_rows_filtered =
557 self.dynamic_filter_source_post_decode_rows_filtered.saturating_add(rows_filtered);
558 },
559 DynamicFilterConsumerReductionStage::FilterPostDecode => {
560 self.dynamic_filter_filter_post_decode_rows_filtered =
561 self.dynamic_filter_filter_post_decode_rows_filtered.saturating_add(rows_filtered);
562 },
563 DynamicFilterConsumerReductionStage::JoinProbePostDecode => {
564 self.dynamic_filter_join_probe_post_decode_rows_filtered =
565 self.dynamic_filter_join_probe_post_decode_rows_filtered.saturating_add(rows_filtered);
566 },
567 DynamicFilterConsumerReductionStage::SourceReader => {},
568 }
569 }
570 }
571}