summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormmynk <mohit.ritanil@gmail.com>2024-04-25 06:28:55 -0700
committerFacebook GitHub Bot <facebook-github-bot@users.noreply.github.com>2024-04-25 06:28:55 -0700
commit4acd7488eaea9147cd879b41ff9826dd8a45a52b (patch)
tree415d9de624d45e1e606835ef127c9c614cbe59f2
parent5ad32a5b1d76d18d1cb09b8f76b1649a5d56a157 (diff)
Add sub-module for reading `tc` stats (#8210)
Summary: - Adds the sub-crate for reading `tc` stats - Uses `netlink-packet-route` library which reads `qdisc`s via rtnetlink Result: ```sh $ below dump tc -b '5s ago' Datetime Interface Kind Queue Length Bps Pps Bytes Packets Backlog Drops Requeues Overlimits MaxPacket EcnMark NewFlowsLen OldFlowsLen CeMark DropOverlimit NewFlowCount MemoryUsage DropOvermemory Target Limit Interval Ecn Quantum CeThreshold DropBatchSize MemoryLimit Flows Timestamp 2024-01-30 20:24:31 lo noqueue 0 0.0 B/s 0/s 0.0 B/s 0/s 0/s 0/s 0/s 0/s ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 1706646271 2024-01-30 20:24:31 ens5 mq 0 0.0 B/s 0/s 167 B/s 2/s 0/s 0/s 0/s 0/s ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 1706646271 2024-01-30 20:24:31 ens5 fq_codel 0 0.0 B/s 0/s 0.0 B/s 0/s 0/s 0/s 0/s 0/s 110 0 0 0 0 0/s 0/s 0/s 0/s 4999 10240 99999 1 1514 0 64 33554432 0/s 1706646271 2024-01-30 20:24:31 ens5 fq_codel 0 0.0 B/s 0/s 167 B/s 2/s 0/s 0/s 0/s 0/s 182 0 0 0 0 0/s 0/s 0/s 0/s 4999 10240 99999 1 1514 0 64 33554432 0/s 1706646271 ``` Pull Request resolved: https://github.com/facebookincubator/below/pull/8210 Reviewed By: lnyng Differential Revision: D56263764 Pulled By: brianc118 fbshipit-source-id: bc225ddd29ddd81fa34d0f0f106177f4a1e2d4b9
-rw-r--r--Cargo.lock99
-rw-r--r--Cargo.toml1
-rw-r--r--below/config/src/lib.rs2
-rw-r--r--below/dump/src/command.rs87
-rw-r--r--below/dump/src/lib.rs38
-rw-r--r--below/dump/src/tc.rs105
-rw-r--r--below/dump/src/test.rs210
-rw-r--r--below/model/Cargo.toml1
-rw-r--r--below/model/src/collector.rs22
-rw-r--r--below/model/src/common_field_ids.rs31
-rw-r--r--below/model/src/lib.rs15
-rw-r--r--below/model/src/sample.rs1
-rw-r--r--below/model/src/sample_model.rs43
-rw-r--r--below/model/src/tc_collector_plugin.rs35
-rw-r--r--below/model/src/tc_model.rs242
-rw-r--r--below/render/src/default_configs.rs180
-rw-r--r--below/src/main.rs75
-rw-r--r--below/tc/Cargo.toml18
-rw-r--r--below/tc/README.md5
-rw-r--r--below/tc/src/errors.rs13
-rw-r--r--below/tc/src/lib.rs136
-rw-r--r--below/tc/src/test.rs145
-rw-r--r--below/tc/src/types.rs224
23 files changed, 1715 insertions, 13 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 331a43c8..e1f0f1f4 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -276,6 +276,7 @@ dependencies = [
"below-common",
"below-ethtool",
"below-gpu-stats",
+ "below-tc",
"below_derive",
"cgroupfs",
"enum-iterator",
@@ -325,6 +326,19 @@ dependencies = [
]
[[package]]
+name = "below-tc"
+version = "0.0.1"
+dependencies = [
+ "netlink-packet-core",
+ "netlink-packet-route",
+ "netlink-packet-utils",
+ "netlink-sys",
+ "nix 0.27.1",
+ "serde",
+ "thiserror",
+]
+
+[[package]]
name = "below-view"
version = "0.8.1"
dependencies = [
@@ -379,10 +393,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
name = "bytes"
-version = "1.2.1"
+version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
+checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
dependencies = [
"serde",
]
@@ -578,7 +598,7 @@ dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
- "memoffset",
+ "memoffset 0.6.5",
"scopeguard",
]
@@ -1082,9 +1102,9 @@ dependencies = [
[[package]]
name = "hermit-abi"
-version = "0.3.1"
+version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
+checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "hostname"
@@ -1295,12 +1315,9 @@ dependencies = [
[[package]]
name = "log"
-version = "0.4.17"
+version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
-dependencies = [
- "cfg-if",
-]
+checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "maplit"
@@ -1339,6 +1356,15 @@ dependencies = [
]
[[package]]
+name = "memoffset"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
name = "miniz_oxide"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1360,6 +1386,54 @@ dependencies = [
]
[[package]]
+name = "netlink-packet-core"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4"
+dependencies = [
+ "anyhow",
+ "byteorder",
+ "netlink-packet-utils",
+]
+
+[[package]]
+name = "netlink-packet-route"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74c171cd77b4ee8c7708da746ce392440cb7bcf618d122ec9ecc607b12938bf4"
+dependencies = [
+ "anyhow",
+ "byteorder",
+ "libc",
+ "log",
+ "netlink-packet-core",
+ "netlink-packet-utils",
+]
+
+[[package]]
+name = "netlink-packet-utils"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34"
+dependencies = [
+ "anyhow",
+ "byteorder",
+ "paste",
+ "thiserror",
+]
+
+[[package]]
+name = "netlink-sys"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6471bf08e7ac0135876a9581bf3217ef0333c191c128d34878079f42ee150411"
+dependencies = [
+ "bytes",
+ "libc",
+ "log",
+]
+
+[[package]]
name = "nix"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1369,7 +1443,7 @@ dependencies = [
"bitflags 1.3.2",
"cfg-if",
"libc",
- "memoffset",
+ "memoffset 0.6.5",
"pin-utils",
]
@@ -1382,6 +1456,7 @@ dependencies = [
"bitflags 2.4.0",
"cfg-if",
"libc",
+ "memoffset 0.9.1",
]
[[package]]
@@ -1454,7 +1529,7 @@ version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
- "hermit-abi 0.3.1",
+ "hermit-abi 0.3.9",
"libc",
]
diff --git a/Cargo.toml b/Cargo.toml
index 6dc8f046..316538d4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,6 +14,7 @@ members = [
"below/render",
"below/resctrlfs",
"below/store",
+ "below/tc",
"below/view",
]
resolver = "2"
diff --git a/below/config/src/lib.rs b/below/config/src/lib.rs
index 3298c597..1f82e9eb 100644
--- a/below/config/src/lib.rs
+++ b/below/config/src/lib.rs
@@ -47,6 +47,7 @@ pub struct BelowConfig {
pub btrfs_min_pct: f64,
pub enable_ethtool_stats: bool,
pub enable_resctrl_stats: bool,
+ pub enable_tc_stats: bool,
}
impl Default for BelowConfig {
@@ -63,6 +64,7 @@ impl Default for BelowConfig {
btrfs_min_pct: btrfs::DEFAULT_MIN_PCT,
enable_ethtool_stats: false,
enable_resctrl_stats: false,
+ enable_tc_stats: false,
}
}
}
diff --git a/below/dump/src/command.rs b/below/dump/src/command.rs
index 300a61af..6a0aa0f7 100644
--- a/below/dump/src/command.rs
+++ b/below/dump/src/command.rs
@@ -26,6 +26,7 @@ use model::SingleDiskModelFieldId;
use model::SingleNetModelFieldId;
use model::SingleProcessModelFieldId;
use model::SingleQueueModelFieldId;
+use model::SingleTcModelFieldId;
use model::SystemModelFieldId;
use once_cell::sync::Lazy;
use regex::Regex;
@@ -1036,6 +1037,81 @@ $ below dump ethtool-queue -b "08:30:00" -e "08:30:30" -O json
)
});
+/// Represents the fields of the tc model.
+#[derive(
+ Clone,
+ Debug,
+ PartialEq,
+ below_derive::EnumFromStr,
+ below_derive::EnumToString
+)]
+pub enum TcAggField {
+ Stats,
+ XStats,
+ QDisc,
+}
+
+impl AggField<SingleTcModelFieldId> for TcAggField {
+ fn expand(&self, _detail: bool) -> Vec<SingleTcModelFieldId> {
+ use model::SingleTcModelFieldId as FieldId;
+
+ match self {
+ Self::Stats => vec![
+ FieldId::Interface,
+ FieldId::Kind,
+ FieldId::Qlen,
+ FieldId::Bps,
+ FieldId::Pps,
+ FieldId::BytesPerSec,
+ FieldId::PacketsPerSec,
+ FieldId::BacklogPerSec,
+ FieldId::DropsPerSec,
+ FieldId::RequeuesPerSec,
+ FieldId::OverlimitsPerSec,
+ ],
+ Self::XStats => enum_iterator::all::<model::XStatsModelFieldId>()
+ .map(FieldId::Xstats)
+ .collect::<Vec<_>>(),
+ Self::QDisc => enum_iterator::all::<model::QDiscModelFieldId>()
+ .map(FieldId::Qdisc)
+ .collect::<Vec<_>>(),
+ }
+ }
+}
+
+pub type TcOptionField = DumpOptionField<SingleTcModelFieldId, TcAggField>;
+
+pub static DEFAULT_TC_FIELDS: &[TcOptionField] = &[
+ DumpOptionField::Unit(DumpField::Common(CommonField::Datetime)),
+ DumpOptionField::Agg(TcAggField::Stats),
+ DumpOptionField::Agg(TcAggField::XStats),
+ DumpOptionField::Agg(TcAggField::QDisc),
+ DumpOptionField::Unit(DumpField::Common(CommonField::Timestamp)),
+];
+
+const TC_ABOUT: &str = "Dump the tc related stats with qdiscs";
+
+/// Generated about message for tc (traffic control) dump so supported fields are up-to-date.
+static TC_LONG_ABOUT: Lazy<String> = Lazy::new(|| {
+ format!(
+ r#"{about}
+********************** Available fields **********************
+{common_fields}, {tc_fields}.
+********************** Aggregated fields **********************
+* --detail: no effect.
+* --default: includes [{default_fields}].
+* --everything: includes everything (equivalent to --default --detail).
+********************** Example Commands **********************
+Example:
+$ below dump tc -b "08:30:00" -e "08:30:30" -O json
+"#,
+ about = TC_ABOUT,
+ common_fields = join(enum_iterator::all::<CommonField>()),
+ tc_fields = join(enum_iterator::all::<SingleTcModelFieldId>()),
+ default_fields = join(DEFAULT_TC_FIELDS.to_owned()),
+ )
+});
+
make_option! (OutputFormat {
"raw": Raw,
"csv": Csv,
@@ -1219,4 +1295,15 @@ pub enum DumpCommand {
#[clap(long, short, conflicts_with("fields"))]
pattern: Option<String>,
},
+ #[clap(about = TC_ABOUT, long_about = TC_LONG_ABOUT.as_str())]
+ Tc {
+ /// Select which fields to display and in what order.
+ #[clap(short, long)]
+ fields: Option<Vec<TcOptionField>>,
+ #[clap(flatten)]
+ opts: GeneralOpt,
+ /// Saved pattern in the dumprc file under [tc] section.
+ #[clap(long, short, conflicts_with("fields"))]
+ pattern: Option<String>,
+ },
}
diff --git a/below/dump/src/lib.rs b/below/dump/src/lib.rs
index 86f3ac01..ba1a65f5 100644
--- a/below/dump/src/lib.rs
+++ b/below/dump/src/lib.rs
@@ -52,6 +52,7 @@ pub mod network;
pub mod print;
pub mod process;
pub mod system;
+pub mod tc;
pub mod tmain;
pub mod transport;
@@ -117,6 +118,7 @@ pub type IfaceField = DumpField<model::SingleNetModelFieldId>;
// Essentially the same as NetworkField
pub type TransportField = DumpField<model::NetworkModelFieldId>;
pub type EthtoolQueueField = DumpField<model::SingleQueueModelFieldId>;
+pub type TcField = DumpField<model::SingleTcModelFieldId>;
fn get_advance(
logger: slog::Logger,
@@ -559,5 +561,41 @@ pub fn run(
errs,
)
}
+ DumpCommand::Tc {
+ fields,
+ opts,
+ pattern,
+ } => {
+ let (time_begin, time_end, advance) =
+ get_advance(logger, dir, host, port, snapshot, &opts)?;
+ let detail = opts.everything || opts.detail;
+ let fields = if let Some(pattern_key) = pattern {
+ parse_pattern(filename, pattern_key, "tc")
+ } else {
+ fields
+ };
+ let fields = expand_fields(
+ match fields.as_ref() {
+ Some(fields) => fields,
+ _ => command::DEFAULT_TC_FIELDS,
+ },
+ detail,
+ );
+ let tc = tc::Tc::new(&opts, fields);
+ let mut output: Box<dyn Write> = match opts.output.as_ref() {
+ Some(file_path) => Box::new(File::create(file_path)?),
+ None => Box::new(io::stdout()),
+ };
+ dump_timeseries(
+ advance,
+ time_begin,
+ time_end,
+ &tc,
+ output.as_mut(),
+ opts.output_format,
+ opts.br,
+ errs,
+ )
+ }
}
}
diff --git a/below/dump/src/tc.rs b/below/dump/src/tc.rs
new file mode 100644
index 00000000..01c9baa3
--- /dev/null
+++ b/below/dump/src/tc.rs
@@ -0,0 +1,105 @@
+use model::SingleTcModel;
+
+use super::*;
+
+pub struct Tc {
+ opts: GeneralOpt,
+ fields: Vec<TcField>,
+}
+
+impl Tc {
+ pub fn new(opts: &GeneralOpt, fields: Vec<TcField>) -> Self {
+ Self {
+ opts: opts.to_owned(),
+ fields,
+ }
+ }
+}
+
+impl Dumper for Tc {
+ fn dump_model(
+ &self,
+ ctx: &CommonFieldContext,
+ model: &model::Model,
+ output: &mut dyn Write,
+ round: &mut usize,
+ comma_flag: bool,
+ ) -> Result<IterExecResult> {
+ let tcs: Vec<&SingleTcModel> = match &model.tc {
+ Some(tc_model) => tc_model.tc.iter().collect(),
+ None => Vec::new(),
+ };
+ if tcs.is_empty() {
+ return Ok(IterExecResult::Skip);
+ }
+
+ let mut json_output = json!([]);
+
+ tcs.into_iter()
+ .map(|tc| {
+ match self.opts.output_format {
+ Some(OutputFormat::Raw) | None => write!(
+ output,
+ "{}",
+ print::dump_raw(
+ &self.fields,
+ ctx,
+ tc,
+ *round,
+ self.opts.repeat_title,
+ self.opts.disable_title,
+ self.opts.raw
+ )
+ )?,
+ Some(OutputFormat::Csv) => write!(
+ output,
+ "{}",
+ print::dump_csv(
+ &self.fields,
+ ctx,
+ tc,
+ *round,
+ self.opts.disable_title,
+ self.opts.raw
+ )
+ )?,
+ Some(OutputFormat::Tsv) => write!(
+ output,
+ "{}",
+ print::dump_tsv(
+ &self.fields,
+ ctx,
+ tc,
+ *round,
+ self.opts.disable_title,
+ self.opts.raw
+ )
+ )?,
+ Some(OutputFormat::KeyVal) => write!(
+ output,
+ "{}",
+ print::dump_kv(&self.fields, ctx, tc, self.opts.raw)
+ )?,
+ Some(OutputFormat::Json) => {
+ let par = print::dump_json(&self.fields, ctx, tc, self.opts.raw);
+ json_output.as_array_mut().unwrap().push(par);
+ }
+ Some(OutputFormat::OpenMetrics) => {
+ write!(output, "{}", print::dump_openmetrics(&self.fields, ctx, tc))?
+ }
+ }
+ *round += 1;
+ Ok(())
+ })
+ .collect::<Result<Vec<_>>>()?;
+
+ match (self.opts.output_format, comma_flag) {
+ (Some(OutputFormat::Json), true) => write!(output, ",{}", json_output)?,
+ (Some(OutputFormat::Json), false) => write!(output, "{}", json_output)?,
+ (Some(OutputFormat::OpenMetrics), _) => (),
+ _ => writeln!(output)?,
+ };
+
+ Ok(IterExecResult::Success)
+ }
+}
diff --git a/below/dump/src/test.rs b/below/dump/src/test.rs
index 6fb44574..4a0d53f5 100644
--- a/below/dump/src/test.rs
+++ b/below/dump/src/test.rs
@@ -983,6 +983,7 @@ fn test_dump_queue_content() {
network,
gpu: None,
resctrl: None,
+ tc: None,
};
let mut opts: GeneralOpt = Default::default();
@@ -1220,3 +1221,212 @@ proc = ["datetime", "mem.anon"]
))
);
}
+
+#[test]
+fn test_tc_titles() {
+ let titles = expand_fields(command::DEFAULT_TC_FIELDS, true)
+ .iter()
+ .filter_map(|dump_field| match dump_field {
+ DumpField::Common(_) => None,
+ DumpField::FieldId(field_id) => Some(field_id.to_string()),
+ })
+ .collect::<Vec<_>>();
+
+ let expected_titles = vec![
+ "interface",
+ "kind",
+ "qlen",
+ "bps",
+ "pps",
+ "bytes_per_sec",
+ "packets_per_sec",
+ "backlog_per_sec",
+ "drops_per_sec",
+ "requeues_per_sec",
+ "overlimits_per_sec",
+ "xstats.fq_codel.maxpacket",
+ "xstats.fq_codel.ecn_mark",
+ "xstats.fq_codel.new_flows_len",
+ "xstats.fq_codel.old_flows_len",
+ "xstats.fq_codel.ce_mark",
+ "xstats.fq_codel.drop_overlimit_per_sec",
+ "xstats.fq_codel.new_flow_count_per_sec",
+ "xstats.fq_codel.memory_usage_per_sec",
+ "xstats.fq_codel.drop_overmemory_per_sec",
+ "qdisc.fq_codel.target",
+ "qdisc.fq_codel.limit",
+ "qdisc.fq_codel.interval",
+ "qdisc.fq_codel.ecn",
+ "qdisc.fq_codel.quantum",
+ "qdisc.fq_codel.ce_threshold",
+ "qdisc.fq_codel.drop_batch_size",
+ "qdisc.fq_codel.memory_limit",
+ "qdisc.fq_codel.flows_per_sec",
+ ];
+ assert_eq!(titles, expected_titles);
+}
+
+#[test]
+fn test_dump_tc_content() {
+ let tc_models = vec![
+ model::SingleTcModel {
+ interface: "eth0".to_string(),
+ kind: "mq".to_string(),
+ qlen: Some(42),
+ bps: Some(420),
+ pps: Some(1337),
+ bytes_per_sec: Some(299792458),
+ packets_per_sec: Some(314),
+ backlog_per_sec: Some(271828182),
+ drops_per_sec: Some(8675309),
+ requeues_per_sec: Some(12345),
+ overlimits_per_sec: Some(314159),
+ qdisc: None,
+ xstats: None,
+ },
+ model::SingleTcModel {
+ interface: "eth0".to_string(),
+ kind: "fq_codel".to_string(),
+ qlen: Some(42),
+ bps: Some(420),
+ pps: Some(1337),
+ bytes_per_sec: Some(299792458),
+ packets_per_sec: Some(314),
+ backlog_per_sec: Some(271828182),
+ drops_per_sec: Some(8675309),
+ requeues_per_sec: Some(12345),
+ overlimits_per_sec: Some(314159),
+ qdisc: Some(model::QDiscModel {
+ fq_codel: Some(model::FqCodelQDiscModel {
+ target: 2701,
+ limit: 7,
+ interval: 3,
+ ecn: 6,
+ quantum: 42,
+ ce_threshold: 101,
+ drop_batch_size: 9000,
+ memory_limit: 123456,
+ flows_per_sec: Some(31415),
+ }),
+ }),
+ xstats: Some(model::XStatsModel {
+ fq_codel: Some(model::FqCodelXStatsModel {
+ maxpacket: 8675309,
+ ecn_mark: 299792458,
+ new_flows_len: 314,
+ old_flows_len: 1729,
+ ce_mark: 42,
+ drop_overlimit_per_sec: Some(420),
+ new_flow_count_per_sec: Some(1337),
+ memory_usage_per_sec: Some(271828182),
+ drop_overmemory_per_sec: Some(27182),
+ }),
+ }),
+ },
+ ];
+
+ let model = model::Model {
+ time_elapsed: Duration::from_secs(60 * 10),
+ timestamp: SystemTime::now(),
+ system: model::SystemModel::default(),
+ cgroup: model::CgroupModel::default(),
+ process: model::ProcessModel::default(),
+ network: model::NetworkModel::default(),
+ gpu: None,
+ resctrl: None,
+ tc: Some(model::TcModel { tc: tc_models }),
+ };
+
+ let mut opts: GeneralOpt = Default::default();
+ let fields = command::expand_fields(command::DEFAULT_TC_FIELDS, true);
+
+ opts.output_format = Some(OutputFormat::Json);
+ let queue_dumper = tc::Tc::new(&opts, fields.clone());
+
+ let mut queue_content: Vec<u8> = Vec::new();
+ let mut round = 0;
+ let ctx = CommonFieldContext {
+ timestamp: 0,
+ hostname: "h".to_string(),
+ };
+
+ let result = queue_dumper
+ .dump_model(&ctx, &model, &mut queue_content, &mut round, false)
+ .expect("Failed to dump queue model");
+ assert!(result == tmain::IterExecResult::Success);
+
+ // verify json correctness
+ assert!(!queue_content.is_empty());
+ let jval: Value =
+ serde_json::from_slice(&queue_content).expect("Fail parse json of queue dump");
+
+ let expected_json = json!([
+ {
+ "Datetime": "1969-12-31 16:00:00",
+ "Interface": "eth0",
+ "Kind": "mq",
+ "Queue Length": "42",
+ "Bps": "420 B/s",
+ "Pps": "1337/s",
+ "Bytes": "285.9 MB/s",
+ "Packets": "314/s",
+ "Backlog": "271828182/s",
+ "Drops": "8675309/s",
+ "Requeues": "12345/s",
+ "Overlimits": "314159/s",
+ "Target": "?",
+ "Limit": "?",
+ "Interval": "?",
+ "Ecn": "?",
+ "Quantum": "?",
+ "CeThreshold": "?",
+ "DropBatchSize": "?",
+ "MemoryLimit": "?",
+ "Flows": "?",
+ "MaxPacket": "?",
+ "EcnMark": "?",
+ "NewFlowsLen": "?",
+ "OldFlowsLen": "?",
+ "CeMark": "?",
+ "DropOverlimit": "?",
+ "NewFlowCount": "?",
+ "MemoryUsage": "?",
+ "DropOvermemory": "?",
+ "Timestamp": "0"
+ },
+ {
+ "Datetime": "1969-12-31 16:00:00",
+ "Interface": "eth0",
+ "Kind": "fq_codel",
+ "Queue Length": "42",
+ "Bps": "420 B/s",
+ "Pps": "1337/s",
+ "Bytes": "285.9 MB/s",
+ "Packets": "314/s",
+ "Backlog": "271828182/s",
+ "Drops": "8675309/s",
+ "Requeues": "12345/s",
+ "Overlimits": "314159/s",
+ "Target": "2701",
+ "Limit": "7",
+ "Interval": "3",
+ "Ecn": "6",
+ "Quantum": "42",
+ "CeThreshold": "101",
+ "DropBatchSize": "9000",
+ "MemoryLimit": "123456",
+ "Flows": "31415/s",
+ "MaxPacket": "8675309",
+ "EcnMark": "299792458",
+ "NewFlowsLen": "314",
+ "OldFlowsLen": "1729",
+ "CeMark": "42",
+ "DropOverlimit": "420/s",
+ "NewFlowCount": "1337/s",
+ "MemoryUsage": "271828182/s",
+ "DropOvermemory": "27182/s",
+ "Timestamp": "0"
+ }
+ ]);
+ assert_eq!(jval, expected_json);
+}
diff --git a/below/model/Cargo.toml b/below/model/Cargo.toml
index f54e2d21..d1c1226a 100644
--- a/below/model/Cargo.toml
+++ b/below/model/Cargo.toml
@@ -27,6 +27,7 @@ resctrlfs = { version = "0.8.1", path = "../resctrlfs" }
serde = { version = "1.0.185", features = ["derive", "rc"] }
serde_json = { version = "1.0.100", features = ["float_roundtrip", "unbounded_depth"] }
slog = { version = "2.7", features = ["max_level_trace", "nested-values"] }
+tc = { package = "below-tc", version = "0.8.1", path = "../tc" }
[dev-dependencies]
futures = { version = "0.3.30", features = ["async-await", "compat"] }
diff --git a/below/model/src/collector.rs b/below/model/src/collector.rs
index 57601723..bf577f09 100644
--- a/below/model/src/collector.rs
+++ b/below/model/src/collector.rs
@@ -31,11 +31,14 @@ pub struct CollectorOptions {
pub enable_btrfs_stats: bool,
pub enable_ethtool_stats: bool,
pub enable_resctrl_stats: bool,
+ pub enable_tc_stats: bool,
pub btrfs_samples: u64,
pub btrfs_min_pct: f64,
pub cgroup_re: Option<Regex>,
pub gpu_stats_receiver:
Option<collector_plugin::Consumer<crate::gpu_stats_collector_plugin::SampleType>>,
+ pub tc_stats_receiver:
+ Option<collector_plugin::Consumer<crate::tc_collector_plugin::SampleType>>,
}
impl Default for CollectorOptions {
@@ -48,10 +51,12 @@ impl Default for CollectorOptions {
enable_btrfs_stats: false,
enable_ethtool_stats: false,
enable_resctrl_stats: false,
+ enable_tc_stats: false,
btrfs_samples: btrfs::DEFAULT_SAMPLES,
btrfs_min_pct: btrfs::DEFAULT_MIN_PCT,
cgroup_re: None,
gpu_stats_receiver: None,
+ tc_stats_receiver: None,
}
}
}
@@ -306,6 +311,16 @@ fn collect_sample(
}
}
},
+ tc: if let Some(tc_stats_receiver) = &options.tc_stats_receiver {
+ Some(
+ tc_stats_receiver
+ .try_take()
+ .context("TC stats collector had an error")?
+ .unwrap_or_default(),
+ )
+ } else {
+ None
+ },
})
}
@@ -467,6 +482,13 @@ macro_rules! count_per_sec {
}
ret
}};
+ ($a:ident, $b:ident, $delta:expr, $target_type:ty) => {{
+ let mut ret = None;
+ if $a <= $b {
+ ret = Some((($b - $a) as f64 / $delta.as_secs_f64()).ceil() as $target_type);
+ }
+ ret
+ }};
($a_opt:expr, $b_opt:expr, $delta:expr, $target_type:ty) => {{
let mut ret = None;
if let (Some(a), Some(b)) = ($a_opt, $b_opt) {
diff --git a/below/model/src/common_field_ids.rs b/below/model/src/common_field_ids.rs
index 28b53314..3b752298 100644
--- a/below/model/src/common_field_ids.rs
+++ b/below/model/src/common_field_ids.rs
@@ -23,7 +23,7 @@
///
/// This list also servers as documentation for available field ids that could
/// be used in other below crates. A test ensures that this list is up-to-date.
-pub const COMMON_MODEL_FIELD_IDS: [&str; 420] = [
+pub const COMMON_MODEL_FIELD_IDS: [&str; 449] = [
"system.hostname",
"system.kernel_version",
"system.os_release",
@@ -444,4 +444,33 @@ pub const COMMON_MODEL_FIELD_IDS: [&str; 420] = [
"network.udp6.sndbuf_errors",
"network.udp6.in_csum_errors",
"network.udp6.ignored_multi",
+ "tc.tc.<idx>.backlog_per_sec",
+ "tc.tc.<idx>.bps",
+ "tc.tc.<idx>.bytes_per_sec",
+ "tc.tc.<idx>.interface",
+ "tc.tc.<idx>.drops_per_sec",
+ "tc.tc.<idx>.kind",
+ "tc.tc.<idx>.overlimits_per_sec",
+ "tc.tc.<idx>.packets_per_sec",
+ "tc.tc.<idx>.pps",
+ "tc.tc.<idx>.qdisc.fq_codel.ce_threshold",
+ "tc.tc.<idx>.qdisc.fq_codel.drop_batch_size",
+ "tc.tc.<idx>.qdisc.fq_codel.ecn",
+ "tc.tc.<idx>.qdisc.fq_codel.flows_per_sec",
+ "tc.tc.<idx>.qdisc.fq_codel.interval",
+ "tc.tc.<idx>.qdisc.fq_codel.limit",
+ "tc.tc.<idx>.qdisc.fq_codel.memory_limit",
+ "tc.tc.<idx>.qdisc.fq_codel.quantum",
+ "tc.tc.<idx>.qdisc.fq_codel.target",
+ "tc.tc.<idx>.qlen",
+ "tc.tc.<idx>.requeues_per_sec",
+ "tc.tc.<idx>.xstats.fq_codel.ce_mark",
+ "tc.tc.<idx>.xstats.fq_codel.drop_overlimit_per_sec",
+ "tc.tc.<idx>.xstats.fq_codel.drop_overmemory_per_sec",
+ "tc.tc.<idx>.xstats.fq_codel.ecn_mark",
+ "tc.tc.<idx>.xstats.fq_codel.maxpacket",
+ "tc.tc.<idx>.xstats.fq_codel.memory_usage_per_sec",
+ "tc.tc.<idx>.xstats.fq_codel.new_flow_count_per_sec",
+ "tc.tc.<idx>.xstats.fq_codel.new_flows_len",
+ "tc.tc.<idx>.xstats.fq_codel.old_flows_len",
];
diff --git a/below/model/src/lib.rs b/below/model/src/lib.rs
index c69380f5..307c