Skip to content

Commit

Permalink
Add model to represent ethtool stats
Browse files Browse the repository at this point in the history
Create an EthtoolModel type to represent the format to be rendered as the diff between two EthtoolStats samples
  • Loading branch information
mmynk committed Aug 24, 2023
1 parent 965795c commit 66dbb26
Show file tree
Hide file tree
Showing 14 changed files with 594 additions and 139 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions below/dump/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,7 @@ impl AggField<SingleNetModelFieldId> for IfaceAggField {
TxHeartbeatErrors,
TxPackets,
TxWindowErrors,
TxTimeoutPerSec,
],
}
}
Expand Down
51 changes: 25 additions & 26 deletions below/ethtool/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
mod errors;
mod ethtool;
mod reader;
mod types;

mod test;

use std::collections::{BTreeMap, HashMap};
use std::collections::BTreeMap;

use errors::Error;
pub use reader::*;
pub use types::*;

pub type Result<T> = std::result::Result<T, errors::Error>;
Expand All @@ -33,44 +34,40 @@ pub fn insert_stat(stat: &mut QueueStats, name: &str, value: u64) {
"tx_unmask_interrupt" => stat.tx_unmask_interrupt = Some(value),
_ => {
stat.raw_stats.insert(name.to_string(), value);
},
}
};
}

