1#![deny(clippy::all)]
16#![allow(non_upper_case_globals)]
17#![allow(non_camel_case_types)]
18#![allow(non_snake_case)]
19
20mod errors;
21mod ethtool_sys;
22mod reader;
23mod types;
24
25mod test;
26
27use std::collections::BTreeMap;
28use std::collections::HashSet;
29
30use errors::EthtoolError;
31pub use reader::*;
32pub use types::*;
33
34pub type Result<T> = std::result::Result<T, errors::EthtoolError>;
35
36fn parse_queue_stat(name: &str) -> Option<(usize, &str)> {
49 if !name.starts_with("queue_") {
50 return None;
51 }
52
53 let stat_segments: Vec<&str> = name.splitn(3, '_').collect();
54 if stat_segments.len() != 3 {
55 return None;
56 }
57 match stat_segments[1].parse::<usize>() {
58 Ok(queue_id) => Some((queue_id, stat_segments[2])),
59 Err(_) => None,
60 }
61}
62
63fn insert_stat(stat: &mut QueueStats, name: &str, value: u64) {
64 match name {
65 "rx_bytes" => stat.rx_bytes = Some(value),
66 "tx_bytes" => stat.tx_bytes = Some(value),
67 "rx_cnt" => stat.rx_count = Some(value),
68 "tx_cnt" => stat.tx_count = Some(value),
69 "tx_missed_tx" => stat.tx_missed_tx = Some(value),
70 "tx_unmask_interrupt" => stat.tx_unmask_interrupt = Some(value),
71 _ => {
72 stat.raw_stats.insert(name.to_string(), value);
73 }
74 };
75}
76
77fn translate_stats(stats: Vec<(String, u64)>) -> Result<NicStats> {
78 let mut nic_stats = NicStats::default();
79 let mut raw_stats = BTreeMap::new();
80 let mut queue_stats_map = BTreeMap::new(); for (name, value) in stats {
82 match parse_queue_stat(&name) {
83 Some((queue_id, stat)) => {
84 let qstat = queue_stats_map
85 .entry(queue_id)
86 .or_insert_with(QueueStats::default);
87 insert_stat(qstat, stat, value);
88 }
89 None => match name.as_str() {
90 "tx_timeout" => nic_stats.tx_timeout = Some(value),
91 other => {
92 raw_stats.insert(other.to_string(), value);
93 }
94 },
95 }
96 }
97
98 let queue_stats = queue_stats_map.into_values().collect();
99
100 nic_stats.queue = queue_stats;
101 nic_stats.raw_stats = raw_stats;
102
103 Ok(nic_stats)
104}
105
106pub struct EthtoolReader;
107
108impl Default for EthtoolReader {
109 fn default() -> Self {
110 Self::new()
111 }
112}
113
114impl EthtoolReader {
115 pub fn new() -> Self {
116 Self {}
117 }
118
119 pub fn read_interfaces(&self) -> Result<HashSet<String>> {
121 let mut if_names = HashSet::new();
122 match nix::ifaddrs::getifaddrs() {
123 Ok(interfaces) => {
124 for if_addr in interfaces {
125 if_names.insert(if_addr.interface_name);
126 }
127 }
128 Err(errno) => {
129 return Err(EthtoolError::IfNamesReadError(errno));
130 }
131 }
132 Ok(if_names)
133 }
134
135 fn read_nic_stats<T: reader::EthtoolReadable>(&self, if_name: &str) -> Result<NicStats> {
137 let ethtool = T::new(if_name)?;
138 match ethtool.stats() {
139 Ok(stats) => translate_stats(stats),
140 Err(error) => Err(error),
141 }
142 }
143
144 pub fn read_stats<T: reader::EthtoolReadable>(&self) -> Result<EthtoolStats> {
145 let mut nic_stats_map = BTreeMap::new();
146 let if_names = self.read_interfaces()?;
147 for if_name in if_names {
148 if let Ok(nic_stats) = self.read_nic_stats::<T>(&if_name) {
149 nic_stats_map.insert(if_name.to_string(), nic_stats);
150 }
151 }
152
153 Ok(EthtoolStats { nic: nic_stats_map })
154 }
155}