1use std::fmt::Display;
16use std::str::FromStr;
17
18use anyhow::bail;
19use anyhow::Error;
20use anyhow::Result;
21use clap::Parser;
22use model::BtrfsModelFieldId;
23use model::FieldId;
24use model::NetworkModelFieldId;
25use model::SingleCgroupModelFieldId;
26use model::SingleDiskModelFieldId;
27use model::SingleNetModelFieldId;
28use model::SingleProcessModelFieldId;
29use model::SingleQueueModelFieldId;
30use model::SingleTcModelFieldId;
31use model::SystemModelFieldId;
32use once_cell::sync::Lazy;
33use regex::Regex;
34
35use crate::CommonField;
36use crate::DumpField;
37
38pub trait AggField<F: FieldId> {
41 fn expand(&self, detail: bool) -> Vec<F>;
42}
43
44#[derive(Clone, Debug, PartialEq)]
48pub enum DumpOptionField<F: FieldId, A: AggField<F>> {
49 Unit(DumpField<F>),
50 Agg(A),
51}
52
53pub fn expand_fields<F: FieldId + Clone, A: AggField<F>>(
55 fields: &[DumpOptionField<F, A>],
56 detail: bool,
57) -> Vec<DumpField<F>> {
58 let mut res = Vec::new();
59 for field in fields {
60 match field {
61 DumpOptionField::Unit(field) => res.push(field.clone()),
62 DumpOptionField::Agg(agg) => {
63 res.extend(agg.expand(detail).into_iter().map(DumpField::FieldId))
64 }
65 }
66 }
67 res
68}
69
70impl<F: FieldId + FromStr, A: AggField<F> + FromStr> FromStr for DumpOptionField<F, A> {
72 type Err = Error;
73
74 fn from_str(s: &str) -> Result<Self, Self::Err> {
77 if let Ok(common) = CommonField::from_str(s) {
78 Ok(Self::Unit(DumpField::Common(common)))
79 } else if let Ok(agg) = A::from_str(s) {
80 Ok(Self::Agg(agg))
81 } else if let Ok(field_id) = F::from_str(s) {
82 Ok(Self::Unit(DumpField::FieldId(field_id)))
83 } else {
84 bail!("Variant not found: {}", s);
85 }
86 }
87}
88
89impl<F: FieldId + Display, A: AggField<F> + Display> Display for DumpOptionField<F, A> {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 match self {
93 Self::Unit(DumpField::Common(common)) => write!(f, "{}", common),
94 Self::Unit(DumpField::FieldId(field_id)) => write!(f, "{}", field_id),
95 Self::Agg(agg) => write!(f, "{}", agg),
96 }
97 }
98}
99
100fn join(iter: impl IntoIterator<Item = impl ToString>) -> String {
103 iter.into_iter()
104 .map(|v| v.to_string())
105 .collect::<Vec<_>>()
106 .join(", ")
107}
108
109macro_rules! make_option {
124 ($name:ident {$($str_field:tt: $enum_field:ident,)*}) => {
125 #[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)]
126 pub enum $name {
127 $($enum_field,)*
128 }
129
130 impl FromStr for $name {
131 type Err = Error;
132
133 fn from_str(opt: &str) -> Result<Self> {
134 match opt.to_lowercase().as_str() {
135 $($str_field => Ok($name::$enum_field),)*
136 _ => bail!("Fail to parse {}", opt)
137 }
138 }
139 }
140 }
141}
142
143#[derive(
145 Clone,
146 Debug,
147 PartialEq,
148 below_derive::EnumFromStr,
149 below_derive::EnumToString
150)]
151pub enum SystemAggField {
152 Cpu,
153 Mem,
154 Vm,
155 Stat,
156}
157
158impl AggField<SystemModelFieldId> for SystemAggField {
159 fn expand(&self, detail: bool) -> Vec<SystemModelFieldId> {
160 use model::MemoryModelFieldId as Mem;
161 use model::ProcStatModelFieldId as Stat;
162 use model::SingleCpuModelFieldId as Cpu;
163 use model::SystemModelFieldId as FieldId;
164 use model::VmModelFieldId as Vm;
165
166 if detail {
167 match self {
168 Self::Cpu => enum_iterator::all::<Cpu>()
169 .filter(|v| v != &Cpu::Idx)
171 .map(FieldId::Cpu)
172 .collect(),
173 Self::Mem => enum_iterator::all::<Mem>().map(FieldId::Mem).collect(),
174 Self::Vm => enum_iterator::all::<Vm>().map(FieldId::Vm).collect(),
175 Self::Stat => enum_iterator::all::<Stat>().map(FieldId::Stat).collect(),
176 }
177 } else {
178 match self {
180 Self::Cpu => vec![Cpu::UsagePct, Cpu::UserPct, Cpu::SystemPct]
181 .into_iter()
182 .map(FieldId::Cpu)
183 .collect(),
184 Self::Mem => vec![Mem::Total, Mem::Free]
185 .into_iter()
186 .map(FieldId::Mem)
187 .collect(),
188 Self::Vm => enum_iterator::all::<Vm>().map(FieldId::Vm).collect(),
189 Self::Stat => enum_iterator::all::<Stat>().map(FieldId::Stat).collect(),
190 }
191 }
192 }
193}
194
195pub type SystemOptionField = DumpOptionField<SystemModelFieldId, SystemAggField>;
196
197pub static DEFAULT_SYSTEM_FIELDS: &[SystemOptionField] = &[
198 DumpOptionField::Unit(DumpField::FieldId(SystemModelFieldId::Hostname)),
199 DumpOptionField::Unit(DumpField::Common(CommonField::Datetime)),
200 DumpOptionField::Agg(SystemAggField::Cpu),
201 DumpOptionField::Agg(SystemAggField::Mem),
202 DumpOptionField::Agg(SystemAggField::Vm),
203 DumpOptionField::Unit(DumpField::FieldId(SystemModelFieldId::KernelVersion)),
204 DumpOptionField::Unit(DumpField::FieldId(SystemModelFieldId::OsRelease)),
205 DumpOptionField::Agg(SystemAggField::Stat),
206 DumpOptionField::Unit(DumpField::Common(CommonField::Timestamp)),
207];
208
209const SYSTEM_ABOUT: &str = "Dump system stats";
210
211static SYSTEM_LONG_ABOUT: Lazy<String> = Lazy::new(|| {
213 format!(
214 r#"{about}
215
216********************** Available fields **********************
217
218{common_fields}, {system_fields}
219
220********************** Aggregated fields **********************
221
222* cpu: includes [{agg_cpu_fields}].
223
224* mem: includes [{agg_memory_fields}].
225
226* vm: includes [{agg_vm_fields}].
227
228* stat: includes [{agg_stat_fields}].
229
230* --detail: includes [<agg_field>.*] for each given aggregated field.
231
232* --default: includes [{default_fields}].
233
234* --everything: includes everything (equivalent to --default --detail).
235
236********************** Example Commands **********************
237
238$ below dump system -b "08:30:00" -e "08:30:30" -f datetime vm hostname -O csv
239
240"#,
241 about = SYSTEM_ABOUT,
242 common_fields = join(enum_iterator::all::<CommonField>()),
243 system_fields = join(enum_iterator::all::<SystemModelFieldId>()),
244 agg_cpu_fields = join(SystemAggField::Cpu.expand(false)),
245 agg_memory_fields = join(SystemAggField::Mem.expand(false)),
246 agg_vm_fields = join(SystemAggField::Vm.expand(false)),
247 agg_stat_fields = join(SystemAggField::Stat.expand(false)),
248 default_fields = join(DEFAULT_SYSTEM_FIELDS.to_owned()),
249 )
250});
251
252#[derive(
253 Clone,
254 Debug,
255 PartialEq,
256 below_derive::EnumFromStr,
257 below_derive::EnumToString
258)]
259pub enum DiskAggField {
260 Read,
261 Write,
262 Discard,
263 FsInfo,
264}
265
266impl AggField<SingleDiskModelFieldId> for DiskAggField {
267 fn expand(&self, _detail: bool) -> Vec<SingleDiskModelFieldId> {
268 use model::SingleDiskModelFieldId::*;
269
270 match self {
271 Self::Read => vec![
272 ReadBytesPerSec,
273 ReadCompleted,
274 ReadMerged,
275 ReadSectors,
276 TimeSpendReadMs,
277 ],
278 Self::Write => vec![
279 WriteBytesPerSec,
280 WriteCompleted,
281 WriteMerged,
282 WriteSectors,
283 TimeSpendWriteMs,
284 ],
285 Self::Discard => vec![
286 DiscardBytesPerSec,
287 DiscardCompleted,
288 DiscardMerged,
289 DiscardSectors,
290 TimeSpendDiscardMs,
291 ],
292 Self::FsInfo => vec![DiskUsage, PartitionSize, FilesystemType],
293 }
294 }
295}
296
297pub type DiskOptionField = DumpOptionField<SingleDiskModelFieldId, DiskAggField>;
298
299pub static DEFAULT_DISK_FIELDS: &[DiskOptionField] = &[
300 DumpOptionField::Unit(DumpField::Common(CommonField::Datetime)),
301 DumpOptionField::Unit(DumpField::FieldId(SingleDiskModelFieldId::Name)),
302 DumpOptionField::Unit(DumpField::FieldId(
303 SingleDiskModelFieldId::DiskTotalBytesPerSec,
304 )),
305 DumpOptionField::Unit(DumpField::FieldId(SingleDiskModelFieldId::Major)),
306 DumpOptionField::Unit(DumpField::FieldId(SingleDiskModelFieldId::Minor)),
307 DumpOptionField::Agg(DiskAggField::Read),
308 DumpOptionField::Agg(DiskAggField::Write),
309 DumpOptionField::Agg(DiskAggField::Discard),
310 DumpOptionField::Agg(DiskAggField::FsInfo),
311 DumpOptionField::Unit(DumpField::Common(CommonField::Timestamp)),
312];
313
314const DISK_ABOUT: &str = "Dump disk stats";
315
316static DISK_LONG_ABOUT: Lazy<String> = Lazy::new(|| {
318 format!(
319 r#"{about}
320
321********************** Available fields **********************
322
323{common_fields}, {disk_fields}
324
325********************** Aggregated fields **********************
326
327* read: includes [{agg_read_fields}].
328
329* write: includes [{agg_write_fields}].
330
331* discard: includes [{agg_discard_fields}].
332
333* fs_info: includes [{agg_fsinfo_fields}].
334
335* --detail: no effect.
336
337* --default: includes [{default_fields}].
338
339* --everything: includes everything (equivalent to --default --detail).
340
341********************** Example Commands **********************
342
343Simple example:
344
345$ below dump disk -b "08:30:00" -e "08:30:30" -f read write discard -O csv
346
347Output stats for all "nvme0*" matched disk from 08:30:00 to 08:30:30:
348
349$ below dump disk -b "08:30:00" -e "08:30:30" -s name -F nvme0* -O json
350
351Output stats for top 5 read partitions for each time slice from 08:30:00 to 08:30:30:
352
353$ below dump disk -b "08:30:00" -e "08:30:30" -s read_bytes_per_sec --rsort --top 5
354
355"#,
356 about = DISK_ABOUT,
357 common_fields = join(enum_iterator::all::<CommonField>()),
358 disk_fields = join(enum_iterator::all::<SingleDiskModelFieldId>()),
359 agg_read_fields = join(DiskAggField::Read.expand(false)),
360 agg_write_fields = join(DiskAggField::Write.expand(false)),
361 agg_discard_fields = join(DiskAggField::Discard.expand(false)),
362 agg_fsinfo_fields = join(DiskAggField::FsInfo.expand(false)),
363 default_fields = join(DEFAULT_DISK_FIELDS.to_owned()),
364 )
365});
366
367#[derive(
368 Clone,
369 Debug,
370 PartialEq,
371 below_derive::EnumFromStr,
372 below_derive::EnumToString
373)]
374pub enum BtrfsAggField {
375 DiskUsage,
376}
377
378impl AggField<BtrfsModelFieldId> for BtrfsAggField {
379 fn expand(&self, _detail: bool) -> Vec<BtrfsModelFieldId> {
380 use model::BtrfsModelFieldId::*;
381
382 match self {
383 Self::DiskUsage => vec![DiskFraction, DiskBytes],
384 }
385 }
386}
387
388pub type BtrfsOptionField = DumpOptionField<BtrfsModelFieldId, BtrfsAggField>;
389
390pub static DEFAULT_BTRFS_FIELDS: &[BtrfsOptionField] = &[
391 DumpOptionField::Unit(DumpField::Common(CommonField::Datetime)),
392 DumpOptionField::Unit(DumpField::FieldId(BtrfsModelFieldId::Name)),
393 DumpOptionField::Agg(BtrfsAggField::DiskUsage),
394 DumpOptionField::Unit(DumpField::Common(CommonField::Timestamp)),
395];
396
397const BTRFS_ABOUT: &str = "Dump btrfs Stats";
398
399static BTRFS_LONG_ABOUT: Lazy<String> = Lazy::new(|| {
400 format!(
401 r#"{about}
402
403********************** Available fields **********************
404
405{common_fields}, {btrfs_fields}
406
407********************** Aggregated fields **********************
408
409* usage: includes [{agg_disk_usage_fields}].
410
411* --detail: no effect.
412
413* --default: includes [{default_fields}].
414
415* --everything: includes everything (equivalent to --default --detail).
416
417********************** Example Commands **********************
418
419Simple example:
420
421$ below dump btrfs -b "08:30:00" -e "08:30:30" -f usage -O csv
422
423Output stats for top 5 subvolumes for each time slice from 08:30:00 to 08:30:30:
424
425$ below dump btrfs -b "08:30:00" -e "08:30:30" -s disk_bytes --rsort --top 5
426
427"#,
428 about = BTRFS_ABOUT,
429 common_fields = join(enum_iterator::all::<CommonField>()),
430 btrfs_fields = join(enum_iterator::all::<BtrfsModelFieldId>()),
431 agg_disk_usage_fields = join(BtrfsAggField::DiskUsage.expand(false)),
432 default_fields = join(DEFAULT_BTRFS_FIELDS.to_owned()),
433 )
434});
435
436#[derive(
438 Clone,
439 Debug,
440 PartialEq,
441 below_derive::EnumFromStr,
442 below_derive::EnumToString
443)]
444pub enum ProcessAggField {
445 Cpu,
446 Mem,
447 Io,
448}
449
450impl AggField<SingleProcessModelFieldId> for ProcessAggField {
451 fn expand(&self, detail: bool) -> Vec<SingleProcessModelFieldId> {
452 use model::ProcessCpuModelFieldId as Cpu;
453 use model::ProcessIoModelFieldId as Io;
454 use model::ProcessMemoryModelFieldId as Mem;
455 use model::SingleProcessModelFieldId as FieldId;
456
457 if detail {
458 match self {
459 Self::Cpu => enum_iterator::all::<Cpu>().map(FieldId::Cpu).collect(),
460 Self::Mem => enum_iterator::all::<Mem>().map(FieldId::Mem).collect(),
461 Self::Io => enum_iterator::all::<Io>().map(FieldId::Io).collect(),
462 }
463 } else {
464 match self {
466 Self::Cpu => vec![FieldId::Cpu(Cpu::UsagePct)],
467 Self::Mem => vec![FieldId::Mem(Mem::RssBytes)],
468 Self::Io => vec![FieldId::Io(Io::RbytesPerSec), FieldId::Io(Io::WbytesPerSec)],
469 }
470 }
471 }
472}
473
474pub type ProcessOptionField = DumpOptionField<SingleProcessModelFieldId, ProcessAggField>;
475
476pub static DEFAULT_PROCESS_FIELDS: &[ProcessOptionField] = &[
477 DumpOptionField::Unit(DumpField::Common(CommonField::Datetime)),
478 DumpOptionField::Unit(DumpField::FieldId(SingleProcessModelFieldId::Pid)),
479 DumpOptionField::Unit(DumpField::FieldId(SingleProcessModelFieldId::Ppid)),
480 DumpOptionField::Unit(DumpField::FieldId(SingleProcessModelFieldId::Comm)),
481 DumpOptionField::Unit(DumpField::FieldId(SingleProcessModelFieldId::State)),
482 DumpOptionField::Agg(ProcessAggField::Cpu),
483 DumpOptionField::Agg(ProcessAggField::Mem),
484 DumpOptionField::Agg(ProcessAggField::Io),
485 DumpOptionField::Unit(DumpField::FieldId(SingleProcessModelFieldId::UptimeSecs)),
486 DumpOptionField::Unit(DumpField::FieldId(SingleProcessModelFieldId::Cgroup)),
487 DumpOptionField::Unit(DumpField::Common(CommonField::Timestamp)),
488 DumpOptionField::Unit(DumpField::FieldId(SingleProcessModelFieldId::Cmdline)),
489 DumpOptionField::Unit(DumpField::FieldId(SingleProcessModelFieldId::ExePath)),
490];
491
492const PROCESS_ABOUT: &str = "Dump process stats";
493
494static PROCESS_LONG_ABOUT: Lazy<String> = Lazy::new(|| {
496 format!(
497 r#"{about}
498
499********************** Available fields **********************
500
501{common_fields}, {process_fields}
502
503********************** Aggregated fields **********************
504
505* cpu: includes [{agg_cpu_fields}].
506
507* mem: includes [{agg_memory_fields}].
508
509* io: includes [{agg_io_fields}].
510
511* --detail: includes [<agg_field>.*] for each given aggregated field.
512
513* --default: includes [{default_fields}].
514
515* --everything: includes everything (equivalent to --default --detail).
516
517********************** Example Commands **********************
518
519Simple example:
520
521$ below dump process -b "08:30:00" -e "08:30:30" -f comm cpu io.rwbytes_per_sec -O csv
522
523Output stats for all "below*" matched processes from 08:30:00 to 08:30:30:
524
525$ below dump process -b "08:30:00" -e "08:30:30" -s comm -F below* -O json
526
527Output stats for top 5 CPU intense processes for each time slice from 08:30:00 to 08:30:30:
528
529$ below dump process -b "08:30:00" -e "08:30:30" -s cpu.usage_pct --rsort --top 5
530
531"#,
532 about = PROCESS_ABOUT,
533 common_fields = join(enum_iterator::all::<CommonField>()),
534 process_fields = join(enum_iterator::all::<SingleProcessModelFieldId>()),
535 agg_cpu_fields = join(ProcessAggField::Cpu.expand(false)),
536 agg_memory_fields = join(ProcessAggField::Mem.expand(false)),
537 agg_io_fields = join(ProcessAggField::Io.expand(false)),
538 default_fields = join(DEFAULT_PROCESS_FIELDS.to_owned()),
539 )
540});
541
542#[derive(
544 Clone,
545 Debug,
546 PartialEq,
547 below_derive::EnumFromStr,
548 below_derive::EnumToString
549)]
550pub enum CgroupAggField {
551 Cpu,
552 Mem,
553 Io,
554 Pids,
555 Pressure,
556}
557
558impl AggField<SingleCgroupModelFieldId> for CgroupAggField {
559 fn expand(&self, detail: bool) -> Vec<SingleCgroupModelFieldId> {
560 use model::CgroupCpuModelFieldId as Cpu;
561 use model::CgroupIoModelFieldId as Io;
562 use model::CgroupMemoryModelFieldId as Mem;
563 use model::CgroupPidsModelFieldId as Pid;
564 use model::CgroupPressureModelFieldId as Pressure;
565 use model::SingleCgroupModelFieldId as FieldId;
566
567 if detail {
568 match self {
569 Self::Cpu => enum_iterator::all::<Cpu>().map(FieldId::Cpu).collect(),
570 Self::Mem => enum_iterator::all::<Mem>().map(FieldId::Mem).collect(),
571 Self::Io => enum_iterator::all::<Io>().map(FieldId::Io).collect(),
572 Self::Pids => enum_iterator::all::<Pid>().map(FieldId::Pids).collect(),
573 Self::Pressure => enum_iterator::all::<Pressure>()
574 .map(FieldId::Pressure)
575 .collect(),
576 }
577 } else {
578 match self {
580 Self::Cpu => vec![FieldId::Cpu(Cpu::UsagePct)],
581 Self::Mem => vec![FieldId::Mem(Mem::Total)],
582 Self::Io => vec![FieldId::Io(Io::RbytesPerSec), FieldId::Io(Io::WbytesPerSec)],
583 Self::Pids => vec![FieldId::Pids(Pid::TidsCurrent)],
584 Self::Pressure => vec![
585 FieldId::Pressure(Pressure::CpuSomePct),
586 FieldId::Pressure(Pressure::MemoryFullPct),
587 FieldId::Pressure(Pressure::IoFullPct),
588 ],
589 }
590 }
591 }
592}
593
594pub type CgroupOptionField = DumpOptionField<SingleCgroupModelFieldId, CgroupAggField>;
595
596pub static DEFAULT_CGROUP_FIELDS: &[CgroupOptionField] = &[
597 DumpOptionField::Unit(DumpField::FieldId(SingleCgroupModelFieldId::Name)),
598 DumpOptionField::Unit(DumpField::FieldId(SingleCgroupModelFieldId::InodeNumber)),
599 DumpOptionField::Unit(DumpField::Common(CommonField::Datetime)),
600 DumpOptionField::Agg(CgroupAggField::Cpu),
601 DumpOptionField::Agg(CgroupAggField::Mem),
602 DumpOptionField::Agg(CgroupAggField::Io),
603 DumpOptionField::Agg(CgroupAggField::Pressure),
604 DumpOptionField::Unit(DumpField::Common(CommonField::Timestamp)),
605];
606
607const CGROUP_ABOUT: &str = "Dump cgroup stats";
608
609static CGROUP_LONG_ABOUT: Lazy<String> = Lazy::new(|| {
611 format!(
612 r#"{about}
613
614********************** Available fields **********************
615
616{common_fields}, {cgroup_fields}
617
618********************** Aggregated fields **********************
619
620* cpu: includes [{agg_cpu_fields}].
621
622* mem: includes [{agg_memory_fields}].
623
624* io: includes [{agg_io_fields}].
625
626* pressure: includes [{agg_pressure_fields}].
627
628* --detail: includes [<agg_field>.*] for each given aggregated field.
629
630* --default: includes [{default_fields}].
631
632* --everything: includes everything (equivalent to --default --detail).
633
634********************** Example Commands **********************
635
636Simple example:
637
638$ below dump cgroup -b "08:30:00" -e "08:30:30" -f name cpu -O csv
639
640Output stats for all cgroups matching pattern "below*" for time slices
641from 08:30:00 to 08:30:30:
642
643$ below dump cgroup -b "08:30:00" -e "08:30:30" -s name -F below* -O json
644
645Output stats for top 5 CPU intense cgroups for each time slice
646from 08:30:00 to 08:30:30 recursively:
647
648$ below dump cgroup -b "08:30:00" -e "08:30:30" -s cpu.usage_pct --rsort --top 5
649
650"#,
651 about = CGROUP_ABOUT,
652 common_fields = join(enum_iterator::all::<CommonField>()),
653 cgroup_fields = join(enum_iterator::all::<SingleCgroupModelFieldId>()),
654 agg_cpu_fields = join(CgroupAggField::Cpu.expand(false)),
655 agg_memory_fields = join(CgroupAggField::Mem.expand(false)),
656 agg_io_fields = join(CgroupAggField::Io.expand(false)),
657 agg_pressure_fields = join(CgroupAggField::Pressure.expand(false)),
658 default_fields = join(DEFAULT_CGROUP_FIELDS.to_owned()),
659 )
660});
661
662#[derive(
664 Clone,
665 Debug,
666 PartialEq,
667 below_derive::EnumFromStr,
668 below_derive::EnumToString
669)]
670pub enum IfaceAggField {
671 Rate,
672 Rx,
673 Tx,
674 Ethtool,
675}
676
677impl AggField<SingleNetModelFieldId> for IfaceAggField {
678 fn expand(&self, detail: bool) -> Vec<SingleNetModelFieldId> {
679 use model::SingleNetModelFieldId::*;
680
681 match self {
682 Self::Rate => vec![
683 RxBytesPerSec,
684 TxBytesPerSec,
685 ThroughputPerSec,
686 RxPacketsPerSec,
687 TxPacketsPerSec,
688 ],
689 Self::Rx => vec![
690 RxBytes,
691 RxCompressed,
692 RxCrcErrors,
693 RxDropped,
694 RxErrors,
695 RxFifoErrors,
696 RxFrameErrors,
697 RxLengthErrors,
698 RxMissedErrors,
699 RxNohandler,
700 RxOverErrors,
701 RxPackets,
702 ],
703 Self::Tx => vec![
704 TxAbortedErrors,
705 TxBytes,
706 TxCarrierErrors,
707 TxCompressed,
708 TxDropped,
709 TxErrors,
710 TxFifoErrors,
711 TxHeartbeatErrors,
712 TxPackets,
713 TxWindowErrors,
714 ],
715 Self::Ethtool => {
716 let mut fields = vec![TxTimeoutPerSec];
717 if detail {
718 fields.push(RawStats);
719 }
720 fields
721 }
722 }
723 }
724}
725
726pub type IfaceOptionField = DumpOptionField<SingleNetModelFieldId, IfaceAggField>;
727
728pub static DEFAULT_IFACE_FIELDS: &[IfaceOptionField] = &[
729 DumpOptionField::Unit(DumpField::Common(CommonField::Datetime)),
730 DumpOptionField::Unit(DumpField::FieldId(SingleNetModelFieldId::Collisions)),
731 DumpOptionField::Unit(DumpField::FieldId(SingleNetModelFieldId::Multicast)),
732 DumpOptionField::Unit(DumpField::FieldId(SingleNetModelFieldId::Interface)),
733 DumpOptionField::Agg(IfaceAggField::Rate),
734 DumpOptionField::Agg(IfaceAggField::Rx),
735 DumpOptionField::Agg(IfaceAggField::Tx),
736 DumpOptionField::Agg(IfaceAggField::Ethtool),
737 DumpOptionField::Unit(DumpField::Common(CommonField::Timestamp)),
738];
739
740const IFACE_ABOUT: &str = "Dump the link layer iface stats";
741
742static IFACE_LONG_ABOUT: Lazy<String> = Lazy::new(|| {
744 format!(
745 r#"{about}
746
747********************** Available fields **********************
748
749{common_fields}, {iface_fields}
750
751********************** Aggregated fields **********************
752
753* rate: includes [{agg_rate_fields}].
754
755* rx: includes [{agg_rx_fields}].
756
757* tx: includes [{agg_tx_fields}].
758
759* ethtool: includes [{agg_ethtool_fields}].
760
761* --detail: includes `raw_stats` field.
762
763* --default: includes [{default_fields}].
764
765* --everything: includes everything (equivalent to --default --detail).
766
767********************** Example Commands **********************
768
769Simple example:
770
771$ below dump iface -b "08:30:00" -e "08:30:30" -f interface rate -O csv
772
773Output stats for all iface stats matching pattern "eth*" for time slices
774from 08:30:00 to 08:30:30:
775
776$ below dump iface -b "08:30:00" -e "08:30:30" -s interface -F eth* -O json
777
778"#,
779 about = IFACE_ABOUT,
780 common_fields = join(enum_iterator::all::<CommonField>()),
781 iface_fields = join(enum_iterator::all::<SingleNetModelFieldId>()),
782 agg_rate_fields = join(IfaceAggField::Rate.expand(false)),
783 agg_rx_fields = join(IfaceAggField::Rx.expand(false)),
784 agg_tx_fields = join(IfaceAggField::Tx.expand(false)),
785 agg_ethtool_fields = join(IfaceAggField::Ethtool.expand(false)),
786 default_fields = join(DEFAULT_IFACE_FIELDS.to_owned()),
787 )
788});
789
790#[derive(
792 Clone,
793 Debug,
794 PartialEq,
795 below_derive::EnumFromStr,
796 below_derive::EnumToString
797)]
798pub enum NetworkAggField {
799 Ip,
800 Ip6,
801 Icmp,
802 Icmp6,
803}
804
805impl AggField<NetworkModelFieldId> for NetworkAggField {
806 fn expand(&self, _detail: bool) -> Vec<NetworkModelFieldId> {
807 use model::NetworkModelFieldId as FieldId;
808 match self {
809 Self::Ip => enum_iterator::all::<model::IpModelFieldId>()
810 .map(FieldId::Ip)
811 .collect(),
812 Self::Ip6 => enum_iterator::all::<model::Ip6ModelFieldId>()
813 .map(FieldId::Ip6)
814 .collect(),
815 Self::Icmp => enum_iterator::all::<model::IcmpModelFieldId>()
816 .map(FieldId::Icmp)
817 .collect(),
818 Self::Icmp6 => enum_iterator::all::<model::Icmp6ModelFieldId>()
819 .map(FieldId::Icmp6)
820 .collect(),
821 }
822 }
823}
824
825pub type NetworkOptionField = DumpOptionField<NetworkModelFieldId, NetworkAggField>;
826
827pub static DEFAULT_NETWORK_FIELDS: &[NetworkOptionField] = &[
828 DumpOptionField::Unit(DumpField::Common(CommonField::Datetime)),
829 DumpOptionField::Agg(NetworkAggField::Ip),
830 DumpOptionField::Agg(NetworkAggField::Ip6),
831 DumpOptionField::Agg(NetworkAggField::Icmp),
832 DumpOptionField::Agg(NetworkAggField::Icmp6),
833 DumpOptionField::Unit(DumpField::Common(CommonField::Timestamp)),
834];
835
836const NETWORK_ABOUT: &str = "Dump the network layer stats including ip and icmp";
837
838static NETWORK_LONG_ABOUT: Lazy<String> = Lazy::new(|| {
840 format!(
841 r#"{about}
842
843********************** Available fields **********************
844
845{common_fields}, {network_fields}
846
847********************** Aggregated fields **********************
848
849* ip: includes [{agg_ip_fields}].
850
851* ip6: includes [{agg_ip6_fields}].
852
853* icmp: includes [{agg_icmp_fields}].
854
855* icmp6: includes [{agg_icmp6_fields}].
856
857* --detail: no effect.
858
859* --default: includes [{default_fields}].
860
861* --everything: includes everything (equivalent to --default --detail).
862
863********************** Example Commands **********************
864
865Example:
866
867$ below dump network -b "08:30:00" -e "08:30:30" -f ip ip6 -O json
868
869"#,
870 about = NETWORK_ABOUT,
871 common_fields = join(enum_iterator::all::<CommonField>()),
872 network_fields = join(enum_iterator::all::<NetworkModelFieldId>()),
873 agg_ip_fields = join(NetworkAggField::Ip.expand(false)),
874 agg_ip6_fields = join(NetworkAggField::Ip6.expand(false)),
875 agg_icmp_fields = join(NetworkAggField::Icmp.expand(false)),
876 agg_icmp6_fields = join(NetworkAggField::Icmp6.expand(false)),
877 default_fields = join(DEFAULT_NETWORK_FIELDS.to_owned()),
878 )
879});
880
881#[derive(
883 Clone,
884 Debug,
885 PartialEq,
886 below_derive::EnumFromStr,
887 below_derive::EnumToString
888)]
889pub enum TransportAggField {
890 Tcp,
891 Udp,
892 Udp6,
893}
894
895impl AggField<NetworkModelFieldId> for TransportAggField {
896 fn expand(&self, _detail: bool) -> Vec<NetworkModelFieldId> {
897 use model::NetworkModelFieldId as FieldId;
898 match self {
899 Self::Tcp => enum_iterator::all::<model::TcpModelFieldId>()
900 .map(FieldId::Tcp)
901 .collect(),
902 Self::Udp => enum_iterator::all::<model::UdpModelFieldId>()
903 .map(FieldId::Udp)
904 .collect(),
905 Self::Udp6 => enum_iterator::all::<model::Udp6ModelFieldId>()
906 .map(FieldId::Udp6)
907 .collect(),
908 }
909 }
910}
911
912pub type TransportOptionField = DumpOptionField<NetworkModelFieldId, TransportAggField>;
913
914pub static DEFAULT_TRANSPORT_FIELDS: &[TransportOptionField] = &[
915 DumpOptionField::Unit(DumpField::Common(CommonField::Datetime)),
916 DumpOptionField::Agg(TransportAggField::Tcp),
917 DumpOptionField::Agg(TransportAggField::Udp),
918 DumpOptionField::Agg(TransportAggField::Udp6),
919 DumpOptionField::Unit(DumpField::Common(CommonField::Timestamp)),
920];
921
922const TRANSPORT_ABOUT: &str = "Dump the transport layer stats including tcp and udp";
923
924static TRANSPORT_LONG_ABOUT: Lazy<String> = Lazy::new(|| {
926 format!(
927 r#"{about}
928
929********************** Available fields **********************
930
931{common_fields}, {network_fields}.
932
933********************** Aggregated fields **********************
934
935* tcp: includes [{agg_tcp_fields}].
936
937* udp: includes [{agg_udp_fields}].
938
939* udp6: includes [{agg_udp6_fields}].
940
941* --detail: no effect.
942
943* --default: includes [{default_fields}].
944
945* --everything: includes everything (equivalent to --default --detail).
946
947********************** Example Commands **********************
948
949Example:
950
951$ below dump transport -b "08:30:00" -e "08:30:30" -f tcp udp -O json
952
953"#,
954 about = TRANSPORT_ABOUT,
955 common_fields = join(enum_iterator::all::<CommonField>()),
956 network_fields = join(enum_iterator::all::<NetworkModelFieldId>()),
957 agg_tcp_fields = join(TransportAggField::Tcp.expand(false)),
958 agg_udp_fields = join(TransportAggField::Udp.expand(false)),
959 agg_udp6_fields = join(TransportAggField::Udp6.expand(false)),
960 default_fields = join(DEFAULT_TRANSPORT_FIELDS.to_owned()),
961 )
962});
963
964#[derive(
966 Clone,
967 Debug,
968 PartialEq,
969 below_derive::EnumFromStr,
970 below_derive::EnumToString
971)]
972pub enum EthtoolQueueAggField {
973 Queue,
974}
975
976impl AggField<SingleQueueModelFieldId> for EthtoolQueueAggField {
977 fn expand(&self, detail: bool) -> Vec<SingleQueueModelFieldId> {
978 use model::SingleQueueModelFieldId::*;
979 let mut fields = vec![
980 Interface,
981 QueueId,
982 RxBytesPerSec,
983 TxBytesPerSec,
984 RxCountPerSec,
985 TxCountPerSec,
986 TxMissedTx,
987 TxUnmaskInterrupt,
988 ];
989 if detail {
990 fields.push(RawStats);
991 }
992 match self {
993 Self::Queue => fields,
994 }
995 }
996}
997
998pub type EthtoolQueueOptionField = DumpOptionField<SingleQueueModelFieldId, EthtoolQueueAggField>;
999
1000pub static DEFAULT_ETHTOOL_QUEUE_FIELDS: &[EthtoolQueueOptionField] = &[
1001 DumpOptionField::Unit(DumpField::Common(CommonField::Datetime)),
1002 DumpOptionField::Agg(EthtoolQueueAggField::Queue),
1003 DumpOptionField::Unit(DumpField::Common(CommonField::Timestamp)),
1004];
1005
1006const ETHTOOL_QUEUE_ABOUT: &str = "Dump network interface queue stats";
1007
1008static ETHTOOL_QUEUE_LONG_ABOUT: Lazy<String> = Lazy::new(|| {
1010 format!(
1011 r#"{about}
1012
1013********************** Available fields **********************
1014
1015{common_fields}, and expanded fields below.
1016
1017********************** Aggregated fields **********************
1018
1019* queue: includes [{agg_queue_fields}].
1020
1021* --detail: includes `raw_stats` field.
1022
1023* --default: includes [{default_fields}].
1024
1025* --everything: includes everything (equivalent to --default --detail).
1026
1027********************** Example Commands **********************
1028
1029Example:
1030
1031$ below dump ethtool-queue -b "08:30:00" -e "08:30:30" -O json
1032
1033"#,
1034 about = ETHTOOL_QUEUE_ABOUT,
1035 common_fields = join(enum_iterator::all::<CommonField>()),
1036 agg_queue_fields = join(EthtoolQueueAggField::Queue.expand(false)),
1037 default_fields = join(DEFAULT_ETHTOOL_QUEUE_FIELDS.to_owned()),
1038 )
1039});
1040
1041#[derive(
1043 Clone,
1044 Debug,
1045 PartialEq,
1046 below_derive::EnumFromStr,
1047 below_derive::EnumToString
1048)]
1049pub enum TcAggField {
1050 Stats,
1051 XStats,
1052 QDisc,
1053}
1054
1055impl AggField<SingleTcModelFieldId> for TcAggField {
1056 fn expand(&self, _detail: bool) -> Vec<SingleTcModelFieldId> {
1057 use model::SingleTcModelFieldId as FieldId;
1058
1059 match self {
1060 Self::Stats => vec![
1061 FieldId::Interface,
1062 FieldId::Kind,
1063 FieldId::Qlen,
1064 FieldId::Bps,
1065 FieldId::Pps,
1066 FieldId::BytesPerSec,
1067 FieldId::PacketsPerSec,
1068 FieldId::BacklogPerSec,
1069 FieldId::DropsPerSec,
1070 FieldId::RequeuesPerSec,
1071 FieldId::OverlimitsPerSec,
1072 ],
1073 Self::XStats => enum_iterator::all::<model::XStatsModelFieldId>()
1074 .map(FieldId::Xstats)
1075 .collect::<Vec<_>>(),
1076 Self::QDisc => enum_iterator::all::<model::QDiscModelFieldId>()
1077 .map(FieldId::Qdisc)
1078 .collect::<Vec<_>>(),
1079 }
1080 }
1081}
1082
1083pub type TcOptionField = DumpOptionField<SingleTcModelFieldId, TcAggField>;
1084
1085pub static DEFAULT_TC_FIELDS: &[TcOptionField] = &[
1086 DumpOptionField::Unit(DumpField::Common(CommonField::Datetime)),
1087 DumpOptionField::Agg(TcAggField::Stats),
1088 DumpOptionField::Agg(TcAggField::XStats),
1089 DumpOptionField::Agg(TcAggField::QDisc),
1090 DumpOptionField::Unit(DumpField::Common(CommonField::Timestamp)),
1091];
1092
1093const TC_ABOUT: &str = "Dump the tc related stats with qdiscs";
1094
1095static TC_LONG_ABOUT: Lazy<String> = Lazy::new(|| {
1097 format!(
1098 r#"{about}
1099********************** Available fields **********************
1100{common_fields}, {tc_fields}.
1101********************** Aggregated fields **********************
1102* --detail: no effect.
1103* --default: includes [{default_fields}].
1104* --everything: includes everything (equivalent to --default --detail).
1105********************** Example Commands **********************
1106Example:
1107$ below dump tc -b "08:30:00" -e "08:30:30" -O json
1108"#,
1109 about = TC_ABOUT,
1110 common_fields = join(enum_iterator::all::<CommonField>()),
1111 tc_fields = join(enum_iterator::all::<SingleTcModelFieldId>()),
1112 default_fields = join(DEFAULT_TC_FIELDS.to_owned()),
1113 )
1114});
1115
1116make_option! (OutputFormat {
1117 "raw": Raw,
1118 "csv": Csv,
1119 "tsv": Tsv,
1120 "json": Json,
1121 "kv": KeyVal,
1122 "openmetrics": OpenMetrics,
1123});
1124
1125#[derive(Debug, Parser, Default, Clone)]
1126pub struct GeneralOpt {
1127 #[clap(long)]
1129 pub default: bool,
1130 #[clap(long)]
1132 pub everything: bool,
1133 #[clap(short, long)]
1135 pub detail: bool,
1136 #[clap(long, short)]
1138 pub begin: String,
1139 #[clap(long, short, group = "time")]
1141 pub end: Option<String>,
1142 #[clap(long, group = "time")]
1147 pub duration: Option<String>,
1148 #[clap(long, short = 'F')]
1150 pub filter: Option<Regex>,
1151 #[clap(long)]
1153 pub sort: bool,
1154 #[clap(long)]
1156 pub rsort: bool,
1157 #[clap(long, default_value = "0")]
1159 pub top: u32,
1160 #[clap(long = "repeat-title")]
1162 pub repeat_title: Option<usize>,
1163 #[clap(long, short = 'O')]
1165 pub output_format: Option<OutputFormat>,
1166 #[clap(long, short)]
1168 pub output: Option<String>,
1169 #[clap(long)]
1171 pub disable_title: bool,
1172 #[clap(short = 'r')]
1174 pub yesterdays: Option<String>,
1175 #[clap(long)]
1177 pub br: Option<String>,
1178 #[clap(long)]
1180 pub raw: bool,
1181}
1182
1183#[derive(Debug, Parser, Clone)]
1184pub enum DumpCommand {
1185 #[clap(about = SYSTEM_ABOUT, long_about = SYSTEM_LONG_ABOUT.as_str())]
1186 System {
1187 #[clap(short, long, num_args = 1..)]
1189 fields: Option<Vec<SystemOptionField>>,
1190 #[clap(flatten)]
1191 opts: GeneralOpt,
1192 #[clap(long, short, conflicts_with("fields"))]
1194 pattern: Option<String>,
1195 },
1196 #[clap(about = DISK_ABOUT, long_about = DISK_LONG_ABOUT.as_str())]
1197 Disk {
1198 #[clap(short, long, num_args = 1..)]
1200 fields: Option<Vec<DiskOptionField>>,
1201 #[clap(flatten)]
1202 opts: GeneralOpt,
1203 #[clap(long, short)]
1205 select: Option<SingleDiskModelFieldId>,
1206 #[clap(long, short, conflicts_with("fields"))]
1208 pattern: Option<String>,
1209 },
1210 #[clap(about = BTRFS_ABOUT, long_about = BTRFS_LONG_ABOUT.as_str())]
1211 Btrfs {
1212 #[clap(short, long)]
1214 fields: Option<Vec<BtrfsOptionField>>,
1215 #[clap(flatten)]
1216 opts: GeneralOpt,
1217 #[clap(long, short)]
1219 select: Option<BtrfsModelFieldId>,
1220 #[clap(long, short, conflicts_with("fields"))]
1222 pattern: Option<String>,
1223 },
1224 #[clap(about = PROCESS_ABOUT, long_about = PROCESS_LONG_ABOUT.as_str())]
1225 Process {
1226 #[clap(short, long, num_args = 1..)]
1228 fields: Option<Vec<ProcessOptionField>>,
1229 #[clap(flatten)]
1230 opts: GeneralOpt,
1231 #[clap(long, short)]
1233 select: Option<SingleProcessModelFieldId>,
1234 #[clap(long, short, conflicts_with("fields"))]
1236 pattern: Option<String>,
1237 },
1238 #[clap(about = CGROUP_ABOUT, long_about = CGROUP_LONG_ABOUT.as_str())]
1239 Cgroup {
1240 #[clap(short, long, num_args = 1..)]
1242 fields: Option<Vec<CgroupOptionField>>,
1243 #[clap(flatten)]
1244 opts: GeneralOpt,
1245 #[clap(long, short)]
1247 select: Option<SingleCgroupModelFieldId>,
1248 #[clap(long, short, conflicts_with("fields"))]
1250 pattern: Option<String>,
1251 },
1252 #[clap(about = IFACE_ABOUT, long_about = IFACE_LONG_ABOUT.as_str())]
1253 Iface {
1254 #[clap(short, long, num_args = 1..)]
1256 fields: Option<Vec<IfaceOptionField>>,
1257 #[clap(flatten)]
1258 opts: GeneralOpt,
1259 #[clap(long, short)]
1261 select: Option<SingleNetModelFieldId>,
1262 #[clap(long, short, conflicts_with("fields"))]
1264 pattern: Option<String>,
1265 },
1266 #[clap(about = NETWORK_ABOUT, long_about = NETWORK_LONG_ABOUT.as_str())]
1267 Network {
1268 #[clap(short, long, num_args = 1..)]
1270 fields: Option<Vec<NetworkOptionField>>,
1271 #[clap(flatten)]
1272 opts: GeneralOpt,
1273 #[clap(long, short, conflicts_with("fields"))]
1275 pattern: Option<String>,
1276 },
1277 #[clap(about = TRANSPORT_ABOUT, long_about = TRANSPORT_LONG_ABOUT.as_str())]
1278 Transport {
1279 #[clap(short, long, num_args = 1..)]
1281 fields: Option<Vec<TransportOptionField>>,
1282 #[clap(flatten)]
1283 opts: GeneralOpt,
1284 #[clap(long, short, conflicts_with("fields"))]
1286 pattern: Option<String>,
1287 },
1288 #[clap(about = ETHTOOL_QUEUE_ABOUT, long_about = ETHTOOL_QUEUE_LONG_ABOUT.as_str())]
1289 EthtoolQueue {
1290 #[clap(short, long, num_args = 1..)]
1292 fields: Option<Vec<EthtoolQueueOptionField>>,
1293 #[clap(flatten)]
1294 opts: GeneralOpt,
1295 #[clap(long, short, conflicts_with("fields"))]
1297 pattern: Option<String>,
1298 },
1299 #[clap(about = TC_ABOUT, long_about = TC_LONG_ABOUT.as_str())]
1300 Tc {
1301 #[clap(short, long)]
1303 fields: Option<Vec<TcOptionField>>,
1304 #[clap(flatten)]
1305 opts: GeneralOpt,
1306 #[clap(long, short, conflicts_with("fields"))]
1308 pattern: Option<String>,
1309 },
1310}