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