Skip to main content

mz_timely_util/pool_config/
metrics.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License in the LICENSE file at the
6// root of this repository, or online at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! Prometheus metrics for the process-wide buffer pool and columnar chunk
17//! work.
18//!
19//! Installed once by compute init via [`register`]. All metrics are computed
20//! gauges that peek at the pool's stats at scrape time, reporting zero until
21//! something initializes the pool — a scrape must observe, not mmap the
22//! pool's address space into every process that happens to be monitored.
23
24use std::sync::OnceLock;
25
26use mz_ore::metric;
27use mz_ore::metrics::{ComputedUIntGauge, MakeCollectorOpts, MetricsRegistry};
28
29/// Install the buffer-pool metrics and the columnar chunk work counters into
30/// `registry`. Idempotent. Repeated calls after the first one are no-ops.
31pub fn register(registry: &MetricsRegistry) {
32    static REGISTERED: OnceLock<()> = OnceLock::new();
33    REGISTERED.get_or_init(|| {
34        crate::columnar::chunk::metrics::register(registry);
35        // Every name and help string is a literal at the `metric!` call so the
36        // metrics-catalog scanner (`bin/gen-metrics-catalog`), which reads the
37        // source rather than the expanded macro, can index them.
38        //
39        // These fields are exposed as computed gauges rather than counters
40        // because the pool owns the atomics. Monotonic counters and
41        // instantaneous levels are mixed under the one gauge type, so the
42        // `_total` name suffix, not the metric type, marks a field as
43        // monotonic.
44        gauge(registry, metric!(name: "mz_column_pool_direct_extent_inserts_total", help: "Inserts written directly to an extent because resident admission was full."), |s| s.direct_extent_inserts);
45        gauge(registry, metric!(name: "mz_column_pool_resident_bytes", help: "Uncompressed bytes resident in the buffer pool."), |s| s.resident_bytes);
46        gauge(registry, metric!(name: "mz_column_pool_oversize_bytes", help: "Bytes held by oversize chunks that bypass pool paging."), |s| s.oversize_bytes);
47        gauge(registry, metric!(name: "mz_column_pool_inserts_total", help: "Chunks inserted into the buffer pool."), |s| s.inserts);
48        gauge(registry, metric!(name: "mz_column_pool_frees_total", help: "Chunks freed from the buffer pool."), |s| s.frees);
49        gauge(registry, metric!(name: "mz_column_pool_writes_elided_total", help: "Backing writes elided: chunks freed while unbacked or while queued for a spill thread, dead before their compression completed."), |s| s.writes_elided);
50        gauge(registry, metric!(name: "mz_column_pool_evictions_compress_total", help: "Evictions that compressed a chunk into a new swap-backed extent."), |s| s.evictions_compress);
51        gauge(registry, metric!(name: "mz_column_pool_evictions_cheap_total", help: "Evictions of already-backed chunks: physical pages released with no compression or extent write."), |s| s.evictions_cheap);
52        gauge(registry, metric!(name: "mz_column_pool_extent_bytes_written_total", help: "Compressed bytes written into swap-backed extents."), |s| s.extent_bytes_written);
53        gauge(registry, metric!(name: "mz_column_pool_spill_scheduled_total", help: "Evictions handed to buffer-pool spill threads."), |s| s.spill_scheduled);
54        gauge(registry, metric!(name: "mz_column_pool_spill_cancelled_total", help: "Compressions cancelled by a concurrent free, whatever their origin (budget eviction or eager backing), so this can exceed the scheduled count."), |s| s.spill_cancelled);
55        gauge(registry, metric!(name: "mz_column_pool_spill_in_flight", help: "Spill entries queued or being processed."), |s| s.spill_in_flight);
56        gauge(registry, metric!(name: "mz_column_pool_admissions_budget_total", help: "Evicted chunks re-admitted to compressed-but-resident by an admitting read out of free budget headroom."), |s| s.admissions_budget);
57        gauge(registry, metric!(name: "mz_column_pool_admissions_steal_total", help: "Evicted chunks re-admitted by an admitting read stealing the slot of a clean backed victim of the same size class."), |s| s.admissions_steal);
58        gauge(registry, metric!(name: "mz_column_pool_admissions_denied_total", help: "Admitting reads that found neither budget headroom nor a clean victim and were served as a plain decompress instead."), |s| s.admissions_denied);
59        gauge(registry, metric!(name: "mz_column_pool_extent_pageout_incomplete_total", help: "Pageout passes whose residency observation found pages still resident. Climbing steadily means pages cannot reach the swap device."), |s| s.extent_pageout_incomplete);
60        gauge(registry, metric!(name: "mz_column_pool_extent_arena_fallbacks_total", help: "Extent writes that fell back to the heap because their extent-arena class had no free slot. Heap-backed extents are never paged out."), |s| s.extent_arena_fallbacks);
61        gauge(registry, metric!(name: "mz_column_pool_slot_exhausted_fallbacks_total", help: "Inserts that fell back to unpageable heap chunks because their size class had no free slot."), |s| s.slot_exhausted_fallbacks);
62        gauge(registry, metric!(name: "mz_column_pool_oversize_payloads_total", help: "Inserts that went to unpageable heap chunks because the payload was larger than the largest size class."), |s| s.oversize_payloads);
63        gauge(registry, metric!(name: "mz_column_pool_live_chunks", help: "Live pool chunks, whatever their residency: for backlog-shaped consumers, the un-drained backlog in chunks."), |s| s.live_chunks);
64        gauge(registry, metric!(name: "mz_column_pool_warm_bytes", help: "Class bytes of free slots kept warm for fault-free reuse; RSS exceeds resident bytes by up to this bounded amount."), |s| s.warm_bytes);
65        gauge(registry, metric!(name: "mz_column_pool_warm_reuses_total", help: "Slot allocations served from the warm list: no page faults, no kernel page zeroing."), |s| s.warm_reuses);
66        gauge(registry, metric!(name: "mz_column_pool_eager_backs_total", help: "Chunks eagerly compressed to compressed-but-resident by idle spill threads; their later eviction is a pure page release."), |s| s.eager_backs);
67        gauge(registry, metric!(name: "mz_column_pool_extent_resident_bytes", help: "Allocation bytes of compressed extents currently resident (the compressed-but-resident tier), bounded by the pool RSS target."), |s| s.extent_resident_bytes);
68        gauge(registry, metric!(name: "mz_column_pool_extent_unreclaimable_bytes", help: "Allocation bytes of resident extents the RSS target cannot push out (retry-capped and heap-fallback extents). Climbing steadily means pages cannot reach the swap device."), |s| s.extent_unreclaimable_bytes);
69        gauge(registry, metric!(name: "mz_column_pool_extent_pageouts_total", help: "Extents pushed to the swap device by RSS-target enforcement."), |s| s.extent_pageouts);
70    });
71}
72
73/// Registers one computed gauge over a [`mz_ore::pool::PoolStats`] field.
74/// Peeks at the process-wide pool at scrape time. Reports zero until something
75/// initializes the pool, or if its reservation failed.
76fn gauge(
77    registry: &MetricsRegistry,
78    opts: MakeCollectorOpts,
79    field: fn(&mz_ore::pool::PoolStats) -> u64,
80) {
81    let _gauge: ComputedUIntGauge = registry.register_computed_gauge(opts, move || {
82        crate::pool_config::global_pool_peek()
83            .map(|pool| field(&pool.stats()))
84            .unwrap_or(0)
85    });
86}