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