below_model/
process.rs

1// Copyright (c) Facebook, Inc. and its affiliates.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use super::*;
16
17/// Folds two optionals together with either `+` operator or provided closure
18macro_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                // Skip the first item as it is always the same as pid
95                .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    /// Sums stats between two process models together, None'ing out fields that semantically
113    /// cannot be summed
114    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            // 80% sure it should be None here. Don't know what someone can infer from summed uptime
122            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    /// See `SingleProcessModel::fold`
160    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    /// See `SingleProcessModel::fold`
191    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    /// See `SingleProcessModel::fold`
236    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}