below_dump/
command.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 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
38/// Field that represents a group of related FieldIds of a Queriable.
39/// Shorthand for specifying fields to dump.
40pub trait AggField<F: FieldId> {
41    fn expand(&self, detail: bool) -> Vec<F>;
42}
43
44/// Generic representation of fields accepted by different dump subcommands.
45/// Each DumpOptionField is either an aggregation of multiple FieldIds, or a
46/// "unit" field which could be either a CommonField or a FieldId.
47#[derive(Clone, Debug, PartialEq)]
48pub enum DumpOptionField<F: FieldId, A: AggField<F>> {
49    Unit(DumpField<F>),
50    Agg(A),
51}
52
53/// Expand the Agg fields and collect them with other Unit fields.
54pub 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
70/// Used by Clap to parse user provided --fields.
71impl<F: FieldId + FromStr, A: AggField<F> + FromStr> FromStr for DumpOptionField<F, A> {
72    type Err = Error;
73
74    /// When parsing command line options into DumpOptionField, priority order
75    /// is CommonField, AggField, and then FieldId.
76    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
89/// Used for generating help string that lists all supported fields.
90impl<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
100/// Join stringified items with ", ". Used for generating help string that lists
101/// all supported fields.
102fn 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
109// make_option macro will build a enum of tags that map to string values by
110// implementing the FromStr trait.
111// This is useful when are trying to processing or display fields base on
112// a user's input. Here's a use case:
113// We display fields in the order of user's input. After we got
114// the input array, dfill trait will automatically generate a vec of fns base
115// on that array. For example, user input `--fields cpu_usage cpu_user`,
116// enum generated by make_option will auto translate string to enum tags. After
117// that dfill trait will generate `vec![print_cpu_usage, print_cpu_user]`. And
118// the dprint trait will just iterate over the fns and call it with current model.
119//
120// Another user case is for the select feature, we don't want a giant match
121// of string patterns once user select some field to do some operations. Instead,
122// we can use a match of enum tags, that will be much faster.
123macro_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/// Represents the four sub-model of SystemModel.
144#[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                    // The Idx field is always -1 (we aggregate all CPUs)
170                    .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            // Default fields for each group
179            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
211/// Generated about message for System dump so supported fields are up-to-date.
212static 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
316/// Generated about message for System dump so supported fields are up-to-date.
317static 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/// Represents the four sub-model of ProcessModel.
437#[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            // Default fields for each group
465            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
494/// Generated about message for Process dump so supported fields are up-to-date.
495static 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/// Represents the four sub-model of SingleCgroupModel.
543#[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            // Default fields for each group
579            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
609/// Generated about message for Cgroup dump so supported fields are up-to-date.
610static 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/// Represents the iface sub-models of network model.
663#[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
742/// Generated about message for Iface dump so supported fields are up-to-date.
743static 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/// Represents the ip and icmp sub-models of the network model.
791#[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
838/// Generated about message for Network dump so supported fields are up-to-date.
839static 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/// Represents the tcp and udp sub-models of the network model.
882#[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
924/// Generated about message for Transport dump so supported fields are up-to-date.
925static 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/// Represents the ethtool queue sub-model of the network model.
965#[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
1008/// Generated about message for Ethtool Queue dump so supported fields are up-to-date.
1009static 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/// Represents the fields of the tc model.
1042#[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
1095/// Generated about message for tc (traffic control) dump so supported fields are up-to-date.
1096static 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    /// Show all top layer fields. If --default is specified, it overrides any specified fields via --fields.
1128    #[clap(long)]
1129    pub default: bool,
1130    /// Show all fields. If --everything is specified, --fields and --default are overridden.
1131    #[clap(long)]
1132    pub everything: bool,
1133    /// Show more infomation other than default.
1134    #[clap(short, long)]
1135    pub detail: bool,
1136    /// Begin time, same format as replay
1137    #[clap(long, short)]
1138    pub begin: String,
1139    /// End time, same format as replay
1140    #[clap(long, short, group = "time")]
1141    pub end: Option<String>,
1142    /// Time string specifying the duration, e.g. "10 min"{n}
1143    /// Keywords: days min, h, sec{n}
1144    /// Relative: {humantime}, e.g. "2 days 3 hr 15m 10sec"{n}
1145    /// _
1146    #[clap(long, group = "time")]
1147    pub duration: Option<String>,
1148    /// Take a regex and apply to --select selected field. See command level doc for example.
1149    #[clap(long, short = 'F')]
1150    pub filter: Option<Regex>,
1151    /// Sort (lower to higher) by --select selected field. See command level doc for example.
1152    #[clap(long)]
1153    pub sort: bool,
1154    /// Sort (higher to lower) by --select selected field. See command level doc for example.
1155    #[clap(long)]
1156    pub rsort: bool,
1157    // display top N field. See command level doc for example.
1158    #[clap(long, default_value = "0")]
1159    pub top: u32,
1160    /// Repeat title, for each N line, it will render a line of title. Only for raw output format.
1161    #[clap(long = "repeat-title")]
1162    pub repeat_title: Option<usize>,
1163    /// Output format. Choose from raw, csv, tsv, kv, json, openmetrics. Default to raw
1164    #[clap(long, short = 'O')]
1165    pub output_format: Option<OutputFormat>,
1166    /// Output destination, default to stdout.
1167    #[clap(long, short)]
1168    pub output: Option<String>,
1169    /// Disable title in raw, csv or tsv format output
1170    #[clap(long)]
1171    pub disable_title: bool,
1172    /// Days adjuster, same as -r option in replay.
1173    #[clap(short = 'r')]
1174    pub yesterdays: Option<String>,
1175    /// Line break symbol between samples
1176    #[clap(long)]
1177    pub br: Option<String>,
1178    /// Dump raw data without units or conversion
1179    #[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        /// Select which fields to display and in what order.
1188        #[clap(short, long, num_args = 1..)]
1189        fields: Option<Vec<SystemOptionField>>,
1190        #[clap(flatten)]
1191        opts: GeneralOpt,
1192        /// Saved pattern in the dumprc file under [system] section.
1193        #[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        /// Select which fields to display and in what order.
1199        #[clap(short, long, num_args = 1..)]
1200        fields: Option<Vec<DiskOptionField>>,
1201        #[clap(flatten)]
1202        opts: GeneralOpt,
1203        /// Select field for operation, use with --sort, --rsort, --filter, --top
1204        #[clap(long, short)]
1205        select: Option<SingleDiskModelFieldId>,
1206        /// Saved pattern in the dumprc file under [disk] section.
1207        #[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        /// Select which fields to display and in what order.
1213        #[clap(short, long)]
1214        fields: Option<Vec<BtrfsOptionField>>,
1215        #[clap(flatten)]
1216        opts: GeneralOpt,
1217        /// Select field for operation, use with --sort, --rsort, --filter, --top
1218        #[clap(long, short)]
1219        select: Option<BtrfsModelFieldId>,
1220        /// Saved pattern in the dumprc file under [btrfs] section.
1221        #[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        /// Select which fields to display and in what order.
1227        #[clap(short, long, num_args = 1..)]
1228        fields: Option<Vec<ProcessOptionField>>,
1229        #[clap(flatten)]
1230        opts: GeneralOpt,
1231        /// Select field for operation, use with --sort, --rsort, --filter, --top
1232        #[clap(long, short)]
1233        select: Option<SingleProcessModelFieldId>,
1234        /// Saved pattern in the dumprc file under [process] section.
1235        #[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        /// Select which fields to display and in what order.
1241        #[clap(short, long, num_args = 1..)]
1242        fields: Option<Vec<CgroupOptionField>>,
1243        #[clap(flatten)]
1244        opts: GeneralOpt,
1245        /// Select field for operation, use with --sort, --rsort, --filter, --top
1246        #[clap(long, short)]
1247        select: Option<SingleCgroupModelFieldId>,
1248        /// Saved pattern in the dumprc file under [cgroup] section.
1249        #[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        /// Select which fields to display and in what order.
1255        #[clap(short, long, num_args = 1..)]
1256        fields: Option<Vec<IfaceOptionField>>,
1257        #[clap(flatten)]
1258        opts: GeneralOpt,
1259        /// Select field for operation, use with --filter
1260        #[clap(long, short)]
1261        select: Option<SingleNetModelFieldId>,
1262        /// Saved pattern in the dumprc file under [iface] section.
1263        #[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        /// Select which fields to display and in what order.
1269        #[clap(short, long, num_args = 1..)]
1270        fields: Option<Vec<NetworkOptionField>>,
1271        #[clap(flatten)]
1272        opts: GeneralOpt,
1273        /// Saved pattern in the dumprc file under [network] section.
1274        #[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        /// Select which fields to display and in what order.
1280        #[clap(short, long, num_args = 1..)]
1281        fields: Option<Vec<TransportOptionField>>,
1282        #[clap(flatten)]
1283        opts: GeneralOpt,
1284        /// Saved pattern in the dumprc file under [transport] section.
1285        #[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        /// Select which fields to display and in what order.
1291        #[clap(short, long, num_args = 1..)]
1292        fields: Option<Vec<EthtoolQueueOptionField>>,
1293        #[clap(flatten)]
1294        opts: GeneralOpt,
1295        /// Saved pattern in the dumprc file under [ethtool] section.
1296        #[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        /// Select which fields to display and in what order.
1302        #[clap(short, long)]
1303        fields: Option<Vec<TcOptionField>>,
1304        #[clap(flatten)]
1305        opts: GeneralOpt,
1306        /// Saved pattern in the dumprc file under [tc] section.
1307        #[clap(long, short, conflicts_with("fields"))]
1308        pattern: Option<String>,
1309    },
1310}