fn translate_stats(stats: Vec<(String, u64)>) -> Result<NicStats> {
let mut nic_stats = NicStats::default();
let mut custom_props = HashMap::new();
let mut queue_stats_map = BTreeMap::new(); // we want the queue stats to be sorted by queue id
let mut custom_props = BTreeMap::new();
let mut queue_stats_map = BTreeMap::new(); // we want the queue stats to be sorted by queue id
for (name, value) in stats {
match parse_queue_stat(&name) {
Some((queue_id, stat)) => {
if !queue_stats_map.contains_key(&queue_id) {
let queue_stat = QueueStats {
raw_stats: HashMap::new(),
raw_stats: BTreeMap::new(),
..Default::default()
};
queue_stats_map.insert(queue_id, queue_stat);
}
let qstat = queue_stats_map.get_mut(&queue_id).unwrap();
insert_stat(qstat, stat, value);
},
None => {
match name.as_str() {
"tx_timeout" => nic_stats.tx_timeout = Some(value),
other => {
custom_props.insert(other.to_string(), value);
},
}
None => match name.as_str() {
"tx_timeout" => nic_stats.tx_timeout = Some(value),
other => {
custom_props.insert(other.to_string(), value);
}
},
}
}

let mut queue_stats = Vec::new();
if !queue_stats_map.is_empty() {
for (_, stats) in queue_stats_map {
queue_stats.push(stats);
}
}
let queue_stats = queue_stats_map
.into_iter()
.map(|(_, v)| v)
.collect();

nic_stats.queue = queue_stats;
nic_stats.raw_stats = custom_props;
Expand All @@ -81,6 +78,10 @@ fn translate_stats(stats: Vec<(String, u64)>) -> Result<NicStats> {
pub struct EthtoolReader;

impl EthtoolReader {
pub fn new() -> Self {
Self {}
}

/// Read the list of interface names.
pub fn read_interfaces(&self) -> Result<Vec<String>> {
let mut if_names = Vec::new();
Expand All @@ -92,29 +93,27 @@ impl EthtoolReader {
}
Err(errno) => {
return Err(Error::IfNamesReadError(errno));
},
}
}
Ok(if_names)
}

/// Read stats for a single NIC identified by `if_name`
fn read_nic_stats<T: ethtool::EthtoolReadable>(&self, if_name: &str) -> Result<NicStats> {
fn read_nic_stats<T: reader::EthtoolReadable>(&self, if_name: &str) -> Result<NicStats> {
let ethtool = T::new(if_name)?;
match ethtool.stats() {
Ok(stats) => {
translate_stats(stats)
}
Ok(stats) => translate_stats(stats),
Err(error) => Err(error),
}
}

pub fn read_stats<T: ethtool::EthtoolReadable>(&self) -> Result<EthtoolStats> {
pub fn read_stats<T: reader::EthtoolReadable>(&self) -> Result<EthtoolStats> {
let mut nic_stats_map = BTreeMap::new();
let if_names = match self.read_interfaces() {
Ok(ifs) => ifs,
Err(err) => {
return Err(err);
},
}
};
for if_name in if_names {
if let Ok(nic_stats) = self.read_nic_stats::<T>(&if_name) {
Expand Down
28 changes: 13 additions & 15 deletions below/ethtool/src/ethtool.rs → below/ethtool/src/reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,9 @@ fn parse_values(
}

pub trait EthtoolReadable {
fn new(if_name: &str) -> Result<Self, Error> where Self: Sized;
fn new(if_name: &str) -> Result<Self, Error>
where
Self: Sized;
fn stats(&self) -> Result<Vec<(String, u64)>, Error>;
}

Expand Down Expand Up @@ -199,24 +201,22 @@ impl EthtoolReadable for Ethtool {
SockFlag::empty(),
None,
) {
Ok(fd) => {
Ok(Ethtool {
sock_fd: fd,
if_name: if_name.to_string(),
})
},
Err(errno) => {
Err(Error::SocketError(errno))
}
Ok(fd) => Ok(Ethtool {
sock_fd: fd,
if_name: if_name.to_string(),
}),
Err(errno) => Err(Error::SocketError(errno)),
}
}

/// Get statistics using ethtool
/// Equivalent to `ethtool -S <ifname>` command
fn stats(&self) -> Result<Vec<(String, u64)>, Error> {
let length = self.gsset_info().map_err(|errno| Error::GSSetInfoReadError(errno))?;
let length = self
.gsset_info()
.map_err(|errno| Error::GSSetInfoReadError(errno))?;

let features = self.gstrings(length)? ;
let features = self.gstrings(length)?;
let values = self.gstats(&features)?;

let final_stats = features.into_iter().zip(values).collect();
Expand All @@ -227,8 +227,6 @@ impl EthtoolReadable for Ethtool {
impl Drop for Ethtool {
/// Close the socket when the object goes out of scope.
fn drop(&mut self) {
unsafe {
nix::libc::close(self.sock_fd)
};
unsafe { nix::libc::close(self.sock_fd) };
}
}
16 changes: 7 additions & 9 deletions below/ethtool/src/test.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
#[cfg(test)]

use crate::EthtoolReader;
use crate::errors::Error;
use crate::ethtool::EthtoolReadable;
use super::*;

struct FakeEthtool;

impl EthtoolReadable for FakeEthtool {
fn new(_: &str) -> Result<Self, Error> {
fn new(_: &str) -> Result<Self> {
Ok(Self {})
}

fn stats(&self) -> Result<Vec<(String, u64)>, Error> {
fn stats(&self) -> Result<Vec<(String, u64)>> {
let res = vec![
("tx_timeout", 10),
("suspend", 0),
Expand Down Expand Up @@ -50,15 +46,17 @@ impl EthtoolReadable for FakeEthtool {
("queue_1_rx_csum_bad", 0),
("queue_1_rx_page_alloc_fail", 0),
];
Ok(res.iter()
Ok(res
.iter()
.map(|stat| (stat.0.to_string(), stat.1))
.collect())
}
}

#[cfg(test)]
#[test]
fn test_read_stats() {
let reader = EthtoolReader{};
let reader = EthtoolReader {};

let if_names = reader.read_interfaces();
assert!(if_names.is_ok());
Expand Down
6 changes: 3 additions & 3 deletions below/ethtool/src/types.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::collections::{BTreeMap, HashMap};
use std::collections::BTreeMap;

use serde::{Deserialize, Serialize};

Expand All @@ -11,7 +11,7 @@ pub struct EthtoolStats {
pub struct NicStats {
pub queue: Vec<QueueStats>,
pub tx_timeout: Option<u64>,
pub raw_stats: HashMap<String, u64>,
pub raw_stats: BTreeMap<String, u64>,
}

#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)]
Expand All @@ -22,5 +22,5 @@ pub struct QueueStats {
pub tx_count: Option<u64>,
pub tx_missed_tx: Option<u64>,
pub tx_unmask_interrupt: Option<u64>,
pub raw_stats: HashMap<String, u64>,
pub raw_stats: BTreeMap<String, u64>,
}
1 change: 1 addition & 0 deletions below/model/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ btrfs = { package = "below-btrfs", version = "0.7.1", path = "../btrfs" }
cgroupfs = { version = "0.7.1", path = "../cgroupfs" }
common = { package = "below-common", version = "0.7.1", path = "../common" }
enum-iterator = "1.4.1"
ethtool = { package = "ethtool", version = "0.1.0", path="../ethtool" }
gpu-stats = { package = "below-gpu-stats", version = "0.7.1", path = "../gpu_stats" }
hostname = "0.3"
os_info = "3.0.7"
Expand Down
10 changes: 10 additions & 0 deletions below/model/src/collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ fn collect_sample(logger: &slog::Logger, options: &CollectorOptions) -> Result<S
let mut reader = procfs::ProcReader::new();
let btrfs_reader =
btrfs::BtrfsReader::new(options.btrfs_samples, options.btrfs_min_pct, logger.clone());
let ethtool_reader = ethtool::EthtoolReader::new();

// Take mutex, then take all values out of shared map and replace with default map
//
Expand Down Expand Up @@ -273,6 +274,15 @@ fn collect_sample(logger: &slog::Logger, options: &CollectorOptions) -> Result<S
None
}
},
ethtool: {
match ethtool_reader.read_stats::<ethtool::Ethtool>() {
Ok(ethtool_stats) => ethtool_stats.into(),
Err(e) => {
error!(logger, "{:#}", e);
Default::default()
}
}
},
})
}

Expand Down
13 changes: 12 additions & 1 deletion below/model/src/common_field_ids.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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; 361] = [
pub const COMMON_MODEL_FIELD_IDS: [&str; 372] = [
"system.hostname",
"system.kernel_version",
"system.os_release",
Expand Down Expand Up @@ -310,6 +310,17 @@ pub const COMMON_MODEL_FIELD_IDS: [&str; 361] = [
"network.interfaces.<key>.tx_heartbeat_errors",
"network.interfaces.<key>.tx_packets",
"network.interfaces.<key>.tx_window_errors",
"network.interfaces.<key>.tx_timeout_per_sec",
"network.interfaces.<key>.raw_stats",
"network.interfaces.<key>.queues.<idx>.interface",
"network.interfaces.<key>.queues.<idx>.queue_id",
"network.interfaces.<key>.queues.<idx>.rx_bytes_per_sec",
"network.interfaces.<key>.queues.<idx>.rx_count_per_sec",
"network.interfaces.<key>.queues.<idx>.tx_bytes_per_sec",
"network.interfaces.<key>.queues.<idx>.tx_count_per_sec",
"network.interfaces.<key>.queues.<idx>.tx_missed_tx",
"network.interfaces.<key>.queues.<idx>.tx_unmask_interrupt",
"network.interfaces.<key>.queues.<idx>.raw_stats",
"network.tcp.active_opens_per_sec",
"network.tcp.passive_opens_per_sec",
"network.tcp.attempt_fails_per_sec",
Expand Down
41 changes: 40 additions & 1 deletion below/model/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ pub enum Field {
PidState(procfs::PidState),
VecU32(Vec<u32>),
StrSet(BTreeSet<String>),
StrU64Map(BTreeMap<String, u64>),
Cpuset(cgroupfs::Cpuset),
MemNodes(cgroupfs::MemNodes),
}
Expand Down Expand Up @@ -177,6 +178,12 @@ impl From<BTreeSet<String>> for Field {
}
}

impl From<BTreeMap<String, u64>> for Field {
fn from(v: BTreeMap<String, u64>) -> Self {
Field::StrU64Map(v)
}
}

impl From<cgroupfs::Cpuset> for Field {
fn from(v: cgroupfs::Cpuset) -> Self {
Field::Cpuset(v)
Expand Down Expand Up @@ -224,6 +231,7 @@ impl PartialEq for Field {
(Field::Str(s), Field::Str(o)) => s == o,
(Field::PidState(s), Field::PidState(o)) => s == o,
(Field::VecU32(s), Field::VecU32(o)) => s == o,
(Field::StrU64Map(s), Field::StrU64Map(o)) => s == o,
_ => false,
}
}
Expand Down Expand Up @@ -266,6 +274,14 @@ impl fmt::Display for Field {
.as_slice()
.join(" ")
)),
Field::StrU64Map(v) => f.write_fmt(format_args!(
"{}",
v.iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect::<Vec<String>>()
.as_slice()
.join(", ")
)),
Field::Cpuset(v) => v.fmt(f),
Field::MemNodes(v) => v.fmt(f),
}
Expand Down Expand Up @@ -478,6 +494,11 @@ impl<K: Ord, Q: Queriable> Queriable for BTreeMap<K, Q> {
}
}

pub struct NetworkStats<'a> {
net: &'a procfs::NetStat,
ethtool: &'a ethtool::EthtoolStats,
}

#[derive(Serialize, Deserialize, below_derive::Queriable)]
pub struct Model {
#[queriable(ignore)]
Expand Down Expand Up @@ -514,7 +535,25 @@ impl Model {
)
.aggr_top_level_val(),
process: ProcessModel::new(&sample.processes, last.map(|(s, d)| (&s.processes, d))),
network: NetworkModel::new(&sample.netstats, last.map(|(s, d)| (&s.netstats, d))),
network: {
let sample = NetworkStats {
net: &sample.netstats,
ethtool: &sample.ethtool
};
let network_stats: NetworkStats;

let last = if let Some((s, d)) = last {
network_stats = NetworkStats {
net: &s.netstats,
ethtool: &s.ethtool
};
Some((&network_stats, d))
} else {
None
};

NetworkModel::new(&sample, last)
},
gpu: sample.gpus.as_ref().map(|gpus| {
GpuModel::new(&gpus.gpu_map, {
if let Some((s, d)) = last {
Expand Down
Loading

0 comments on commit 66dbb26

Please sign in to comment.