1use std::borrow::Cow;
11
12use mz_controller_types::ClusterId;
13use mz_ore::metric;
14use mz_ore::metrics::{
15 MakeCollector, MakeCollectorOpts, MetricTag, MetricVisibility, MetricsRegistry, UIntGauge,
16 remove_children_with_label,
17};
18use mz_ore::stats::{histogram_milliseconds_buckets, histogram_seconds_buckets};
19use mz_sql::ast::{AstInfo, Statement, StatementKind, SubscribeOutput};
20use mz_sql::session::hint::ApplicationNameHint;
21use mz_sql::session::user::User;
22use mz_sql::session::vars::IsolationLevel;
23use mz_sql_parser::ast::statement_kind_label_value;
24use prometheus::core::{AtomicU64, GenericCounter};
25use prometheus::{Histogram, HistogramVec, IntCounter, IntCounterVec, IntGaugeVec};
26
27use crate::statement_logging::StatementExecutionStrategy;
28
29pub(crate) const OCC_CALLER_SESSION: &str = "session";
30pub(crate) const OCC_CALLER_BACKGROUND: &str = "background";
31
32#[derive(Debug, Clone)]
33pub struct Metrics {
34 pub query_total: IntCounterVec,
35 pub active_sessions: IntGaugeVec,
36 pub active_subscribes: IntGaugeVec,
37 pub active_internal_subscribes: IntGaugeVec,
38 pub active_copy_tos: IntGaugeVec,
39 pub queue_busy_seconds: Histogram,
40 pub commands: IntCounterVec,
41 pub storage_usage_collection_time_seconds: Histogram,
42 pub arrangement_sizes_collection_time_seconds: Histogram,
43 pub arrangement_sizes_rows_written: IntCounter,
44 pub hydration_history_mutations: IntCounterVec,
45 pub hydration_history_retention_batch_full: IntCounter,
46 pub hydration_history_rows_affected: IntCounterVec,
47 pub hydration_history_sweep_duration_seconds: Histogram,
48 pub subscribe_outputs: IntCounterVec,
49 pub canceled_peeks: IntCounter,
50 pub linearize_message_seconds: HistogramVec,
51 pub statement_logging_records: IntCounterVec,
52 pub statement_logging_unsampled_bytes: IntCounter,
53 pub statement_logging_actual_bytes: IntCounter,
54 pub message_batch: Histogram,
55 pub message_handling: HistogramVec,
56 pub optimization_notices: IntCounterVec,
57 pub append_table_duration_seconds: Histogram,
58 pub webhook_validation_reduce_failures: IntCounterVec,
59 pub webhook_get_appender: IntCounter,
60 pub row_set_finishing_seconds: Histogram,
61 pub session_startup_table_writes_seconds: Histogram,
62 pub parse_seconds: Histogram,
63 pub pgwire_message_processing_seconds: HistogramVec,
64 pub result_rows_first_to_last_byte_seconds: HistogramVec,
65 pub pgwire_ensure_transaction_seconds: HistogramVec,
66 pub catalog_snapshot_seconds: HistogramVec,
67 pub catalog_snapshot_cache: IntCounterVec,
68 pub catalog_arc_strong_count: UIntGauge,
69 pub catalog_arc_weak_count: UIntGauge,
70 pub pgwire_recv_scheduling_delay_ms: HistogramVec,
71 pub catalog_transact_seconds: HistogramVec,
72 pub catalog_transact_phase_seconds: HistogramVec,
73 pub apply_catalog_implications_seconds: Histogram,
74 pub group_commit_catalog_upper_seconds: Histogram,
75 pub occ_retry_count: HistogramVec,
76 pub by_cluster: ClusterLabeledMetrics,
77}
78
79impl Metrics {
80 pub(crate) fn register_into(registry: &MetricsRegistry) -> Self {
81 Self {
82 query_total: registry.register(metric!(
83 name: "mz_query_total",
84 help: "The total number of queries issued of the given type since process start.",
85 var_labels: ["session_type", "statement_type"],
86 visibility: MetricVisibility::Public,
87 tags: [MetricTag::Environment],
88 )),
89 active_sessions: registry.register(metric!(
90 name: "mz_active_sessions",
91 help: "The number of active coordinator sessions.",
92 var_labels: ["session_type"],
93 visibility: MetricVisibility::Public,
94 tags: [MetricTag::Environment],
95 )),
96 active_subscribes: registry.register(metric!(
97 name: "mz_active_subscribes",
98 help: "The number of active SUBSCRIBE queries.",
99 var_labels: ["session_type"],
100 visibility: MetricVisibility::Public,
101 tags: [MetricTag::Environment],
102 )),
103 active_internal_subscribes: registry.register(metric!(
104 name: "mz_active_internal_subscribes",
105 help: "The number of active internal subscribes used by read-then-write operations and background maintenance.",
106 var_labels: ["session_type"],
107 )),
108 active_copy_tos: registry.register(metric!(
109 name: "mz_active_copy_tos",
110 help: "The number of active COPY TO queries.",
111 var_labels: ["session_type"],
112 )),
113 queue_busy_seconds: registry.register(metric!(
114 name: "mz_coord_queue_busy_seconds",
115 help: "The number of seconds the coord queue was processing before it was empty. This is a sampled metric and does not measure the full coord queue wait/idle times.",
116 buckets: histogram_seconds_buckets(0.000_128, 32.0)
117 )),
118 commands: registry.register(metric!(
119 name: "mz_adapter_commands",
120 help: "The total number of adapter commands issued of the given type since process start.",
121 var_labels: ["command_type", "status", "application_name"],
122 visibility: MetricVisibility::Public,
123 tags: [MetricTag::Environment],
124 )),
125 storage_usage_collection_time_seconds: registry.register(metric!(
126 name: "mz_storage_usage_collection_time_seconds",
127 help: "The number of seconds the coord spends collecting usage metrics from storage.",
128 buckets: histogram_seconds_buckets(0.000_128, 8.0)
129 )),
130 arrangement_sizes_collection_time_seconds: registry.register(metric!(
131 name: "mz_arrangement_sizes_collection_time_seconds",
132 help: "Seconds to read mz_object_arrangement_sizes and prepare history records for one snapshot.",
133 buckets: histogram_seconds_buckets(0.000_128, 8.0)
134 )),
135 arrangement_sizes_rows_written: registry.register(metric!(
136 name: "mz_arrangement_sizes_rows_written_total",
137 help: "Total rows appended to mz_object_arrangement_size_history since process start.",
138 )),
139 hydration_history_mutations: registry.register(metric!(
140 name: "mz_hydration_history_mutations_total",
141 help: "Total hydration-history collection and retention mutations since process start.",
142 var_labels: ["operation", "outcome"],
143 )),
144 hydration_history_retention_batch_full: registry.register(metric!(
145 name: "mz_hydration_history_retention_batch_full_total",
146 help: "Total hydration-history retention batches that were full. Repeated increments mean retention may not be keeping up with its schedule.",
147 )),
148 hydration_history_rows_affected: registry.register(metric!(
149 name: "mz_hydration_history_rows_affected_total",
150 help: "Total rows changed by hydration-history maintenance since process start.",
151 var_labels: ["action"],
152 )),
153 hydration_history_sweep_duration_seconds: registry.register(metric!(
154 name: "mz_hydration_history_sweep_duration_seconds",
155 help: "Wall time of a complete hydration-history collection and retention sweep.",
156 buckets: histogram_seconds_buckets(0.128, 1024.0),
157 )),
158 subscribe_outputs: registry.register(metric!(
159 name: "mz_subscribe_outputs",
160 help: "The total number of different subscribe outputs used",
161 var_labels: ["session_type", "subscribe_output"],
162 )),
163 canceled_peeks: registry.register(metric!(
164 name: "mz_canceled_peeks_total",
165 help: "The total number of canceled peeks since process start.",
166 )),
167 linearize_message_seconds: registry.register(metric!(
168 name: "mz_linearize_message_seconds",
169 help: "The number of seconds it takes to linearize strict serializable messages",
170 var_labels: ["type", "immediately_handled"],
171 buckets: histogram_seconds_buckets(0.000_128, 8.0),
172 )),
173 statement_logging_records: registry.register(metric! {
174 name: "mz_statement_logging_record_count",
175 help: "The total number of SQL statements tagged with whether or not they were recorded.",
176 var_labels: ["sample"],
177 }),
178 statement_logging_unsampled_bytes: registry.register(metric!(
179 name: "mz_statement_logging_unsampled_bytes",
180 help: "The total amount of SQL text that would have been logged if statement logging were unsampled.",
181 )),
182 statement_logging_actual_bytes: registry.register(metric!(
183 name: "mz_statement_logging_actual_bytes",
184 help: "The total amount of SQL text that was logged by statement logging.",
185 )),
186 message_batch: registry.register(metric!(
187 name: "mz_coordinator_message_batch_size",
188 help: "Message batch size handled by the coordinator.",
189 buckets: vec![0., 1., 2., 3., 4., 6., 8., 12., 16., 24., 32., 48., 64.],
190 )),
191 message_handling: registry.register(metric!(
192 name: "mz_slow_message_handling",
193 help: "Latency for ALL coordinator messages. 'slow' is in the name for legacy reasons, but is not accurate.",
194 var_labels: ["message_kind"],
195 buckets: histogram_seconds_buckets(0.000_128, 512.0),
196 )),
197 optimization_notices: registry.register(metric!(
198 name: "mz_optimization_notices",
199 help: "Number of optimization notices per notice type.",
200 var_labels: ["notice_type"],
201 )),
202 append_table_duration_seconds: registry.register(metric!(
203 name: "mz_append_table_duration_seconds",
204 help: "Latency for appending to any (user or system) table.",
205 buckets: histogram_seconds_buckets(0.128, 32.0),
206 )),
207 webhook_validation_reduce_failures: registry.register(metric!(
208 name: "mz_webhook_validation_reduce_failures",
209 help: "Count of how many times we've failed to reduce a webhook source's CHECK statement.",
210 var_labels: ["reason"],
211 )),
212 webhook_get_appender: registry.register(metric!(
213 name: "mz_webhook_get_appender_count",
214 help: "Count of getting a webhook appender from the Coordinator.",
215 )),
216 row_set_finishing_seconds: registry.register(metric!(
217 name: "mz_row_set_finishing_seconds",
218 help: "The time it takes to run RowSetFinishing::finish.",
219 buckets: histogram_seconds_buckets(0.000_128, 16.0),
220 )),
221 session_startup_table_writes_seconds: registry.register(metric!(
222 name: "mz_session_startup_table_writes_seconds",
223 help: "If we had to wait for builtin table writes before processing a query, how long did we wait for.",
224 buckets: histogram_seconds_buckets(0.000_008, 4.0),
225 )),
226 parse_seconds: registry.register(metric!(
227 name: "mz_parse_seconds",
228 help: "The time it takes to parse a SQL statement. (Works for both Simple Queries and the Extended Query protocol.)",
229 buckets: histogram_seconds_buckets(0.001, 8.0),
230 )),
231 pgwire_message_processing_seconds: registry.register(metric!(
232 name: "mz_pgwire_message_processing_seconds",
233 help: "The time it takes to process each of the pgwire message types, measured in the Adapter frontend",
234 var_labels: ["message_type"],
235 buckets: histogram_seconds_buckets(0.001, 512.0),
236 )),
237 result_rows_first_to_last_byte_seconds: registry.register(metric!(
238 name: "mz_result_rows_first_to_last_byte_seconds",
239 help: "The time from just before sending the first result row to sending a final response message after having successfully flushed the last result row to the connection. (This can span multiple FETCH statements.) (This is never observed for unbounded SUBSCRIBEs, i.e., which have no last result row.)",
240 var_labels: ["statement_type"],
241 buckets: histogram_seconds_buckets(0.001, 8192.0),
242 )),
243 pgwire_ensure_transaction_seconds: registry.register(metric!(
244 name: "mz_pgwire_ensure_transaction_seconds",
245 help: "The time it takes to run `ensure_transactions` when processing pgwire messages.",
246 var_labels: ["message_type"],
247 buckets: histogram_seconds_buckets(0.001, 512.0),
248 )),
249 catalog_snapshot_seconds: registry.register(metric!(
250 name: "mz_catalog_snapshot_seconds",
251 help: "The time it takes to fetch a catalog snapshot from the Coordinator. \
252 Only observed on session snapshot cache misses.",
253 var_labels: ["context"],
254 buckets: histogram_seconds_buckets(0.001, 512.0),
255 )),
256 catalog_snapshot_cache: registry.register(metric!(
257 name: "mz_catalog_snapshot_cache",
258 help: "Hits and misses of the session-side catalog snapshot cache. A miss \
259 costs a Coordinator round-trip.",
260 var_labels: ["context", "result"],
261 )),
262 catalog_arc_strong_count: registry.register(metric!(
263 name: "mz_catalog_arc_strong_count",
264 help: "The number of strong references to the current catalog snapshot: roughly, \
265 in-flight users plus a small constant baseline.",
266 )),
267 catalog_arc_weak_count: registry.register(metric!(
268 name: "mz_catalog_arc_weak_count",
269 help: "The number of weak references to the current catalog snapshot: sessions \
270 whose snapshot cache points at the current catalog version (older \
271 versions are not counted). Drops on catalog changes and recovers as \
272 session caches repopulate.",
273 )),
274 pgwire_recv_scheduling_delay_ms: registry.register(metric!(
275 name: "mz_pgwire_recv_scheduling_delay_ms",
276 help: "The time between a pgwire connection's receiver task being woken up by incoming data and getting polled.",
277 var_labels: ["message_type"],
278 buckets: histogram_milliseconds_buckets(0.128, 512000.),
279 )),
280 catalog_transact_seconds: registry.register(metric!(
281 name: "mz_catalog_transact_seconds",
282 help: "The time it takes to run various catalog transact methods.",
283 var_labels: ["method"],
284 buckets: histogram_seconds_buckets(0.001, 32.0),
285 )),
286 catalog_transact_phase_seconds: registry.register(metric!(
287 name: "mz_catalog_transact_phase_seconds",
288 help: "Wall time of the individual phases of a coordinator catalog transaction, to attribute where transact time is spent. Phases overlap and do not sum to mz_catalog_transact_seconds. The transact phase includes the durable catalog sync and commit.",
289 var_labels: ["phase"],
290 buckets: histogram_seconds_buckets(0.000_128, 32.0),
291 )),
292 apply_catalog_implications_seconds: registry.register(metric!(
293 name: "mz_apply_catalog_implications_seconds",
294 help: "The time it takes to apply catalog implications.",
295 buckets: histogram_seconds_buckets(0.001, 32.0),
296 )),
297 group_commit_catalog_upper_seconds: registry.register(metric!(
298 name: "mz_group_commit_catalog_upper_seconds",
299 help: "The time it takes to advance the catalog shard upper for a txns-shard write (group commits and table register/forget).",
300 buckets: histogram_seconds_buckets(0.001, 32.0),
301 )),
302 occ_retry_count: registry.register(metric!(
303 name: "mz_occ_read_then_write_retry_count",
304 help: "Number of OCC retries per read-then-write operation.",
305 var_labels: ["caller"],
306 buckets: vec![
307 0., 1., 2., 3., 5., 10., 25., 50., 100., 200., 300., 500., 750., 1000.,
308 ],
309 )),
310 by_cluster: ClusterLabeledMetrics::register_into(registry),
311 }
312 }
313
314 pub(crate) fn row_set_finishing_seconds(&self) -> Histogram {
315 self.row_set_finishing_seconds.clone()
316 }
317
318 pub(crate) fn session_metrics(&self) -> SessionMetrics {
319 SessionMetrics {
320 row_set_finishing_seconds: self.row_set_finishing_seconds(),
321 session_startup_table_writes_seconds: self.session_startup_table_writes_seconds.clone(),
322 query_total: self.query_total.clone(),
323 subscribe_outputs: self.subscribe_outputs.clone(),
324 by_cluster: self.by_cluster.clone(),
325 optimization_notices: self.optimization_notices.clone(),
326 statement_logging_records: self.statement_logging_records.clone(),
327 statement_logging_unsampled_bytes: self.statement_logging_unsampled_bytes.clone(),
328 statement_logging_actual_bytes: self.statement_logging_actual_bytes.clone(),
329 }
330 }
331}
332
333#[derive(Debug, Clone)]
335pub struct SessionMetrics {
336 row_set_finishing_seconds: Histogram,
337 session_startup_table_writes_seconds: Histogram,
338 query_total: IntCounterVec,
339 subscribe_outputs: IntCounterVec,
340 by_cluster: ClusterLabeledMetrics,
341 optimization_notices: IntCounterVec,
342 statement_logging_records: IntCounterVec,
343 statement_logging_unsampled_bytes: IntCounter,
344 statement_logging_actual_bytes: IntCounter,
345}
346
347impl SessionMetrics {
348 pub(crate) fn row_set_finishing_seconds(&self) -> &Histogram {
349 &self.row_set_finishing_seconds
350 }
351
352 pub(crate) fn session_startup_table_writes_seconds(&self) -> &Histogram {
353 &self.session_startup_table_writes_seconds
354 }
355
356 pub(crate) fn query_total(&self, label_values: &[&str]) -> GenericCounter<AtomicU64> {
357 self.query_total.with_label_values(label_values)
358 }
359
360 pub(crate) fn subscribe_outputs(&self, label_values: &[&str]) -> GenericCounter<AtomicU64> {
361 self.subscribe_outputs.with_label_values(label_values)
362 }
363
364 pub(crate) fn by_cluster(&self) -> &ClusterLabeledMetrics {
365 &self.by_cluster
366 }
367
368 pub(crate) fn optimization_notices(&self, label_values: &[&str]) -> GenericCounter<AtomicU64> {
369 self.optimization_notices.with_label_values(label_values)
370 }
371
372 pub(crate) fn statement_logging_records(
373 &self,
374 label_values: &[&str],
375 ) -> GenericCounter<AtomicU64> {
376 self.statement_logging_records
377 .with_label_values(label_values)
378 }
379
380 pub(crate) fn statement_logging_unsampled_bytes(&self) -> &IntCounter {
381 &self.statement_logging_unsampled_bytes
382 }
383
384 pub(crate) fn statement_logging_actual_bytes(&self) -> &IntCounter {
385 &self.statement_logging_actual_bytes
386 }
387}
388
389pub(crate) fn session_type_label_value(user: &User) -> &'static str {
390 match user.is_internal() {
391 true => "system",
392 false => "user",
393 }
394}
395
396pub fn statement_type_label_value<T>(stmt: &Statement<T>) -> &'static str
397where
398 T: AstInfo,
399{
400 statement_kind_label_value(StatementKind::from(stmt))
401}
402
403pub(crate) fn subscribe_output_label_value<T>(output: &SubscribeOutput<T>) -> &'static str
404where
405 T: AstInfo,
406{
407 match output {
408 SubscribeOutput::Diffs => "diffs",
409 SubscribeOutput::WithinTimestampOrderBy { .. } => "within_timestamp_order_by",
410 SubscribeOutput::EnvelopeUpsert { .. } => "envelope_upsert",
411 SubscribeOutput::EnvelopeDebezium { .. } => "envelope_debezium",
412 }
413}
414
415#[derive(Debug, Clone)]
421pub struct ClusterLabeledMetrics {
422 time_to_first_row_seconds: HistogramVec,
423 determine_timestamp: IntCounterVec,
424 timestamp_difference_for_bounded_staleness_ms: HistogramVec,
425 delete_on_cluster_drop: Vec<ClusterLabeledVec>,
428}
429
430#[derive(Debug, Clone)]
432enum ClusterLabeledVec {
433 Histogram(HistogramVec, &'static str),
434 Counter(IntCounterVec, &'static str),
435}
436
437impl ClusterLabeledVec {
438 fn delete_cluster(&self, cluster: &str) {
439 match self {
440 Self::Histogram(vec, label) => remove_children_with_label(vec, label, cluster),
441 Self::Counter(vec, label) => remove_children_with_label(vec, label, cluster),
442 }
443 }
444}
445
446impl ClusterLabeledMetrics {
447 fn register_into(registry: &MetricsRegistry) -> Self {
448 let mut vecs = Vec::new();
449 Self {
450 time_to_first_row_seconds: Self::register(
451 registry,
452 &mut vecs,
453 ClusterLabeledVec::Histogram,
454 "instance_id",
455 metric! {
456 name: "mz_time_to_first_row_seconds",
457 help: "Latency of an execute for a successful query from pgwire's perspective",
458 var_labels: ["instance_id", "isolation_level", "strategy", "application_name"],
459 buckets: histogram_seconds_buckets(0.000_512, 32.0)
461 },
462 ),
463 determine_timestamp: Self::register(
464 registry,
465 &mut vecs,
466 ClusterLabeledVec::Counter,
467 "compute_instance",
468 metric!(
469 name: "mz_determine_timestamp",
470 help: "The total number of calls to determine_timestamp.",
471 var_labels: ["respond_immediately", "isolation_level", "compute_instance"],
472 ),
473 ),
474 timestamp_difference_for_bounded_staleness_ms: Self::register(
475 registry,
476 &mut vecs,
477 ClusterLabeledVec::Histogram,
478 "compute_instance",
479 metric!(
480 name: "mz_timestamp_difference_for_bounded_staleness_ms",
481 help: "How much older bounded-staleness timestamps are compared to serializable, in milliseconds. Measures the actual staleness incurred.",
482 var_labels: ["compute_instance"],
483 buckets: histogram_milliseconds_buckets(1., 8000.),
484 ),
485 ),
486 delete_on_cluster_drop: vecs,
487 }
488 }
489
490 fn register<M: MakeCollector>(
495 registry: &MetricsRegistry,
496 vecs: &mut Vec<ClusterLabeledVec>,
497 wrap: fn(M, &'static str) -> ClusterLabeledVec,
498 cluster_label: &'static str,
499 opts: MakeCollectorOpts,
500 ) -> M {
501 assert!(
502 opts.opts
503 .variable_labels
504 .iter()
505 .any(|label| label == cluster_label),
506 "{cluster_label} is not a label of {}",
507 opts.opts.name
508 );
509 let vec: M = registry.register(opts);
510 vecs.push(wrap(vec.clone(), cluster_label));
513 vec
514 }
515
516 pub(crate) fn remove_cluster(&self, cluster_id: ClusterId) {
518 let cluster = cluster_id.to_string();
519 for vec in &self.delete_on_cluster_drop {
520 vec.delete_cluster(&cluster);
521 }
522 }
523
524 pub(crate) fn time_to_first_row_seconds(
526 &self,
527 cluster_id: Option<ClusterId>,
528 isolation_level: IsolationLevel,
529 strategy: Option<StatementExecutionStrategy>,
530 application_name: ApplicationNameHint,
531 ) -> Histogram {
532 let instance = match cluster_id {
533 Some(id) => Cow::Owned(id.to_string()),
534 None => Cow::Borrowed("none"),
535 };
536 self.time_to_first_row_seconds.with_label_values(&[
537 instance.as_ref(),
538 isolation_level.as_variant_str(),
539 strategy.map_or("none", |strategy| strategy.name()),
540 application_name.as_str(),
541 ])
542 }
543
544 pub(crate) fn determine_timestamp(
545 &self,
546 cluster_id: ClusterId,
547 respond_immediately: bool,
548 isolation_level: IsolationLevel,
549 ) -> GenericCounter<AtomicU64> {
550 self.determine_timestamp.with_label_values(&[
551 if respond_immediately { "true" } else { "false" },
552 isolation_level.as_variant_str(),
553 &cluster_id.to_string(),
554 ])
555 }
556
557 pub(crate) fn timestamp_difference_for_bounded_staleness_ms(
558 &self,
559 cluster_id: ClusterId,
560 ) -> Histogram {
561 self.timestamp_difference_for_bounded_staleness_ms
562 .with_label_values(&[&cluster_id.to_string()])
563 }
564}
565
566#[cfg(test)]
567mod tests {
568 use std::collections::BTreeSet;
569
570 use super::*;
571
572 const U1: ClusterId = ClusterId::User(1);
573 const U2: ClusterId = ClusterId::User(2);
574
575 fn families(registry: &MetricsRegistry) -> BTreeSet<String> {
576 registry
577 .gather()
578 .into_iter()
579 .map(|family| family.name().to_string())
580 .collect()
581 }
582
583 fn families_with_label_value(registry: &MetricsRegistry, value: &str) -> BTreeSet<String> {
585 registry
586 .gather()
587 .into_iter()
588 .filter(|family| {
589 family
590 .get_metric()
591 .iter()
592 .flat_map(|metric| metric.get_label())
593 .any(|label| label.value() == value)
594 })
595 .map(|family| family.name().to_string())
596 .collect()
597 }
598
599 fn observe_first_row(
600 metrics: &ClusterLabeledMetrics,
601 cluster_id: Option<ClusterId>,
602 strategy: Option<StatementExecutionStrategy>,
603 ) {
604 metrics
605 .time_to_first_row_seconds(
606 cluster_id,
607 IsolationLevel::StrictSerializable,
608 strategy,
609 ApplicationNameHint::from_str("psql"),
610 )
611 .observe(0.1);
612 }
613
614 #[mz_ore::test]
615 fn dropping_a_cluster_removes_its_series() {
616 let registry = MetricsRegistry::new();
617 let metrics = ClusterLabeledMetrics::register_into(®istry);
618
619 observe_first_row(&metrics, Some(U1), None);
620 observe_first_row(
621 &metrics,
622 Some(U1),
623 Some(StatementExecutionStrategy::FastPath),
624 );
625 observe_first_row(&metrics, Some(U2), None);
626 metrics
627 .determine_timestamp(U1, true, IsolationLevel::Serializable)
628 .inc();
629 metrics
630 .timestamp_difference_for_bounded_staleness_ms(U1)
631 .observe(5.0);
632 assert_eq!(
633 families_with_label_value(®istry, "u1"),
634 families(®istry),
635 "every registered family needs a u1 series for the drop to be exercised"
636 );
637
638 metrics.remove_cluster(U1);
639
640 assert_eq!(
641 families_with_label_value(®istry, "u1"),
642 BTreeSet::new(),
643 "u1 series survived the drop"
644 );
645 assert_eq!(
646 families_with_label_value(®istry, "u2"),
647 BTreeSet::from(["mz_time_to_first_row_seconds".to_string()]),
648 "the other cluster's series must be untouched"
649 );
650 }
651
652 #[mz_ore::test]
653 fn statements_without_a_cluster_are_unaffected_by_drops() {
654 let registry = MetricsRegistry::new();
655 let metrics = ClusterLabeledMetrics::register_into(®istry);
656 observe_first_row(&metrics, None, Some(StatementExecutionStrategy::Constant));
657 observe_first_row(&metrics, Some(U1), None);
658 metrics.remove_cluster(U1);
659 assert_eq!(families_with_label_value(®istry, "u1"), BTreeSet::new());
660 assert_eq!(
661 families_with_label_value(®istry, "none"),
662 BTreeSet::from(["mz_time_to_first_row_seconds".to_string()])
663 );
664 }
665
666 #[mz_ore::test]
667 #[should_panic(expected = "is not a label of mz_test")]
668 fn registering_under_a_label_the_metric_lacks_panics() {
669 let registry = MetricsRegistry::new();
670 let _: IntCounterVec = ClusterLabeledMetrics::register(
671 ®istry,
672 &mut Vec::new(),
673 ClusterLabeledVec::Counter,
674 "cluster_id",
675 metric!(
676 name: "mz_test",
677 help: "test",
678 var_labels: ["compute_instance"],
679 ),
680 );
681 }
682}