mz_metrics/
lgalloc.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//! Report lgalloc metrics.
7
8use std::collections::BTreeMap;
9use std::ops::AddAssign;
10
11use lgalloc::{FileStats, MapStats};
12use mz_ore::cast::CastFrom;
13use mz_ore::metrics::{MetricsRegistry, raw};
14use paste::paste;
15use prometheus::core::{AtomicU64, GenericGauge};
16use tracing::error;
17
18use crate::MetricsUpdate;
19
20/// Error during FileStats
21#[derive(Debug, thiserror::Error)]
22#[error(transparent)]
23pub struct Error {
24    /// Kind of error
25    #[from]
26    pub kind: ErrorKind,
27}
28
29/// Kind of error during FileStats
30#[derive(Debug, thiserror::Error)]
31pub enum ErrorKind {
32    /// Failed to get file stats
33    #[error("Failed to get file stats: {0}")]
34    FileStatsFailed(String),
35}
36
37impl Error {
38    /// Create a new error
39    pub fn new(kind: ErrorKind) -> Error {
40        Error { kind }
41    }
42}
43
44/// An accumulator for [`FileStats`].
45#[derive(Default)]
46struct FileStatsAccum {
47    /// The size of the file in bytes.
48    pub file_size: usize,
49    /// Size of the file on disk in bytes.
50    pub allocated_size: usize,
51}
52
53/// An accumulator for [`MapStats`].
54#[derive(Default)]
55struct MapStatsAccum {
56    /// Number of mapped bytes, if different from `dirty`. Consult `man 7 numa` for details.
57    pub mapped: usize,
58    /// Number of active bytes. Consult `man 7 numa` for details.
59    pub active: usize,
60    /// Number of dirty bytes. Consult `man 7 numa` for details.
61    pub dirty: usize,
62}
63
64impl AddAssign<&FileStats> for FileStatsAccum {
65    fn add_assign(&mut self, rhs: &FileStats) {
66        self.file_size += rhs.file_size;
67        self.allocated_size += rhs.allocated_size;
68    }
69}
70
71impl AddAssign<&MapStats> for MapStatsAccum {
72    fn add_assign(&mut self, rhs: &MapStats) {
73        self.mapped += rhs.mapped;
74        self.active += rhs.active;
75        self.dirty += rhs.dirty;
76    }
77}
78
79macro_rules! metrics_size_class {
80    ($namespace:ident
81        @size_class ($(($name:ident, $desc:expr, $metric:expr, $conv:expr)),*)
82        @file ($(($f_name:ident, $f_metric:ident, $f_desc:expr)),*)
83    ) => {
84        metrics_size_class! {
85            @define $namespace
86            @size_class $(($name, $desc, $metric, $conv)),*
87            @file $(($f_name, $f_metric, $f_desc)),*
88        }
89    };
90    (@define $namespace:ident
91        @size_class $(($name:ident, $desc:expr, $metric:expr, $conv:expr)),*
92        @file $(($f_name:ident, $f_metric:ident, $f_desc:expr)),*
93    ) => {
94        paste! {
95            pub(crate) struct LgMetrics {
96                size_class: BTreeMap<usize, LgMetricsSC>,
97                $($metric: raw::UIntGaugeVec,)*
98                $($f_metric: raw::UIntGaugeVec,)*
99            }
100            struct LgMetricsSC {
101                $($metric: GenericGauge<AtomicU64>,)*
102                $($f_metric: GenericGauge<AtomicU64>,)*
103            }
104            impl LgMetrics {
105                fn new(registry: &MetricsRegistry) -> Self {
106                    Self {
107                        size_class: BTreeMap::default(),
108                        $($metric: registry.register(mz_ore::metric!(
109                            name: concat!(stringify!($namespace), "_", stringify!($metric)),
110                            help: $desc,
111                            var_labels: ["size_class"],
112                        )),)*
113                        $($f_metric: registry.register(mz_ore::metric!(
114                            name: concat!(stringify!($namespace), "_", stringify!($f_metric)),
115                            help: $f_desc,
116                            var_labels: ["size_class"],
117                        )),)*
118                    }
119                }
120                fn get_size_class(&mut self, size_class: usize) -> &LgMetricsSC {
121                    self.size_class.entry(size_class).or_insert_with(|| {
122                        let labels: &[&str] = &[&size_class.to_string()];
123                        LgMetricsSC {
124                            $($metric: self.$metric.with_label_values(labels),)*
125                            $($f_metric: self.$f_metric.with_label_values(labels),)*
126                        }
127                    })
128                }
129                fn update(&mut self) -> Result<(), Error> {
130                    let stats = lgalloc::lgalloc_stats();
131                    for (size_class, sc) in &stats.size_class {
132                        let sc_stats = self.get_size_class(*size_class);
133                        $(sc_stats.$metric.set(($conv)(u64::cast_from(sc.$name), *size_class));)*
134                    }
135                    let mut f_accums = BTreeMap::new();
136                    for (size_class, file_stat) in &stats.file {
137                        let accum: &mut FileStatsAccum = f_accums.entry(*size_class).or_default();
138                        match file_stat {
139                            Ok(file_stat) => {
140                                accum.add_assign(file_stat);
141                            }
142                            Err(err) => {
143                                return Err(ErrorKind::FileStatsFailed(err.to_string()).into());
144                            }
145                        }
146                    }
147                    for (size_class, f_accum) in f_accums {
148                        let sc_stats = self.get_size_class(size_class);
149                        $(sc_stats.$f_metric.set(u64::cast_from(f_accum.$f_name));)*
150                    }
151                    Ok(())
152                }
153            }
154        }
155    };
156}
157
158macro_rules! map_metrics {
159    ($namespace:ident
160        @mem ($(($m_name:ident, $m_metric:ident, $m_desc:expr)),*)
161    ) => {
162        map_metrics! {
163            @define $namespace
164            @mem $(($m_name, $m_metric, $m_desc)),*
165        }
166    };
167    (@define $namespace:ident
168        @mem $(($m_name:ident, $m_metric:ident, $m_desc:expr)),*
169    ) => {
170        paste! {
171            pub(crate) struct LgMapMetrics {
172                size_class: BTreeMap<usize, LgMapMetricsSC>,
173                $($m_metric: raw::UIntGaugeVec,)*
174            }
175            struct LgMapMetricsSC {
176                $($m_metric: GenericGauge<AtomicU64>,)*
177            }
178            impl LgMapMetrics {
179                fn new(registry: &MetricsRegistry) -> Self {
180                    Self {
181                        size_class: BTreeMap::default(),
182                        $($m_metric: registry.register(mz_ore::metric!(
183                            name: concat!(stringify!($namespace), "_", stringify!($m_metric)),
184                            help: $m_desc,
185                            var_labels: ["size_class"],
186                        )),)*
187                    }
188                }
189                fn get_size_class(&mut self, size_class: usize) -> &LgMapMetricsSC {
190                    self.size_class.entry(size_class).or_insert_with(|| {
191                        let labels: &[&str] = &[&size_class.to_string()];
192                        LgMapMetricsSC {
193                            $($m_metric: self.$m_metric.with_label_values(labels),)*
194                        }
195                    })
196                }
197                fn update(&mut self) -> std::io::Result<()> {
198                    #[cfg(target_os = "linux")]
199                    let stats = lgalloc::lgalloc_stats_with_mapping()?;
200                    // We don't have `numa_maps` on non-Linux platforms, so we use the regular
201                    // stats instead. It won't extract anything, but avoids unused variable
202                    // warnings.
203                    #[cfg(not(target_os = "linux"))]
204                    let stats = lgalloc::lgalloc_stats();
205                    let mut m_accums = BTreeMap::new();
206                    for (size_class, map_stat) in stats.map.iter().flatten() {
207                        let accum: &mut MapStatsAccum = m_accums.entry(*size_class).or_default();
208                        accum.add_assign(map_stat);
209                    }
210                    for (size_class, m_accum) in m_accums {
211                        let sc_stats = self.get_size_class(size_class);
212                        $(sc_stats.$m_metric.set(u64::cast_from(m_accum.$m_name));)*
213                    }
214                    Ok(())
215                }
216            }
217        }
218    };
219}
220
221fn normalize_by_size_class(value: u64, size_class: usize) -> u64 {
222    value * u64::cast_from(size_class)
223}
224
225fn id(value: u64, _size_class: usize) -> u64 {
226    value
227}
228
229metrics_size_class! {
230    mz_metrics_lgalloc
231    @size_class (
232        (allocations, "Number of region allocations in size class", allocations_total, id),
233        (area_total_bytes, "Number of bytes in all areas in size class", area_total_bytes, id),
234        (areas, "Number of areas backing size class", areas_total, id),
235        (clean_regions, "Number of clean regions in size class", clean_regions_total, id),
236        (clean_regions, "Number of clean regions in size class", clean_regions_bytes_total, normalize_by_size_class),
237        (deallocations, "Number of region deallocations for size class", deallocations_total, id),
238        (free_regions, "Number of free regions in size class", free_regions_total, id),
239        (free_regions, "Number of free regions in size class", free_regions_bytes_total, normalize_by_size_class),
240        (global_regions, "Number of global regions in size class", global_regions_total, id),
241        (global_regions, "Number of global regions in size class", global_regions_bytes_total, normalize_by_size_class),
242        (refill, "Number of area refills for size class", refill_total, id),
243        (slow_path, "Number of slow path region allocations for size class", slow_path_total, id),
244        (thread_regions, "Number of thread regions in size class", thread_regions_total, id),
245        (thread_regions, "Number of thread regions in size class", thread_regions_bytes_total, normalize_by_size_class),
246        (clear_eager_total, "Number of thread regions in size class", clear_eager_total, id),
247        (clear_eager_total, "Number of thread regions in size class", clear_eager_bytes_total, normalize_by_size_class),
248        (clear_slow_total, "Number of thread regions in size class", clear_slow_total, id),
249        (clear_slow_total, "Number of thread regions in size class", clear_slow_bytes_total, normalize_by_size_class)
250    )
251    @file (
252        (file_size, file_size_bytes, "Sum of file sizes in size class"),
253        (allocated_size, file_allocated_size_bytes, "Sum of allocated sizes in size class")
254    )
255}
256
257map_metrics! {
258    mz_metrics_lgalloc
259    @mem (
260        (mapped, vm_mapped_bytes, "Sum of mapped sizes in size class"),
261        (active, vm_active_bytes, "Sum of active sizes in size class"),
262        (dirty, vm_dirty_bytes, "Sum of dirty sizes in size class")
263    )
264}
265
266/// Register a task to read lgalloc stats.
267pub(crate) fn register_metrics_into(metrics_registry: &MetricsRegistry) -> LgMetrics {
268    LgMetrics::new(metrics_registry)
269}
270
271/// Register a task to read mapping-related lgalloc stats.
272pub(crate) fn register_map_metrics_into(metrics_registry: &MetricsRegistry) -> LgMapMetrics {
273    LgMapMetrics::new(metrics_registry)
274}
275
276impl MetricsUpdate for LgMetrics {
277    type Error = Error;
278    const NAME: &'static str = "lgalloc";
279    fn update(&mut self) -> Result<(), Self::Error> {
280        self.update()
281    }
282}
283
284impl MetricsUpdate for LgMapMetrics {
285    type Error = std::io::Error;
286    const NAME: &'static str = "lgalloc_map";
287    fn update(&mut self) -> Result<(), Self::Error> {
288        self.update()
289    }
290}