rtrtr/metrics.rs
1//! Maintaining and outputting metrics.
2//!
3//! Metrics are operational data maintained by components that allow users to
4//! understand what their instance of RTRTR is doing. Because they are updated
5//! by components and printed by other components in different threads,
6//! management is a bit tricky.
7//!
8//! Typically, all metrics of a component are kept in a single object that is
9//! shared between that component and everything that could possibly output
10//! metrics. We use atomic data types (such as `std::sync::atomic::AtomicU32`)
11//! the keep and allow updating the actual values and keep the value behind an
12//! arc for easy sharing.
13//!
14//! When a component is started, it registers its metrics object with a
15//! metrics [`Collection`] it receives via its
16//! [`Component`][crate::manager::Component].
17//!
18//! The object needs to implement the [`Source`] trait by appending all its
19//! data to a [`Target`]. To make that task easier, the [`Metric`] type is
20//! used to define all the properties of an individual metric. Values of this
21//! type can be created as constants.
22
23use std::fmt;
24use std::sync::{Arc, Mutex, Weak};
25use std::fmt::Write;
26use arc_swap::ArcSwap;
27use clap::{crate_name, crate_version};
28
29
30//------------ Module Configuration ------------------------------------------
31
32/// The application prefix to use in the names of Prometheus metrics.
33const PROMETHEUS_PREFIX: &str = "rtrtr";
34
35
36//------------ Collection ----------------------------------------------------
37
38/// A collection of metrics sources.
39///
40/// This type provides a shared collection. I.e., if a value is cloned, both
41/// clones will reference the same collection. Both will see newly
42/// added sources.
43///
44/// Such new sources can be registered with the [`register`][Self::register]
45/// method. A string with all the current values of all known sources can be
46/// obtained via the [`assemble`][Self::assemble] method.
47#[derive(Clone, Default)]
48pub struct Collection {
49 /// The currently registered sources.
50 sources: Arc<ArcSwap<Vec<RegisteredSource>>>,
51
52 /// A mutex to be held during registration of a new source.
53 ///
54 /// Updating `sources` is done by taking the existing sources,
55 /// construct a new vec, and then swapping that vec into the arc. Because
56 /// of this, updates cannot be done concurrently. The mutex guarantees
57 /// that.
58 register: Arc<Mutex<()>>,
59}
60
61impl Collection {
62 /// Registers a new source with the collection.
63 ///
64 /// The name of the component registering the source is passed via `name`.
65 /// The source itself is given as a weak pointer so that it gets dropped
66 /// when the owning component terminates.
67 pub fn register(&self, name: Arc<str>, source: Weak<dyn Source>) {
68 let lock = self.register.lock().unwrap();
69 let old_sources = self.sources.load();
70 let mut new_sources = Vec::new();
71 for item in old_sources.iter() {
72 if item.source.strong_count() > 0 {
73 new_sources.push(item.clone())
74 }
75 }
76 new_sources.push(
77 RegisteredSource { name, source }
78 );
79 new_sources.sort_by(|l, r| l.name.as_ref().cmp(r.name.as_ref()));
80 self.sources.store(new_sources.into());
81 drop(lock);
82 }
83
84 /// Assembles metrics output.
85 ///
86 /// Produces an output of all the sources in the collection in the given
87 /// format and returns it as a string.
88 pub fn assemble(&self, format: OutputFormat) -> String {
89 let sources = self.sources.load();
90 let mut target = Target::new(format);
91 for item in sources.iter() {
92 if let Some(source) = item.source.upgrade() {
93 source.append(&item.name, &mut target)
94 }
95 }
96 target.into_string()
97 }
98}
99
100
101impl fmt::Debug for Collection {
102 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
103 let len = self.sources.load().len();
104 write!(f, "Collection({len} sources)")
105 }
106}
107
108
109//------------ RegisteredSource ----------------------------------------------
110
111/// All information on a source registered with a collection.
112#[derive(Clone)]
113struct RegisteredSource {
114 /// The name of the component owning the source.
115 name: Arc<str>,
116
117 /// A weak pointer to the source.
118 source: Weak<dyn Source>,
119}
120
121
122//------------ Source --------------------------------------------------------
123
124/// A type producing some metrics.
125///
126/// All this type needs to be able to do is output its metrics.
127pub trait Source: Send + Sync {
128 /// Appends the metrics to the target.
129 ///
130 /// The unit name is provided so a source doesn’t need to keep it around.
131 fn append(&self, unit_name: &str, target: &mut Target);
132}
133
134impl<T: Source> Source for Arc<T> {
135 fn append(&self, unit_name: &str, target: &mut Target) {
136 AsRef::<T>::as_ref(self).append(unit_name, target)
137 }
138}
139
140
141//------------ Target --------------------------------------------------------
142
143/// A target for outputting metrics.
144///
145/// A new target can be created via [`new`](Self::new), passing in the
146/// requested output format. Individual metrics are appended to the target
147/// via [`append`](Self::append) or the shortcut
148/// [`append_simple`](Self::append_simple). Finally, when all metrics are
149/// assembled, you can turn the target into a string of the output via
150/// [`into_string`](Self::into_string).
151#[derive(Clone, Debug)]
152pub struct Target {
153 /// The format of the assembled output.
154 format: OutputFormat,
155
156 /// The output assembled so far.
157 target: String,
158}
159
160impl Target {
161 /// Creates a new target.
162 ///
163 /// The target will produce output in the given format.
164 pub fn new(format: OutputFormat) -> Self {
165 let mut target = String::new();
166 if matches!(format, OutputFormat::Plain) {
167 target.push_str(
168 concat!(
169 "version: ", crate_name!(), "/", crate_version!(), "\n"
170 )
171 );
172 }
173 Target { format, target }
174 }
175
176 /// Converts the target into a string with the assembled output.
177 pub fn into_string(self) -> String {
178 self.target
179 }
180
181 /// Appends metrics to the target.
182 ///
183 /// The method can append multiple metrics values at once via the closure.
184 /// All values are, however, for the same metrics described by `metric`.
185 /// If the values are for a specific component, it’s name is given via
186 /// `unit_name`. If they are global, this can be left at `None`.
187 pub fn append<F: FnOnce(&mut Records)>(
188 &mut self,
189 metric: &Metric,
190 unit_name: Option<&str>,
191 values: F,
192 ) {
193 if !self.format.supports_type(metric.metric_type) {
194 return
195 }
196
197 if matches!(self.format, OutputFormat::Prometheus) {
198 self.target.push_str("# HELP ");
199 self.append_metric_name(metric, unit_name);
200 self.target.push(' ');
201 self.target.push_str(metric.help);
202 self.target.push('\n');
203
204 self.target.push_str("# TYPE ");
205 self.append_metric_name(metric, unit_name);
206 writeln!(&mut self.target, " {}", metric.metric_type).unwrap();
207 }
208 values(&mut Records { target: self, metric, unit_name })
209 }
210
211 /// Append a single metric value to the target.
212 ///
213 /// This is a shortcut version of [`append`](Self::append) when there is
214 /// only a single value to be append for a metric. The metric is described
215 /// by `metric`. If the value is for a specific component, it’s name is
216 /// given via `unit_name`. If they are global, this can be left at `None`.
217 pub fn append_simple(
218 &mut self,
219 metric: &Metric,
220 unit_name: Option<&str>,
221 value: impl fmt::Display,
222 ) {
223 self.append(metric, unit_name, |records| {
224 records.value(value)
225 })
226 }
227
228 /// Constructs and appends the name of the given metric.
229 fn append_metric_name(
230 &mut self, metric: &Metric, unit_name: Option<&str>
231 ) {
232 match self.format {
233 OutputFormat::Prometheus => {
234 write!(&mut self.target,
235 "{}_{}_{}",
236 PROMETHEUS_PREFIX, metric.name, metric.unit
237 ).unwrap();
238 }
239 OutputFormat::Plain => {
240 match unit_name {
241 Some(unit) => {
242 write!(&mut self.target,
243 "{} {}", unit, metric.name
244 ).unwrap();
245 }
246 None => {
247 write!(&mut self.target,
248 "{}", metric.name
249 ).unwrap();
250 }
251 }
252 }
253 }
254 }
255}
256
257
258//------------ Records -------------------------------------------------------
259
260/// Allows adding all values for an individual metric.
261///
262/// Values can either be simple, in which case they only consist of a value
263/// and are appended via [`value`](Self::value), or they can be labelled, in
264/// which case there are multiple values for a metric that are distinguished
265/// via a set of labels. Such values are appended via
266/// [`label_value`](Self::label_value).
267pub struct Records<'a> {
268 /// A reference to the target.
269 target: &'a mut Target,
270
271 /// A reference to the properties of the metric in question.
272 metric: &'a Metric,
273
274 /// An reference to the name of the component if any.
275 unit_name: Option<&'a str>,
276}
277
278impl Records<'_> {
279 /// Appends a simple value to the metrics target.
280 ///
281 /// The value is simply output via the `Display` trait.
282 pub fn value(&mut self, value: impl fmt::Display) {
283 match self.target.format {
284 OutputFormat::Prometheus => {
285 self.target.append_metric_name(
286 self.metric, self.unit_name
287 );
288 if let Some(unit_name) = self.unit_name {
289 write!(&mut self.target.target,
290 "{{component=\"{unit_name}\"}}"
291 ).unwrap();
292 }
293 writeln!(&mut self.target.target, " {value}").unwrap()
294 }
295 OutputFormat::Plain => {
296 self.target.append_metric_name(self.metric, self.unit_name);
297 writeln!(&mut self.target.target, ": {value}").unwrap()
298 }
299 }
300 }
301
302 /// Appends a single labelled value to the metrics target.
303 ///
304 /// The labels are a slice of pairs of strings with the first element the
305 /// name of the label and the second the label value. The metrics value
306 /// is simply printed via the `Display` trait.
307 pub fn label_value(
308 &mut self,
309 labels: &[(&str, &str)],
310 value: impl fmt::Display
311 ) {
312 match self.target.format {
313 OutputFormat::Prometheus => {
314 self.target.append_metric_name(self.metric, self.unit_name);
315 self.target.target.push('{');
316 let mut comma = false;
317 if let Some(unit_name) = self.unit_name {
318 write!(&mut self.target.target,
319 "component=\"{unit_name}\""
320 ).unwrap();
321 comma = true;
322 }
323 for (name, value) in labels {
324 if comma {
325 write!(&mut self.target.target,
326 ", {name}=\"{value}\""
327 ).unwrap();
328 }
329 else {
330 write!(&mut self.target.target,
331 "{name}=\"{value}\""
332 ).unwrap();
333 comma = true;
334 }
335 }
336 writeln!(&mut self.target.target, "}} {value}").unwrap()
337 }
338 OutputFormat::Plain => {
339 self.target.append_metric_name(self.metric, self.unit_name);
340 for (name, value) in labels {
341 write!(&mut self.target.target,
342 " {name}={value}"
343 ).unwrap();
344 }
345 writeln!(&mut self.target.target, ": {value}").unwrap()
346 }
347 }
348 }
349}
350
351
352//------------ OutputFormat --------------------------------------------------
353
354/// The output format for metrics.
355///
356/// This is a non-exhaustive enum so that we can add additional metrics
357/// without having to do breaking releases. Output for unknown formats should
358/// be empty.
359#[non_exhaustive]
360#[derive(Clone, Copy, Debug)]
361pub enum OutputFormat {
362 /// Prometheus’ text-base exposition format.
363 ///
364 /// See <https://prometheus.io/docs/instrumenting/exposition_formats/>
365 /// for details.
366 Prometheus,
367
368 /// Simple, human-readable plain-text output.
369 Plain
370}
371
372impl OutputFormat {
373 /// Returns whether the format supports non-numerical metrics.
374 #[allow(clippy::match_like_matches_macro)]
375 pub fn allows_text(self) -> bool {
376 match self {
377 OutputFormat::Prometheus => false,
378 OutputFormat::Plain => true,
379 }
380 }
381
382 /// Returns whether this output format supports this metric type.
383 #[allow(clippy::match_like_matches_macro)]
384 pub fn supports_type(self, metric: MetricType) -> bool {
385 match (self, metric) {
386 (OutputFormat::Prometheus, MetricType::Text) => false,
387 _ => true
388 }
389 }
390}
391
392
393//------------ Metric --------------------------------------------------------
394
395/// The properties of a metric.
396pub struct Metric {
397 /// The name of the metric.
398 ///
399 /// The final name written to the target will be composed of more than
400 /// just this name according to the rules stipulated by the output format.
401 pub name: &'static str,
402
403 /// The help text for the metric.
404 pub help: &'static str,
405
406 /// The type of the metric.
407 pub metric_type: MetricType,
408
409 /// The unit of the metric.
410 pub unit: MetricUnit,
411}
412
413impl Metric {
414 /// Constructs a new metric from all values.
415 ///
416 /// This is a const function and can be used to construct associated
417 /// constants.
418 pub const fn new(
419 name: &'static str, help: &'static str,
420 metric_type: MetricType, unit: MetricUnit
421 ) -> Self {
422 Metric { name, help, metric_type, unit
423 }
424 }
425}
426
427
428//------------ MetricType ----------------------------------------------------
429
430/// The type of a metric.
431#[derive(Clone, Copy, Debug)]
432pub enum MetricType {
433 /// A monotonically increasing counter.
434 ///
435 /// Values can only increase or be reset to zero.
436 Counter,
437
438 /// A value that can go up and down.
439 Gauge,
440
441 /// A Prometheus-style histogram.
442 Histogram,
443
444 /// A Prometheus-style summary.
445 Summary,
446
447 /// A text metric.
448 ///
449 /// Metrics of this type are only output to output formats that allow
450 /// text metrics.
451 Text,
452}
453
454impl fmt::Display for MetricType {
455 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
456 match *self {
457 MetricType::Counter => f.write_str("counter"),
458 MetricType::Gauge => f.write_str("gauge"),
459 MetricType::Histogram => f.write_str("histogram"),
460 MetricType::Summary => f.write_str("summary"),
461 MetricType::Text => f.write_str("text"),
462 }
463 }
464}
465
466
467//------------ MetricUnit ----------------------------------------------------
468
469/// A unit of measure for a metric.
470///
471/// This determines what a value of 1 means.
472#[derive(Clone, Copy, Debug)]
473pub enum MetricUnit {
474 Second,
475 Celsius,
476 Meter,
477 Byte,
478 Ratio,
479 Volt,
480 Ampere,
481 Joule,
482 Gram,
483
484 /// Use this for counting things.
485 Total,
486
487 /// Use this for non-numerical metrics.
488 Info,
489}
490
491impl fmt::Display for MetricUnit {
492 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
493 match *self {
494 MetricUnit::Second => f.write_str("seconds"),
495 MetricUnit::Celsius => f.write_str("celsius"),
496 MetricUnit::Meter => f.write_str("meters"),
497 MetricUnit::Byte => f.write_str("bytes"),
498 MetricUnit::Ratio => f.write_str("ratio"),
499 MetricUnit::Volt => f.write_str("volts"),
500 MetricUnit::Ampere => f.write_str("amperes"),
501 MetricUnit::Joule => f.write_str("joules"),
502 MetricUnit::Gram => f.write_str("grams"),
503 MetricUnit::Total => f.write_str("total"),
504 MetricUnit::Info => f.write_str("info"),
505 }
506 }
507}
508