use std::sync::OnceLock;
use libc::pid_t;
use measured::{
MetricGroup,
metric::{
MetricEncoding,
gauge::{GaugeState, write_gauge},
group::Encoding,
},
};
pub struct ProcessCollector {
pid: pid_t,
start_time: Option<i64>,
}
impl ProcessCollector {
pub fn new(pid: pid_t) -> ProcessCollector {
let mut start_time = None;
#[cfg(target_os = "linux")]
if let Ok(boot_time) = procfs::boot_time_secs() {
if let Ok(stat) = procfs::process::Process::myself().and_then(|p| p.stat()) {
start_time = Some(stat.starttime as i64 / clk_tck() + boot_time as i64);
}
}
ProcessCollector { pid, start_time }
}
pub fn for_self() -> ProcessCollector {
let pid = unsafe { libc::getpid() };
ProcessCollector::new(pid)
}
}
impl<Enc: Encoding> MetricGroup<Enc> for ProcessCollector
where
GaugeState: MetricEncoding<Enc>,
{
fn collect_group_into(&self, enc: &mut Enc) -> Result<(), Enc::Err> {
#[cfg(target_os = "linux")]
{
use measured::label::NoLabels;
use measured::metric::name::MetricName;
let Ok(p) = procfs::process::Process::new(self.pid) else {
return Ok(());
};
if let Ok(fd_count) = p.fd_count() {
let fd = MetricName::from_str("open_fds");
enc.write_help(fd, "Number of open file descriptors.")?;
write_gauge(enc, fd, NoLabels, fd_count as i64)?;
}
if let Ok(limits) = p.limits() {
if let procfs::process::LimitValue::Value(max) = limits.max_open_files.soft_limit {
let fd = MetricName::from_str("max_fds");
enc.write_help(fd, "Maximum number of open file descriptors.")?;
write_gauge(enc, fd, NoLabels, max as i64)?;
}
}
if let Ok(stat) = p.stat() {
let vmm = MetricName::from_str("virtual_memory_bytes");
enc.write_help(vmm, "Virtual memory size in bytes.")?;
write_gauge(enc, vmm, NoLabels, stat.vsize as i64)?;
let rss = MetricName::from_str("resident_memory_bytes");
enc.write_help(rss, "Resident memory size in bytes.")?;
write_gauge(enc, rss, NoLabels, (stat.rss as i64) * pagesize())?;
let cpu = MetricName::from_str("cpu_seconds_total");
enc.write_help(cpu, "Total user and system CPU time spent in seconds.")?;
write_gauge(
enc,
cpu,
NoLabels,
(stat.utime + stat.stime) as i64 / clk_tck(),
)?;
let threads = MetricName::from_str("threads");
enc.write_help(threads, "Number of OS threads in the process.")?;
write_gauge(enc, threads, NoLabels, stat.num_threads)?;
}
if let Some(start_time) = self.start_time {
let name = MetricName::from_str("start_time_seconds");
enc.write_help(
name,
"Start time of the process since unix epoch in seconds.",
)?;
write_gauge(enc, name, NoLabels, start_time)?;
}
}
Ok(())
}
}
fn clk_tck() -> i64 {
static CLK_TCK: OnceLock<i64> = OnceLock::new();
*CLK_TCK.get_or_init(|| unsafe { libc::sysconf(libc::_SC_CLK_TCK) } as i64)
}
fn pagesize() -> i64 {
static PAGESIZE: OnceLock<i64> = OnceLock::new();
*PAGESIZE.get_or_init(|| unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as i64)
}