1use super::*;
16
17macro_rules! fold_optionals {
19 ($left:expr, $right:expr) => {
20 fold_optionals!($left, $right, |l, r| l + r)
21 };
22
23 ($left:expr, $right:expr, $f:expr) => {
24 match ($left, $right) {
25 (Some(l), Some(r)) => Some($f(l, r)),
26 (Some(l), None) => Some(l.clone()),
27 (None, Some(r)) => Some(r.clone()),
28 (None, None) => None,
29 }
30 };
31}
32
33#[::below_derive::queriable_derives]
34pub struct ProcessModel {
35 #[queriable(subquery)]
36 pub processes: BTreeMap<i32, SingleProcessModel>,
37}
38
39impl ProcessModel {
40 pub fn new(sample: &procfs::PidMap, last: Option<(&procfs::PidMap, Duration)>) -> ProcessModel {
41 let mut processes: BTreeMap<i32, SingleProcessModel> = BTreeMap::new();
42
43 for (pid, pidinfo) in sample.iter() {
44 processes.insert(
45 *pid,
46 SingleProcessModel::new(
47 pidinfo,
48 last.and_then(|(p, d)| p.get(pid).map(|p| (p, d))),
49 ),
50 );
51 }
52
53 ProcessModel { processes }
54 }
55}
56
57impl Nameable for ProcessModel {
58 fn name() -> &'static str {
59 "process"
60 }
61}
62
63#[::below_derive::queriable_derives]
64pub struct SingleProcessModel {
65 pub pid: Option<i32>,
66 pub ppid: Option<i32>,
67 pub ns_tgid: Option<Vec<u32>>,
68 pub comm: Option<String>,
69 pub state: Option<procfs::PidState>,
70 pub uptime_secs: Option<u64>,
71 pub cgroup: Option<String>,
72 #[queriable(subquery)]
73 pub io: Option<ProcessIoModel>,
74 #[queriable(subquery)]
75 pub mem: Option<ProcessMemoryModel>,
76 #[queriable(subquery)]
77 pub cpu: Option<ProcessCpuModel>,
78 pub cmdline: Option<String>,
79 pub exe_path: Option<String>,
80}
81
82impl SingleProcessModel {
83 fn new(
84 sample: &procfs::PidInfo,
85 last: Option<(&procfs::PidInfo, Duration)>,
86 ) -> SingleProcessModel {
87 SingleProcessModel {
88 pid: sample.stat.pid,
89 ppid: sample.stat.ppid,
90 ns_tgid: sample
91 .status
92 .ns_tgid
93 .as_ref()
94 .map(|v| v.iter().skip(1).cloned().collect()),
96 comm: sample.stat.comm.clone(),
97 state: sample.stat.state.clone(),
98 uptime_secs: sample.stat.running_secs,
99 cgroup: Some(sample.cgroup.clone()),
100 io: last.map(|(l, d)| ProcessIoModel::new(&l.io, &sample.io, d)),
101 mem: last.map(|(l, d)| ProcessMemoryModel::new(l, sample, d)),
102 cpu: last.map(|(l, d)| ProcessCpuModel::new(&l.stat, &sample.stat, d)),
103 cmdline: if let Some(cmd_vec) = sample.cmdline_vec.as_ref() {
104 Some(cmd_vec.join(" "))
105 } else {
106 Some("?".into())
107 },
108 exe_path: sample.exe_path.clone(),
109 }
110 }
111
112 pub fn fold(left: &SingleProcessModel, right: &SingleProcessModel) -> SingleProcessModel {
115 SingleProcessModel {
116 pid: None,
117 ppid: None,
118 ns_tgid: None,
119 comm: None,
120 state: None,
121 uptime_secs: None,
123 cgroup: None,
124 io: fold_optionals!(&left.io, &right.io, ProcessIoModel::fold),
125 mem: fold_optionals!(&left.mem, &right.mem, ProcessMemoryModel::fold),
126 cpu: fold_optionals!(&left.cpu, &right.cpu, ProcessCpuModel::fold),
127 cmdline: None,
128 exe_path: None,
129 }
130 }
131}
132
133impl Nameable for SingleProcessModel {
134 fn name() -> &'static str {
135 "process"
136 }
137}
138
139#[::below_derive::queriable_derives]
140pub struct ProcessIoModel {
141 pub rbytes_per_sec: Option<f64>,
142 pub wbytes_per_sec: Option<f64>,
143 pub rwbytes_per_sec: Option<f64>,
144}
145
146impl ProcessIoModel {
147 fn new(begin: &procfs::PidIo, end: &procfs::PidIo, delta: Duration) -> ProcessIoModel {
148 let rbytes_per_sec = count_per_sec!(begin.rbytes, end.rbytes, delta);
149 let wbytes_per_sec = count_per_sec!(begin.wbytes, end.wbytes, delta);
150 let rwbytes_per_sec =
151 Some(rbytes_per_sec.unwrap_or_default() + wbytes_per_sec.unwrap_or_default());
152 ProcessIoModel {
153 rbytes_per_sec,
154 wbytes_per_sec,
155 rwbytes_per_sec,
156 }
157 }
158
159 pub fn fold(left: &ProcessIoModel, right: &ProcessIoModel) -> ProcessIoModel {
161 ProcessIoModel {
162 rbytes_per_sec: fold_optionals!(left.rbytes_per_sec, right.rbytes_per_sec),
163 wbytes_per_sec: fold_optionals!(left.wbytes_per_sec, right.wbytes_per_sec),
164 rwbytes_per_sec: fold_optionals!(left.rwbytes_per_sec, right.rwbytes_per_sec),
165 }
166 }
167}
168
169#[::below_derive::queriable_derives]
170pub struct ProcessCpuModel {
171 pub usage_pct: Option<f64>,
172 pub user_pct: Option<f64>,
173 pub system_pct: Option<f64>,
174 pub num_threads: Option<u64>,
175}
176
177impl ProcessCpuModel {
178 fn new(begin: &procfs::PidStat, end: &procfs::PidStat, delta: Duration) -> ProcessCpuModel {
179 let user_pct = usec_pct!(begin.user_usecs, end.user_usecs, delta);
180 let system_pct = usec_pct!(begin.system_usecs, end.system_usecs, delta);
181 let usage_pct = collector::opt_add(user_pct, system_pct);
182 ProcessCpuModel {
183 usage_pct,
184 user_pct,
185 system_pct,
186 num_threads: end.num_threads,
187 }
188 }
189
190 pub fn fold(left: &ProcessCpuModel, right: &ProcessCpuModel) -> ProcessCpuModel {
192 ProcessCpuModel {
193 usage_pct: fold_optionals!(left.usage_pct, right.usage_pct),
194 user_pct: fold_optionals!(left.user_pct, right.user_pct),
195 system_pct: fold_optionals!(left.system_pct, right.system_pct),
196 num_threads: fold_optionals!(left.num_threads, right.num_threads),
197 }
198 }
199}
200
201#[::below_derive::queriable_derives]
202pub struct ProcessMemoryModel {
203 pub minorfaults_per_sec: Option<f64>,
204 pub majorfaults_per_sec: Option<f64>,
205 pub rss_bytes: Option<u64>,
206 pub vm_size: Option<u64>,
207 pub lock: Option<u64>,
208 pub pin: Option<u64>,
209 pub anon: Option<u64>,
210 pub file: Option<u64>,
211 pub shmem: Option<u64>,
212 pub pte: Option<u64>,
213 pub swap: Option<u64>,
214 pub huge_tlb: Option<u64>,
215}
216
217impl ProcessMemoryModel {
218 fn new(begin: &procfs::PidInfo, end: &procfs::PidInfo, delta: Duration) -> ProcessMemoryModel {
219 ProcessMemoryModel {
220 minorfaults_per_sec: count_per_sec!(begin.stat.minflt, end.stat.minflt, delta),
221 majorfaults_per_sec: count_per_sec!(begin.stat.majflt, end.stat.majflt, delta),
222 rss_bytes: end.stat.rss_bytes,
223 vm_size: end.status.vm_size,
224 lock: end.status.lock,
225 pin: end.status.pin,
226 anon: end.status.anon,
227 file: end.status.file,
228 shmem: end.status.shmem,
229 pte: end.status.pte,
230 swap: end.status.swap,
231 huge_tlb: end.status.huge_tlb,
232 }
233 }
234
235 pub fn fold(left: &ProcessMemoryModel, right: &ProcessMemoryModel) -> ProcessMemoryModel {
237 ProcessMemoryModel {
238 minorfaults_per_sec: fold_optionals!(
239 left.minorfaults_per_sec,
240 right.minorfaults_per_sec
241 ),
242 majorfaults_per_sec: fold_optionals!(
243 left.majorfaults_per_sec,
244 right.majorfaults_per_sec
245 ),
246 rss_bytes: fold_optionals!(left.rss_bytes, right.rss_bytes),
247 vm_size: fold_optionals!(left.vm_size, right.vm_size),
248 lock: fold_optionals!(left.lock, right.lock),
249 pin: fold_optionals!(left.pin, right.pin),
250 anon: fold_optionals!(left.anon, right.anon),
251 file: fold_optionals!(left.file, right.file),
252 shmem: fold_optionals!(left.shmem, right.shmem),
253 pte: fold_optionals!(left.pte, right.pte),
254 swap: fold_optionals!(left.swap, right.swap),
255 huge_tlb: fold_optionals!(left.huge_tlb, right.huge_tlb),
256 }
257 }
258}
259
260#[cfg(test)]
261mod test {
262 use super::*;
263
264 #[test]
265 fn query_model() {
266 let model_json = r#"
267 {
268 "processes": {
269 "1": {
270 "pid": 1,
271 "comm": "systemd"
272 }
273 }
274 }
275 "#;
276 let model: ProcessModel = serde_json::from_str(model_json).unwrap();
277 assert_eq!(
278 model.query(&ProcessModelFieldId::from_str("processes.1.comm").unwrap()),
279 Some(Field::Str("systemd".to_owned()))
280 );
281 }
282}