Skip to main content

mz_metrics/
rusage.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 rusage metrics.
7
8use std::time::Duration;
9
10use mz_ore::cast::CastFrom;
11use mz_ore::metrics::MetricsRegistry;
12use prometheus::{Gauge, IntGauge};
13
14use crate::MetricsUpdate;
15
16pub(crate) const SOURCE: &str = file!();
17
18macro_rules! metrics {
19    ($namespace:ident $(($name:ident, $desc:expr, $suffix:expr, $type:ident)),*) => {
20        metrics! { @define $namespace $(($name, $desc, $suffix, $type)),*}
21    };
22    (@define $namespace:ident $(($name:ident, $desc:expr, $suffix:expr, $type:ident)),*) => {
23        pub(crate) struct RuMetrics {
24            $($name: <$type as Unit>::Gauge,)*
25        }
26        impl RuMetrics {
27            fn new(registry: &MetricsRegistry) -> Self {
28                Self {
29                    $($name: registry.register(mz_ore::metric!(
30                        name: concat!(stringify!($namespace), "_", stringify!($name), $suffix),
31                        help: $desc,
32                    )),)*
33                }
34            }
35            fn update_internal(&self) -> Result<(), std::io::Error> {
36                let rusage = unsafe {
37                    let mut rusage = std::mem::zeroed();
38                    let ret = libc::getrusage(libc::RUSAGE_SELF, &mut rusage);
39                    if ret < 0 {
40                        return Err(std::io::Error::last_os_error());
41                    }
42                    rusage
43                };
44                $(self.$name.set(<$type as Unit>::from(rusage.$name));)*
45                Ok(())
46            }
47            /// Returns the `(name, help)` of every metric defined here.
48            ///
49            /// Used by `mz-metrics-catalog` to document metrics whose names are
50            /// assembled at macro-expansion time and so are invisible to its
51            /// source scraper.
52            pub(crate) fn descs(&self) -> Vec<(String, String)> {
53                use prometheus::core::Collector;
54                let mut descs = Vec::new();
55                $(for d in self.$name.desc() {
56                    descs.push((d.fq_name.clone(), d.help.clone()));
57                })*
58                descs
59            }
60        }
61    };
62}
63
64/// Type for converting values from POSIX to Prometheus.
65trait Unit {
66    /// Prometheus gauge
67    type Gauge;
68    /// Libc type
69    type From;
70    /// Gauge type.
71    type To;
72    /// Convert an actual value.
73    fn from(value: Self::From) -> Self::To;
74}
75
76/// Unit for converting POSIX timeval.
77struct Timeval;
78impl Unit for Timeval {
79    type Gauge = Gauge;
80    type From = libc::timeval;
81    type To = f64;
82    fn from(Self::From { tv_sec, tv_usec }: Self::From) -> Self::To {
83        // timeval can capture negative values; it'd be surprising to see a negative values here.
84        (Duration::from_secs(u64::cast_from(tv_sec.abs_diff(0)))
85            + Duration::from_micros(u64::cast_from(tv_usec.abs_diff(0))))
86        .as_secs_f64()
87    }
88}
89
90/// Unit for direct conversion to i64.
91struct Unitless;
92impl Unit for Unitless {
93    type Gauge = IntGauge;
94    type From = libc::c_long;
95    type To = i64;
96    fn from(value: Self::From) -> Self::To {
97        value
98    }
99}
100
101/// Unit for converting maxrss values to bytes expressed as i64.
102///
103/// The maxrss unit depends on the OS:
104///  * Linux: KiB
105///  * macOS: bytes
106struct MaxrssToBytes;
107impl Unit for MaxrssToBytes {
108    type Gauge = IntGauge;
109    type From = libc::c_long;
110    type To = i64;
111
112    #[cfg(not(target_os = "macos"))]
113    fn from(value: Self::From) -> Self::To {
114        value.saturating_mul(1024)
115    }
116
117    #[cfg(target_os = "macos")]
118    fn from(value: Self::From) -> Self::To {
119        value
120    }
121}
122
123metrics! {
124    mz_metrics_libc
125    (ru_utime, "user CPU time used", "_seconds_total", Timeval),
126    (ru_stime, "system CPU time used", "_seconds_total", Timeval),
127    (ru_maxrss, "maximum resident set size", "_bytes", MaxrssToBytes),
128    (ru_minflt, "page reclaims (soft page faults)", "_total", Unitless),
129    (ru_majflt, "page faults (hard page faults)", "_total", Unitless),
130    (ru_inblock, "block input operations", "_total", Unitless),
131    (ru_oublock, "block output operations", "_total", Unitless),
132    (ru_nvcsw, "voluntary context switches", "_total", Unitless),
133    (ru_nivcsw, "involuntary context switches", "_total", Unitless)
134}
135
136/// Register a task to read rusage stats.
137pub(crate) fn register_metrics_into(metrics_registry: &MetricsRegistry) -> RuMetrics {
138    RuMetrics::new(metrics_registry)
139}
140
141impl MetricsUpdate for RuMetrics {
142    type Error = std::io::Error;
143    const NAME: &'static str = "rusage";
144    fn update(&mut self) -> Result<(), Self::Error> {
145        self.update_internal()
146    }
147}