1use std::sync::OnceLock;
29
30use libc::pid_t;
31use measured::{
32 label::NoLabels,
33 metric::{
34 gauge::{write_gauge, GaugeState},
35 group::Encoding,
36 name::MetricName,
37 MetricEncoding,
38 },
39 MetricGroup,
40};
41
42pub struct ProcessCollector {
50 pid: pid_t,
51 start_time: Option<i64>,
52}
53
54impl ProcessCollector {
55 pub fn new(pid: pid_t) -> ProcessCollector {
57 let mut start_time = None;
59 #[cfg(target_os = "linux")]
60 if let Ok(boot_time) = procfs::boot_time_secs() {
61 if let Ok(stat) = procfs::process::Process::myself().and_then(|p| p.stat()) {
62 start_time = Some(stat.starttime as i64 / clk_tck() + boot_time as i64);
63 }
64 }
65
66 ProcessCollector { pid, start_time }
67 }
68
69 pub fn for_self() -> ProcessCollector {
71 let pid = unsafe { libc::getpid() };
72 ProcessCollector::new(pid)
73 }
74}
75
76impl<Enc: Encoding> MetricGroup<Enc> for ProcessCollector
77where
78 GaugeState: MetricEncoding<Enc>,
79{
80 fn collect_group_into(&self, enc: &mut Enc) -> Result<(), Enc::Err> {
81 #[cfg(target_os = "linux")]
82 {
83 let Ok(p) = procfs::process::Process::new(self.pid) else {
84 return Ok(());
86 };
87
88 if let Ok(fd_count) = p.fd_count() {
90 let fd = MetricName::from_str("open_fds");
91 enc.write_help(fd, "Number of open file descriptors.")?;
92 write_gauge(enc, fd, NoLabels, fd_count as i64)?;
93 }
94 if let Ok(limits) = p.limits() {
95 if let procfs::process::LimitValue::Value(max) = limits.max_open_files.soft_limit {
96 let fd = MetricName::from_str("max_fds");
97 enc.write_help(fd, "Maximum number of open file descriptors.")?;
98 write_gauge(enc, fd, NoLabels, max as i64)?;
99 }
100 }
101
102 if let Ok(stat) = p.stat() {
103 let vmm = MetricName::from_str("virtual_memory_bytes");
105 enc.write_help(vmm, "Virtual memory size in bytes.")?;
106 write_gauge(enc, vmm, NoLabels, stat.vsize as i64)?;
107
108 let rss = MetricName::from_str("resident_memory_bytes");
109 enc.write_help(rss, "Resident memory size in bytes.")?;
110 write_gauge(enc, rss, NoLabels, (stat.rss as i64) * pagesize())?;
111
112 let cpu = MetricName::from_str("cpu_seconds_total");
114 enc.write_help(cpu, "Total user and system CPU time spent in seconds.")?;
115 write_gauge(
116 enc,
117 cpu,
118 NoLabels,
119 (stat.utime + stat.stime) as i64 / clk_tck(),
120 )?;
121
122 let threads = MetricName::from_str("threads");
124 enc.write_help(threads, "Number of OS threads in the process.")?;
125 write_gauge(enc, threads, NoLabels, stat.num_threads)?;
126 }
127
128 if let Some(start_time) = self.start_time {
129 let name = MetricName::from_str("start_time_seconds");
130 enc.write_help(
131 name,
132 "Start time of the process since unix epoch in seconds.",
133 )?;
134 write_gauge(enc, name, NoLabels, start_time)?;
135 }
136 }
137
138 Ok(())
139 }
140}
141
142fn clk_tck() -> i64 {
143 static CLK_TCK: OnceLock<i64> = OnceLock::new();
144 *CLK_TCK.get_or_init(|| unsafe { libc::sysconf(libc::_SC_CLK_TCK) } as i64)
145}
146
147fn pagesize() -> i64 {
148 static PAGESIZE: OnceLock<i64> = OnceLock::new();
149 *PAGESIZE.get_or_init(|| unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as i64)
150}