1use prometheus::{
4 Histogram, HistogramOpts, HistogramVec, IntCounter, IntCounterVec, IntGauge, IntGaugeVec, Opts,
5 Registry,
6};
7
8#[cfg(feature = "cluster")]
10pub struct ClusterSubscriptionMetrics {
11 pub active_readers: IntGauge,
13 pub open_total: IntCounter,
15 pub open_failures_total: IntCounter,
17 pub frames_committed_total: IntCounter,
19 pub rows_committed_total: IntCounter,
21 pub bytes_committed_total: IntCounter,
23 pub segments_written_total: IntCounter,
25 pub segment_write_failures_total: IntCounter,
27 pub manifest_failures_total: IntCounter,
29 pub integrity_failures_total: IntCounter,
31 pub stale_writer_rejections_total: IntCounter,
33 pub sequence_gaps_total: IntCounter,
35 pub replay_bytes_total: IntCounter,
37 pub replay_frames_total: IntCounter,
39 pub replay_pruned_total: IntCounter,
41 pub gateway_lag_disconnects_total: IntCounter,
43 pub pending_bytes: IntGauge,
45 pub retained_bytes: IntGauge,
47 pub orphan_bytes: IntGauge,
49 pub checkpoint_prepare_seconds: Histogram,
51 pub commit_visibility_seconds: Histogram,
53 pub gateway_manifest_refresh_seconds: Histogram,
55}
56
57#[cfg(feature = "cluster")]
58impl ClusterSubscriptionMetrics {
59 fn new(registry: &Registry) -> Self {
60 Self {
61 active_readers: register_int_gauge(
62 registry,
63 "cluster_subscription_active_readers",
64 "Committed cluster subscription readers attached to this process",
65 ),
66 open_total: register_int_counter(
67 registry,
68 "cluster_subscription_open_total",
69 "Committed cluster subscription open attempts",
70 ),
71 open_failures_total: register_int_counter(
72 registry,
73 "cluster_subscription_open_failures_total",
74 "Failed committed cluster subscription open attempts",
75 ),
76 frames_committed_total: register_int_counter(
77 registry,
78 "cluster_subscription_frames_committed_total",
79 "Output frames made visible by authoritative checkpoint commits",
80 ),
81 rows_committed_total: register_int_counter(
82 registry,
83 "cluster_subscription_rows_committed_total",
84 "Output rows made visible by authoritative checkpoint commits",
85 ),
86 bytes_committed_total: register_int_counter(
87 registry,
88 "cluster_subscription_bytes_committed_total",
89 "Encoded output bytes made visible by authoritative checkpoint commits",
90 ),
91 segments_written_total: register_int_counter(
92 registry,
93 "cluster_subscription_segments_written_total",
94 "Immutable committed-output segments written by this process",
95 ),
96 segment_write_failures_total: register_int_counter(
97 registry,
98 "cluster_subscription_segment_write_failures_total",
99 "Immutable committed-output segment writes that failed",
100 ),
101 manifest_failures_total: register_int_counter(
102 registry,
103 "cluster_subscription_manifest_failures_total",
104 "Committed-output manifest construction, loading, or validation failures",
105 ),
106 integrity_failures_total: register_int_counter(
107 registry,
108 "cluster_subscription_integrity_failures_total",
109 "Committed-output manifest or segment integrity failures",
110 ),
111 stale_writer_rejections_total: register_int_counter(
112 registry,
113 "cluster_subscription_stale_writer_rejections_total",
114 "Fenced committed-output writer attempts rejected by this process",
115 ),
116 sequence_gaps_total: register_int_counter(
117 registry,
118 "cluster_subscription_sequence_gaps_total",
119 "Partition sequence continuity failures detected by writers or readers",
120 ),
121 replay_bytes_total: register_int_counter(
122 registry,
123 "cluster_subscription_replay_bytes_total",
124 "Encoded bytes read during committed subscription replay",
125 ),
126 replay_frames_total: register_int_counter(
127 registry,
128 "cluster_subscription_replay_frames_total",
129 "Frames read during committed subscription replay",
130 ),
131 replay_pruned_total: register_int_counter(
132 registry,
133 "cluster_subscription_replay_pruned_total",
134 "Replay opens rejected because their epoch was pruned",
135 ),
136 gateway_lag_disconnects_total: register_int_counter(
137 registry,
138 "cluster_subscription_gateway_lag_disconnects_total",
139 "Readers disconnected after exhausting bounded gateway lag allowance",
140 ),
141 pending_bytes: register_int_gauge(
142 registry,
143 "cluster_subscription_pending_bytes",
144 "In-memory committed-output bytes awaiting checkpoint disposition",
145 ),
146 retained_bytes: register_int_gauge(
147 registry,
148 "cluster_subscription_retained_bytes",
149 "Bytes reachable from the retained committed output-log suffix",
150 ),
151 orphan_bytes: register_int_gauge(
152 registry,
153 "cluster_subscription_orphan_bytes",
154 "Grace-held unreachable output bytes found by the latest orphan scan",
155 ),
156 checkpoint_prepare_seconds: register_subscription_histogram(
157 registry,
158 "cluster_subscription_checkpoint_prepare_seconds",
159 "Subscription output encoding and upload duration during checkpoint preparation",
160 ),
161 commit_visibility_seconds: register_subscription_histogram(
162 registry,
163 "cluster_subscription_commit_visibility_seconds",
164 "Authoritative checkpoint Commit publication duration before local visibility",
165 ),
166 gateway_manifest_refresh_seconds: register_subscription_histogram(
167 registry,
168 "cluster_subscription_gateway_manifest_refresh_seconds",
169 "Gateway committed-index refresh duration",
170 ),
171 }
172 }
173}
174
175pub struct ViewMetrics {
177 pub updates: IntCounter,
179 pub bytes_stored: IntGauge,
181 pub committed_staleness_ms: IntGauge,
183 pub freshness_breaches_total: IntCounterVec,
185 pub rows_below_horizon_total: IntCounter,
188 pub reads_total: IntCounterVec,
190 pub committed_read_age_seconds: Histogram,
192 pub recent_read_wait_seconds: Histogram,
195 pub recency_probe_failures_total: IntCounterVec,
197}
198
199impl ViewMetrics {
200 fn new(registry: &Registry) -> Self {
201 let metrics = Self {
202 updates: IntCounter::new("mv_updates_total", "Materialized view updates").unwrap(),
203 bytes_stored: IntGauge::new("mv_bytes_stored", "Approximate MV bytes stored").unwrap(),
204 committed_staleness_ms: IntGauge::new(
205 "mv_committed_staleness_ms",
206 "Age of the newest committed checkpoint that committed view reads observe",
207 )
208 .unwrap(),
209 freshness_breaches_total: IntCounterVec::new(
210 Opts::new(
211 "mv_freshness_breaches_total",
212 "Times committed view reads became staler than the view's declared freshness",
213 ),
214 &["view"],
215 )
216 .unwrap(),
217 rows_below_horizon_total: IntCounter::new(
218 "mv_rows_below_horizon_total",
219 "Rows append-only views dropped because they arrived behind the retention horizon",
220 )
221 .unwrap(),
222 reads_total: IntCounterVec::new(
223 Opts::new(
224 "mv_reads_total",
225 "Ad-hoc reads of materialized views by read level",
226 ),
227 &["level"],
228 )
229 .unwrap(),
230 committed_read_age_seconds: Histogram::with_opts(
231 HistogramOpts::new(
232 "mv_committed_read_age_seconds",
233 "Age of the committed checkpoint each committed view read observed",
234 )
235 .buckets(vec![0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0, 60.0]),
236 )
237 .unwrap(),
238 recent_read_wait_seconds: Histogram::with_opts(
239 HistogramOpts::new(
240 "mv_recent_read_wait_seconds",
241 "Time each recent view read waited for a committed checkpoint holding what \
242 its sources had upstream when it arrived",
243 )
244 .buckets(vec![0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0]),
245 )
246 .unwrap(),
247 recency_probe_failures_total: IntCounterVec::new(
248 Opts::new(
249 "mv_recency_probe_failures_total",
250 "Times a source could not report its upstream position to a recent view read",
251 ),
252 &["source"],
253 )
254 .unwrap(),
255 };
256 registry
257 .register(Box::new(metrics.updates.clone()))
258 .unwrap();
259 registry
260 .register(Box::new(metrics.bytes_stored.clone()))
261 .unwrap();
262 registry
263 .register(Box::new(metrics.committed_staleness_ms.clone()))
264 .unwrap();
265 registry
266 .register(Box::new(metrics.freshness_breaches_total.clone()))
267 .unwrap();
268 registry
269 .register(Box::new(metrics.rows_below_horizon_total.clone()))
270 .unwrap();
271 registry
272 .register(Box::new(metrics.reads_total.clone()))
273 .unwrap();
274 registry
275 .register(Box::new(metrics.committed_read_age_seconds.clone()))
276 .unwrap();
277 registry
278 .register(Box::new(metrics.recent_read_wait_seconds.clone()))
279 .unwrap();
280 registry
281 .register(Box::new(metrics.recency_probe_failures_total.clone()))
282 .unwrap();
283 metrics
284 }
285
286 pub(crate) fn observe_read(&self, level: &str, taken_at_ms: Option<u64>) {
289 self.reads_total.with_label_values(&[level]).inc();
290 let Some(taken_at_ms) = taken_at_ms else {
291 return;
292 };
293 let now_ms = std::time::SystemTime::now()
294 .duration_since(std::time::UNIX_EPOCH)
295 .map_or(0, |elapsed| {
296 u64::try_from(elapsed.as_millis()).unwrap_or(u64::MAX)
297 });
298 #[allow(clippy::cast_precision_loss)] self.committed_read_age_seconds
300 .observe(now_ms.saturating_sub(taken_at_ms) as f64 / 1_000.0);
301 }
302}
303
304#[cfg(feature = "cluster")]
306pub struct PlacementMetrics {
307 pub vnodes_per_domain: IntGaugeVec,
309 pub blast_radius_ratio: prometheus::Gauge,
311 pub owned_vnodes: IntGauge,
313}
314
315#[cfg(feature = "cluster")]
316impl PlacementMetrics {
317 fn new(registry: &Registry) -> Self {
318 let vnodes_per_domain = IntGaugeVec::new(
319 Opts::new(
320 "placement_vnodes_per_domain",
321 "Vnodes owned per failure domain (cluster mode)",
322 ),
323 &["domain"],
324 )
325 .unwrap();
326 registry
327 .register(Box::new(vnodes_per_domain.clone()))
328 .unwrap();
329 let blast_radius_ratio = prometheus::Gauge::new(
330 "placement_blast_radius_ratio",
331 "Largest single domain's share of all vnodes (0-1); state affected if it fails",
332 )
333 .unwrap();
334 registry
335 .register(Box::new(blast_radius_ratio.clone()))
336 .unwrap();
337 Self {
338 vnodes_per_domain,
339 blast_radius_ratio,
340 owned_vnodes: register_int_gauge(
341 registry,
342 "placement_owned_vnodes",
343 "Vnodes owned by this process (cluster mode)",
344 ),
345 }
346 }
347}
348
349fn register_int_counter(registry: &Registry, name: &str, help: &str) -> IntCounter {
350 let metric = IntCounter::new(name, help).unwrap();
351 registry.register(Box::new(metric.clone())).unwrap();
352 metric
353}
354
355fn register_event_counters(registry: &Registry) -> (IntCounter, IntCounter) {
356 (
357 register_int_counter(
358 registry,
359 "events_ingested_total",
360 "Events ingested from sources",
361 ),
362 register_int_counter(
363 registry,
364 "events_emitted_total",
365 "Events emitted to streams",
366 ),
367 )
368}
369
370fn register_operator_process_duration(registry: &Registry) -> HistogramVec {
371 let metric = HistogramVec::new(
373 HistogramOpts::new(
374 "operator_process_duration_seconds",
375 "Operator processing duration by catalog operator and execution mode",
376 )
377 .buckets(prometheus::exponential_buckets(0.0001, 4.0, 10).unwrap()),
378 &["operator", "mode"],
379 )
380 .unwrap();
381 registry.register(Box::new(metric.clone())).unwrap();
382 metric
383}
384
385#[cfg(feature = "cluster")]
386fn register_int_gauge(registry: &Registry, name: &str, help: &str) -> IntGauge {
387 let metric = IntGauge::new(name, help).unwrap();
388 registry.register(Box::new(metric.clone())).unwrap();
389 metric
390}
391
392#[cfg(feature = "cluster")]
393fn register_subscription_histogram(registry: &Registry, name: &str, help: &str) -> Histogram {
394 let metric = Histogram::with_opts(
395 HistogramOpts::new(name, help)
396 .buckets(prometheus::exponential_buckets(0.001, 2.0, 18).unwrap()),
397 )
398 .unwrap();
399 registry.register(Box::new(metric.clone())).unwrap();
400 metric
401}
402
403pub struct EngineMetrics {
408 pub(crate) flow: crate::flow_metrics::FlowMetrics,
409 #[cfg(feature = "cluster")]
411 pub cluster_subscription: ClusterSubscriptionMetrics,
412 pub events_ingested: IntCounter,
414 pub events_emitted: IntCounter,
416 pub events_dropped: IntCounter,
418 pub cycles: IntCounter,
420 pub batches: IntCounter,
422 pub queries_compiled: IntCounter,
424 pub queries_cached_plan: IntCounter,
426 pub cycles_backpressured: IntCounter,
428 pub views: ViewMetrics,
430 pub pipeline_watermark: IntGauge,
432 pub source_watermark_ms: IntGaugeVec,
434 pub source_idle: IntGaugeVec,
437 pub stream_watermark_ms: IntGaugeVec,
439 pub input_buf_bytes: IntGaugeVec,
441 pub managed_state_accounted_bytes: IntGaugeVec,
447 pub shed_records_total: IntCounterVec,
449 pub checkpoints_completed: IntCounter,
451 pub checkpoints_failed: IntCounter,
453 pub checkpoint_epoch: IntGauge,
455 pub checkpoint_size_bytes: IntGauge,
457 pub sink_write_failures: IntCounter,
459 pub sink_write_timeouts: IntCounter,
461 pub sink_task_channel_closed: IntCounter,
463 pub sink_filter_rejected_rows: IntCounterVec,
466 pub window_late_dropped: IntCounter,
469 pub events_null_timestamp: IntCounter,
471 pub temporal_filter_buffered: IntGauge,
473 pub temporal_filter_inserts: IntCounter,
475 pub temporal_filter_retracts: IntCounter,
477 pub temporal_filter_dropped: IntCounter,
479 pub cycle_duration: Histogram,
481 pub cycle_execute_duration: Histogram,
483 pub cycle_output_store_duration: Histogram,
485 pub cycle_sink_enqueue_duration: Histogram,
487 pub operator_process_duration: HistogramVec,
490 pub checkpoint_duration: Histogram,
492 pub checkpoint_state_capture_duration: Histogram,
494 pub checkpoint_pipeline_stall_duration: Histogram,
498 pub checkpoint_barrier_local_duration: Histogram,
501 pub checkpoint_aligned_resume_wait: Histogram,
504 pub checkpoint_shuffle_flush_waves: Histogram,
507 pub sink_precommit_duration: Histogram,
509 pub lookup_cache_hits: IntCounterVec,
511 pub lookup_cache_misses: IntCounterVec,
513 pub lookup_source_errors: IntCounterVec,
515 pub lookup_in_flight_rows: IntGaugeVec,
517 #[cfg(feature = "cluster")]
519 pub placement: PlacementMetrics,
520 pub pipeline_faults_total: IntCounter,
522 pub pipeline_cycle_errors_total: IntCounter,
524 pub pipeline_restarts_total: IntCounter,
526 pub coordinated_recoveries_total: IntCounter,
528 pub coordinated_recovery_failures_total: IntCounter,
530 pub shuffle_delivery_loss_incidents_total: IntCounter,
532 pub colocated_routing_fallbacks_total: IntCounter,
535}
536
537impl EngineMetrics {
538 #[must_use]
544 #[allow(clippy::too_many_lines)]
545 pub fn new(registry: &Registry) -> Self {
546 macro_rules! reg {
547 ($m:expr) => {{
548 let m = $m;
549 registry.register(Box::new(m.clone())).unwrap();
550 m
551 }};
552 }
553
554 let (events_ingested, events_emitted) = register_event_counters(registry);
555
556 Self {
557 flow: crate::flow_metrics::FlowMetrics::new(registry),
558 #[cfg(feature = "cluster")]
559 cluster_subscription: ClusterSubscriptionMetrics::new(registry),
560 events_ingested,
561 events_emitted,
562 events_dropped: reg!(IntCounter::new("events_dropped_total", "Events dropped").unwrap()),
563 cycles: reg!(IntCounter::new(
564 "cycles_total",
565 "Normal processing cycles completed (excludes checkpoint graph drains)"
566 )
567 .unwrap()),
568 batches: reg!(IntCounter::new("batches_total", "Batches processed").unwrap()),
569 queries_compiled: reg!(IntCounter::new(
570 "queries_compiled_total",
571 "Queries using compiled PhysicalExpr"
572 )
573 .unwrap()),
574 queries_cached_plan: reg!(IntCounter::new(
575 "queries_cached_plan_total",
576 "Queries using cached logical plan"
577 )
578 .unwrap()),
579 cycles_backpressured: reg!(IntCounter::new(
580 "cycles_backpressured_total",
581 "Cycles whose source intake waited for graph port room"
582 )
583 .unwrap()),
584 views: ViewMetrics::new(registry),
585 pipeline_watermark: reg!(IntGauge::new(
586 "pipeline_watermark",
587 "Global pipeline watermark"
588 )
589 .unwrap()),
590 source_watermark_ms: reg!(IntGaugeVec::new(
592 Opts::new("source_watermark_ms", "Per-source watermark (epoch-ms)"),
593 &["source"],
594 )
595 .unwrap()),
596 source_idle: reg!(IntGaugeVec::new(
597 Opts::new(
598 "source_idle",
599 "1 if source idle (excluded from watermark min)"
600 ),
601 &["source"],
602 )
603 .unwrap()),
604 stream_watermark_ms: reg!(IntGaugeVec::new(
605 Opts::new("stream_watermark_ms", "Per-stream watermark (epoch-ms)"),
606 &["stream"],
607 )
608 .unwrap()),
609 input_buf_bytes: reg!(IntGaugeVec::new(
610 Opts::new("input_buf_bytes", "Per-stream input buffer bytes"),
611 &["stream"],
612 )
613 .unwrap()),
614 managed_state_accounted_bytes: reg!(IntGaugeVec::new(
616 Opts::new(
617 "managed_state_accounted_bytes",
618 "Operator-reported retained-state charge; current aggregate values are lower bounds between checkpoint/lifecycle reconciliation and exclude hash buckets, nested/shared payloads, allocator overhead, and RSS",
619 ),
620 &["operator", "phase"],
621 )
622 .unwrap()),
623 shed_records_total: reg!(IntCounterVec::new(
624 Opts::new("shed_records_total", "Rows shed by ShedOldest policy"),
625 &["stream"],
626 )
627 .unwrap()),
628 checkpoints_completed: reg!(IntCounter::new(
629 "checkpoints_completed_total",
630 "Completed checkpoints"
631 )
632 .unwrap()),
633 checkpoints_failed: reg!(IntCounter::new(
634 "checkpoints_failed_total",
635 "Failed checkpoints"
636 )
637 .unwrap()),
638 checkpoint_epoch: reg!(
639 IntGauge::new("checkpoint_epoch", "Current checkpoint epoch").unwrap()
640 ),
641 checkpoint_size_bytes: reg!(IntGauge::new(
642 "checkpoint_size_bytes",
643 "Last checkpoint size"
644 )
645 .unwrap()),
646 sink_write_failures: reg!(IntCounter::new(
647 "sink_write_failures_total",
648 "Sink write errors"
649 )
650 .unwrap()),
651 sink_write_timeouts: reg!(IntCounter::new(
652 "sink_write_timeouts_total",
653 "Sink write timeouts"
654 )
655 .unwrap()),
656 sink_task_channel_closed: reg!(IntCounter::new(
657 "sink_task_channel_closed_total",
658 "Sink task channel closed"
659 )
660 .unwrap()),
661 sink_filter_rejected_rows: reg!(IntCounterVec::new(
662 Opts::new(
663 "sink_filter_rejected_rows_total",
664 "Rows dropped because the sink filter failed to compile",
665 ),
666 &["sink"],
667 )
668 .unwrap()),
669 window_late_dropped: reg!(IntCounter::new(
670 "window_late_dropped_total",
671 "Window assignments dropped past allowed_lateness"
672 )
673 .unwrap()),
674 events_null_timestamp: reg!(IntCounter::new(
675 "events_null_timestamp_total",
676 "Source rows dropped because the event-time column was null"
677 )
678 .unwrap()),
679 temporal_filter_buffered: reg!(IntGauge::new(
680 "temporal_filter_buffered",
681 "Rows buffered by retracting temporal-filter operators"
682 )
683 .unwrap()),
684 temporal_filter_inserts: reg!(IntCounter::new(
685 "temporal_filter_inserts_total",
686 "Z-set inserts emitted by temporal-filter operators"
687 )
688 .unwrap()),
689 temporal_filter_retracts: reg!(IntCounter::new(
690 "temporal_filter_retracts_total",
691 "Z-set retractions emitted by temporal-filter operators"
692 )
693 .unwrap()),
694 temporal_filter_dropped: reg!(IntCounter::new(
695 "temporal_filter_dropped_total",
696 "Rows dropped un-emitted by temporal-filter operators"
697 )
698 .unwrap()),
699 cycle_duration: reg!(Histogram::with_opts(
700 HistogramOpts::new(
701 "cycle_duration_seconds",
702 "Normal processing-cycle duration (excludes checkpoint graph drains)",
703 )
704 .buckets(vec![
705 1e-7, 5e-7, 1e-6, 5e-6, 1e-5, 5e-5, 1e-4, 5e-4, 1e-3, 5e-3, 1e-2, 5e-2,
706 1e-1, 5e-1, 1.0,
707 ]),
708 )
709 .unwrap()),
710 cycle_execute_duration: reg!(Histogram::with_opts(
711 HistogramOpts::new(
712 "cycle_execute_duration_seconds",
713 "SQL operator-graph execution within successful normal processing cycles",
714 )
715 .buckets(vec![
716 1e-7, 5e-7, 1e-6, 5e-6, 1e-5, 5e-5, 1e-4, 5e-4, 1e-3, 5e-3, 1e-2, 5e-2,
717 1e-1, 5e-1, 1.0,
718 ]),
719 )
720 .unwrap()),
721 cycle_output_store_duration: reg!(Histogram::with_opts(
722 HistogramOpts::new(
723 "cycle_output_store_duration_seconds",
724 "Materialized-view and subscription-stream publication within successful normal processing cycles",
725 )
726 .buckets(vec![
727 1e-7, 5e-7, 1e-6, 5e-6, 1e-5, 5e-5, 1e-4, 5e-4, 1e-3, 5e-3, 1e-2, 5e-2,
728 1e-1, 5e-1, 1.0,
729 ]),
730 )
731 .unwrap()),
732 cycle_sink_enqueue_duration: reg!(Histogram::with_opts(
733 HistogramOpts::new(
734 "cycle_sink_enqueue_duration_seconds",
735 "Sink publication preparation and command admission within successful normal processing cycles",
736 )
737 .buckets(vec![
738 1e-7, 5e-7, 1e-6, 5e-6, 1e-5, 5e-5, 1e-4, 5e-4, 1e-3, 5e-3, 1e-2, 5e-2,
739 1e-1, 5e-1, 1.0,
740 ]),
741 )
742 .unwrap()),
743 operator_process_duration: register_operator_process_duration(registry),
744 checkpoint_duration: reg!(Histogram::with_opts(
747 HistogramOpts::new("checkpoint_duration_seconds", "Checkpoint cycle duration")
748 .buckets(prometheus::exponential_buckets(0.01, 2.0, 15).unwrap()),
749 )
750 .unwrap()),
751 checkpoint_state_capture_duration: reg!(Histogram::with_opts(
752 HistogramOpts::new(
753 "checkpoint_state_capture_duration_seconds",
754 "Synchronous mutable checkpoint-state capture duration",
755 )
756 .buckets(prometheus::exponential_buckets(0.001, 2.0, 16).unwrap()),
757 )
758 .unwrap()),
759 checkpoint_pipeline_stall_duration: reg!(Histogram::with_opts(
762 HistogramOpts::new(
763 "checkpoint_pipeline_stall_duration_seconds",
764 "Pipeline stall per checkpoint barrier (align + capture + resume gate)",
765 )
766 .buckets(prometheus::exponential_buckets(0.001, 2.0, 16).unwrap()),
767 )
768 .unwrap()),
769 checkpoint_barrier_local_duration: reg!(Histogram::with_opts(
770 HistogramOpts::new(
771 "checkpoint_barrier_local_duration_seconds",
772 "Local paused barrier work (sink fence + shuffle align + state capture + tail handoff)",
773 )
774 .buckets(prometheus::exponential_buckets(0.001, 2.0, 16).unwrap()),
775 )
776 .unwrap()),
777 checkpoint_aligned_resume_wait: reg!(Histogram::with_opts(
778 HistogramOpts::new(
779 "checkpoint_aligned_resume_wait_seconds",
780 "Cluster-shuffle pause waiting for the global Aligned release",
781 )
782 .buckets(prometheus::exponential_buckets(0.001, 2.0, 16).unwrap()),
783 )
784 .unwrap()),
785 checkpoint_shuffle_flush_waves: reg!(Histogram::with_opts(
786 HistogramOpts::new(
787 "checkpoint_shuffle_flush_waves",
788 "Waves of the checkpoint's distributed shuffle flush",
789 )
790 .buckets(prometheus::linear_buckets(1.0, 1.0, 16).unwrap()),
791 )
792 .unwrap()),
793 sink_precommit_duration: reg!(Histogram::with_opts(
795 HistogramOpts::new("sink_precommit_duration_seconds", "Sink pre-commit latency")
796 .buckets(prometheus::exponential_buckets(0.005, 2.0, 16).unwrap()),
797 )
798 .unwrap()),
799 lookup_cache_hits: reg!(IntCounterVec::new(
801 Opts::new("lookup_cache_hits_total", "On-demand lookup cache hits"),
802 &["table"],
803 )
804 .unwrap()),
805 lookup_cache_misses: reg!(IntCounterVec::new(
806 Opts::new("lookup_cache_misses_total", "On-demand lookup cache misses"),
807 &["table"],
808 )
809 .unwrap()),
810 lookup_source_errors: reg!(IntCounterVec::new(
811 Opts::new(
812 "lookup_source_errors_total",
813 "On-demand lookup source fetch errors"
814 ),
815 &["table"],
816 )
817 .unwrap()),
818 lookup_in_flight_rows: reg!(IntGaugeVec::new(
819 Opts::new(
820 "lookup_in_flight_rows",
821 "On-demand lookup rows awaiting a source fetch"
822 ),
823 &["table"],
824 )
825 .unwrap()),
826 #[cfg(feature = "cluster")]
827 placement: PlacementMetrics::new(registry),
828 pipeline_faults_total: reg!(IntCounter::new(
829 "pipeline_faults_total",
830 "Compute-thread crashes that faulted the pipeline"
831 )
832 .unwrap()),
833 pipeline_cycle_errors_total: reg!(IntCounter::new(
834 "pipeline_cycle_errors_total",
835 "Fatal cycle errors dropped-and-continued under at-least-once delivery"
836 )
837 .unwrap()),
838 pipeline_restarts_total: reg!(IntCounter::new(
839 "pipeline_restarts_total",
840 "Automatic recover-from-checkpoint restarts after a pipeline fault"
841 )
842 .unwrap()),
843 coordinated_recoveries_total: reg!(IntCounter::new(
844 "coordinated_recoveries_total",
845 "Leader-coordinated global restart-to-epoch rounds applied by this node"
846 )
847 .unwrap()),
848 coordinated_recovery_failures_total: reg!(IntCounter::new(
849 "coordinated_recovery_failures_total",
850 "Leader-coordinated recovery rounds abandoned (self-restore failed or quorum timed out)"
851 )
852 .unwrap()),
853 shuffle_delivery_loss_incidents_total: reg!(IntCounter::new(
854 "shuffle_delivery_loss_incidents_total",
855 "Cross-node shuffle delivery-loss incidents (fences the epoch, forces replay)"
856 )
857 .unwrap()),
858 colocated_routing_fallbacks_total: reg!(IntCounter::new(
859 "colocated_routing_fallbacks_total",
860 "Colocated aggregate input batches repartitioned because rows were owned elsewhere"
861 )
862 .unwrap()),
863 }
864 }
865}
866
867#[cfg(all(test, feature = "cluster"))]
868mod tests {
869 use super::*;
870
871 #[test]
872 fn cluster_subscription_metric_family_has_the_release_names() {
873 let registry = Registry::new();
874 let _metrics = EngineMetrics::new(®istry);
875 let names = registry
876 .gather()
877 .into_iter()
878 .map(|family| family.name().to_owned())
879 .collect::<std::collections::BTreeSet<_>>();
880 for expected in [
881 "cluster_subscription_active_readers",
882 "cluster_subscription_open_total",
883 "cluster_subscription_open_failures_total",
884 "cluster_subscription_frames_committed_total",
885 "cluster_subscription_rows_committed_total",
886 "cluster_subscription_bytes_committed_total",
887 "cluster_subscription_segments_written_total",
888 "cluster_subscription_segment_write_failures_total",
889 "cluster_subscription_manifest_failures_total",
890 "cluster_subscription_integrity_failures_total",
891 "cluster_subscription_stale_writer_rejections_total",
892 "cluster_subscription_sequence_gaps_total",
893 "cluster_subscription_replay_bytes_total",
894 "cluster_subscription_replay_frames_total",
895 "cluster_subscription_replay_pruned_total",
896 "cluster_subscription_gateway_lag_disconnects_total",
897 "cluster_subscription_pending_bytes",
898 "cluster_subscription_retained_bytes",
899 "cluster_subscription_orphan_bytes",
900 "cluster_subscription_checkpoint_prepare_seconds",
901 "cluster_subscription_commit_visibility_seconds",
902 "cluster_subscription_gateway_manifest_refresh_seconds",
903 ] {
904 assert!(names.contains(expected), "missing metric {expected}");
905 }
906 }
907}