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, SizeClassStats};
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    /// Number of mapped bytes, if different from `dirty`. Consult `man 7 numa` for details.
52    pub mapped: usize,
53    /// Number of active bytes. Consult `man 7 numa` for details.
54    pub active: usize,
55    /// Number of dirty bytes. Consult `man 7 numa` for details.
56    pub dirty: usize,
57}
58
59impl AddAssign<&FileStats> for FileStatsAccum {
60    fn add_assign(&mut self, rhs: &FileStats) {
61        self.file_size += rhs.file_size;
62        self.allocated_size += rhs.allocated_size;
63        self.mapped += rhs.mapped;
64        self.active += rhs.active;
65        self.dirty += rhs.dirty;
66    }
67}
68
69macro_rules! metrics_size_class {
70    ($namespace:ident
71        @size_class ($(($name:ident, $desc:expr, $metric:expr, $conv:expr)),*)
72        @file ($(($f_name:ident, $f_metric:ident, $f_desc:expr)),*)
73    ) => {
74        metrics_size_class! {
75            @define $namespace
76            @size_class $(($name, $desc, $metric, $conv)),*
77            @file $(($f_name, $f_metric, $f_desc)),*
78        }
79    };
80    (@define $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        paste! {
85            pub(crate) struct LgMetrics {
86                size_class: BTreeMap<usize, LgMetricsSC>,
87                $($metric: raw::UIntGaugeVec,)*
88                $($f_metric: raw::UIntGaugeVec,)*
89            }
90            struct LgMetricsSC {
91                $($metric: GenericGauge<AtomicU64>,)*
92                $($f_metric: GenericGauge<AtomicU64>,)*
93            }
94            impl LgMetrics {
95                fn new(registry: &MetricsRegistry) -> Self {
96                    Self {
97                        size_class: BTreeMap::default(),
98                        $($metric: registry.register(mz_ore::metric!(
99                            name: concat!(stringify!($namespace), "_", stringify!($metric)),
100                            help: $desc,
101                            var_labels: ["size_class"],
102                        )),)*
103                        $($f_metric: registry.register(mz_ore::metric!(
104                            name: concat!(stringify!($namespace), "_", stringify!($f_metric)),
105                            help: $f_desc,
106                            var_labels: ["size_class"],
107                        )),)*
108                    }
109                }
110                fn get_size_class(&mut self, size_class: usize) -> &LgMetricsSC {
111                    self.size_class.entry(size_class).or_insert_with(|| {
112                        let labels: &[&str] = &[&size_class.to_string()];
113                        LgMetricsSC {
114                            $($metric: self.$metric.with_label_values(labels),)*
115                            $($f_metric: self.$f_metric.with_label_values(labels),)*
116                        }
117                    })
118                }
119                fn update(&mut self) -> Result<(), Error> {
120                    let stats = lgalloc::lgalloc_stats();
121                    for sc in &stats.size_class {
122                        let sc_stats = self.get_size_class(sc.size_class);
123                        $(sc_stats.$metric.set(($conv)(u64::cast_from(sc.$name), sc));)*
124                    }
125                    let mut accums = BTreeMap::new();
126                    match &stats.file_stats {
127                        Ok(file_stats) => {
128                            for file_stat in file_stats {
129                                let accum: &mut FileStatsAccum = accums.entry(file_stat.size_class).or_default();
130                                accum.add_assign(file_stat);
131                            }
132                        }
133                        #[cfg(target_os = "linux")]
134                        Err(err) => {
135                            return Err(Error::new(ErrorKind::FileStatsFailed(err.to_string())));
136                        }
137                        #[cfg(not(target_os = "linux"))]
138                        Err(err) => {
139                            if err.kind() != std::io::ErrorKind::NotFound {
140                                return Err(Error::new(ErrorKind::FileStatsFailed(err.to_string())));
141                            }
142                        }
143                    }
144                    for (size_class, accum) in accums {
145                        let sc_stats = self.get_size_class(size_class);
146                        $(sc_stats.$f_metric.set(u64::cast_from(accum.$f_name));)*
147                    }
148                    Ok(())
149                }
150            }
151        }
152    };
153}
154
155fn normalize_by_size_class(value: u64, stats: &SizeClassStats) -> u64 {
156    value * u64::cast_from(stats.size_class)
157}
158
159fn id(value: u64, _stats: &SizeClassStats) -> u64 {
160    value
161}
162
163metrics_size_class! {
164    mz_metrics_lgalloc
165    @size_class (
166        (allocations, "Number of region allocations in size class", allocations_total, id),
167        (area_total_bytes, "Number of bytes in all areas in size class", area_total_bytes, id),
168        (areas, "Number of areas backing size class", areas_total, id),
169        (clean_regions, "Number of clean regions in size class", clean_regions_total, id),
170        (clean_regions, "Number of clean regions in size class", clean_regions_bytes_total, normalize_by_size_class),
171        (deallocations, "Number of region deallocations for size class", deallocations_total, id),
172        (free_regions, "Number of free regions in size class", free_regions_total, id),
173        (free_regions, "Number of free regions in size class", free_regions_bytes_total, normalize_by_size_class),
174        (global_regions, "Number of global regions in size class", global_regions_total, id),
175        (global_regions, "Number of global regions in size class", global_regions_bytes_total, normalize_by_size_class),
176        (refill, "Number of area refills for size class", refill_total, id),
177        (slow_path, "Number of slow path region allocations for size class", slow_path_total, id),
178        (thread_regions, "Number of thread regions in size class", thread_regions_total, id),
179        (thread_regions, "Number of thread regions in size class", thread_regions_bytes_total, normalize_by_size_class),
180        (clear_eager_total, "Number of thread regions in size class", clear_eager_total, id),
181        (clear_eager_total, "Number of thread regions in size class", clear_eager_bytes_total, normalize_by_size_class),
182        (clear_slow_total, "Number of thread regions in size class", clear_slow_total, id),
183        (clear_slow_total, "Number of thread regions in size class", clear_slow_bytes_total, normalize_by_size_class)
184    )
185    @file (
186        (file_size, file_size_bytes, "Sum of file sizes in size class"),
187        (allocated_size, file_allocated_size_bytes, "Sum of allocated sizes in size class"),
188        (mapped, vm_mapped_bytes, "Sum of mapped sizes in size class"),
189        (active, vm_active_bytes, "Sum of active sizes in size class"),
190        (dirty, vm_dirty_bytes, "Sum of dirty sizes in size class")
191    )
192}
193
194/// Register a task to read lgalloc stats.
195pub(crate) fn register_metrics_into(metrics_registry: &MetricsRegistry) -> LgMetrics {
196    LgMetrics::new(metrics_registry)
197}
198
199impl MetricsUpdate for LgMetrics {
200    type Error = Error;
201    const NAME: &'static str = "lgalloc";
202    fn update(&mut self) -> Result<(), Self::Error> {
203        self.update()
204    }
205}