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