1use std::sync::OnceLock;
25
26use mz_ore::metric;
27use mz_ore::metrics::{ComputedUIntGauge, MakeCollectorOpts, MetricsRegistry};
28use mz_ore::pool::{BackendKind, READ_LATENCY_BUCKETS};
29
30pub fn register(registry: &MetricsRegistry) {
33 static REGISTERED: OnceLock<()> = OnceLock::new();
34 REGISTERED.get_or_init(|| {
35 crate::columnar::chunk::metrics::register(registry);
36 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);
46 gauge(registry, metric!(name: "mz_column_pool_resident_bytes", help: "Uncompressed bytes resident in the buffer pool."), |s| s.resident_bytes);
47 gauge(registry, metric!(name: "mz_column_pool_oversize_bytes", help: "Bytes held by oversize chunks that bypass pool paging."), |s| s.oversize_bytes);
48 gauge(registry, metric!(name: "mz_column_pool_inserts_total", help: "Chunks inserted into the buffer pool."), |s| s.inserts);
49 gauge(registry, metric!(name: "mz_column_pool_frees_total", help: "Chunks freed from the buffer pool."), |s| s.frees);
50 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);
51 gauge(registry, metric!(name: "mz_column_pool_evictions_compress_total", help: "Evictions that compressed a chunk into a new extent."), |s| s.evictions_compress);
52 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);
53 gauge(registry, metric!(name: "mz_column_pool_extent_bytes_written_total", help: "Compressed bytes written into extents."), |s| s.extent_bytes_written);
54 gauge(registry, metric!(name: "mz_column_pool_spill_scheduled_total", help: "Evictions handed to buffer-pool spill threads."), |s| s.spill_scheduled);
55 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);
56 gauge(registry, metric!(name: "mz_column_pool_spill_in_flight", help: "Spill entries queued or being processed."), |s| s.spill_in_flight);
57 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);
58 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);
59 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);
60 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 be moved out of RAM."), |s| s.extent_pageout_incomplete);
61 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);
62 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);
63 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);
64 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);
65 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);
66 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);
67 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);
68 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);
69 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 be moved out of RAM."), |s| s.extent_unreclaimable_bytes);
70 gauge(registry, metric!(name: "mz_column_pool_extent_pageouts_total", help: "Extents pushed out of RAM by RSS-target enforcement, to the swap device or the file store."), |s| s.extent_pageouts);
71 gauge(registry, metric!(name: "mz_column_pool_extent_demotions_elided_total", help: "Extents freed while resident in a reclaimable arena slot, so the free needed no swap or file I/O."), |s| s.extent_demotions_elided);
72 gauge(registry, metric!(name: "mz_column_pool_extent_file_bytes", help: "Slot bytes of live extents demoted to the file store."), |s| s.extent_file_bytes);
73 gauge(registry, metric!(name: "mz_column_pool_extent_file_allocated_bytes", help: "Bytes the file store holds on the filesystem: slots in use plus free slots whose blocks were not punched."), |s| s.extent_file_allocated_bytes);
74 gauge(registry, metric!(name: "mz_column_pool_extent_file_capacity_bytes", help: "The file store's effective capacity in bytes."), |s| s.extent_file_capacity_bytes);
75 gauge(registry, metric!(name: "mz_column_pool_extent_file_writes_total", help: "Demotion writes that reached the file store without error, including those whose chunk was freed before the demotion committed."), |s| s.extent_file_writes);
76 gauge(registry, metric!(name: "mz_column_pool_extent_file_writes_inline_total", help: "File store demotion writes run by inline enforcement rather than spill threads."), |s| s.extent_file_writes_inline);
77 gauge(registry, metric!(name: "mz_column_pool_extent_file_write_bytes_identity_total", help: "Page-rounded bytes of file store demotion writes of uncompressed extents."), |s| s.extent_file_write_bytes_identity);
78 gauge(registry, metric!(name: "mz_column_pool_extent_file_write_bytes_compressed_total", help: "Page-rounded bytes of file store demotion writes of compressed extents."), |s| s.extent_file_write_bytes_compressed);
79 gauge(registry, metric!(name: "mz_column_pool_extent_file_reads_total", help: "Reads of extents demoted to the file store."), |s| s.extent_file_reads);
80 gauge(registry, metric!(name: "mz_column_pool_extent_file_read_bytes_total", help: "Page-rounded bytes transferred by file store reads."), |s| s.extent_file_read_bytes);
81 gauge(registry, metric!(name: "mz_column_pool_extent_file_repeat_reads_total", help: "File store reads of extents already read since their demotion."), |s| s.extent_file_repeat_reads);
82 gauge(registry, metric!(name: "mz_column_pool_extent_file_full_total", help: "Demotion passes that left an extent in RAM because the file store was full or had writes disabled."), |s| s.extent_file_full);
83 gauge(registry, metric!(name: "mz_column_pool_extent_file_write_errors_total", help: "File store I/O errors, after which demotion writes are disabled."), |s| s.extent_file_write_errors);
84 gauge(registry, metric!(name: "mz_column_pool_extent_file_holes_punched_bytes_total", help: "Bytes of free file store slots returned to the filesystem."), |s| s.extent_file_holes_punched_bytes);
85 read_latency_gauges(registry);
86 backend_gauges(registry);
87 let _gauge: ComputedUIntGauge = registry.register_computed_gauge(
88 metric!(
89 name: "mz_column_pool_backend_fallback",
90 help: "1 if the buffer pool was configured for the file store and installed on swap instead, else 0. Covers installation only: a store that later disables writes on an I/O error reports through mz_column_pool_extent_file_write_errors_total.",
91 ),
92 || u64::from(crate::pool_config::file_backend_fell_back()),
93 );
94 });
95}
96
97fn gauge(
101 registry: &MetricsRegistry,
102 opts: MakeCollectorOpts,
103 field: fn(&mz_ore::pool::PoolStats) -> u64,
104) {
105 let _gauge: ComputedUIntGauge = registry.register_computed_gauge(opts, move || {
106 crate::pool_config::global_pool_peek()
107 .map(|pool| field(&pool.stats()))
108 .unwrap_or(0)
109 });
110}
111
112fn read_latency_gauges(registry: &MetricsRegistry) {
119 const BOUNDS: [&str; READ_LATENCY_BUCKETS] = [
120 "0.000032", "0.000064", "0.000128", "0.000256", "0.000512", "0.001024", "0.002048",
121 "0.004096", "0.008192", "0.016384", "0.032768", "0.065536", "+Inf",
122 ];
123 for (bucket, le) in BOUNDS.into_iter().enumerate() {
124 let _gauge: ComputedUIntGauge = registry.register_computed_gauge(
125 metric!(
126 name: "mz_column_pool_extent_file_read_latency_bucket",
127 help: "Cumulative count of file store reads that took less than `le` seconds.",
128 const_labels: {"le" => le}
129 ),
130 move || {
131 crate::pool_config::global_pool_peek()
132 .map(|pool| {
133 pool.stats().extent_file_read_latency[..=bucket]
134 .iter()
135 .sum()
136 })
137 .unwrap_or(0)
138 },
139 );
140 }
141}
142
143fn backend_gauges(registry: &MetricsRegistry) {
147 let kinds = [
148 ("swap", BackendKind::Swap),
149 ("file_direct", BackendKind::FileDirect),
150 ("file_buffered", BackendKind::FileBuffered),
151 ];
152 for (label, kind) in kinds {
153 let _gauge: ComputedUIntGauge = registry.register_computed_gauge(
154 metric!(
155 name: "mz_column_pool_backend",
156 help: "The extent store the buffer pool runs on: 1 for the labeled backend, 0 for the others.",
157 const_labels: {"backend" => label}
158 ),
159 move || {
160 let active = crate::pool_config::global_pool_peek()
161 .is_some_and(|pool| pool.backend_kind() == kind);
162 u64::from(active)
163 },
164 );
165 }
166}