1use 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#[derive(Debug, thiserror::Error)]
22#[error(transparent)]
23pub struct Error {
24 #[from]
26 pub kind: ErrorKind,
27}
28
29#[derive(Debug, thiserror::Error)]
31pub enum ErrorKind {
32 #[error("Failed to get file stats: {0}")]
34 FileStatsFailed(String),
35}
36
37impl Error {
38 pub fn new(kind: ErrorKind) -> Error {
40 Error { kind }
41 }
42}
43
44#[derive(Default)]
46struct FileStatsAccum {
47 pub file_size: usize,
49 pub allocated_size: usize,
51}
52
53#[derive(Default)]
55struct MapStatsAccum {
56 pub mapped: usize,
58 pub active: usize,
60 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 #[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
266pub(crate) fn register_metrics_into(metrics_registry: &MetricsRegistry) -> LgMetrics {
268 LgMetrics::new(metrics_registry)
269}
270
271pub(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}