kubert_prometheus_process/
lib.rs

1//! Process metrics for Prometheus.
2//!
3//! This crate registers a collector that provides the standard set of [Process
4//! metrics][pm].
5//!
6//! ```
7//! let mut prom = prometheus_client::registry::Registry::default();
8//! if let Err(error) =
9//!     kubert_prometheus_process::register(prom.sub_registry_with_prefix("process"))
10//! {
11//!     tracing::warn!(%error, "Failed to register process metrics");
12//! }
13//! ```
14//!
15//! [pm]: https://prometheus.io/docs/instrumenting/writing_clientlibs/#process-metrics
16//
17// Based on linkerd2-proxy.
18//
19// Copyright The Linkerd Authors
20//
21// Licensed under the Apache License, Version 2.0 (the "License");
22// you may not use this file except in compliance with the License.
23// You may obtain a copy of the License at
24//
25//     http://www.apache.org/licenses/LICENSE-2.0
26//
27// Unless required by applicable law or agreed to in writing, software
28// distributed under the License is distributed on an "AS IS" BASIS,
29// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
30// See the License for the specific language governing permissions and
31// limitations under the License.
32
33#![deny(
34    rust_2018_idioms,
35    clippy::disallowed_methods,
36    unsafe_code,
37    missing_docs
38)]
39#![cfg_attr(docsrs, feature(doc_cfg))]
40
41use prometheus_client::{
42    collector::Collector,
43    encoding::{DescriptorEncoder, EncodeMetric},
44    metrics::{
45        counter::ConstCounter,
46        gauge::{self, ConstGauge, Gauge},
47        MetricType,
48    },
49    registry::{Registry, Unit},
50};
51use std::time::{Instant, SystemTime, UNIX_EPOCH};
52
53#[cfg(target_os = "linux")]
54mod linux;
55
56/// Registers process metrics with the given registry. Note that the 'process_'
57/// prefix is NOT added and should be specified by the caller if desired.
58pub fn register(reg: &mut Registry) -> std::io::Result<()> {
59    let start_time = Instant::now();
60    let start_time_from_epoch = SystemTime::now()
61        .duration_since(UNIX_EPOCH)
62        .expect("process start time");
63
64    #[cfg(target_os = "linux")]
65    let system = linux::System::load()?;
66
67    reg.register_with_unit(
68        "start_time",
69        "Time that the process started (in seconds since the UNIX epoch)",
70        Unit::Seconds,
71        ConstGauge::new(start_time_from_epoch.as_secs_f64()),
72    );
73
74    let clock_time_ts = Gauge::<f64, ClockMetric>::default();
75    reg.register_with_unit(
76        "clock_time",
77        "Current system time for this process",
78        Unit::Seconds,
79        clock_time_ts,
80    );
81
82    reg.register_collector(Box::new(ProcessCollector {
83        start_time,
84        #[cfg(target_os = "linux")]
85        system,
86    }));
87
88    Ok(())
89}
90
91#[derive(Debug)]
92struct ProcessCollector {
93    start_time: Instant,
94    #[cfg(target_os = "linux")]
95    system: linux::System,
96}
97
98impl Collector for ProcessCollector {
99    fn encode(&self, mut encoder: DescriptorEncoder<'_>) -> std::fmt::Result {
100        let uptime = ConstCounter::new(
101            Instant::now()
102                .saturating_duration_since(self.start_time)
103                .as_secs_f64(),
104        );
105        let ue = encoder.encode_descriptor(
106            "uptime",
107            "Total time since the process started (in seconds)",
108            Some(&Unit::Seconds),
109            MetricType::Counter,
110        )?;
111        uptime.encode(ue)?;
112
113        #[cfg(target_os = "linux")]
114        self.system.encode(encoder)?;
115
116        Ok(())
117    }
118}
119
120// Metric that always reports the current system time on a call to [`get`].
121#[derive(Copy, Clone, Debug, Default)]
122struct ClockMetric;
123
124impl gauge::Atomic<f64> for ClockMetric {
125    fn inc(&self) -> f64 {
126        self.get()
127    }
128
129    fn inc_by(&self, _v: f64) -> f64 {
130        self.get()
131    }
132
133    fn dec(&self) -> f64 {
134        self.get()
135    }
136
137    fn dec_by(&self, _v: f64) -> f64 {
138        self.get()
139    }
140
141    fn set(&self, _v: f64) -> f64 {
142        self.get()
143    }
144
145    fn get(&self) -> f64 {
146        match SystemTime::now().duration_since(UNIX_EPOCH) {
147            Ok(elapsed) => elapsed.as_secs_f64().floor(),
148            Err(e) => {
149                tracing::warn!(
150                    "System time is before the UNIX epoch; reporting negative timestamp"
151                );
152                -e.duration().as_secs_f64().floor()
153            }
154        }
155    }
156}