1#![warn(missing_docs, missing_debug_implementations)]
19
20use std::path::PathBuf;
21use std::time::Duration;
22
23use mz_dyncfg::{ConfigSet, ConfigUpdates};
24use mz_ore::metrics::MetricsRegistry;
25use tokio::time::Interval;
26
27pub use dyncfgs::all_dyncfgs;
28
29mod dyncfgs;
30pub mod lgalloc;
31pub mod rusage;
32pub mod usage;
33
34#[derive(Debug)]
36pub struct Metrics {
37 config_set: ConfigSet,
38 lgalloc: MetricsTask,
39 lgalloc_map: MetricsTask,
40 rusage: MetricsTask,
41 usage: MetricsTask,
42}
43
44static METRICS: std::sync::Mutex<Option<Metrics>> = std::sync::Mutex::new(None);
45
46#[allow(clippy::unused_async)]
57pub async fn register_metrics_into(
58 metrics_registry: &MetricsRegistry,
59 config_set: ConfigSet,
60 disk_root: Option<PathBuf>,
61) {
62 let update_duration_metric = metrics_registry.register(mz_ore::metric!(
63 name: "mz_metrics_update_duration",
64 help: "The time it took to update lgalloc stats",
65 var_labels: ["name"],
66 buckets: mz_ore::stats::histogram_seconds_buckets(0.000_500, 32.),
67 ));
68
69 let lgalloc = Metrics::new_metrics_task(
70 metrics_registry,
71 lgalloc::register_metrics_into,
72 dyncfgs::MZ_METRICS_LGALLOC_REFRESH_INTERVAL,
73 &update_duration_metric,
74 );
75 let lgalloc_map = Metrics::new_metrics_task(
76 metrics_registry,
77 lgalloc::register_map_metrics_into,
78 dyncfgs::MZ_METRICS_LGALLOC_MAP_REFRESH_INTERVAL,
79 &update_duration_metric,
80 );
81 let rusage = Metrics::new_metrics_task(
82 metrics_registry,
83 rusage::register_metrics_into,
84 dyncfgs::MZ_METRICS_RUSAGE_REFRESH_INTERVAL,
85 &update_duration_metric,
86 );
87
88 let usage = Metrics::new_metrics_task(
89 metrics_registry,
90 |registry| usage::register_metrics_into(registry, disk_root),
91 dyncfgs::MZ_METRICS_USAGE_REFRESH_INTERVAL,
92 &update_duration_metric,
93 );
94
95 *METRICS.lock().expect("lock poisoned") = Some(Metrics {
96 lgalloc,
97 lgalloc_map,
98 rusage,
99 usage,
100 config_set,
101 });
102}
103
104pub fn describe_metrics() -> Vec<(String, String, Vec<String>, &'static str)> {
114 let registry = MetricsRegistry::new();
115
116 let tag = |descs: Vec<(String, String, Vec<String>)>, src: &'static str| {
117 descs
118 .into_iter()
119 .map(move |(name, help, labels)| (name, help, labels, src))
120 };
121
122 let mut out = Vec::new();
123 out.extend(tag(
124 lgalloc::register_metrics_into(®istry).descs(),
125 lgalloc::SOURCE,
126 ));
127 out.extend(tag(
128 lgalloc::register_map_metrics_into(®istry).descs(),
129 lgalloc::SOURCE,
130 ));
131 out.extend(tag(
132 rusage::register_metrics_into(®istry).descs(),
133 rusage::SOURCE,
134 ));
135 out
136}
137
138pub(crate) fn desc_labels(desc: &prometheus::core::Desc) -> Vec<String> {
140 let mut labels: Vec<String> = desc
141 .variable_labels
142 .iter()
143 .cloned()
144 .chain(desc.const_label_pairs.iter().map(|p| p.name().to_owned()))
145 .collect();
146 labels.sort();
147 labels.dedup();
148 labels
149}
150
151pub fn update_dyncfg(config_updates: &ConfigUpdates) {
153 if let Some(metrics) = METRICS.lock().expect("lock poisoned").as_mut() {
154 metrics.apply_dyncfg_updates(config_updates);
155 }
156}
157
158impl Metrics {
159 pub fn apply_dyncfg_updates(&mut self, config_updates: &ConfigUpdates) {
161 config_updates.apply(&self.config_set);
163 self.lgalloc.update_dyncfg(&self.config_set);
165 self.lgalloc_map.update_dyncfg(&self.config_set);
166 self.rusage.update_dyncfg(&self.config_set);
167 self.usage.update_dyncfg(&self.config_set);
168 }
169
170 fn new_metrics_task<T: MetricsUpdate>(
171 metrics_registry: &MetricsRegistry,
172 constructor: impl FnOnce(&MetricsRegistry) -> T,
173 interval_config: mz_dyncfg::Config<Duration>,
174 update_duration_metric: &mz_ore::metrics::HistogramVec,
175 ) -> MetricsTask {
176 let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
177
178 let mut interval: Option<Interval> = None;
180
181 let update_duration_metric =
182 update_duration_metric.get_delete_on_drop_metric(&[T::NAME][..]);
183
184 let mut metrics = constructor(metrics_registry);
185
186 let mut update_metrics = move || {
187 tracing::debug!(metrics = T::NAME, "updating metrics");
188 let start = std::time::Instant::now();
189 if let Err(err) = metrics.update() {
190 tracing::error!(metrics = T::NAME, ?err, "metrics update failed");
191 }
192 let elapsed = start.elapsed();
193 update_duration_metric.observe(elapsed.as_secs_f64());
194 };
195
196 let update_interval = |new_interval, interval: &mut Option<Interval>| {
197 if new_interval == Duration::ZERO {
199 *interval = None;
200 return;
201 }
202 if Some(new_interval) == interval.as_ref().map(Interval::period) {
204 return;
205 }
206 tracing::debug!(metrics = T::NAME, ?new_interval, "updating interval");
207 let mut new_interval = tokio::time::interval(new_interval);
208 new_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
209 *interval = Some(new_interval);
210 };
211
212 mz_ore::task::spawn(|| format!("mz_metrics_update({})", T::NAME), async move {
213 loop {
214 tokio::select! {
215 _ = async {
216 interval.as_mut().unwrap().tick().await
217 }, if interval.is_some() => {
218 update_metrics()
219 }
220 new_interval = rx.recv() => match new_interval {
221 Some(new_interval) => update_interval(new_interval, &mut interval),
222 None => break,
223 }
224 }
225 }
226 });
227
228 MetricsTask {
229 tx,
230 interval_config,
231 }
232 }
233}
234
235pub trait MetricsUpdate: Send + Sync + 'static {
237 type Error: std::fmt::Debug;
239 const NAME: &'static str;
241 fn update(&mut self) -> Result<(), Self::Error>;
243}
244
245#[derive(Debug)]
246struct MetricsTask {
247 interval_config: mz_dyncfg::Config<Duration>,
248 tx: tokio::sync::mpsc::UnboundedSender<Duration>,
249}
250
251impl MetricsTask {
252 pub(crate) fn update_dyncfg(&self, config_set: &ConfigSet) {
253 self.tx
254 .send(self.interval_config.get(config_set))
255 .expect("Receiver exists");
256 }
257}