1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#[cfg(target_os = "linux")]
use libc;
#[cfg(target_os = "linux")]
use procinfo;
use std::time::SystemTime;
#[cfg(target_os = "linux")]
use std::time::UNIX_EPOCH;
use std::vec;

use metric::Metric;
#[cfg(target_os = "linux")]
use metrics::{CounterBuilder, GaugeBuilder};
use Collect;

#[cfg(target_os = "linux")]
lazy_static! {
    static ref CLK_TCK: f64 = { unsafe { libc::sysconf(libc::_SC_CLK_TCK) as f64 } };
    static ref PAGESIZE: usize = { unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize } };
}

/// Process metrics collector.
///
/// # Notice
///
/// On non Linux platforms, the `collect` method always returns `None`.
///
/// # Reference
///
/// - [process metrics](https://prometheus.io/docs/instrumenting/writing_clientlibs/#process-metrics)
///
/// # Examples
///
/// ```
/// use prometrics::{default_gatherer, default_registry};
/// use prometrics::metrics::ProcessMetricsCollector;
///
/// // Register
/// default_registry().register(ProcessMetricsCollector::new());
///
/// // Gather
/// let _metrics = default_gatherer().lock().unwrap().gather();
/// ```
#[derive(Debug)]
pub struct ProcessMetricsCollector {
    start_time: SystemTime,
}
impl ProcessMetricsCollector {
    /// Makes a new `ProcessMetricsCollector` instance.
    pub fn new() -> Self {
        ProcessMetricsCollector {
            start_time: SystemTime::now(),
        }
    }
}
impl Default for ProcessMetricsCollector {
    fn default() -> Self {
        Self::new()
    }
}
impl Collect for ProcessMetricsCollector {
    type Metrics = vec::IntoIter<Metric>;
    #[cfg(target_os = "linux")]
    fn collect(&mut self) -> Option<Self::Metrics> {
        let mut metrics = Vec::new();

        if let Ok(limits) = procinfo::pid::limits_self() {
            if let Some(fds) = limits.max_open_files.soft {
                metrics.push(gauge("max_fds", fds as f64));
            }
        }
        if let Ok(status) = procinfo::pid::status_self() {
            metrics.push(gauge("open_fds", f64::from(status.fd_allocated)));
        }
        if let Ok(stat) = procinfo::pid::stat_self() {
            metrics.push(counter(
                "cpu_seconds_total",
                (stat.utime + stat.stime) as f64 / *CLK_TCK,
            ));
            if let Ok(start_time) = self.start_time.duration_since(UNIX_EPOCH) {
                metrics.push(gauge("start_time_seconds", start_time.as_secs() as f64));
            }
            metrics.push(gauge("threads_total", f64::from(stat.num_threads)));
            metrics.push(gauge("virtual_memory_bytes", stat.vsize as f64));
            metrics.push(gauge(
                "resident_memory_bytes",
                (stat.rss * *PAGESIZE) as f64,
            ));
        }

        Some(metrics.into_iter())
    }
    #[cfg(not(target_os = "linux"))]
    fn collect(&mut self) -> Option<Self::Metrics> {
        None
    }
}

#[cfg(target_os = "linux")]
fn counter(name: &str, value: f64) -> Metric {
    let counter = CounterBuilder::new(name)
        .namespace("process")
        .finish()
        .expect("Never fails");
    let _ = counter.add(value);
    counter.into()
}

#[cfg(target_os = "linux")]
fn gauge(name: &str, value: f64) -> Metric {
    let gauge = GaugeBuilder::new(name)
        .namespace("process")
        .finish()
        .expect("Never fails");
    gauge.set(value);
    gauge.into()
}