Skip to main content

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