1#![doc = include_str!("../README.md")]
2#![deny(missing_docs)]
3#![deny(unsafe_code)]
4#![deny(unused_crate_dependencies)]
5#![cfg_attr(docsrs, feature(doc_cfg))]
6
7use std::{process, sync::LazyLock};
8
9use fastmetrics::{
10 error::Result,
11 metrics::{
12 counter::LazyCounter,
13 gauge::{ConstGauge, LazyGauge},
14 lazy_group::LazyGroup,
15 },
16 registry::{Register, Registry, Unit},
17};
18use parking_lot::Mutex;
19use sysinfo::{Pid, ProcessRefreshKind, ProcessesToUpdate, System};
20
21#[derive(Clone)]
28pub struct ProcessMetrics {
29 pid: ConstGauge<i64>,
30 cpu_seconds_total: LazyCounter<f64>,
31 cpu_usage_percent: LazyGauge<f32>,
32 resident_memory_bytes: LazyGauge<i64>,
33 virtual_memory_bytes: LazyGauge<i64>,
34 start_time_seconds: LazyGauge<i64>,
35 run_time_seconds: LazyGauge<i64>,
36 open_fds: LazyGauge<i64>,
37 max_fds: LazyGauge<i64>,
38 threads: LazyGauge<i64>,
39}
40
41static PROCESS_SAMPLER: LazyLock<ProcessSampler> = LazyLock::new(ProcessSampler::new);
42
43impl Default for ProcessMetrics {
44 fn default() -> Self {
45 let group: LazyGroup<ProcessSample> = LazyGroup::new(|| PROCESS_SAMPLER.sample());
46 Self {
47 pid: ConstGauge::new(PROCESS_SAMPLER.pid.as_u32() as i64),
48 cpu_seconds_total: group.counter(|s| s.cpu_seconds_total),
49 cpu_usage_percent: group.gauge(|s| s.cpu_usage_percent),
50 resident_memory_bytes: group.gauge(|s| s.resident_memory_bytes),
51 virtual_memory_bytes: group.gauge(|s| s.virtual_memory_bytes),
52 start_time_seconds: group.gauge(|s| s.start_time_seconds),
53 run_time_seconds: group.gauge(|s| s.run_time_seconds),
54 open_fds: group.gauge(|s| s.open_fds),
55 max_fds: group.gauge(|s| s.max_fds),
56 threads: group.gauge(|s| s.threads),
57 }
58 }
59}
60
61impl Register for ProcessMetrics {
62 fn register(&self, registry: &mut Registry) -> Result<()> {
63 registry.register("pid", "Process ID.", self.pid.clone())?;
64 registry.register(
65 "cpu_seconds_total",
66 "Total user and system CPU time spent in seconds.",
67 self.cpu_seconds_total.clone(),
68 )?;
69 registry.register(
70 "cpu_usage_percent",
71 "CPU usage of the process in percent.",
72 self.cpu_usage_percent.clone(),
73 )?;
74 registry.register_with_unit(
75 "resident_memory",
76 "Resident memory size in bytes.",
77 Unit::Bytes,
78 self.resident_memory_bytes.clone(),
79 )?;
80 registry.register_with_unit(
81 "virtual_memory",
82 "Virtual memory size in bytes.",
83 Unit::Bytes,
84 self.virtual_memory_bytes.clone(),
85 )?;
86 registry.register_with_unit(
87 "start_time",
88 "Start time of the process since unix epoch in seconds.",
89 Unit::Seconds,
90 self.start_time_seconds.clone(),
91 )?;
92 registry.register_with_unit(
93 "run_time",
94 "Process run time in seconds.",
95 Unit::Seconds,
96 self.run_time_seconds.clone(),
97 )?;
98 registry.register("open_fds", "Number of open file descriptors.", self.open_fds.clone())?;
99 registry.register(
100 "max_fds",
101 "Maximum number of open file descriptors.",
102 self.max_fds.clone(),
103 )?;
104 registry.register(
105 "threads",
106 "Number of OS threads in the process.",
107 self.threads.clone(),
108 )?;
109 Ok(())
110 }
111}
112
113#[derive(Clone, Copy, Default)]
114struct ProcessSample {
115 cpu_seconds_total: f64,
116 cpu_usage_percent: f32,
117 resident_memory_bytes: i64,
118 virtual_memory_bytes: i64,
119 start_time_seconds: i64,
120 run_time_seconds: i64,
121 open_fds: i64,
122 max_fds: i64,
123 threads: i64,
124}
125
126struct ProcessSampler {
127 pid: Pid,
128 system: Mutex<System>,
129}
130
131impl ProcessSampler {
132 fn new() -> Self {
133 let pid = Pid::from_u32(process::id());
134 let mut system = System::new();
135
136 sample(&mut system, pid);
137
138 Self { pid, system: Mutex::new(system) }
139 }
140
141 fn sample(&self) -> ProcessSample {
142 let mut system = self.system.lock();
143 sample(&mut system, self.pid)
144 }
145}
146
147fn sample(system: &mut System, pid: Pid) -> ProcessSample {
148 system.refresh_processes_specifics(
149 ProcessesToUpdate::Some(&[pid]),
150 true,
151 ProcessRefreshKind::everything(),
152 );
153
154 let Some(process) = system.process(pid) else {
155 return ProcessSample::default();
156 };
157
158 ProcessSample {
159 cpu_seconds_total: process.accumulated_cpu_time() as f64 / 1_000.0,
160 cpu_usage_percent: process.cpu_usage(),
161 resident_memory_bytes: u64_to_i64_saturating(process.memory()),
162 virtual_memory_bytes: u64_to_i64_saturating(process.virtual_memory()),
163 start_time_seconds: u64_to_i64_saturating(process.start_time()),
164 run_time_seconds: u64_to_i64_saturating(process.run_time()),
165 open_fds: process.open_files().unwrap_or(0) as i64,
166 max_fds: process.open_files_limit().unwrap_or(0) as i64,
167 threads: process.tasks().map(|t| t.len()).unwrap_or(0) as i64,
168 }
169}
170
171#[inline]
172fn u64_to_i64_saturating(v: u64) -> i64 {
173 if v > i64::MAX as u64 { i64::MAX } else { v as i64 }
174}