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};
16
17use crate::MetricsUpdate;
18
19pub(crate) const SOURCE: &str = file!();
20
21#[derive(Debug, thiserror::Error)]
23#[error(transparent)]
24pub struct Error {
25 #[from]
27 pub kind: ErrorKind,
28}
29
30#[derive(Debug, thiserror::Error)]
32pub enum ErrorKind {
33 #[error("Failed to get file stats: {0}")]
35 FileStatsFailed(String),
36}
37
38impl Error {
39 pub fn new(kind: ErrorKind) -> Error {
41 Error { kind }
42 }
43}
44
45#[derive(Default)]
47struct FileStatsAccum {
48 pub file_size: usize,
50 pub allocated_size: usize,
52}
53
54#[derive(Default)]
56struct MapStatsAccum {
57 pub mapped: usize,
59 pub active: usize,
61 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 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 #[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 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
295pub(crate) fn register_metrics_into(metrics_registry: &MetricsRegistry) -> LgMetrics {
297 LgMetrics::new(metrics_registry)
298}
299
300pub(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}