1use async_stream::stream;
13use mz_persist_types::stats::PartStatsMetrics;
14use std::collections::BTreeMap;
15use std::sync::atomic::{AtomicBool, Ordering};
16use std::sync::{Arc, Mutex, Weak};
17use std::time::{Duration, Instant};
18use tokio::sync::{OnceCell, OwnedSemaphorePermit, Semaphore};
19
20use async_trait::async_trait;
21use bytes::Bytes;
22use futures_util::StreamExt;
23use mz_dyncfg::ConfigSet;
24use mz_ore::bytes::SegmentedBytes;
25use mz_ore::cast::{CastFrom, CastLossy};
26use mz_ore::instrument;
27use mz_ore::metric;
28use mz_ore::metrics::aggregation::{AggregatedFamilies, AggregatedFamily, AggregationKey};
29use mz_ore::metrics::{
30 ComputedGauge, ComputedUIntGauge, Counter, DeleteOnDropCounter, DeleteOnDropGauge, IntCounter,
31 MakeCollector, MetricsRegistry, PrometheusOpts, UIntGauge, UIntGaugeVec, raw,
32};
33use mz_ore::stats::histogram_seconds_buckets;
34use mz_persist::location::{
35 Blob, BlobMetadata, CaSResult, Consensus, ExternalError, ResultStream, SeqNo, VersionedData,
36};
37use mz_persist::metrics::{BlobHedgeMetrics, ColumnarMetrics, S3BlobMetrics};
38use mz_persist::retry::RetryStream;
39use mz_persist_types::Codec64;
40use mz_postgres_client::metrics::PostgresClientMetrics;
41use prometheus::core::{AtomicI64, AtomicU64, Collector, Desc, GenericGauge};
42use prometheus::proto::MetricFamily;
43use prometheus::{CounterVec, Gauge, GaugeVec, Histogram, HistogramVec, IntCounterVec};
44use regex::Regex;
45use timely::progress::Antichain;
46use tokio_metrics::TaskMonitor;
47use tracing::{Instrument, debug, info, info_span, warn};
48
49use crate::cfg::{
50 PER_SHARD_METRICS_ENABLE_REGEX, SHARD_INFO_METRIC, SHARD_METRICS, ShardMetricsExport,
51};
52use crate::fetch::{FETCH_SEMAPHORE_COST_ADJUSTMENT, FETCH_SEMAPHORE_PERMIT_ADJUSTMENT};
53use crate::internal::paths::BlobKey;
54use crate::{PersistConfig, ShardId};
55
56pub struct Metrics {
61 _vecs: MetricsVecs,
62 _uptime: ComputedGauge,
63
64 pub blob: BlobMetrics,
66 pub consensus: ConsensusMetrics,
68 pub cmds: CmdsMetrics,
70 pub retries: RetriesMetrics,
72 pub user: BatchWriteMetrics,
75 pub read: BatchPartReadMetrics,
77 pub compaction: CompactionMetrics,
79 pub gc: GcMetrics,
81 pub lease: LeaseMetrics,
83 pub codecs: CodecsMetrics,
85 pub state: StateMetrics,
87 pub shards: ShardsMetrics,
89 pub audit: UsageAuditMetrics,
91 pub locks: LocksMetrics,
93 pub watch: WatchMetrics,
95 pub pubsub_client: PubSubClientMetrics,
97 pub pushdown: PushdownMetrics,
99 pub consolidation: ConsolidationMetrics,
101 pub blob_cache_mem: BlobMemCache,
103 pub tasks: TasksMetrics,
105 pub columnar: ColumnarMetrics,
107 pub schema: SchemaMetrics,
109 pub inline: InlineMetrics,
111 pub(crate) semaphore: SemaphoreMetrics,
113
114 pub sink: SinkMetrics,
116 pub backpressure: BackpressureMetrics,
118
119 pub s3_blob: S3BlobMetrics,
121 pub blob_hedge: BlobHedgeMetrics,
123 pub postgres_consensus: PostgresClientMetrics,
125
126 #[allow(dead_code)]
127 pub(crate) registry: MetricsRegistry,
128}
129
130impl std::fmt::Debug for Metrics {
131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132 f.debug_struct("Metrics").finish_non_exhaustive()
133 }
134}
135
136impl Metrics {
137 pub fn new(cfg: &PersistConfig, registry: &MetricsRegistry) -> Self {
139 let vecs = MetricsVecs::new(registry);
140 let start = Instant::now();
141 let uptime = registry.register_computed_gauge(
142 metric!(
143 name: "mz_persist_metadata_seconds",
144 help: "server uptime, labels are build metadata",
145 const_labels: {
146 "version" => cfg.build_version,
147 "build_type" => if cfg!(release) { "release" } else { "debug" }
148 },
149 ),
150 move || start.elapsed().as_secs_f64(),
151 );
152 let s3_blob = S3BlobMetrics::new(registry);
153 let columnar = ColumnarMetrics::new(registry);
154 let mut per_shard_filter = PerShardMetricsFilter {
155 configs: Arc::clone(&cfg.configs),
156 mode: (String::new(), ShardMetricsExport::default()),
157 invalid_mode: registry.register(metric!(
158 name: "mz_persist_shard_metrics_mode_invalid",
159 help: "count of metrics scrapes that found persist_shard_metrics unrecognized and so exported the default",
160 )),
161 allow: (String::new(), None),
162 invalid_regex: registry.register(metric!(
163 name: "mz_persist_per_shard_metrics_regex_invalid",
164 help: "count of metrics scrapes that found persist_per_shard_metrics_enable_regex invalid and so kept no per-shard series",
165 )),
166 };
167 registry.register_postprocessor(move |families| per_shard_filter.filter(families));
168 Metrics {
169 blob: vecs.blob_metrics(),
170 consensus: vecs.consensus_metrics(),
171 cmds: vecs.cmds_metrics(registry),
172 retries: vecs.retries_metrics(),
173 codecs: vecs.codecs_metrics(),
174 user: BatchWriteMetrics::new(registry, "user"),
175 read: vecs.batch_part_read_metrics(),
176 compaction: CompactionMetrics::new(registry),
177 gc: GcMetrics::new(registry),
178 lease: LeaseMetrics::new(registry),
179 state: StateMetrics::new(registry),
180 shards: ShardsMetrics::new(registry, Arc::clone(&cfg.configs)),
181 audit: UsageAuditMetrics::new(registry),
182 locks: vecs.locks_metrics(),
183 watch: WatchMetrics::new(registry),
184 pubsub_client: PubSubClientMetrics::new(registry),
185 pushdown: PushdownMetrics::new(registry),
186 consolidation: ConsolidationMetrics::new(registry),
187 blob_cache_mem: BlobMemCache::new(registry),
188 tasks: TasksMetrics::new(registry),
189 columnar,
190 schema: SchemaMetrics::new(registry),
191 inline: InlineMetrics::new(registry),
192 semaphore: SemaphoreMetrics::new(cfg.clone(), registry.clone()),
193 sink: SinkMetrics::new(registry),
194 backpressure: BackpressureMetrics::new(registry),
195 s3_blob,
196 blob_hedge: BlobHedgeMetrics::new(registry),
197 postgres_consensus: PostgresClientMetrics::new(registry, "mz_persist"),
198 _vecs: vecs,
199 _uptime: uptime,
200 registry: registry.clone(),
201 }
202 }
203
204 pub fn write_amplification(&self) -> f64 {
209 let total_written = self.blob.set.bytes.get();
212 let user_written = self.user.goodbytes.get();
213 #[allow(clippy::as_conversions)]
214 {
215 total_written as f64 / user_written as f64
216 }
217 }
218}
219
220#[derive(Debug)]
221struct MetricsVecs {
222 cmd_started: IntCounterVec,
223 cmd_cas_mismatch: IntCounterVec,
224 cmd_succeeded: IntCounterVec,
225 cmd_failed: IntCounterVec,
226 cmd_seconds: CounterVec,
227
228 external_op_started: IntCounterVec,
229 external_op_succeeded: IntCounterVec,
230 external_op_failed: IntCounterVec,
231 external_op_bytes: IntCounterVec,
232 external_op_seconds: CounterVec,
233 external_consensus_truncated_count: IntCounter,
234 external_blob_delete_noop_count: IntCounter,
235 external_blob_sizes: Histogram,
236 external_rtt_latency: GaugeVec,
237 external_op_latency: HistogramVec,
238
239 retry_started: IntCounterVec,
240 retry_finished: IntCounterVec,
241 retry_retries: IntCounterVec,
242 retry_sleep_seconds: CounterVec,
243
244 encode_count: IntCounterVec,
245 encode_seconds: CounterVec,
246 decode_count: IntCounterVec,
247 decode_seconds: CounterVec,
248
249 read_part_bytes: IntCounterVec,
250 read_part_goodbytes: IntCounterVec,
251 read_part_count: IntCounterVec,
252 read_part_seconds: CounterVec,
253 read_ts_rewrite: IntCounterVec,
254
255 lock_acquire_count: IntCounterVec,
256 lock_blocking_acquire_count: IntCounterVec,
257 lock_blocking_seconds: CounterVec,
258
259 alerts_metrics: Arc<AlertsMetrics>,
261}
262
263impl MetricsVecs {
264 fn new(registry: &MetricsRegistry) -> Self {
265 MetricsVecs {
266 cmd_started: registry.register(metric!(
267 name: "mz_persist_cmd_started_count",
268 help: "count of commands started",
269 var_labels: ["cmd"],
270 )),
271 cmd_cas_mismatch: registry.register(metric!(
272 name: "mz_persist_cmd_cas_mismatch_count",
273 help: "count of command retries from CaS mismatch",
274 var_labels: ["cmd"],
275 )),
276 cmd_succeeded: registry.register(metric!(
277 name: "mz_persist_cmd_succeeded_count",
278 help: "count of commands succeeded",
279 var_labels: ["cmd"],
280 )),
281 cmd_failed: registry.register(metric!(
282 name: "mz_persist_cmd_failed_count",
283 help: "count of commands failed",
284 var_labels: ["cmd"],
285 )),
286 cmd_seconds: registry.register(metric!(
287 name: "mz_persist_cmd_seconds",
288 help: "time spent applying commands",
289 var_labels: ["cmd"],
290 )),
291
292 external_op_started: registry.register(metric!(
293 name: "mz_persist_external_started_count",
294 help: "count of external service calls started",
295 var_labels: ["op"],
296 )),
297 external_op_succeeded: registry.register(metric!(
298 name: "mz_persist_external_succeeded_count",
299 help: "count of external service calls succeeded",
300 var_labels: ["op"],
301 )),
302 external_op_failed: registry.register(metric!(
303 name: "mz_persist_external_failed_count",
304 help: "count of external service calls failed",
305 var_labels: ["op"],
306 )),
307 external_op_bytes: registry.register(metric!(
308 name: "mz_persist_external_bytes_count",
309 help: "total size represented by external service calls",
310 var_labels: ["op"],
311 )),
312 external_op_seconds: registry.register(metric!(
313 name: "mz_persist_external_seconds",
314 help: "time spent in external service calls",
315 var_labels: ["op"],
316 )),
317 external_consensus_truncated_count: registry.register(metric!(
318 name: "mz_persist_external_consensus_truncated_count",
319 help: "count of versions deleted by consensus truncate calls",
320 )),
321 external_blob_delete_noop_count: registry.register(metric!(
322 name: "mz_persist_external_blob_delete_noop_count",
323 help: "count of blob delete calls that deleted a non-existent key",
324 )),
325 external_blob_sizes: registry.register(metric!(
326 name: "mz_persist_external_blob_sizes",
327 help: "histogram of blob sizes at put time",
328 buckets: mz_ore::stats::HISTOGRAM_BYTE_BUCKETS.to_vec(),
329 )),
330 external_rtt_latency: registry.register(metric!(
331 name: "mz_persist_external_rtt_latency",
332 help: "roundtrip-time to external service as seen by this process",
333 var_labels: ["external"],
334 )),
335 external_op_latency: registry.register(metric!(
336 name: "mz_persist_external_op_latency",
337 help: "rountrip latency observed by individual performance-critical operations",
338 var_labels: ["op"],
339 buckets: histogram_seconds_buckets(0.000_500, 32.0),
342 )),
343
344 retry_started: registry.register(metric!(
345 name: "mz_persist_retry_started_count",
346 help: "count of retry loops started",
347 var_labels: ["op"],
348 )),
349 retry_finished: registry.register(metric!(
350 name: "mz_persist_retry_finished_count",
351 help: "count of retry loops finished",
352 var_labels: ["op"],
353 )),
354 retry_retries: registry.register(metric!(
355 name: "mz_persist_retry_retries_count",
356 help: "count of total attempts by retry loops",
357 var_labels: ["op"],
358 )),
359 retry_sleep_seconds: registry.register(metric!(
360 name: "mz_persist_retry_sleep_seconds",
361 help: "time spent in retry loop backoff",
362 var_labels: ["op"],
363 )),
364
365 encode_count: registry.register(metric!(
366 name: "mz_persist_encode_count",
367 help: "count of op encodes",
368 var_labels: ["op"],
369 )),
370 encode_seconds: registry.register(metric!(
371 name: "mz_persist_encode_seconds",
372 help: "time spent in op encodes",
373 var_labels: ["op"],
374 )),
375 decode_count: registry.register(metric!(
376 name: "mz_persist_decode_count",
377 help: "count of op decodes",
378 var_labels: ["op"],
379 )),
380 decode_seconds: registry.register(metric!(
381 name: "mz_persist_decode_seconds",
382 help: "time spent in op decodes",
383 var_labels: ["op"],
384 )),
385
386 read_part_bytes: registry.register(metric!(
387 name: "mz_persist_read_batch_part_bytes",
388 help: "total encoded size of batch parts read",
389 var_labels: ["op"],
390 )),
391 read_part_goodbytes: registry.register(metric!(
392 name: "mz_persist_read_batch_part_goodbytes",
393 help: "total logical size of batch parts read",
394 var_labels: ["op"],
395 )),
396 read_part_count: registry.register(metric!(
397 name: "mz_persist_read_batch_part_count",
398 help: "count of batch parts read",
399 var_labels: ["op"],
400 )),
401 read_part_seconds: registry.register(metric!(
402 name: "mz_persist_read_batch_part_seconds",
403 help: "time spent reading batch parts",
404 var_labels: ["op"],
405 )),
406 read_ts_rewrite: registry.register(metric!(
407 name: "mz_persist_read_ts_rewite",
408 help: "count of updates read with rewritten ts",
409 var_labels: ["op"],
410 )),
411
412 lock_acquire_count: registry.register(metric!(
413 name: "mz_persist_lock_acquire_count",
414 help: "count of locks acquired",
415 var_labels: ["op"],
416 )),
417 lock_blocking_acquire_count: registry.register(metric!(
418 name: "mz_persist_lock_blocking_acquire_count",
419 help: "count of locks acquired that required blocking",
420 var_labels: ["op"],
421 )),
422 lock_blocking_seconds: registry.register(metric!(
423 name: "mz_persist_lock_blocking_seconds",
424 help: "time spent blocked for a lock",
425 var_labels: ["op"],
426 )),
427
428 alerts_metrics: Arc::new(AlertsMetrics::new(registry)),
429 }
430 }
431
432 fn cmds_metrics(&self, registry: &MetricsRegistry) -> CmdsMetrics {
433 CmdsMetrics {
434 init_state: self.cmd_metrics("init_state"),
435 add_rollup: self.cmd_metrics("add_rollup"),
436 remove_rollups: self.cmd_metrics("remove_rollups"),
437 upgrade_version: self.cmd_metrics("upgrade_version"),
438 register: self.cmd_metrics("register"),
439 compare_and_append: self.cmd_metrics("compare_and_append"),
440 compare_and_append_noop: registry.register(metric!(
441 name: "mz_persist_cmd_compare_and_append_noop",
442 help: "count of compare_and_append retries that were discoverd to have already committed",
443 )),
444 compare_and_downgrade_since: self.cmd_metrics("compare_and_downgrade_since"),
445 downgrade_since: self.cmd_metrics("downgrade_since"),
446 expire_reader: self.cmd_metrics("expire_reader"),
447 expire_writer: self.cmd_metrics("expire_writer"),
448 merge_res: self.cmd_metrics("merge_res"),
449 become_tombstone: self.cmd_metrics("become_tombstone"),
450 compare_and_evolve_schema: self.cmd_metrics("compare_and_evolve_schema"),
451 spine_exert: self.cmd_metrics("spine_exert"),
452 fetch_upper_count: registry.register(metric!(
453 name: "mz_persist_cmd_fetch_upper_count",
454 help: "count of fetch_upper calls",
455 ))
456 }
457 }
458
459 fn cmd_metrics(&self, cmd: &str) -> CmdMetrics {
460 CmdMetrics {
461 name: cmd.to_owned(),
462 started: self.cmd_started.with_label_values(&[cmd]),
463 succeeded: self.cmd_succeeded.with_label_values(&[cmd]),
464 cas_mismatch: self.cmd_cas_mismatch.with_label_values(&[cmd]),
465 failed: self.cmd_failed.with_label_values(&[cmd]),
466 seconds: self.cmd_seconds.with_label_values(&[cmd]),
467 }
468 }
469
470 fn retries_metrics(&self) -> RetriesMetrics {
471 RetriesMetrics {
472 determinate: RetryDeterminate {
473 apply_unbatched_cmd_cas: self.retry_metrics("apply_unbatched_cmd::cas"),
474 },
475 external: RetryExternal {
476 batch_delete: Arc::new(self.retry_metrics("batch::delete")),
477 batch_set: self.retry_metrics("batch::set"),
478 blob_open: self.retry_metrics("blob::open"),
479 compaction_noop_delete: Arc::new(self.retry_metrics("compaction_noop::delete")),
480 consensus_open: self.retry_metrics("consensus::open"),
481 fetch_batch_get: self.retry_metrics("fetch_batch::get"),
482 fetch_state_scan: self.retry_metrics("fetch_state::scan"),
483 gc_truncate: self.retry_metrics("gc::truncate"),
484 maybe_init_cas: self.retry_metrics("maybe_init::cas"),
485 rollup_delete: self.retry_metrics("rollup::delete"),
486 rollup_get: self.retry_metrics("rollup::get"),
487 rollup_set: self.retry_metrics("rollup::set"),
488 hollow_run_get: self.retry_metrics("hollow_run::get"),
489 hollow_run_set: self.retry_metrics("hollow_run::set"),
490 storage_usage_shard_size: self.retry_metrics("storage_usage::shard_size"),
491 },
492 compare_and_append_idempotent: self.retry_metrics("compare_and_append_idempotent"),
493 fetch_latest_state: self.retry_metrics("fetch_latest_state"),
494 fetch_live_states: self.retry_metrics("fetch_live_states"),
495 idempotent_cmd: self.retry_metrics("idempotent_cmd"),
496 next_listen_batch: self.retry_metrics("next_listen_batch"),
497 snapshot: self.retry_metrics("snapshot"),
498 }
499 }
500
501 fn retry_metrics(&self, name: &str) -> RetryMetrics {
502 RetryMetrics {
503 name: name.to_owned(),
504 started: self.retry_started.with_label_values(&[name]),
505 finished: self.retry_finished.with_label_values(&[name]),
506 retries: self.retry_retries.with_label_values(&[name]),
507 sleep_seconds: self.retry_sleep_seconds.with_label_values(&[name]),
508 }
509 }
510
511 fn codecs_metrics(&self) -> CodecsMetrics {
512 CodecsMetrics {
513 state: self.codec_metrics("state"),
514 state_diff: self.codec_metrics("state_diff"),
515 batch: self.codec_metrics("batch"),
516 key: self.codec_metrics("key"),
517 val: self.codec_metrics("val"),
518 }
519 }
520
521 fn codec_metrics(&self, op: &str) -> CodecMetrics {
522 CodecMetrics {
523 encode_count: self.encode_count.with_label_values(&[op]),
524 encode_seconds: self.encode_seconds.with_label_values(&[op]),
525 decode_count: self.decode_count.with_label_values(&[op]),
526 decode_seconds: self.decode_seconds.with_label_values(&[op]),
527 }
528 }
529
530 fn blob_metrics(&self) -> BlobMetrics {
531 BlobMetrics {
532 set: self.external_op_metrics("blob_set", true),
533 get: self.external_op_metrics("blob_get", true),
534 list_keys: self.external_op_metrics("blob_list_keys", false),
535 delete: self.external_op_metrics("blob_delete", false),
536 restore: self.external_op_metrics("restore", false),
537 delete_noop: self.external_blob_delete_noop_count.clone(),
538 blob_sizes: self.external_blob_sizes.clone(),
539 rtt_latency: self.external_rtt_latency.with_label_values(&["blob"]),
540 }
541 }
542
543 fn consensus_metrics(&self) -> ConsensusMetrics {
544 ConsensusMetrics {
545 list_keys: self.external_op_metrics("consensus_list_keys", false),
546 head: self.external_op_metrics("consensus_head", false),
547 compare_and_set: self.external_op_metrics("consensus_cas", true),
548 scan: self.external_op_metrics("consensus_scan", false),
549 truncate: self.external_op_metrics("consensus_truncate", false),
550 truncated_count: self.external_consensus_truncated_count.clone(),
551 rtt_latency: self.external_rtt_latency.with_label_values(&["consensus"]),
552 }
553 }
554
555 fn external_op_metrics(&self, op: &str, latency_histogram: bool) -> ExternalOpMetrics {
556 ExternalOpMetrics {
557 started: self.external_op_started.with_label_values(&[op]),
558 succeeded: self.external_op_succeeded.with_label_values(&[op]),
559 failed: self.external_op_failed.with_label_values(&[op]),
560 bytes: self.external_op_bytes.with_label_values(&[op]),
561 seconds: self.external_op_seconds.with_label_values(&[op]),
562 seconds_histogram: if latency_histogram {
563 Some(self.external_op_latency.with_label_values(&[op]))
564 } else {
565 None
566 },
567 alerts_metrics: Arc::clone(&self.alerts_metrics),
568 }
569 }
570
571 fn batch_part_read_metrics(&self) -> BatchPartReadMetrics {
572 BatchPartReadMetrics {
573 listen: self.read_metrics("listen"),
574 snapshot: self.read_metrics("snapshot"),
575 batch_fetcher: self.read_metrics("batch_fetcher"),
576 compaction: self.read_metrics("compaction"),
577 unindexed: self.read_metrics("unindexed"),
578 }
579 }
580
581 fn read_metrics(&self, op: &str) -> ReadMetrics {
582 ReadMetrics {
583 part_bytes: self.read_part_bytes.with_label_values(&[op]),
584 part_goodbytes: self.read_part_goodbytes.with_label_values(&[op]),
585 part_count: self.read_part_count.with_label_values(&[op]),
586 seconds: self.read_part_seconds.with_label_values(&[op]),
587 ts_rewrite: self.read_ts_rewrite.with_label_values(&[op]),
588 }
589 }
590
591 fn locks_metrics(&self) -> LocksMetrics {
592 LocksMetrics {
593 applier_read_cacheable: self.lock_metrics("applier_read_cacheable"),
594 applier_read_noncacheable: self.lock_metrics("applier_read_noncacheable"),
595 applier_write: self.lock_metrics("applier_write"),
596 watch: self.lock_metrics("watch"),
597 }
598 }
599
600 fn lock_metrics(&self, op: &str) -> LockMetrics {
601 LockMetrics {
602 acquire_count: self.lock_acquire_count.with_label_values(&[op]),
603 blocking_acquire_count: self.lock_blocking_acquire_count.with_label_values(&[op]),
604 blocking_seconds: self.lock_blocking_seconds.with_label_values(&[op]),
605 }
606 }
607}
608
609#[derive(Debug)]
610pub struct CmdMetrics {
611 pub(crate) name: String,
612 pub(crate) started: IntCounter,
613 pub(crate) cas_mismatch: IntCounter,
614 pub(crate) succeeded: IntCounter,
615 pub(crate) failed: IntCounter,
616 pub(crate) seconds: Counter,
617}
618
619impl CmdMetrics {
620 pub async fn run_cmd<R, E, F, CmdFn>(
621 &self,
622 shard_metrics: &ShardMetrics,
623 cmd_fn: CmdFn,
624 ) -> Result<R, E>
625 where
626 F: std::future::Future<Output = Result<R, E>>,
627 CmdFn: FnOnce() -> F,
628 {
629 self.started.inc();
630 let start = Instant::now();
631 let res = cmd_fn().await;
632 self.seconds.inc_by(start.elapsed().as_secs_f64());
633 match res.as_ref() {
634 Ok(_) => {
635 self.succeeded.inc();
636 shard_metrics.cmd_succeeded.inc();
637 }
638 Err(_) => self.failed.inc(),
639 };
640 res
641 }
642}
643
644#[derive(Debug)]
645pub struct CmdsMetrics {
646 pub(crate) init_state: CmdMetrics,
647 pub(crate) add_rollup: CmdMetrics,
648 pub(crate) remove_rollups: CmdMetrics,
649 pub(crate) upgrade_version: CmdMetrics,
650 pub(crate) register: CmdMetrics,
651 pub(crate) compare_and_append: CmdMetrics,
652 pub(crate) compare_and_append_noop: IntCounter,
653 pub(crate) compare_and_downgrade_since: CmdMetrics,
654 pub(crate) downgrade_since: CmdMetrics,
655 pub(crate) expire_reader: CmdMetrics,
656 pub(crate) expire_writer: CmdMetrics,
657 pub(crate) merge_res: CmdMetrics,
658 pub(crate) become_tombstone: CmdMetrics,
659 pub(crate) compare_and_evolve_schema: CmdMetrics,
660 pub(crate) spine_exert: CmdMetrics,
661 pub(crate) fetch_upper_count: IntCounter,
662}
663
664#[derive(Debug)]
665pub struct RetryMetrics {
666 pub(crate) name: String,
667 pub(crate) started: IntCounter,
668 pub(crate) finished: IntCounter,
669 pub(crate) retries: IntCounter,
670 pub(crate) sleep_seconds: Counter,
671}
672
673impl RetryMetrics {
674 pub(crate) fn stream(&self, retry: RetryStream) -> MetricsRetryStream {
675 MetricsRetryStream::new(retry, self)
676 }
677}
678
679#[derive(Debug)]
680pub struct RetryDeterminate {
681 pub(crate) apply_unbatched_cmd_cas: RetryMetrics,
682}
683
684#[derive(Debug)]
685pub struct RetryExternal {
686 pub(crate) batch_delete: Arc<RetryMetrics>,
687 pub(crate) batch_set: RetryMetrics,
688 pub(crate) blob_open: RetryMetrics,
689 pub(crate) compaction_noop_delete: Arc<RetryMetrics>,
690 pub(crate) consensus_open: RetryMetrics,
691 pub(crate) fetch_batch_get: RetryMetrics,
692 pub(crate) fetch_state_scan: RetryMetrics,
693 pub(crate) gc_truncate: RetryMetrics,
694 pub(crate) maybe_init_cas: RetryMetrics,
695 pub(crate) rollup_delete: RetryMetrics,
696 pub(crate) rollup_get: RetryMetrics,
697 pub(crate) rollup_set: RetryMetrics,
698 pub(crate) hollow_run_get: RetryMetrics,
699 pub(crate) hollow_run_set: RetryMetrics,
700 pub(crate) storage_usage_shard_size: RetryMetrics,
701}
702
703#[derive(Debug)]
704pub struct RetriesMetrics {
705 pub(crate) determinate: RetryDeterminate,
706 pub(crate) external: RetryExternal,
707
708 pub(crate) compare_and_append_idempotent: RetryMetrics,
709 pub(crate) fetch_latest_state: RetryMetrics,
710 pub(crate) fetch_live_states: RetryMetrics,
711 pub(crate) idempotent_cmd: RetryMetrics,
712 pub(crate) next_listen_batch: RetryMetrics,
713 pub(crate) snapshot: RetryMetrics,
714}
715
716#[derive(Debug)]
717pub struct BatchPartReadMetrics {
718 pub(crate) listen: ReadMetrics,
719 pub(crate) snapshot: ReadMetrics,
720 pub(crate) batch_fetcher: ReadMetrics,
721 pub(crate) compaction: ReadMetrics,
722 pub(crate) unindexed: ReadMetrics,
723}
724
725#[derive(Debug, Clone)]
726pub struct ReadMetrics {
727 pub(crate) part_bytes: IntCounter,
728 pub(crate) part_goodbytes: IntCounter,
729 pub(crate) part_count: IntCounter,
730 pub(crate) seconds: Counter,
731 pub(crate) ts_rewrite: IntCounter,
732}
733
734#[derive(Debug, Clone)]
737pub struct BatchWriteMetrics {
738 pub(crate) bytes: IntCounter,
739 pub(crate) goodbytes: IntCounter,
740 pub(crate) seconds: Counter,
741 pub(crate) write_stalls: IntCounter,
742 pub(crate) key_lower_too_big: IntCounter,
743
744 pub(crate) unordered: IntCounter,
745 pub(crate) codec_order: IntCounter,
746 pub(crate) structured_order: IntCounter,
747 _order_counts: IntCounterVec,
748
749 pub(crate) step_stats: Counter,
750 pub(crate) step_part_writing: Counter,
751 pub(crate) step_inline: Counter,
752}
753
754impl BatchWriteMetrics {
755 fn new(registry: &MetricsRegistry, name: &str) -> Self {
756 let order_counts: IntCounterVec = registry.register(metric!(
757 name: format!("mz_persist_{}_write_batch_order", name),
758 help: "count of batches by the data ordering",
759 var_labels: ["order"],
760 ));
761 let unordered = order_counts.with_label_values(&["unordered"]);
762 let codec_order = order_counts.with_label_values(&["codec"]);
763 let structured_order = order_counts.with_label_values(&["structured"]);
764
765 BatchWriteMetrics {
766 bytes: registry.register(metric!(
767 name: format!("mz_persist_{}_bytes", name),
768 help: format!("total encoded size of {} batches written", name),
769 )),
770 goodbytes: registry.register(metric!(
771 name: format!("mz_persist_{}_goodbytes", name),
772 help: format!("total logical size of {} batches written", name),
773 )),
774 seconds: registry.register(metric!(
775 name: format!("mz_persist_{}_write_batch_part_seconds", name),
776 help: format!("time spent writing {} batches", name),
777 )),
778 write_stalls: registry.register(metric!(
779 name: format!("mz_persist_{}_write_stall_count", name),
780 help: format!(
781 "count of {} writes stalling to await max outstanding reqs",
782 name
783 ),
784 )),
785 key_lower_too_big: registry.register(metric!(
786 name: format!("mz_persist_{}_key_lower_too_big", name),
787 help: format!(
788 "count of {} writes that were unable to write a key lower, because the size threshold was too low",
789 name
790 ),
791 )),
792 unordered,
793 codec_order,
794 structured_order,
795 _order_counts: order_counts,
796 step_stats: registry.register(metric!(
797 name: format!("mz_persist_{}_step_stats", name),
798 help: format!("time spent computing {} update stats", name),
799 )),
800 step_part_writing: registry.register(metric!(
801 name: format!("mz_persist_{}_step_part_writing", name),
802 help: format!("blocking time spent writing parts for {} updates", name),
803 )),
804 step_inline: registry.register(metric!(
805 name: format!("mz_persist_{}_step_inline", name),
806 help: format!("time spent encoding {} inline batches", name)
807 )),
808 }
809 }
810}
811
812#[derive(Debug)]
813pub struct CompactionMetrics {
814 pub(crate) requested: IntCounter,
815 pub(crate) dropped: IntCounter,
816 pub(crate) disabled: IntCounter,
817 pub(crate) skipped: IntCounter,
818 pub(crate) started: IntCounter,
819 pub(crate) applied: IntCounter,
820 pub(crate) timed_out: IntCounter,
821 pub(crate) failed: IntCounter,
822 pub(crate) noop: IntCounter,
823 pub(crate) seconds: Counter,
824 pub(crate) concurrency_waits: IntCounter,
825 pub(crate) queued_seconds: Counter,
826 pub(crate) memory_violations: IntCounter,
827 pub(crate) runs_compacted: IntCounter,
828 pub(crate) chunks_compacted: IntCounter,
829 pub(crate) not_all_prefetched: IntCounter,
830 pub(crate) parts_prefetched: IntCounter,
831 pub(crate) parts_waited: IntCounter,
832 pub(crate) fast_path_eligible: IntCounter,
833 pub(crate) admin_count: IntCounter,
834
835 pub(crate) applied_exact_match: IntCounter,
836 pub(crate) applied_subset_match: IntCounter,
837 pub(crate) not_applied_too_many_updates: IntCounter,
838
839 pub(crate) batch: BatchWriteMetrics,
840 pub(crate) steps: CompactionStepTimings,
841 pub(crate) schema_selection: CompactionSchemaSelection,
842
843 pub(crate) _steps_vec: CounterVec,
844}
845
846impl CompactionMetrics {
847 fn new(registry: &MetricsRegistry) -> Self {
848 let step_timings: CounterVec = registry.register(metric!(
849 name: "mz_persist_compaction_step_seconds",
850 help: "time spent on individual steps of compaction",
851 var_labels: ["step"],
852 ));
853 let schema_selection: CounterVec = registry.register(metric!(
854 name: "mz_persist_compaction_schema_selection",
855 help: "count of compactions and how we did schema selection",
856 var_labels: ["selection"],
857 ));
858
859 CompactionMetrics {
860 requested: registry.register(metric!(
861 name: "mz_persist_compaction_requested",
862 help: "count of total compaction requests",
863 )),
864 dropped: registry.register(metric!(
865 name: "mz_persist_compaction_dropped",
866 help: "count of total compaction requests dropped due to a full queue",
867 )),
868 disabled: registry.register(metric!(
869 name: "mz_persist_compaction_disabled",
870 help: "count of total compaction requests dropped because compaction was disabled",
871 )),
872 skipped: registry.register(metric!(
873 name: "mz_persist_compaction_skipped",
874 help: "count of compactions skipped due to heuristics",
875 )),
876 started: registry.register(metric!(
877 name: "mz_persist_compaction_started",
878 help: "count of compactions started",
879 )),
880 failed: registry.register(metric!(
881 name: "mz_persist_compaction_failed",
882 help: "count of compactions failed",
883 )),
884 applied: registry.register(metric!(
885 name: "mz_persist_compaction_applied",
886 help: "count of compactions applied to state",
887 )),
888 timed_out: registry.register(metric!(
889 name: "mz_persist_compaction_timed_out",
890 help: "count of compactions that timed out",
891 )),
892 noop: registry.register(metric!(
893 name: "mz_persist_compaction_noop",
894 help: "count of compactions discarded (obsolete)",
895 )),
896 seconds: registry.register(metric!(
897 name: "mz_persist_compaction_seconds",
898 help: "time spent in compaction",
899 )),
900 concurrency_waits: registry.register(metric!(
901 name: "mz_persist_compaction_concurrency_waits",
902 help: "count of compaction requests that ever blocked due to concurrency limit",
903 )),
904 queued_seconds: registry.register(metric!(
905 name: "mz_persist_compaction_queued_seconds",
906 help: "time that compaction requests spent queued",
907 )),
908 memory_violations: registry.register(metric!(
909 name: "mz_persist_compaction_memory_violations",
910 help: "count of compaction memory requirement violations",
911 )),
912 runs_compacted: registry.register(metric!(
913 name: "mz_persist_compaction_runs_compacted",
914 help: "count of runs compacted",
915 )),
916 chunks_compacted: registry.register(metric!(
917 name: "mz_persist_compaction_chunks_compacted",
918 help: "count of run chunks compacted",
919 )),
920 not_all_prefetched: registry.register(metric!(
921 name: "mz_persist_compaction_not_all_prefetched",
922 help: "count of compactions where not all inputs were prefetched",
923 )),
924 parts_prefetched: registry.register(metric!(
925 name: "mz_persist_compaction_parts_prefetched",
926 help: "count of compaction parts completely prefetched by the time they're needed",
927 )),
928 parts_waited: registry.register(metric!(
929 name: "mz_persist_compaction_parts_waited",
930 help: "count of compaction parts that had to be waited on",
931 )),
932 fast_path_eligible: registry.register(metric!(
933 name: "mz_persist_compaction_fast_path_eligible",
934 help: "count of compaction requests that could have used the fast-path optimization",
935 )),
936 admin_count: registry.register(metric!(
937 name: "mz_persist_compaction_admin_count",
938 help: "count of compaction requests that were performed by admin tooling",
939 )),
940 applied_exact_match: registry.register(metric!(
941 name: "mz_persist_compaction_applied_exact_match",
942 help: "count of merge results that exactly replaced a SpineBatch",
943 )),
944 applied_subset_match: registry.register(metric!(
945 name: "mz_persist_compaction_applied_subset_match",
946 help: "count of merge results that replaced a subset of a SpineBatch",
947 )),
948 not_applied_too_many_updates: registry.register(metric!(
949 name: "mz_persist_compaction_not_applied_too_many_updates",
950 help: "count of merge results that did not apply due to too many updates",
951 )),
952 batch: BatchWriteMetrics::new(registry, "compaction"),
953 steps: CompactionStepTimings::new(step_timings.clone()),
954 schema_selection: CompactionSchemaSelection::new(schema_selection.clone()),
955 _steps_vec: step_timings,
956 }
957 }
958}
959
960#[derive(Debug)]
961pub struct CompactionStepTimings {
962 pub(crate) part_fetch_seconds: Counter,
963 pub(crate) heap_population_seconds: Counter,
964}
965
966impl CompactionStepTimings {
967 fn new(step_timings: CounterVec) -> CompactionStepTimings {
968 CompactionStepTimings {
969 part_fetch_seconds: step_timings.with_label_values(&["part_fetch"]),
970 heap_population_seconds: step_timings.with_label_values(&["heap_population"]),
971 }
972 }
973}
974
975#[derive(Debug)]
976pub struct CompactionSchemaSelection {
977 pub(crate) recent_schema: Counter,
978 pub(crate) no_schema: Counter,
979}
980
981impl CompactionSchemaSelection {
982 fn new(schema_selection: CounterVec) -> CompactionSchemaSelection {
983 CompactionSchemaSelection {
984 recent_schema: schema_selection.with_label_values(&["recent"]),
985 no_schema: schema_selection.with_label_values(&["none"]),
986 }
987 }
988}
989
990#[derive(Debug)]
991pub struct GcMetrics {
992 pub(crate) noop: IntCounter,
993 pub(crate) started: IntCounter,
994 pub(crate) finished: IntCounter,
995 pub(crate) merged: IntCounter,
996 pub(crate) seconds: Counter,
997 pub(crate) steps: GcStepTimings,
998}
999
1000#[derive(Debug)]
1001pub struct GcStepTimings {
1002 pub(crate) find_removable_rollups: Counter,
1003 pub(crate) fetch_seconds: Counter,
1004 pub(crate) find_deletable_blobs_seconds: Counter,
1005 pub(crate) delete_rollup_seconds: Counter,
1006 pub(crate) delete_batch_part_seconds: Counter,
1007 pub(crate) truncate_diff_seconds: Counter,
1008 pub(crate) remove_rollups_from_state: Counter,
1009 pub(crate) post_gc_calculations_seconds: Counter,
1010}
1011
1012impl GcStepTimings {
1013 fn new(step_timings: CounterVec) -> Self {
1014 Self {
1015 find_removable_rollups: step_timings.with_label_values(&["find_removable_rollups"]),
1016 fetch_seconds: step_timings.with_label_values(&["fetch"]),
1017 find_deletable_blobs_seconds: step_timings.with_label_values(&["find_deletable_blobs"]),
1018 delete_rollup_seconds: step_timings.with_label_values(&["delete_rollup"]),
1019 delete_batch_part_seconds: step_timings.with_label_values(&["delete_batch_part"]),
1020 truncate_diff_seconds: step_timings.with_label_values(&["truncate_diff"]),
1021 remove_rollups_from_state: step_timings
1022 .with_label_values(&["remove_rollups_from_state"]),
1023 post_gc_calculations_seconds: step_timings.with_label_values(&["post_gc_calculations"]),
1024 }
1025 }
1026}
1027
1028impl GcMetrics {
1029 fn new(registry: &MetricsRegistry) -> Self {
1030 let step_timings: CounterVec = registry.register(metric!(
1031 name: "mz_persist_gc_step_seconds",
1032 help: "time spent on individual steps of gc",
1033 var_labels: ["step"],
1034 ));
1035 GcMetrics {
1036 noop: registry.register(metric!(
1037 name: "mz_persist_gc_noop",
1038 help: "count of garbage collections skipped because they were already done",
1039 )),
1040 started: registry.register(metric!(
1041 name: "mz_persist_gc_started",
1042 help: "count of garbage collections started",
1043 )),
1044 finished: registry.register(metric!(
1045 name: "mz_persist_gc_finished",
1046 help: "count of garbage collections finished",
1047 )),
1048 merged: registry.register(metric!(
1049 name: "mz_persist_gc_merged_reqs",
1050 help: "count of garbage collection requests merged",
1051 )),
1052 seconds: registry.register(metric!(
1053 name: "mz_persist_gc_seconds",
1054 help: "time spent in garbage collections",
1055 )),
1056 steps: GcStepTimings::new(step_timings),
1057 }
1058 }
1059}
1060
1061#[derive(Debug)]
1062pub struct LeaseMetrics {
1063 pub(crate) timeout_read: IntCounter,
1064 pub(crate) dropped_part: IntCounter,
1065}
1066
1067impl LeaseMetrics {
1068 fn new(registry: &MetricsRegistry) -> Self {
1069 LeaseMetrics {
1070 timeout_read: registry.register(metric!(
1071 name: "mz_persist_lease_timeout_read",
1072 help: "count of readers whose lease timed out",
1073 )),
1074 dropped_part: registry.register(metric!(
1075 name: "mz_persist_lease_dropped_part",
1076 help: "count of LeasedBatchParts that were dropped without being politely returned",
1077 )),
1078 }
1079 }
1080}
1081
1082struct IncOnDrop(IntCounter);
1083
1084impl Drop for IncOnDrop {
1085 fn drop(&mut self) {
1086 self.0.inc()
1087 }
1088}
1089
1090pub struct MetricsRetryStream {
1091 retry: RetryStream,
1092 pub(crate) retries: IntCounter,
1093 sleep_seconds: Counter,
1094 _finished: IncOnDrop,
1095}
1096
1097impl MetricsRetryStream {
1098 pub fn new(retry: RetryStream, metrics: &RetryMetrics) -> Self {
1099 metrics.started.inc();
1100 MetricsRetryStream {
1101 retry,
1102 retries: metrics.retries.clone(),
1103 sleep_seconds: metrics.sleep_seconds.clone(),
1104 _finished: IncOnDrop(metrics.finished.clone()),
1105 }
1106 }
1107
1108 pub fn attempt(&self) -> usize {
1110 self.retry.attempt()
1111 }
1112
1113 pub fn next_sleep(&self) -> Duration {
1115 self.retry.next_sleep()
1116 }
1117
1118 pub async fn sleep(self) -> Self {
1123 self.retries.inc();
1124 self.sleep_seconds
1125 .inc_by(self.retry.next_sleep().as_secs_f64());
1126 let retry = self.retry.sleep().await;
1127 MetricsRetryStream {
1128 retry,
1129 retries: self.retries,
1130 sleep_seconds: self.sleep_seconds,
1131 _finished: self._finished,
1132 }
1133 }
1134}
1135
1136#[derive(Debug)]
1137pub struct CodecsMetrics {
1138 pub(crate) state: CodecMetrics,
1139 pub(crate) state_diff: CodecMetrics,
1140 pub(crate) batch: CodecMetrics,
1141 pub(crate) key: CodecMetrics,
1142 pub(crate) val: CodecMetrics,
1143 }
1146
1147#[derive(Debug)]
1148pub struct CodecMetrics {
1149 pub(crate) encode_count: IntCounter,
1150 pub(crate) encode_seconds: Counter,
1151 pub(crate) decode_count: IntCounter,
1152 pub(crate) decode_seconds: Counter,
1153}
1154
1155impl CodecMetrics {
1156 pub(crate) fn encode<R, F: FnOnce() -> R>(&self, f: F) -> R {
1157 let now = Instant::now();
1158 let r = f();
1159 self.encode_count.inc();
1160 self.encode_seconds.inc_by(now.elapsed().as_secs_f64());
1161 r
1162 }
1163
1164 pub(crate) fn decode<R, F: FnOnce() -> R>(&self, f: F) -> R {
1165 let now = Instant::now();
1166 let r = f();
1167 self.decode_count.inc();
1168 self.decode_seconds.inc_by(now.elapsed().as_secs_f64());
1169 r
1170 }
1171}
1172
1173#[derive(Debug)]
1174pub struct StateMetrics {
1175 pub(crate) apply_spine_fast_path: IntCounter,
1176 pub(crate) apply_spine_slow_path: IntCounter,
1177 pub(crate) apply_spine_slow_path_lenient: IntCounter,
1178 pub(crate) apply_spine_slow_path_lenient_adjustment: IntCounter,
1179 pub(crate) apply_spine_slow_path_with_reconstruction: IntCounter,
1180 pub(crate) apply_spine_flattened: IntCounter,
1181 pub(crate) update_state_noop_path: IntCounter,
1182 pub(crate) update_state_empty_path: IntCounter,
1183 pub(crate) update_state_fast_path: IntCounter,
1184 pub(crate) update_state_slow_path: IntCounter,
1185 pub(crate) rollup_at_seqno_migration: IntCounter,
1186 pub(crate) fetch_recent_live_diffs_fast_path: IntCounter,
1187 pub(crate) fetch_recent_live_diffs_slow_path: IntCounter,
1188 pub(crate) writer_added: IntCounter,
1189 pub(crate) writer_removed: IntCounter,
1190 pub(crate) force_apply_hostname: IntCounter,
1191 pub(crate) rollup_write_success: IntCounter,
1192 pub(crate) rollup_write_noop_latest: IntCounter,
1193 pub(crate) rollup_write_noop_truncated: IntCounter,
1194}
1195
1196impl StateMetrics {
1197 pub(crate) fn new(registry: &MetricsRegistry) -> Self {
1198 let rollup_write_noop: IntCounterVec = registry.register(metric!(
1199 name: "mz_persist_state_rollup_write_noop",
1200 help: "count of no-op rollup writes",
1201 var_labels: ["reason"],
1202 ));
1203
1204 StateMetrics {
1205 apply_spine_fast_path: registry.register(metric!(
1206 name: "mz_persist_state_apply_spine_fast_path",
1207 help: "count of spine diff applications that hit the fast path",
1208 )),
1209 apply_spine_slow_path: registry.register(metric!(
1210 name: "mz_persist_state_apply_spine_slow_path",
1211 help: "count of spine diff applications that hit the slow path",
1212 )),
1213 apply_spine_slow_path_lenient: registry.register(metric!(
1214 name: "mz_persist_state_apply_spine_slow_path_lenient",
1215 help: "count of spine diff applications that hit the lenient compaction apply path",
1216 )),
1217 apply_spine_slow_path_lenient_adjustment: registry.register(metric!(
1218 name: "mz_persist_state_apply_spine_slow_path_lenient_adjustment",
1219 help: "count of adjustments made by the lenient compaction apply path",
1220 )),
1221 apply_spine_slow_path_with_reconstruction: registry.register(metric!(
1222 name: "mz_persist_state_apply_spine_slow_path_with_reconstruction",
1223 help: "count of spine diff applications that hit the slow path with extra spine reconstruction step",
1224 )),
1225 apply_spine_flattened: registry.register(metric!(
1226 name: "mz_persist_state_apply_spine_flattened",
1227 help: "count of spine diff applications that flatten the trace",
1228 )),
1229 update_state_noop_path: registry.register(metric!(
1230 name: "mz_persist_state_update_state_noop_path",
1231 help: "count of state update applications that no-oped due to shared state",
1232 )),
1233 update_state_empty_path: registry.register(metric!(
1234 name: "mz_persist_state_update_state_empty_path",
1235 help: "count of state update applications that found no new updates",
1236 )),
1237 update_state_fast_path: registry.register(metric!(
1238 name: "mz_persist_state_update_state_fast_path",
1239 help: "count of state update applications that hit the fast path",
1240 )),
1241 update_state_slow_path: registry.register(metric!(
1242 name: "mz_persist_state_update_state_slow_path",
1243 help: "count of state update applications that hit the slow path",
1244 )),
1245 rollup_at_seqno_migration: registry.register(metric!(
1246 name: "mz_persist_state_rollup_at_seqno_migration",
1247 help: "count of fetch_rollup_at_seqno calls that only worked because of the migration",
1248 )),
1249 fetch_recent_live_diffs_fast_path: registry.register(metric!(
1250 name: "mz_persist_state_fetch_recent_live_diffs_fast_path",
1251 help: "count of fetch_recent_live_diffs that hit the fast path",
1252 )),
1253 fetch_recent_live_diffs_slow_path: registry.register(metric!(
1254 name: "mz_persist_state_fetch_recent_live_diffs_slow_path",
1255 help: "count of fetch_recent_live_diffs that hit the slow path",
1256 )),
1257 writer_added: registry.register(metric!(
1258 name: "mz_persist_state_writer_added",
1259 help: "count of writers added to the state",
1260 )),
1261 writer_removed: registry.register(metric!(
1262 name: "mz_persist_state_writer_removed",
1263 help: "count of writers removed from the state",
1264 )),
1265 force_apply_hostname: registry.register(metric!(
1266 name: "mz_persist_state_force_applied_hostname",
1267 help: "count of when hostname diffs needed to be force applied",
1268 )),
1269 rollup_write_success: registry.register(metric!(
1270 name: "mz_persist_state_rollup_write_success",
1271 help: "count of rollups written successful (may not be linked in to state)",
1272 )),
1273 rollup_write_noop_latest: rollup_write_noop.with_label_values(&["latest"]),
1274 rollup_write_noop_truncated: rollup_write_noop.with_label_values(&["truncated"]),
1275 }
1276 }
1277}
1278
1279#[derive(Debug)]
1280pub struct ShardsMetrics {
1281 encoded_rollup_size: mz_ore::metrics::UIntGaugeVec,
1293 encoded_diff_size: mz_ore::metrics::IntCounterVec,
1294 hollow_batch_count: mz_ore::metrics::UIntGaugeVec,
1295 spine_batch_count: mz_ore::metrics::UIntGaugeVec,
1296 batch_part_count: mz_ore::metrics::UIntGaugeVec,
1297 batch_part_version_count: mz_ore::metrics::UIntGaugeVec,
1298 update_count: mz_ore::metrics::UIntGaugeVec,
1299 rollup_count: mz_ore::metrics::UIntGaugeVec,
1300 largest_batch_size: mz_ore::metrics::UIntGaugeVec,
1301 seqnos_held: mz_ore::metrics::UIntGaugeVec,
1302 seqnos_since_last_rollup: mz_ore::metrics::UIntGaugeVec,
1303 gc_seqno_held_parts: mz_ore::metrics::UIntGaugeVec,
1304 gc_live_diffs: mz_ore::metrics::UIntGaugeVec,
1305 gc_finished: mz_ore::metrics::IntCounterVec,
1306 compaction_applied: mz_ore::metrics::IntCounterVec,
1307 cmd_succeeded: mz_ore::metrics::IntCounterVec,
1308 usage_current_state_batches_bytes: mz_ore::metrics::UIntGaugeVec,
1309 usage_current_state_rollups_bytes: mz_ore::metrics::UIntGaugeVec,
1310 usage_referenced_not_current_state_bytes: mz_ore::metrics::UIntGaugeVec,
1311 usage_not_leaked_not_referenced_bytes: mz_ore::metrics::UIntGaugeVec,
1312 usage_leaked_bytes: mz_ore::metrics::UIntGaugeVec,
1313 blob_gets: mz_ore::metrics::IntCounterVec,
1314 blob_sets: mz_ore::metrics::IntCounterVec,
1315 unconsolidated_snapshot: mz_ore::metrics::IntCounterVec,
1316 inline_part_count: UIntGaugeVec,
1317 compact_batches: UIntGaugeVec,
1318 compacting_batches: UIntGaugeVec,
1319 noncompact_batches: UIntGaugeVec,
1320 process_blob_gets: IntCounter,
1321 process_blob_sets: IntCounter,
1322 process_encoded_diff_size: IntCounter,
1323 process_unconsolidated_snapshot: IntCounter,
1324 shards: Arc<Mutex<BTreeMap<ShardId, Weak<ShardMetrics>>>>,
1328}
1329
1330struct PerShardMetricsFilter {
1342 configs: Arc<ConfigSet>,
1343 mode: (String, ShardMetricsExport),
1345 invalid_mode: IntCounter,
1346 allow: (String, Option<Regex>),
1349 invalid_regex: IntCounter,
1350}
1351
1352impl PerShardMetricsFilter {
1353 fn filter(&mut self, families: &mut Vec<MetricFamily>) {
1354 if self.mode().per_shard() {
1355 return;
1356 }
1357 let allow = self.allow_regex();
1358 families.retain_mut(|f| {
1359 if !PER_SHARD_FAMILIES.iter().any(|p| p.name == f.name()) {
1360 return true;
1361 }
1362 let Some(allow) = allow else {
1363 return false;
1364 };
1365 f.mut_metric().retain(|m| {
1366 m.get_label()
1367 .iter()
1368 .any(|l| matches!(l.name(), "shard" | "name") && allow.is_match(l.value()))
1369 });
1370 !f.get_metric().is_empty()
1371 });
1372 }
1373
1374 fn allow_regex(&mut self) -> Option<&Regex> {
1381 let value = PER_SHARD_METRICS_ENABLE_REGEX.get(&self.configs);
1382 if value != self.allow.0 {
1383 let compiled = if value.is_empty() {
1384 None
1385 } else {
1386 Regex::new(&value)
1387 .inspect_err(|err| {
1388 warn!(
1389 "invalid {}, keeping no per-shard series: {err}",
1390 PER_SHARD_METRICS_ENABLE_REGEX.name()
1391 )
1392 })
1393 .ok()
1394 };
1395 self.allow = (value, compiled);
1396 }
1397 if !self.allow.0.is_empty() && self.allow.1.is_none() {
1398 self.invalid_regex.inc();
1399 }
1400 self.allow.1.as_ref()
1401 }
1402
1403 fn mode(&mut self) -> ShardMetricsExport {
1408 let value = SHARD_METRICS.get(&self.configs);
1409 if value != self.mode.0 {
1410 let parsed = ShardMetricsExport::parse(&value).unwrap_or_else(|| {
1411 let default = ShardMetricsExport::default();
1412 warn!(
1413 "unrecognized {}: '{value}', exporting '{}'",
1414 SHARD_METRICS.name(),
1415 default.as_str()
1416 );
1417 default
1418 });
1419 self.mode = (value, parsed);
1420 }
1421 if self.mode.1.as_str() != self.mode.0 {
1422 self.invalid_mode.inc();
1423 }
1424 self.mode.1
1425 }
1426}
1427
1428impl ShardsMetrics {
1429 fn new(registry: &MetricsRegistry, configs: Arc<ConfigSet>) -> Self {
1430 let shards = Arc::new(Mutex::new(BTreeMap::new()));
1431 registry.register_collector(ShardsAggregateMetrics::new(Arc::clone(&shards), configs));
1432 ShardsMetrics {
1433 encoded_rollup_size: registry.register(metric!(
1434 name: "mz_persist_shard_rollup_size_bytes",
1435 help: "total encoded rollup size by shard",
1436 var_labels: ["shard", "name"],
1437 )),
1438 encoded_diff_size: registry.register(metric!(
1439 name: "mz_persist_shard_diff_size_bytes",
1440 help: "total encoded diff size by shard",
1441 var_labels: ["shard", "name"],
1442 )),
1443 hollow_batch_count: registry.register(metric!(
1444 name: "mz_persist_shard_hollow_batch_count",
1445 help: "count of hollow batches by shard",
1446 var_labels: ["shard", "name"],
1447 )),
1448 spine_batch_count: registry.register(metric!(
1449 name: "mz_persist_shard_spine_batch_count",
1450 help: "count of spine batches by shard",
1451 var_labels: ["shard", "name"],
1452 )),
1453 batch_part_count: registry.register(metric!(
1454 name: "mz_persist_shard_batch_part_count",
1455 help: "count of batch parts by shard",
1456 var_labels: ["shard", "name"],
1457 )),
1458 batch_part_version_count: registry.register(metric!(
1459 name: "mz_persist_shard_batch_part_version_count",
1460 help: "count of batch parts by shard and version",
1461 var_labels: ["shard", "name", "version"],
1462 )),
1463 update_count: registry.register(metric!(
1464 name: "mz_persist_shard_update_count",
1465 help: "count of updates by shard",
1466 var_labels: ["shard", "name"],
1467 )),
1468 rollup_count: registry.register(metric!(
1469 name: "mz_persist_shard_rollup_count",
1470 help: "count of rollups by shard",
1471 var_labels: ["shard", "name"],
1472 )),
1473 largest_batch_size: registry.register(metric!(
1474 name: "mz_persist_shard_largest_batch_size",
1475 help: "largest encoded batch size by shard",
1476 var_labels: ["shard", "name"],
1477 )),
1478 seqnos_held: registry.register(metric!(
1479 name: "mz_persist_shard_seqnos_held",
1480 help: "maximum count of gc-ineligible states by shard",
1481 var_labels: ["shard", "name"],
1482 )),
1483 seqnos_since_last_rollup: registry.register(metric!(
1484 name: "mz_persist_shard_seqnos_since_last_rollup",
1485 help: "count of seqnos since last rollup",
1486 var_labels: ["shard", "name"],
1487 )),
1488 gc_seqno_held_parts: registry.register(metric!(
1489 name: "mz_persist_shard_gc_seqno_held_parts",
1490 help: "count of parts referenced by some live state but not the current state (ie. parts kept only to satisfy seqno holds) at GC time",
1491 var_labels: ["shard", "name"],
1492 )),
1493 gc_live_diffs: registry.register(metric!(
1494 name: "mz_persist_shard_gc_live_diffs",
1495 help: "the number of diffs (or, alternatively, the number of seqnos) present in consensus state at GC time",
1496 var_labels: ["shard", "name"],
1497 )),
1498 gc_finished: registry.register(metric!(
1499 name: "mz_persist_shard_gc_finished",
1500 help: "count of garbage collections finished by shard",
1501 var_labels: ["shard", "name"],
1502 )),
1503 compaction_applied: registry.register(metric!(
1504 name: "mz_persist_shard_compaction_applied",
1505 help: "count of compactions applied to state by shard",
1506 var_labels: ["shard", "name"],
1507 )),
1508 cmd_succeeded: registry.register(metric!(
1509 name: "mz_persist_shard_cmd_succeeded",
1510 help: "count of commands succeeded by shard",
1511 var_labels: ["shard", "name"],
1512 )),
1513 usage_current_state_batches_bytes: registry.register(metric!(
1514 name: "mz_persist_shard_usage_current_state_batches_bytes",
1515 help: "data in batches/parts referenced by current version of state",
1516 var_labels: ["shard", "name"],
1517 )),
1518 usage_current_state_rollups_bytes: registry.register(metric!(
1519 name: "mz_persist_shard_usage_current_state_rollups_bytes",
1520 help: "data in rollups referenced by current version of state",
1521 var_labels: ["shard", "name"],
1522 )),
1523 usage_referenced_not_current_state_bytes: registry.register(metric!(
1524 name: "mz_persist_shard_usage_referenced_not_current_state_bytes",
1525 help: "data referenced only by a previous version of state",
1526 var_labels: ["shard", "name"],
1527 )),
1528 usage_not_leaked_not_referenced_bytes: registry.register(metric!(
1529 name: "mz_persist_shard_usage_not_leaked_not_referenced_bytes",
1530 help: "data written by an active writer but not referenced by any version of state",
1531 var_labels: ["shard", "name"],
1532 )),
1533 usage_leaked_bytes: registry.register(metric!(
1534 name: "mz_persist_shard_usage_leaked_bytes",
1535 help: "data reclaimable by a leaked blob detector",
1536 var_labels: ["shard", "name"],
1537 )),
1538 blob_gets: registry.register(metric!(
1539 name: "mz_persist_shard_blob_gets",
1540 help: "number of Blob::get calls for this shard",
1541 var_labels: ["shard", "name"],
1542 )),
1543 blob_sets: registry.register(metric!(
1544 name: "mz_persist_shard_blob_sets",
1545 help: "number of Blob::set calls for this shard",
1546 var_labels: ["shard", "name"],
1547 )),
1548 unconsolidated_snapshot: registry.register(metric!(
1549 name: "mz_persist_shard_unconsolidated_snapshot",
1550 help: "in snapshot_and_read, the number of times consolidating the raw data wasn't enough to produce consolidated output",
1551 var_labels: ["shard", "name"],
1552 )),
1553 inline_part_count: registry.register(metric!(
1554 name: "mz_persist_shard_inline_part_count",
1555 help: "count of parts inline in shard metadata",
1556 var_labels: ["shard", "name"],
1557 )),
1558 compact_batches: registry.register(metric!(
1559 name: "mz_persist_shard_compact_batches",
1560 help: "number of fully compact batches in the shard",
1561 var_labels: ["shard", "name"],
1562 )),
1563 compacting_batches: registry.register(metric!(
1564 name: "mz_persist_shard_compacting_batches",
1565 help: "number of batches in the shard with compactions in progress",
1566 var_labels: ["shard", "name"],
1567 )),
1568 noncompact_batches: registry.register(metric!(
1569 name: "mz_persist_shard_noncompact_batches",
1570 help: "number of batches in the shard that aren't compact and have no ongoing compaction",
1571 var_labels: ["shard", "name"],
1572 )),
1573 process_blob_gets: registry.register(metric!(
1574 name: "mz_persist_blob_gets",
1575 help: "number of Blob::get calls for batch parts, over all shards",
1576 )),
1577 process_blob_sets: registry.register(metric!(
1578 name: "mz_persist_blob_sets",
1579 help: "number of Blob::set calls for batch parts, over all shards",
1580 )),
1581 process_encoded_diff_size: registry.register(metric!(
1582 name: "mz_persist_diff_size_bytes",
1583 help: "total encoded diff size, over all shards",
1584 )),
1585 process_unconsolidated_snapshot: registry.register(metric!(
1586 name: "mz_persist_unconsolidated_snapshot",
1587 help: "in snapshot_and_read, the number of times consolidating the raw data wasn't enough to produce consolidated output, over all shards",
1588 )),
1589 shards,
1590 }
1591 }
1592
1593 pub fn shard(&self, shard_id: &ShardId, name: &str) -> Arc<ShardMetrics> {
1594 let mut shards = self.shards.lock().expect("mutex poisoned");
1595 if let Some(shard) = shards.get(shard_id) {
1596 if let Some(shard) = shard.upgrade() {
1597 return Arc::clone(&shard);
1598 } else {
1599 assert!(shards.remove(shard_id).is_some());
1600 }
1601 }
1602 let shard = Arc::new(ShardMetrics::new(shard_id, name, self));
1603 assert!(
1604 shards
1605 .insert(shard_id.clone(), Arc::downgrade(&shard))
1606 .is_none()
1607 );
1608 shard
1609 }
1610
1611 fn live_shards(
1616 shards: &Arc<Mutex<BTreeMap<ShardId, Weak<ShardMetrics>>>>,
1617 ) -> Vec<Arc<ShardMetrics>> {
1618 let mut shards = shards.lock().expect("mutex poisoned");
1619 let mut live = Vec::with_capacity(shards.len());
1620 shards.retain(|_, metrics| match metrics.upgrade() {
1621 Some(metrics) => {
1622 live.push(metrics);
1623 true
1624 }
1625 None => false,
1626 });
1627 live
1628 }
1629}
1630
1631struct PerShardFamily {
1634 name: &'static str,
1635 aggregate: ShardAggregate,
1636}
1637
1638enum ShardAggregate {
1639 Gauge(ShardGauge),
1641 VersionSum,
1643 Counter {
1650 #[allow(dead_code)]
1653 total: &'static str,
1654 },
1655}
1656
1657struct ShardGauge {
1658 help: &'static str,
1661 top_k: usize,
1663 sum: bool,
1665 get: fn(&ShardMetrics) -> u64,
1666}
1667
1668const SHARD_TOP_K: usize = 10;
1670
1671const PER_SHARD_FAMILIES: &[PerShardFamily] = &[
1674 PerShardFamily {
1675 name: "mz_persist_shard_rollup_size_bytes",
1676 aggregate: ShardAggregate::Gauge(ShardGauge {
1677 help: "total encoded rollup size by shard",
1678 top_k: SHARD_TOP_K,
1679 sum: true,
1680 get: |m| m.latest_rollup_size.get(),
1681 }),
1682 },
1683 PerShardFamily {
1684 name: "mz_persist_shard_diff_size_bytes",
1685 aggregate: ShardAggregate::Counter {
1686 total: "mz_persist_diff_size_bytes",
1687 },
1688 },
1689 PerShardFamily {
1690 name: "mz_persist_shard_hollow_batch_count",
1691 aggregate: ShardAggregate::Gauge(ShardGauge {
1692 help: "count of hollow batches by shard",
1693 top_k: SHARD_TOP_K,
1694 sum: true,
1695 get: |m| m.hollow_batch_count.get(),
1696 }),
1697 },
1698 PerShardFamily {
1699 name: "mz_persist_shard_spine_batch_count",
1700 aggregate: ShardAggregate::Gauge(ShardGauge {
1701 help: "count of spine batches by shard",
1702 top_k: SHARD_TOP_K,
1703 sum: true,
1704 get: |m| m.spine_batch_count.get(),
1705 }),
1706 },
1707 PerShardFamily {
1708 name: "mz_persist_shard_batch_part_count",
1709 aggregate: ShardAggregate::Gauge(ShardGauge {
1710 help: "count of batch parts by shard",
1711 top_k: SHARD_TOP_K,
1712 sum: true,
1713 get: |m| m.batch_part_count.get(),
1714 }),
1715 },
1716 PerShardFamily {
1717 name: "mz_persist_shard_batch_part_version_count",
1718 aggregate: ShardAggregate::VersionSum,
1719 },
1720 PerShardFamily {
1721 name: "mz_persist_shard_update_count",
1722 aggregate: ShardAggregate::Gauge(ShardGauge {
1723 help: "count of updates by shard",
1724 top_k: SHARD_TOP_K,
1725 sum: true,
1726 get: |m| m.update_count.get(),
1727 }),
1728 },
1729 PerShardFamily {
1730 name: "mz_persist_shard_rollup_count",
1731 aggregate: ShardAggregate::Gauge(ShardGauge {
1732 help: "count of rollups by shard",
1733 top_k: SHARD_TOP_K,
1734 sum: true,
1735 get: |m| m.rollup_count.get(),
1736 }),
1737 },
1738 PerShardFamily {
1739 name: "mz_persist_shard_largest_batch_size",
1740 aggregate: ShardAggregate::Gauge(ShardGauge {
1741 help: "largest encoded batch size by shard",
1742 top_k: SHARD_TOP_K,
1743 sum: false,
1744 get: |m| m.largest_batch_size.get(),
1745 }),
1746 },
1747 PerShardFamily {
1748 name: "mz_persist_shard_seqnos_held",
1749 aggregate: ShardAggregate::Gauge(ShardGauge {
1750 help: "maximum count of gc-ineligible states by shard",
1751 top_k: SHARD_TOP_K,
1752 sum: true,
1753 get: |m| m.seqnos_held.get(),
1754 }),
1755 },
1756 PerShardFamily {
1757 name: "mz_persist_shard_seqnos_since_last_rollup",
1758 aggregate: ShardAggregate::Gauge(ShardGauge {
1759 help: "count of seqnos since last rollup",
1760 top_k: SHARD_TOP_K,
1761 sum: true,
1762 get: |m| m.seqnos_since_last_rollup.get(),
1763 }),
1764 },
1765 PerShardFamily {
1766 name: "mz_persist_shard_gc_seqno_held_parts",
1767 aggregate: ShardAggregate::Gauge(ShardGauge {
1768 help: "count of parts referenced by some live state but not the current state (ie. parts kept only to satisfy seqno holds) at GC time",
1769 top_k: SHARD_TOP_K,
1770 sum: true,
1771 get: |m| m.gc_seqno_held_parts.get(),
1772 }),
1773 },
1774 PerShardFamily {
1775 name: "mz_persist_shard_gc_live_diffs",
1776 aggregate: ShardAggregate::Gauge(ShardGauge {
1777 help: "the number of diffs (or, alternatively, the number of seqnos) present in consensus state at GC time",
1778 top_k: SHARD_TOP_K,
1779 sum: true,
1780 get: |m| m.gc_live_diffs.get(),
1781 }),
1782 },
1783 PerShardFamily {
1784 name: "mz_persist_shard_gc_finished",
1785 aggregate: ShardAggregate::Counter {
1786 total: "mz_persist_gc_finished",
1787 },
1788 },
1789 PerShardFamily {
1790 name: "mz_persist_shard_compaction_applied",
1791 aggregate: ShardAggregate::Counter {
1792 total: "mz_persist_compaction_applied",
1793 },
1794 },
1795 PerShardFamily {
1796 name: "mz_persist_shard_cmd_succeeded",
1797 aggregate: ShardAggregate::Counter {
1798 total: "mz_persist_cmd_succeeded_count",
1799 },
1800 },
1801 PerShardFamily {
1802 name: "mz_persist_shard_usage_current_state_batches_bytes",
1803 aggregate: ShardAggregate::Gauge(ShardGauge {
1804 help: "data in batches/parts referenced by current version of state",
1805 top_k: SHARD_TOP_K,
1806 sum: true,
1807 get: |m| m.usage_current_state_batches_bytes.get(),
1808 }),
1809 },
1810 PerShardFamily {
1811 name: "mz_persist_shard_usage_current_state_rollups_bytes",
1812 aggregate: ShardAggregate::Gauge(ShardGauge {
1813 help: "data in rollups referenced by current version of state",
1814 top_k: SHARD_TOP_K,
1815 sum: true,
1816 get: |m| m.usage_current_state_rollups_bytes.get(),
1817 }),
1818 },
1819 PerShardFamily {
1820 name: "mz_persist_shard_usage_referenced_not_current_state_bytes",
1821 aggregate: ShardAggregate::Gauge(ShardGauge {
1822 help: "data referenced only by a previous version of state",
1823 top_k: SHARD_TOP_K,
1824 sum: true,
1825 get: |m| m.usage_referenced_not_current_state_bytes.get(),
1826 }),
1827 },
1828 PerShardFamily {
1829 name: "mz_persist_shard_usage_not_leaked_not_referenced_bytes",
1830 aggregate: ShardAggregate::Gauge(ShardGauge {
1831 help: "data written by an active writer but not referenced by any version of state",
1832 top_k: SHARD_TOP_K,
1833 sum: true,
1834 get: |m| m.usage_not_leaked_not_referenced_bytes.get(),
1835 }),
1836 },
1837 PerShardFamily {
1838 name: "mz_persist_shard_usage_leaked_bytes",
1839 aggregate: ShardAggregate::Gauge(ShardGauge {
1840 help: "data reclaimable by a leaked blob detector",
1841 top_k: SHARD_TOP_K,
1842 sum: true,
1843 get: |m| m.usage_leaked_bytes.get(),
1844 }),
1845 },
1846 PerShardFamily {
1847 name: "mz_persist_shard_blob_gets",
1848 aggregate: ShardAggregate::Counter {
1849 total: "mz_persist_blob_gets",
1850 },
1851 },
1852 PerShardFamily {
1853 name: "mz_persist_shard_blob_sets",
1854 aggregate: ShardAggregate::Counter {
1855 total: "mz_persist_blob_sets",
1856 },
1857 },
1858 PerShardFamily {
1859 name: "mz_persist_shard_unconsolidated_snapshot",
1860 aggregate: ShardAggregate::Counter {
1861 total: "mz_persist_unconsolidated_snapshot",
1862 },
1863 },
1864 PerShardFamily {
1865 name: "mz_persist_shard_inline_part_count",
1866 aggregate: ShardAggregate::Gauge(ShardGauge {
1867 help: "count of parts inline in shard metadata",
1868 top_k: SHARD_TOP_K,
1869 sum: true,
1870 get: |m| m.inline_part_count.get(),
1871 }),
1872 },
1873 PerShardFamily {
1874 name: "mz_persist_shard_compact_batches",
1875 aggregate: ShardAggregate::Gauge(ShardGauge {
1876 help: "number of fully compact batches in the shard",
1877 top_k: SHARD_TOP_K,
1878 sum: true,
1879 get: |m| m.compact_batches.get(),
1880 }),
1881 },
1882 PerShardFamily {
1883 name: "mz_persist_shard_compacting_batches",
1884 aggregate: ShardAggregate::Gauge(ShardGauge {
1885 help: "number of batches in the shard with compactions in progress",
1886 top_k: SHARD_TOP_K,
1887 sum: true,
1888 get: |m| m.compacting_batches.get(),
1889 }),
1890 },
1891 PerShardFamily {
1892 name: "mz_persist_shard_noncompact_batches",
1893 aggregate: ShardAggregate::Gauge(ShardGauge {
1894 help: "number of batches in the shard that aren't compact and have no ongoing compaction",
1895 top_k: SHARD_TOP_K,
1896 sum: true,
1897 get: |m| m.noncompact_batches.get(),
1898 }),
1899 },
1900];
1901
1902fn shard_gauges() -> impl Iterator<Item = (&'static str, &'static ShardGauge)> {
1904 PER_SHARD_FAMILIES
1905 .iter()
1906 .filter_map(|f| match &f.aggregate {
1907 ShardAggregate::Gauge(g) => Some((f.name, g)),
1908 _ => None,
1909 })
1910}
1911
1912const SHARD_INFO_NAME: &str = "mz_persist_shard_info";
1913const BATCH_PART_VERSION_COUNT_NAME: &str = "mz_persist_batch_part_version_count";
1914const BATCH_PART_VERSION_COUNT_HELP: &str = "count of batch parts by version, over all shards";
1915
1916#[derive(Debug)]
1919struct ShardKey(Arc<ShardMetrics>);
1920
1921impl AggregationKey for ShardKey {
1922 const LABEL_NAMES: &'static [&'static str] = &["shard", "name"];
1923
1924 fn label_values(&self) -> Vec<String> {
1925 vec![self.0.shard_id.to_string(), self.0.name.clone()]
1926 }
1927}
1928
1929#[derive(Debug)]
1937struct ShardsAggregateMetrics {
1938 shards: Arc<Mutex<BTreeMap<ShardId, Weak<ShardMetrics>>>>,
1939 configs: Arc<ConfigSet>,
1940 count: GenericGauge<AtomicI64>,
1941 stale_count: GenericGauge<AtomicI64>,
1942 gauges: AggregatedFamilies<ShardKey>,
1943 gauge_gets: Vec<fn(&ShardMetrics) -> u64>,
1946 batch_part_version_count: raw::UIntGaugeVec,
1949 shard_info: raw::UIntGaugeVec,
1951}
1952
1953impl ShardsAggregateMetrics {
1954 fn new(
1955 shards: Arc<Mutex<BTreeMap<ShardId, Weak<ShardMetrics>>>>,
1956 configs: Arc<ConfigSet>,
1957 ) -> Self {
1958 let gauges = shard_gauges()
1959 .map(|(name, g)| AggregatedFamily {
1960 name: name.to_string(),
1961 help: g.help.to_string(),
1962 sum: g.sum,
1963 top_k: Some(g.top_k),
1964 })
1965 .collect();
1966 ShardsAggregateMetrics {
1967 shards,
1968 configs,
1969 count: MakeCollector::make_collector(metric!(
1970 name: "mz_persist_shard_count",
1971 help: "count of all active shards on this process",
1972 )),
1973 stale_count: MakeCollector::make_collector(metric!(
1974 name: "mz_persist_stale_shard_count",
1975 help: "count of shards on this process whose persisted state version \
1976 is behind this process's build version; per-process, so summing \
1977 across processes counts (shard, process) pairs, not distinct shards",
1978 )),
1979 gauges: AggregatedFamilies::new(gauges),
1980 gauge_gets: shard_gauges().map(|(_, g)| g.get).collect(),
1981 batch_part_version_count: new_batch_part_version_count(),
1982 shard_info: new_shard_info(),
1983 }
1984 }
1985
1986 fn summaries(&self, live: Vec<Arc<ShardMetrics>>) -> Vec<MetricFamily> {
1988 let mut versions = BTreeMap::<String, u64>::new();
1989 let mut values = Vec::new();
1990 let mut snapshot = self.gauges.snapshot();
1991 for m in live {
1992 values.clear();
1993 values.extend(self.gauge_gets.iter().map(|get| get(&m)));
1994 let version_map = m
1995 .batch_part_version_map
1996 .lock()
1997 .expect("mutex should not be poisoned");
1998 for (version, metrics) in version_map.iter() {
1999 let n = metrics.batch_part_version_count.get();
2000 if n == 0 {
2001 continue;
2002 }
2003 match versions.get_mut(version) {
2004 Some(total) => *total += n,
2005 None => {
2006 versions.insert(version.clone(), n);
2007 }
2008 }
2009 }
2010 drop(version_map);
2011 snapshot.push(ShardKey(m), &values);
2012 }
2013
2014 let mut families = snapshot.finish();
2015 let batch_part_version_count = new_batch_part_version_count();
2016 for (version, n) in versions {
2017 batch_part_version_count
2018 .with_label_values(&[version.as_str()])
2019 .set(n);
2020 }
2021 families.extend(batch_part_version_count.collect());
2022 families
2023 }
2024}
2025
2026fn new_shard_info() -> raw::UIntGaugeVec {
2029 raw::UIntGaugeVec::new(
2030 PrometheusOpts::new(SHARD_INFO_NAME, "1 for each active shard on this process"),
2031 ShardKey::LABEL_NAMES,
2032 )
2033 .expect("valid gauge vec")
2034}
2035
2036fn new_batch_part_version_count() -> raw::UIntGaugeVec {
2037 raw::UIntGaugeVec::new(
2038 PrometheusOpts::new(BATCH_PART_VERSION_COUNT_NAME, BATCH_PART_VERSION_COUNT_HELP),
2039 &["version"],
2040 )
2041 .expect("valid gauge vec")
2042}
2043
2044impl Collector for ShardsAggregateMetrics {
2045 fn desc(&self) -> Vec<&Desc> {
2046 let mut descs = self.count.desc();
2047 descs.extend(self.stale_count.desc());
2048 descs.extend(self.gauges.descs());
2049 descs.extend(self.batch_part_version_count.desc());
2050 descs.extend(self.shard_info.desc());
2051 descs
2052 }
2053
2054 fn collect(&self) -> Vec<MetricFamily> {
2055 let live = ShardsMetrics::live_shards(&self.shards);
2056 let mut count = 0;
2057 let mut stale_count = 0;
2058 for m in &live {
2059 count += 1;
2060 if m.stale.load(Ordering::Relaxed) {
2061 stale_count += 1;
2062 }
2063 }
2064 self.count.set(count);
2065 self.stale_count.set(stale_count);
2066
2067 let mut families = Vec::new();
2068 if SHARD_INFO_METRIC.get(&self.configs) {
2069 let shard_info = new_shard_info();
2070 for m in &live {
2071 shard_info
2072 .with_label_values(&[m.shard_id.to_string(), m.name.clone()])
2073 .set(1);
2074 }
2075 families.extend(shard_info.collect());
2076 }
2077 if ShardMetricsExport::get(&self.configs).summary() {
2078 families.extend(self.summaries(live));
2079 }
2080 families.extend(self.count.collect());
2081 families.extend(self.stale_count.collect());
2082 families
2083 }
2084}
2085
2086#[derive(Debug)]
2087pub struct ShardMetrics {
2088 pub shard_id: ShardId,
2089 pub name: String,
2090 pub largest_batch_size: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2091 pub latest_rollup_size: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2092 pub encoded_diff_size: ShardCounter,
2093 pub hollow_batch_count: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2094 pub spine_batch_count: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2095 pub batch_part_count: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2096 batch_part_version_count: mz_ore::metrics::UIntGaugeVec,
2097 batch_part_version_map: Mutex<BTreeMap<String, BatchPartVersionMetrics>>,
2098 pub update_count: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2099 pub rollup_count: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2100 pub seqnos_held: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2101 pub seqnos_since_last_rollup: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2102 pub gc_seqno_held_parts: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2103 pub gc_live_diffs: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2104 pub usage_current_state_batches_bytes: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2105 pub usage_current_state_rollups_bytes: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2106 pub usage_referenced_not_current_state_bytes: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2107 pub usage_not_leaked_not_referenced_bytes: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2108 pub usage_leaked_bytes: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2109 pub gc_finished: DeleteOnDropCounter<AtomicU64, Vec<String>>,
2110 pub compaction_applied: DeleteOnDropCounter<AtomicU64, Vec<String>>,
2111 pub cmd_succeeded: DeleteOnDropCounter<AtomicU64, Vec<String>>,
2112 pub blob_gets: ShardCounter,
2113 pub blob_sets: ShardCounter,
2114 pub unconsolidated_snapshot: ShardCounter,
2115 pub inline_part_count: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2116 pub compact_batches: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2117 pub compacting_batches: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2118 pub noncompact_batches: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2119 pub stale: AtomicBool,
2123}
2124
2125impl ShardMetrics {
2126 pub fn new(shard_id: &ShardId, name: &str, shards_metrics: &ShardsMetrics) -> Self {
2127 let shard = shard_id.to_string();
2128 ShardMetrics {
2129 shard_id: *shard_id,
2130 name: name.to_string(),
2131 latest_rollup_size: shards_metrics
2132 .encoded_rollup_size
2133 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2134 encoded_diff_size: ShardCounter {
2135 shard: shards_metrics
2136 .encoded_diff_size
2137 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2138 process: shards_metrics.process_encoded_diff_size.clone(),
2139 },
2140 hollow_batch_count: shards_metrics
2141 .hollow_batch_count
2142 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2143 spine_batch_count: shards_metrics
2144 .spine_batch_count
2145 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2146 batch_part_count: shards_metrics
2147 .batch_part_count
2148 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2149 batch_part_version_count: shards_metrics.batch_part_version_count.clone(),
2150 batch_part_version_map: Mutex::new(BTreeMap::new()),
2151 update_count: shards_metrics
2152 .update_count
2153 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2154 rollup_count: shards_metrics
2155 .rollup_count
2156 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2157 largest_batch_size: shards_metrics
2158 .largest_batch_size
2159 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2160 seqnos_held: shards_metrics
2161 .seqnos_held
2162 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2163 seqnos_since_last_rollup: shards_metrics
2164 .seqnos_since_last_rollup
2165 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2166 gc_seqno_held_parts: shards_metrics
2167 .gc_seqno_held_parts
2168 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2169 gc_live_diffs: shards_metrics
2170 .gc_live_diffs
2171 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2172 gc_finished: shards_metrics
2173 .gc_finished
2174 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2175 compaction_applied: shards_metrics
2176 .compaction_applied
2177 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2178 cmd_succeeded: shards_metrics
2179 .cmd_succeeded
2180 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2181 usage_current_state_batches_bytes: shards_metrics
2182 .usage_current_state_batches_bytes
2183 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2184 usage_current_state_rollups_bytes: shards_metrics
2185 .usage_current_state_rollups_bytes
2186 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2187 usage_referenced_not_current_state_bytes: shards_metrics
2188 .usage_referenced_not_current_state_bytes
2189 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2190 usage_not_leaked_not_referenced_bytes: shards_metrics
2191 .usage_not_leaked_not_referenced_bytes
2192 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2193 usage_leaked_bytes: shards_metrics
2194 .usage_leaked_bytes
2195 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2196 blob_gets: ShardCounter {
2197 shard: shards_metrics
2198 .blob_gets
2199 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2200 process: shards_metrics.process_blob_gets.clone(),
2201 },
2202 blob_sets: ShardCounter {
2203 shard: shards_metrics
2204 .blob_sets
2205 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2206 process: shards_metrics.process_blob_sets.clone(),
2207 },
2208 unconsolidated_snapshot: ShardCounter {
2209 shard: shards_metrics
2210 .unconsolidated_snapshot
2211 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2212 process: shards_metrics.process_unconsolidated_snapshot.clone(),
2213 },
2214 inline_part_count: shards_metrics
2215 .inline_part_count
2216 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2217 compact_batches: shards_metrics
2218 .compact_batches
2219 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2220 compacting_batches: shards_metrics
2221 .compacting_batches
2222 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2223 noncompact_batches: shards_metrics
2224 .noncompact_batches
2225 .get_delete_on_drop_metric(vec![shard, name.to_string()]),
2226 stale: AtomicBool::new(false),
2227 }
2228 }
2229
2230 pub(crate) fn set_batch_part_versions<'a>(
2231 &self,
2232 batch_parts_by_version: impl Iterator<Item = &'a str>,
2233 ) {
2234 let mut map = self
2235 .batch_part_version_map
2236 .lock()
2237 .expect("mutex should not be poisoned");
2238 for x in map.values() {
2245 x.batch_part_version_count.set(0);
2246 }
2247
2248 for key in batch_parts_by_version {
2251 if !map.contains_key(key) {
2252 map.insert(
2253 key.to_owned(),
2254 BatchPartVersionMetrics {
2255 batch_part_version_count: self
2256 .batch_part_version_count
2257 .get_delete_on_drop_metric(vec![
2258 self.shard_id.to_string(),
2259 self.name.clone(),
2260 key.to_owned(),
2261 ]),
2262 },
2263 );
2264 }
2265 let value = map.get(key).expect("inserted above");
2266 value.batch_part_version_count.inc();
2267 }
2268 }
2269}
2270
2271#[derive(Debug)]
2274pub struct ShardCounter {
2275 shard: DeleteOnDropCounter<AtomicU64, Vec<String>>,
2276 process: IntCounter,
2277}
2278
2279impl ShardCounter {
2280 pub fn inc(&self) {
2281 self.inc_by(1);
2282 }
2283
2284 pub fn inc_by(&self, v: u64) {
2285 self.shard.inc_by(v);
2286 self.process.inc_by(v);
2287 }
2288}
2289
2290#[derive(Debug)]
2291pub struct BatchPartVersionMetrics {
2292 pub batch_part_version_count: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2293}
2294
2295#[derive(Debug)]
2297pub struct UsageAuditMetrics {
2298 pub blob_batch_part_bytes: UIntGauge,
2300 pub blob_batch_part_count: UIntGauge,
2302 pub blob_rollup_bytes: UIntGauge,
2304 pub blob_rollup_count: UIntGauge,
2306 pub blob_bytes: UIntGauge,
2308 pub blob_count: UIntGauge,
2310 pub step_blob_metadata: Counter,
2312 pub step_state: Counter,
2314 pub step_math: Counter,
2316}
2317
2318impl UsageAuditMetrics {
2319 fn new(registry: &MetricsRegistry) -> Self {
2320 let step_timings: CounterVec = registry.register(metric!(
2321 name: "mz_persist_audit_step_seconds",
2322 help: "time spent on individual steps of audit",
2323 var_labels: ["step"],
2324 ));
2325 UsageAuditMetrics {
2326 blob_batch_part_bytes: registry.register(metric!(
2327 name: "mz_persist_audit_blob_batch_part_bytes",
2328 help: "total size of batch parts in blob",
2329 )),
2330 blob_batch_part_count: registry.register(metric!(
2331 name: "mz_persist_audit_blob_batch_part_count",
2332 help: "count of batch parts in blob",
2333 )),
2334 blob_rollup_bytes: registry.register(metric!(
2335 name: "mz_persist_audit_blob_rollup_bytes",
2336 help: "total size of state rollups stored in blob",
2337 )),
2338 blob_rollup_count: registry.register(metric!(
2339 name: "mz_persist_audit_blob_rollup_count",
2340 help: "count of all state rollups in blob",
2341 )),
2342 blob_bytes: registry.register(metric!(
2343 name: "mz_persist_audit_blob_bytes",
2344 help: "total size of blob",
2345 )),
2346 blob_count: registry.register(metric!(
2347 name: "mz_persist_audit_blob_count",
2348 help: "count of all blobs",
2349 )),
2350 step_blob_metadata: step_timings.with_label_values(&["blob_metadata"]),
2351 step_state: step_timings.with_label_values(&["state"]),
2352 step_math: step_timings.with_label_values(&["math"]),
2353 }
2354 }
2355}
2356
2357#[derive(Debug)]
2360pub enum UpdateDelta {
2361 Negative(u64),
2363 NonNegative(u64),
2365}
2366
2367impl UpdateDelta {
2368 pub fn new(new: usize, old: usize) -> Self {
2371 if new < old {
2372 UpdateDelta::Negative(CastFrom::cast_from(old - new))
2373 } else {
2374 UpdateDelta::NonNegative(CastFrom::cast_from(new - old))
2375 }
2376 }
2377}
2378
2379#[derive(Debug, Clone)]
2382pub struct SinkMetrics {
2383 correction_insertions_total: IntCounter,
2385 correction_deletions_total: IntCounter,
2387 correction_capacity_increases_total: IntCounter,
2389 correction_capacity_decreases_total: IntCounter,
2391 correction_max_per_sink_worker_len_updates: raw::UIntGaugeVec,
2393 correction_max_per_sink_worker_capacity_updates: raw::UIntGaugeVec,
2395}
2396
2397impl SinkMetrics {
2398 fn new(registry: &MetricsRegistry) -> Self {
2399 SinkMetrics {
2400 correction_insertions_total: registry.register(metric!(
2401 name: "mz_persist_sink_correction_insertions_total",
2402 help: "The cumulative insertions observed on the correction buffer across workers and persist sinks.",
2403 )),
2404 correction_deletions_total: registry.register(metric!(
2405 name: "mz_persist_sink_correction_deletions_total",
2406 help: "The cumulative deletions observed on the correction buffer across workers and persist sinks.",
2407 )),
2408 correction_capacity_increases_total: registry.register(metric!(
2409 name: "mz_persist_sink_correction_capacity_increases_total",
2410 help: "The cumulative capacity increases observed on the correction buffer across workers and persist sinks.",
2411 )),
2412 correction_capacity_decreases_total: registry.register(metric!(
2413 name: "mz_persist_sink_correction_capacity_decreases_total",
2414 help: "The cumulative capacity decreases observed on the correction buffer across workers and persist sinks.",
2415 )),
2416 correction_max_per_sink_worker_len_updates: registry.register(metric!(
2417 name: "mz_persist_sink_correction_max_per_sink_worker_len_updates",
2418 help: "The maximum length observed for the correction buffer of any single persist sink per worker.",
2419 var_labels: ["worker_id"],
2420 )),
2421 correction_max_per_sink_worker_capacity_updates: registry.register(metric!(
2422 name: "mz_persist_sink_correction_max_per_sink_worker_capacity_updates",
2423 help: "The maximum capacity observed for the correction buffer of any single persist sink per worker.",
2424 var_labels: ["worker_id"],
2425 )),
2426 }
2427 }
2428
2429 pub fn for_worker(&self, worker_id: usize) -> SinkWorkerMetrics {
2434 let worker = worker_id.to_string();
2435 let correction_max_per_sink_worker_len_updates = self
2436 .correction_max_per_sink_worker_len_updates
2437 .with_label_values(&[&worker]);
2438 let correction_max_per_sink_worker_capacity_updates = self
2439 .correction_max_per_sink_worker_capacity_updates
2440 .with_label_values(&[&worker]);
2441 SinkWorkerMetrics {
2442 correction_max_per_sink_worker_len_updates,
2443 correction_max_per_sink_worker_capacity_updates,
2444 }
2445 }
2446
2447 pub fn report_correction_update_deltas(
2453 &self,
2454 correction_len_delta: UpdateDelta,
2455 correction_cap_delta: UpdateDelta,
2456 ) {
2457 match correction_len_delta {
2459 UpdateDelta::NonNegative(delta) => {
2460 if delta > 0 {
2461 self.correction_insertions_total.inc_by(delta)
2462 }
2463 }
2464 UpdateDelta::Negative(delta) => self.correction_deletions_total.inc_by(delta),
2465 }
2466 match correction_cap_delta {
2468 UpdateDelta::NonNegative(delta) => {
2469 if delta > 0 {
2470 self.correction_capacity_increases_total.inc_by(delta)
2471 }
2472 }
2473 UpdateDelta::Negative(delta) => self.correction_capacity_decreases_total.inc_by(delta),
2474 }
2475 }
2476}
2477
2478#[derive(Clone, Debug)]
2480pub struct SinkWorkerMetrics {
2481 correction_max_per_sink_worker_len_updates: UIntGauge,
2482 correction_max_per_sink_worker_capacity_updates: UIntGauge,
2483}
2484
2485impl SinkWorkerMetrics {
2486 pub fn report_correction_update_totals(&self, correction_len: usize, correction_cap: usize) {
2491 let correction_len = CastFrom::cast_from(correction_len);
2493 if correction_len > self.correction_max_per_sink_worker_len_updates.get() {
2494 self.correction_max_per_sink_worker_len_updates
2495 .set(correction_len);
2496 }
2497 let correction_cap = CastFrom::cast_from(correction_cap);
2498 if correction_cap > self.correction_max_per_sink_worker_capacity_updates.get() {
2499 self.correction_max_per_sink_worker_capacity_updates
2500 .set(correction_cap);
2501 }
2502 }
2503}
2504
2505#[derive(Debug)]
2510pub struct BackpressureMetrics {
2511 pub emitted_bytes: IntCounter,
2513 pub last_backpressured_bytes: UIntGauge,
2517 pub retired_bytes: IntCounter,
2519}
2520
2521impl BackpressureMetrics {
2522 fn new(registry: &MetricsRegistry) -> Self {
2523 BackpressureMetrics {
2524 emitted_bytes: registry.register(metric!(
2525 name: "mz_persist_backpressure_emitted_bytes",
2526 help: "bytes emitted by backpressure operators",
2527 )),
2528 last_backpressured_bytes: registry.register(metric!(
2529 name: "mz_persist_backpressure_last_backpressured_bytes",
2530 help: "sum over backpressure operators of the inflight bytes each last \
2531 stalled on; not comparable to retired bytes, but nonzero growth \
2532 indicates backpressure is happening",
2533 )),
2534 retired_bytes: registry.register(metric!(
2535 name: "mz_persist_backpressure_retired_bytes",
2536 help: "bytes retired by processing downstream of backpressure operators",
2537 )),
2538 }
2539 }
2540}
2541
2542#[derive(Debug)]
2544pub struct AlertsMetrics {
2545 pub(crate) blob_failures: IntCounter,
2546 pub(crate) consensus_failures: IntCounter,
2547}
2548
2549impl AlertsMetrics {
2550 fn new(registry: &MetricsRegistry) -> Self {
2551 AlertsMetrics {
2552 blob_failures: registry.register(metric!(
2553 name: "mz_persist_blob_failures",
2554 help: "count of all blob operation failures",
2555 const_labels: {"honeycomb" => "import"},
2556 )),
2557 consensus_failures: registry.register(metric!(
2558 name: "mz_persist_consensus_failures",
2559 help: "count of determinate consensus operation failures",
2560 const_labels: {"honeycomb" => "import"},
2561 )),
2562 }
2563 }
2564}
2565
2566#[derive(Debug)]
2568pub struct PubSubServerMetrics {
2569 pub(crate) active_connections: UIntGauge,
2570 pub(crate) broadcasted_diff_count: IntCounter,
2571 pub(crate) broadcasted_diff_bytes: IntCounter,
2572 pub(crate) broadcasted_diff_dropped_channel_full: IntCounter,
2573
2574 pub(crate) push_seconds: Counter,
2575 pub(crate) subscribe_seconds: Counter,
2576 pub(crate) unsubscribe_seconds: Counter,
2577 pub(crate) connection_cleanup_seconds: Counter,
2578
2579 pub(crate) push_call_count: IntCounter,
2580 pub(crate) subscribe_call_count: IntCounter,
2581 pub(crate) unsubscribe_call_count: IntCounter,
2582}
2583
2584impl PubSubServerMetrics {
2585 pub(crate) fn new(registry: &MetricsRegistry) -> Self {
2586 let op_timings: CounterVec = registry.register(metric!(
2587 name: "mz_persist_pubsub_server_operation_seconds",
2588 help: "time spent in pubsub server performing each operation",
2589 var_labels: ["op"],
2590 ));
2591 let call_count: IntCounterVec = registry.register(metric!(
2592 name: "mz_persist_pubsub_server_call_count",
2593 help: "count of each pubsub server message received",
2594 var_labels: ["call"],
2595 ));
2596
2597 Self {
2598 active_connections: registry.register(metric!(
2599 name: "mz_persist_pubsub_server_active_connections",
2600 help: "number of active connections to server",
2601 )),
2602 broadcasted_diff_count: registry.register(metric!(
2603 name: "mz_persist_pubsub_server_broadcasted_diff_count",
2604 help: "count of total broadcast diff messages sent",
2605 )),
2606 broadcasted_diff_bytes: registry.register(metric!(
2607 name: "mz_persist_pubsub_server_broadcasted_diff_bytes",
2608 help: "count of total broadcast diff bytes sent",
2609 )),
2610 broadcasted_diff_dropped_channel_full: registry.register(metric!(
2611 name: "mz_persist_pubsub_server_broadcasted_diff_dropped_channel_full",
2612 help: "count of diffs dropped due to full connection channel",
2613 )),
2614
2615 push_seconds: op_timings.with_label_values(&["push"]),
2616 subscribe_seconds: op_timings.with_label_values(&["subscribe"]),
2617 unsubscribe_seconds: op_timings.with_label_values(&["unsubscribe"]),
2618 connection_cleanup_seconds: op_timings.with_label_values(&["cleanup"]),
2619
2620 push_call_count: call_count.with_label_values(&["push"]),
2621 subscribe_call_count: call_count.with_label_values(&["subscribe"]),
2622 unsubscribe_call_count: call_count.with_label_values(&["unsubscribe"]),
2623 }
2624 }
2625}
2626
2627#[derive(Debug)]
2629pub struct PubSubClientMetrics {
2630 pub sender: PubSubClientSenderMetrics,
2631 pub receiver: PubSubClientReceiverMetrics,
2632 pub grpc_connection: PubSubGrpcClientConnectionMetrics,
2633}
2634
2635impl PubSubClientMetrics {
2636 fn new(registry: &MetricsRegistry) -> Self {
2637 PubSubClientMetrics {
2638 sender: PubSubClientSenderMetrics::new(registry),
2639 receiver: PubSubClientReceiverMetrics::new(registry),
2640 grpc_connection: PubSubGrpcClientConnectionMetrics::new(registry),
2641 }
2642 }
2643}
2644
2645#[derive(Debug)]
2646pub struct PubSubGrpcClientConnectionMetrics {
2647 pub(crate) connected: UIntGauge,
2648 pub(crate) connection_established_count: IntCounter,
2649 pub(crate) connect_call_attempt_count: IntCounter,
2650 pub(crate) broadcast_recv_lagged_count: IntCounter,
2651 pub(crate) grpc_error_count: IntCounter,
2652}
2653
2654impl PubSubGrpcClientConnectionMetrics {
2655 fn new(registry: &MetricsRegistry) -> Self {
2656 Self {
2657 connected: registry.register(metric!(
2658 name: "mz_persist_pubsub_client_grpc_connected",
2659 help: "whether the grpc client is currently connected",
2660 )),
2661 connection_established_count: registry.register(metric!(
2662 name: "mz_persist_pubsub_client_grpc_connection_established_count",
2663 help: "count of grpc connection establishments to pubsub server",
2664 )),
2665 connect_call_attempt_count: registry.register(metric!(
2666 name: "mz_persist_pubsub_client_grpc_connect_call_attempt_count",
2667 help: "count of connection call attempts (including retries) to pubsub server",
2668 )),
2669 broadcast_recv_lagged_count: registry.register(metric!(
2670 name: "mz_persist_pubsub_client_grpc_broadcast_recv_lagged_count",
2671 help: "times a message was missed by broadcast receiver due to lag",
2672 )),
2673 grpc_error_count: registry.register(metric!(
2674 name: "mz_persist_pubsub_client_grpc_error_count",
2675 help: "count of grpc errors received",
2676 )),
2677 }
2678 }
2679}
2680
2681#[derive(Clone, Debug)]
2682pub struct PubSubClientReceiverMetrics {
2683 pub(crate) push_received: IntCounter,
2684 pub(crate) unknown_message_received: IntCounter,
2685 pub(crate) approx_diff_latency_seconds: Histogram,
2686
2687 pub(crate) state_pushed_diff_fast_path: IntCounter,
2688 pub(crate) state_pushed_diff_slow_path_succeeded: IntCounter,
2689 pub(crate) state_pushed_diff_slow_path_failed: IntCounter,
2690
2691 pub(crate) diff_applied: IntCounter,
2692 pub(crate) diff_not_applied_stale: IntCounter,
2693 pub(crate) diff_not_applied_out_of_order: IntCounter,
2694}
2695
2696impl PubSubClientReceiverMetrics {
2697 fn new(registry: &MetricsRegistry) -> Self {
2698 let call_received: IntCounterVec = registry.register(metric!(
2699 name: "mz_persist_pubsub_client_call_received",
2700 help: "times a pubsub client call was received",
2701 var_labels: ["call"],
2702 ));
2703
2704 Self {
2705 push_received: call_received.with_label_values(&["push"]),
2706 unknown_message_received: call_received.with_label_values(&["unknown"]),
2707 approx_diff_latency_seconds: registry.register(metric!(
2708 name: "mz_persist_pubsub_client_approx_diff_apply_latency_seconds",
2709 help: "histogram of (approximate) latency between sending a diff and applying it",
2710 buckets: prometheus::exponential_buckets(0.001, 2.0, 13).expect("buckets"),
2711 )),
2712
2713 state_pushed_diff_fast_path: registry.register(metric!(
2714 name: "mz_persist_pubsub_client_receiver_state_push_diff_fast_path",
2715 help: "count fast-path state push_diff calls",
2716 )),
2717 state_pushed_diff_slow_path_succeeded: registry.register(metric!(
2718 name: "mz_persist_pubsub_client_receiver_state_push_diff_slow_path_succeeded",
2719 help: "count of successful slow-path state push_diff calls",
2720 )),
2721 state_pushed_diff_slow_path_failed: registry.register(metric!(
2722 name: "mz_persist_pubsub_client_receiver_state_push_diff_slow_path_failed",
2723 help: "count of unsuccessful slow-path state push_diff calls",
2724 )),
2725 diff_applied: registry.register(metric!(
2726 name: "mz_persist_pubsub_client_receiver_diff_applied",
2727 help: "number of diffs received via pubsub that applied",
2728 )),
2729 diff_not_applied_stale: registry.register(metric!(
2730 name: "mz_persist_pubsub_client_receiver_diff_not_applied_stale",
2731 help: "number of diffs received via pubsub that did not apply due to staleness",
2732 )),
2733 diff_not_applied_out_of_order: registry.register(metric!(
2734 name: "mz_persist_pubsub_client_receiver_diff_not_applied_out_of_order",
2735 help: "number of diffs received via pubsub that did not apply due to out-of-order delivery",
2736 )),
2737 }
2738 }
2739}
2740
2741#[derive(Debug)]
2742pub struct PubSubClientSenderMetrics {
2743 pub push: PubSubClientCallMetrics,
2744 pub subscribe: PubSubClientCallMetrics,
2745 pub unsubscribe: PubSubClientCallMetrics,
2746}
2747
2748#[derive(Debug)]
2749pub struct PubSubClientCallMetrics {
2750 pub(crate) succeeded: IntCounter,
2751 pub(crate) bytes_sent: IntCounter,
2752 pub(crate) failed: IntCounter,
2753}
2754
2755impl PubSubClientSenderMetrics {
2756 fn new(registry: &MetricsRegistry) -> Self {
2757 let call_bytes_sent: IntCounterVec = registry.register(metric!(
2758 name: "mz_persist_pubsub_client_call_bytes_sent",
2759 help: "number of bytes sent for a given pubsub client call",
2760 var_labels: ["call"],
2761 ));
2762 let call_succeeded: IntCounterVec = registry.register(metric!(
2763 name: "mz_persist_pubsub_client_call_succeeded",
2764 help: "times a pubsub client call succeeded",
2765 var_labels: ["call"],
2766 ));
2767 let call_failed: IntCounterVec = registry.register(metric!(
2768 name: "mz_persist_pubsub_client_call_failed",
2769 help: "times a pubsub client call failed",
2770 var_labels: ["call"],
2771 ));
2772
2773 Self {
2774 push: PubSubClientCallMetrics {
2775 succeeded: call_succeeded.with_label_values(&["push"]),
2776 failed: call_failed.with_label_values(&["push"]),
2777 bytes_sent: call_bytes_sent.with_label_values(&["push"]),
2778 },
2779 subscribe: PubSubClientCallMetrics {
2780 succeeded: call_succeeded.with_label_values(&["subscribe"]),
2781 failed: call_failed.with_label_values(&["subscribe"]),
2782 bytes_sent: call_bytes_sent.with_label_values(&["subscribe"]),
2783 },
2784 unsubscribe: PubSubClientCallMetrics {
2785 succeeded: call_succeeded.with_label_values(&["unsubscribe"]),
2786 failed: call_failed.with_label_values(&["unsubscribe"]),
2787 bytes_sent: call_bytes_sent.with_label_values(&["unsubscribe"]),
2788 },
2789 }
2790 }
2791}
2792
2793#[derive(Debug)]
2794pub struct LocksMetrics {
2795 pub(crate) applier_read_cacheable: LockMetrics,
2796 pub(crate) applier_read_noncacheable: LockMetrics,
2797 pub(crate) applier_write: LockMetrics,
2798 pub(crate) watch: LockMetrics,
2799}
2800
2801#[derive(Debug, Clone)]
2802pub struct LockMetrics {
2803 pub(crate) acquire_count: IntCounter,
2804 pub(crate) blocking_acquire_count: IntCounter,
2805 pub(crate) blocking_seconds: Counter,
2806}
2807
2808#[derive(Debug)]
2809pub struct WatchMetrics {
2810 pub(crate) wait_woken_via_watch: IntCounter,
2811 pub(crate) wait_woken_via_sleep: IntCounter,
2812 pub(crate) wait_resolved_via_watch: IntCounter,
2813 pub(crate) wait_resolved_via_sleep: IntCounter,
2814 pub(crate) notify_sent: IntCounter,
2815 pub(crate) notify_upper_sent: IntCounter,
2816 pub(crate) notify_noop: IntCounter,
2817 pub(crate) notify_recv: IntCounter,
2818 pub(crate) notify_lagged: IntCounter,
2819 pub(crate) notify_wait_started: IntCounter,
2820 pub(crate) notify_wait_finished: IntCounter,
2821}
2822
2823impl WatchMetrics {
2824 fn new(registry: &MetricsRegistry) -> Self {
2825 WatchMetrics {
2826 wait_woken_via_watch: registry.register(metric!(
2827 name: "mz_persist_wait_woken_via_watch",
2828 help: "count of wait-for-uppers wakes via watch notify",
2829 )),
2830 wait_woken_via_sleep: registry.register(metric!(
2831 name: "mz_persist_wait_woken_via_sleep",
2832 help: "count of wait-for-uppers wakes via sleep",
2833 )),
2834 wait_resolved_via_watch: registry.register(metric!(
2835 name: "mz_persist_wait_resolved_via_watch",
2836 help: "count of wait-for-uppers resolved via watch notify",
2837 )),
2838 wait_resolved_via_sleep: registry.register(metric!(
2839 name: "mz_persist_wait_resolved_via_sleep",
2840 help: "count of wait-for-uppers resolved via sleep",
2841 )),
2842 notify_sent: registry.register(metric!(
2843 name: "mz_persist_watch_notify_sent",
2844 help: "count of watch notifications sent to a non-empty broadcast channel",
2845 )),
2846 notify_upper_sent: registry.register(metric!(
2847 name: "mz_persist_watch_notify_upper_sent",
2848 help: "count of strict shard upper advances signaled to upper waiters",
2849 )),
2850 notify_noop: registry.register(metric!(
2851 name: "mz_persist_watch_notify_noop",
2852 help: "count of watch notifications sent to an broadcast channel",
2853 )),
2854 notify_recv: registry.register(metric!(
2855 name: "mz_persist_watch_notify_recv",
2856 help: "count of watch notifications received from the broadcast channel",
2857 )),
2858 notify_lagged: registry.register(metric!(
2859 name: "mz_persist_watch_notify_lagged",
2860 help: "count of lagged events in the watch notification broadcast channel",
2861 )),
2862 notify_wait_started: registry.register(metric!(
2863 name: "mz_persist_watch_notify_wait_started",
2864 help: "count of watch wait calls started",
2865 )),
2866 notify_wait_finished: registry.register(metric!(
2867 name: "mz_persist_watch_notify_wait_finished",
2868 help: "count of watch wait calls resolved",
2869 )),
2870 }
2871 }
2872}
2873
2874#[derive(Debug)]
2875pub struct PushdownMetrics {
2876 pub(crate) parts_filtered_count: IntCounter,
2877 pub(crate) parts_filtered_bytes: IntCounter,
2878 pub(crate) parts_fetched_count: IntCounter,
2879 pub(crate) parts_fetched_bytes: IntCounter,
2880 pub(crate) parts_audited_count: IntCounter,
2881 pub(crate) parts_audited_bytes: IntCounter,
2882 pub(crate) parts_inline_count: IntCounter,
2883 pub(crate) parts_inline_bytes: IntCounter,
2884 pub(crate) parts_faked_count: IntCounter,
2885 pub(crate) parts_faked_bytes: IntCounter,
2886 pub(crate) parts_stats_trimmed_count: IntCounter,
2887 pub(crate) parts_stats_trimmed_bytes: IntCounter,
2888 pub(crate) parts_projection_trimmed_bytes: IntCounter,
2889 pub part_stats: PartStatsMetrics,
2890}
2891
2892impl PushdownMetrics {
2893 fn new(registry: &MetricsRegistry) -> Self {
2894 PushdownMetrics {
2895 parts_filtered_count: registry.register(metric!(
2896 name: "mz_persist_pushdown_parts_filtered_count",
2897 help: "count of parts filtered by pushdown",
2898 )),
2899 parts_filtered_bytes: registry.register(metric!(
2900 name: "mz_persist_pushdown_parts_filtered_bytes",
2901 help: "total size of parts filtered by pushdown in bytes",
2902 )),
2903 parts_fetched_count: registry.register(metric!(
2904 name: "mz_persist_pushdown_parts_fetched_count",
2905 help: "count of parts not filtered by pushdown",
2906 )),
2907 parts_fetched_bytes: registry.register(metric!(
2908 name: "mz_persist_pushdown_parts_fetched_bytes",
2909 help: "total size of parts not filtered by pushdown in bytes",
2910 )),
2911 parts_audited_count: registry.register(metric!(
2912 name: "mz_persist_pushdown_parts_audited_count",
2913 help: "count of parts fetched only for pushdown audit",
2914 )),
2915 parts_audited_bytes: registry.register(metric!(
2916 name: "mz_persist_pushdown_parts_audited_bytes",
2917 help: "total size of parts fetched only for pushdown audit",
2918 )),
2919 parts_inline_count: registry.register(metric!(
2920 name: "mz_persist_pushdown_parts_inline_count",
2921 help: "count of parts not fetched because they were inline",
2922 )),
2923 parts_inline_bytes: registry.register(metric!(
2924 name: "mz_persist_pushdown_parts_inline_bytes",
2925 help: "total size of parts not fetched because they were inline",
2926 )),
2927 parts_faked_count: registry.register(metric!(
2928 name: "mz_persist_pushdown_parts_faked_count",
2929 help: "count of parts faked because of aggressive projection pushdown",
2930 )),
2931 parts_faked_bytes: registry.register(metric!(
2932 name: "mz_persist_pushdown_parts_faked_bytes",
2933 help: "total size of parts replaced with fakes by aggressive projection pushdown",
2934 )),
2935 parts_stats_trimmed_count: registry.register(metric!(
2936 name: "mz_persist_pushdown_parts_stats_trimmed_count",
2937 help: "count of trimmed part stats",
2938 )),
2939 parts_stats_trimmed_bytes: registry.register(metric!(
2940 name: "mz_persist_pushdown_parts_stats_trimmed_bytes",
2941 help: "total bytes trimmed from part stats",
2942 )),
2943 parts_projection_trimmed_bytes: registry.register(metric!(
2944 name: "mz_persist_pushdown_parts_projection_trimmed_bytes",
2945 help: "total bytes trimmed from columnar data because of projection pushdown",
2946 )),
2947 part_stats: PartStatsMetrics::new(registry),
2948 }
2949 }
2950}
2951
2952#[derive(Debug)]
2953pub struct ConsolidationMetrics {
2954 pub(crate) parts_fetched: IntCounter,
2955 pub(crate) parts_skipped: IntCounter,
2956 pub(crate) parts_wasted: IntCounter,
2957 pub(crate) wrong_sort: IntCounter,
2958}
2959
2960impl ConsolidationMetrics {
2961 fn new(registry: &MetricsRegistry) -> Self {
2962 ConsolidationMetrics {
2963 parts_fetched: registry.register(metric!(
2964 name: "mz_persist_consolidation_parts_fetched_count",
2965 help: "count of parts that were fetched and used during consolidation",
2966 )),
2967 parts_skipped: registry.register(metric!(
2968 name: "mz_persist_consolidation_parts_skipped_count",
2969 help: "count of parts that were never needed during consolidation",
2970 )),
2971 parts_wasted: registry.register(metric!(
2972 name: "mz_persist_consolidation_parts_wasted_count",
2973 help: "count of parts that were fetched but not needed during consolidation",
2974 )),
2975 wrong_sort: registry.register(metric!(
2976 name: "mz_persist_consolidation_wrong_sort_count",
2977 help: "count of runs that were sorted using the wrong ordering for the current consolidation",
2978 )),
2979 }
2980 }
2981}
2982
2983#[derive(Debug)]
2984pub struct BlobMemCache {
2985 pub(crate) size_blobs: UIntGauge,
2986 pub(crate) size_bytes: UIntGauge,
2987 pub(crate) hits_blobs: IntCounter,
2988 pub(crate) hits_bytes: IntCounter,
2989 pub(crate) evictions: IntCounter,
2990}
2991
2992impl BlobMemCache {
2993 fn new(registry: &MetricsRegistry) -> Self {
2994 BlobMemCache {
2995 size_blobs: registry.register(metric!(
2996 name: "mz_persist_blob_cache_size_blobs",
2997 help: "count of blobs in the cache",
2998 const_labels: {"cache" => "mem"},
2999 )),
3000 size_bytes: registry.register(metric!(
3001 name: "mz_persist_blob_cache_size_bytes",
3002 help: "total size of blobs in the cache",
3003 const_labels: {"cache" => "mem"},
3004 )),
3005 hits_blobs: registry.register(metric!(
3006 name: "mz_persist_blob_cache_hits_blobs",
3007 help: "count of blobs served via cache instead of s3",
3008 const_labels: {"cache" => "mem"},
3009 )),
3010 hits_bytes: registry.register(metric!(
3011 name: "mz_persist_blob_cache_hits_bytes",
3012 help: "total size of blobs served via cache instead of s3",
3013 const_labels: {"cache" => "mem"},
3014 )),
3015 evictions: registry.register(metric!(
3016 name: "mz_persist_blob_cache_evictions",
3017 help: "count of capacity-based cache evictions",
3018 const_labels: {"cache" => "mem"},
3019 )),
3020 }
3021 }
3022}
3023
3024#[derive(Debug)]
3025pub struct SemaphoreMetrics {
3026 cfg: PersistConfig,
3027 registry: MetricsRegistry,
3028 fetch: OnceCell<MetricsSemaphore>,
3029}
3030
3031impl SemaphoreMetrics {
3032 fn new(cfg: PersistConfig, registry: MetricsRegistry) -> Self {
3033 SemaphoreMetrics {
3034 cfg,
3035 registry,
3036 fetch: OnceCell::new(),
3037 }
3038 }
3039
3040 async fn fetch(&self) -> &MetricsSemaphore {
3044 if let Some(x) = self.fetch.get() {
3045 return x;
3047 }
3048 let cfg = self.cfg.clone();
3049 let registry = self.registry.clone();
3050 let init = async move {
3051 let total_permits = match cfg.announce_memory_limit {
3052 Some(mem) if cfg.is_cc_active => {
3055 info!("fetch semaphore awaiting first dyncfg values");
3058 let () = cfg.configs_synced_once().await;
3059 let total_permits = usize::cast_lossy(
3060 f64::cast_lossy(mem) * FETCH_SEMAPHORE_PERMIT_ADJUSTMENT.get(&cfg),
3061 );
3062 info!("fetch_semaphore got first dyncfg values");
3063 total_permits
3064 }
3065 Some(_) | None => Semaphore::MAX_PERMITS,
3066 };
3067 MetricsSemaphore::new(®istry, "fetch", total_permits)
3068 };
3069 self.fetch.get_or_init(|| init).await
3070 }
3071
3072 pub(crate) async fn acquire_fetch_permits(&self, encoded_size_bytes: usize) -> MetricsPermits {
3073 let requested_permits = f64::cast_lossy(encoded_size_bytes);
3076 let requested_permits = requested_permits * FETCH_SEMAPHORE_COST_ADJUSTMENT.get(&self.cfg);
3077 let requested_permits = usize::cast_lossy(requested_permits);
3078 self.fetch().await.acquire_permits(requested_permits).await
3079 }
3080}
3081
3082#[derive(Debug)]
3083pub struct MetricsSemaphore {
3084 name: &'static str,
3085 semaphore: Arc<Semaphore>,
3086 total_permits: usize,
3087 acquire_count: IntCounter,
3088 blocking_count: IntCounter,
3089 blocking_seconds: Counter,
3090 acquired_permits: IntCounter,
3091 released_permits: IntCounter,
3092 _available_permits: ComputedUIntGauge,
3093}
3094
3095impl MetricsSemaphore {
3096 pub fn new(registry: &MetricsRegistry, name: &'static str, total_permits: usize) -> Self {
3097 let total_permits = std::cmp::min(total_permits, Semaphore::MAX_PERMITS);
3098 let semaphore = Arc::new(Semaphore::new(total_permits));
3101 MetricsSemaphore {
3102 name,
3103 total_permits,
3104 acquire_count: registry.register(metric!(
3105 name: "mz_persist_semaphore_acquire_count",
3106 help: "count of acquire calls (not acquired permits count)",
3107 const_labels: {"name" => name},
3108 )),
3109 blocking_count: registry.register(metric!(
3110 name: "mz_persist_semaphore_blocking_count",
3111 help: "count of acquire calls that had to block",
3112 const_labels: {"name" => name},
3113 )),
3114 blocking_seconds: registry.register(metric!(
3115 name: "mz_persist_semaphore_blocking_seconds",
3116 help: "total time spent blocking on permit acquisition",
3117 const_labels: {"name" => name},
3118 )),
3119 acquired_permits: registry.register(metric!(
3120 name: "mz_persist_semaphore_acquired_permits",
3121 help: "total sum of acquired permits",
3122 const_labels: {"name" => name},
3123 )),
3124 released_permits: registry.register(metric!(
3125 name: "mz_persist_semaphore_released_permits",
3126 help: "total sum of released permits",
3127 const_labels: {"name" => name},
3128 )),
3129 _available_permits: registry.register_computed_gauge(
3130 metric!(
3131 name: "mz_persist_semaphore_available_permits",
3132 help: "currently available permits according to the semaphore",
3133 ),
3134 {
3135 let semaphore = Arc::clone(&semaphore);
3136 move || u64::cast_from(semaphore.available_permits())
3137 },
3138 ),
3139 semaphore,
3140 }
3141 }
3142
3143 pub async fn acquire_permits(&self, requested_permits: usize) -> MetricsPermits {
3144 let total_permits = u32::try_from(self.total_permits).unwrap_or(u32::MAX);
3147 let requested_permits = u32::try_from(requested_permits).unwrap_or(u32::MAX);
3148 let requested_permits = std::cmp::min(requested_permits, total_permits);
3149 let wrap = |_permit| {
3150 self.acquired_permits.inc_by(u64::from(requested_permits));
3151 MetricsPermits {
3152 _permit,
3153 released_metric: self.released_permits.clone(),
3154 count: requested_permits,
3155 }
3156 };
3157
3158 self.acquire_count.inc();
3160 match Arc::clone(&self.semaphore).try_acquire_many_owned(requested_permits) {
3161 Ok(x) => return wrap(x),
3162 Err(_) => {}
3163 };
3164
3165 self.blocking_count.inc();
3167 let start = Instant::now();
3168 let ret = Arc::clone(&self.semaphore)
3169 .acquire_many_owned(requested_permits)
3170 .instrument(info_span!("acquire_permits"))
3171 .await;
3172 let elapsed = start.elapsed();
3173 self.blocking_seconds.inc_by(elapsed.as_secs_f64());
3174 debug!(
3175 "acquisition of {} {} permits blocked for {:?}",
3176 self.name, requested_permits, elapsed
3177 );
3178 wrap(ret.expect("semaphore is never closed"))
3179 }
3180}
3181
3182#[derive(Debug)]
3183pub struct MetricsPermits {
3184 _permit: OwnedSemaphorePermit,
3185 released_metric: IntCounter,
3186 count: u32,
3187}
3188
3189impl Drop for MetricsPermits {
3190 fn drop(&mut self) {
3191 self.released_metric.inc_by(u64::from(self.count))
3192 }
3193}
3194
3195#[derive(Debug)]
3196pub struct ExternalOpMetrics {
3197 started: IntCounter,
3198 succeeded: IntCounter,
3199 failed: IntCounter,
3200 bytes: IntCounter,
3201 seconds: Counter,
3202 seconds_histogram: Option<Histogram>,
3203 alerts_metrics: Arc<AlertsMetrics>,
3204}
3205
3206impl ExternalOpMetrics {
3207 async fn run_op<R, F, OpFn, ErrFn>(
3208 &self,
3209 op_fn: OpFn,
3210 on_err_fn: ErrFn,
3211 ) -> Result<R, ExternalError>
3212 where
3213 F: std::future::Future<Output = Result<R, ExternalError>>,
3214 OpFn: FnOnce() -> F,
3215 ErrFn: FnOnce(&AlertsMetrics, &ExternalError),
3216 {
3217 self.started.inc();
3218 let start = Instant::now();
3219 let res = op_fn().await;
3220 let elapsed_seconds = start.elapsed().as_secs_f64();
3221 self.seconds.inc_by(elapsed_seconds);
3222 if let Some(h) = &self.seconds_histogram {
3223 h.observe(elapsed_seconds);
3224 }
3225 match res.as_ref() {
3226 Ok(_) => self.succeeded.inc(),
3227 Err(err) => {
3228 self.failed.inc();
3229 on_err_fn(&self.alerts_metrics, err);
3230 }
3231 };
3232 res
3233 }
3234
3235 fn run_stream<'a, R: 'a, S, OpFn, ErrFn>(
3236 &'a self,
3237 op_fn: OpFn,
3238 mut on_err_fn: ErrFn,
3239 ) -> impl futures::Stream<Item = Result<R, ExternalError>> + 'a
3240 where
3241 S: futures::Stream<Item = Result<R, ExternalError>> + Unpin + 'a,
3242 OpFn: FnOnce() -> S,
3243 ErrFn: FnMut(&AlertsMetrics, &ExternalError) + 'a,
3244 {
3245 self.started.inc();
3246 let start = Instant::now();
3247 let mut stream = op_fn();
3248 stream! {
3249 let mut succeeded = true;
3250 while let Some(res) = stream.next().await {
3251 if let Err(err) = res.as_ref() {
3252 on_err_fn(&self.alerts_metrics, err);
3253 succeeded = false;
3254 }
3255 yield res;
3256 }
3257 if succeeded {
3258 self.succeeded.inc()
3259 } else {
3260 self.failed.inc()
3261 }
3262 let elapsed_seconds = start.elapsed().as_secs_f64();
3263 self.seconds.inc_by(elapsed_seconds);
3264 if let Some(h) = &self.seconds_histogram {
3265 h.observe(elapsed_seconds);
3266 }
3267 }
3268 }
3269}
3270
3271#[derive(Debug)]
3272pub struct BlobMetrics {
3273 set: ExternalOpMetrics,
3274 get: ExternalOpMetrics,
3275 list_keys: ExternalOpMetrics,
3276 delete: ExternalOpMetrics,
3277 restore: ExternalOpMetrics,
3278 delete_noop: IntCounter,
3279 blob_sizes: Histogram,
3280 pub rtt_latency: Gauge,
3281}
3282
3283#[derive(Debug)]
3284pub struct MetricsBlob {
3285 blob: Arc<dyn Blob>,
3286 metrics: Arc<Metrics>,
3287}
3288
3289impl MetricsBlob {
3290 pub fn new(blob: Arc<dyn Blob>, metrics: Arc<Metrics>) -> Self {
3291 MetricsBlob { blob, metrics }
3292 }
3293
3294 fn on_err(alerts_metrics: &AlertsMetrics, _err: &ExternalError) {
3295 alerts_metrics.blob_failures.inc()
3296 }
3297}
3298
3299#[async_trait]
3300impl Blob for MetricsBlob {
3301 #[instrument(name = "blob::get", fields(shard=blob_key_shard_id(key)))]
3302 async fn get(&self, key: &str) -> Result<Option<SegmentedBytes>, ExternalError> {
3303 let res = self
3304 .metrics
3305 .blob
3306 .get
3307 .run_op(|| self.blob.get(key), Self::on_err)
3308 .await;
3309 if let Ok(Some(value)) = res.as_ref() {
3310 self.metrics
3311 .blob
3312 .get
3313 .bytes
3314 .inc_by(u64::cast_from(value.len()));
3315 }
3316 res
3317 }
3318
3319 #[instrument(name = "blob::list_keys_and_metadata", fields(shard=blob_key_shard_id(key_prefix)))]
3320 async fn list_keys_and_metadata(
3321 &self,
3322 key_prefix: &str,
3323 f: &mut (dyn FnMut(BlobMetadata) + Send + Sync),
3324 ) -> Result<(), ExternalError> {
3325 let mut byte_total = 0;
3326 let mut instrumented = |blob_metadata: BlobMetadata| {
3327 byte_total += blob_metadata.key.len();
3330 f(blob_metadata)
3331 };
3332
3333 let res = self
3334 .metrics
3335 .blob
3336 .list_keys
3337 .run_op(
3338 || {
3339 self.blob
3340 .list_keys_and_metadata(key_prefix, &mut instrumented)
3341 },
3342 Self::on_err,
3343 )
3344 .await;
3345
3346 self.metrics
3347 .blob
3348 .list_keys
3349 .bytes
3350 .inc_by(u64::cast_from(byte_total));
3351
3352 res
3353 }
3354
3355 #[instrument(name = "blob::set", fields(shard=blob_key_shard_id(key),size_bytes=value.len()))]
3356 async fn set(&self, key: &str, value: Bytes) -> Result<(), ExternalError> {
3357 let bytes = value.len();
3358 let res = self
3359 .metrics
3360 .blob
3361 .set
3362 .run_op(|| self.blob.set(key, value), Self::on_err)
3363 .await;
3364 if res.is_ok() {
3365 self.metrics.blob.set.bytes.inc_by(u64::cast_from(bytes));
3366 self.metrics.blob.blob_sizes.observe(f64::cast_lossy(bytes));
3367 }
3368 res
3369 }
3370
3371 #[instrument(name = "blob::delete", fields(shard=blob_key_shard_id(key)))]
3372 async fn delete(&self, key: &str) -> Result<Option<usize>, ExternalError> {
3373 let bytes = self
3374 .metrics
3375 .blob
3376 .delete
3377 .run_op(|| self.blob.delete(key), Self::on_err)
3378 .await?;
3379 if let Some(bytes) = bytes {
3380 self.metrics.blob.delete.bytes.inc_by(u64::cast_from(bytes));
3381 } else {
3382 self.metrics.blob.delete_noop.inc();
3383 }
3384 Ok(bytes)
3385 }
3386
3387 async fn restore(&self, key: &str) -> Result<(), ExternalError> {
3388 self.metrics
3389 .blob
3390 .restore
3391 .run_op(|| self.blob.restore(key), Self::on_err)
3392 .await
3393 }
3394}
3395
3396#[derive(Debug)]
3397pub struct ConsensusMetrics {
3398 list_keys: ExternalOpMetrics,
3399 head: ExternalOpMetrics,
3400 compare_and_set: ExternalOpMetrics,
3401 scan: ExternalOpMetrics,
3402 truncate: ExternalOpMetrics,
3403 truncated_count: IntCounter,
3404 pub rtt_latency: Gauge,
3405}
3406
3407#[derive(Debug)]
3408pub struct MetricsConsensus {
3409 consensus: Arc<dyn Consensus>,
3410 metrics: Arc<Metrics>,
3411}
3412
3413impl MetricsConsensus {
3414 pub fn new(consensus: Arc<dyn Consensus>, metrics: Arc<Metrics>) -> Self {
3415 MetricsConsensus { consensus, metrics }
3416 }
3417
3418 fn on_err(alerts_metrics: &AlertsMetrics, err: &ExternalError) {
3419 if let ExternalError::Indeterminate(_) = err {
3423 alerts_metrics.consensus_failures.inc()
3424 }
3425 }
3426}
3427
3428#[async_trait]
3429impl Consensus for MetricsConsensus {
3430 fn list_keys(&self) -> ResultStream<'_, String> {
3431 Box::pin(
3432 self.metrics
3433 .consensus
3434 .list_keys
3435 .run_stream(|| self.consensus.list_keys(), Self::on_err),
3436 )
3437 }
3438
3439 #[instrument(name = "consensus::head", fields(shard=key))]
3440 async fn head(&self, key: &str) -> Result<Option<VersionedData>, ExternalError> {
3441 let res = self
3442 .metrics
3443 .consensus
3444 .head
3445 .run_op(|| self.consensus.head(key), Self::on_err)
3446 .await;
3447 if let Ok(Some(data)) = res.as_ref() {
3448 self.metrics
3449 .consensus
3450 .head
3451 .bytes
3452 .inc_by(u64::cast_from(data.data.len()));
3453 }
3454 res
3455 }
3456
3457 #[instrument(name = "consensus::compare_and_set", fields(shard=key,size_bytes=new.data.len()))]
3458 async fn compare_and_set(
3459 &self,
3460 key: &str,
3461 new: VersionedData,
3462 ) -> Result<CaSResult, ExternalError> {
3463 let bytes = new.data.len();
3464 let res = self
3465 .metrics
3466 .consensus
3467 .compare_and_set
3468 .run_op(|| self.consensus.compare_and_set(key, new), Self::on_err)
3469 .await;
3470 match res.as_ref() {
3471 Ok(CaSResult::Committed) => self
3472 .metrics
3473 .consensus
3474 .compare_and_set
3475 .bytes
3476 .inc_by(u64::cast_from(bytes)),
3477 Ok(CaSResult::ExpectationMismatch) | Err(_) => {}
3478 }
3479 res
3480 }
3481
3482 #[instrument(name = "consensus::scan", fields(shard=key))]
3483 async fn scan(
3484 &self,
3485 key: &str,
3486 from: SeqNo,
3487 limit: usize,
3488 ) -> Result<Vec<VersionedData>, ExternalError> {
3489 let res = self
3490 .metrics
3491 .consensus
3492 .scan
3493 .run_op(|| self.consensus.scan(key, from, limit), Self::on_err)
3494 .await;
3495 if let Ok(dataz) = res.as_ref() {
3496 let bytes: usize = dataz.iter().map(|x| x.data.len()).sum();
3497 self.metrics
3498 .consensus
3499 .scan
3500 .bytes
3501 .inc_by(u64::cast_from(bytes));
3502 }
3503 res
3504 }
3505
3506 #[instrument(name = "consensus::truncate", fields(shard=key))]
3507 async fn truncate(&self, key: &str, seqno: SeqNo) -> Result<Option<usize>, ExternalError> {
3508 let metrics = &self.metrics.consensus;
3509 let deleted = metrics
3510 .truncate
3511 .run_op(|| self.consensus.truncate(key, seqno), Self::on_err)
3512 .await?;
3513 if let Some(deleted) = deleted {
3514 metrics.truncated_count.inc_by(u64::cast_from(deleted));
3515 }
3516 Ok(deleted)
3517 }
3518}
3519
3520#[derive(Debug, Clone)]
3523pub struct TaskMetrics {
3524 f64_gauges: Vec<(Gauge, fn(&tokio_metrics::TaskMetrics) -> f64)>,
3525 u64_gauges: Vec<(
3526 GenericGauge<AtomicU64>,
3527 fn(&tokio_metrics::TaskMetrics) -> u64,
3528 )>,
3529 monitor: TaskMonitor,
3530}
3531
3532impl TaskMetrics {
3533 pub fn new(name: &str) -> Self {
3534 let monitor = TaskMonitor::new();
3535 Self {
3536 f64_gauges: vec![
3537 (
3538 Gauge::make_collector(metric!(
3539 name: "mz_persist_task_total_idle_duration",
3540 help: "Seconds of time spent idling, ie. waiting for a task to be woken up.",
3541 const_labels: {"name" => name}
3542 )),
3543 |m| m.total_idle_duration.as_secs_f64(),
3544 ),
3545 (
3546 Gauge::make_collector(metric!(
3547 name: "mz_persist_task_total_scheduled_duration",
3548 help: "Seconds of time spent scheduled, ie. ready to poll but not yet polled.",
3549 const_labels: {"name" => name}
3550 )),
3551 |m| m.total_scheduled_duration.as_secs_f64(),
3552 ),
3553 ],
3554 u64_gauges: vec![
3555 (
3556 MakeCollector::make_collector(metric!(
3557 name: "mz_persist_task_total_scheduled_count",
3558 help: "The total number of task schedules. Useful for computing the average scheduled time.",
3559 const_labels: {"name" => name}
3560 )),
3561 |m| m.total_scheduled_count,
3562 ),
3563 (
3564 MakeCollector::make_collector(metric!(
3565 name: "mz_persist_task_total_idled_count",
3566 help: "The total number of task idles. Useful for computing the average idle time.",
3567 const_labels: {"name" => name}
3568 ,
3569 )),
3570 |m| m.total_idled_count,
3571 ),
3572 ],
3573 monitor,
3574 }
3575 }
3576
3577 pub fn instrument_task<F>(&self, task: F) -> tokio_metrics::Instrumented<F> {
3580 TaskMonitor::instrument(&self.monitor, task)
3581 }
3582}
3583
3584impl Collector for TaskMetrics {
3585 fn desc(&self) -> Vec<&Desc> {
3586 let mut descs = Vec::with_capacity(self.f64_gauges.len() + self.u64_gauges.len());
3587 for (g, _) in &self.f64_gauges {
3588 descs.extend(g.desc());
3589 }
3590 for (g, _) in &self.u64_gauges {
3591 descs.extend(g.desc());
3592 }
3593 descs
3594 }
3595
3596 fn collect(&self) -> Vec<MetricFamily> {
3597 let mut families = Vec::with_capacity(self.f64_gauges.len() + self.u64_gauges.len());
3598 let metrics = self.monitor.cumulative();
3599 for (g, metrics_fn) in &self.f64_gauges {
3600 g.set(metrics_fn(&metrics));
3601 families.extend(g.collect());
3602 }
3603 for (g, metrics_fn) in &self.u64_gauges {
3604 g.set(metrics_fn(&metrics));
3605 families.extend(g.collect());
3606 }
3607 families
3608 }
3609}
3610
3611#[derive(Debug)]
3612pub struct TasksMetrics {
3613 pub heartbeat_read: TaskMetrics,
3614}
3615
3616impl TasksMetrics {
3617 fn new(registry: &MetricsRegistry) -> Self {
3618 let heartbeat_read = TaskMetrics::new("heartbeat_read");
3619 registry.register_collector(heartbeat_read.clone());
3620 TasksMetrics { heartbeat_read }
3621 }
3622}
3623
3624#[derive(Debug)]
3625pub struct SchemaMetrics {
3626 pub(crate) cache_fetch_state_count: IntCounter,
3627 pub(crate) cache_schema: SchemaCacheMetrics,
3628 pub(crate) cache_migration: SchemaCacheMetrics,
3629 pub(crate) migration_count_same: IntCounter,
3630 pub(crate) migration_count_codec: IntCounter,
3631 pub(crate) migration_count_either: IntCounter,
3632 pub(crate) migration_len_legacy_codec: IntCounter,
3633 pub(crate) migration_len_either_codec: IntCounter,
3634 pub(crate) migration_len_either_arrow: IntCounter,
3635 pub(crate) migration_new_count: IntCounter,
3636 pub(crate) migration_new_seconds: Counter,
3637 pub(crate) migration_migrate_seconds: Counter,
3638}
3639
3640impl SchemaMetrics {
3641 fn new(registry: &MetricsRegistry) -> Self {
3642 let cached: IntCounterVec = registry.register(metric!(
3643 name: "mz_persist_schema_cache_cached_count",
3644 help: "count of schema cache entries served from cache",
3645 var_labels: ["op"],
3646 ));
3647 let computed: IntCounterVec = registry.register(metric!(
3648 name: "mz_persist_schema_cache_computed_count",
3649 help: "count of schema cache entries computed",
3650 var_labels: ["op"],
3651 ));
3652 let unavailable: IntCounterVec = registry.register(metric!(
3653 name: "mz_persist_schema_cache_unavailable_count",
3654 help: "count of schema cache entries unavailable at current state",
3655 var_labels: ["op"],
3656 ));
3657 let added: IntCounterVec = registry.register(metric!(
3658 name: "mz_persist_schema_cache_added_count",
3659 help: "count of schema cache entries added",
3660 var_labels: ["op"],
3661 ));
3662 let dropped: IntCounterVec = registry.register(metric!(
3663 name: "mz_persist_schema_cache_dropped_count",
3664 help: "count of schema cache entries dropped",
3665 var_labels: ["op"],
3666 ));
3667 let cache = |name| SchemaCacheMetrics {
3668 cached_count: cached.with_label_values(&[name]),
3669 computed_count: computed.with_label_values(&[name]),
3670 unavailable_count: unavailable.with_label_values(&[name]),
3671 added_count: added.with_label_values(&[name]),
3672 dropped_count: dropped.with_label_values(&[name]),
3673 };
3674 let migration_count: IntCounterVec = registry.register(metric!(
3675 name: "mz_persist_schema_migration_count",
3676 help: "count of fetch part migrations",
3677 var_labels: ["op"],
3678 ));
3679 let migration_len: IntCounterVec = registry.register(metric!(
3680 name: "mz_persist_schema_migration_len",
3681 help: "count of migrated update records",
3682 var_labels: ["op"],
3683 ));
3684 SchemaMetrics {
3685 cache_fetch_state_count: registry.register(metric!(
3686 name: "mz_persist_schema_cache_fetch_state_count",
3687 help: "count of state fetches by the schema cache",
3688 )),
3689 cache_schema: cache("schema"),
3690 cache_migration: cache("migration"),
3691 migration_count_same: migration_count.with_label_values(&["same"]),
3692 migration_count_codec: migration_count.with_label_values(&["codec"]),
3693 migration_count_either: migration_count.with_label_values(&["either"]),
3694 migration_len_legacy_codec: migration_len.with_label_values(&["legacy_codec"]),
3695 migration_len_either_codec: migration_len.with_label_values(&["either_codec"]),
3696 migration_len_either_arrow: migration_len.with_label_values(&["either_arrow"]),
3697 migration_new_count: registry.register(metric!(
3698 name: "mz_persist_schema_migration_new_count",
3699 help: "count of migrations constructed",
3700 )),
3701 migration_new_seconds: registry.register(metric!(
3702 name: "mz_persist_schema_migration_new_seconds",
3703 help: "seconds spent constructing migration logic",
3704 )),
3705 migration_migrate_seconds: registry.register(metric!(
3706 name: "mz_persist_schema_migration_migrate_seconds",
3707 help: "seconds spent applying migration logic",
3708 )),
3709 }
3710 }
3711}
3712
3713#[derive(Debug, Clone)]
3714pub struct SchemaCacheMetrics {
3715 pub(crate) cached_count: IntCounter,
3716 pub(crate) computed_count: IntCounter,
3717 pub(crate) unavailable_count: IntCounter,
3718 pub(crate) added_count: IntCounter,
3719 pub(crate) dropped_count: IntCounter,
3720}
3721
3722#[derive(Debug)]
3723pub struct InlineMetrics {
3724 pub(crate) part_commit_count: IntCounter,
3725 pub(crate) part_commit_bytes: IntCounter,
3726 pub(crate) backpressure: BatchWriteMetrics,
3727}
3728
3729impl InlineMetrics {
3730 fn new(registry: &MetricsRegistry) -> Self {
3731 InlineMetrics {
3732 part_commit_count: registry.register(metric!(
3733 name: "mz_persist_inline_part_commit_count",
3734 help: "count of inline parts committed to state",
3735 )),
3736 part_commit_bytes: registry.register(metric!(
3737 name: "mz_persist_inline_part_commit_bytes",
3738 help: "total size of of inline parts committed to state",
3739 )),
3740 backpressure: BatchWriteMetrics::new(registry, "inline_backpressure"),
3741 }
3742 }
3743}
3744
3745fn blob_key_shard_id(key: &str) -> Option<String> {
3746 let (shard_id, _) = BlobKey::parse_ids(key).ok()?;
3747 Some(shard_id.to_string())
3748}
3749
3750pub fn encode_ts_metric<T: Codec64>(ts: &Antichain<T>) -> i64 {
3752 match ts.elements().first() {
3762 Some(ts) => i64::from_le_bytes(Codec64::encode(ts)),
3763 None => i64::MAX,
3764 }
3765}
3766
3767#[cfg(test)]
3768mod tests {
3769 use std::collections::BTreeSet;
3770
3771 use mz_dyncfg::ConfigUpdates;
3772 use mz_ore::metrics::aggregation::PERCENTILES;
3773
3774 use crate::cfg::SHARD_METRICS;
3775
3776 use super::*;
3777
3778 #[mz_ore::test]
3779 fn shards_aggregate_metrics_one_pass() {
3780 let registry = MetricsRegistry::new();
3781 let configs = Arc::clone(&PersistConfig::new_for_tests().configs);
3782 let shards = ShardsMetrics::new(®istry, Arc::clone(&configs));
3783 let agg = ShardsAggregateMetrics::new(Arc::clone(&shards.shards), configs);
3784 let a = shards.shard(&ShardId::new(), "a");
3785 let b = shards.shard(&ShardId::new(), "b");
3786
3787 agg.collect();
3789 assert_eq!(agg.count.get(), 2);
3790 assert_eq!(agg.stale_count.get(), 0);
3791
3792 a.stale.store(true, Ordering::Relaxed);
3793 agg.collect();
3794 assert_eq!(agg.count.get(), 2);
3795 assert_eq!(agg.stale_count.get(), 1);
3796
3797 b.stale.store(true, Ordering::Relaxed);
3798 agg.collect();
3799 assert_eq!(agg.stale_count.get(), 2);
3800
3801 a.stale.store(false, Ordering::Relaxed);
3804 drop(b);
3805 agg.collect();
3806 assert_eq!(agg.count.get(), 1);
3807 assert_eq!(agg.stale_count.get(), 0);
3808 }
3809
3810 #[mz_ore::test]
3811 fn per_shard_families_match_registrations() {
3812 let cfg = PersistConfig::new_for_tests();
3813 set_shard_metrics(&cfg, ShardMetricsExport::Both);
3814 let registry = MetricsRegistry::new();
3815 let metrics = Metrics::new(&cfg, ®istry);
3816 let shard = metrics.shards.shard(&ShardId::new(), "s");
3817 shard.set_batch_part_versions(["v1"].into_iter());
3818 let gathered = registry.gather();
3819
3820 let registered: BTreeSet<_> = gathered
3821 .iter()
3822 .filter(|f| !f.name().ends_with("_topk"))
3823 .filter(|f| {
3824 f.get_metric()
3825 .iter()
3826 .any(|m| m.get_label().iter().any(|l| l.name() == "shard"))
3827 })
3828 .map(|f| f.name())
3829 .collect();
3830 let listed: BTreeSet<_> = PER_SHARD_FAMILIES.iter().map(|f| f.name).collect();
3831 assert_eq!(registered, listed);
3832
3833 for (name, gauge) in shard_gauges() {
3836 let registered = find(&gathered, name)
3837 .unwrap_or_else(|| panic!("{name} not gathered"))
3838 .help();
3839 assert_eq!(gauge.help, registered, "{name}");
3840 }
3841
3842 let gathered: BTreeSet<_> = gathered.iter().map(|f| f.name()).collect();
3843 for family in PER_SHARD_FAMILIES {
3844 if let ShardAggregate::Counter { total } = family.aggregate {
3845 assert!(gathered.contains(total), "{total} not gathered");
3846 }
3847 }
3848 }
3849
3850 #[mz_ore::test]
3851 fn every_shard_gauge_has_its_aggregates() {
3852 let registry = MetricsRegistry::new();
3853 let configs = Arc::clone(&PersistConfig::new_for_tests().configs);
3854 let shards = ShardsMetrics::new(®istry, Arc::clone(&configs));
3855 let agg = ShardsAggregateMetrics::new(Arc::clone(&shards.shards), configs);
3856 let declared: BTreeSet<_> = agg.desc().iter().map(|d| d.fq_name.clone()).collect();
3857 for (gauge, g) in shard_gauges() {
3858 for suffix in ["_percentile", "_topk"] {
3859 let name = format!("{gauge}{suffix}");
3860 assert!(declared.contains(&name), "{name} not declared");
3861 }
3862 let sum = format!("{gauge}_sum");
3863 assert_eq!(declared.contains(&sum), g.sum, "{sum}");
3864 }
3865 }
3866
3867 fn find<'a>(families: &'a [MetricFamily], name: &str) -> Option<&'a MetricFamily> {
3868 families.iter().find(|f| f.name() == name)
3869 }
3870
3871 fn label<'a>(m: &'a prometheus::proto::Metric, name: &str) -> &'a str {
3872 m.get_label()
3873 .iter()
3874 .find(|l| l.name() == name)
3875 .map(|l| l.value())
3876 .unwrap_or_else(|| panic!("no label {name}"))
3877 }
3878
3879 fn gauge_set(
3882 families: &[MetricFamily],
3883 name: &str,
3884 label_name: &str,
3885 ) -> BTreeSet<(String, u64)> {
3886 find(families, name).map_or_else(BTreeSet::new, |f| {
3887 f.get_metric()
3888 .iter()
3889 .map(|m| {
3890 (
3891 label(m, label_name).to_string(),
3892 u64::cast_lossy(m.get_gauge().value()),
3893 )
3894 })
3895 .collect()
3896 })
3897 }
3898
3899 fn scalar(families: &[MetricFamily], name: &str) -> f64 {
3900 let f = find(families, name).unwrap_or_else(|| panic!("{name} not gathered"));
3901 let m = &f.get_metric()[0];
3902 if f.get_field_type() == prometheus::proto::MetricType::COUNTER {
3903 m.get_counter().value()
3904 } else {
3905 m.get_gauge().value()
3906 }
3907 }
3908
3909 fn metrics_with_shards(n: u64) -> (PersistConfig, MetricsRegistry, Vec<Arc<ShardMetrics>>) {
3912 let cfg = PersistConfig::new_for_tests();
3913 let registry = MetricsRegistry::new();
3914 let metrics = Metrics::new(&cfg, ®istry);
3915 let shards = (1..=n)
3916 .map(|i| {
3917 let shard = metrics.shards.shard(&ShardId::new(), &format!("s{i}"));
3918 shard.usage_current_state_batches_bytes.set(i);
3919 shard.set_batch_part_versions(["v1"].into_iter());
3920 shard
3921 })
3922 .collect();
3923 (cfg, registry, shards)
3924 }
3925
3926 fn set_shard_metrics(cfg: &PersistConfig, mode: ShardMetricsExport) {
3927 let mut updates = ConfigUpdates::default();
3928 updates.add(&SHARD_METRICS, mode.as_str());
3929 cfg.apply_from(&updates);
3930 }
3931
3932 fn family_names(families: &[MetricFamily]) -> BTreeSet<String> {
3933 families.iter().map(|f| f.name().to_string()).collect()
3934 }
3935
3936 fn always_exported() -> BTreeSet<String> {
3939 let mut names: BTreeSet<_> = ["mz_persist_shard_count", "mz_persist_stale_shard_count"]
3940 .into_iter()
3941 .map(String::from)
3942 .collect();
3943 names.extend(PER_SHARD_FAMILIES.iter().filter_map(|f| match f.aggregate {
3944 ShardAggregate::Counter { total } => Some(total.to_string()),
3945 _ => None,
3946 }));
3947 names
3948 }
3949
3950 #[mz_ore::test]
3951 fn shard_metrics_modes_round_trip() {
3952 for mode in ShardMetricsExport::ALL {
3953 assert_eq!(ShardMetricsExport::parse(mode.as_str()), Some(mode));
3954 }
3955 assert_eq!(
3956 ShardMetricsExport::parse(SHARD_METRICS.default()),
3957 Some(ShardMetricsExport::default())
3958 );
3959 assert_eq!(ShardMetricsExport::parse("not_a_mode"), None);
3960 }
3961
3962 #[mz_ore::test]
3963 fn unknown_shard_metrics_mode_exports_the_default() {
3964 let (cfg, registry, _shards) = metrics_with_shards(3);
3965 let summary = family_names(®istry.gather());
3966 let invalid =
3967 |families: &[MetricFamily]| scalar(families, "mz_persist_shard_metrics_mode_invalid");
3968
3969 let mut updates = ConfigUpdates::default();
3970 updates.add(&SHARD_METRICS, "summry");
3971 cfg.apply_from(&updates);
3972
3973 let gathered = registry.gather();
3974 assert_eq!(family_names(&gathered), summary);
3975 assert_eq!(invalid(&gathered), 0.0);
3977 assert_eq!(invalid(®istry.gather()), 1.0);
3978
3979 set_shard_metrics(&cfg, ShardMetricsExport::Both);
3980 let gathered = registry.gather();
3981 assert!(summary.is_subset(&family_names(&gathered)));
3982 assert_eq!(invalid(&gathered), 2.0);
3983 assert_eq!(invalid(®istry.gather()), 2.0);
3984 }
3985
3986 #[mz_ore::test]
3987 fn counter_totals_are_unchanged_by_the_mode() {
3988 let (cfg, registry, shards) = metrics_with_shards(3);
3989 for (i, shard) in shards.iter().enumerate() {
3990 shard.blob_gets.inc_by(u64::cast_from(i) + 1);
3991 shard.blob_sets.inc();
3992 }
3993 let totals = |families: &[MetricFamily]| -> BTreeMap<&'static str, f64> {
3994 PER_SHARD_FAMILIES
3995 .iter()
3996 .filter_map(|f| match f.aggregate {
3997 ShardAggregate::Counter { total } => Some((total, scalar(families, total))),
3998 _ => None,
3999 })
4000 .collect()
4001 };
4002 let both = registry.gather();
4003 assert_eq!(totals(&both)["mz_persist_blob_gets"], 6.0);
4004
4005 for mode in ShardMetricsExport::ALL {
4008 set_shard_metrics(&cfg, mode);
4009 assert_eq!(totals(®istry.gather()), totals(&both), "{mode:?}");
4010 }
4011 }
4012
4013 #[mz_ore::test]
4014 fn each_shard_metrics_mode_exports_its_families() {
4015 let (cfg, registry, _shards) = metrics_with_shards(3);
4016 set_shard_metrics(&cfg, ShardMetricsExport::Both);
4017 let both = family_names(®istry.gather());
4018 let per_shard: BTreeSet<_> = PER_SHARD_FAMILIES
4019 .iter()
4020 .map(|f| f.name.to_string())
4021 .collect();
4022 let summary: BTreeSet<_> = both
4023 .iter()
4024 .filter(|n| {
4025 n.ends_with("_percentile")
4026 || n.ends_with("_sum")
4027 || n.ends_with("_topk")
4028 || *n == "mz_persist_batch_part_version_count"
4029 })
4030 .cloned()
4031 .collect();
4032 let always = always_exported();
4033 assert!(both.is_superset(&per_shard));
4034 assert!(both.is_superset(&always));
4035 assert!(!summary.is_empty());
4036 assert!(always.is_disjoint(&per_shard) && always.is_disjoint(&summary));
4037
4038 for mode in ShardMetricsExport::ALL {
4041 set_shard_metrics(&cfg, mode);
4042 let mut expected = both.clone();
4043 if !mode.per_shard() {
4044 expected.retain(|n| !per_shard.contains(n));
4045 }
4046 if !mode.summary() {
4047 expected.retain(|n| !summary.contains(n));
4048 }
4049 assert!(expected.is_superset(&always), "{mode:?}");
4050 assert_eq!(family_names(®istry.gather()), expected, "{mode:?}");
4051 }
4052 }
4053
4054 #[mz_ore::test]
4055 #[cfg_attr(miri, ignore)] fn summary_scrape_is_flat_in_shard_count() {
4057 let scrape = |n: u64, mode: ShardMetricsExport| {
4058 let (cfg, registry, _shards) = metrics_with_shards(n);
4059 set_shard_metrics(&cfg, mode);
4060 let families = registry.gather();
4061 let series: usize = families.iter().map(|f| f.get_metric().len()).sum();
4062 let body = prometheus::TextEncoder::new()
4063 .encode_to_string(&families)
4064 .expect("encodable");
4065 (series, body.len())
4066 };
4067 let (few_series, few_bytes) = scrape(10, ShardMetricsExport::Summary);
4068 let (many_series, many_bytes) = scrape(10_000, ShardMetricsExport::Summary);
4069 let (both_series, both_bytes) = scrape(10_000, ShardMetricsExport::Both);
4070 assert_eq!(few_series, many_series);
4071 assert!(
4072 both_series >= many_series + PER_SHARD_FAMILIES.len() * 10_000,
4073 "{both_series} vs {many_series}"
4074 );
4075 assert!(many_bytes < few_bytes + 4096, "{many_bytes} vs {few_bytes}");
4077 assert!(
4078 both_bytes > 100 * many_bytes,
4079 "{both_bytes} vs {many_bytes}"
4080 );
4081 }
4082
4083 #[mz_ore::test]
4084 fn shard_info_lists_live_shards_when_enabled() {
4085 let (cfg, registry, mut shards) = metrics_with_shards(3);
4086 let info =
4087 |registry: &MetricsRegistry| gauge_set(®istry.gather(), SHARD_INFO_NAME, "name");
4088 let mut updates = ConfigUpdates::default();
4089 updates.add(&SHARD_INFO_METRIC, true);
4090 cfg.apply_from(&updates);
4091 for mode in ShardMetricsExport::ALL {
4093 set_shard_metrics(&cfg, mode);
4094 let expected = ["s1", "s2", "s3"].map(|n| (n.to_string(), 1)).into();
4095 assert_eq!(info(®istry), expected, "{mode:?}");
4096 }
4097
4098 shards.pop();
4099 assert_eq!(info(®istry).len(), 2);
4100 }
4101
4102 fn set_enable_regex(cfg: &PersistConfig, regex: &str) {
4103 let mut updates = ConfigUpdates::default();
4104 updates.add(&PER_SHARD_METRICS_ENABLE_REGEX, regex);
4105 cfg.apply_from(&updates);
4106 }
4107
4108 fn per_shard_series(
4110 families: &[MetricFamily],
4111 ) -> BTreeMap<(String, Vec<(String, String)>), prometheus::proto::Metric> {
4112 families
4113 .iter()
4114 .filter(|f| PER_SHARD_FAMILIES.iter().any(|p| p.name == f.name()))
4115 .flat_map(|f| {
4116 f.get_metric().iter().map(|m| {
4117 let labels = m
4118 .get_label()
4119 .iter()
4120 .map(|l| (l.name().to_string(), l.value().to_string()))
4121 .collect();
4122 ((f.name().to_string(), labels), m.clone())
4123 })
4124 })
4125 .collect()
4126 }
4127
4128 fn exported_shard_names(families: &[MetricFamily]) -> BTreeSet<String> {
4130 per_shard_series(families)
4131 .into_keys()
4132 .map(|(_, labels)| {
4133 labels
4134 .into_iter()
4135 .find(|(k, _)| k == "name")
4136 .expect("per-shard series have a name label")
4137 .1
4138 })
4139 .collect()
4140 }
4141
4142 #[mz_ore::test]
4143 fn enable_regex_keeps_matching_shards_unchanged() {
4144 let (cfg, registry, shards) = metrics_with_shards(5);
4145 set_shard_metrics(&cfg, ShardMetricsExport::Both);
4146 let both = registry.gather();
4147 set_shard_metrics(&cfg, ShardMetricsExport::Summary);
4148 let summary = registry.gather();
4149 assert!(exported_shard_names(&summary).is_empty());
4150
4151 let shard_id = shards[2].shard_id.to_string();
4154 set_enable_regex(&cfg, &shard_id);
4155 let enabled = registry.gather();
4156 let expected: BTreeMap<_, _> = per_shard_series(&both)
4157 .into_iter()
4158 .filter(|((_, labels), _)| labels.contains(&("shard".into(), shard_id.clone())))
4159 .collect();
4160 assert_eq!(expected.len(), PER_SHARD_FAMILIES.len());
4161 assert_eq!(per_shard_series(&enabled), expected);
4162 assert_eq!(family_names(&enabled), {
4163 let mut names = family_names(&summary);
4164 names.extend(PER_SHARD_FAMILIES.iter().map(|f| f.name.to_string()));
4165 names
4166 });
4167
4168 set_enable_regex(&cfg, "^s2$");
4170 assert_eq!(
4171 exported_shard_names(®istry.gather()),
4172 BTreeSet::from(["s2".to_string()])
4173 );
4174 set_enable_regex(&cfg, "^s(1|4)$");
4175 assert_eq!(
4176 exported_shard_names(®istry.gather()),
4177 BTreeSet::from(["s1".to_string(), "s4".to_string()])
4178 );
4179
4180 set_enable_regex(&cfg, "");
4182 assert_eq!(family_names(®istry.gather()), family_names(&summary));
4183
4184 set_enable_regex(&cfg, "^s2$");
4186 set_shard_metrics(&cfg, ShardMetricsExport::None);
4187 let gathered = registry.gather();
4188 assert_eq!(
4189 exported_shard_names(&gathered),
4190 BTreeSet::from(["s2".to_string()])
4191 );
4192 assert!(gathered.iter().all(|f| !f.name().ends_with("_percentile")));
4193 set_shard_metrics(&cfg, ShardMetricsExport::Both);
4194 assert_eq!(exported_shard_names(®istry.gather()).len(), 5);
4195 }
4196
4197 #[mz_ore::test]
4198 fn unanchored_enable_regex_over_matches() {
4199 let cfg = PersistConfig::new_for_tests();
4200 let registry = MetricsRegistry::new();
4201 let metrics = Metrics::new(&cfg, ®istry);
4202 let shard = |name: &str| metrics.shards.shard(&ShardId::new(), name);
4203 let _named: Vec<_> = (1..=10).map(|i| shard(&format!("src{i}"))).collect();
4206 set_shard_metrics(&cfg, ShardMetricsExport::Summary);
4207
4208 set_enable_regex(&cfg, "src1");
4211 assert_eq!(
4212 exported_shard_names(®istry.gather()),
4213 BTreeSet::from(["src1".to_string(), "src10".to_string()])
4214 );
4215
4216 let target = shard("by-id");
4221 let fragment = target.shard_id.to_string()[3..9].to_string();
4222 let decoy = shard(&format!("t-{fragment}-t"));
4223 assert!(!decoy.shard_id.to_string().contains(&fragment));
4224 set_enable_regex(&cfg, &fragment);
4225 let kept = exported_shard_names(®istry.gather());
4226 assert!(kept.contains(&target.name), "{kept:?}");
4227 assert!(kept.contains(&decoy.name), "{kept:?}");
4228 }
4229
4230 #[mz_ore::test]
4231 fn invalid_enable_regex_fails_closed() {
4232 let (cfg, registry, _shards) = metrics_with_shards(3);
4233 set_shard_metrics(&cfg, ShardMetricsExport::Summary);
4234 let summary = family_names(®istry.gather());
4235 let invalid = |families: &[MetricFamily]| {
4236 scalar(families, "mz_persist_per_shard_metrics_regex_invalid")
4237 };
4238
4239 set_enable_regex(&cfg, "(");
4240 let gathered = registry.gather();
4241 assert_eq!(family_names(&gathered), summary);
4242 assert_eq!(invalid(&gathered), 0.0);
4244 assert_eq!(invalid(®istry.gather()), 1.0);
4245
4246 set_enable_regex(&cfg, "^s1$");
4247 let gathered = registry.gather();
4248 assert_eq!(
4249 exported_shard_names(&gathered),
4250 BTreeSet::from(["s1".to_string()])
4251 );
4252 assert_eq!(invalid(&gathered), 2.0);
4253 assert_eq!(invalid(®istry.gather()), 2.0);
4254 }
4255
4256 fn gauge_count(families: &[MetricFamily], name: &str) -> usize {
4257 find(families, name).map_or(0, |f| f.get_metric().len())
4258 }
4259
4260 fn percentile(families: &[MetricFamily], name: &str, percentile: &str) -> u64 {
4262 gauge_set(families, name, "percentile")
4263 .into_iter()
4264 .find_map(|(p, v)| (p == percentile).then_some(v))
4265 .unwrap_or_else(|| panic!("no {percentile} in {name}"))
4266 }
4267
4268 #[mz_ore::test]
4269 fn shard_aggregates_fold_live_shards() {
4270 let registry = MetricsRegistry::new();
4271 let metrics = Metrics::new(&PersistConfig::new_for_tests(), ®istry);
4272 let mut shards: Vec<_> = (1..=12u64)
4273 .map(|i| {
4274 let shard = metrics.shards.shard(&ShardId::new(), &format!("s{i}"));
4275 shard.usage_current_state_batches_bytes.set(i * 100);
4276 shard
4277 })
4278 .collect();
4279 shards[0].set_batch_part_versions(["v1", "v1", "v2"].into_iter());
4280 shards[1].set_batch_part_versions(["v1"].into_iter());
4281
4282 let gathered = registry.gather();
4283 assert_eq!(scalar(&gathered, "mz_persist_shard_count"), 12.0);
4284 let percentile_families = gathered
4285 .iter()
4286 .filter(|f| {
4287 f.name().starts_with("mz_persist_shard_") && f.name().ends_with("_percentile")
4288 })
4289 .count();
4290 assert_eq!(percentile_families, shard_gauges().count());
4291 for (gauge, _) in shard_gauges() {
4292 let name = format!("{gauge}_percentile");
4293 assert_eq!(gauge_count(&gathered, &name), PERCENTILES.len(), "{name}");
4294 }
4295 let percentiles = "mz_persist_shard_usage_current_state_batches_bytes_percentile";
4296 assert_eq!(percentile(&gathered, percentiles, "min"), 100);
4297 assert_eq!(percentile(&gathered, percentiles, "p50"), 600);
4298 assert_eq!(percentile(&gathered, percentiles, "max"), 1200);
4299 let top_k = "mz_persist_shard_usage_current_state_batches_bytes_topk";
4300 let expected: BTreeSet<_> = shards[2..]
4301 .iter()
4302 .map(|s| {
4303 (
4304 s.shard_id.to_string(),
4305 s.usage_current_state_batches_bytes.get(),
4306 )
4307 })
4308 .collect();
4309 assert_eq!(gauge_set(&gathered, top_k, "shard"), expected);
4310 assert_eq!(
4311 gauge_set(&gathered, "mz_persist_batch_part_version_count", "version"),
4312 BTreeSet::from([("v1".to_string(), 3), ("v2".to_string(), 1)])
4313 );
4314
4315 let largest = shards.pop().expect("non-empty");
4318 let largest_id = largest.shard_id.to_string();
4319 drop(largest);
4320 shards[0].set_batch_part_versions(["v1"].into_iter());
4321 let gathered = registry.gather();
4322 assert_eq!(scalar(&gathered, "mz_persist_shard_count"), 11.0);
4323 assert_eq!(percentile(&gathered, percentiles, "max"), 1100);
4324 let top_k_shards = gauge_set(&gathered, top_k, "shard");
4325 assert_eq!(top_k_shards.len(), SHARD_TOP_K);
4326 assert!(top_k_shards.iter().all(|(shard, _)| *shard != largest_id));
4327 assert_eq!(
4328 gauge_set(&gathered, "mz_persist_batch_part_version_count", "version"),
4329 BTreeSet::from([("v1".to_string(), 2)])
4330 );
4331 }
4332
4333 #[mz_ore::test]
4334 fn process_counters_survive_shard_drop() {
4335 let cfg = PersistConfig::new_for_tests();
4336 set_shard_metrics(&cfg, ShardMetricsExport::Both);
4337 let registry = MetricsRegistry::new();
4338 let metrics = Metrics::new(&cfg, ®istry);
4339 let a = metrics.shards.shard(&ShardId::new(), "a");
4340 let b = metrics.shards.shard(&ShardId::new(), "b");
4341 a.blob_gets.inc_by(5);
4342 b.blob_gets.inc();
4343 a.encoded_diff_size.inc_by(100);
4344
4345 let gathered = registry.gather();
4346 assert_eq!(scalar(&gathered, "mz_persist_blob_gets"), 6.0);
4347 assert_eq!(gauge_count(&gathered, "mz_persist_shard_blob_gets"), 2);
4348
4349 drop(a);
4350 let gathered = registry.gather();
4351 assert_eq!(scalar(&gathered, "mz_persist_blob_gets"), 6.0);
4352 assert_eq!(scalar(&gathered, "mz_persist_diff_size_bytes"), 100.0);
4353 assert_eq!(gauge_count(&gathered, "mz_persist_shard_blob_gets"), 1);
4354 }
4355}