1use 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 (
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}