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.
17//!
18//! Installed once by compute init via [`register`]. All metrics are computed
19//! gauges that peek at the pool's stats at scrape time, reporting zero until
20//! something initializes the pool — a scrape must observe, not mmap the
21//! pool's address space into every process that happens to be monitored.
22
23use std::sync::OnceLock;
24
25use mz_ore::metric;
26use mz_ore::metrics::{ComputedUIntGauge, MakeCollectorOpts, MetricsRegistry};
27
28/// Install the buffer-pool metrics into `registry`. Idempotent. Repeated
29/// calls after the first one are no-ops.
30pub fn register(registry: &MetricsRegistry) {
31    static REGISTERED: OnceLock<()> = OnceLock::new();
32    REGISTERED.get_or_init(|| {
33        // Every name and help string is a literal at the `metric!` call so the
34        // metrics-catalog scanner (`bin/gen-metrics-catalog`), which reads the
35        // source rather than the expanded macro, can index them.
36        //
37        // These fields are exposed as computed gauges rather than counters
38        // because the pool owns the atomics. Monotonic counters and
39        // instantaneous levels are mixed under the one gauge type, so the
40        // `_total` name suffix, not the metric type, marks a field as
41        // monotonic.
42        gauge(registry, metric!(name: "mz_column_pool_resident_bytes", help: "Uncompressed bytes resident in the buffer pool."), |s| s.resident_bytes);
43        gauge(registry, metric!(name: "mz_column_pool_oversize_bytes", help: "Bytes held by oversize chunks that bypass pool paging."), |s| s.oversize_bytes);
44        gauge(registry, metric!(name: "mz_column_pool_inserts_total", help: "Chunks inserted into the buffer pool."), |s| s.inserts);
45        gauge(registry, metric!(name: "mz_column_pool_frees_total", help: "Chunks freed from the buffer pool."), |s| s.frees);
46        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);
47        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);
48        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);
49        gauge(registry, metric!(name: "mz_column_pool_extent_bytes_written_total", help: "Compressed bytes written into swap-backed extents."), |s| s.extent_bytes_written);
50        gauge(registry, metric!(name: "mz_column_pool_spill_scheduled_total", help: "Evictions handed to buffer-pool spill threads."), |s| s.spill_scheduled);
51        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);
52        gauge(registry, metric!(name: "mz_column_pool_spill_in_flight", help: "Spill entries queued or being processed."), |s| s.spill_in_flight);
53        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);
54        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);
55        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);
56        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);
57        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);
58        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);
59        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);
60        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);
61        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);
62        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);
63        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);
64        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);
65        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);
66        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);
67    });
68}
69
70/// Registers one computed gauge over a [`mz_ore::pool::PoolStats`] field.
71/// Peeks at the process-wide pool at scrape time. Reports zero until something
72/// initializes the pool, or if its reservation failed.
73fn gauge(
74    registry: &MetricsRegistry,
75    opts: MakeCollectorOpts,
76    field: fn(&mz_ore::pool::PoolStats) -> u64,
77) {
78    let _gauge: ComputedUIntGauge = registry.register_computed_gauge(opts, move || {
79        crate::pool_config::global_pool_peek()
80            .map(|pool| field(&pool.stats()))
81            .unwrap_or(0)
82    });
83}