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