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