Skip to main content

mz_persist_client/internal/
metrics.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10//! Prometheus monitoring metrics.
11
12use 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_ore::bytes::SegmentedBytes;
24use mz_ore::cast::{CastFrom, CastLossy};
25use mz_ore::instrument;
26use mz_ore::metric;
27use mz_ore::metrics::{
28    ComputedGauge, ComputedUIntGauge, Counter, DeleteOnDropCounter, DeleteOnDropGauge, IntCounter,
29    MakeCollector, MetricsRegistry, UIntGauge, UIntGaugeVec, raw,
30};
31use mz_ore::stats::histogram_seconds_buckets;
32use mz_persist::location::{
33    Blob, BlobMetadata, CaSResult, Consensus, ExternalError, ResultStream, SeqNo, VersionedData,
34};
35use mz_persist::metrics::{BlobHedgeMetrics, ColumnarMetrics, S3BlobMetrics};
36use mz_persist::retry::RetryStream;
37use mz_persist_types::Codec64;
38use mz_postgres_client::metrics::PostgresClientMetrics;
39use prometheus::core::{AtomicI64, AtomicU64, Collector, Desc, GenericGauge};
40use prometheus::proto::MetricFamily;
41use prometheus::{CounterVec, Gauge, GaugeVec, Histogram, HistogramVec, IntCounterVec};
42use timely::progress::Antichain;
43use tokio_metrics::TaskMonitor;
44use tracing::{Instrument, debug, info, info_span};
45
46use crate::fetch::{FETCH_SEMAPHORE_COST_ADJUSTMENT, FETCH_SEMAPHORE_PERMIT_ADJUSTMENT};
47use crate::internal::paths::BlobKey;
48use crate::{PersistConfig, ShardId};
49
50/// Prometheus monitoring metrics.
51///
52/// Intentionally not Clone because we expect this to be passed around in an
53/// Arc.
54pub struct Metrics {
55    _vecs: MetricsVecs,
56    _uptime: ComputedGauge,
57
58    /// Metrics for [Blob] usage.
59    pub blob: BlobMetrics,
60    /// Metrics for [Consensus] usage.
61    pub consensus: ConsensusMetrics,
62    /// Metrics of command evaluation.
63    pub cmds: CmdsMetrics,
64    /// Metrics for each retry loop.
65    pub retries: RetriesMetrics,
66    /// Metrics for batches written directly on behalf of a user (BatchBuilder
67    /// or one of the sugar methods that use it).
68    pub user: BatchWriteMetrics,
69    /// Metrics for reading batch parts
70    pub read: BatchPartReadMetrics,
71    /// Metrics for compaction.
72    pub compaction: CompactionMetrics,
73    /// Metrics for garbage collection.
74    pub gc: GcMetrics,
75    /// Metrics for leasing and automatic lease expiry.
76    pub lease: LeaseMetrics,
77    /// Metrics for various encodings and decodings.
78    pub codecs: CodecsMetrics,
79    /// Metrics for (incremental) state updates and fetches.
80    pub state: StateMetrics,
81    /// Metrics for various per-shard measurements.
82    pub shards: ShardsMetrics,
83    /// Metrics for auditing persist usage
84    pub audit: UsageAuditMetrics,
85    /// Metrics for locking.
86    pub locks: LocksMetrics,
87    /// Metrics for StateWatch.
88    pub watch: WatchMetrics,
89    /// Metrics for PubSub client.
90    pub pubsub_client: PubSubClientMetrics,
91    /// Metrics for mfp/filter pushdown.
92    pub pushdown: PushdownMetrics,
93    /// Metrics for consolidation.
94    pub consolidation: ConsolidationMetrics,
95    /// Metrics for blob caching.
96    pub blob_cache_mem: BlobMemCache,
97    /// Metrics for tokio tasks.
98    pub tasks: TasksMetrics,
99    /// Metrics for columnar data encoding and decoding.
100    pub columnar: ColumnarMetrics,
101    /// Metrics for schemas and the schema registry.
102    pub schema: SchemaMetrics,
103    /// Metrics for inline writes.
104    pub inline: InlineMetrics,
105    /// Semaphore to limit memory/disk use by fetches.
106    pub(crate) semaphore: SemaphoreMetrics,
107
108    /// Metrics for the persist sink.
109    pub sink: SinkMetrics,
110    /// Metrics for the persist_source backpressure operator.
111    pub backpressure: BackpressureMetrics,
112
113    /// Metrics for S3-backed blob implementation
114    pub s3_blob: S3BlobMetrics,
115    /// Metrics for hedged blob gets
116    pub blob_hedge: BlobHedgeMetrics,
117    /// Metrics for Postgres-backed consensus implementation
118    pub postgres_consensus: PostgresClientMetrics,
119
120    #[allow(dead_code)]
121    pub(crate) registry: MetricsRegistry,
122}
123
124impl std::fmt::Debug for Metrics {
125    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126        f.debug_struct("Metrics").finish_non_exhaustive()
127    }
128}
129
130impl Metrics {
131    /// Returns a new [Metrics] instance connected to the given registry.
132    pub fn new(cfg: &PersistConfig, registry: &MetricsRegistry) -> Self {
133        let vecs = MetricsVecs::new(registry);
134        let start = Instant::now();
135        let uptime = registry.register_computed_gauge(
136            metric!(
137                name: "mz_persist_metadata_seconds",
138                help: "server uptime, labels are build metadata",
139                const_labels: {
140                    "version" => cfg.build_version,
141                    "build_type" => if cfg!(release) { "release" } else { "debug" }
142                },
143            ),
144            move || start.elapsed().as_secs_f64(),
145        );
146        let s3_blob = S3BlobMetrics::new(registry);
147        let columnar = ColumnarMetrics::new(registry);
148        Metrics {
149            blob: vecs.blob_metrics(),
150            consensus: vecs.consensus_metrics(),
151            cmds: vecs.cmds_metrics(registry),
152            retries: vecs.retries_metrics(),
153            codecs: vecs.codecs_metrics(),
154            user: BatchWriteMetrics::new(registry, "user"),
155            read: vecs.batch_part_read_metrics(),
156            compaction: CompactionMetrics::new(registry),
157            gc: GcMetrics::new(registry),
158            lease: LeaseMetrics::new(registry),
159            state: StateMetrics::new(registry),
160            shards: ShardsMetrics::new(registry),
161            audit: UsageAuditMetrics::new(registry),
162            locks: vecs.locks_metrics(),
163            watch: WatchMetrics::new(registry),
164            pubsub_client: PubSubClientMetrics::new(registry),
165            pushdown: PushdownMetrics::new(registry),
166            consolidation: ConsolidationMetrics::new(registry),
167            blob_cache_mem: BlobMemCache::new(registry),
168            tasks: TasksMetrics::new(registry),
169            columnar,
170            schema: SchemaMetrics::new(registry),
171            inline: InlineMetrics::new(registry),
172            semaphore: SemaphoreMetrics::new(cfg.clone(), registry.clone()),
173            sink: SinkMetrics::new(registry),
174            backpressure: BackpressureMetrics::new(registry),
175            s3_blob,
176            blob_hedge: BlobHedgeMetrics::new(registry),
177            postgres_consensus: PostgresClientMetrics::new(registry, "mz_persist"),
178            _vecs: vecs,
179            _uptime: uptime,
180            registry: registry.clone(),
181        }
182    }
183
184    /// Returns the current lifetime write amplification reflected in these
185    /// metrics.
186    ///
187    /// Only exposed for tests, persistcli, and benchmarks.
188    pub fn write_amplification(&self) -> f64 {
189        // This intentionally uses "bytes" for total and "goodbytes" for user so
190        // that the overhead of our blob format is included.
191        let total_written = self.blob.set.bytes.get();
192        let user_written = self.user.goodbytes.get();
193        #[allow(clippy::as_conversions)]
194        {
195            total_written as f64 / user_written as f64
196        }
197    }
198}
199
200#[derive(Debug)]
201struct MetricsVecs {
202    cmd_started: IntCounterVec,
203    cmd_cas_mismatch: IntCounterVec,
204    cmd_succeeded: IntCounterVec,
205    cmd_failed: IntCounterVec,
206    cmd_seconds: CounterVec,
207
208    external_op_started: IntCounterVec,
209    external_op_succeeded: IntCounterVec,
210    external_op_failed: IntCounterVec,
211    external_op_bytes: IntCounterVec,
212    external_op_seconds: CounterVec,
213    external_consensus_truncated_count: IntCounter,
214    external_blob_delete_noop_count: IntCounter,
215    external_blob_sizes: Histogram,
216    external_rtt_latency: GaugeVec,
217    external_op_latency: HistogramVec,
218
219    retry_started: IntCounterVec,
220    retry_finished: IntCounterVec,
221    retry_retries: IntCounterVec,
222    retry_sleep_seconds: CounterVec,
223
224    encode_count: IntCounterVec,
225    encode_seconds: CounterVec,
226    decode_count: IntCounterVec,
227    decode_seconds: CounterVec,
228
229    read_part_bytes: IntCounterVec,
230    read_part_goodbytes: IntCounterVec,
231    read_part_count: IntCounterVec,
232    read_part_seconds: CounterVec,
233    read_ts_rewrite: IntCounterVec,
234
235    lock_acquire_count: IntCounterVec,
236    lock_blocking_acquire_count: IntCounterVec,
237    lock_blocking_seconds: CounterVec,
238
239    /// A minimal set of metrics imported into honeycomb for alerting.
240    alerts_metrics: Arc<AlertsMetrics>,
241}
242
243impl MetricsVecs {
244    fn new(registry: &MetricsRegistry) -> Self {
245        MetricsVecs {
246            cmd_started: registry.register(metric!(
247                name: "mz_persist_cmd_started_count",
248                help: "count of commands started",
249                var_labels: ["cmd"],
250            )),
251            cmd_cas_mismatch: registry.register(metric!(
252                name: "mz_persist_cmd_cas_mismatch_count",
253                help: "count of command retries from CaS mismatch",
254                var_labels: ["cmd"],
255            )),
256            cmd_succeeded: registry.register(metric!(
257                name: "mz_persist_cmd_succeeded_count",
258                help: "count of commands succeeded",
259                var_labels: ["cmd"],
260            )),
261            cmd_failed: registry.register(metric!(
262                name: "mz_persist_cmd_failed_count",
263                help: "count of commands failed",
264                var_labels: ["cmd"],
265            )),
266            cmd_seconds: registry.register(metric!(
267                name: "mz_persist_cmd_seconds",
268                help: "time spent applying commands",
269                var_labels: ["cmd"],
270            )),
271
272            external_op_started: registry.register(metric!(
273                name: "mz_persist_external_started_count",
274                help: "count of external service calls started",
275                var_labels: ["op"],
276            )),
277            external_op_succeeded: registry.register(metric!(
278                name: "mz_persist_external_succeeded_count",
279                help: "count of external service calls succeeded",
280                var_labels: ["op"],
281            )),
282            external_op_failed: registry.register(metric!(
283                name: "mz_persist_external_failed_count",
284                help: "count of external service calls failed",
285                var_labels: ["op"],
286            )),
287            external_op_bytes: registry.register(metric!(
288                name: "mz_persist_external_bytes_count",
289                help: "total size represented by external service calls",
290                var_labels: ["op"],
291            )),
292            external_op_seconds: registry.register(metric!(
293                name: "mz_persist_external_seconds",
294                help: "time spent in external service calls",
295                var_labels: ["op"],
296            )),
297            external_consensus_truncated_count: registry.register(metric!(
298                name: "mz_persist_external_consensus_truncated_count",
299                help: "count of versions deleted by consensus truncate calls",
300            )),
301            external_blob_delete_noop_count: registry.register(metric!(
302                name: "mz_persist_external_blob_delete_noop_count",
303                help: "count of blob delete calls that deleted a non-existent key",
304            )),
305            external_blob_sizes: registry.register(metric!(
306                name: "mz_persist_external_blob_sizes",
307                help: "histogram of blob sizes at put time",
308                buckets: mz_ore::stats::HISTOGRAM_BYTE_BUCKETS.to_vec(),
309            )),
310            external_rtt_latency: registry.register(metric!(
311                name: "mz_persist_external_rtt_latency",
312                help: "roundtrip-time to external service as seen by this process",
313                var_labels: ["external"],
314            )),
315            external_op_latency: registry.register(metric!(
316                name: "mz_persist_external_op_latency",
317                help: "rountrip latency observed by individual performance-critical operations",
318                var_labels: ["op"],
319                // NB: If we end up overrunning metrics quotas, we could plausibly cut this
320                // down by switching to a factor of 4 between buckets (vs. the standard 2).
321                buckets: histogram_seconds_buckets(0.000_500, 32.0),
322            )),
323
324            retry_started: registry.register(metric!(
325                name: "mz_persist_retry_started_count",
326                help: "count of retry loops started",
327                var_labels: ["op"],
328            )),
329            retry_finished: registry.register(metric!(
330                name: "mz_persist_retry_finished_count",
331                help: "count of retry loops finished",
332                var_labels: ["op"],
333            )),
334            retry_retries: registry.register(metric!(
335                name: "mz_persist_retry_retries_count",
336                help: "count of total attempts by retry loops",
337                var_labels: ["op"],
338            )),
339            retry_sleep_seconds: registry.register(metric!(
340                name: "mz_persist_retry_sleep_seconds",
341                help: "time spent in retry loop backoff",
342                var_labels: ["op"],
343            )),
344
345            encode_count: registry.register(metric!(
346                name: "mz_persist_encode_count",
347                help: "count of op encodes",
348                var_labels: ["op"],
349            )),
350            encode_seconds: registry.register(metric!(
351                name: "mz_persist_encode_seconds",
352                help: "time spent in op encodes",
353                var_labels: ["op"],
354            )),
355            decode_count: registry.register(metric!(
356                name: "mz_persist_decode_count",
357                help: "count of op decodes",
358                var_labels: ["op"],
359            )),
360            decode_seconds: registry.register(metric!(
361                name: "mz_persist_decode_seconds",
362                help: "time spent in op decodes",
363                var_labels: ["op"],
364            )),
365
366            read_part_bytes: registry.register(metric!(
367                name: "mz_persist_read_batch_part_bytes",
368                help: "total encoded size of batch parts read",
369                var_labels: ["op"],
370            )),
371            read_part_goodbytes: registry.register(metric!(
372                name: "mz_persist_read_batch_part_goodbytes",
373                help: "total logical size of batch parts read",
374                var_labels: ["op"],
375            )),
376            read_part_count: registry.register(metric!(
377                name: "mz_persist_read_batch_part_count",
378                help: "count of batch parts read",
379                var_labels: ["op"],
380            )),
381            read_part_seconds: registry.register(metric!(
382                name: "mz_persist_read_batch_part_seconds",
383                help: "time spent reading batch parts",
384                var_labels: ["op"],
385            )),
386            read_ts_rewrite: registry.register(metric!(
387                name: "mz_persist_read_ts_rewite",
388                help: "count of updates read with rewritten ts",
389                var_labels: ["op"],
390            )),
391
392            lock_acquire_count: registry.register(metric!(
393                name: "mz_persist_lock_acquire_count",
394                help: "count of locks acquired",
395                var_labels: ["op"],
396            )),
397            lock_blocking_acquire_count: registry.register(metric!(
398                name: "mz_persist_lock_blocking_acquire_count",
399                help: "count of locks acquired that required blocking",
400                var_labels: ["op"],
401            )),
402            lock_blocking_seconds: registry.register(metric!(
403                name: "mz_persist_lock_blocking_seconds",
404                help: "time spent blocked for a lock",
405                var_labels: ["op"],
406            )),
407
408            alerts_metrics: Arc::new(AlertsMetrics::new(registry)),
409        }
410    }
411
412    fn cmds_metrics(&self, registry: &MetricsRegistry) -> CmdsMetrics {
413        CmdsMetrics {
414            init_state: self.cmd_metrics("init_state"),
415            add_rollup: self.cmd_metrics("add_rollup"),
416            remove_rollups: self.cmd_metrics("remove_rollups"),
417            upgrade_version: self.cmd_metrics("upgrade_version"),
418            register: self.cmd_metrics("register"),
419            compare_and_append: self.cmd_metrics("compare_and_append"),
420            compare_and_append_noop:             registry.register(metric!(
421                name: "mz_persist_cmd_compare_and_append_noop",
422                help: "count of compare_and_append retries that were discoverd to have already committed",
423            )),
424            compare_and_downgrade_since: self.cmd_metrics("compare_and_downgrade_since"),
425            downgrade_since: self.cmd_metrics("downgrade_since"),
426            expire_reader: self.cmd_metrics("expire_reader"),
427            expire_writer: self.cmd_metrics("expire_writer"),
428            merge_res: self.cmd_metrics("merge_res"),
429            become_tombstone: self.cmd_metrics("become_tombstone"),
430            compare_and_evolve_schema: self.cmd_metrics("compare_and_evolve_schema"),
431            spine_exert: self.cmd_metrics("spine_exert"),
432            fetch_upper_count: registry.register(metric!(
433                name: "mz_persist_cmd_fetch_upper_count",
434                help: "count of fetch_upper calls",
435            ))
436        }
437    }
438
439    fn cmd_metrics(&self, cmd: &str) -> CmdMetrics {
440        CmdMetrics {
441            name: cmd.to_owned(),
442            started: self.cmd_started.with_label_values(&[cmd]),
443            succeeded: self.cmd_succeeded.with_label_values(&[cmd]),
444            cas_mismatch: self.cmd_cas_mismatch.with_label_values(&[cmd]),
445            failed: self.cmd_failed.with_label_values(&[cmd]),
446            seconds: self.cmd_seconds.with_label_values(&[cmd]),
447        }
448    }
449
450    fn retries_metrics(&self) -> RetriesMetrics {
451        RetriesMetrics {
452            determinate: RetryDeterminate {
453                apply_unbatched_cmd_cas: self.retry_metrics("apply_unbatched_cmd::cas"),
454            },
455            external: RetryExternal {
456                batch_delete: Arc::new(self.retry_metrics("batch::delete")),
457                batch_set: self.retry_metrics("batch::set"),
458                blob_open: self.retry_metrics("blob::open"),
459                compaction_noop_delete: Arc::new(self.retry_metrics("compaction_noop::delete")),
460                consensus_open: self.retry_metrics("consensus::open"),
461                fetch_batch_get: self.retry_metrics("fetch_batch::get"),
462                fetch_state_scan: self.retry_metrics("fetch_state::scan"),
463                gc_truncate: self.retry_metrics("gc::truncate"),
464                maybe_init_cas: self.retry_metrics("maybe_init::cas"),
465                rollup_delete: self.retry_metrics("rollup::delete"),
466                rollup_get: self.retry_metrics("rollup::get"),
467                rollup_set: self.retry_metrics("rollup::set"),
468                hollow_run_get: self.retry_metrics("hollow_run::get"),
469                hollow_run_set: self.retry_metrics("hollow_run::set"),
470                storage_usage_shard_size: self.retry_metrics("storage_usage::shard_size"),
471            },
472            compare_and_append_idempotent: self.retry_metrics("compare_and_append_idempotent"),
473            fetch_latest_state: self.retry_metrics("fetch_latest_state"),
474            fetch_live_states: self.retry_metrics("fetch_live_states"),
475            idempotent_cmd: self.retry_metrics("idempotent_cmd"),
476            next_listen_batch: self.retry_metrics("next_listen_batch"),
477            snapshot: self.retry_metrics("snapshot"),
478        }
479    }
480
481    fn retry_metrics(&self, name: &str) -> RetryMetrics {
482        RetryMetrics {
483            name: name.to_owned(),
484            started: self.retry_started.with_label_values(&[name]),
485            finished: self.retry_finished.with_label_values(&[name]),
486            retries: self.retry_retries.with_label_values(&[name]),
487            sleep_seconds: self.retry_sleep_seconds.with_label_values(&[name]),
488        }
489    }
490
491    fn codecs_metrics(&self) -> CodecsMetrics {
492        CodecsMetrics {
493            state: self.codec_metrics("state"),
494            state_diff: self.codec_metrics("state_diff"),
495            batch: self.codec_metrics("batch"),
496            key: self.codec_metrics("key"),
497            val: self.codec_metrics("val"),
498        }
499    }
500
501    fn codec_metrics(&self, op: &str) -> CodecMetrics {
502        CodecMetrics {
503            encode_count: self.encode_count.with_label_values(&[op]),
504            encode_seconds: self.encode_seconds.with_label_values(&[op]),
505            decode_count: self.decode_count.with_label_values(&[op]),
506            decode_seconds: self.decode_seconds.with_label_values(&[op]),
507        }
508    }
509
510    fn blob_metrics(&self) -> BlobMetrics {
511        BlobMetrics {
512            set: self.external_op_metrics("blob_set", true),
513            get: self.external_op_metrics("blob_get", true),
514            list_keys: self.external_op_metrics("blob_list_keys", false),
515            delete: self.external_op_metrics("blob_delete", false),
516            restore: self.external_op_metrics("restore", false),
517            delete_noop: self.external_blob_delete_noop_count.clone(),
518            blob_sizes: self.external_blob_sizes.clone(),
519            rtt_latency: self.external_rtt_latency.with_label_values(&["blob"]),
520        }
521    }
522
523    fn consensus_metrics(&self) -> ConsensusMetrics {
524        ConsensusMetrics {
525            list_keys: self.external_op_metrics("consensus_list_keys", false),
526            head: self.external_op_metrics("consensus_head", false),
527            compare_and_set: self.external_op_metrics("consensus_cas", true),
528            scan: self.external_op_metrics("consensus_scan", false),
529            truncate: self.external_op_metrics("consensus_truncate", false),
530            truncated_count: self.external_consensus_truncated_count.clone(),
531            rtt_latency: self.external_rtt_latency.with_label_values(&["consensus"]),
532        }
533    }
534
535    fn external_op_metrics(&self, op: &str, latency_histogram: bool) -> ExternalOpMetrics {
536        ExternalOpMetrics {
537            started: self.external_op_started.with_label_values(&[op]),
538            succeeded: self.external_op_succeeded.with_label_values(&[op]),
539            failed: self.external_op_failed.with_label_values(&[op]),
540            bytes: self.external_op_bytes.with_label_values(&[op]),
541            seconds: self.external_op_seconds.with_label_values(&[op]),
542            seconds_histogram: if latency_histogram {
543                Some(self.external_op_latency.with_label_values(&[op]))
544            } else {
545                None
546            },
547            alerts_metrics: Arc::clone(&self.alerts_metrics),
548        }
549    }
550
551    fn batch_part_read_metrics(&self) -> BatchPartReadMetrics {
552        BatchPartReadMetrics {
553            listen: self.read_metrics("listen"),
554            snapshot: self.read_metrics("snapshot"),
555            batch_fetcher: self.read_metrics("batch_fetcher"),
556            compaction: self.read_metrics("compaction"),
557            unindexed: self.read_metrics("unindexed"),
558        }
559    }
560
561    fn read_metrics(&self, op: &str) -> ReadMetrics {
562        ReadMetrics {
563            part_bytes: self.read_part_bytes.with_label_values(&[op]),
564            part_goodbytes: self.read_part_goodbytes.with_label_values(&[op]),
565            part_count: self.read_part_count.with_label_values(&[op]),
566            seconds: self.read_part_seconds.with_label_values(&[op]),
567            ts_rewrite: self.read_ts_rewrite.with_label_values(&[op]),
568        }
569    }
570
571    fn locks_metrics(&self) -> LocksMetrics {
572        LocksMetrics {
573            applier_read_cacheable: self.lock_metrics("applier_read_cacheable"),
574            applier_read_noncacheable: self.lock_metrics("applier_read_noncacheable"),
575            applier_write: self.lock_metrics("applier_write"),
576            watch: self.lock_metrics("watch"),
577        }
578    }
579
580    fn lock_metrics(&self, op: &str) -> LockMetrics {
581        LockMetrics {
582            acquire_count: self.lock_acquire_count.with_label_values(&[op]),
583            blocking_acquire_count: self.lock_blocking_acquire_count.with_label_values(&[op]),
584            blocking_seconds: self.lock_blocking_seconds.with_label_values(&[op]),
585        }
586    }
587}
588
589#[derive(Debug)]
590pub struct CmdMetrics {
591    pub(crate) name: String,
592    pub(crate) started: IntCounter,
593    pub(crate) cas_mismatch: IntCounter,
594    pub(crate) succeeded: IntCounter,
595    pub(crate) failed: IntCounter,
596    pub(crate) seconds: Counter,
597}
598
599impl CmdMetrics {
600    pub async fn run_cmd<R, E, F, CmdFn>(
601        &self,
602        shard_metrics: &ShardMetrics,
603        cmd_fn: CmdFn,
604    ) -> Result<R, E>
605    where
606        F: std::future::Future<Output = Result<R, E>>,
607        CmdFn: FnOnce() -> F,
608    {
609        self.started.inc();
610        let start = Instant::now();
611        let res = cmd_fn().await;
612        self.seconds.inc_by(start.elapsed().as_secs_f64());
613        match res.as_ref() {
614            Ok(_) => {
615                self.succeeded.inc();
616                shard_metrics.cmd_succeeded.inc();
617            }
618            Err(_) => self.failed.inc(),
619        };
620        res
621    }
622}
623
624#[derive(Debug)]
625pub struct CmdsMetrics {
626    pub(crate) init_state: CmdMetrics,
627    pub(crate) add_rollup: CmdMetrics,
628    pub(crate) remove_rollups: CmdMetrics,
629    pub(crate) upgrade_version: CmdMetrics,
630    pub(crate) register: CmdMetrics,
631    pub(crate) compare_and_append: CmdMetrics,
632    pub(crate) compare_and_append_noop: IntCounter,
633    pub(crate) compare_and_downgrade_since: CmdMetrics,
634    pub(crate) downgrade_since: CmdMetrics,
635    pub(crate) expire_reader: CmdMetrics,
636    pub(crate) expire_writer: CmdMetrics,
637    pub(crate) merge_res: CmdMetrics,
638    pub(crate) become_tombstone: CmdMetrics,
639    pub(crate) compare_and_evolve_schema: CmdMetrics,
640    pub(crate) spine_exert: CmdMetrics,
641    pub(crate) fetch_upper_count: IntCounter,
642}
643
644#[derive(Debug)]
645pub struct RetryMetrics {
646    pub(crate) name: String,
647    pub(crate) started: IntCounter,
648    pub(crate) finished: IntCounter,
649    pub(crate) retries: IntCounter,
650    pub(crate) sleep_seconds: Counter,
651}
652
653impl RetryMetrics {
654    pub(crate) fn stream(&self, retry: RetryStream) -> MetricsRetryStream {
655        MetricsRetryStream::new(retry, self)
656    }
657}
658
659#[derive(Debug)]
660pub struct RetryDeterminate {
661    pub(crate) apply_unbatched_cmd_cas: RetryMetrics,
662}
663
664#[derive(Debug)]
665pub struct RetryExternal {
666    pub(crate) batch_delete: Arc<RetryMetrics>,
667    pub(crate) batch_set: RetryMetrics,
668    pub(crate) blob_open: RetryMetrics,
669    pub(crate) compaction_noop_delete: Arc<RetryMetrics>,
670    pub(crate) consensus_open: RetryMetrics,
671    pub(crate) fetch_batch_get: RetryMetrics,
672    pub(crate) fetch_state_scan: RetryMetrics,
673    pub(crate) gc_truncate: RetryMetrics,
674    pub(crate) maybe_init_cas: RetryMetrics,
675    pub(crate) rollup_delete: RetryMetrics,
676    pub(crate) rollup_get: RetryMetrics,
677    pub(crate) rollup_set: RetryMetrics,
678    pub(crate) hollow_run_get: RetryMetrics,
679    pub(crate) hollow_run_set: RetryMetrics,
680    pub(crate) storage_usage_shard_size: RetryMetrics,
681}
682
683#[derive(Debug)]
684pub struct RetriesMetrics {
685    pub(crate) determinate: RetryDeterminate,
686    pub(crate) external: RetryExternal,
687
688    pub(crate) compare_and_append_idempotent: RetryMetrics,
689    pub(crate) fetch_latest_state: RetryMetrics,
690    pub(crate) fetch_live_states: RetryMetrics,
691    pub(crate) idempotent_cmd: RetryMetrics,
692    pub(crate) next_listen_batch: RetryMetrics,
693    pub(crate) snapshot: RetryMetrics,
694}
695
696#[derive(Debug)]
697pub struct BatchPartReadMetrics {
698    pub(crate) listen: ReadMetrics,
699    pub(crate) snapshot: ReadMetrics,
700    pub(crate) batch_fetcher: ReadMetrics,
701    pub(crate) compaction: ReadMetrics,
702    pub(crate) unindexed: ReadMetrics,
703}
704
705#[derive(Debug, Clone)]
706pub struct ReadMetrics {
707    pub(crate) part_bytes: IntCounter,
708    pub(crate) part_goodbytes: IntCounter,
709    pub(crate) part_count: IntCounter,
710    pub(crate) seconds: Counter,
711    pub(crate) ts_rewrite: IntCounter,
712}
713
714// This one is Clone in contrast to the others because it has to get moved into
715// a task.
716#[derive(Debug, Clone)]
717pub struct BatchWriteMetrics {
718    pub(crate) bytes: IntCounter,
719    pub(crate) goodbytes: IntCounter,
720    pub(crate) seconds: Counter,
721    pub(crate) write_stalls: IntCounter,
722    pub(crate) key_lower_too_big: IntCounter,
723
724    pub(crate) unordered: IntCounter,
725    pub(crate) codec_order: IntCounter,
726    pub(crate) structured_order: IntCounter,
727    _order_counts: IntCounterVec,
728
729    pub(crate) step_stats: Counter,
730    pub(crate) step_part_writing: Counter,
731    pub(crate) step_inline: Counter,
732}
733
734impl BatchWriteMetrics {
735    fn new(registry: &MetricsRegistry, name: &str) -> Self {
736        let order_counts: IntCounterVec = registry.register(metric!(
737                name: format!("mz_persist_{}_write_batch_order", name),
738                help: "count of batches by the data ordering",
739                var_labels: ["order"],
740        ));
741        let unordered = order_counts.with_label_values(&["unordered"]);
742        let codec_order = order_counts.with_label_values(&["codec"]);
743        let structured_order = order_counts.with_label_values(&["structured"]);
744
745        BatchWriteMetrics {
746            bytes: registry.register(metric!(
747                name: format!("mz_persist_{}_bytes", name),
748                help: format!("total encoded size of {} batches written", name),
749            )),
750            goodbytes: registry.register(metric!(
751                name: format!("mz_persist_{}_goodbytes", name),
752                help: format!("total logical size of {} batches written", name),
753            )),
754            seconds: registry.register(metric!(
755                name: format!("mz_persist_{}_write_batch_part_seconds", name),
756                help: format!("time spent writing {} batches", name),
757            )),
758            write_stalls: registry.register(metric!(
759                name: format!("mz_persist_{}_write_stall_count", name),
760                help: format!(
761                    "count of {} writes stalling to await max outstanding reqs",
762                    name
763                ),
764            )),
765            key_lower_too_big: registry.register(metric!(
766                name: format!("mz_persist_{}_key_lower_too_big", name),
767                help: format!(
768                    "count of {} writes that were unable to write a key lower, because the size threshold was too low",
769                    name
770                ),
771            )),
772            unordered,
773            codec_order,
774            structured_order,
775            _order_counts: order_counts,
776            step_stats: registry.register(metric!(
777                name: format!("mz_persist_{}_step_stats", name),
778                help: format!("time spent computing {} update stats", name),
779            )),
780            step_part_writing: registry.register(metric!(
781                name: format!("mz_persist_{}_step_part_writing", name),
782                help: format!("blocking time spent writing parts for {} updates", name),
783            )),
784            step_inline: registry.register(metric!(
785                name: format!("mz_persist_{}_step_inline", name),
786                help: format!("time spent encoding {} inline batches", name)
787            )),
788        }
789    }
790}
791
792#[derive(Debug)]
793pub struct CompactionMetrics {
794    pub(crate) requested: IntCounter,
795    pub(crate) dropped: IntCounter,
796    pub(crate) disabled: IntCounter,
797    pub(crate) skipped: IntCounter,
798    pub(crate) started: IntCounter,
799    pub(crate) applied: IntCounter,
800    pub(crate) timed_out: IntCounter,
801    pub(crate) failed: IntCounter,
802    pub(crate) noop: IntCounter,
803    pub(crate) seconds: Counter,
804    pub(crate) concurrency_waits: IntCounter,
805    pub(crate) queued_seconds: Counter,
806    pub(crate) memory_violations: IntCounter,
807    pub(crate) runs_compacted: IntCounter,
808    pub(crate) chunks_compacted: IntCounter,
809    pub(crate) not_all_prefetched: IntCounter,
810    pub(crate) parts_prefetched: IntCounter,
811    pub(crate) parts_waited: IntCounter,
812    pub(crate) fast_path_eligible: IntCounter,
813    pub(crate) admin_count: IntCounter,
814
815    pub(crate) applied_exact_match: IntCounter,
816    pub(crate) applied_subset_match: IntCounter,
817    pub(crate) not_applied_too_many_updates: IntCounter,
818
819    pub(crate) batch: BatchWriteMetrics,
820    pub(crate) steps: CompactionStepTimings,
821    pub(crate) schema_selection: CompactionSchemaSelection,
822
823    pub(crate) _steps_vec: CounterVec,
824}
825
826impl CompactionMetrics {
827    fn new(registry: &MetricsRegistry) -> Self {
828        let step_timings: CounterVec = registry.register(metric!(
829                name: "mz_persist_compaction_step_seconds",
830                help: "time spent on individual steps of compaction",
831                var_labels: ["step"],
832        ));
833        let schema_selection: CounterVec = registry.register(metric!(
834            name: "mz_persist_compaction_schema_selection",
835            help: "count of compactions and how we did schema selection",
836            var_labels: ["selection"],
837        ));
838
839        CompactionMetrics {
840            requested: registry.register(metric!(
841                name: "mz_persist_compaction_requested",
842                help: "count of total compaction requests",
843            )),
844            dropped: registry.register(metric!(
845                name: "mz_persist_compaction_dropped",
846                help: "count of total compaction requests dropped due to a full queue",
847            )),
848            disabled: registry.register(metric!(
849                name: "mz_persist_compaction_disabled",
850                help: "count of total compaction requests dropped because compaction was disabled",
851            )),
852            skipped: registry.register(metric!(
853                name: "mz_persist_compaction_skipped",
854                help: "count of compactions skipped due to heuristics",
855            )),
856            started: registry.register(metric!(
857                name: "mz_persist_compaction_started",
858                help: "count of compactions started",
859            )),
860            failed: registry.register(metric!(
861                name: "mz_persist_compaction_failed",
862                help: "count of compactions failed",
863            )),
864            applied: registry.register(metric!(
865                name: "mz_persist_compaction_applied",
866                help: "count of compactions applied to state",
867            )),
868            timed_out: registry.register(metric!(
869                name: "mz_persist_compaction_timed_out",
870                help: "count of compactions that timed out",
871            )),
872            noop: registry.register(metric!(
873                name: "mz_persist_compaction_noop",
874                help: "count of compactions discarded (obsolete)",
875            )),
876            seconds: registry.register(metric!(
877                name: "mz_persist_compaction_seconds",
878                help: "time spent in compaction",
879            )),
880            concurrency_waits: registry.register(metric!(
881                name: "mz_persist_compaction_concurrency_waits",
882                help: "count of compaction requests that ever blocked due to concurrency limit",
883            )),
884            queued_seconds: registry.register(metric!(
885                name: "mz_persist_compaction_queued_seconds",
886                help: "time that compaction requests spent queued",
887            )),
888            memory_violations: registry.register(metric!(
889                name: "mz_persist_compaction_memory_violations",
890                help: "count of compaction memory requirement violations",
891            )),
892            runs_compacted: registry.register(metric!(
893                name: "mz_persist_compaction_runs_compacted",
894                help: "count of runs compacted",
895            )),
896            chunks_compacted: registry.register(metric!(
897                name: "mz_persist_compaction_chunks_compacted",
898                help: "count of run chunks compacted",
899            )),
900            not_all_prefetched: registry.register(metric!(
901                name: "mz_persist_compaction_not_all_prefetched",
902                help: "count of compactions where not all inputs were prefetched",
903            )),
904            parts_prefetched: registry.register(metric!(
905                name: "mz_persist_compaction_parts_prefetched",
906                help: "count of compaction parts completely prefetched by the time they're needed",
907            )),
908            parts_waited: registry.register(metric!(
909                name: "mz_persist_compaction_parts_waited",
910                help: "count of compaction parts that had to be waited on",
911            )),
912            fast_path_eligible: registry.register(metric!(
913                name: "mz_persist_compaction_fast_path_eligible",
914                help: "count of compaction requests that could have used the fast-path optimization",
915            )),
916            admin_count: registry.register(metric!(
917                name: "mz_persist_compaction_admin_count",
918                help: "count of compaction requests that were performed by admin tooling",
919            )),
920            applied_exact_match: registry.register(metric!(
921                name: "mz_persist_compaction_applied_exact_match",
922                help: "count of merge results that exactly replaced a SpineBatch",
923            )),
924            applied_subset_match: registry.register(metric!(
925                name: "mz_persist_compaction_applied_subset_match",
926                help: "count of merge results that replaced a subset of a SpineBatch",
927            )),
928            not_applied_too_many_updates: registry.register(metric!(
929                name: "mz_persist_compaction_not_applied_too_many_updates",
930                help: "count of merge results that did not apply due to too many updates",
931            )),
932            batch: BatchWriteMetrics::new(registry, "compaction"),
933            steps: CompactionStepTimings::new(step_timings.clone()),
934            schema_selection: CompactionSchemaSelection::new(schema_selection.clone()),
935            _steps_vec: step_timings,
936        }
937    }
938}
939
940#[derive(Debug)]
941pub struct CompactionStepTimings {
942    pub(crate) part_fetch_seconds: Counter,
943    pub(crate) heap_population_seconds: Counter,
944}
945
946impl CompactionStepTimings {
947    fn new(step_timings: CounterVec) -> CompactionStepTimings {
948        CompactionStepTimings {
949            part_fetch_seconds: step_timings.with_label_values(&["part_fetch"]),
950            heap_population_seconds: step_timings.with_label_values(&["heap_population"]),
951        }
952    }
953}
954
955#[derive(Debug)]
956pub struct CompactionSchemaSelection {
957    pub(crate) recent_schema: Counter,
958    pub(crate) no_schema: Counter,
959}
960
961impl CompactionSchemaSelection {
962    fn new(schema_selection: CounterVec) -> CompactionSchemaSelection {
963        CompactionSchemaSelection {
964            recent_schema: schema_selection.with_label_values(&["recent"]),
965            no_schema: schema_selection.with_label_values(&["none"]),
966        }
967    }
968}
969
970#[derive(Debug)]
971pub struct GcMetrics {
972    pub(crate) noop: IntCounter,
973    pub(crate) started: IntCounter,
974    pub(crate) finished: IntCounter,
975    pub(crate) merged: IntCounter,
976    pub(crate) seconds: Counter,
977    pub(crate) steps: GcStepTimings,
978}
979
980#[derive(Debug)]
981pub struct GcStepTimings {
982    pub(crate) find_removable_rollups: Counter,
983    pub(crate) fetch_seconds: Counter,
984    pub(crate) find_deletable_blobs_seconds: Counter,
985    pub(crate) delete_rollup_seconds: Counter,
986    pub(crate) delete_batch_part_seconds: Counter,
987    pub(crate) truncate_diff_seconds: Counter,
988    pub(crate) remove_rollups_from_state: Counter,
989    pub(crate) post_gc_calculations_seconds: Counter,
990}
991
992impl GcStepTimings {
993    fn new(step_timings: CounterVec) -> Self {
994        Self {
995            find_removable_rollups: step_timings.with_label_values(&["find_removable_rollups"]),
996            fetch_seconds: step_timings.with_label_values(&["fetch"]),
997            find_deletable_blobs_seconds: step_timings.with_label_values(&["find_deletable_blobs"]),
998            delete_rollup_seconds: step_timings.with_label_values(&["delete_rollup"]),
999            delete_batch_part_seconds: step_timings.with_label_values(&["delete_batch_part"]),
1000            truncate_diff_seconds: step_timings.with_label_values(&["truncate_diff"]),
1001            remove_rollups_from_state: step_timings
1002                .with_label_values(&["remove_rollups_from_state"]),
1003            post_gc_calculations_seconds: step_timings.with_label_values(&["post_gc_calculations"]),
1004        }
1005    }
1006}
1007
1008impl GcMetrics {
1009    fn new(registry: &MetricsRegistry) -> Self {
1010        let step_timings: CounterVec = registry.register(metric!(
1011                name: "mz_persist_gc_step_seconds",
1012                help: "time spent on individual steps of gc",
1013                var_labels: ["step"],
1014        ));
1015        GcMetrics {
1016            noop: registry.register(metric!(
1017                name: "mz_persist_gc_noop",
1018                help: "count of garbage collections skipped because they were already done",
1019            )),
1020            started: registry.register(metric!(
1021                name: "mz_persist_gc_started",
1022                help: "count of garbage collections started",
1023            )),
1024            finished: registry.register(metric!(
1025                name: "mz_persist_gc_finished",
1026                help: "count of garbage collections finished",
1027            )),
1028            merged: registry.register(metric!(
1029                name: "mz_persist_gc_merged_reqs",
1030                help: "count of garbage collection requests merged",
1031            )),
1032            seconds: registry.register(metric!(
1033                name: "mz_persist_gc_seconds",
1034                help: "time spent in garbage collections",
1035            )),
1036            steps: GcStepTimings::new(step_timings),
1037        }
1038    }
1039}
1040
1041#[derive(Debug)]
1042pub struct LeaseMetrics {
1043    pub(crate) timeout_read: IntCounter,
1044    pub(crate) dropped_part: IntCounter,
1045}
1046
1047impl LeaseMetrics {
1048    fn new(registry: &MetricsRegistry) -> Self {
1049        LeaseMetrics {
1050            timeout_read: registry.register(metric!(
1051                name: "mz_persist_lease_timeout_read",
1052                help: "count of readers whose lease timed out",
1053            )),
1054            dropped_part: registry.register(metric!(
1055                name: "mz_persist_lease_dropped_part",
1056                help: "count of LeasedBatchParts that were dropped without being politely returned",
1057            )),
1058        }
1059    }
1060}
1061
1062struct IncOnDrop(IntCounter);
1063
1064impl Drop for IncOnDrop {
1065    fn drop(&mut self) {
1066        self.0.inc()
1067    }
1068}
1069
1070pub struct MetricsRetryStream {
1071    retry: RetryStream,
1072    pub(crate) retries: IntCounter,
1073    sleep_seconds: Counter,
1074    _finished: IncOnDrop,
1075}
1076
1077impl MetricsRetryStream {
1078    pub fn new(retry: RetryStream, metrics: &RetryMetrics) -> Self {
1079        metrics.started.inc();
1080        MetricsRetryStream {
1081            retry,
1082            retries: metrics.retries.clone(),
1083            sleep_seconds: metrics.sleep_seconds.clone(),
1084            _finished: IncOnDrop(metrics.finished.clone()),
1085        }
1086    }
1087
1088    /// How many times [Self::sleep] has been called.
1089    pub fn attempt(&self) -> usize {
1090        self.retry.attempt()
1091    }
1092
1093    /// The next sleep (without jitter for easy printing in logs).
1094    pub fn next_sleep(&self) -> Duration {
1095        self.retry.next_sleep()
1096    }
1097
1098    /// Executes the next sleep in the series.
1099    ///
1100    /// This isn't cancel-safe, so it consumes and returns self, to prevent
1101    /// accidental mis-use.
1102    pub async fn sleep(self) -> Self {
1103        self.retries.inc();
1104        self.sleep_seconds
1105            .inc_by(self.retry.next_sleep().as_secs_f64());
1106        let retry = self.retry.sleep().await;
1107        MetricsRetryStream {
1108            retry,
1109            retries: self.retries,
1110            sleep_seconds: self.sleep_seconds,
1111            _finished: self._finished,
1112        }
1113    }
1114}
1115
1116#[derive(Debug)]
1117pub struct CodecsMetrics {
1118    pub(crate) state: CodecMetrics,
1119    pub(crate) state_diff: CodecMetrics,
1120    pub(crate) batch: CodecMetrics,
1121    pub(crate) key: CodecMetrics,
1122    pub(crate) val: CodecMetrics,
1123    // Intentionally not adding time and diff because they're just
1124    // `{to,from}_le_bytes`.
1125}
1126
1127#[derive(Debug)]
1128pub struct CodecMetrics {
1129    pub(crate) encode_count: IntCounter,
1130    pub(crate) encode_seconds: Counter,
1131    pub(crate) decode_count: IntCounter,
1132    pub(crate) decode_seconds: Counter,
1133}
1134
1135impl CodecMetrics {
1136    pub(crate) fn encode<R, F: FnOnce() -> R>(&self, f: F) -> R {
1137        let now = Instant::now();
1138        let r = f();
1139        self.encode_count.inc();
1140        self.encode_seconds.inc_by(now.elapsed().as_secs_f64());
1141        r
1142    }
1143
1144    pub(crate) fn decode<R, F: FnOnce() -> R>(&self, f: F) -> R {
1145        let now = Instant::now();
1146        let r = f();
1147        self.decode_count.inc();
1148        self.decode_seconds.inc_by(now.elapsed().as_secs_f64());
1149        r
1150    }
1151}
1152
1153#[derive(Debug)]
1154pub struct StateMetrics {
1155    pub(crate) apply_spine_fast_path: IntCounter,
1156    pub(crate) apply_spine_slow_path: IntCounter,
1157    pub(crate) apply_spine_slow_path_lenient: IntCounter,
1158    pub(crate) apply_spine_slow_path_lenient_adjustment: IntCounter,
1159    pub(crate) apply_spine_slow_path_with_reconstruction: IntCounter,
1160    pub(crate) apply_spine_flattened: IntCounter,
1161    pub(crate) update_state_noop_path: IntCounter,
1162    pub(crate) update_state_empty_path: IntCounter,
1163    pub(crate) update_state_fast_path: IntCounter,
1164    pub(crate) update_state_slow_path: IntCounter,
1165    pub(crate) rollup_at_seqno_migration: IntCounter,
1166    pub(crate) fetch_recent_live_diffs_fast_path: IntCounter,
1167    pub(crate) fetch_recent_live_diffs_slow_path: IntCounter,
1168    pub(crate) writer_added: IntCounter,
1169    pub(crate) writer_removed: IntCounter,
1170    pub(crate) force_apply_hostname: IntCounter,
1171    pub(crate) rollup_write_success: IntCounter,
1172    pub(crate) rollup_write_noop_latest: IntCounter,
1173    pub(crate) rollup_write_noop_truncated: IntCounter,
1174}
1175
1176impl StateMetrics {
1177    pub(crate) fn new(registry: &MetricsRegistry) -> Self {
1178        let rollup_write_noop: IntCounterVec = registry.register(metric!(
1179                name: "mz_persist_state_rollup_write_noop",
1180                help: "count of no-op rollup writes",
1181                var_labels: ["reason"],
1182        ));
1183
1184        StateMetrics {
1185            apply_spine_fast_path: registry.register(metric!(
1186                name: "mz_persist_state_apply_spine_fast_path",
1187                help: "count of spine diff applications that hit the fast path",
1188            )),
1189            apply_spine_slow_path: registry.register(metric!(
1190                name: "mz_persist_state_apply_spine_slow_path",
1191                help: "count of spine diff applications that hit the slow path",
1192            )),
1193            apply_spine_slow_path_lenient: registry.register(metric!(
1194                name: "mz_persist_state_apply_spine_slow_path_lenient",
1195                help: "count of spine diff applications that hit the lenient compaction apply path",
1196            )),
1197            apply_spine_slow_path_lenient_adjustment: registry.register(metric!(
1198                name: "mz_persist_state_apply_spine_slow_path_lenient_adjustment",
1199                help: "count of adjustments made by the lenient compaction apply path",
1200            )),
1201            apply_spine_slow_path_with_reconstruction: registry.register(metric!(
1202                name: "mz_persist_state_apply_spine_slow_path_with_reconstruction",
1203                help: "count of spine diff applications that hit the slow path with extra spine reconstruction step",
1204            )),
1205            apply_spine_flattened: registry.register(metric!(
1206                name: "mz_persist_state_apply_spine_flattened",
1207                help: "count of spine diff applications that flatten the trace",
1208            )),
1209            update_state_noop_path: registry.register(metric!(
1210                name: "mz_persist_state_update_state_noop_path",
1211                help: "count of state update applications that no-oped due to shared state",
1212            )),
1213            update_state_empty_path: registry.register(metric!(
1214                name: "mz_persist_state_update_state_empty_path",
1215                help: "count of state update applications that found no new updates",
1216            )),
1217            update_state_fast_path: registry.register(metric!(
1218                name: "mz_persist_state_update_state_fast_path",
1219                help: "count of state update applications that hit the fast path",
1220            )),
1221            update_state_slow_path: registry.register(metric!(
1222                name: "mz_persist_state_update_state_slow_path",
1223                help: "count of state update applications that hit the slow path",
1224            )),
1225            rollup_at_seqno_migration: registry.register(metric!(
1226                name: "mz_persist_state_rollup_at_seqno_migration",
1227                help: "count of fetch_rollup_at_seqno calls that only worked because of the migration",
1228            )),
1229            fetch_recent_live_diffs_fast_path: registry.register(metric!(
1230                name: "mz_persist_state_fetch_recent_live_diffs_fast_path",
1231                help: "count of fetch_recent_live_diffs that hit the fast path",
1232            )),
1233            fetch_recent_live_diffs_slow_path: registry.register(metric!(
1234                name: "mz_persist_state_fetch_recent_live_diffs_slow_path",
1235                help: "count of fetch_recent_live_diffs that hit the slow path",
1236            )),
1237            writer_added: registry.register(metric!(
1238                name: "mz_persist_state_writer_added",
1239                help: "count of writers added to the state",
1240            )),
1241            writer_removed: registry.register(metric!(
1242                name: "mz_persist_state_writer_removed",
1243                help: "count of writers removed from the state",
1244            )),
1245            force_apply_hostname: registry.register(metric!(
1246                name: "mz_persist_state_force_applied_hostname",
1247                help: "count of when hostname diffs needed to be force applied",
1248            )),
1249            rollup_write_success: registry.register(metric!(
1250                name: "mz_persist_state_rollup_write_success",
1251                help: "count of rollups written successful (may not be linked in to state)",
1252            )),
1253            rollup_write_noop_latest: rollup_write_noop.with_label_values(&["latest"]),
1254            rollup_write_noop_truncated: rollup_write_noop.with_label_values(&["truncated"]),
1255        }
1256    }
1257}
1258
1259#[derive(Debug)]
1260pub struct ShardsMetrics {
1261    // Unlike all the other metrics in here, ShardsMetrics intentionally uses
1262    // the DeleteOnDrop wrappers. A process might stop using a shard (drop all
1263    // handles to it) but e.g. the set of commands never changes.
1264    //
1265    // The process-level shard aggregates (`mz_persist_shard_count`,
1266    // `mz_persist_stale_shard_count`) are not fields here: they live in a
1267    // `ShardsAggregateMetrics` collector that the registry owns, sharing this
1268    // struct's `shards` map so one scrape walk feeds both.
1269    encoded_rollup_size: mz_ore::metrics::UIntGaugeVec,
1270    encoded_diff_size: mz_ore::metrics::IntCounterVec,
1271    hollow_batch_count: mz_ore::metrics::UIntGaugeVec,
1272    spine_batch_count: mz_ore::metrics::UIntGaugeVec,
1273    batch_part_count: mz_ore::metrics::UIntGaugeVec,
1274    batch_part_version_count: mz_ore::metrics::UIntGaugeVec,
1275    update_count: mz_ore::metrics::UIntGaugeVec,
1276    rollup_count: mz_ore::metrics::UIntGaugeVec,
1277    largest_batch_size: mz_ore::metrics::UIntGaugeVec,
1278    seqnos_held: mz_ore::metrics::UIntGaugeVec,
1279    seqnos_since_last_rollup: mz_ore::metrics::UIntGaugeVec,
1280    gc_seqno_held_parts: mz_ore::metrics::UIntGaugeVec,
1281    gc_live_diffs: mz_ore::metrics::UIntGaugeVec,
1282    gc_finished: mz_ore::metrics::IntCounterVec,
1283    compaction_applied: mz_ore::metrics::IntCounterVec,
1284    cmd_succeeded: mz_ore::metrics::IntCounterVec,
1285    usage_current_state_batches_bytes: mz_ore::metrics::UIntGaugeVec,
1286    usage_current_state_rollups_bytes: mz_ore::metrics::UIntGaugeVec,
1287    usage_referenced_not_current_state_bytes: mz_ore::metrics::UIntGaugeVec,
1288    usage_not_leaked_not_referenced_bytes: mz_ore::metrics::UIntGaugeVec,
1289    usage_leaked_bytes: mz_ore::metrics::UIntGaugeVec,
1290    blob_gets: mz_ore::metrics::IntCounterVec,
1291    blob_sets: mz_ore::metrics::IntCounterVec,
1292    unconsolidated_snapshot: mz_ore::metrics::IntCounterVec,
1293    inline_part_count: UIntGaugeVec,
1294    compact_batches: UIntGaugeVec,
1295    compacting_batches: UIntGaugeVec,
1296    noncompact_batches: UIntGaugeVec,
1297    // We hand out `Arc<ShardMetrics>` to read and write handles, but store it
1298    // here as `Weak`. This allows us to discover if it's no longer in use and
1299    // so we can remove it from the map.
1300    shards: Arc<Mutex<BTreeMap<ShardId, Weak<ShardMetrics>>>>,
1301}
1302
1303impl ShardsMetrics {
1304    fn new(registry: &MetricsRegistry) -> Self {
1305        let shards = Arc::new(Mutex::new(BTreeMap::new()));
1306        registry.register_collector(ShardsAggregateMetrics::new(Arc::clone(&shards)));
1307        ShardsMetrics {
1308            encoded_rollup_size: registry.register(metric!(
1309                name: "mz_persist_shard_rollup_size_bytes",
1310                help: "total encoded rollup size by shard",
1311                var_labels: ["shard", "name"],
1312            )),
1313            encoded_diff_size: registry.register(metric!(
1314                name: "mz_persist_shard_diff_size_bytes",
1315                help: "total encoded diff size by shard",
1316                var_labels: ["shard", "name"],
1317            )),
1318            hollow_batch_count: registry.register(metric!(
1319                name: "mz_persist_shard_hollow_batch_count",
1320                help: "count of hollow batches by shard",
1321                var_labels: ["shard", "name"],
1322            )),
1323            spine_batch_count: registry.register(metric!(
1324                name: "mz_persist_shard_spine_batch_count",
1325                help: "count of spine batches by shard",
1326                var_labels: ["shard", "name"],
1327            )),
1328            batch_part_count: registry.register(metric!(
1329                name: "mz_persist_shard_batch_part_count",
1330                help: "count of batch parts by shard",
1331                var_labels: ["shard", "name"],
1332            )),
1333            batch_part_version_count: registry.register(metric!(
1334                name: "mz_persist_shard_batch_part_version_count",
1335                help: "count of batch parts by shard and version",
1336                var_labels: ["shard", "name", "version"],
1337            )),
1338            update_count: registry.register(metric!(
1339                name: "mz_persist_shard_update_count",
1340                help: "count of updates by shard",
1341                var_labels: ["shard", "name"],
1342            )),
1343            rollup_count: registry.register(metric!(
1344                name: "mz_persist_shard_rollup_count",
1345                help: "count of rollups by shard",
1346                var_labels: ["shard", "name"],
1347            )),
1348            largest_batch_size: registry.register(metric!(
1349                name: "mz_persist_shard_largest_batch_size",
1350                help: "largest encoded batch size by shard",
1351                var_labels: ["shard", "name"],
1352            )),
1353            seqnos_held: registry.register(metric!(
1354                name: "mz_persist_shard_seqnos_held",
1355                help: "maximum count of gc-ineligible states by shard",
1356                var_labels: ["shard", "name"],
1357            )),
1358            seqnos_since_last_rollup: registry.register(metric!(
1359                name: "mz_persist_shard_seqnos_since_last_rollup",
1360                help: "count of seqnos since last rollup",
1361                var_labels: ["shard", "name"],
1362            )),
1363            gc_seqno_held_parts: registry.register(metric!(
1364                name: "mz_persist_shard_gc_seqno_held_parts",
1365                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",
1366                var_labels: ["shard", "name"],
1367            )),
1368            gc_live_diffs: registry.register(metric!(
1369                name: "mz_persist_shard_gc_live_diffs",
1370                help: "the number of diffs (or, alternatively, the number of seqnos) present in consensus state at GC time",
1371                var_labels: ["shard", "name"],
1372            )),
1373            gc_finished: registry.register(metric!(
1374                name: "mz_persist_shard_gc_finished",
1375                help: "count of garbage collections finished by shard",
1376                var_labels: ["shard", "name"],
1377            )),
1378            compaction_applied: registry.register(metric!(
1379                name: "mz_persist_shard_compaction_applied",
1380                help: "count of compactions applied to state by shard",
1381                var_labels: ["shard", "name"],
1382            )),
1383            cmd_succeeded: registry.register(metric!(
1384                name: "mz_persist_shard_cmd_succeeded",
1385                help: "count of commands succeeded by shard",
1386                var_labels: ["shard", "name"],
1387            )),
1388            usage_current_state_batches_bytes: registry.register(metric!(
1389                name: "mz_persist_shard_usage_current_state_batches_bytes",
1390                help: "data in batches/parts referenced by current version of state",
1391                var_labels: ["shard", "name"],
1392            )),
1393            usage_current_state_rollups_bytes: registry.register(metric!(
1394                name: "mz_persist_shard_usage_current_state_rollups_bytes",
1395                help: "data in rollups referenced by current version of state",
1396                var_labels: ["shard", "name"],
1397            )),
1398            usage_referenced_not_current_state_bytes: registry.register(metric!(
1399                name: "mz_persist_shard_usage_referenced_not_current_state_bytes",
1400                help: "data referenced only by a previous version of state",
1401                var_labels: ["shard", "name"],
1402            )),
1403            usage_not_leaked_not_referenced_bytes: registry.register(metric!(
1404                name: "mz_persist_shard_usage_not_leaked_not_referenced_bytes",
1405                help: "data written by an active writer but not referenced by any version of state",
1406                var_labels: ["shard", "name"],
1407            )),
1408            usage_leaked_bytes: registry.register(metric!(
1409                name: "mz_persist_shard_usage_leaked_bytes",
1410                help: "data reclaimable by a leaked blob detector",
1411                var_labels: ["shard", "name"],
1412            )),
1413            blob_gets: registry.register(metric!(
1414                name: "mz_persist_shard_blob_gets",
1415                help: "number of Blob::get calls for this shard",
1416                var_labels: ["shard", "name"],
1417            )),
1418            blob_sets: registry.register(metric!(
1419                name: "mz_persist_shard_blob_sets",
1420                help: "number of Blob::set calls for this shard",
1421                var_labels: ["shard", "name"],
1422            )),
1423            unconsolidated_snapshot: registry.register(metric!(
1424                name: "mz_persist_shard_unconsolidated_snapshot",
1425                help: "in snapshot_and_read, the number of times consolidating the raw data wasn't enough to produce consolidated output",
1426                var_labels: ["shard", "name"],
1427            )),
1428            inline_part_count: registry.register(metric!(
1429                name: "mz_persist_shard_inline_part_count",
1430                help: "count of parts inline in shard metadata",
1431                var_labels: ["shard", "name"],
1432            )),
1433            compact_batches: registry.register(metric!(
1434                name: "mz_persist_shard_compact_batches",
1435                help: "number of fully compact batches in the shard",
1436                var_labels: ["shard", "name"],
1437            )),
1438            compacting_batches: registry.register(metric!(
1439                name: "mz_persist_shard_compacting_batches",
1440                help: "number of batches in the shard with compactions in progress",
1441                var_labels: ["shard", "name"],
1442            )),
1443            noncompact_batches: registry.register(metric!(
1444                name: "mz_persist_shard_noncompact_batches",
1445                help: "number of batches in the shard that aren't compact and have no ongoing compaction",
1446                var_labels: ["shard", "name"],
1447            )),
1448            shards,
1449        }
1450    }
1451
1452    pub fn shard(&self, shard_id: &ShardId, name: &str) -> Arc<ShardMetrics> {
1453        let mut shards = self.shards.lock().expect("mutex poisoned");
1454        if let Some(shard) = shards.get(shard_id) {
1455            if let Some(shard) = shard.upgrade() {
1456                return Arc::clone(&shard);
1457            } else {
1458                assert!(shards.remove(shard_id).is_some());
1459            }
1460        }
1461        let shard = Arc::new(ShardMetrics::new(shard_id, name, self));
1462        assert!(
1463            shards
1464                .insert(shard_id.clone(), Arc::downgrade(&shard))
1465                .is_none()
1466        );
1467        shard
1468    }
1469
1470    fn compute<F: FnMut(&ShardMetrics)>(
1471        shards: &Arc<Mutex<BTreeMap<ShardId, Weak<ShardMetrics>>>>,
1472        mut f: F,
1473    ) {
1474        let mut shards = shards.lock().expect("mutex poisoned");
1475        let mut deleted_shards = Vec::new();
1476        for (shard_id, metrics) in shards.iter() {
1477            if let Some(metrics) = metrics.upgrade() {
1478                f(&metrics);
1479            } else {
1480                deleted_shards.push(shard_id.clone());
1481            }
1482        }
1483        for deleted_shard_id in deleted_shards {
1484            assert!(shards.remove(&deleted_shard_id).is_some());
1485        }
1486    }
1487}
1488
1489/// Process-level gauges derived from the shards map, collected in a single walk.
1490///
1491/// Each is the sum of a per-shard quantity, so a `register_computed_gauge`
1492/// closure apiece would lock and walk the map once per gauge per scrape. This
1493/// collector shares [`ShardsMetrics`]'s `shards` map and folds all of them in
1494/// one pass instead.
1495#[derive(Debug)]
1496struct ShardsAggregateMetrics {
1497    shards: Arc<Mutex<BTreeMap<ShardId, Weak<ShardMetrics>>>>,
1498    count: GenericGauge<AtomicI64>,
1499    stale_count: GenericGauge<AtomicI64>,
1500}
1501
1502impl ShardsAggregateMetrics {
1503    fn new(shards: Arc<Mutex<BTreeMap<ShardId, Weak<ShardMetrics>>>>) -> Self {
1504        ShardsAggregateMetrics {
1505            shards,
1506            count: MakeCollector::make_collector(metric!(
1507                name: "mz_persist_shard_count",
1508                help: "count of all active shards on this process",
1509            )),
1510            stale_count: MakeCollector::make_collector(metric!(
1511                name: "mz_persist_stale_shard_count",
1512                help: "count of shards on this process whose persisted state version \
1513                       is behind this process's build version; per-process, so summing \
1514                       across processes counts (shard, process) pairs, not distinct shards",
1515            )),
1516        }
1517    }
1518}
1519
1520impl Collector for ShardsAggregateMetrics {
1521    fn desc(&self) -> Vec<&Desc> {
1522        let mut descs = self.count.desc();
1523        descs.extend(self.stale_count.desc());
1524        descs
1525    }
1526
1527    fn collect(&self) -> Vec<MetricFamily> {
1528        let mut count = 0;
1529        let mut stale_count = 0;
1530        ShardsMetrics::compute(&self.shards, |m| {
1531            count += 1;
1532            if m.stale.load(Ordering::Relaxed) {
1533                stale_count += 1;
1534            }
1535        });
1536        self.count.set(count);
1537        self.stale_count.set(stale_count);
1538        let mut families = self.count.collect();
1539        families.extend(self.stale_count.collect());
1540        families
1541    }
1542}
1543
1544#[derive(Debug)]
1545pub struct ShardMetrics {
1546    pub shard_id: ShardId,
1547    pub name: String,
1548    pub largest_batch_size: DeleteOnDropGauge<AtomicU64, Vec<String>>,
1549    pub latest_rollup_size: DeleteOnDropGauge<AtomicU64, Vec<String>>,
1550    pub encoded_diff_size: DeleteOnDropCounter<AtomicU64, Vec<String>>,
1551    pub hollow_batch_count: DeleteOnDropGauge<AtomicU64, Vec<String>>,
1552    pub spine_batch_count: DeleteOnDropGauge<AtomicU64, Vec<String>>,
1553    pub batch_part_count: DeleteOnDropGauge<AtomicU64, Vec<String>>,
1554    batch_part_version_count: mz_ore::metrics::UIntGaugeVec,
1555    batch_part_version_map: Mutex<BTreeMap<String, BatchPartVersionMetrics>>,
1556    pub update_count: DeleteOnDropGauge<AtomicU64, Vec<String>>,
1557    pub rollup_count: DeleteOnDropGauge<AtomicU64, Vec<String>>,
1558    pub seqnos_held: DeleteOnDropGauge<AtomicU64, Vec<String>>,
1559    pub seqnos_since_last_rollup: DeleteOnDropGauge<AtomicU64, Vec<String>>,
1560    pub gc_seqno_held_parts: DeleteOnDropGauge<AtomicU64, Vec<String>>,
1561    pub gc_live_diffs: DeleteOnDropGauge<AtomicU64, Vec<String>>,
1562    pub usage_current_state_batches_bytes: DeleteOnDropGauge<AtomicU64, Vec<String>>,
1563    pub usage_current_state_rollups_bytes: DeleteOnDropGauge<AtomicU64, Vec<String>>,
1564    pub usage_referenced_not_current_state_bytes: DeleteOnDropGauge<AtomicU64, Vec<String>>,
1565    pub usage_not_leaked_not_referenced_bytes: DeleteOnDropGauge<AtomicU64, Vec<String>>,
1566    pub usage_leaked_bytes: DeleteOnDropGauge<AtomicU64, Vec<String>>,
1567    pub gc_finished: DeleteOnDropCounter<AtomicU64, Vec<String>>,
1568    pub compaction_applied: DeleteOnDropCounter<AtomicU64, Vec<String>>,
1569    pub cmd_succeeded: DeleteOnDropCounter<AtomicU64, Vec<String>>,
1570    pub blob_gets: DeleteOnDropCounter<AtomicU64, Vec<String>>,
1571    pub blob_sets: DeleteOnDropCounter<AtomicU64, Vec<String>>,
1572    pub unconsolidated_snapshot: DeleteOnDropCounter<AtomicU64, Vec<String>>,
1573    pub inline_part_count: DeleteOnDropGauge<AtomicU64, Vec<String>>,
1574    pub compact_batches: DeleteOnDropGauge<AtomicU64, Vec<String>>,
1575    pub compacting_batches: DeleteOnDropGauge<AtomicU64, Vec<String>>,
1576    pub noncompact_batches: DeleteOnDropGauge<AtomicU64, Vec<String>>,
1577    // Not a registered per-shard series: this feeds the process-level
1578    // `mz_persist_stale_shard_count` computed gauge, which sums it across shards
1579    // instead of exporting one series per shard.
1580    pub stale: AtomicBool,
1581}
1582
1583impl ShardMetrics {
1584    pub fn new(shard_id: &ShardId, name: &str, shards_metrics: &ShardsMetrics) -> Self {
1585        let shard = shard_id.to_string();
1586        ShardMetrics {
1587            shard_id: *shard_id,
1588            name: name.to_string(),
1589            latest_rollup_size: shards_metrics
1590                .encoded_rollup_size
1591                .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
1592            encoded_diff_size: shards_metrics
1593                .encoded_diff_size
1594                .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
1595            hollow_batch_count: shards_metrics
1596                .hollow_batch_count
1597                .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
1598            spine_batch_count: shards_metrics
1599                .spine_batch_count
1600                .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
1601            batch_part_count: shards_metrics
1602                .batch_part_count
1603                .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
1604            batch_part_version_count: shards_metrics.batch_part_version_count.clone(),
1605            batch_part_version_map: Mutex::new(BTreeMap::new()),
1606            update_count: shards_metrics
1607                .update_count
1608                .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
1609            rollup_count: shards_metrics
1610                .rollup_count
1611                .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
1612            largest_batch_size: shards_metrics
1613                .largest_batch_size
1614                .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
1615            seqnos_held: shards_metrics
1616                .seqnos_held
1617                .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
1618            seqnos_since_last_rollup: shards_metrics
1619                .seqnos_since_last_rollup
1620                .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
1621            gc_seqno_held_parts: shards_metrics
1622                .gc_seqno_held_parts
1623                .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
1624            gc_live_diffs: shards_metrics
1625                .gc_live_diffs
1626                .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
1627            gc_finished: shards_metrics
1628                .gc_finished
1629                .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
1630            compaction_applied: shards_metrics
1631                .compaction_applied
1632                .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
1633            cmd_succeeded: shards_metrics
1634                .cmd_succeeded
1635                .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
1636            usage_current_state_batches_bytes: shards_metrics
1637                .usage_current_state_batches_bytes
1638                .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
1639            usage_current_state_rollups_bytes: shards_metrics
1640                .usage_current_state_rollups_bytes
1641                .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
1642            usage_referenced_not_current_state_bytes: shards_metrics
1643                .usage_referenced_not_current_state_bytes
1644                .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
1645            usage_not_leaked_not_referenced_bytes: shards_metrics
1646                .usage_not_leaked_not_referenced_bytes
1647                .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
1648            usage_leaked_bytes: shards_metrics
1649                .usage_leaked_bytes
1650                .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
1651            blob_gets: shards_metrics
1652                .blob_gets
1653                .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
1654            blob_sets: shards_metrics
1655                .blob_sets
1656                .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
1657            unconsolidated_snapshot: shards_metrics
1658                .unconsolidated_snapshot
1659                .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
1660            inline_part_count: shards_metrics
1661                .inline_part_count
1662                .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
1663            compact_batches: shards_metrics
1664                .compact_batches
1665                .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
1666            compacting_batches: shards_metrics
1667                .compacting_batches
1668                .get_delete_on_drop_metric(vec![shard.clone(), name.to_string()]),
1669            noncompact_batches: shards_metrics
1670                .noncompact_batches
1671                .get_delete_on_drop_metric(vec![shard, name.to_string()]),
1672            stale: AtomicBool::new(false),
1673        }
1674    }
1675
1676    pub(crate) fn set_batch_part_versions<'a>(
1677        &self,
1678        batch_parts_by_version: impl Iterator<Item = &'a str>,
1679    ) {
1680        let mut map = self
1681            .batch_part_version_map
1682            .lock()
1683            .expect("mutex should not be poisoned");
1684        // NB: It's a bit sus that the below assumes that no one else is
1685        // concurrently modifying the atomics in the gauges, but we're holding
1686        // the mutex this whole time, so it should be true.
1687
1688        // We want to do this in a way that avoids allocating (e.g. summing up a
1689        // map). First reset everything.
1690        for x in map.values() {
1691            x.batch_part_version_count.set(0);
1692        }
1693
1694        // Then go through the iterator, creating new entries as necessary and
1695        // adding.
1696        for key in batch_parts_by_version {
1697            if !map.contains_key(key) {
1698                map.insert(
1699                    key.to_owned(),
1700                    BatchPartVersionMetrics {
1701                        batch_part_version_count: self
1702                            .batch_part_version_count
1703                            .get_delete_on_drop_metric(vec![
1704                                self.shard_id.to_string(),
1705                                self.name.clone(),
1706                                key.to_owned(),
1707                            ]),
1708                    },
1709                );
1710            }
1711            let value = map.get(key).expect("inserted above");
1712            value.batch_part_version_count.inc();
1713        }
1714    }
1715}
1716
1717#[derive(Debug)]
1718pub struct BatchPartVersionMetrics {
1719    pub batch_part_version_count: DeleteOnDropGauge<AtomicU64, Vec<String>>,
1720}
1721
1722/// Metrics recorded by audits of persist usage
1723#[derive(Debug)]
1724pub struct UsageAuditMetrics {
1725    /// Size of all batch parts stored in Blob
1726    pub blob_batch_part_bytes: UIntGauge,
1727    /// Count of batch parts stored in Blob
1728    pub blob_batch_part_count: UIntGauge,
1729    /// Size of all state rollups stored in Blob
1730    pub blob_rollup_bytes: UIntGauge,
1731    /// Count of state rollups stored in Blob
1732    pub blob_rollup_count: UIntGauge,
1733    /// Size of Blob
1734    pub blob_bytes: UIntGauge,
1735    /// Count of all blobs
1736    pub blob_count: UIntGauge,
1737    /// Time spent fetching blob metadata
1738    pub step_blob_metadata: Counter,
1739    /// Time spent fetching state versions
1740    pub step_state: Counter,
1741    /// Time spent doing math
1742    pub step_math: Counter,
1743}
1744
1745impl UsageAuditMetrics {
1746    fn new(registry: &MetricsRegistry) -> Self {
1747        let step_timings: CounterVec = registry.register(metric!(
1748                name: "mz_persist_audit_step_seconds",
1749                help: "time spent on individual steps of audit",
1750                var_labels: ["step"],
1751        ));
1752        UsageAuditMetrics {
1753            blob_batch_part_bytes: registry.register(metric!(
1754                name: "mz_persist_audit_blob_batch_part_bytes",
1755                help: "total size of batch parts in blob",
1756            )),
1757            blob_batch_part_count: registry.register(metric!(
1758                name: "mz_persist_audit_blob_batch_part_count",
1759                help: "count of batch parts in blob",
1760            )),
1761            blob_rollup_bytes: registry.register(metric!(
1762                name: "mz_persist_audit_blob_rollup_bytes",
1763                help: "total size of state rollups stored in blob",
1764            )),
1765            blob_rollup_count: registry.register(metric!(
1766                name: "mz_persist_audit_blob_rollup_count",
1767                help: "count of all state rollups in blob",
1768            )),
1769            blob_bytes: registry.register(metric!(
1770                name: "mz_persist_audit_blob_bytes",
1771                help: "total size of blob",
1772            )),
1773            blob_count: registry.register(metric!(
1774                name: "mz_persist_audit_blob_count",
1775                help: "count of all blobs",
1776            )),
1777            step_blob_metadata: step_timings.with_label_values(&["blob_metadata"]),
1778            step_state: step_timings.with_label_values(&["state"]),
1779            step_math: step_timings.with_label_values(&["math"]),
1780        }
1781    }
1782}
1783
1784/// Represents a change in a number of updates kept in a data structure
1785/// (e.g., a buffer length or capacity change).
1786#[derive(Debug)]
1787pub enum UpdateDelta {
1788    /// A negative delta in the number of updates.
1789    Negative(u64),
1790    /// A non-negative delta in the number of updates.
1791    NonNegative(u64),
1792}
1793
1794impl UpdateDelta {
1795    /// Creates a new `UpdateDelta` from the difference between a new value
1796    /// for a number of updates and the corresponding old value.
1797    pub fn new(new: usize, old: usize) -> Self {
1798        if new < old {
1799            UpdateDelta::Negative(CastFrom::cast_from(old - new))
1800        } else {
1801            UpdateDelta::NonNegative(CastFrom::cast_from(new - old))
1802        }
1803    }
1804}
1805
1806/// Metrics for the persist sink. (While this lies slightly outside the usual
1807/// abstraction boundary of the client, it's convenient to manage them together.
1808#[derive(Debug, Clone)]
1809pub struct SinkMetrics {
1810    /// Cumulative record insertions made to the correction buffer across workers
1811    correction_insertions_total: IntCounter,
1812    /// Cumulative record deletions made to the correction buffer across workers
1813    correction_deletions_total: IntCounter,
1814    /// Cumulative capacity increases made to the correction buffer across workers
1815    correction_capacity_increases_total: IntCounter,
1816    /// Cumulative capacity decreases made to the correction buffer across workers
1817    correction_capacity_decreases_total: IntCounter,
1818    /// Maximum length observed for any one correction buffer per worker
1819    correction_max_per_sink_worker_len_updates: raw::UIntGaugeVec,
1820    /// Maximum capacity observed for any one correction buffer per worker
1821    correction_max_per_sink_worker_capacity_updates: raw::UIntGaugeVec,
1822}
1823
1824impl SinkMetrics {
1825    fn new(registry: &MetricsRegistry) -> Self {
1826        SinkMetrics {
1827            correction_insertions_total: registry.register(metric!(
1828                name: "mz_persist_sink_correction_insertions_total",
1829                help: "The cumulative insertions observed on the correction buffer across workers and persist sinks.",
1830            )),
1831            correction_deletions_total: registry.register(metric!(
1832                name: "mz_persist_sink_correction_deletions_total",
1833                help: "The cumulative deletions observed on the correction buffer across workers and persist sinks.",
1834            )),
1835            correction_capacity_increases_total: registry.register(metric!(
1836                name: "mz_persist_sink_correction_capacity_increases_total",
1837                help: "The cumulative capacity increases observed on the correction buffer across workers and persist sinks.",
1838            )),
1839            correction_capacity_decreases_total: registry.register(metric!(
1840                name: "mz_persist_sink_correction_capacity_decreases_total",
1841                help: "The cumulative capacity decreases observed on the correction buffer across workers and persist sinks.",
1842            )),
1843            correction_max_per_sink_worker_len_updates: registry.register(metric!(
1844                name: "mz_persist_sink_correction_max_per_sink_worker_len_updates",
1845                help: "The maximum length observed for the correction buffer of any single persist sink per worker.",
1846                var_labels: ["worker_id"],
1847            )),
1848            correction_max_per_sink_worker_capacity_updates: registry.register(metric!(
1849                name: "mz_persist_sink_correction_max_per_sink_worker_capacity_updates",
1850                help: "The maximum capacity observed for the correction buffer of any single persist sink per worker.",
1851                var_labels: ["worker_id"],
1852            )),
1853        }
1854    }
1855
1856    /// Obtains a `SinkWorkerMetrics` instance, which allows for metric reporting
1857    /// from a specific `persist_sink` instance for a given worker. The reports will
1858    /// update metrics shared across workers, but provide per-worker contributions
1859    /// to them.
1860    pub fn for_worker(&self, worker_id: usize) -> SinkWorkerMetrics {
1861        let worker = worker_id.to_string();
1862        let correction_max_per_sink_worker_len_updates = self
1863            .correction_max_per_sink_worker_len_updates
1864            .with_label_values(&[&worker]);
1865        let correction_max_per_sink_worker_capacity_updates = self
1866            .correction_max_per_sink_worker_capacity_updates
1867            .with_label_values(&[&worker]);
1868        SinkWorkerMetrics {
1869            correction_max_per_sink_worker_len_updates,
1870            correction_max_per_sink_worker_capacity_updates,
1871        }
1872    }
1873
1874    /// Reports updates to the length and capacity of the correction buffer in the
1875    /// `write_batches` operator of a `persist_sink`.
1876    ///
1877    /// This method updates monotonic metrics based on the deltas and thus can be
1878    /// called across workers and instances of `persist_sink`.
1879    pub fn report_correction_update_deltas(
1880        &self,
1881        correction_len_delta: UpdateDelta,
1882        correction_cap_delta: UpdateDelta,
1883    ) {
1884        // Report insertions or deletions.
1885        match correction_len_delta {
1886            UpdateDelta::NonNegative(delta) => {
1887                if delta > 0 {
1888                    self.correction_insertions_total.inc_by(delta)
1889                }
1890            }
1891            UpdateDelta::Negative(delta) => self.correction_deletions_total.inc_by(delta),
1892        }
1893        // Report capacity increases or decreases.
1894        match correction_cap_delta {
1895            UpdateDelta::NonNegative(delta) => {
1896                if delta > 0 {
1897                    self.correction_capacity_increases_total.inc_by(delta)
1898                }
1899            }
1900            UpdateDelta::Negative(delta) => self.correction_capacity_decreases_total.inc_by(delta),
1901        }
1902    }
1903}
1904
1905/// Metrics for the persist sink that are labeled per-worker.
1906#[derive(Clone, Debug)]
1907pub struct SinkWorkerMetrics {
1908    correction_max_per_sink_worker_len_updates: UIntGauge,
1909    correction_max_per_sink_worker_capacity_updates: UIntGauge,
1910}
1911
1912impl SinkWorkerMetrics {
1913    /// Reports the length and capacity of the correction buffer in the `write_batches`
1914    /// operator of `persist_sink`.
1915    ///
1916    /// This method is used to update metrics that are kept per worker.
1917    pub fn report_correction_update_totals(&self, correction_len: usize, correction_cap: usize) {
1918        // Maintain per-worker peaks.
1919        let correction_len = CastFrom::cast_from(correction_len);
1920        if correction_len > self.correction_max_per_sink_worker_len_updates.get() {
1921            self.correction_max_per_sink_worker_len_updates
1922                .set(correction_len);
1923        }
1924        let correction_cap = CastFrom::cast_from(correction_cap);
1925        if correction_cap > self.correction_max_per_sink_worker_capacity_updates.get() {
1926            self.correction_max_per_sink_worker_capacity_updates
1927                .set(correction_cap);
1928        }
1929    }
1930}
1931
1932/// Metrics for the `persist_source` backpressure operator, summed over every
1933/// instance of the operator in the process. Like [SinkMetrics], these belong
1934/// to a dataflow operator rather than the client, but the client owns the only
1935/// registry the operator can reach.
1936#[derive(Debug)]
1937pub struct BackpressureMetrics {
1938    /// Bytes emitted by backpressure operators.
1939    pub emitted_bytes: IntCounter,
1940    /// Sum over live operator instances of the inflight bytes each one most
1941    /// recently stalled on. Instances contribute deltas, so the sum stays exact
1942    /// as operators start and stop.
1943    pub last_backpressured_bytes: UIntGauge,
1944    /// Bytes retired by processing downstream of backpressure operators.
1945    pub retired_bytes: IntCounter,
1946}
1947
1948impl BackpressureMetrics {
1949    fn new(registry: &MetricsRegistry) -> Self {
1950        BackpressureMetrics {
1951            emitted_bytes: registry.register(metric!(
1952                name: "mz_persist_backpressure_emitted_bytes",
1953                help: "bytes emitted by backpressure operators",
1954            )),
1955            last_backpressured_bytes: registry.register(metric!(
1956                name: "mz_persist_backpressure_last_backpressured_bytes",
1957                help: "sum over backpressure operators of the inflight bytes each last \
1958                    stalled on; not comparable to retired bytes, but nonzero growth \
1959                    indicates backpressure is happening",
1960            )),
1961            retired_bytes: registry.register(metric!(
1962                name: "mz_persist_backpressure_retired_bytes",
1963                help: "bytes retired by processing downstream of backpressure operators",
1964            )),
1965        }
1966    }
1967}
1968
1969/// A minimal set of metrics imported into honeycomb for alerting.
1970#[derive(Debug)]
1971pub struct AlertsMetrics {
1972    pub(crate) blob_failures: IntCounter,
1973    pub(crate) consensus_failures: IntCounter,
1974}
1975
1976impl AlertsMetrics {
1977    fn new(registry: &MetricsRegistry) -> Self {
1978        AlertsMetrics {
1979            blob_failures: registry.register(metric!(
1980                name: "mz_persist_blob_failures",
1981                help: "count of all blob operation failures",
1982                const_labels: {"honeycomb" => "import"},
1983            )),
1984            consensus_failures: registry.register(metric!(
1985                name: "mz_persist_consensus_failures",
1986                help: "count of determinate consensus operation failures",
1987                const_labels: {"honeycomb" => "import"},
1988            )),
1989        }
1990    }
1991}
1992
1993/// Metrics for the PubSubServer implementation.
1994#[derive(Debug)]
1995pub struct PubSubServerMetrics {
1996    pub(crate) active_connections: UIntGauge,
1997    pub(crate) broadcasted_diff_count: IntCounter,
1998    pub(crate) broadcasted_diff_bytes: IntCounter,
1999    pub(crate) broadcasted_diff_dropped_channel_full: IntCounter,
2000
2001    pub(crate) push_seconds: Counter,
2002    pub(crate) subscribe_seconds: Counter,
2003    pub(crate) unsubscribe_seconds: Counter,
2004    pub(crate) connection_cleanup_seconds: Counter,
2005
2006    pub(crate) push_call_count: IntCounter,
2007    pub(crate) subscribe_call_count: IntCounter,
2008    pub(crate) unsubscribe_call_count: IntCounter,
2009}
2010
2011impl PubSubServerMetrics {
2012    pub(crate) fn new(registry: &MetricsRegistry) -> Self {
2013        let op_timings: CounterVec = registry.register(metric!(
2014                name: "mz_persist_pubsub_server_operation_seconds",
2015                help: "time spent in pubsub server performing each operation",
2016                var_labels: ["op"],
2017        ));
2018        let call_count: IntCounterVec = registry.register(metric!(
2019                name: "mz_persist_pubsub_server_call_count",
2020                help: "count of each pubsub server message received",
2021                var_labels: ["call"],
2022        ));
2023
2024        Self {
2025            active_connections: registry.register(metric!(
2026                    name: "mz_persist_pubsub_server_active_connections",
2027                    help: "number of active connections to server",
2028            )),
2029            broadcasted_diff_count: registry.register(metric!(
2030                    name: "mz_persist_pubsub_server_broadcasted_diff_count",
2031                    help: "count of total broadcast diff messages sent",
2032            )),
2033            broadcasted_diff_bytes: registry.register(metric!(
2034                    name: "mz_persist_pubsub_server_broadcasted_diff_bytes",
2035                    help: "count of total broadcast diff bytes sent",
2036            )),
2037            broadcasted_diff_dropped_channel_full: registry.register(metric!(
2038                    name: "mz_persist_pubsub_server_broadcasted_diff_dropped_channel_full",
2039                    help: "count of diffs dropped due to full connection channel",
2040            )),
2041
2042            push_seconds: op_timings.with_label_values(&["push"]),
2043            subscribe_seconds: op_timings.with_label_values(&["subscribe"]),
2044            unsubscribe_seconds: op_timings.with_label_values(&["unsubscribe"]),
2045            connection_cleanup_seconds: op_timings.with_label_values(&["cleanup"]),
2046
2047            push_call_count: call_count.with_label_values(&["push"]),
2048            subscribe_call_count: call_count.with_label_values(&["subscribe"]),
2049            unsubscribe_call_count: call_count.with_label_values(&["unsubscribe"]),
2050        }
2051    }
2052}
2053
2054/// Metrics for the PubSubClient implementation.
2055#[derive(Debug)]
2056pub struct PubSubClientMetrics {
2057    pub sender: PubSubClientSenderMetrics,
2058    pub receiver: PubSubClientReceiverMetrics,
2059    pub grpc_connection: PubSubGrpcClientConnectionMetrics,
2060}
2061
2062impl PubSubClientMetrics {
2063    fn new(registry: &MetricsRegistry) -> Self {
2064        PubSubClientMetrics {
2065            sender: PubSubClientSenderMetrics::new(registry),
2066            receiver: PubSubClientReceiverMetrics::new(registry),
2067            grpc_connection: PubSubGrpcClientConnectionMetrics::new(registry),
2068        }
2069    }
2070}
2071
2072#[derive(Debug)]
2073pub struct PubSubGrpcClientConnectionMetrics {
2074    pub(crate) connected: UIntGauge,
2075    pub(crate) connection_established_count: IntCounter,
2076    pub(crate) connect_call_attempt_count: IntCounter,
2077    pub(crate) broadcast_recv_lagged_count: IntCounter,
2078    pub(crate) grpc_error_count: IntCounter,
2079}
2080
2081impl PubSubGrpcClientConnectionMetrics {
2082    fn new(registry: &MetricsRegistry) -> Self {
2083        Self {
2084            connected: registry.register(metric!(
2085                    name: "mz_persist_pubsub_client_grpc_connected",
2086                    help: "whether the grpc client is currently connected",
2087            )),
2088            connection_established_count: registry.register(metric!(
2089                    name: "mz_persist_pubsub_client_grpc_connection_established_count",
2090                    help: "count of grpc connection establishments to pubsub server",
2091            )),
2092            connect_call_attempt_count: registry.register(metric!(
2093                    name: "mz_persist_pubsub_client_grpc_connect_call_attempt_count",
2094                    help: "count of connection call attempts (including retries) to pubsub server",
2095            )),
2096            broadcast_recv_lagged_count: registry.register(metric!(
2097                    name: "mz_persist_pubsub_client_grpc_broadcast_recv_lagged_count",
2098                    help: "times a message was missed by broadcast receiver due to lag",
2099            )),
2100            grpc_error_count: registry.register(metric!(
2101                    name: "mz_persist_pubsub_client_grpc_error_count",
2102                    help: "count of grpc errors received",
2103            )),
2104        }
2105    }
2106}
2107
2108#[derive(Clone, Debug)]
2109pub struct PubSubClientReceiverMetrics {
2110    pub(crate) push_received: IntCounter,
2111    pub(crate) unknown_message_received: IntCounter,
2112    pub(crate) approx_diff_latency_seconds: Histogram,
2113
2114    pub(crate) state_pushed_diff_fast_path: IntCounter,
2115    pub(crate) state_pushed_diff_slow_path_succeeded: IntCounter,
2116    pub(crate) state_pushed_diff_slow_path_failed: IntCounter,
2117
2118    pub(crate) diff_applied: IntCounter,
2119    pub(crate) diff_not_applied_stale: IntCounter,
2120    pub(crate) diff_not_applied_out_of_order: IntCounter,
2121}
2122
2123impl PubSubClientReceiverMetrics {
2124    fn new(registry: &MetricsRegistry) -> Self {
2125        let call_received: IntCounterVec = registry.register(metric!(
2126                name: "mz_persist_pubsub_client_call_received",
2127                help: "times a pubsub client call was received",
2128                var_labels: ["call"],
2129        ));
2130
2131        Self {
2132            push_received: call_received.with_label_values(&["push"]),
2133            unknown_message_received: call_received.with_label_values(&["unknown"]),
2134            approx_diff_latency_seconds: registry.register(metric!(
2135                name: "mz_persist_pubsub_client_approx_diff_apply_latency_seconds",
2136                help: "histogram of (approximate) latency between sending a diff and applying it",
2137                buckets: prometheus::exponential_buckets(0.001, 2.0, 13).expect("buckets"),
2138            )),
2139
2140            state_pushed_diff_fast_path: registry.register(metric!(
2141                name: "mz_persist_pubsub_client_receiver_state_push_diff_fast_path",
2142                help: "count fast-path state push_diff calls",
2143            )),
2144            state_pushed_diff_slow_path_succeeded: registry.register(metric!(
2145                name: "mz_persist_pubsub_client_receiver_state_push_diff_slow_path_succeeded",
2146                help: "count of successful slow-path state push_diff calls",
2147            )),
2148            state_pushed_diff_slow_path_failed: registry.register(metric!(
2149                name: "mz_persist_pubsub_client_receiver_state_push_diff_slow_path_failed",
2150                help: "count of unsuccessful slow-path state push_diff calls",
2151            )),
2152            diff_applied: registry.register(metric!(
2153                name: "mz_persist_pubsub_client_receiver_diff_applied",
2154                help: "number of diffs received via pubsub that applied",
2155            )),
2156            diff_not_applied_stale: registry.register(metric!(
2157                name: "mz_persist_pubsub_client_receiver_diff_not_applied_stale",
2158                help: "number of diffs received via pubsub that did not apply due to staleness",
2159            )),
2160            diff_not_applied_out_of_order: registry.register(metric!(
2161                name: "mz_persist_pubsub_client_receiver_diff_not_applied_out_of_order",
2162                help: "number of diffs received via pubsub that did not apply due to out-of-order delivery",
2163            )),
2164        }
2165    }
2166}
2167
2168#[derive(Debug)]
2169pub struct PubSubClientSenderMetrics {
2170    pub push: PubSubClientCallMetrics,
2171    pub subscribe: PubSubClientCallMetrics,
2172    pub unsubscribe: PubSubClientCallMetrics,
2173}
2174
2175#[derive(Debug)]
2176pub struct PubSubClientCallMetrics {
2177    pub(crate) succeeded: IntCounter,
2178    pub(crate) bytes_sent: IntCounter,
2179    pub(crate) failed: IntCounter,
2180}
2181
2182impl PubSubClientSenderMetrics {
2183    fn new(registry: &MetricsRegistry) -> Self {
2184        let call_bytes_sent: IntCounterVec = registry.register(metric!(
2185                name: "mz_persist_pubsub_client_call_bytes_sent",
2186                help: "number of bytes sent for a given pubsub client call",
2187                var_labels: ["call"],
2188        ));
2189        let call_succeeded: IntCounterVec = registry.register(metric!(
2190                name: "mz_persist_pubsub_client_call_succeeded",
2191                help: "times a pubsub client call succeeded",
2192                var_labels: ["call"],
2193        ));
2194        let call_failed: IntCounterVec = registry.register(metric!(
2195                name: "mz_persist_pubsub_client_call_failed",
2196                help: "times a pubsub client call failed",
2197                var_labels: ["call"],
2198        ));
2199
2200        Self {
2201            push: PubSubClientCallMetrics {
2202                succeeded: call_succeeded.with_label_values(&["push"]),
2203                failed: call_failed.with_label_values(&["push"]),
2204                bytes_sent: call_bytes_sent.with_label_values(&["push"]),
2205            },
2206            subscribe: PubSubClientCallMetrics {
2207                succeeded: call_succeeded.with_label_values(&["subscribe"]),
2208                failed: call_failed.with_label_values(&["subscribe"]),
2209                bytes_sent: call_bytes_sent.with_label_values(&["subscribe"]),
2210            },
2211            unsubscribe: PubSubClientCallMetrics {
2212                succeeded: call_succeeded.with_label_values(&["unsubscribe"]),
2213                failed: call_failed.with_label_values(&["unsubscribe"]),
2214                bytes_sent: call_bytes_sent.with_label_values(&["unsubscribe"]),
2215            },
2216        }
2217    }
2218}
2219
2220#[derive(Debug)]
2221pub struct LocksMetrics {
2222    pub(crate) applier_read_cacheable: LockMetrics,
2223    pub(crate) applier_read_noncacheable: LockMetrics,
2224    pub(crate) applier_write: LockMetrics,
2225    pub(crate) watch: LockMetrics,
2226}
2227
2228#[derive(Debug, Clone)]
2229pub struct LockMetrics {
2230    pub(crate) acquire_count: IntCounter,
2231    pub(crate) blocking_acquire_count: IntCounter,
2232    pub(crate) blocking_seconds: Counter,
2233}
2234
2235#[derive(Debug)]
2236pub struct WatchMetrics {
2237    pub(crate) wait_woken_via_watch: IntCounter,
2238    pub(crate) wait_woken_via_sleep: IntCounter,
2239    pub(crate) wait_resolved_via_watch: IntCounter,
2240    pub(crate) wait_resolved_via_sleep: IntCounter,
2241    pub(crate) notify_sent: IntCounter,
2242    pub(crate) notify_upper_sent: IntCounter,
2243    pub(crate) notify_noop: IntCounter,
2244    pub(crate) notify_recv: IntCounter,
2245    pub(crate) notify_lagged: IntCounter,
2246    pub(crate) notify_wait_started: IntCounter,
2247    pub(crate) notify_wait_finished: IntCounter,
2248}
2249
2250impl WatchMetrics {
2251    fn new(registry: &MetricsRegistry) -> Self {
2252        WatchMetrics {
2253            wait_woken_via_watch: registry.register(metric!(
2254                name: "mz_persist_wait_woken_via_watch",
2255                help: "count of wait-for-uppers wakes via watch notify",
2256            )),
2257            wait_woken_via_sleep: registry.register(metric!(
2258                name: "mz_persist_wait_woken_via_sleep",
2259                help: "count of wait-for-uppers wakes via sleep",
2260            )),
2261            wait_resolved_via_watch: registry.register(metric!(
2262                name: "mz_persist_wait_resolved_via_watch",
2263                help: "count of wait-for-uppers resolved via watch notify",
2264            )),
2265            wait_resolved_via_sleep: registry.register(metric!(
2266                name: "mz_persist_wait_resolved_via_sleep",
2267                help: "count of wait-for-uppers resolved via sleep",
2268            )),
2269            notify_sent: registry.register(metric!(
2270                name: "mz_persist_watch_notify_sent",
2271                help: "count of watch notifications sent to a non-empty broadcast channel",
2272            )),
2273            notify_upper_sent: registry.register(metric!(
2274                name: "mz_persist_watch_notify_upper_sent",
2275                help: "count of strict shard upper advances signaled to upper waiters",
2276            )),
2277            notify_noop: registry.register(metric!(
2278                name: "mz_persist_watch_notify_noop",
2279                help: "count of watch notifications sent to an broadcast channel",
2280            )),
2281            notify_recv: registry.register(metric!(
2282                name: "mz_persist_watch_notify_recv",
2283                help: "count of watch notifications received from the broadcast channel",
2284            )),
2285            notify_lagged: registry.register(metric!(
2286                name: "mz_persist_watch_notify_lagged",
2287                help: "count of lagged events in the watch notification broadcast channel",
2288            )),
2289            notify_wait_started: registry.register(metric!(
2290                name: "mz_persist_watch_notify_wait_started",
2291                help: "count of watch wait calls started",
2292            )),
2293            notify_wait_finished: registry.register(metric!(
2294                name: "mz_persist_watch_notify_wait_finished",
2295                help: "count of watch wait calls resolved",
2296            )),
2297        }
2298    }
2299}
2300
2301#[derive(Debug)]
2302pub struct PushdownMetrics {
2303    pub(crate) parts_filtered_count: IntCounter,
2304    pub(crate) parts_filtered_bytes: IntCounter,
2305    pub(crate) parts_fetched_count: IntCounter,
2306    pub(crate) parts_fetched_bytes: IntCounter,
2307    pub(crate) parts_audited_count: IntCounter,
2308    pub(crate) parts_audited_bytes: IntCounter,
2309    pub(crate) parts_inline_count: IntCounter,
2310    pub(crate) parts_inline_bytes: IntCounter,
2311    pub(crate) parts_faked_count: IntCounter,
2312    pub(crate) parts_faked_bytes: IntCounter,
2313    pub(crate) parts_stats_trimmed_count: IntCounter,
2314    pub(crate) parts_stats_trimmed_bytes: IntCounter,
2315    pub(crate) parts_projection_trimmed_bytes: IntCounter,
2316    pub part_stats: PartStatsMetrics,
2317}
2318
2319impl PushdownMetrics {
2320    fn new(registry: &MetricsRegistry) -> Self {
2321        PushdownMetrics {
2322            parts_filtered_count: registry.register(metric!(
2323                name: "mz_persist_pushdown_parts_filtered_count",
2324                help: "count of parts filtered by pushdown",
2325            )),
2326            parts_filtered_bytes: registry.register(metric!(
2327                name: "mz_persist_pushdown_parts_filtered_bytes",
2328                help: "total size of parts filtered by pushdown in bytes",
2329            )),
2330            parts_fetched_count: registry.register(metric!(
2331                name: "mz_persist_pushdown_parts_fetched_count",
2332                help: "count of parts not filtered by pushdown",
2333            )),
2334            parts_fetched_bytes: registry.register(metric!(
2335                name: "mz_persist_pushdown_parts_fetched_bytes",
2336                help: "total size of parts not filtered by pushdown in bytes",
2337            )),
2338            parts_audited_count: registry.register(metric!(
2339                name: "mz_persist_pushdown_parts_audited_count",
2340                help: "count of parts fetched only for pushdown audit",
2341            )),
2342            parts_audited_bytes: registry.register(metric!(
2343                name: "mz_persist_pushdown_parts_audited_bytes",
2344                help: "total size of parts fetched only for pushdown audit",
2345            )),
2346            parts_inline_count: registry.register(metric!(
2347                name: "mz_persist_pushdown_parts_inline_count",
2348                help: "count of parts not fetched because they were inline",
2349            )),
2350            parts_inline_bytes: registry.register(metric!(
2351                name: "mz_persist_pushdown_parts_inline_bytes",
2352                help: "total size of parts not fetched because they were inline",
2353            )),
2354            parts_faked_count: registry.register(metric!(
2355                name: "mz_persist_pushdown_parts_faked_count",
2356                help: "count of parts faked because of aggressive projection pushdown",
2357            )),
2358            parts_faked_bytes: registry.register(metric!(
2359                name: "mz_persist_pushdown_parts_faked_bytes",
2360                help: "total size of parts replaced with fakes by aggressive projection pushdown",
2361            )),
2362            parts_stats_trimmed_count: registry.register(metric!(
2363                name: "mz_persist_pushdown_parts_stats_trimmed_count",
2364                help: "count of trimmed part stats",
2365            )),
2366            parts_stats_trimmed_bytes: registry.register(metric!(
2367                name: "mz_persist_pushdown_parts_stats_trimmed_bytes",
2368                help: "total bytes trimmed from part stats",
2369            )),
2370            parts_projection_trimmed_bytes: registry.register(metric!(
2371                name: "mz_persist_pushdown_parts_projection_trimmed_bytes",
2372                help: "total bytes trimmed from columnar data because of projection pushdown",
2373            )),
2374            part_stats: PartStatsMetrics::new(registry),
2375        }
2376    }
2377}
2378
2379#[derive(Debug)]
2380pub struct ConsolidationMetrics {
2381    pub(crate) parts_fetched: IntCounter,
2382    pub(crate) parts_skipped: IntCounter,
2383    pub(crate) parts_wasted: IntCounter,
2384    pub(crate) wrong_sort: IntCounter,
2385}
2386
2387impl ConsolidationMetrics {
2388    fn new(registry: &MetricsRegistry) -> Self {
2389        ConsolidationMetrics {
2390            parts_fetched: registry.register(metric!(
2391                name: "mz_persist_consolidation_parts_fetched_count",
2392                help: "count of parts that were fetched and used during consolidation",
2393            )),
2394            parts_skipped: registry.register(metric!(
2395                name: "mz_persist_consolidation_parts_skipped_count",
2396                help: "count of parts that were never needed during consolidation",
2397            )),
2398            parts_wasted: registry.register(metric!(
2399                name: "mz_persist_consolidation_parts_wasted_count",
2400                help: "count of parts that were fetched but not needed during consolidation",
2401            )),
2402            wrong_sort: registry.register(metric!(
2403                name: "mz_persist_consolidation_wrong_sort_count",
2404                help: "count of runs that were sorted using the wrong ordering for the current consolidation",
2405            )),
2406        }
2407    }
2408}
2409
2410#[derive(Debug)]
2411pub struct BlobMemCache {
2412    pub(crate) size_blobs: UIntGauge,
2413    pub(crate) size_bytes: UIntGauge,
2414    pub(crate) hits_blobs: IntCounter,
2415    pub(crate) hits_bytes: IntCounter,
2416    pub(crate) evictions: IntCounter,
2417}
2418
2419impl BlobMemCache {
2420    fn new(registry: &MetricsRegistry) -> Self {
2421        BlobMemCache {
2422            size_blobs: registry.register(metric!(
2423                name: "mz_persist_blob_cache_size_blobs",
2424                help: "count of blobs in the cache",
2425                const_labels: {"cache" => "mem"},
2426            )),
2427            size_bytes: registry.register(metric!(
2428                name: "mz_persist_blob_cache_size_bytes",
2429                help: "total size of blobs in the cache",
2430                const_labels: {"cache" => "mem"},
2431            )),
2432            hits_blobs: registry.register(metric!(
2433                name: "mz_persist_blob_cache_hits_blobs",
2434                help: "count of blobs served via cache instead of s3",
2435                const_labels: {"cache" => "mem"},
2436            )),
2437            hits_bytes: registry.register(metric!(
2438                name: "mz_persist_blob_cache_hits_bytes",
2439                help: "total size of blobs served via cache instead of s3",
2440                const_labels: {"cache" => "mem"},
2441            )),
2442            evictions: registry.register(metric!(
2443                name: "mz_persist_blob_cache_evictions",
2444                help: "count of capacity-based cache evictions",
2445                const_labels: {"cache" => "mem"},
2446            )),
2447        }
2448    }
2449}
2450
2451#[derive(Debug)]
2452pub struct SemaphoreMetrics {
2453    cfg: PersistConfig,
2454    registry: MetricsRegistry,
2455    fetch: OnceCell<MetricsSemaphore>,
2456}
2457
2458impl SemaphoreMetrics {
2459    fn new(cfg: PersistConfig, registry: MetricsRegistry) -> Self {
2460        SemaphoreMetrics {
2461            cfg,
2462            registry,
2463            fetch: OnceCell::new(),
2464        }
2465    }
2466
2467    /// We can't easily change the number of permits, and the dyncfgs are all
2468    /// set to defaults on process start, so make sure we only initialize the
2469    /// semaphore once we've synced dyncfgs at least once.
2470    async fn fetch(&self) -> &MetricsSemaphore {
2471        if let Some(x) = self.fetch.get() {
2472            // Common case of already initialized avoids the cloning below.
2473            return x;
2474        }
2475        let cfg = self.cfg.clone();
2476        let registry = self.registry.clone();
2477        let init = async move {
2478            let total_permits = match cfg.announce_memory_limit {
2479                // Non-cc replicas have the old physical flow control mechanism,
2480                // so only apply this one on cc replicas.
2481                Some(mem) if cfg.is_cc_active => {
2482                    // We can't easily adjust the number of permits later, so
2483                    // make sure we've synced dyncfg values at least once.
2484                    info!("fetch semaphore awaiting first dyncfg values");
2485                    let () = cfg.configs_synced_once().await;
2486                    let total_permits = usize::cast_lossy(
2487                        f64::cast_lossy(mem) * FETCH_SEMAPHORE_PERMIT_ADJUSTMENT.get(&cfg),
2488                    );
2489                    info!("fetch_semaphore got first dyncfg values");
2490                    total_permits
2491                }
2492                Some(_) | None => Semaphore::MAX_PERMITS,
2493            };
2494            MetricsSemaphore::new(&registry, "fetch", total_permits)
2495        };
2496        self.fetch.get_or_init(|| init).await
2497    }
2498
2499    pub(crate) async fn acquire_fetch_permits(&self, encoded_size_bytes: usize) -> MetricsPermits {
2500        // Adjust the requested permits to account for the difference between
2501        // encoded_size_bytes and the decoded size in lgalloc.
2502        let requested_permits = f64::cast_lossy(encoded_size_bytes);
2503        let requested_permits = requested_permits * FETCH_SEMAPHORE_COST_ADJUSTMENT.get(&self.cfg);
2504        let requested_permits = usize::cast_lossy(requested_permits);
2505        self.fetch().await.acquire_permits(requested_permits).await
2506    }
2507}
2508
2509#[derive(Debug)]
2510pub struct MetricsSemaphore {
2511    name: &'static str,
2512    semaphore: Arc<Semaphore>,
2513    total_permits: usize,
2514    acquire_count: IntCounter,
2515    blocking_count: IntCounter,
2516    blocking_seconds: Counter,
2517    acquired_permits: IntCounter,
2518    released_permits: IntCounter,
2519    _available_permits: ComputedUIntGauge,
2520}
2521
2522impl MetricsSemaphore {
2523    pub fn new(registry: &MetricsRegistry, name: &'static str, total_permits: usize) -> Self {
2524        let total_permits = std::cmp::min(total_permits, Semaphore::MAX_PERMITS);
2525        // TODO: Sadly, tokio::sync::Semaphore makes it difficult to have a
2526        // dynamic total_permits count.
2527        let semaphore = Arc::new(Semaphore::new(total_permits));
2528        MetricsSemaphore {
2529            name,
2530            total_permits,
2531            acquire_count: registry.register(metric!(
2532                name: "mz_persist_semaphore_acquire_count",
2533                help: "count of acquire calls (not acquired permits count)",
2534                const_labels: {"name" => name},
2535            )),
2536            blocking_count: registry.register(metric!(
2537                name: "mz_persist_semaphore_blocking_count",
2538                help: "count of acquire calls that had to block",
2539                const_labels: {"name" => name},
2540            )),
2541            blocking_seconds: registry.register(metric!(
2542                name: "mz_persist_semaphore_blocking_seconds",
2543                help: "total time spent blocking on permit acquisition",
2544                const_labels: {"name" => name},
2545            )),
2546            acquired_permits: registry.register(metric!(
2547                name: "mz_persist_semaphore_acquired_permits",
2548                help: "total sum of acquired permits",
2549                const_labels: {"name" => name},
2550            )),
2551            released_permits: registry.register(metric!(
2552                name: "mz_persist_semaphore_released_permits",
2553                help: "total sum of released permits",
2554                const_labels: {"name" => name},
2555            )),
2556            _available_permits: registry.register_computed_gauge(
2557                metric!(
2558                    name: "mz_persist_semaphore_available_permits",
2559                    help: "currently available permits according to the semaphore",
2560                ),
2561                {
2562                    let semaphore = Arc::clone(&semaphore);
2563                    move || u64::cast_from(semaphore.available_permits())
2564                },
2565            ),
2566            semaphore,
2567        }
2568    }
2569
2570    pub async fn acquire_permits(&self, requested_permits: usize) -> MetricsPermits {
2571        // HACK: Cap the request at the total permit count. This prevents
2572        // deadlock, even if the cfg gets set to some small value.
2573        let total_permits = u32::try_from(self.total_permits).unwrap_or(u32::MAX);
2574        let requested_permits = u32::try_from(requested_permits).unwrap_or(u32::MAX);
2575        let requested_permits = std::cmp::min(requested_permits, total_permits);
2576        let wrap = |_permit| {
2577            self.acquired_permits.inc_by(u64::from(requested_permits));
2578            MetricsPermits {
2579                _permit,
2580                released_metric: self.released_permits.clone(),
2581                count: requested_permits,
2582            }
2583        };
2584
2585        // Special-case non-blocking happy path.
2586        self.acquire_count.inc();
2587        match Arc::clone(&self.semaphore).try_acquire_many_owned(requested_permits) {
2588            Ok(x) => return wrap(x),
2589            Err(_) => {}
2590        };
2591
2592        // Sad path, gotta block.
2593        self.blocking_count.inc();
2594        let start = Instant::now();
2595        let ret = Arc::clone(&self.semaphore)
2596            .acquire_many_owned(requested_permits)
2597            .instrument(info_span!("acquire_permits"))
2598            .await;
2599        let elapsed = start.elapsed();
2600        self.blocking_seconds.inc_by(elapsed.as_secs_f64());
2601        debug!(
2602            "acquisition of {} {} permits blocked for {:?}",
2603            self.name, requested_permits, elapsed
2604        );
2605        wrap(ret.expect("semaphore is never closed"))
2606    }
2607}
2608
2609#[derive(Debug)]
2610pub struct MetricsPermits {
2611    _permit: OwnedSemaphorePermit,
2612    released_metric: IntCounter,
2613    count: u32,
2614}
2615
2616impl Drop for MetricsPermits {
2617    fn drop(&mut self) {
2618        self.released_metric.inc_by(u64::from(self.count))
2619    }
2620}
2621
2622#[derive(Debug)]
2623pub struct ExternalOpMetrics {
2624    started: IntCounter,
2625    succeeded: IntCounter,
2626    failed: IntCounter,
2627    bytes: IntCounter,
2628    seconds: Counter,
2629    seconds_histogram: Option<Histogram>,
2630    alerts_metrics: Arc<AlertsMetrics>,
2631}
2632
2633impl ExternalOpMetrics {
2634    async fn run_op<R, F, OpFn, ErrFn>(
2635        &self,
2636        op_fn: OpFn,
2637        on_err_fn: ErrFn,
2638    ) -> Result<R, ExternalError>
2639    where
2640        F: std::future::Future<Output = Result<R, ExternalError>>,
2641        OpFn: FnOnce() -> F,
2642        ErrFn: FnOnce(&AlertsMetrics, &ExternalError),
2643    {
2644        self.started.inc();
2645        let start = Instant::now();
2646        let res = op_fn().await;
2647        let elapsed_seconds = start.elapsed().as_secs_f64();
2648        self.seconds.inc_by(elapsed_seconds);
2649        if let Some(h) = &self.seconds_histogram {
2650            h.observe(elapsed_seconds);
2651        }
2652        match res.as_ref() {
2653            Ok(_) => self.succeeded.inc(),
2654            Err(err) => {
2655                self.failed.inc();
2656                on_err_fn(&self.alerts_metrics, err);
2657            }
2658        };
2659        res
2660    }
2661
2662    fn run_stream<'a, R: 'a, S, OpFn, ErrFn>(
2663        &'a self,
2664        op_fn: OpFn,
2665        mut on_err_fn: ErrFn,
2666    ) -> impl futures::Stream<Item = Result<R, ExternalError>> + 'a
2667    where
2668        S: futures::Stream<Item = Result<R, ExternalError>> + Unpin + 'a,
2669        OpFn: FnOnce() -> S,
2670        ErrFn: FnMut(&AlertsMetrics, &ExternalError) + 'a,
2671    {
2672        self.started.inc();
2673        let start = Instant::now();
2674        let mut stream = op_fn();
2675        stream! {
2676            let mut succeeded = true;
2677            while let Some(res) = stream.next().await {
2678                if let Err(err) = res.as_ref() {
2679                    on_err_fn(&self.alerts_metrics, err);
2680                    succeeded = false;
2681                }
2682                yield res;
2683            }
2684            if succeeded {
2685                self.succeeded.inc()
2686            } else {
2687                self.failed.inc()
2688            }
2689            let elapsed_seconds = start.elapsed().as_secs_f64();
2690            self.seconds.inc_by(elapsed_seconds);
2691            if let Some(h) = &self.seconds_histogram {
2692                h.observe(elapsed_seconds);
2693            }
2694        }
2695    }
2696}
2697
2698#[derive(Debug)]
2699pub struct BlobMetrics {
2700    set: ExternalOpMetrics,
2701    get: ExternalOpMetrics,
2702    list_keys: ExternalOpMetrics,
2703    delete: ExternalOpMetrics,
2704    restore: ExternalOpMetrics,
2705    delete_noop: IntCounter,
2706    blob_sizes: Histogram,
2707    pub rtt_latency: Gauge,
2708}
2709
2710#[derive(Debug)]
2711pub struct MetricsBlob {
2712    blob: Arc<dyn Blob>,
2713    metrics: Arc<Metrics>,
2714}
2715
2716impl MetricsBlob {
2717    pub fn new(blob: Arc<dyn Blob>, metrics: Arc<Metrics>) -> Self {
2718        MetricsBlob { blob, metrics }
2719    }
2720
2721    fn on_err(alerts_metrics: &AlertsMetrics, _err: &ExternalError) {
2722        alerts_metrics.blob_failures.inc()
2723    }
2724}
2725
2726#[async_trait]
2727impl Blob for MetricsBlob {
2728    #[instrument(name = "blob::get", fields(shard=blob_key_shard_id(key)))]
2729    async fn get(&self, key: &str) -> Result<Option<SegmentedBytes>, ExternalError> {
2730        let res = self
2731            .metrics
2732            .blob
2733            .get
2734            .run_op(|| self.blob.get(key), Self::on_err)
2735            .await;
2736        if let Ok(Some(value)) = res.as_ref() {
2737            self.metrics
2738                .blob
2739                .get
2740                .bytes
2741                .inc_by(u64::cast_from(value.len()));
2742        }
2743        res
2744    }
2745
2746    #[instrument(name = "blob::list_keys_and_metadata", fields(shard=blob_key_shard_id(key_prefix)))]
2747    async fn list_keys_and_metadata(
2748        &self,
2749        key_prefix: &str,
2750        f: &mut (dyn FnMut(BlobMetadata) + Send + Sync),
2751    ) -> Result<(), ExternalError> {
2752        let mut byte_total = 0;
2753        let mut instrumented = |blob_metadata: BlobMetadata| {
2754            // Track the size of the _keys_, not the blobs, so that we get a
2755            // sense for how much network bandwidth these calls are using.
2756            byte_total += blob_metadata.key.len();
2757            f(blob_metadata)
2758        };
2759
2760        let res = self
2761            .metrics
2762            .blob
2763            .list_keys
2764            .run_op(
2765                || {
2766                    self.blob
2767                        .list_keys_and_metadata(key_prefix, &mut instrumented)
2768                },
2769                Self::on_err,
2770            )
2771            .await;
2772
2773        self.metrics
2774            .blob
2775            .list_keys
2776            .bytes
2777            .inc_by(u64::cast_from(byte_total));
2778
2779        res
2780    }
2781
2782    #[instrument(name = "blob::set", fields(shard=blob_key_shard_id(key),size_bytes=value.len()))]
2783    async fn set(&self, key: &str, value: Bytes) -> Result<(), ExternalError> {
2784        let bytes = value.len();
2785        let res = self
2786            .metrics
2787            .blob
2788            .set
2789            .run_op(|| self.blob.set(key, value), Self::on_err)
2790            .await;
2791        if res.is_ok() {
2792            self.metrics.blob.set.bytes.inc_by(u64::cast_from(bytes));
2793            self.metrics.blob.blob_sizes.observe(f64::cast_lossy(bytes));
2794        }
2795        res
2796    }
2797
2798    #[instrument(name = "blob::delete", fields(shard=blob_key_shard_id(key)))]
2799    async fn delete(&self, key: &str) -> Result<Option<usize>, ExternalError> {
2800        let bytes = self
2801            .metrics
2802            .blob
2803            .delete
2804            .run_op(|| self.blob.delete(key), Self::on_err)
2805            .await?;
2806        if let Some(bytes) = bytes {
2807            self.metrics.blob.delete.bytes.inc_by(u64::cast_from(bytes));
2808        } else {
2809            self.metrics.blob.delete_noop.inc();
2810        }
2811        Ok(bytes)
2812    }
2813
2814    async fn restore(&self, key: &str) -> Result<(), ExternalError> {
2815        self.metrics
2816            .blob
2817            .restore
2818            .run_op(|| self.blob.restore(key), Self::on_err)
2819            .await
2820    }
2821}
2822
2823#[derive(Debug)]
2824pub struct ConsensusMetrics {
2825    list_keys: ExternalOpMetrics,
2826    head: ExternalOpMetrics,
2827    compare_and_set: ExternalOpMetrics,
2828    scan: ExternalOpMetrics,
2829    truncate: ExternalOpMetrics,
2830    truncated_count: IntCounter,
2831    pub rtt_latency: Gauge,
2832}
2833
2834#[derive(Debug)]
2835pub struct MetricsConsensus {
2836    consensus: Arc<dyn Consensus>,
2837    metrics: Arc<Metrics>,
2838}
2839
2840impl MetricsConsensus {
2841    pub fn new(consensus: Arc<dyn Consensus>, metrics: Arc<Metrics>) -> Self {
2842        MetricsConsensus { consensus, metrics }
2843    }
2844
2845    fn on_err(alerts_metrics: &AlertsMetrics, err: &ExternalError) {
2846        // As of 2022-09-06, regular determinate errors are expected in
2847        // Consensus (i.e. "txn conflict, please retry"), so only count the
2848        // indeterminate ones.
2849        if let ExternalError::Indeterminate(_) = err {
2850            alerts_metrics.consensus_failures.inc()
2851        }
2852    }
2853}
2854
2855#[async_trait]
2856impl Consensus for MetricsConsensus {
2857    fn list_keys(&self) -> ResultStream<'_, String> {
2858        Box::pin(
2859            self.metrics
2860                .consensus
2861                .list_keys
2862                .run_stream(|| self.consensus.list_keys(), Self::on_err),
2863        )
2864    }
2865
2866    #[instrument(name = "consensus::head", fields(shard=key))]
2867    async fn head(&self, key: &str) -> Result<Option<VersionedData>, ExternalError> {
2868        let res = self
2869            .metrics
2870            .consensus
2871            .head
2872            .run_op(|| self.consensus.head(key), Self::on_err)
2873            .await;
2874        if let Ok(Some(data)) = res.as_ref() {
2875            self.metrics
2876                .consensus
2877                .head
2878                .bytes
2879                .inc_by(u64::cast_from(data.data.len()));
2880        }
2881        res
2882    }
2883
2884    #[instrument(name = "consensus::compare_and_set", fields(shard=key,size_bytes=new.data.len()))]
2885    async fn compare_and_set(
2886        &self,
2887        key: &str,
2888        new: VersionedData,
2889    ) -> Result<CaSResult, ExternalError> {
2890        let bytes = new.data.len();
2891        let res = self
2892            .metrics
2893            .consensus
2894            .compare_and_set
2895            .run_op(|| self.consensus.compare_and_set(key, new), Self::on_err)
2896            .await;
2897        match res.as_ref() {
2898            Ok(CaSResult::Committed) => self
2899                .metrics
2900                .consensus
2901                .compare_and_set
2902                .bytes
2903                .inc_by(u64::cast_from(bytes)),
2904            Ok(CaSResult::ExpectationMismatch) | Err(_) => {}
2905        }
2906        res
2907    }
2908
2909    #[instrument(name = "consensus::scan", fields(shard=key))]
2910    async fn scan(
2911        &self,
2912        key: &str,
2913        from: SeqNo,
2914        limit: usize,
2915    ) -> Result<Vec<VersionedData>, ExternalError> {
2916        let res = self
2917            .metrics
2918            .consensus
2919            .scan
2920            .run_op(|| self.consensus.scan(key, from, limit), Self::on_err)
2921            .await;
2922        if let Ok(dataz) = res.as_ref() {
2923            let bytes: usize = dataz.iter().map(|x| x.data.len()).sum();
2924            self.metrics
2925                .consensus
2926                .scan
2927                .bytes
2928                .inc_by(u64::cast_from(bytes));
2929        }
2930        res
2931    }
2932
2933    #[instrument(name = "consensus::truncate", fields(shard=key))]
2934    async fn truncate(&self, key: &str, seqno: SeqNo) -> Result<Option<usize>, ExternalError> {
2935        let metrics = &self.metrics.consensus;
2936        let deleted = metrics
2937            .truncate
2938            .run_op(|| self.consensus.truncate(key, seqno), Self::on_err)
2939            .await?;
2940        if let Some(deleted) = deleted {
2941            metrics.truncated_count.inc_by(u64::cast_from(deleted));
2942        }
2943        Ok(deleted)
2944    }
2945}
2946
2947/// A standard set of metrics for an async task. Call [TaskMetrics::instrument_task] to instrument
2948/// a future and report its metrics for this task type.
2949#[derive(Debug, Clone)]
2950pub struct TaskMetrics {
2951    f64_gauges: Vec<(Gauge, fn(&tokio_metrics::TaskMetrics) -> f64)>,
2952    u64_gauges: Vec<(
2953        GenericGauge<AtomicU64>,
2954        fn(&tokio_metrics::TaskMetrics) -> u64,
2955    )>,
2956    monitor: TaskMonitor,
2957}
2958
2959impl TaskMetrics {
2960    pub fn new(name: &str) -> Self {
2961        let monitor = TaskMonitor::new();
2962        Self {
2963            f64_gauges: vec![
2964                (
2965                    Gauge::make_collector(metric!(
2966                        name: "mz_persist_task_total_idle_duration",
2967                        help: "Seconds of time spent idling, ie. waiting for a task to be woken up.",
2968                        const_labels: {"name" => name}
2969                    )),
2970                    |m| m.total_idle_duration.as_secs_f64(),
2971                ),
2972                (
2973                    Gauge::make_collector(metric!(
2974                        name: "mz_persist_task_total_scheduled_duration",
2975                        help: "Seconds of time spent scheduled, ie. ready to poll but not yet polled.",
2976                        const_labels: {"name" => name}
2977                    )),
2978                    |m| m.total_scheduled_duration.as_secs_f64(),
2979                ),
2980            ],
2981            u64_gauges: vec![
2982                (
2983                    MakeCollector::make_collector(metric!(
2984                        name: "mz_persist_task_total_scheduled_count",
2985                        help: "The total number of task schedules. Useful for computing the average scheduled time.",
2986                        const_labels: {"name" => name}
2987                    )),
2988                    |m| m.total_scheduled_count,
2989                ),
2990                (
2991                    MakeCollector::make_collector(metric!(
2992                        name: "mz_persist_task_total_idled_count",
2993                        help: "The total number of task idles. Useful for computing the average idle time.",
2994                        const_labels: {"name" => name}
2995                    ,
2996                    )),
2997                    |m| m.total_idled_count,
2998                ),
2999            ],
3000            monitor,
3001        }
3002    }
3003
3004    /// Instrument the provided future. The expectation is that the result will be executed
3005    /// as a task. (See [TaskMonitor::instrument] for more context.)
3006    pub fn instrument_task<F>(&self, task: F) -> tokio_metrics::Instrumented<F> {
3007        TaskMonitor::instrument(&self.monitor, task)
3008    }
3009}
3010
3011impl Collector for TaskMetrics {
3012    fn desc(&self) -> Vec<&Desc> {
3013        let mut descs = Vec::with_capacity(self.f64_gauges.len() + self.u64_gauges.len());
3014        for (g, _) in &self.f64_gauges {
3015            descs.extend(g.desc());
3016        }
3017        for (g, _) in &self.u64_gauges {
3018            descs.extend(g.desc());
3019        }
3020        descs
3021    }
3022
3023    fn collect(&self) -> Vec<MetricFamily> {
3024        let mut families = Vec::with_capacity(self.f64_gauges.len() + self.u64_gauges.len());
3025        let metrics = self.monitor.cumulative();
3026        for (g, metrics_fn) in &self.f64_gauges {
3027            g.set(metrics_fn(&metrics));
3028            families.extend(g.collect());
3029        }
3030        for (g, metrics_fn) in &self.u64_gauges {
3031            g.set(metrics_fn(&metrics));
3032            families.extend(g.collect());
3033        }
3034        families
3035    }
3036}
3037
3038#[derive(Debug)]
3039pub struct TasksMetrics {
3040    pub heartbeat_read: TaskMetrics,
3041}
3042
3043impl TasksMetrics {
3044    fn new(registry: &MetricsRegistry) -> Self {
3045        let heartbeat_read = TaskMetrics::new("heartbeat_read");
3046        registry.register_collector(heartbeat_read.clone());
3047        TasksMetrics { heartbeat_read }
3048    }
3049}
3050
3051#[derive(Debug)]
3052pub struct SchemaMetrics {
3053    pub(crate) cache_fetch_state_count: IntCounter,
3054    pub(crate) cache_schema: SchemaCacheMetrics,
3055    pub(crate) cache_migration: SchemaCacheMetrics,
3056    pub(crate) migration_count_same: IntCounter,
3057    pub(crate) migration_count_codec: IntCounter,
3058    pub(crate) migration_count_either: IntCounter,
3059    pub(crate) migration_len_legacy_codec: IntCounter,
3060    pub(crate) migration_len_either_codec: IntCounter,
3061    pub(crate) migration_len_either_arrow: IntCounter,
3062    pub(crate) migration_new_count: IntCounter,
3063    pub(crate) migration_new_seconds: Counter,
3064    pub(crate) migration_migrate_seconds: Counter,
3065}
3066
3067impl SchemaMetrics {
3068    fn new(registry: &MetricsRegistry) -> Self {
3069        let cached: IntCounterVec = registry.register(metric!(
3070            name: "mz_persist_schema_cache_cached_count",
3071            help: "count of schema cache entries served from cache",
3072            var_labels: ["op"],
3073        ));
3074        let computed: IntCounterVec = registry.register(metric!(
3075            name: "mz_persist_schema_cache_computed_count",
3076            help: "count of schema cache entries computed",
3077            var_labels: ["op"],
3078        ));
3079        let unavailable: IntCounterVec = registry.register(metric!(
3080            name: "mz_persist_schema_cache_unavailable_count",
3081            help: "count of schema cache entries unavailable at current state",
3082            var_labels: ["op"],
3083        ));
3084        let added: IntCounterVec = registry.register(metric!(
3085            name: "mz_persist_schema_cache_added_count",
3086            help: "count of schema cache entries added",
3087            var_labels: ["op"],
3088        ));
3089        let dropped: IntCounterVec = registry.register(metric!(
3090            name: "mz_persist_schema_cache_dropped_count",
3091            help: "count of schema cache entries dropped",
3092            var_labels: ["op"],
3093        ));
3094        let cache = |name| SchemaCacheMetrics {
3095            cached_count: cached.with_label_values(&[name]),
3096            computed_count: computed.with_label_values(&[name]),
3097            unavailable_count: unavailable.with_label_values(&[name]),
3098            added_count: added.with_label_values(&[name]),
3099            dropped_count: dropped.with_label_values(&[name]),
3100        };
3101        let migration_count: IntCounterVec = registry.register(metric!(
3102            name: "mz_persist_schema_migration_count",
3103            help: "count of fetch part migrations",
3104            var_labels: ["op"],
3105        ));
3106        let migration_len: IntCounterVec = registry.register(metric!(
3107            name: "mz_persist_schema_migration_len",
3108            help: "count of migrated update records",
3109            var_labels: ["op"],
3110        ));
3111        SchemaMetrics {
3112            cache_fetch_state_count: registry.register(metric!(
3113                name: "mz_persist_schema_cache_fetch_state_count",
3114                help: "count of state fetches by the schema cache",
3115            )),
3116            cache_schema: cache("schema"),
3117            cache_migration: cache("migration"),
3118            migration_count_same: migration_count.with_label_values(&["same"]),
3119            migration_count_codec: migration_count.with_label_values(&["codec"]),
3120            migration_count_either: migration_count.with_label_values(&["either"]),
3121            migration_len_legacy_codec: migration_len.with_label_values(&["legacy_codec"]),
3122            migration_len_either_codec: migration_len.with_label_values(&["either_codec"]),
3123            migration_len_either_arrow: migration_len.with_label_values(&["either_arrow"]),
3124            migration_new_count: registry.register(metric!(
3125                name: "mz_persist_schema_migration_new_count",
3126                help: "count of migrations constructed",
3127            )),
3128            migration_new_seconds: registry.register(metric!(
3129                name: "mz_persist_schema_migration_new_seconds",
3130                help: "seconds spent constructing migration logic",
3131            )),
3132            migration_migrate_seconds: registry.register(metric!(
3133                name: "mz_persist_schema_migration_migrate_seconds",
3134                help: "seconds spent applying migration logic",
3135            )),
3136        }
3137    }
3138}
3139
3140#[derive(Debug, Clone)]
3141pub struct SchemaCacheMetrics {
3142    pub(crate) cached_count: IntCounter,
3143    pub(crate) computed_count: IntCounter,
3144    pub(crate) unavailable_count: IntCounter,
3145    pub(crate) added_count: IntCounter,
3146    pub(crate) dropped_count: IntCounter,
3147}
3148
3149#[derive(Debug)]
3150pub struct InlineMetrics {
3151    pub(crate) part_commit_count: IntCounter,
3152    pub(crate) part_commit_bytes: IntCounter,
3153    pub(crate) backpressure: BatchWriteMetrics,
3154}
3155
3156impl InlineMetrics {
3157    fn new(registry: &MetricsRegistry) -> Self {
3158        InlineMetrics {
3159            part_commit_count: registry.register(metric!(
3160                name: "mz_persist_inline_part_commit_count",
3161                help: "count of inline parts committed to state",
3162            )),
3163            part_commit_bytes: registry.register(metric!(
3164                name: "mz_persist_inline_part_commit_bytes",
3165                help: "total size of of inline parts committed to state",
3166            )),
3167            backpressure: BatchWriteMetrics::new(registry, "inline_backpressure"),
3168        }
3169    }
3170}
3171
3172fn blob_key_shard_id(key: &str) -> Option<String> {
3173    let (shard_id, _) = BlobKey::parse_ids(key).ok()?;
3174    Some(shard_id.to_string())
3175}
3176
3177/// Encode a frontier into an i64 acceptable for use in metrics.
3178pub fn encode_ts_metric<T: Codec64>(ts: &Antichain<T>) -> i64 {
3179    // We have two problems in mapping a persist frontier into a metric.
3180    // First is that we only have a `T: Timestamp+Codec64`. Second, is
3181    // mapping an antichain to a single counter value. We solve both by
3182    // taking advantage of the fact that in practice, timestamps in mz are
3183    // currently always a u64 (and if we switch them, it will be to an i64).
3184    // This means that for all values that mz would actually produce,
3185    // interpreting the encoded bytes as a little-endian i64 will work.
3186    // Both of them impl PartialOrder, so in practice, there will always be
3187    // zero or one elements in the antichain.
3188    match ts.elements().first() {
3189        Some(ts) => i64::from_le_bytes(Codec64::encode(ts)),
3190        None => i64::MAX,
3191    }
3192}
3193
3194#[cfg(test)]
3195mod tests {
3196    use super::*;
3197
3198    #[mz_ore::test]
3199    fn shards_aggregate_metrics_one_pass() {
3200        let registry = MetricsRegistry::new();
3201        let shards = ShardsMetrics::new(&registry);
3202        let agg = ShardsAggregateMetrics::new(Arc::clone(&shards.shards));
3203        let a = shards.shard(&ShardId::new(), "a");
3204        let b = shards.shard(&ShardId::new(), "b");
3205
3206        // collect() recomputes both gauges from a single walk of the map.
3207        agg.collect();
3208        assert_eq!(agg.count.get(), 2);
3209        assert_eq!(agg.stale_count.get(), 0);
3210
3211        a.stale.store(true, Ordering::Relaxed);
3212        agg.collect();
3213        assert_eq!(agg.count.get(), 2);
3214        assert_eq!(agg.stale_count.get(), 1);
3215
3216        b.stale.store(true, Ordering::Relaxed);
3217        agg.collect();
3218        assert_eq!(agg.stale_count.get(), 2);
3219
3220        // Flipping back and dropping a handle both lower the counts: the gauge
3221        // is recomputed, not ticked.
3222        a.stale.store(false, Ordering::Relaxed);
3223        drop(b);
3224        agg.collect();
3225        assert_eq!(agg.count.get(), 1);
3226        assert_eq!(agg.stale_count.get(), 0);
3227    }
3228}