below_model/
system.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#[::below_derive::queriable_derives]
18pub struct SystemModel {
19    pub hostname: String,
20    pub kernel_version: Option<String>,
21    pub os_release: Option<String>,
22    #[queriable(subquery)]
23    pub stat: ProcStatModel,
24    #[queriable(subquery)]
25    #[queriable(preferred_name = cpu)]
26    pub total_cpu: SingleCpuModel,
27    #[queriable(subquery)]
28    pub cpus: BTreeMap<u32, SingleCpuModel>,
29    #[queriable(subquery)]
30    pub mem: MemoryModel,
31    #[queriable(subquery)]
32    pub vm: VmModel,
33    #[queriable(subquery)]
34    pub slab: Vec<SingleSlabModel>,
35    #[queriable(subquery)]
36    pub ksm: Option<KsmModel>,
37    #[queriable(subquery)]
38    pub disks: BTreeMap<String, SingleDiskModel>,
39    #[queriable(subquery)]
40    pub btrfs: Option<BTreeMap<String, BtrfsModel>>,
41}
42
43impl SystemModel {
44    pub fn new(sample: &SystemSample, last: Option<(&SystemSample, Duration)>) -> SystemModel {
45        let stat = ProcStatModel::new(&sample.stat);
46        let total_cpu = match (
47            last.and_then(|(last, _)| last.stat.total_cpu.as_ref()),
48            sample.stat.total_cpu.as_ref(),
49        ) {
50            (Some(prev), Some(curr)) => SingleCpuModel::new(-1, prev, curr),
51            _ => Default::default(),
52        };
53
54        let cpus: BTreeMap<u32, SingleCpuModel> = match (
55            last.and_then(|(last, _)| last.stat.cpus_map.as_ref()),
56            sample.stat.cpus_map.as_ref(),
57        ) {
58            (Some(prev), Some(curr)) => curr
59                .iter()
60                .map(|(idx, curr)| {
61                    (
62                        *idx,
63                        prev.get(idx).map_or_else(Default::default, |prev| {
64                            SingleCpuModel::new(*idx as i32, prev, curr)
65                        }),
66                    )
67                })
68                .collect(),
69            (_, Some(curr)) => curr.keys().map(|idx| (*idx, Default::default())).collect(),
70            _ => Default::default(),
71        };
72
73        let mem = MemoryModel::new(&sample.meminfo);
74        let vm = last
75            .map(|(last, duration)| VmModel::new(&last.vmstat, &sample.vmstat, duration))
76            .unwrap_or_default();
77
78        let mut slab = sample
79            .slabinfo_vec
80            .iter()
81            .map(SingleSlabModel::new)
82            .collect::<Vec<SingleSlabModel>>();
83
84        let slab_total = slab.iter().fold(
85            SingleSlabModel {
86                name: Some(String::from("TOTAL")),
87                ..Default::default()
88            },
89            |mut acc, slabinfo| {
90                acc.active_objs = opt_add(acc.active_objs, slabinfo.active_objs);
91                acc.num_objs = opt_add(acc.num_objs, slabinfo.num_objs);
92                acc.num_slabs = opt_add(acc.num_slabs, slabinfo.num_slabs);
93                acc.active_caches = opt_add(acc.active_caches, slabinfo.active_caches);
94                acc.num_caches = opt_add(acc.num_caches, slabinfo.num_caches);
95                acc.active_size = opt_add(acc.active_size, slabinfo.active_size);
96                acc.total_size = opt_add(acc.total_size, slabinfo.total_size);
97                acc
98            },
99        );
100        slab.insert(0, slab_total);
101
102        let ksm = sample.ksm.as_ref().map(KsmModel::new);
103
104        let mut disks: BTreeMap<String, SingleDiskModel> = BTreeMap::new();
105        sample.disks.iter().for_each(|(disk_name, end_disk_stat)| {
106            disks.insert(
107                disk_name.clone(),
108                match last {
109                    Some((last_sample, duration)) if last_sample.disks.contains_key(disk_name) => {
110                        SingleDiskModel::new(
111                            last_sample.disks.get(disk_name).unwrap(),
112                            end_disk_stat,
113                            duration,
114                        )
115                    }
116                    _ => SingleDiskModel {
117                        name: Some(disk_name.clone()),
118                        ..Default::default()
119                    },
120                },
121            );
122        });
123
124        let mut btrfs: Option<BTreeMap<String, BtrfsModel>> = None;
125        match &sample.btrfs {
126            Some(b) => {
127                let tmp_btrfs: BTreeMap<String, BtrfsModel> = b
128                    .iter()
129                    .map(|(dir_name, end_dir_stat)| {
130                        (dir_name.clone(), BtrfsModel::new(end_dir_stat))
131                    })
132                    .collect();
133                btrfs = Some(tmp_btrfs);
134            }
135            None => {}
136        }
137
138        SystemModel {
139            hostname: sample.hostname.clone(),
140            kernel_version: sample.kernel_version.clone(),
141            os_release: sample.os_release.clone(),
142            stat,
143            total_cpu,
144            cpus,
145            mem,
146            vm,
147            slab,
148            ksm,
149            disks,
150            btrfs,
151        }
152    }
153}
154
155impl Nameable for SystemModel {
156    fn name() -> &'static str {
157        "system"
158    }
159}
160
161#[::below_derive::queriable_derives]
162pub struct ProcStatModel {
163    pub total_interrupt_ct: Option<u64>,
164    pub context_switches: Option<u64>,
165    pub boot_time_epoch_secs: Option<u64>,
166    pub total_processes: Option<u64>,
167    pub running_processes: Option<u32>,
168    pub blocked_processes: Option<u32>,
169}
170
171impl ProcStatModel {
172    pub fn new(stat: &procfs::Stat) -> Self {
173        ProcStatModel {
174            total_interrupt_ct: stat.total_interrupt_count,
175            context_switches: stat.context_switches,
176            boot_time_epoch_secs: stat.boot_time_epoch_secs,
177            total_processes: stat.total_processes,
178            running_processes: stat.running_processes,
179            blocked_processes: stat.blocked_processes,
180        }
181    }
182}
183
184#[::below_derive::queriable_derives]
185pub struct SingleCpuModel {
186    pub idx: i32,
187    pub usage_pct: Option<f64>,
188    pub user_pct: Option<f64>,
189    pub system_pct: Option<f64>,
190    pub idle_pct: Option<f64>,
191    pub nice_pct: Option<f64>,
192    pub iowait_pct: Option<f64>,
193    pub irq_pct: Option<f64>,
194    pub softirq_pct: Option<f64>,
195    pub stolen_pct: Option<f64>,
196    pub guest_pct: Option<f64>,
197    pub guest_nice_pct: Option<f64>,
198}
199
200impl SingleCpuModel {
201    pub fn new(idx: i32, begin: &procfs::CpuStat, end: &procfs::CpuStat) -> SingleCpuModel {
202        match (begin, end) {
203            // guest and guest_nice are ignored
204            (
205                procfs::CpuStat {
206                    user_usec: Some(prev_user),
207                    nice_usec: Some(prev_nice),
208                    system_usec: Some(prev_system),
209                    idle_usec: Some(prev_idle),
210                    iowait_usec: Some(prev_iowait),
211                    irq_usec: Some(prev_irq),
212                    softirq_usec: Some(prev_softirq),
213                    stolen_usec: Some(prev_stolen),
214                    guest_usec: Some(prev_guest),
215                    guest_nice_usec: Some(prev_guest_nice),
216                },
217                procfs::CpuStat {
218                    user_usec: Some(curr_user),
219                    nice_usec: Some(curr_nice),
220                    system_usec: Some(curr_system),
221                    idle_usec: Some(curr_idle),
222                    iowait_usec: Some(curr_iowait),
223                    irq_usec: Some(curr_irq),
224                    softirq_usec: Some(curr_softirq),
225                    stolen_usec: Some(curr_stolen),
226                    guest_usec: Some(curr_guest),
227                    guest_nice_usec: Some(curr_guest_nice),
228                },
229            ) => {
230                let idle_usec = curr_idle - prev_idle;
231                let iowait_usec = curr_iowait - prev_iowait;
232                let user_usec = curr_user - prev_user;
233                let system_usec = curr_system - prev_system;
234                let nice_usec = curr_nice - prev_nice;
235                let irq_usec = curr_irq - prev_irq;
236                let softirq_usec = curr_softirq - prev_softirq;
237                let stolen_usec = curr_stolen - prev_stolen;
238                let guest_usec = curr_guest - prev_guest;
239                let guest_nice_usec = curr_guest_nice - prev_guest_nice;
240
241                let busy_usec =
242                    user_usec + system_usec + nice_usec + irq_usec + softirq_usec + stolen_usec;
243                let total_usec = idle_usec + busy_usec + iowait_usec;
244                SingleCpuModel {
245                    idx,
246                    usage_pct: Some(busy_usec as f64 * 100.0 / total_usec as f64),
247                    user_pct: Some(user_usec as f64 * 100.0 / total_usec as f64),
248                    idle_pct: Some(idle_usec as f64 * 100.0 / total_usec as f64),
249                    system_pct: Some(system_usec as f64 * 100.0 / total_usec as f64),
250                    nice_pct: Some(nice_usec as f64 * 100.0 / total_usec as f64),
251                    iowait_pct: Some(iowait_usec as f64 * 100.0 / total_usec as f64),
252                    irq_pct: Some(irq_usec as f64 * 100.0 / total_usec as f64),
253                    softirq_pct: Some(softirq_usec as f64 * 100.0 / total_usec as f64),
254                    stolen_pct: Some(stolen_usec as f64 * 100.0 / total_usec as f64),
255                    guest_pct: Some(guest_usec as f64 * 100.0 / total_usec as f64),
256                    guest_nice_pct: Some(guest_nice_usec as f64 * 100.0 / total_usec as f64),
257                }
258            }
259            _ => SingleCpuModel {
260                idx,
261                ..Default::default()
262            },
263        }
264    }
265}
266
267#[::below_derive::queriable_derives]
268pub struct MemoryModel {
269    pub total: Option<u64>,
270    pub free: Option<u64>,
271    pub available: Option<u64>,
272    pub buffers: Option<u64>,
273    pub cached: Option<u64>,
274    pub swap_cached: Option<u64>,
275    pub active: Option<u64>,
276    pub inactive: Option<u64>,
277    pub anon: Option<u64>,
278    pub file: Option<u64>,
279    pub unevictable: Option<u64>,
280    pub mlocked: Option<u64>,
281    pub swap_total: Option<u64>,
282    pub swap_free: Option<u64>,
283    pub dirty: Option<u64>,
284    pub writeback: Option<u64>,
285    pub anon_pages: Option<u64>,
286    pub mapped: Option<u64>,
287    pub shmem: Option<u64>,
288    pub kreclaimable: Option<u64>,
289    pub slab: Option<u64>,
290    pub slab_reclaimable: Option<u64>,
291    pub slab_unreclaimable: Option<u64>,
292    pub kernel_stack: Option<u64>,
293    pub page_tables: Option<u64>,
294    pub anon_huge_pages_bytes: Option<u64>,
295    pub shmem_huge_pages_bytes: Option<u64>,
296    pub file_huge_pages_bytes: Option<u64>,
297    pub hugetlb: Option<u64>,
298    pub cma_total: Option<u64>,
299    pub cma_free: Option<u64>,
300    pub vmalloc_total: Option<u64>,
301    pub vmalloc_used: Option<u64>,
302    pub vmalloc_chunk: Option<u64>,
303    pub direct_map_4k: Option<u64>,
304    pub direct_map_2m: Option<u64>,
305    pub direct_map_1g: Option<u64>,
306}
307
308impl MemoryModel {
309    fn new(meminfo: &procfs::MemInfo) -> MemoryModel {
310        MemoryModel {
311            total: meminfo.total,
312            free: meminfo.free,
313            available: meminfo.available,
314            buffers: meminfo.buffers,
315            cached: meminfo.cached,
316            swap_cached: meminfo.swap_cached,
317            active: meminfo.active,
318            inactive: meminfo.inactive,
319            anon: opt_add(meminfo.active_anon, meminfo.inactive_anon),
320            file: opt_add(meminfo.active_file, meminfo.inactive_file),
321            unevictable: meminfo.unevictable,
322            mlocked: meminfo.mlocked,
323            swap_total: meminfo.swap_total,
324            swap_free: meminfo.swap_free,
325            dirty: meminfo.dirty,
326            writeback: meminfo.writeback,
327            anon_pages: meminfo.anon_pages,
328            mapped: meminfo.mapped,
329            shmem: meminfo.shmem,
330            kreclaimable: meminfo.kreclaimable,
331            slab: meminfo.slab,
332            slab_reclaimable: meminfo.slab_reclaimable,
333            slab_unreclaimable: meminfo.slab_unreclaimable,
334            kernel_stack: meminfo.kernel_stack,
335            page_tables: meminfo.page_tables,
336            anon_huge_pages_bytes: meminfo.anon_huge_pages,
337            shmem_huge_pages_bytes: meminfo.shmem_huge_pages,
338            file_huge_pages_bytes: meminfo.file_huge_pages,
339            hugetlb: meminfo.hugetlb,
340            cma_total: meminfo.cma_total,
341            cma_free: meminfo.cma_free,
342            vmalloc_total: meminfo.vmalloc_total,
343            vmalloc_used: meminfo.vmalloc_used,
344            vmalloc_chunk: meminfo.vmalloc_chunk,
345            direct_map_4k: meminfo.direct_map_4k,
346            direct_map_2m: meminfo.direct_map_2m,
347            direct_map_1g: meminfo.direct_map_1g,
348        }
349    }
350}
351
352#[::below_derive::queriable_derives]
353pub struct VmModel {
354    pub pgpgin_per_sec: Option<f64>,
355    pub pgpgout_per_sec: Option<f64>,
356    pub pswpin_per_sec: Option<f64>,
357    pub pswpout_per_sec: Option<f64>,
358    pub pgsteal_kswapd: Option<u64>,
359    pub pgsteal_direct: Option<u64>,
360    pub pgscan_kswapd: Option<u64>,
361    pub pgscan_direct: Option<u64>,
362    pub oom_kill: Option<u64>,
363}
364
365impl VmModel {
366    fn new(begin: &procfs::VmStat, end: &procfs::VmStat, duration: Duration) -> VmModel {
367        VmModel {
368            pgpgin_per_sec: count_per_sec!(begin.pgpgin, end.pgpgin, duration),
369            pgpgout_per_sec: count_per_sec!(begin.pgpgout, end.pgpgout, duration),
370            pswpin_per_sec: count_per_sec!(begin.pswpin, end.pswpin, duration),
371            pswpout_per_sec: count_per_sec!(begin.pswpout, end.pswpout, duration),
372            pgsteal_kswapd: count_per_sec!(begin.pgsteal_kswapd, end.pgsteal_kswapd, duration, u64),
373            pgsteal_direct: count_per_sec!(begin.pgsteal_direct, end.pgsteal_direct, duration, u64),
374            pgscan_kswapd: count_per_sec!(begin.pgscan_kswapd, end.pgscan_kswapd, duration, u64),
375            pgscan_direct: count_per_sec!(begin.pgscan_direct, end.pgscan_direct, duration, u64),
376            oom_kill: end.oom_kill,
377        }
378    }
379}
380
381#[::below_derive::queriable_derives]
382pub struct SingleSlabModel {
383    pub name: Option<String>,
384    pub active_objs: Option<u64>,
385    pub num_objs: Option<u64>,
386    pub obj_size: Option<u64>,
387    pub obj_per_slab: Option<u64>,
388    pub num_slabs: Option<u64>,
389    pub active_caches: Option<u64>,
390    pub num_caches: Option<u64>,
391    pub active_size: Option<u64>,
392    pub total_size: Option<u64>,
393}
394
395impl SingleSlabModel {
396    fn new(slabinfo: &procfs::SlabInfo) -> SingleSlabModel {
397        SingleSlabModel {
398            name: slabinfo.name.clone(),
399            active_objs: slabinfo.active_objs,
400            num_objs: slabinfo.num_objs,
401            obj_size: slabinfo.obj_size,
402            obj_per_slab: slabinfo.obj_per_slab,
403            num_slabs: slabinfo.num_slabs,
404            active_caches: slabinfo.active_objs.map(
405                |active_objs| {
406                    if active_objs > 0 { 1 } else { 0 }
407                },
408            ),
409            num_caches: Some(1),
410            active_size: opt_multiply(slabinfo.obj_size, slabinfo.active_objs),
411            total_size: opt_multiply(slabinfo.obj_size, slabinfo.num_objs),
412        }
413    }
414}
415
416#[::below_derive::queriable_derives]
417pub struct KsmModel {
418    pub advisor_max_cpu: Option<u64>,
419    pub advisor_max_pages_to_scan: Option<u64>,
420    pub advisor_min_pages_to_scan: Option<u64>,
421    pub advisor_mode: Option<String>,
422    pub advisor_target_scan_time: Option<u64>,
423    pub full_scans: Option<u64>,
424    pub general_profit: Option<i64>,
425    pub ksm_zero_pages: Option<i64>,
426    pub max_page_sharing: Option<u64>,
427    pub merge_across_nodes: Option<u64>,
428    pub pages_scanned: Option<u64>,
429    pub pages_shared: Option<u64>,
430    pub pages_sharing: Option<u64>,
431    pub pages_skipped: Option<u64>,
432    pub pages_to_scan: Option<u64>,
433    pub pages_unshared: Option<u64>,
434    pub pages_volatile: Option<u64>,
435    pub run: Option<u64>,
436    pub sleep_millisecs: Option<u64>,
437    pub smart_scan: Option<u64>,
438    pub stable_node_chains: Option<u64>,
439    pub stable_node_chains_prune_millisecs: Option<u64>,
440    pub stable_node_dups: Option<u64>,
441    pub use_zero_pages: Option<u64>,
442}
443
444impl KsmModel {
445    fn new(ksm: &procfs::Ksm) -> KsmModel {
446        KsmModel {
447            advisor_max_cpu: ksm.advisor_max_cpu,
448            advisor_max_pages_to_scan: ksm.advisor_max_pages_to_scan,
449            advisor_min_pages_to_scan: ksm.advisor_min_pages_to_scan,
450            advisor_mode: ksm.advisor_mode.clone(),
451            advisor_target_scan_time: ksm.advisor_target_scan_time,
452            full_scans: ksm.full_scans,
453            general_profit: ksm.general_profit,
454            ksm_zero_pages: ksm.ksm_zero_pages,
455            max_page_sharing: ksm.max_page_sharing,
456            merge_across_nodes: ksm.merge_across_nodes,
457            pages_scanned: ksm.pages_scanned,
458            pages_shared: ksm.pages_shared,
459            pages_sharing: ksm.pages_sharing,
460            pages_skipped: ksm.pages_skipped,
461            pages_to_scan: ksm.pages_to_scan,
462            pages_unshared: ksm.pages_unshared,
463            pages_volatile: ksm.pages_volatile,
464            run: ksm.run,
465            sleep_millisecs: ksm.sleep_millisecs,
466            smart_scan: ksm.smart_scan,
467            stable_node_chains: ksm.stable_node_chains,
468            stable_node_chains_prune_millisecs: ksm.stable_node_chains_prune_millisecs,
469            stable_node_dups: ksm.stable_node_dups,
470            use_zero_pages: ksm.use_zero_pages,
471        }
472    }
473}
474
475#[::below_derive::queriable_derives]
476pub struct SingleDiskModel {
477    pub name: Option<String>,
478    pub disk_usage: Option<f32>,
479    pub partition_size: Option<u64>,
480    pub filesystem_type: Option<String>,
481    pub read_bytes_per_sec: Option<f64>,
482    pub write_bytes_per_sec: Option<f64>,
483    pub discard_bytes_per_sec: Option<f64>,
484    pub disk_total_bytes_per_sec: Option<f64>,
485    pub read_completed: Option<u64>,
486    pub read_merged: Option<u64>,
487    pub read_sectors: Option<u64>,
488    pub time_spend_read_ms: Option<u64>,
489    pub write_completed: Option<u64>,
490    pub write_merged: Option<u64>,
491    pub write_sectors: Option<u64>,
492    pub time_spend_write_ms: Option<u64>,
493    pub discard_completed: Option<u64>,
494    pub discard_merged: Option<u64>,
495    pub discard_sectors: Option<u64>,
496    pub time_spend_discard_ms: Option<u64>,
497    pub major: Option<u64>,
498    pub minor: Option<u64>,
499}
500
501impl Recursive for SingleDiskModel {
502    fn get_depth(&self) -> usize {
503        if self.minor == Some(0) { 0 } else { 1 }
504    }
505}
506
507impl SingleDiskModel {
508    fn new(
509        begin: &procfs::DiskStat,
510        end: &procfs::DiskStat,
511        duration: Duration,
512    ) -> SingleDiskModel {
513        let read_bytes_per_sec =
514            count_per_sec!(begin.read_sectors, end.read_sectors, duration).map(|val| val * 512.0);
515        let write_bytes_per_sec =
516            count_per_sec!(begin.write_sectors, end.write_sectors, duration).map(|val| val * 512.0);
517        SingleDiskModel {
518            name: end.name.clone(),
519            disk_usage: end.disk_usage,
520            partition_size: end.partition_size,
521            filesystem_type: end.filesystem_type.clone(),
522            read_bytes_per_sec,
523            write_bytes_per_sec,
524            discard_bytes_per_sec: count_per_sec!(
525                begin.discard_sectors,
526                end.discard_sectors,
527                duration
528            )
529            .map(|val| val * 512.0),
530            disk_total_bytes_per_sec: opt_add(read_bytes_per_sec, write_bytes_per_sec),
531            read_completed: end.read_completed,
532            read_merged: end.read_merged,
533            read_sectors: end.read_sectors,
534            time_spend_read_ms: end.time_spend_read_ms,
535            write_completed: end.write_completed,
536            write_merged: end.write_merged,
537            write_sectors: end.write_sectors,
538            time_spend_write_ms: end.time_spend_write_ms,
539            discard_completed: end.discard_completed,
540            discard_merged: end.discard_merged,
541            discard_sectors: end.discard_sectors,
542            time_spend_discard_ms: end.time_spend_discard_ms,
543            major: end.major,
544            minor: end.minor,
545        }
546    }
547}
548
549impl Nameable for SingleDiskModel {
550    fn name() -> &'static str {
551        "disk"
552    }
553}
554
555#[::below_derive::queriable_derives]
556pub struct BtrfsModel {
557    pub name: Option<String>,
558    pub disk_fraction: Option<f64>,
559    pub disk_bytes: Option<u64>,
560}
561
562impl BtrfsModel {
563    fn new(end: &btrfs::BtrfsStat) -> BtrfsModel {
564        BtrfsModel {
565            name: end.name.clone(),
566            disk_fraction: end.disk_fraction,
567            disk_bytes: end.disk_bytes,
568        }
569    }
570}
571
572impl Nameable for BtrfsModel {
573    fn name() -> &'static str {
574        "btrfs"
575    }
576}
577
578#[cfg(test)]
579mod test {
580    use super::*;
581
582    #[test]
583    fn query_model() {
584        let model_json = r#"
585        {
586            "hostname": "example.com",
587            "stat": {},
588            "total_cpu": {
589                "idx": -1
590            },
591            "cpus": {},
592            "mem": {},
593            "vm": {},
594            "slab": [],
595            "ksm": {},
596            "disks": {
597                "sda": {
598                    "name": "sda",
599                    "read_bytes_per_sec": 42
600                }
601            },
602            "btrfs": {}
603        }
604        "#;
605        let model: SystemModel = serde_json::from_str(model_json).unwrap();
606        assert_eq!(
607            model.query(&SystemModelFieldId::from_str("disks.sda.read_bytes_per_sec").unwrap()),
608            Some(Field::F64(42.0))
609        );
610    }
611}