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) expired_live_read: IntCounter,
1067 pub(crate) dropped_part: IntCounter,
1068}
1069
1070impl LeaseMetrics {
1071 fn new(registry: &MetricsRegistry) -> Self {
1072 LeaseMetrics {
1073 timeout_read: registry.register(metric!(
1074 name: "mz_persist_lease_timeout_read",
1075 help: "count of readers whose lease timed out",
1076 )),
1077 expired_live_read: registry.register(metric!(
1078 name: "mz_persist_lease_expired_live_read",
1079 help: "count of fetches that found their reader's lease had timed out",
1080 )),
1081 dropped_part: registry.register(metric!(
1082 name: "mz_persist_lease_dropped_part",
1083 help: "count of LeasedBatchParts that were dropped without being politely returned",
1084 )),
1085 }
1086 }
1087}
1088
1089struct IncOnDrop(IntCounter);
1090
1091impl Drop for IncOnDrop {
1092 fn drop(&mut self) {
1093 self.0.inc()
1094 }
1095}
1096
1097pub struct MetricsRetryStream {
1098 retry: RetryStream,
1099 pub(crate) retries: IntCounter,
1100 sleep_seconds: Counter,
1101 _finished: IncOnDrop,
1102}
1103
1104impl MetricsRetryStream {
1105 pub fn new(retry: RetryStream, metrics: &RetryMetrics) -> Self {
1106 metrics.started.inc();
1107 MetricsRetryStream {
1108 retry,
1109 retries: metrics.retries.clone(),
1110 sleep_seconds: metrics.sleep_seconds.clone(),
1111 _finished: IncOnDrop(metrics.finished.clone()),
1112 }
1113 }
1114
1115 pub fn attempt(&self) -> usize {
1117 self.retry.attempt()
1118 }
1119
1120 pub fn next_sleep(&self) -> Duration {
1122 self.retry.next_sleep()
1123 }
1124
1125 pub async fn sleep(self) -> Self {
1130 self.retries.inc();
1131 self.sleep_seconds
1132 .inc_by(self.retry.next_sleep().as_secs_f64());
1133 let retry = self.retry.sleep().await;
1134 MetricsRetryStream {
1135 retry,
1136 retries: self.retries,
1137 sleep_seconds: self.sleep_seconds,
1138 _finished: self._finished,
1139 }
1140 }
1141}
1142
1143#[derive(Debug)]
1144pub struct CodecsMetrics {
1145 pub(crate) state: CodecMetrics,
1146 pub(crate) state_diff: CodecMetrics,
1147 pub(crate) batch: CodecMetrics,
1148 pub(crate) key: CodecMetrics,
1149 pub(crate) val: CodecMetrics,
1150 }
1153
1154#[derive(Debug)]
1155pub struct CodecMetrics {
1156 pub(crate) encode_count: IntCounter,
1157 pub(crate) encode_seconds: Counter,
1158 pub(crate) decode_count: IntCounter,
1159 pub(crate) decode_seconds: Counter,
1160}
1161
1162impl CodecMetrics {
1163 pub(crate) fn encode<R, F: FnOnce() -> R>(&self, f: F) -> R {
1164 let now = Instant::now();
1165 let r = f();
1166 self.encode_count.inc();
1167 self.encode_seconds.inc_by(now.elapsed().as_secs_f64());
1168 r
1169 }
1170
1171 pub(crate) fn decode<R, F: FnOnce() -> R>(&self, f: F) -> R {
1172 let now = Instant::now();
1173 let r = f();
1174 self.decode_count.inc();
1175 self.decode_seconds.inc_by(now.elapsed().as_secs_f64());
1176 r
1177 }
1178}
1179
1180#[derive(Debug)]
1181pub struct StateMetrics {
1182 pub(crate) apply_spine_fast_path: IntCounter,
1183 pub(crate) apply_spine_slow_path: IntCounter,
1184 pub(crate) apply_spine_slow_path_lenient: IntCounter,
1185 pub(crate) apply_spine_slow_path_lenient_adjustment: IntCounter,
1186 pub(crate) apply_spine_slow_path_with_reconstruction: IntCounter,
1187 pub(crate) apply_spine_flattened: IntCounter,
1188 pub(crate) update_state_noop_path: IntCounter,
1189 pub(crate) update_state_empty_path: IntCounter,
1190 pub(crate) update_state_fast_path: IntCounter,
1191 pub(crate) update_state_slow_path: IntCounter,
1192 pub(crate) rollup_at_seqno_migration: IntCounter,
1193 pub(crate) fetch_recent_live_diffs_fast_path: IntCounter,
1194 pub(crate) fetch_recent_live_diffs_slow_path: IntCounter,
1195 pub(crate) writer_added: IntCounter,
1196 pub(crate) writer_removed: IntCounter,
1197 pub(crate) force_apply_hostname: IntCounter,
1198 pub(crate) rollup_write_success: IntCounter,
1199 pub(crate) rollup_write_noop_latest: IntCounter,
1200 pub(crate) rollup_write_noop_truncated: IntCounter,
1201 pub(crate) hollow_run_missing_on_delete: IntCounter,
1202 pub(crate) finalized_rollup_assigned: IntCounter,
1203}
1204
1205impl StateMetrics {
1206 pub(crate) fn new(registry: &MetricsRegistry) -> Self {
1207 let rollup_write_noop: IntCounterVec = registry.register(metric!(
1208 name: "mz_persist_state_rollup_write_noop",
1209 help: "count of no-op rollup writes",
1210 var_labels: ["reason"],
1211 ));
1212
1213 StateMetrics {
1214 apply_spine_fast_path: registry.register(metric!(
1215 name: "mz_persist_state_apply_spine_fast_path",
1216 help: "count of spine diff applications that hit the fast path",
1217 )),
1218 apply_spine_slow_path: registry.register(metric!(
1219 name: "mz_persist_state_apply_spine_slow_path",
1220 help: "count of spine diff applications that hit the slow path",
1221 )),
1222 apply_spine_slow_path_lenient: registry.register(metric!(
1223 name: "mz_persist_state_apply_spine_slow_path_lenient",
1224 help: "count of spine diff applications that hit the lenient compaction apply path",
1225 )),
1226 apply_spine_slow_path_lenient_adjustment: registry.register(metric!(
1227 name: "mz_persist_state_apply_spine_slow_path_lenient_adjustment",
1228 help: "count of adjustments made by the lenient compaction apply path",
1229 )),
1230 apply_spine_slow_path_with_reconstruction: registry.register(metric!(
1231 name: "mz_persist_state_apply_spine_slow_path_with_reconstruction",
1232 help: "count of spine diff applications that hit the slow path with extra spine reconstruction step",
1233 )),
1234 apply_spine_flattened: registry.register(metric!(
1235 name: "mz_persist_state_apply_spine_flattened",
1236 help: "count of spine diff applications that flatten the trace",
1237 )),
1238 update_state_noop_path: registry.register(metric!(
1239 name: "mz_persist_state_update_state_noop_path",
1240 help: "count of state update applications that no-oped due to shared state",
1241 )),
1242 update_state_empty_path: registry.register(metric!(
1243 name: "mz_persist_state_update_state_empty_path",
1244 help: "count of state update applications that found no new updates",
1245 )),
1246 update_state_fast_path: registry.register(metric!(
1247 name: "mz_persist_state_update_state_fast_path",
1248 help: "count of state update applications that hit the fast path",
1249 )),
1250 update_state_slow_path: registry.register(metric!(
1251 name: "mz_persist_state_update_state_slow_path",
1252 help: "count of state update applications that hit the slow path",
1253 )),
1254 finalized_rollup_assigned: registry.register(metric!(
1255 name: "mz_persist_state_finalized_rollup_assigned",
1256 help: "count of rollups assigned on finalized shards, normally two or three per shard (steady growth means a rollup loop)",
1257 )),
1258 rollup_at_seqno_migration: registry.register(metric!(
1259 name: "mz_persist_state_rollup_at_seqno_migration",
1260 help: "count of fetch_rollup_at_seqno calls that only worked because of the migration",
1261 )),
1262 fetch_recent_live_diffs_fast_path: registry.register(metric!(
1263 name: "mz_persist_state_fetch_recent_live_diffs_fast_path",
1264 help: "count of fetch_recent_live_diffs that hit the fast path",
1265 )),
1266 fetch_recent_live_diffs_slow_path: registry.register(metric!(
1267 name: "mz_persist_state_fetch_recent_live_diffs_slow_path",
1268 help: "count of fetch_recent_live_diffs that hit the slow path",
1269 )),
1270 writer_added: registry.register(metric!(
1271 name: "mz_persist_state_writer_added",
1272 help: "count of writers added to the state",
1273 )),
1274 writer_removed: registry.register(metric!(
1275 name: "mz_persist_state_writer_removed",
1276 help: "count of writers removed from the state",
1277 )),
1278 force_apply_hostname: registry.register(metric!(
1279 name: "mz_persist_state_force_applied_hostname",
1280 help: "count of when hostname diffs needed to be force applied",
1281 )),
1282 rollup_write_success: registry.register(metric!(
1283 name: "mz_persist_state_rollup_write_success",
1284 help: "count of rollups written successful (may not be linked in to state)",
1285 )),
1286 rollup_write_noop_latest: rollup_write_noop.with_label_values(&["latest"]),
1287 rollup_write_noop_truncated: rollup_write_noop.with_label_values(&["truncated"]),
1288 hollow_run_missing_on_delete: registry.register(metric!(
1289 name: "mz_persist_state_hollow_run_missing_on_delete",
1290 help: "count of hollow run blobs found already missing while deleting the parts they reference",
1291 )),
1292 }
1293 }
1294}
1295
1296#[derive(Debug)]
1297pub struct ShardsMetrics {
1298 encoded_rollup_size: mz_ore::metrics::UIntGaugeVec,
1310 encoded_diff_size: mz_ore::metrics::IntCounterVec,
1311 hollow_batch_count: mz_ore::metrics::UIntGaugeVec,
1312 spine_batch_count: mz_ore::metrics::UIntGaugeVec,
1313 batch_part_count: mz_ore::metrics::UIntGaugeVec,
1314 batch_part_version_count: mz_ore::metrics::UIntGaugeVec,
1315 update_count: mz_ore::metrics::UIntGaugeVec,
1316 rollup_count: mz_ore::metrics::UIntGaugeVec,
1317 largest_batch_size: mz_ore::metrics::UIntGaugeVec,
1318 seqnos_held: mz_ore::metrics::UIntGaugeVec,
1319 seqnos_since_last_rollup: mz_ore::metrics::UIntGaugeVec,
1320 gc_seqno_held_parts: mz_ore::metrics::UIntGaugeVec,
1321 gc_live_diffs: mz_ore::metrics::UIntGaugeVec,
1322 gc_finished: mz_ore::metrics::IntCounterVec,
1323 compaction_applied: mz_ore::metrics::IntCounterVec,
1324 cmd_succeeded: mz_ore::metrics::IntCounterVec,
1325 usage_current_state_batches_bytes: mz_ore::metrics::UIntGaugeVec,
1326 usage_current_state_rollups_bytes: mz_ore::metrics::UIntGaugeVec,
1327 usage_referenced_not_current_state_bytes: mz_ore::metrics::UIntGaugeVec,
1328 usage_not_leaked_not_referenced_bytes: mz_ore::metrics::UIntGaugeVec,
1329 usage_leaked_bytes: mz_ore::metrics::UIntGaugeVec,
1330 blob_gets: mz_ore::metrics::IntCounterVec,
1331 blob_sets: mz_ore::metrics::IntCounterVec,
1332 unconsolidated_snapshot: mz_ore::metrics::IntCounterVec,
1333 inline_part_count: UIntGaugeVec,
1334 compact_batches: UIntGaugeVec,
1335 compacting_batches: UIntGaugeVec,
1336 noncompact_batches: UIntGaugeVec,
1337 process_blob_gets: IntCounter,
1338 process_blob_sets: IntCounter,
1339 process_encoded_diff_size: IntCounter,
1340 process_unconsolidated_snapshot: IntCounter,
1341 shards: Arc<Mutex<BTreeMap<ShardId, Weak<ShardMetrics>>>>,
1345}
1346
1347struct PerShardMetricsFilter {
1359 configs: Arc<ConfigSet>,
1360 mode: (String, ShardMetricsExport),
1362 invalid_mode: IntCounter,
1363 allow: (String, Option<Regex>),
1366 invalid_regex: IntCounter,
1367}
1368
1369impl PerShardMetricsFilter {
1370 fn filter(&mut self, families: &mut Vec<MetricFamily>) {
1371 if self.mode().per_shard() {
1372 return;
1373 }
1374 let allow = self.allow_regex();
1375 families.retain_mut(|f| {
1376 if !PER_SHARD_FAMILIES.iter().any(|p| p.name == f.name()) {
1377 return true;
1378 }
1379 let Some(allow) = allow else {
1380 return false;
1381 };
1382 f.mut_metric().retain(|m| {
1383 m.get_label()
1384 .iter()
1385 .any(|l| matches!(l.name(), "shard" | "name") && allow.is_match(l.value()))
1386 });
1387 !f.get_metric().is_empty()
1388 });
1389 }
1390
1391 fn allow_regex(&mut self) -> Option<&Regex> {
1398 let value = PER_SHARD_METRICS_ENABLE_REGEX.get(&self.configs);
1399 if value != self.allow.0 {
1400 let compiled = if value.is_empty() {
1401 None
1402 } else {
1403 Regex::new(&value)
1404 .inspect_err(|err| {
1405 warn!(
1406 "invalid {}, keeping no per-shard series: {err}",
1407 PER_SHARD_METRICS_ENABLE_REGEX.name()
1408 )
1409 })
1410 .ok()
1411 };
1412 self.allow = (value, compiled);
1413 }
1414 if !self.allow.0.is_empty() && self.allow.1.is_none() {
1415 self.invalid_regex.inc();
1416 }
1417 self.allow.1.as_ref()
1418 }
1419
1420 fn mode(&mut self) -> ShardMetricsExport {
1425 let value = SHARD_METRICS.get(&self.configs);
1426 if value != self.mode.0 {
1427 let parsed = ShardMetricsExport::parse(&value).unwrap_or_else(|| {
1428 let default = ShardMetricsExport::default();
1429 warn!(
1430 "unrecognized {}: '{value}', exporting '{}'",
1431 SHARD_METRICS.name(),
1432 default.as_str()
1433 );
1434 default
1435 });
1436 self.mode = (value, parsed);
1437 }
1438 if self.mode.1.as_str() != self.mode.0 {
1439 self.invalid_mode.inc();
1440 }
1441 self.mode.1
1442 }
1443}
1444
1445impl ShardsMetrics {
1446 fn new(registry: &MetricsRegistry, configs: Arc<ConfigSet>) -> Self {
1447 let shards = Arc::new(Mutex::new(BTreeMap::new()));
1448 registry.register_collector(ShardsAggregateMetrics::new(Arc::clone(&shards), configs));
1449 ShardsMetrics {
1450 encoded_rollup_size: registry.register(metric!(
1451 name: "mz_persist_shard_rollup_size_bytes",
1452 help: "total encoded rollup size by shard",
1453 var_labels: ["shard", "name"],
1454 )),
1455 encoded_diff_size: registry.register(metric!(
1456 name: "mz_persist_shard_diff_size_bytes",
1457 help: "total encoded diff size by shard",
1458 var_labels: ["shard", "name"],
1459 )),
1460 hollow_batch_count: registry.register(metric!(
1461 name: "mz_persist_shard_hollow_batch_count",
1462 help: "count of hollow batches by shard",
1463 var_labels: ["shard", "name"],
1464 )),
1465 spine_batch_count: registry.register(metric!(
1466 name: "mz_persist_shard_spine_batch_count",
1467 help: "count of spine batches by shard",
1468 var_labels: ["shard", "name"],
1469 )),
1470 batch_part_count: registry.register(metric!(
1471 name: "mz_persist_shard_batch_part_count",
1472 help: "count of batch parts by shard",
1473 var_labels: ["shard", "name"],
1474 )),
1475 batch_part_version_count: registry.register(metric!(
1476 name: "mz_persist_shard_batch_part_version_count",
1477 help: "count of batch parts by shard and version",
1478 var_labels: ["shard", "name", "version"],
1479 )),
1480 update_count: registry.register(metric!(
1481 name: "mz_persist_shard_update_count",
1482 help: "count of updates by shard",
1483 var_labels: ["shard", "name"],
1484 )),
1485 rollup_count: registry.register(metric!(
1486 name: "mz_persist_shard_rollup_count",
1487 help: "count of rollups by shard",
1488 var_labels: ["shard", "name"],
1489 )),
1490 largest_batch_size: registry.register(metric!(
1491 name: "mz_persist_shard_largest_batch_size",
1492 help: "largest encoded batch size by shard",
1493 var_labels: ["shard", "name"],
1494 )),
1495 seqnos_held: registry.register(metric!(
1496 name: "mz_persist_shard_seqnos_held",
1497 help: "maximum count of gc-ineligible states by shard",
1498 var_labels: ["shard", "name"],
1499 )),
1500 seqnos_since_last_rollup: registry.register(metric!(
1501 name: "mz_persist_shard_seqnos_since_last_rollup",
1502 help: "count of seqnos since last rollup",
1503 var_labels: ["shard", "name"],
1504 )),
1505 gc_seqno_held_parts: registry.register(metric!(
1506 name: "mz_persist_shard_gc_seqno_held_parts",
1507 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",
1508 var_labels: ["shard", "name"],
1509 )),
1510 gc_live_diffs: registry.register(metric!(
1511 name: "mz_persist_shard_gc_live_diffs",
1512 help: "the number of diffs (or, alternatively, the number of seqnos) present in consensus state at GC time",
1513 var_labels: ["shard", "name"],
1514 )),
1515 gc_finished: registry.register(metric!(
1516 name: "mz_persist_shard_gc_finished",
1517 help: "count of garbage collections finished by shard",
1518 var_labels: ["shard", "name"],
1519 )),
1520 compaction_applied: registry.register(metric!(
1521 name: "mz_persist_shard_compaction_applied",
1522 help: "count of compactions applied to state by shard",
1523 var_labels: ["shard", "name"],
1524 )),
1525 cmd_succeeded: registry.register(metric!(
1526 name: "mz_persist_shard_cmd_succeeded",
1527 help: "count of commands succeeded by shard",
1528 var_labels: ["shard", "name"],
1529 )),
1530 usage_current_state_batches_bytes: registry.register(metric!(
1531 name: "mz_persist_shard_usage_current_state_batches_bytes",
1532 help: "data in batches/parts referenced by current version of state",
1533 var_labels: ["shard", "name"],
1534 )),
1535 usage_current_state_rollups_bytes: registry.register(metric!(
1536 name: "mz_persist_shard_usage_current_state_rollups_bytes",
1537 help: "data in rollups referenced by current version of state",
1538 var_labels: ["shard", "name"],
1539 )),
1540 usage_referenced_not_current_state_bytes: registry.register(metric!(
1541 name: "mz_persist_shard_usage_referenced_not_current_state_bytes",
1542 help: "data referenced only by a previous version of state",
1543 var_labels: ["shard", "name"],
1544 )),
1545 usage_not_leaked_not_referenced_bytes: registry.register(metric!(
1546 name: "mz_persist_shard_usage_not_leaked_not_referenced_bytes",
1547 help: "data written by an active writer but not referenced by any version of state",
1548 var_labels: ["shard", "name"],
1549 )),
1550 usage_leaked_bytes: registry.register(metric!(
1551 name: "mz_persist_shard_usage_leaked_bytes",
1552 help: "data reclaimable by a leaked blob detector",
1553 var_labels: ["shard", "name"],
1554 )),
1555 blob_gets: registry.register(metric!(
1556 name: "mz_persist_shard_blob_gets",
1557 help: "number of Blob::get calls for this shard",
1558 var_labels: ["shard", "name"],
1559 )),
1560 blob_sets: registry.register(metric!(
1561 name: "mz_persist_shard_blob_sets",
1562 help: "number of Blob::set calls for this shard",
1563 var_labels: ["shard", "name"],
1564 )),
1565 unconsolidated_snapshot: registry.register(metric!(
1566 name: "mz_persist_shard_unconsolidated_snapshot",
1567 help: "in snapshot_and_read, the number of times consolidating the raw data wasn't enough to produce consolidated output",
1568 var_labels: ["shard", "name"],
1569 )),
1570 inline_part_count: registry.register(metric!(
1571 name: "mz_persist_shard_inline_part_count",
1572 help: "count of parts inline in shard metadata",
1573 var_labels: ["shard", "name"],
1574 )),
1575 compact_batches: registry.register(metric!(
1576 name: "mz_persist_shard_compact_batches",
1577 help: "number of fully compact batches in the shard",
1578 var_labels: ["shard", "name"],
1579 )),
1580 compacting_batches: registry.register(metric!(
1581 name: "mz_persist_shard_compacting_batches",
1582 help: "number of batches in the shard with compactions in progress",
1583 var_labels: ["shard", "name"],
1584 )),
1585 noncompact_batches: registry.register(metric!(
1586 name: "mz_persist_shard_noncompact_batches",
1587 help: "number of batches in the shard that aren't compact and have no ongoing compaction",
1588 var_labels: ["shard", "name"],
1589 )),
1590 process_blob_gets: registry.register(metric!(
1591 name: "mz_persist_blob_gets",
1592 help: "number of Blob::get calls for batch parts, over all shards",
1593 )),
1594 process_blob_sets: registry.register(metric!(
1595 name: "mz_persist_blob_sets",
1596 help: "number of Blob::set calls for batch parts, over all shards",
1597 )),
1598 process_encoded_diff_size: registry.register(metric!(
1599 name: "mz_persist_diff_size_bytes",
1600 help: "total encoded diff size, over all shards",
1601 )),
1602 process_unconsolidated_snapshot: registry.register(metric!(
1603 name: "mz_persist_unconsolidated_snapshot",
1604 help: "in snapshot_and_read, the number of times consolidating the raw data wasn't enough to produce consolidated output, over all shards",
1605 )),
1606 shards,
1607 }
1608 }
1609
1610 pub fn shard(&self, shard_id: &ShardId, name: &str) -> Arc<ShardMetrics> {
1611 let mut shards = self.shards.lock().expect("mutex poisoned");
1612 if let Some(shard) = shards.get(shard_id) {
1613 if let Some(shard) = shard.upgrade() {
1614 return Arc::clone(&shard);
1615 } else {
1616 assert!(shards.remove(shard_id).is_some());
1617 }
1618 }
1619 let shard = Arc::new(ShardMetrics::new(shard_id, name, self));
1620 assert!(
1621 shards
1622 .insert(shard_id.clone(), Arc::downgrade(&shard))
1623 .is_none()
1624 );
1625 shard
1626 }
1627
1628 fn live_shards(
1633 shards: &Arc<Mutex<BTreeMap<ShardId, Weak<ShardMetrics>>>>,
1634 ) -> Vec<Arc<ShardMetrics>> {
1635 let mut shards = shards.lock().expect("mutex poisoned");
1636 let mut live = Vec::with_capacity(shards.len());
1637 shards.retain(|_, metrics| match metrics.upgrade() {
1638 Some(metrics) => {
1639 live.push(metrics);
1640 true
1641 }
1642 None => false,
1643 });
1644 live
1645 }
1646}
1647
1648struct PerShardFamily {
1651 name: &'static str,
1652 aggregate: ShardAggregate,
1653}
1654
1655enum ShardAggregate {
1656 Gauge(ShardGauge),
1658 VersionSum,
1660 Counter {
1667 #[allow(dead_code)]
1670 total: &'static str,
1671 },
1672}
1673
1674struct ShardGauge {
1675 help: &'static str,
1678 top_k: usize,
1680 sum: bool,
1682 get: fn(&ShardMetrics) -> u64,
1683}
1684
1685const SHARD_TOP_K: usize = 10;
1687
1688const PER_SHARD_FAMILIES: &[PerShardFamily] = &[
1691 PerShardFamily {
1692 name: "mz_persist_shard_rollup_size_bytes",
1693 aggregate: ShardAggregate::Gauge(ShardGauge {
1694 help: "total encoded rollup size by shard",
1695 top_k: SHARD_TOP_K,
1696 sum: true,
1697 get: |m| m.latest_rollup_size.get(),
1698 }),
1699 },
1700 PerShardFamily {
1701 name: "mz_persist_shard_diff_size_bytes",
1702 aggregate: ShardAggregate::Counter {
1703 total: "mz_persist_diff_size_bytes",
1704 },
1705 },
1706 PerShardFamily {
1707 name: "mz_persist_shard_hollow_batch_count",
1708 aggregate: ShardAggregate::Gauge(ShardGauge {
1709 help: "count of hollow batches by shard",
1710 top_k: SHARD_TOP_K,
1711 sum: true,
1712 get: |m| m.hollow_batch_count.get(),
1713 }),
1714 },
1715 PerShardFamily {
1716 name: "mz_persist_shard_spine_batch_count",
1717 aggregate: ShardAggregate::Gauge(ShardGauge {
1718 help: "count of spine batches by shard",
1719 top_k: SHARD_TOP_K,
1720 sum: true,
1721 get: |m| m.spine_batch_count.get(),
1722 }),
1723 },
1724 PerShardFamily {
1725 name: "mz_persist_shard_batch_part_count",
1726 aggregate: ShardAggregate::Gauge(ShardGauge {
1727 help: "count of batch parts by shard",
1728 top_k: SHARD_TOP_K,
1729 sum: true,
1730 get: |m| m.batch_part_count.get(),
1731 }),
1732 },
1733 PerShardFamily {
1734 name: "mz_persist_shard_batch_part_version_count",
1735 aggregate: ShardAggregate::VersionSum,
1736 },
1737 PerShardFamily {
1738 name: "mz_persist_shard_update_count",
1739 aggregate: ShardAggregate::Gauge(ShardGauge {
1740 help: "count of updates by shard",
1741 top_k: SHARD_TOP_K,
1742 sum: true,
1743 get: |m| m.update_count.get(),
1744 }),
1745 },
1746 PerShardFamily {
1747 name: "mz_persist_shard_rollup_count",
1748 aggregate: ShardAggregate::Gauge(ShardGauge {
1749 help: "count of rollups by shard",
1750 top_k: SHARD_TOP_K,
1751 sum: true,
1752 get: |m| m.rollup_count.get(),
1753 }),
1754 },
1755 PerShardFamily {
1756 name: "mz_persist_shard_largest_batch_size",
1757 aggregate: ShardAggregate::Gauge(ShardGauge {
1758 help: "largest encoded batch size by shard",
1759 top_k: SHARD_TOP_K,
1760 sum: false,
1761 get: |m| m.largest_batch_size.get(),
1762 }),
1763 },
1764 PerShardFamily {
1765 name: "mz_persist_shard_seqnos_held",
1766 aggregate: ShardAggregate::Gauge(ShardGauge {
1767 help: "maximum count of gc-ineligible states by shard",
1768 top_k: SHARD_TOP_K,
1769 sum: true,
1770 get: |m| m.seqnos_held.get(),
1771 }),
1772 },
1773 PerShardFamily {
1774 name: "mz_persist_shard_seqnos_since_last_rollup",
1775 aggregate: ShardAggregate::Gauge(ShardGauge {
1776 help: "count of seqnos since last rollup",
1777 top_k: SHARD_TOP_K,
1778 sum: true,
1779 get: |m| m.seqnos_since_last_rollup.get(),
1780 }),
1781 },
1782 PerShardFamily {
1783 name: "mz_persist_shard_gc_seqno_held_parts",
1784 aggregate: ShardAggregate::Gauge(ShardGauge {
1785 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",
1786 top_k: SHARD_TOP_K,
1787 sum: true,
1788 get: |m| m.gc_seqno_held_parts.get(),
1789 }),
1790 },
1791 PerShardFamily {
1792 name: "mz_persist_shard_gc_live_diffs",
1793 aggregate: ShardAggregate::Gauge(ShardGauge {
1794 help: "the number of diffs (or, alternatively, the number of seqnos) present in consensus state at GC time",
1795 top_k: SHARD_TOP_K,
1796 sum: true,
1797 get: |m| m.gc_live_diffs.get(),
1798 }),
1799 },
1800 PerShardFamily {
1801 name: "mz_persist_shard_gc_finished",
1802 aggregate: ShardAggregate::Counter {
1803 total: "mz_persist_gc_finished",
1804 },
1805 },
1806 PerShardFamily {
1807 name: "mz_persist_shard_compaction_applied",
1808 aggregate: ShardAggregate::Counter {
1809 total: "mz_persist_compaction_applied",
1810 },
1811 },
1812 PerShardFamily {
1813 name: "mz_persist_shard_cmd_succeeded",
1814 aggregate: ShardAggregate::Counter {
1815 total: "mz_persist_cmd_succeeded_count",
1816 },
1817 },
1818 PerShardFamily {
1819 name: "mz_persist_shard_usage_current_state_batches_bytes",
1820 aggregate: ShardAggregate::Gauge(ShardGauge {
1821 help: "data in batches/parts referenced by current version of state",
1822 top_k: SHARD_TOP_K,
1823 sum: true,
1824 get: |m| m.usage_current_state_batches_bytes.get(),
1825 }),
1826 },
1827 PerShardFamily {
1828 name: "mz_persist_shard_usage_current_state_rollups_bytes",
1829 aggregate: ShardAggregate::Gauge(ShardGauge {
1830 help: "data in rollups referenced by current version of state",
1831 top_k: SHARD_TOP_K,
1832 sum: true,
1833 get: |m| m.usage_current_state_rollups_bytes.get(),
1834 }),
1835 },
1836 PerShardFamily {
1837 name: "mz_persist_shard_usage_referenced_not_current_state_bytes",
1838 aggregate: ShardAggregate::Gauge(ShardGauge {
1839 help: "data referenced only by a previous version of state",
1840 top_k: SHARD_TOP_K,
1841 sum: true,
1842 get: |m| m.usage_referenced_not_current_state_bytes.get(),
1843 }),
1844 },
1845 PerShardFamily {
1846 name: "mz_persist_shard_usage_not_leaked_not_referenced_bytes",
1847 aggregate: ShardAggregate::Gauge(ShardGauge {
1848 help: "data written by an active writer but not referenced by any version of state",
1849 top_k: SHARD_TOP_K,
1850 sum: true,
1851 get: |m| m.usage_not_leaked_not_referenced_bytes.get(),
1852 }),
1853 },
1854 PerShardFamily {
1855 name: "mz_persist_shard_usage_leaked_bytes",
1856 aggregate: ShardAggregate::Gauge(ShardGauge {
1857 help: "data reclaimable by a leaked blob detector",
1858 top_k: SHARD_TOP_K,
1859 sum: true,
1860 get: |m| m.usage_leaked_bytes.get(),
1861 }),
1862 },
1863 PerShardFamily {
1864 name: "mz_persist_shard_blob_gets",
1865 aggregate: ShardAggregate::Counter {
1866 total: "mz_persist_blob_gets",
1867 },
1868 },
1869 PerShardFamily {
1870 name: "mz_persist_shard_blob_sets",
1871 aggregate: ShardAggregate::Counter {
1872 total: "mz_persist_blob_sets",
1873 },
1874 },
1875 PerShardFamily {
1876 name: "mz_persist_shard_unconsolidated_snapshot",
1877 aggregate: ShardAggregate::Counter {
1878 total: "mz_persist_unconsolidated_snapshot",
1879 },
1880 },
1881 PerShardFamily {
1882 name: "mz_persist_shard_inline_part_count",
1883 aggregate: ShardAggregate::Gauge(ShardGauge {
1884 help: "count of parts inline in shard metadata",
1885 top_k: SHARD_TOP_K,
1886 sum: true,
1887 get: |m| m.inline_part_count.get(),
1888 }),
1889 },
1890 PerShardFamily {
1891 name: "mz_persist_shard_compact_batches",
1892 aggregate: ShardAggregate::Gauge(ShardGauge {
1893 help: "number of fully compact batches in the shard",
1894 top_k: SHARD_TOP_K,
1895 sum: true,
1896 get: |m| m.compact_batches.get(),
1897 }),
1898 },
1899 PerShardFamily {
1900 name: "mz_persist_shard_compacting_batches",
1901 aggregate: ShardAggregate::Gauge(ShardGauge {
1902 help: "number of batches in the shard with compactions in progress",
1903 top_k: SHARD_TOP_K,
1904 sum: true,
1905 get: |m| m.compacting_batches.get(),
1906 }),
1907 },
1908 PerShardFamily {
1909 name: "mz_persist_shard_noncompact_batches",
1910 aggregate: ShardAggregate::Gauge(ShardGauge {
1911 help: "number of batches in the shard that aren't compact and have no ongoing compaction",
1912 top_k: SHARD_TOP_K,
1913 sum: true,
1914 get: |m| m.noncompact_batches.get(),
1915 }),
1916 },
1917];
1918
1919fn shard_gauges() -> impl Iterator<Item = (&'static str, &'static ShardGauge)> {
1921 PER_SHARD_FAMILIES
1922 .iter()
1923 .filter_map(|f| match &f.aggregate {
1924 ShardAggregate::Gauge(g) => Some((f.name, g)),
1925 _ => None,
1926 })
1927}
1928
1929const SHARD_INFO_NAME: &str = "mz_persist_shard_info";
1930const BATCH_PART_VERSION_COUNT_NAME: &str = "mz_persist_batch_part_version_count";
1931const BATCH_PART_VERSION_COUNT_HELP: &str = "count of batch parts by version, over all shards";
1932
1933#[derive(Debug)]
1936struct ShardKey(Arc<ShardMetrics>);
1937
1938impl AggregationKey for ShardKey {
1939 const LABEL_NAMES: &'static [&'static str] = &["shard", "name"];
1940
1941 fn label_values(&self) -> Vec<String> {
1942 vec![self.0.shard_id.to_string(), self.0.name.clone()]
1943 }
1944}
1945
1946#[derive(Debug)]
1954struct ShardsAggregateMetrics {
1955 shards: Arc<Mutex<BTreeMap<ShardId, Weak<ShardMetrics>>>>,
1956 configs: Arc<ConfigSet>,
1957 count: GenericGauge<AtomicI64>,
1958 stale_count: GenericGauge<AtomicI64>,
1959 gauges: AggregatedFamilies<ShardKey>,
1960 gauge_gets: Vec<fn(&ShardMetrics) -> u64>,
1963 batch_part_version_count: raw::UIntGaugeVec,
1966 shard_info: raw::UIntGaugeVec,
1968}
1969
1970impl ShardsAggregateMetrics {
1971 fn new(
1972 shards: Arc<Mutex<BTreeMap<ShardId, Weak<ShardMetrics>>>>,
1973 configs: Arc<ConfigSet>,
1974 ) -> Self {
1975 let gauges = shard_gauges()
1976 .map(|(name, g)| AggregatedFamily {
1977 name: name.to_string(),
1978 help: g.help.to_string(),
1979 sum: g.sum,
1980 top_k: Some(g.top_k),
1981 })
1982 .collect();
1983 ShardsAggregateMetrics {
1984 shards,
1985 configs,
1986 count: MakeCollector::make_collector(metric!(
1987 name: "mz_persist_shard_count",
1988 help: "count of all active shards on this process",
1989 )),
1990 stale_count: MakeCollector::make_collector(metric!(
1991 name: "mz_persist_stale_shard_count",
1992 help: "count of shards on this process whose persisted state version \
1993 is behind this process's build version; per-process, so summing \
1994 across processes counts (shard, process) pairs, not distinct shards",
1995 )),
1996 gauges: AggregatedFamilies::new(gauges),
1997 gauge_gets: shard_gauges().map(|(_, g)| g.get).collect(),
1998 batch_part_version_count: new_batch_part_version_count(),
1999 shard_info: new_shard_info(),
2000 }
2001 }
2002
2003 fn summaries(&self, live: Vec<Arc<ShardMetrics>>) -> Vec<MetricFamily> {
2005 let mut versions = BTreeMap::<String, u64>::new();
2006 let mut values = Vec::new();
2007 let mut snapshot = self.gauges.snapshot();
2008 for m in live {
2009 values.clear();
2010 values.extend(self.gauge_gets.iter().map(|get| get(&m)));
2011 let version_map = m
2012 .batch_part_version_map
2013 .lock()
2014 .expect("mutex should not be poisoned");
2015 for (version, metrics) in version_map.iter() {
2016 let n = metrics.batch_part_version_count.get();
2017 if n == 0 {
2018 continue;
2019 }
2020 match versions.get_mut(version) {
2021 Some(total) => *total += n,
2022 None => {
2023 versions.insert(version.clone(), n);
2024 }
2025 }
2026 }
2027 drop(version_map);
2028 snapshot.push(ShardKey(m), &values);
2029 }
2030
2031 let mut families = snapshot.finish();
2032 let batch_part_version_count = new_batch_part_version_count();
2033 for (version, n) in versions {
2034 batch_part_version_count
2035 .with_label_values(&[version.as_str()])
2036 .set(n);
2037 }
2038 families.extend(batch_part_version_count.collect());
2039 families
2040 }
2041}
2042
2043fn new_shard_info() -> raw::UIntGaugeVec {
2046 raw::UIntGaugeVec::new(
2047 PrometheusOpts::new(SHARD_INFO_NAME, "1 for each active shard on this process"),
2048 ShardKey::LABEL_NAMES,
2049 )
2050 .expect("valid gauge vec")
2051}
2052
2053fn new_batch_part_version_count() -> raw::UIntGaugeVec {
2054 raw::UIntGaugeVec::new(
2055 PrometheusOpts::new(BATCH_PART_VERSION_COUNT_NAME, BATCH_PART_VERSION_COUNT_HELP),
2056 &["version"],
2057 )
2058 .expect("valid gauge vec")
2059}
2060
2061impl Collector for ShardsAggregateMetrics {
2062 fn desc(&self) -> Vec<&Desc> {
2063 let mut descs = self.count.desc();
2064 descs.extend(self.stale_count.desc());
2065 descs.extend(self.gauges.descs());
2066 descs.extend(self.batch_part_version_count.desc());
2067 descs.extend(self.shard_info.desc());
2068 descs
2069 }
2070
2071 fn collect(&self) -> Vec<MetricFamily> {
2072 let live = ShardsMetrics::live_shards(&self.shards);
2073 let mut count = 0;
2074 let mut stale_count = 0;
2075 for m in &live {
2076 count += 1;
2077 if m.stale.load(Ordering::Relaxed) {
2078 stale_count += 1;
2079 }
2080 }
2081 self.count.set(count);
2082 self.stale_count.set(stale_count);
2083
2084 let mut families = Vec::new();
2085 if SHARD_INFO_METRIC.get(&self.configs) {
2086 let shard_info = new_shard_info();
2087 for m in &live {
2088 shard_info
2089 .with_label_values(&[m.shard_id.to_string(), m.name.clone()])
2090 .set(1);
2091 }
2092 families.extend(shard_info.collect());
2093 }
2094 if ShardMetricsExport::get(&self.configs).summary() {
2095 families.extend(self.summaries(live));
2096 }
2097 families.extend(self.count.collect());
2098 families.extend(self.stale_count.collect());
2099 families
2100 }
2101}
2102
2103#[derive(Debug)]
2104pub struct ShardMetrics {
2105 pub shard_id: ShardId,
2106 pub name: String,
2107 pub largest_batch_size: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2108 pub latest_rollup_size: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2109 pub encoded_diff_size: ShardCounter,
2110 pub hollow_batch_count: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2111 pub spine_batch_count: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2112 pub batch_part_count: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2113 batch_part_version_count: mz_ore::metrics::UIntGaugeVec,
2114 batch_part_version_map: Mutex<BTreeMap<String, BatchPartVersionMetrics>>,
2115 pub update_count: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2116 pub rollup_count: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2117 pub seqnos_held: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2118 pub seqnos_since_last_rollup: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2119 pub gc_seqno_held_parts: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2120 pub gc_live_diffs: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2121 pub usage_current_state_batches_bytes: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2122 pub usage_current_state_rollups_bytes: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2123 pub usage_referenced_not_current_state_bytes: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2124 pub usage_not_leaked_not_referenced_bytes: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2125 pub usage_leaked_bytes: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2126 pub gc_finished: DeleteOnDropCounter<AtomicU64, Vec<String>>,
2127 pub compaction_applied: DeleteOnDropCounter<AtomicU64, Vec<String>>,
2128 pub cmd_succeeded: DeleteOnDropCounter<AtomicU64, Vec<String>>,
2129 pub blob_gets: ShardCounter,
2130 pub blob_sets: ShardCounter,
2131 pub unconsolidated_snapshot: ShardCounter,
2132 pub inline_part_count: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2133 pub compact_batches: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2134 pub compacting_batches: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2135 pub noncompact_batches: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2136 pub stale: AtomicBool,
2140}
2141
2142impl ShardMetrics {
2143 pub fn new(shard_id: &ShardId, name: &str, shards_metrics: &ShardsMetrics) -> Self {
2144 let shard = shard_id.to_string();
2145 ShardMetrics {
2146 shard_id: *shard_id,
2147 name: name.to_string(),
2148 latest_rollup_size: shards_metrics
2149 .encoded_rollup_size
2150 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2151 encoded_diff_size: ShardCounter {
2152 shard: shards_metrics
2153 .encoded_diff_size
2154 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2155 process: shards_metrics.process_encoded_diff_size.clone(),
2156 },
2157 hollow_batch_count: shards_metrics
2158 .hollow_batch_count
2159 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2160 spine_batch_count: shards_metrics
2161 .spine_batch_count
2162 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2163 batch_part_count: shards_metrics
2164 .batch_part_count
2165 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2166 batch_part_version_count: shards_metrics.batch_part_version_count.clone(),
2167 batch_part_version_map: Mutex::new(BTreeMap::new()),
2168 update_count: shards_metrics
2169 .update_count
2170 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2171 rollup_count: shards_metrics
2172 .rollup_count
2173 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2174 largest_batch_size: shards_metrics
2175 .largest_batch_size
2176 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2177 seqnos_held: shards_metrics
2178 .seqnos_held
2179 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2180 seqnos_since_last_rollup: shards_metrics
2181 .seqnos_since_last_rollup
2182 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2183 gc_seqno_held_parts: shards_metrics
2184 .gc_seqno_held_parts
2185 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2186 gc_live_diffs: shards_metrics
2187 .gc_live_diffs
2188 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2189 gc_finished: shards_metrics
2190 .gc_finished
2191 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2192 compaction_applied: shards_metrics
2193 .compaction_applied
2194 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2195 cmd_succeeded: shards_metrics
2196 .cmd_succeeded
2197 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2198 usage_current_state_batches_bytes: shards_metrics
2199 .usage_current_state_batches_bytes
2200 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2201 usage_current_state_rollups_bytes: shards_metrics
2202 .usage_current_state_rollups_bytes
2203 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2204 usage_referenced_not_current_state_bytes: shards_metrics
2205 .usage_referenced_not_current_state_bytes
2206 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2207 usage_not_leaked_not_referenced_bytes: shards_metrics
2208 .usage_not_leaked_not_referenced_bytes
2209 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2210 usage_leaked_bytes: shards_metrics
2211 .usage_leaked_bytes
2212 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2213 blob_gets: ShardCounter {
2214 shard: shards_metrics
2215 .blob_gets
2216 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2217 process: shards_metrics.process_blob_gets.clone(),
2218 },
2219 blob_sets: ShardCounter {
2220 shard: shards_metrics
2221 .blob_sets
2222 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2223 process: shards_metrics.process_blob_sets.clone(),
2224 },
2225 unconsolidated_snapshot: ShardCounter {
2226 shard: shards_metrics
2227 .unconsolidated_snapshot
2228 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2229 process: shards_metrics.process_unconsolidated_snapshot.clone(),
2230 },
2231 inline_part_count: shards_metrics
2232 .inline_part_count
2233 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2234 compact_batches: shards_metrics
2235 .compact_batches
2236 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2237 compacting_batches: shards_metrics
2238 .compacting_batches
2239 .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
2240 noncompact_batches: shards_metrics
2241 .noncompact_batches
2242 .get_delete_on_drop_metric(vec![shard, name.to_string()]),
2243 stale: AtomicBool::new(false),
2244 }
2245 }
2246
2247 pub(crate) fn set_batch_part_versions<'a>(
2248 &self,
2249 batch_parts_by_version: impl Iterator<Item = &'a str>,
2250 ) {
2251 let mut map = self
2252 .batch_part_version_map
2253 .lock()
2254 .expect("mutex should not be poisoned");
2255 for x in map.values() {
2262 x.batch_part_version_count.set(0);
2263 }
2264
2265 for key in batch_parts_by_version {
2268 if !map.contains_key(key) {
2269 map.insert(
2270 key.to_owned(),
2271 BatchPartVersionMetrics {
2272 batch_part_version_count: self
2273 .batch_part_version_count
2274 .get_delete_on_drop_metric(vec![
2275 self.shard_id.to_string(),
2276 self.name.clone(),
2277 key.to_owned(),
2278 ]),
2279 },
2280 );
2281 }
2282 let value = map.get(key).expect("inserted above");
2283 value.batch_part_version_count.inc();
2284 }
2285 }
2286}
2287
2288#[derive(Debug)]
2291pub struct ShardCounter {
2292 shard: DeleteOnDropCounter<AtomicU64, Vec<String>>,
2293 process: IntCounter,
2294}
2295
2296impl ShardCounter {
2297 pub fn inc(&self) {
2298 self.inc_by(1);
2299 }
2300
2301 pub fn inc_by(&self, v: u64) {
2302 self.shard.inc_by(v);
2303 self.process.inc_by(v);
2304 }
2305}
2306
2307#[derive(Debug)]
2308pub struct BatchPartVersionMetrics {
2309 pub batch_part_version_count: DeleteOnDropGauge<AtomicU64, Vec<String>>,
2310}
2311
2312#[derive(Debug)]
2314pub struct UsageAuditMetrics {
2315 pub blob_batch_part_bytes: UIntGauge,
2317 pub blob_batch_part_count: UIntGauge,
2319 pub blob_rollup_bytes: UIntGauge,
2321 pub blob_rollup_count: UIntGauge,
2323 pub blob_bytes: UIntGauge,
2325 pub blob_count: UIntGauge,
2327 pub step_blob_metadata: Counter,
2329 pub step_state: Counter,
2331 pub step_math: Counter,
2333}
2334
2335impl UsageAuditMetrics {
2336 fn new(registry: &MetricsRegistry) -> Self {
2337 let step_timings: CounterVec = registry.register(metric!(
2338 name: "mz_persist_audit_step_seconds",
2339 help: "time spent on individual steps of audit",
2340 var_labels: ["step"],
2341 ));
2342 UsageAuditMetrics {
2343 blob_batch_part_bytes: registry.register(metric!(
2344 name: "mz_persist_audit_blob_batch_part_bytes",
2345 help: "total size of batch parts in blob",
2346 )),
2347 blob_batch_part_count: registry.register(metric!(
2348 name: "mz_persist_audit_blob_batch_part_count",
2349 help: "count of batch parts in blob",
2350 )),
2351 blob_rollup_bytes: registry.register(metric!(
2352 name: "mz_persist_audit_blob_rollup_bytes",
2353 help: "total size of state rollups stored in blob",
2354 )),
2355 blob_rollup_count: registry.register(metric!(
2356 name: "mz_persist_audit_blob_rollup_count",
2357 help: "count of all state rollups in blob",
2358 )),
2359 blob_bytes: registry.register(metric!(
2360 name: "mz_persist_audit_blob_bytes",
2361 help: "total size of blob",
2362 )),
2363 blob_count: registry.register(metric!(
2364 name: "mz_persist_audit_blob_count",
2365 help: "count of all blobs",
2366 )),
2367 step_blob_metadata: step_timings.with_label_values(&["blob_metadata"]),
2368 step_state: step_timings.with_label_values(&["state"]),
2369 step_math: step_timings.with_label_values(&["math"]),
2370 }
2371 }
2372}
2373
2374#[derive(Debug)]
2377pub enum UpdateDelta {
2378 Negative(u64),
2380 NonNegative(u64),
2382}
2383
2384impl UpdateDelta {
2385 pub fn new(new: usize, old: usize) -> Self {
2388 if new < old {
2389 UpdateDelta::Negative(CastFrom::cast_from(old - new))
2390 } else {
2391 UpdateDelta::NonNegative(CastFrom::cast_from(new - old))
2392 }
2393 }
2394}
2395
2396#[derive(Debug, Clone)]
2399pub struct SinkMetrics {
2400 correction_insertions_total: IntCounter,
2402 correction_deletions_total: IntCounter,
2404 correction_capacity_increases_total: IntCounter,
2406 correction_capacity_decreases_total: IntCounter,
2408 correction_max_per_sink_worker_len_updates: raw::UIntGaugeVec,
2410 correction_max_per_sink_worker_capacity_updates: raw::UIntGaugeVec,
2412}
2413
2414impl SinkMetrics {
2415 fn new(registry: &MetricsRegistry) -> Self {
2416 SinkMetrics {
2417 correction_insertions_total: registry.register(metric!(
2418 name: "mz_persist_sink_correction_insertions_total",
2419 help: "The cumulative insertions observed on the correction buffer across workers and persist sinks.",
2420 )),
2421 correction_deletions_total: registry.register(metric!(
2422 name: "mz_persist_sink_correction_deletions_total",
2423 help: "The cumulative deletions observed on the correction buffer across workers and persist sinks.",
2424 )),
2425 correction_capacity_increases_total: registry.register(metric!(
2426 name: "mz_persist_sink_correction_capacity_increases_total",
2427 help: "The cumulative capacity increases observed on the correction buffer across workers and persist sinks.",
2428 )),
2429 correction_capacity_decreases_total: registry.register(metric!(
2430 name: "mz_persist_sink_correction_capacity_decreases_total",
2431 help: "The cumulative capacity decreases observed on the correction buffer across workers and persist sinks.",
2432 )),
2433 correction_max_per_sink_worker_len_updates: registry.register(metric!(
2434 name: "mz_persist_sink_correction_max_per_sink_worker_len_updates",
2435 help: "The maximum length observed for the correction buffer of any single persist sink per worker.",
2436 var_labels: ["worker_id"],
2437 )),
2438 correction_max_per_sink_worker_capacity_updates: registry.register(metric!(
2439 name: "mz_persist_sink_correction_max_per_sink_worker_capacity_updates",
2440 help: "The maximum capacity observed for the correction buffer of any single persist sink per worker.",
2441 var_labels: ["worker_id"],
2442 )),
2443 }
2444 }
2445
2446 pub fn for_worker(&self, worker_id: usize) -> SinkWorkerMetrics {
2451 let worker = worker_id.to_string();
2452 let correction_max_per_sink_worker_len_updates = self
2453 .correction_max_per_sink_worker_len_updates
2454 .with_label_values(&[&worker]);
2455 let correction_max_per_sink_worker_capacity_updates = self
2456 .correction_max_per_sink_worker_capacity_updates
2457 .with_label_values(&[&worker]);
2458 SinkWorkerMetrics {
2459 correction_max_per_sink_worker_len_updates,
2460 correction_max_per_sink_worker_capacity_updates,
2461 }
2462 }
2463
2464 pub fn report_correction_update_deltas(
2470 &self,
2471 correction_len_delta: UpdateDelta,
2472 correction_cap_delta: UpdateDelta,
2473 ) {
2474 match correction_len_delta {
2476 UpdateDelta::NonNegative(delta) => {
2477 if delta > 0 {
2478 self.correction_insertions_total.inc_by(delta)
2479 }
2480 }
2481 UpdateDelta::Negative(delta) => self.correction_deletions_total.inc_by(delta),
2482 }
2483 match correction_cap_delta {
2485 UpdateDelta::NonNegative(delta) => {
2486 if delta > 0 {
2487 self.correction_capacity_increases_total.inc_by(delta)
2488 }
2489 }
2490 UpdateDelta::Negative(delta) => self.correction_capacity_decreases_total.inc_by(delta),
2491 }
2492 }
2493}
2494
2495#[derive(Clone, Debug)]
2497pub struct SinkWorkerMetrics {
2498 correction_max_per_sink_worker_len_updates: UIntGauge,
2499 correction_max_per_sink_worker_capacity_updates: UIntGauge,
2500}
2501
2502impl SinkWorkerMetrics {
2503 pub fn report_correction_update_totals(&self, correction_len: usize, correction_cap: usize) {
2508 let correction_len = CastFrom::cast_from(correction_len);
2510 if correction_len > self.correction_max_per_sink_worker_len_updates.get() {
2511 self.correction_max_per_sink_worker_len_updates
2512 .set(correction_len);
2513 }
2514 let correction_cap = CastFrom::cast_from(correction_cap);
2515 if correction_cap > self.correction_max_per_sink_worker_capacity_updates.get() {
2516 self.correction_max_per_sink_worker_capacity_updates
2517 .set(correction_cap);
2518 }
2519 }
2520}
2521
2522#[derive(Debug)]
2527pub struct BackpressureMetrics {
2528 pub emitted_bytes: IntCounter,
2530 pub last_backpressured_bytes: UIntGauge,
2534 pub retired_bytes: IntCounter,
2536}
2537
2538impl BackpressureMetrics {
2539 fn new(registry: &MetricsRegistry) -> Self {
2540 BackpressureMetrics {
2541 emitted_bytes: registry.register(metric!(
2542 name: "mz_persist_backpressure_emitted_bytes",
2543 help: "bytes emitted by backpressure operators",
2544 )),
2545 last_backpressured_bytes: registry.register(metric!(
2546 name: "mz_persist_backpressure_last_backpressured_bytes",
2547 help: "sum over backpressure operators of the inflight bytes each last \
2548 stalled on; not comparable to retired bytes, but nonzero growth \
2549 indicates backpressure is happening",
2550 )),
2551 retired_bytes: registry.register(metric!(
2552 name: "mz_persist_backpressure_retired_bytes",
2553 help: "bytes retired by processing downstream of backpressure operators",
2554 )),
2555 }
2556 }
2557}
2558
2559#[derive(Debug)]
2561pub struct AlertsMetrics {
2562 pub(crate) blob_failures: IntCounter,
2563 pub(crate) consensus_failures: IntCounter,
2564}
2565
2566impl AlertsMetrics {
2567 fn new(registry: &MetricsRegistry) -> Self {
2568 AlertsMetrics {
2569 blob_failures: registry.register(metric!(
2570 name: "mz_persist_blob_failures",
2571 help: "count of all blob operation failures",
2572 const_labels: {"honeycomb" => "import"},
2573 )),
2574 consensus_failures: registry.register(metric!(
2575 name: "mz_persist_consensus_failures",
2576 help: "count of determinate consensus operation failures",
2577 const_labels: {"honeycomb" => "import"},
2578 )),
2579 }
2580 }
2581}
2582
2583#[derive(Debug)]
2585pub struct PubSubServerMetrics {
2586 pub(crate) active_connections: UIntGauge,
2587 pub(crate) broadcasted_diff_count: IntCounter,
2588 pub(crate) broadcasted_diff_bytes: IntCounter,
2589 pub(crate) broadcasted_diff_dropped_channel_full: IntCounter,
2590
2591 pub(crate) push_seconds: Counter,
2592 pub(crate) subscribe_seconds: Counter,
2593 pub(crate) unsubscribe_seconds: Counter,
2594 pub(crate) connection_cleanup_seconds: Counter,
2595
2596 pub(crate) push_call_count: IntCounter,
2597 pub(crate) subscribe_call_count: IntCounter,
2598 pub(crate) unsubscribe_call_count: IntCounter,
2599}
2600
2601impl PubSubServerMetrics {
2602 pub(crate) fn new(registry: &MetricsRegistry) -> Self {
2603 let op_timings: CounterVec = registry.register(metric!(
2604 name: "mz_persist_pubsub_server_operation_seconds",
2605 help: "time spent in pubsub server performing each operation",
2606 var_labels: ["op"],
2607 ));
2608 let call_count: IntCounterVec = registry.register(metric!(
2609 name: "mz_persist_pubsub_server_call_count",
2610 help: "count of each pubsub server message received",
2611 var_labels: ["call"],
2612 ));
2613
2614 Self {
2615 active_connections: registry.register(metric!(
2616 name: "mz_persist_pubsub_server_active_connections",
2617 help: "number of active connections to server",
2618 )),
2619 broadcasted_diff_count: registry.register(metric!(
2620 name: "mz_persist_pubsub_server_broadcasted_diff_count",
2621 help: "count of total broadcast diff messages sent",
2622 )),
2623 broadcasted_diff_bytes: registry.register(metric!(
2624 name: "mz_persist_pubsub_server_broadcasted_diff_bytes",
2625 help: "count of total broadcast diff bytes sent",
2626 )),
2627 broadcasted_diff_dropped_channel_full: registry.register(metric!(
2628 name: "mz_persist_pubsub_server_broadcasted_diff_dropped_channel_full",
2629 help: "count of diffs dropped due to full connection channel",
2630 )),
2631
2632 push_seconds: op_timings.with_label_values(&["push"]),
2633 subscribe_seconds: op_timings.with_label_values(&["subscribe"]),
2634 unsubscribe_seconds: op_timings.with_label_values(&["unsubscribe"]),
2635 connection_cleanup_seconds: op_timings.with_label_values(&["cleanup"]),
2636
2637 push_call_count: call_count.with_label_values(&["push"]),
2638 subscribe_call_count: call_count.with_label_values(&["subscribe"]),
2639 unsubscribe_call_count: call_count.with_label_values(&["unsubscribe"]),
2640 }
2641 }
2642}
2643
2644#[derive(Debug)]
2646pub struct PubSubClientMetrics {
2647 pub sender: PubSubClientSenderMetrics,
2648 pub receiver: PubSubClientReceiverMetrics,
2649 pub grpc_connection: PubSubGrpcClientConnectionMetrics,
2650}
2651
2652impl PubSubClientMetrics {
2653 fn new(registry: &MetricsRegistry) -> Self {
2654 PubSubClientMetrics {
2655 sender: PubSubClientSenderMetrics::new(registry),
2656 receiver: PubSubClientReceiverMetrics::new(registry),
2657 grpc_connection: PubSubGrpcClientConnectionMetrics::new(registry),
2658 }
2659 }
2660}
2661
2662#[derive(Debug)]
2663pub struct PubSubGrpcClientConnectionMetrics {
2664 pub(crate) connected: UIntGauge,
2665 pub(crate) connection_established_count: IntCounter,
2666 pub(crate) connect_call_attempt_count: IntCounter,
2667 pub(crate) broadcast_recv_lagged_count: IntCounter,
2668 pub(crate) grpc_error_count: IntCounter,
2669}
2670
2671impl PubSubGrpcClientConnectionMetrics {
2672 fn new(registry: &MetricsRegistry) -> Self {
2673 Self {
2674 connected: registry.register(metric!(
2675 name: "mz_persist_pubsub_client_grpc_connected",
2676 help: "whether the grpc client is currently connected",
2677 )),
2678 connection_established_count: registry.register(metric!(
2679 name: "mz_persist_pubsub_client_grpc_connection_established_count",
2680 help: "count of grpc connection establishments to pubsub server",
2681 )),
2682 connect_call_attempt_count: registry.register(metric!(
2683 name: "mz_persist_pubsub_client_grpc_connect_call_attempt_count",
2684 help: "count of connection call attempts (including retries) to pubsub server",
2685 )),
2686 broadcast_recv_lagged_count: registry.register(metric!(
2687 name: "mz_persist_pubsub_client_grpc_broadcast_recv_lagged_count",
2688 help: "times a message was missed by broadcast receiver due to lag",
2689 )),
2690 grpc_error_count: registry.register(metric!(
2691 name: "mz_persist_pubsub_client_grpc_error_count",
2692 help: "count of grpc errors received",
2693 )),
2694 }
2695 }
2696}
2697
2698#[derive(Clone, Debug)]
2699pub struct PubSubClientReceiverMetrics {
2700 pub(crate) push_received: IntCounter,
2701 pub(crate) unknown_message_received: IntCounter,
2702 pub(crate) approx_diff_latency_seconds: Histogram,
2703
2704 pub(crate) state_pushed_diff_fast_path: IntCounter,
2705 pub(crate) state_pushed_diff_slow_path_succeeded: IntCounter,
2706 pub(crate) state_pushed_diff_slow_path_failed: IntCounter,
2707
2708 pub(crate) diff_applied: IntCounter,
2709 pub(crate) diff_not_applied_stale: IntCounter,
2710 pub(crate) diff_not_applied_out_of_order: IntCounter,
2711}
2712
2713impl PubSubClientReceiverMetrics {
2714 fn new(registry: &MetricsRegistry) -> Self {
2715 let call_received: IntCounterVec = registry.register(metric!(
2716 name: "mz_persist_pubsub_client_call_received",
2717 help: "times a pubsub client call was received",
2718 var_labels: ["call"],
2719 ));
2720
2721 Self {
2722 push_received: call_received.with_label_values(&["push"]),
2723 unknown_message_received: call_received.with_label_values(&["unknown"]),
2724 approx_diff_latency_seconds: registry.register(metric!(
2725 name: "mz_persist_pubsub_client_approx_diff_apply_latency_seconds",
2726 help: "histogram of (approximate) latency between sending a diff and applying it",
2727 buckets: prometheus::exponential_buckets(0.001, 2.0, 13).expect("buckets"),
2728 )),
2729
2730 state_pushed_diff_fast_path: registry.register(metric!(
2731 name: "mz_persist_pubsub_client_receiver_state_push_diff_fast_path",
2732 help: "count fast-path state push_diff calls",
2733 )),
2734 state_pushed_diff_slow_path_succeeded: registry.register(metric!(
2735 name: "mz_persist_pubsub_client_receiver_state_push_diff_slow_path_succeeded",
2736 help: "count of successful slow-path state push_diff calls",
2737 )),
2738 state_pushed_diff_slow_path_failed: registry.register(metric!(
2739 name: "mz_persist_pubsub_client_receiver_state_push_diff_slow_path_failed",
2740 help: "count of unsuccessful slow-path state push_diff calls",
2741 )),
2742 diff_applied: registry.register(metric!(
2743 name: "mz_persist_pubsub_client_receiver_diff_applied",
2744 help: "number of diffs received via pubsub that applied",
2745 )),
2746 diff_not_applied_stale: registry.register(metric!(
2747 name: "mz_persist_pubsub_client_receiver_diff_not_applied_stale",
2748 help: "number of diffs received via pubsub that did not apply due to staleness",
2749 )),
2750 diff_not_applied_out_of_order: registry.register(metric!(
2751 name: "mz_persist_pubsub_client_receiver_diff_not_applied_out_of_order",
2752 help: "number of diffs received via pubsub that did not apply due to out-of-order delivery",
2753 )),
2754 }
2755 }
2756}
2757
2758#[derive(Debug)]
2759pub struct PubSubClientSenderMetrics {
2760 pub push: PubSubClientCallMetrics,
2761 pub subscribe: PubSubClientCallMetrics,
2762 pub unsubscribe: PubSubClientCallMetrics,
2763}
2764
2765#[derive(Debug)]
2766pub struct PubSubClientCallMetrics {
2767 pub(crate) succeeded: IntCounter,
2768 pub(crate) bytes_sent: IntCounter,
2769 pub(crate) failed: IntCounter,
2770}
2771
2772impl PubSubClientSenderMetrics {
2773 fn new(registry: &MetricsRegistry) -> Self {
2774 let call_bytes_sent: IntCounterVec = registry.register(metric!(
2775 name: "mz_persist_pubsub_client_call_bytes_sent",
2776 help: "number of bytes sent for a given pubsub client call",
2777 var_labels: ["call"],
2778 ));
2779 let call_succeeded: IntCounterVec = registry.register(metric!(
2780 name: "mz_persist_pubsub_client_call_succeeded",
2781 help: "times a pubsub client call succeeded",
2782 var_labels: ["call"],
2783 ));
2784 let call_failed: IntCounterVec = registry.register(metric!(
2785 name: "mz_persist_pubsub_client_call_failed",
2786 help: "times a pubsub client call failed",
2787 var_labels: ["call"],
2788 ));
2789
2790 Self {
2791 push: PubSubClientCallMetrics {
2792 succeeded: call_succeeded.with_label_values(&["push"]),
2793 failed: call_failed.with_label_values(&["push"]),
2794 bytes_sent: call_bytes_sent.with_label_values(&["push"]),
2795 },
2796 subscribe: PubSubClientCallMetrics {
2797 succeeded: call_succeeded.with_label_values(&["subscribe"]),
2798 failed: call_failed.with_label_values(&["subscribe"]),
2799 bytes_sent: call_bytes_sent.with_label_values(&["subscribe"]),
2800 },
2801 unsubscribe: PubSubClientCallMetrics {
2802 succeeded: call_succeeded.with_label_values(&["unsubscribe"]),
2803 failed: call_failed.with_label_values(&["unsubscribe"]),
2804 bytes_sent: call_bytes_sent.with_label_values(&["unsubscribe"]),
2805 },
2806 }
2807 }
2808}
2809
2810#[derive(Debug)]
2811pub struct LocksMetrics {
2812 pub(crate) applier_read_cacheable: LockMetrics,
2813 pub(crate) applier_read_noncacheable: LockMetrics,
2814 pub(crate) applier_write: LockMetrics,
2815 pub(crate) watch: LockMetrics,
2816}
2817
2818#[derive(Debug, Clone)]
2819pub struct LockMetrics {
2820 pub(crate) acquire_count: IntCounter,
2821 pub(crate) blocking_acquire_count: IntCounter,
2822 pub(crate) blocking_seconds: Counter,
2823}
2824
2825#[derive(Debug)]
2826pub struct WatchMetrics {
2827 pub(crate) wait_woken_via_watch: IntCounter,
2828 pub(crate) wait_woken_via_sleep: IntCounter,
2829 pub(crate) wait_resolved_via_watch: IntCounter,
2830 pub(crate) wait_resolved_via_sleep: IntCounter,
2831 pub(crate) notify_sent: IntCounter,
2832 pub(crate) notify_upper_sent: IntCounter,
2833 pub(crate) notify_noop: IntCounter,
2834 pub(crate) notify_recv: IntCounter,
2835 pub(crate) notify_lagged: IntCounter,
2836 pub(crate) notify_wait_started: IntCounter,
2837 pub(crate) notify_wait_finished: IntCounter,
2838}
2839
2840impl WatchMetrics {
2841 fn new(registry: &MetricsRegistry) -> Self {
2842 WatchMetrics {
2843 wait_woken_via_watch: registry.register(metric!(
2844 name: "mz_persist_wait_woken_via_watch",
2845 help: "count of wait-for-uppers wakes via watch notify",
2846 )),
2847 wait_woken_via_sleep: registry.register(metric!(
2848 name: "mz_persist_wait_woken_via_sleep",
2849 help: "count of wait-for-uppers wakes via sleep",
2850 )),
2851 wait_resolved_via_watch: registry.register(metric!(
2852 name: "mz_persist_wait_resolved_via_watch",
2853 help: "count of wait-for-uppers resolved via watch notify",
2854 )),
2855 wait_resolved_via_sleep: registry.register(metric!(
2856 name: "mz_persist_wait_resolved_via_sleep",
2857 help: "count of wait-for-uppers resolved via sleep",
2858 )),
2859 notify_sent: registry.register(metric!(
2860 name: "mz_persist_watch_notify_sent",
2861 help: "count of watch notifications sent to a non-empty broadcast channel",
2862 )),
2863 notify_upper_sent: registry.register(metric!(
2864 name: "mz_persist_watch_notify_upper_sent",
2865 help: "count of strict shard upper advances signaled to upper waiters",
2866 )),
2867 notify_noop: registry.register(metric!(
2868 name: "mz_persist_watch_notify_noop",
2869 help: "count of watch notifications sent to an broadcast channel",
2870 )),
2871 notify_recv: registry.register(metric!(
2872 name: "mz_persist_watch_notify_recv",
2873 help: "count of watch notifications received from the broadcast channel",
2874 )),
2875 notify_lagged: registry.register(metric!(
2876 name: "mz_persist_watch_notify_lagged",
2877 help: "count of lagged events in the watch notification broadcast channel",
2878 )),
2879 notify_wait_started: registry.register(metric!(
2880 name: "mz_persist_watch_notify_wait_started",
2881 help: "count of watch wait calls started",
2882 )),
2883 notify_wait_finished: registry.register(metric!(
2884 name: "mz_persist_watch_notify_wait_finished",
2885 help: "count of watch wait calls resolved",
2886 )),
2887 }
2888 }
2889}
2890
2891#[derive(Debug)]
2892pub struct PushdownMetrics {
2893 pub(crate) parts_filtered_count: IntCounter,
2894 pub(crate) parts_filtered_bytes: IntCounter,
2895 pub(crate) parts_fetched_count: IntCounter,
2896 pub(crate) parts_fetched_bytes: IntCounter,
2897 pub(crate) parts_audited_count: IntCounter,
2898 pub(crate) parts_audited_bytes: IntCounter,
2899 pub(crate) parts_inline_count: IntCounter,
2900 pub(crate) parts_inline_bytes: IntCounter,
2901 pub(crate) parts_faked_count: IntCounter,
2902 pub(crate) parts_faked_bytes: IntCounter,
2903 pub(crate) parts_stats_trimmed_count: IntCounter,
2904 pub(crate) parts_stats_trimmed_bytes: IntCounter,
2905 pub(crate) parts_projection_trimmed_bytes: IntCounter,
2906 pub part_stats: PartStatsMetrics,
2907}
2908
2909impl PushdownMetrics {
2910 fn new(registry: &MetricsRegistry) -> Self {
2911 PushdownMetrics {
2912 parts_filtered_count: registry.register(metric!(
2913 name: "mz_persist_pushdown_parts_filtered_count",
2914 help: "count of parts filtered by pushdown",
2915 )),
2916 parts_filtered_bytes: registry.register(metric!(
2917 name: "mz_persist_pushdown_parts_filtered_bytes",
2918 help: "total size of parts filtered by pushdown in bytes",
2919 )),
2920 parts_fetched_count: registry.register(metric!(
2921 name: "mz_persist_pushdown_parts_fetched_count",
2922 help: "count of parts not filtered by pushdown",
2923 )),
2924 parts_fetched_bytes: registry.register(metric!(
2925 name: "mz_persist_pushdown_parts_fetched_bytes",
2926 help: "total size of parts not filtered by pushdown in bytes",
2927 )),
2928 parts_audited_count: registry.register(metric!(
2929 name: "mz_persist_pushdown_parts_audited_count",
2930 help: "count of parts fetched only for pushdown audit",
2931 )),
2932 parts_audited_bytes: registry.register(metric!(
2933 name: "mz_persist_pushdown_parts_audited_bytes",
2934 help: "total size of parts fetched only for pushdown audit",
2935 )),
2936 parts_inline_count: registry.register(metric!(
2937 name: "mz_persist_pushdown_parts_inline_count",
2938 help: "count of parts not fetched because they were inline",
2939 )),
2940 parts_inline_bytes: registry.register(metric!(
2941 name: "mz_persist_pushdown_parts_inline_bytes",
2942 help: "total size of parts not fetched because they were inline",
2943 )),
2944 parts_faked_count: registry.register(metric!(
2945 name: "mz_persist_pushdown_parts_faked_count",
2946 help: "count of parts faked because of aggressive projection pushdown",
2947 )),
2948 parts_faked_bytes: registry.register(metric!(
2949 name: "mz_persist_pushdown_parts_faked_bytes",
2950 help: "total size of parts replaced with fakes by aggressive projection pushdown",
2951 )),
2952 parts_stats_trimmed_count: registry.register(metric!(
2953 name: "mz_persist_pushdown_parts_stats_trimmed_count",
2954 help: "count of trimmed part stats",
2955 )),
2956 parts_stats_trimmed_bytes: registry.register(metric!(
2957 name: "mz_persist_pushdown_parts_stats_trimmed_bytes",
2958 help: "total bytes trimmed from part stats",
2959 )),
2960 parts_projection_trimmed_bytes: registry.register(metric!(
2961 name: "mz_persist_pushdown_parts_projection_trimmed_bytes",
2962 help: "total bytes trimmed from columnar data because of projection pushdown",
2963 )),
2964 part_stats: PartStatsMetrics::new(registry),
2965 }
2966 }
2967}
2968
2969#[derive(Debug)]
2970pub struct ConsolidationMetrics {
2971 pub(crate) parts_fetched: IntCounter,
2972 pub(crate) parts_skipped: IntCounter,
2973 pub(crate) parts_wasted: IntCounter,
2974 pub(crate) wrong_sort: IntCounter,
2975}
2976
2977impl ConsolidationMetrics {
2978 fn new(registry: &MetricsRegistry) -> Self {
2979 ConsolidationMetrics {
2980 parts_fetched: registry.register(metric!(
2981 name: "mz_persist_consolidation_parts_fetched_count",
2982 help: "count of parts that were fetched and used during consolidation",
2983 )),
2984 parts_skipped: registry.register(metric!(
2985 name: "mz_persist_consolidation_parts_skipped_count",
2986 help: "count of parts that were never needed during consolidation",
2987 )),
2988 parts_wasted: registry.register(metric!(
2989 name: "mz_persist_consolidation_parts_wasted_count",
2990 help: "count of parts that were fetched but not needed during consolidation",
2991 )),
2992 wrong_sort: registry.register(metric!(
2993 name: "mz_persist_consolidation_wrong_sort_count",
2994 help: "count of runs that were sorted using the wrong ordering for the current consolidation",
2995 )),
2996 }
2997 }
2998}
2999
3000#[derive(Debug)]
3001pub struct BlobMemCache {
3002 pub(crate) size_blobs: UIntGauge,
3003 pub(crate) size_bytes: UIntGauge,
3004 pub(crate) hits_blobs: IntCounter,
3005 pub(crate) hits_bytes: IntCounter,
3006 pub(crate) evictions: IntCounter,
3007}
3008
3009impl BlobMemCache {
3010 fn new(registry: &MetricsRegistry) -> Self {
3011 BlobMemCache {
3012 size_blobs: registry.register(metric!(
3013 name: "mz_persist_blob_cache_size_blobs",
3014 help: "count of blobs in the cache",
3015 const_labels: {"cache" => "mem"},
3016 )),
3017 size_bytes: registry.register(metric!(
3018 name: "mz_persist_blob_cache_size_bytes",
3019 help: "total size of blobs in the cache",
3020 const_labels: {"cache" => "mem"},
3021 )),
3022 hits_blobs: registry.register(metric!(
3023 name: "mz_persist_blob_cache_hits_blobs",
3024 help: "count of blobs served via cache instead of s3",
3025 const_labels: {"cache" => "mem"},
3026 )),
3027 hits_bytes: registry.register(metric!(
3028 name: "mz_persist_blob_cache_hits_bytes",
3029 help: "total size of blobs served via cache instead of s3",
3030 const_labels: {"cache" => "mem"},
3031 )),
3032 evictions: registry.register(metric!(
3033 name: "mz_persist_blob_cache_evictions",
3034 help: "count of capacity-based cache evictions",
3035 const_labels: {"cache" => "mem"},
3036 )),
3037 }
3038 }
3039}
3040
3041#[derive(Debug)]
3042pub struct SemaphoreMetrics {
3043 cfg: PersistConfig,
3044 registry: MetricsRegistry,
3045 fetch: OnceCell<MetricsSemaphore>,
3046}
3047
3048impl SemaphoreMetrics {
3049 fn new(cfg: PersistConfig, registry: MetricsRegistry) -> Self {
3050 SemaphoreMetrics {
3051 cfg,
3052 registry,
3053 fetch: OnceCell::new(),
3054 }
3055 }
3056
3057 async fn fetch(&self) -> &MetricsSemaphore {
3061 if let Some(x) = self.fetch.get() {
3062 return x;
3064 }
3065 let cfg = self.cfg.clone();
3066 let registry = self.registry.clone();
3067 let init = async move {
3068 let total_permits = match cfg.announce_memory_limit {
3069 Some(mem) if cfg.is_cc_active => {
3072 info!("fetch semaphore awaiting first dyncfg values");
3075 let () = cfg.configs_synced_once().await;
3076 let total_permits = usize::cast_lossy(
3077 f64::cast_lossy(mem) * FETCH_SEMAPHORE_PERMIT_ADJUSTMENT.get(&cfg),
3078 );
3079 info!("fetch_semaphore got first dyncfg values");
3080 total_permits
3081 }
3082 Some(_) | None => Semaphore::MAX_PERMITS,
3083 };
3084 MetricsSemaphore::new(®istry, "fetch", total_permits)
3085 };
3086 self.fetch.get_or_init(|| init).await
3087 }
3088
3089 pub(crate) async fn acquire_fetch_permits(&self, encoded_size_bytes: usize) -> MetricsPermits {
3090 let requested_permits = f64::cast_lossy(encoded_size_bytes);
3093 let requested_permits = requested_permits * FETCH_SEMAPHORE_COST_ADJUSTMENT.get(&self.cfg);
3094 let requested_permits = usize::cast_lossy(requested_permits);
3095 self.fetch().await.acquire_permits(requested_permits).await
3096 }
3097}
3098
3099#[derive(Debug)]
3100pub struct MetricsSemaphore {
3101 name: &'static str,
3102 semaphore: Arc<Semaphore>,
3103 total_permits: usize,
3104 acquire_count: IntCounter,
3105 blocking_count: IntCounter,
3106 blocking_seconds: Counter,
3107 acquired_permits: IntCounter,
3108 released_permits: IntCounter,
3109 _available_permits: ComputedUIntGauge,
3110}
3111
3112impl MetricsSemaphore {
3113 pub fn new(registry: &MetricsRegistry, name: &'static str, total_permits: usize) -> Self {
3114 let total_permits = std::cmp::min(total_permits, Semaphore::MAX_PERMITS);
3115 let semaphore = Arc::new(Semaphore::new(total_permits));
3118 MetricsSemaphore {
3119 name,
3120 total_permits,
3121 acquire_count: registry.register(metric!(
3122 name: "mz_persist_semaphore_acquire_count",
3123 help: "count of acquire calls (not acquired permits count)",
3124 const_labels: {"name" => name},
3125 )),
3126 blocking_count: registry.register(metric!(
3127 name: "mz_persist_semaphore_blocking_count",
3128 help: "count of acquire calls that had to block",
3129 const_labels: {"name" => name},
3130 )),
3131 blocking_seconds: registry.register(metric!(
3132 name: "mz_persist_semaphore_blocking_seconds",
3133 help: "total time spent blocking on permit acquisition",
3134 const_labels: {"name" => name},
3135 )),
3136 acquired_permits: registry.register(metric!(
3137 name: "mz_persist_semaphore_acquired_permits",
3138 help: "total sum of acquired permits",
3139 const_labels: {"name" => name},
3140 )),
3141 released_permits: registry.register(metric!(
3142 name: "mz_persist_semaphore_released_permits",
3143 help: "total sum of released permits",
3144 const_labels: {"name" => name},
3145 )),
3146 _available_permits: registry.register_computed_gauge(
3147 metric!(
3148 name: "mz_persist_semaphore_available_permits",
3149 help: "currently available permits according to the semaphore",
3150 ),
3151 {
3152 let semaphore = Arc::clone(&semaphore);
3153 move || u64::cast_from(semaphore.available_permits())
3154 },
3155 ),
3156 semaphore,
3157 }
3158 }
3159
3160 pub async fn acquire_permits(&self, requested_permits: usize) -> MetricsPermits {
3161 let total_permits = u32::try_from(self.total_permits).unwrap_or(u32::MAX);
3164 let requested_permits = u32::try_from(requested_permits).unwrap_or(u32::MAX);
3165 let requested_permits = std::cmp::min(requested_permits, total_permits);
3166 let wrap = |_permit| {
3167 self.acquired_permits.inc_by(u64::from(requested_permits));
3168 MetricsPermits {
3169 _permit,
3170 released_metric: self.released_permits.clone(),
3171 count: requested_permits,
3172 }
3173 };
3174
3175 self.acquire_count.inc();
3177 match Arc::clone(&self.semaphore).try_acquire_many_owned(requested_permits) {
3178 Ok(x) => return wrap(x),
3179 Err(_) => {}
3180 };
3181
3182 self.blocking_count.inc();
3184 let start = Instant::now();
3185 let ret = Arc::clone(&self.semaphore)
3186 .acquire_many_owned(requested_permits)
3187 .instrument(info_span!("acquire_permits"))
3188 .await;
3189 let elapsed = start.elapsed();
3190 self.blocking_seconds.inc_by(elapsed.as_secs_f64());
3191 debug!(
3192 "acquisition of {} {} permits blocked for {:?}",
3193 self.name, requested_permits, elapsed
3194 );
3195 wrap(ret.expect("semaphore is never closed"))
3196 }
3197}
3198
3199#[derive(Debug)]
3200pub struct MetricsPermits {
3201 _permit: OwnedSemaphorePermit,
3202 released_metric: IntCounter,
3203 count: u32,
3204}
3205
3206impl Drop for MetricsPermits {
3207 fn drop(&mut self) {
3208 self.released_metric.inc_by(u64::from(self.count))
3209 }
3210}
3211
3212#[derive(Debug)]
3213pub struct ExternalOpMetrics {
3214 started: IntCounter,
3215 succeeded: IntCounter,
3216 failed: IntCounter,
3217 bytes: IntCounter,
3218 seconds: Counter,
3219 seconds_histogram: Option<Histogram>,
3220 alerts_metrics: Arc<AlertsMetrics>,
3221}
3222
3223impl ExternalOpMetrics {
3224 async fn run_op<R, F, OpFn, ErrFn>(
3225 &self,
3226 op_fn: OpFn,
3227 on_err_fn: ErrFn,
3228 ) -> Result<R, ExternalError>
3229 where
3230 F: std::future::Future<Output = Result<R, ExternalError>>,
3231 OpFn: FnOnce() -> F,
3232 ErrFn: FnOnce(&AlertsMetrics, &ExternalError),
3233 {
3234 self.started.inc();
3235 let start = Instant::now();
3236 let res = op_fn().await;
3237 let elapsed_seconds = start.elapsed().as_secs_f64();
3238 self.seconds.inc_by(elapsed_seconds);
3239 if let Some(h) = &self.seconds_histogram {
3240 h.observe(elapsed_seconds);
3241 }
3242 match res.as_ref() {
3243 Ok(_) => self.succeeded.inc(),
3244 Err(err) => {
3245 self.failed.inc();
3246 on_err_fn(&self.alerts_metrics, err);
3247 }
3248 };
3249 res
3250 }
3251
3252 fn run_stream<'a, R: 'a, S, OpFn, ErrFn>(
3253 &'a self,
3254 op_fn: OpFn,
3255 mut on_err_fn: ErrFn,
3256 ) -> impl futures::Stream<Item = Result<R, ExternalError>> + 'a
3257 where
3258 S: futures::Stream<Item = Result<R, ExternalError>> + Unpin + 'a,
3259 OpFn: FnOnce() -> S,
3260 ErrFn: FnMut(&AlertsMetrics, &ExternalError) + 'a,
3261 {
3262 self.started.inc();
3263 let start = Instant::now();
3264 let mut stream = op_fn();
3265 stream! {
3266 let mut succeeded = true;
3267 while let Some(res) = stream.next().await {
3268 if let Err(err) = res.as_ref() {
3269 on_err_fn(&self.alerts_metrics, err);
3270 succeeded = false;
3271 }
3272 yield res;
3273 }
3274 if succeeded {
3275 self.succeeded.inc()
3276 } else {
3277 self.failed.inc()
3278 }
3279 let elapsed_seconds = start.elapsed().as_secs_f64();
3280 self.seconds.inc_by(elapsed_seconds);
3281 if let Some(h) = &self.seconds_histogram {
3282 h.observe(elapsed_seconds);
3283 }
3284 }
3285 }
3286}
3287
3288#[derive(Debug)]
3289pub struct BlobMetrics {
3290 set: ExternalOpMetrics,
3291 get: ExternalOpMetrics,
3292 list_keys: ExternalOpMetrics,
3293 delete: ExternalOpMetrics,
3294 restore: ExternalOpMetrics,
3295 delete_noop: IntCounter,
3296 blob_sizes: Histogram,
3297 pub rtt_latency: Gauge,
3298}
3299
3300#[derive(Debug)]
3301pub struct MetricsBlob {
3302 blob: Arc<dyn Blob>,
3303 metrics: Arc<Metrics>,
3304}
3305
3306impl MetricsBlob {
3307 pub fn new(blob: Arc<dyn Blob>, metrics: Arc<Metrics>) -> Self {
3308 MetricsBlob { blob, metrics }
3309 }
3310
3311 fn on_err(alerts_metrics: &AlertsMetrics, _err: &ExternalError) {
3312 alerts_metrics.blob_failures.inc()
3313 }
3314}
3315
3316#[async_trait]
3317impl Blob for MetricsBlob {
3318 #[instrument(name = "blob::get", fields(shard=blob_key_shard_id(key)))]
3319 async fn get(&self, key: &str) -> Result<Option<SegmentedBytes>, ExternalError> {
3320 let res = self
3321 .metrics
3322 .blob
3323 .get
3324 .run_op(|| self.blob.get(key), Self::on_err)
3325 .await;
3326 if let Ok(Some(value)) = res.as_ref() {
3327 self.metrics
3328 .blob
3329 .get
3330 .bytes
3331 .inc_by(u64::cast_from(value.len()));
3332 }
3333 res
3334 }
3335
3336 #[instrument(name = "blob::list_keys_and_metadata", fields(shard=blob_key_shard_id(key_prefix)))]
3337 async fn list_keys_and_metadata(
3338 &self,
3339 key_prefix: &str,
3340 f: &mut (dyn FnMut(BlobMetadata) + Send + Sync),
3341 ) -> Result<(), ExternalError> {
3342 let mut byte_total = 0;
3343 let mut instrumented = |blob_metadata: BlobMetadata| {
3344 byte_total += blob_metadata.key.len();
3347 f(blob_metadata)
3348 };
3349
3350 let res = self
3351 .metrics
3352 .blob
3353 .list_keys
3354 .run_op(
3355 || {
3356 self.blob
3357 .list_keys_and_metadata(key_prefix, &mut instrumented)
3358 },
3359 Self::on_err,
3360 )
3361 .await;
3362
3363 self.metrics
3364 .blob
3365 .list_keys
3366 .bytes
3367 .inc_by(u64::cast_from(byte_total));
3368
3369 res
3370 }
3371
3372 #[instrument(name = "blob::set", fields(shard=blob_key_shard_id(key),size_bytes=value.len()))]
3373 async fn set(&self, key: &str, value: Bytes) -> Result<(), ExternalError> {
3374 let bytes = value.len();
3375 let res = self
3376 .metrics
3377 .blob
3378 .set
3379 .run_op(|| self.blob.set(key, value), Self::on_err)
3380 .await;
3381 if res.is_ok() {
3382 self.metrics.blob.set.bytes.inc_by(u64::cast_from(bytes));
3383 self.metrics.blob.blob_sizes.observe(f64::cast_lossy(bytes));
3384 }
3385 res
3386 }
3387
3388 #[instrument(name = "blob::delete", fields(shard=blob_key_shard_id(key)))]
3389 async fn delete(&self, key: &str) -> Result<Option<usize>, ExternalError> {
3390 let bytes = self
3391 .metrics
3392 .blob
3393 .delete
3394 .run_op(|| self.blob.delete(key), Self::on_err)
3395 .await?;
3396 if let Some(bytes) = bytes {
3397 self.metrics.blob.delete.bytes.inc_by(u64::cast_from(bytes));
3398 } else {
3399 self.metrics.blob.delete_noop.inc();
3400 }
3401 Ok(bytes)
3402 }
3403
3404 async fn restore(&self, key: &str) -> Result<(), ExternalError> {
3405 self.metrics
3406 .blob
3407 .restore
3408 .run_op(|| self.blob.restore(key), Self::on_err)
3409 .await
3410 }
3411}
3412
3413#[derive(Debug)]
3414pub struct ConsensusMetrics {
3415 list_keys: ExternalOpMetrics,
3416 head: ExternalOpMetrics,
3417 compare_and_set: ExternalOpMetrics,
3418 scan: ExternalOpMetrics,
3419 truncate: ExternalOpMetrics,
3420 truncated_count: IntCounter,
3421 pub rtt_latency: Gauge,
3422}
3423
3424#[derive(Debug)]
3425pub struct MetricsConsensus {
3426 consensus: Arc<dyn Consensus>,
3427 metrics: Arc<Metrics>,
3428}
3429
3430impl MetricsConsensus {
3431 pub fn new(consensus: Arc<dyn Consensus>, metrics: Arc<Metrics>) -> Self {
3432 MetricsConsensus { consensus, metrics }
3433 }
3434
3435 fn on_err(alerts_metrics: &AlertsMetrics, err: &ExternalError) {
3436 if let ExternalError::Indeterminate(_) = err {
3440 alerts_metrics.consensus_failures.inc()
3441 }
3442 }
3443}
3444
3445#[async_trait]
3446impl Consensus for MetricsConsensus {
3447 fn list_keys(&self) -> ResultStream<'_, String> {
3448 Box::pin(
3449 self.metrics
3450 .consensus
3451 .list_keys
3452 .run_stream(|| self.consensus.list_keys(), Self::on_err),
3453 )
3454 }
3455
3456 #[instrument(name = "consensus::head", fields(shard=key))]
3457 async fn head(&self, key: &str) -> Result<Option<VersionedData>, ExternalError> {
3458 let res = self
3459 .metrics
3460 .consensus
3461 .head
3462 .run_op(|| self.consensus.head(key), Self::on_err)
3463 .await;
3464 if let Ok(Some(data)) = res.as_ref() {
3465 self.metrics
3466 .consensus
3467 .head
3468 .bytes
3469 .inc_by(u64::cast_from(data.data.len()));
3470 }
3471 res
3472 }
3473
3474 #[instrument(name = "consensus::compare_and_set", fields(shard=key,size_bytes=new.data.len()))]
3475 async fn compare_and_set(
3476 &self,
3477 key: &str,
3478 new: VersionedData,
3479 ) -> Result<CaSResult, ExternalError> {
3480 let bytes = new.data.len();
3481 let res = self
3482 .metrics
3483 .consensus
3484 .compare_and_set
3485 .run_op(|| self.consensus.compare_and_set(key, new), Self::on_err)
3486 .await;
3487 match res.as_ref() {
3488 Ok(CaSResult::Committed) => self
3489 .metrics
3490 .consensus
3491 .compare_and_set
3492 .bytes
3493 .inc_by(u64::cast_from(bytes)),
3494 Ok(CaSResult::ExpectationMismatch) | Err(_) => {}
3495 }
3496 res
3497 }
3498
3499 #[instrument(name = "consensus::scan", fields(shard=key))]
3500 async fn scan(
3501 &self,
3502 key: &str,
3503 from: SeqNo,
3504 limit: usize,
3505 ) -> Result<Vec<VersionedData>, ExternalError> {
3506 let res = self
3507 .metrics
3508 .consensus
3509 .scan
3510 .run_op(|| self.consensus.scan(key, from, limit), Self::on_err)
3511 .await;
3512 if let Ok(dataz) = res.as_ref() {
3513 let bytes: usize = dataz.iter().map(|x| x.data.len()).sum();
3514 self.metrics
3515 .consensus
3516 .scan
3517 .bytes
3518 .inc_by(u64::cast_from(bytes));
3519 }
3520 res
3521 }
3522
3523 #[instrument(name = "consensus::truncate", fields(shard=key))]
3524 async fn truncate(&self, key: &str, seqno: SeqNo) -> Result<Option<usize>, ExternalError> {
3525 let metrics = &self.metrics.consensus;
3526 let deleted = metrics
3527 .truncate
3528 .run_op(|| self.consensus.truncate(key, seqno), Self::on_err)
3529 .await?;
3530 if let Some(deleted) = deleted {
3531 metrics.truncated_count.inc_by(u64::cast_from(deleted));
3532 }
3533 Ok(deleted)
3534 }
3535}
3536
3537#[derive(Debug, Clone)]
3540pub struct TaskMetrics {
3541 f64_gauges: Vec<(Gauge, fn(&tokio_metrics::TaskMetrics) -> f64)>,
3542 u64_gauges: Vec<(
3543 GenericGauge<AtomicU64>,
3544 fn(&tokio_metrics::TaskMetrics) -> u64,
3545 )>,
3546 monitor: TaskMonitor,
3547}
3548
3549impl TaskMetrics {
3550 pub fn new(name: &str) -> Self {
3551 let monitor = TaskMonitor::new();
3552 Self {
3553 f64_gauges: vec![
3554 (
3555 Gauge::make_collector(metric!(
3556 name: "mz_persist_task_total_idle_duration",
3557 help: "Seconds of time spent idling, ie. waiting for a task to be woken up.",
3558 const_labels: {"name" => name}
3559 )),
3560 |m| m.total_idle_duration.as_secs_f64(),
3561 ),
3562 (
3563 Gauge::make_collector(metric!(
3564 name: "mz_persist_task_total_scheduled_duration",
3565 help: "Seconds of time spent scheduled, ie. ready to poll but not yet polled.",
3566 const_labels: {"name" => name}
3567 )),
3568 |m| m.total_scheduled_duration.as_secs_f64(),
3569 ),
3570 ],
3571 u64_gauges: vec![
3572 (
3573 MakeCollector::make_collector(metric!(
3574 name: "mz_persist_task_total_scheduled_count",
3575 help: "The total number of task schedules. Useful for computing the average scheduled time.",
3576 const_labels: {"name" => name}
3577 )),
3578 |m| m.total_scheduled_count,
3579 ),
3580 (
3581 MakeCollector::make_collector(metric!(
3582 name: "mz_persist_task_total_idled_count",
3583 help: "The total number of task idles. Useful for computing the average idle time.",
3584 const_labels: {"name" => name}
3585 ,
3586 )),
3587 |m| m.total_idled_count,
3588 ),
3589 ],
3590 monitor,
3591 }
3592 }
3593
3594 pub fn instrument_task<F>(&self, task: F) -> tokio_metrics::Instrumented<F> {
3597 TaskMonitor::instrument(&self.monitor, task)
3598 }
3599}
3600
3601impl Collector for TaskMetrics {
3602 fn desc(&self) -> Vec<&Desc> {
3603 let mut descs = Vec::with_capacity(self.f64_gauges.len() + self.u64_gauges.len());
3604 for (g, _) in &self.f64_gauges {
3605 descs.extend(g.desc());
3606 }
3607 for (g, _) in &self.u64_gauges {
3608 descs.extend(g.desc());
3609 }
3610 descs
3611 }
3612
3613 fn collect(&self) -> Vec<MetricFamily> {
3614 let mut families = Vec::with_capacity(self.f64_gauges.len() + self.u64_gauges.len());
3615 let metrics = self.monitor.cumulative();
3616 for (g, metrics_fn) in &self.f64_gauges {
3617 g.set(metrics_fn(&metrics));
3618 families.extend(g.collect());
3619 }
3620 for (g, metrics_fn) in &self.u64_gauges {
3621 g.set(metrics_fn(&metrics));
3622 families.extend(g.collect());
3623 }
3624 families
3625 }
3626}
3627
3628#[derive(Debug)]
3629pub struct TasksMetrics {
3630 pub heartbeat_read: TaskMetrics,
3631}
3632
3633impl TasksMetrics {
3634 fn new(registry: &MetricsRegistry) -> Self {
3635 let heartbeat_read = TaskMetrics::new("heartbeat_read");
3636 registry.register_collector(heartbeat_read.clone());
3637 TasksMetrics { heartbeat_read }
3638 }
3639}
3640
3641#[derive(Debug)]
3642pub struct SchemaMetrics {
3643 pub(crate) cache_fetch_state_count: IntCounter,
3644 pub(crate) cache_schema: SchemaCacheMetrics,
3645 pub(crate) cache_migration: SchemaCacheMetrics,
3646 pub(crate) migration_count_same: IntCounter,
3647 pub(crate) migration_count_codec: IntCounter,
3648 pub(crate) migration_count_either: IntCounter,
3649 pub(crate) migration_len_legacy_codec: IntCounter,
3650 pub(crate) migration_len_either_codec: IntCounter,
3651 pub(crate) migration_len_either_arrow: IntCounter,
3652 pub(crate) migration_new_count: IntCounter,
3653 pub(crate) migration_new_seconds: Counter,
3654 pub(crate) migration_migrate_seconds: Counter,
3655}
3656
3657impl SchemaMetrics {
3658 fn new(registry: &MetricsRegistry) -> Self {
3659 let cached: IntCounterVec = registry.register(metric!(
3660 name: "mz_persist_schema_cache_cached_count",
3661 help: "count of schema cache entries served from cache",
3662 var_labels: ["op"],
3663 ));
3664 let computed: IntCounterVec = registry.register(metric!(
3665 name: "mz_persist_schema_cache_computed_count",
3666 help: "count of schema cache entries computed",
3667 var_labels: ["op"],
3668 ));
3669 let unavailable: IntCounterVec = registry.register(metric!(
3670 name: "mz_persist_schema_cache_unavailable_count",
3671 help: "count of schema cache entries unavailable at current state",
3672 var_labels: ["op"],
3673 ));
3674 let added: IntCounterVec = registry.register(metric!(
3675 name: "mz_persist_schema_cache_added_count",
3676 help: "count of schema cache entries added",
3677 var_labels: ["op"],
3678 ));
3679 let dropped: IntCounterVec = registry.register(metric!(
3680 name: "mz_persist_schema_cache_dropped_count",
3681 help: "count of schema cache entries dropped",
3682 var_labels: ["op"],
3683 ));
3684 let cache = |name| SchemaCacheMetrics {
3685 cached_count: cached.with_label_values(&[name]),
3686 computed_count: computed.with_label_values(&[name]),
3687 unavailable_count: unavailable.with_label_values(&[name]),
3688 added_count: added.with_label_values(&[name]),
3689 dropped_count: dropped.with_label_values(&[name]),
3690 };
3691 let migration_count: IntCounterVec = registry.register(metric!(
3692 name: "mz_persist_schema_migration_count",
3693 help: "count of fetch part migrations",
3694 var_labels: ["op"],
3695 ));
3696 let migration_len: IntCounterVec = registry.register(metric!(
3697 name: "mz_persist_schema_migration_len",
3698 help: "count of migrated update records",
3699 var_labels: ["op"],
3700 ));
3701 SchemaMetrics {
3702 cache_fetch_state_count: registry.register(metric!(
3703 name: "mz_persist_schema_cache_fetch_state_count",
3704 help: "count of state fetches by the schema cache",
3705 )),
3706 cache_schema: cache("schema"),
3707 cache_migration: cache("migration"),
3708 migration_count_same: migration_count.with_label_values(&["same"]),
3709 migration_count_codec: migration_count.with_label_values(&["codec"]),
3710 migration_count_either: migration_count.with_label_values(&["either"]),
3711 migration_len_legacy_codec: migration_len.with_label_values(&["legacy_codec"]),
3712 migration_len_either_codec: migration_len.with_label_values(&["either_codec"]),
3713 migration_len_either_arrow: migration_len.with_label_values(&["either_arrow"]),
3714 migration_new_count: registry.register(metric!(
3715 name: "mz_persist_schema_migration_new_count",
3716 help: "count of migrations constructed",
3717 )),
3718 migration_new_seconds: registry.register(metric!(
3719 name: "mz_persist_schema_migration_new_seconds",
3720 help: "seconds spent constructing migration logic",
3721 )),
3722 migration_migrate_seconds: registry.register(metric!(
3723 name: "mz_persist_schema_migration_migrate_seconds",
3724 help: "seconds spent applying migration logic",
3725 )),
3726 }
3727 }
3728}
3729
3730#[derive(Debug, Clone)]
3731pub struct SchemaCacheMetrics {
3732 pub(crate) cached_count: IntCounter,
3733 pub(crate) computed_count: IntCounter,
3734 pub(crate) unavailable_count: IntCounter,
3735 pub(crate) added_count: IntCounter,
3736 pub(crate) dropped_count: IntCounter,
3737}
3738
3739#[derive(Debug)]
3740pub struct InlineMetrics {
3741 pub(crate) part_commit_count: IntCounter,
3742 pub(crate) part_commit_bytes: IntCounter,
3743 pub(crate) backpressure: BatchWriteMetrics,
3744}
3745
3746impl InlineMetrics {
3747 fn new(registry: &MetricsRegistry) -> Self {
3748 InlineMetrics {
3749 part_commit_count: registry.register(metric!(
3750 name: "mz_persist_inline_part_commit_count",
3751 help: "count of inline parts committed to state",
3752 )),
3753 part_commit_bytes: registry.register(metric!(
3754 name: "mz_persist_inline_part_commit_bytes",
3755 help: "total size of of inline parts committed to state",
3756 )),
3757 backpressure: BatchWriteMetrics::new(registry, "inline_backpressure"),
3758 }
3759 }
3760}
3761
3762fn blob_key_shard_id(key: &str) -> Option<String> {
3763 let (shard_id, _) = BlobKey::parse_ids(key).ok()?;
3764 Some(shard_id.to_string())
3765}
3766
3767pub fn encode_ts_metric<T: Codec64>(ts: &Antichain<T>) -> i64 {
3769 match ts.elements().first() {
3779 Some(ts) => i64::from_le_bytes(Codec64::encode(ts)),
3780 None => i64::MAX,
3781 }
3782}
3783
3784#[cfg(test)]
3785mod tests {
3786 use std::collections::BTreeSet;
3787
3788 use mz_dyncfg::ConfigUpdates;
3789 use mz_ore::metrics::aggregation::PERCENTILES;
3790
3791 use crate::cfg::SHARD_METRICS;
3792
3793 use super::*;
3794
3795 #[mz_ore::test]
3796 fn shards_aggregate_metrics_one_pass() {
3797 let registry = MetricsRegistry::new();
3798 let configs = Arc::clone(&PersistConfig::new_for_tests().configs);
3799 let shards = ShardsMetrics::new(®istry, Arc::clone(&configs));
3800 let agg = ShardsAggregateMetrics::new(Arc::clone(&shards.shards), configs);
3801 let a = shards.shard(&ShardId::new(), "a");
3802 let b = shards.shard(&ShardId::new(), "b");
3803
3804 agg.collect();
3806 assert_eq!(agg.count.get(), 2);
3807 assert_eq!(agg.stale_count.get(), 0);
3808
3809 a.stale.store(true, Ordering::Relaxed);
3810 agg.collect();
3811 assert_eq!(agg.count.get(), 2);
3812 assert_eq!(agg.stale_count.get(), 1);
3813
3814 b.stale.store(true, Ordering::Relaxed);
3815 agg.collect();
3816 assert_eq!(agg.stale_count.get(), 2);
3817
3818 a.stale.store(false, Ordering::Relaxed);
3821 drop(b);
3822 agg.collect();
3823 assert_eq!(agg.count.get(), 1);
3824 assert_eq!(agg.stale_count.get(), 0);
3825 }
3826
3827 #[mz_ore::test]
3828 fn per_shard_families_match_registrations() {
3829 let cfg = PersistConfig::new_for_tests();
3830 set_shard_metrics(&cfg, ShardMetricsExport::Both);
3831 let registry = MetricsRegistry::new();
3832 let metrics = Metrics::new(&cfg, ®istry);
3833 let shard = metrics.shards.shard(&ShardId::new(), "s");
3834 shard.set_batch_part_versions(["v1"].into_iter());
3835 let gathered = registry.gather();
3836
3837 let registered: BTreeSet<_> = gathered
3838 .iter()
3839 .filter(|f| !f.name().ends_with("_topk"))
3840 .filter(|f| {
3841 f.get_metric()
3842 .iter()
3843 .any(|m| m.get_label().iter().any(|l| l.name() == "shard"))
3844 })
3845 .map(|f| f.name())
3846 .collect();
3847 let listed: BTreeSet<_> = PER_SHARD_FAMILIES.iter().map(|f| f.name).collect();
3848 assert_eq!(registered, listed);
3849
3850 for (name, gauge) in shard_gauges() {
3853 let registered = find(&gathered, name)
3854 .unwrap_or_else(|| panic!("{name} not gathered"))
3855 .help();
3856 assert_eq!(gauge.help, registered, "{name}");
3857 }
3858
3859 let gathered: BTreeSet<_> = gathered.iter().map(|f| f.name()).collect();
3860 for family in PER_SHARD_FAMILIES {
3861 if let ShardAggregate::Counter { total } = family.aggregate {
3862 assert!(gathered.contains(total), "{total} not gathered");
3863 }
3864 }
3865 }
3866
3867 #[mz_ore::test]
3868 fn every_shard_gauge_has_its_aggregates() {
3869 let registry = MetricsRegistry::new();
3870 let configs = Arc::clone(&PersistConfig::new_for_tests().configs);
3871 let shards = ShardsMetrics::new(®istry, Arc::clone(&configs));
3872 let agg = ShardsAggregateMetrics::new(Arc::clone(&shards.shards), configs);
3873 let declared: BTreeSet<_> = agg.desc().iter().map(|d| d.fq_name.clone()).collect();
3874 for (gauge, g) in shard_gauges() {
3875 for suffix in ["_percentile", "_topk"] {
3876 let name = format!("{gauge}{suffix}");
3877 assert!(declared.contains(&name), "{name} not declared");
3878 }
3879 let sum = format!("{gauge}_sum");
3880 assert_eq!(declared.contains(&sum), g.sum, "{sum}");
3881 }
3882 }
3883
3884 fn find<'a>(families: &'a [MetricFamily], name: &str) -> Option<&'a MetricFamily> {
3885 families.iter().find(|f| f.name() == name)
3886 }
3887
3888 fn label<'a>(m: &'a prometheus::proto::Metric, name: &str) -> &'a str {
3889 m.get_label()
3890 .iter()
3891 .find(|l| l.name() == name)
3892 .map(|l| l.value())
3893 .unwrap_or_else(|| panic!("no label {name}"))
3894 }
3895
3896 fn gauge_set(
3899 families: &[MetricFamily],
3900 name: &str,
3901 label_name: &str,
3902 ) -> BTreeSet<(String, u64)> {
3903 find(families, name).map_or_else(BTreeSet::new, |f| {
3904 f.get_metric()
3905 .iter()
3906 .map(|m| {
3907 (
3908 label(m, label_name).to_string(),
3909 u64::cast_lossy(m.get_gauge().value()),
3910 )
3911 })
3912 .collect()
3913 })
3914 }
3915
3916 fn scalar(families: &[MetricFamily], name: &str) -> f64 {
3917 let f = find(families, name).unwrap_or_else(|| panic!("{name} not gathered"));
3918 let m = &f.get_metric()[0];
3919 if f.get_field_type() == prometheus::proto::MetricType::COUNTER {
3920 m.get_counter().value()
3921 } else {
3922 m.get_gauge().value()
3923 }
3924 }
3925
3926 fn metrics_with_shards(n: u64) -> (PersistConfig, MetricsRegistry, Vec<Arc<ShardMetrics>>) {
3929 let cfg = PersistConfig::new_for_tests();
3930 let registry = MetricsRegistry::new();
3931 let metrics = Metrics::new(&cfg, ®istry);
3932 let shards = (1..=n)
3933 .map(|i| {
3934 let shard = metrics.shards.shard(&ShardId::new(), &format!("s{i}"));
3935 shard.usage_current_state_batches_bytes.set(i);
3936 shard.set_batch_part_versions(["v1"].into_iter());
3937 shard
3938 })
3939 .collect();
3940 (cfg, registry, shards)
3941 }
3942
3943 fn set_shard_metrics(cfg: &PersistConfig, mode: ShardMetricsExport) {
3944 let mut updates = ConfigUpdates::default();
3945 updates.add(&SHARD_METRICS, mode.as_str());
3946 cfg.apply_from(&updates);
3947 }
3948
3949 fn family_names(families: &[MetricFamily]) -> BTreeSet<String> {
3950 families.iter().map(|f| f.name().to_string()).collect()
3951 }
3952
3953 fn always_exported() -> BTreeSet<String> {
3956 let mut names: BTreeSet<_> = ["mz_persist_shard_count", "mz_persist_stale_shard_count"]
3957 .into_iter()
3958 .map(String::from)
3959 .collect();
3960 names.extend(PER_SHARD_FAMILIES.iter().filter_map(|f| match f.aggregate {
3961 ShardAggregate::Counter { total } => Some(total.to_string()),
3962 _ => None,
3963 }));
3964 names
3965 }
3966
3967 #[mz_ore::test]
3968 fn shard_metrics_modes_round_trip() {
3969 for mode in ShardMetricsExport::ALL {
3970 assert_eq!(ShardMetricsExport::parse(mode.as_str()), Some(mode));
3971 }
3972 assert_eq!(
3973 ShardMetricsExport::parse(SHARD_METRICS.default()),
3974 Some(ShardMetricsExport::default())
3975 );
3976 assert_eq!(ShardMetricsExport::parse("not_a_mode"), None);
3977 }
3978
3979 #[mz_ore::test]
3980 fn unknown_shard_metrics_mode_exports_the_default() {
3981 let (cfg, registry, _shards) = metrics_with_shards(3);
3982 let summary = family_names(®istry.gather());
3983 let invalid =
3984 |families: &[MetricFamily]| scalar(families, "mz_persist_shard_metrics_mode_invalid");
3985
3986 let mut updates = ConfigUpdates::default();
3987 updates.add(&SHARD_METRICS, "summry");
3988 cfg.apply_from(&updates);
3989
3990 let gathered = registry.gather();
3991 assert_eq!(family_names(&gathered), summary);
3992 assert_eq!(invalid(&gathered), 0.0);
3994 assert_eq!(invalid(®istry.gather()), 1.0);
3995
3996 set_shard_metrics(&cfg, ShardMetricsExport::Both);
3997 let gathered = registry.gather();
3998 assert!(summary.is_subset(&family_names(&gathered)));
3999 assert_eq!(invalid(&gathered), 2.0);
4000 assert_eq!(invalid(®istry.gather()), 2.0);
4001 }
4002
4003 #[mz_ore::test]
4004 fn counter_totals_are_unchanged_by_the_mode() {
4005 let (cfg, registry, shards) = metrics_with_shards(3);
4006 for (i, shard) in shards.iter().enumerate() {
4007 shard.blob_gets.inc_by(u64::cast_from(i) + 1);
4008 shard.blob_sets.inc();
4009 }
4010 let totals = |families: &[MetricFamily]| -> BTreeMap<&'static str, f64> {
4011 PER_SHARD_FAMILIES
4012 .iter()
4013 .filter_map(|f| match f.aggregate {
4014 ShardAggregate::Counter { total } => Some((total, scalar(families, total))),
4015 _ => None,
4016 })
4017 .collect()
4018 };
4019 let both = registry.gather();
4020 assert_eq!(totals(&both)["mz_persist_blob_gets"], 6.0);
4021
4022 for mode in ShardMetricsExport::ALL {
4025 set_shard_metrics(&cfg, mode);
4026 assert_eq!(totals(®istry.gather()), totals(&both), "{mode:?}");
4027 }
4028 }
4029
4030 #[mz_ore::test]
4031 fn each_shard_metrics_mode_exports_its_families() {
4032 let (cfg, registry, _shards) = metrics_with_shards(3);
4033 set_shard_metrics(&cfg, ShardMetricsExport::Both);
4034 let both = family_names(®istry.gather());
4035 let per_shard: BTreeSet<_> = PER_SHARD_FAMILIES
4036 .iter()
4037 .map(|f| f.name.to_string())
4038 .collect();
4039 let summary: BTreeSet<_> = both
4040 .iter()
4041 .filter(|n| {
4042 n.ends_with("_percentile")
4043 || n.ends_with("_sum")
4044 || n.ends_with("_topk")
4045 || *n == "mz_persist_batch_part_version_count"
4046 })
4047 .cloned()
4048 .collect();
4049 let always = always_exported();
4050 assert!(both.is_superset(&per_shard));
4051 assert!(both.is_superset(&always));
4052 assert!(!summary.is_empty());
4053 assert!(always.is_disjoint(&per_shard) && always.is_disjoint(&summary));
4054
4055 for mode in ShardMetricsExport::ALL {
4058 set_shard_metrics(&cfg, mode);
4059 let mut expected = both.clone();
4060 if !mode.per_shard() {
4061 expected.retain(|n| !per_shard.contains(n));
4062 }
4063 if !mode.summary() {
4064 expected.retain(|n| !summary.contains(n));
4065 }
4066 assert!(expected.is_superset(&always), "{mode:?}");
4067 assert_eq!(family_names(®istry.gather()), expected, "{mode:?}");
4068 }
4069 }
4070
4071 #[mz_ore::test]
4072 #[cfg_attr(miri, ignore)] fn summary_scrape_is_flat_in_shard_count() {
4074 let scrape = |n: u64, mode: ShardMetricsExport| {
4075 let (cfg, registry, _shards) = metrics_with_shards(n);
4076 set_shard_metrics(&cfg, mode);
4077 let families = registry.gather();
4078 let series: usize = families.iter().map(|f| f.get_metric().len()).sum();
4079 let body = prometheus::TextEncoder::new()
4080 .encode_to_string(&families)
4081 .expect("encodable");
4082 (series, body.len())
4083 };
4084 let (few_series, few_bytes) = scrape(10, ShardMetricsExport::Summary);
4085 let (many_series, many_bytes) = scrape(10_000, ShardMetricsExport::Summary);
4086 let (both_series, both_bytes) = scrape(10_000, ShardMetricsExport::Both);
4087 assert_eq!(few_series, many_series);
4088 assert!(
4089 both_series >= many_series + PER_SHARD_FAMILIES.len() * 10_000,
4090 "{both_series} vs {many_series}"
4091 );
4092 assert!(many_bytes < few_bytes + 4096, "{many_bytes} vs {few_bytes}");
4094 assert!(
4095 both_bytes > 100 * many_bytes,
4096 "{both_bytes} vs {many_bytes}"
4097 );
4098 }
4099
4100 #[mz_ore::test]
4101 fn shard_info_lists_live_shards_when_enabled() {
4102 let (cfg, registry, mut shards) = metrics_with_shards(3);
4103 let info =
4104 |registry: &MetricsRegistry| gauge_set(®istry.gather(), SHARD_INFO_NAME, "name");
4105 let mut updates = ConfigUpdates::default();
4106 updates.add(&SHARD_INFO_METRIC, true);
4107 cfg.apply_from(&updates);
4108 for mode in ShardMetricsExport::ALL {
4110 set_shard_metrics(&cfg, mode);
4111 let expected = ["s1", "s2", "s3"].map(|n| (n.to_string(), 1)).into();
4112 assert_eq!(info(®istry), expected, "{mode:?}");
4113 }
4114
4115 shards.pop();
4116 assert_eq!(info(®istry).len(), 2);
4117 }
4118
4119 fn set_enable_regex(cfg: &PersistConfig, regex: &str) {
4120 let mut updates = ConfigUpdates::default();
4121 updates.add(&PER_SHARD_METRICS_ENABLE_REGEX, regex);
4122 cfg.apply_from(&updates);
4123 }
4124
4125 fn per_shard_series(
4127 families: &[MetricFamily],
4128 ) -> BTreeMap<(String, Vec<(String, String)>), prometheus::proto::Metric> {
4129 families
4130 .iter()
4131 .filter(|f| PER_SHARD_FAMILIES.iter().any(|p| p.name == f.name()))
4132 .flat_map(|f| {
4133 f.get_metric().iter().map(|m| {
4134 let labels = m
4135 .get_label()
4136 .iter()
4137 .map(|l| (l.name().to_string(), l.value().to_string()))
4138 .collect();
4139 ((f.name().to_string(), labels), m.clone())
4140 })
4141 })
4142 .collect()
4143 }
4144
4145 fn exported_shard_names(families: &[MetricFamily]) -> BTreeSet<String> {
4147 per_shard_series(families)
4148 .into_keys()
4149 .map(|(_, labels)| {
4150 labels
4151 .into_iter()
4152 .find(|(k, _)| k == "name")
4153 .expect("per-shard series have a name label")
4154 .1
4155 })
4156 .collect()
4157 }
4158
4159 #[mz_ore::test]
4160 fn enable_regex_keeps_matching_shards_unchanged() {
4161 let (cfg, registry, shards) = metrics_with_shards(5);
4162 set_shard_metrics(&cfg, ShardMetricsExport::Both);
4163 let both = registry.gather();
4164 set_shard_metrics(&cfg, ShardMetricsExport::Summary);
4165 let summary = registry.gather();
4166 assert!(exported_shard_names(&summary).is_empty());
4167
4168 let shard_id = shards[2].shard_id.to_string();
4171 set_enable_regex(&cfg, &shard_id);
4172 let enabled = registry.gather();
4173 let expected: BTreeMap<_, _> = per_shard_series(&both)
4174 .into_iter()
4175 .filter(|((_, labels), _)| labels.contains(&("shard".into(), shard_id.clone())))
4176 .collect();
4177 assert_eq!(expected.len(), PER_SHARD_FAMILIES.len());
4178 assert_eq!(per_shard_series(&enabled), expected);
4179 assert_eq!(family_names(&enabled), {
4180 let mut names = family_names(&summary);
4181 names.extend(PER_SHARD_FAMILIES.iter().map(|f| f.name.to_string()));
4182 names
4183 });
4184
4185 set_enable_regex(&cfg, "^s2$");
4187 assert_eq!(
4188 exported_shard_names(®istry.gather()),
4189 BTreeSet::from(["s2".to_string()])
4190 );
4191 set_enable_regex(&cfg, "^s(1|4)$");
4192 assert_eq!(
4193 exported_shard_names(®istry.gather()),
4194 BTreeSet::from(["s1".to_string(), "s4".to_string()])
4195 );
4196
4197 set_enable_regex(&cfg, "");
4199 assert_eq!(family_names(®istry.gather()), family_names(&summary));
4200
4201 set_enable_regex(&cfg, "^s2$");
4203 set_shard_metrics(&cfg, ShardMetricsExport::None);
4204 let gathered = registry.gather();
4205 assert_eq!(
4206 exported_shard_names(&gathered),
4207 BTreeSet::from(["s2".to_string()])
4208 );
4209 assert!(gathered.iter().all(|f| !f.name().ends_with("_percentile")));
4210 set_shard_metrics(&cfg, ShardMetricsExport::Both);
4211 assert_eq!(exported_shard_names(®istry.gather()).len(), 5);
4212 }
4213
4214 #[mz_ore::test]
4215 fn unanchored_enable_regex_over_matches() {
4216 let cfg = PersistConfig::new_for_tests();
4217 let registry = MetricsRegistry::new();
4218 let metrics = Metrics::new(&cfg, ®istry);
4219 let shard = |name: &str| metrics.shards.shard(&ShardId::new(), name);
4220 let _named: Vec<_> = (1..=10).map(|i| shard(&format!("src{i}"))).collect();
4223 set_shard_metrics(&cfg, ShardMetricsExport::Summary);
4224
4225 set_enable_regex(&cfg, "src1");
4228 assert_eq!(
4229 exported_shard_names(®istry.gather()),
4230 BTreeSet::from(["src1".to_string(), "src10".to_string()])
4231 );
4232
4233 let target = shard("by-id");
4238 let fragment = target.shard_id.to_string()[3..9].to_string();
4239 let decoy = shard(&format!("t-{fragment}-t"));
4240 assert!(!decoy.shard_id.to_string().contains(&fragment));
4241 set_enable_regex(&cfg, &fragment);
4242 let kept = exported_shard_names(®istry.gather());
4243 assert!(kept.contains(&target.name), "{kept:?}");
4244 assert!(kept.contains(&decoy.name), "{kept:?}");
4245 }
4246
4247 #[mz_ore::test]
4248 fn invalid_enable_regex_fails_closed() {
4249 let (cfg, registry, _shards) = metrics_with_shards(3);
4250 set_shard_metrics(&cfg, ShardMetricsExport::Summary);
4251 let summary = family_names(®istry.gather());
4252 let invalid = |families: &[MetricFamily]| {
4253 scalar(families, "mz_persist_per_shard_metrics_regex_invalid")
4254 };
4255
4256 set_enable_regex(&cfg, "(");
4257 let gathered = registry.gather();
4258 assert_eq!(family_names(&gathered), summary);
4259 assert_eq!(invalid(&gathered), 0.0);
4261 assert_eq!(invalid(®istry.gather()), 1.0);
4262
4263 set_enable_regex(&cfg, "^s1$");
4264 let gathered = registry.gather();
4265 assert_eq!(
4266 exported_shard_names(&gathered),
4267 BTreeSet::from(["s1".to_string()])
4268 );
4269 assert_eq!(invalid(&gathered), 2.0);
4270 assert_eq!(invalid(®istry.gather()), 2.0);
4271 }
4272
4273 fn gauge_count(families: &[MetricFamily], name: &str) -> usize {
4274 find(families, name).map_or(0, |f| f.get_metric().len())
4275 }
4276
4277 fn percentile(families: &[MetricFamily], name: &str, percentile: &str) -> u64 {
4279 gauge_set(families, name, "percentile")
4280 .into_iter()
4281 .find_map(|(p, v)| (p == percentile).then_some(v))
4282 .unwrap_or_else(|| panic!("no {percentile} in {name}"))
4283 }
4284
4285 #[mz_ore::test]
4286 fn shard_aggregates_fold_live_shards() {
4287 let registry = MetricsRegistry::new();
4288 let metrics = Metrics::new(&PersistConfig::new_for_tests(), ®istry);
4289 let mut shards: Vec<_> = (1..=12u64)
4290 .map(|i| {
4291 let shard = metrics.shards.shard(&ShardId::new(), &format!("s{i}"));
4292 shard.usage_current_state_batches_bytes.set(i * 100);
4293 shard
4294 })
4295 .collect();
4296 shards[0].set_batch_part_versions(["v1", "v1", "v2"].into_iter());
4297 shards[1].set_batch_part_versions(["v1"].into_iter());
4298
4299 let gathered = registry.gather();
4300 assert_eq!(scalar(&gathered, "mz_persist_shard_count"), 12.0);
4301 let percentile_families = gathered
4302 .iter()
4303 .filter(|f| {
4304 f.name().starts_with("mz_persist_shard_") && f.name().ends_with("_percentile")
4305 })
4306 .count();
4307 assert_eq!(percentile_families, shard_gauges().count());
4308 for (gauge, _) in shard_gauges() {
4309 let name = format!("{gauge}_percentile");
4310 assert_eq!(gauge_count(&gathered, &name), PERCENTILES.len(), "{name}");
4311 }
4312 let percentiles = "mz_persist_shard_usage_current_state_batches_bytes_percentile";
4313 assert_eq!(percentile(&gathered, percentiles, "min"), 100);
4314 assert_eq!(percentile(&gathered, percentiles, "p50"), 600);
4315 assert_eq!(percentile(&gathered, percentiles, "max"), 1200);
4316 let top_k = "mz_persist_shard_usage_current_state_batches_bytes_topk";
4317 let expected: BTreeSet<_> = shards[2..]
4318 .iter()
4319 .map(|s| {
4320 (
4321 s.shard_id.to_string(),
4322 s.usage_current_state_batches_bytes.get(),
4323 )
4324 })
4325 .collect();
4326 assert_eq!(gauge_set(&gathered, top_k, "shard"), expected);
4327 assert_eq!(
4328 gauge_set(&gathered, "mz_persist_batch_part_version_count", "version"),
4329 BTreeSet::from([("v1".to_string(), 3), ("v2".to_string(), 1)])
4330 );
4331
4332 let largest = shards.pop().expect("non-empty");
4335 let largest_id = largest.shard_id.to_string();
4336 drop(largest);
4337 shards[0].set_batch_part_versions(["v1"].into_iter());
4338 let gathered = registry.gather();
4339 assert_eq!(scalar(&gathered, "mz_persist_shard_count"), 11.0);
4340 assert_eq!(percentile(&gathered, percentiles, "max"), 1100);
4341 let top_k_shards = gauge_set(&gathered, top_k, "shard");
4342 assert_eq!(top_k_shards.len(), SHARD_TOP_K);
4343 assert!(top_k_shards.iter().all(|(shard, _)| *shard != largest_id));
4344 assert_eq!(
4345 gauge_set(&gathered, "mz_persist_batch_part_version_count", "version"),
4346 BTreeSet::from([("v1".to_string(), 2)])
4347 );
4348 }
4349
4350 #[mz_ore::test]
4351 fn process_counters_survive_shard_drop() {
4352 let cfg = PersistConfig::new_for_tests();
4353 set_shard_metrics(&cfg, ShardMetricsExport::Both);
4354 let registry = MetricsRegistry::new();
4355 let metrics = Metrics::new(&cfg, ®istry);
4356 let a = metrics.shards.shard(&ShardId::new(), "a");
4357 let b = metrics.shards.shard(&ShardId::new(), "b");
4358 a.blob_gets.inc_by(5);
4359 b.blob_gets.inc();
4360 a.encoded_diff_size.inc_by(100);
4361
4362 let gathered = registry.gather();
4363 assert_eq!(scalar(&gathered, "mz_persist_blob_gets"), 6.0);
4364 assert_eq!(gauge_count(&gathered, "mz_persist_shard_blob_gets"), 2);
4365
4366 drop(a);
4367 let gathered = registry.gather();
4368 assert_eq!(scalar(&gathered, "mz_persist_blob_gets"), 6.0);
4369 assert_eq!(scalar(&gathered, "mz_persist_diff_size_bytes"), 100.0);
4370 assert_eq!(gauge_count(&gathered, "mz_persist_shard_blob_gets"), 1);
4371 }
4372}