below_ethtool/
lib.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
15#![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
36/// Translate the name of a queue stat to a tuple of (queue_id, stat_name).
37/// Returns None if the name is not a queue stat.
38///
39/// The queue stat name is expected to be in the format of "queue_{queue_id}_{stat_name}".
40/// Other formats are as of now treated as non-queue stats.
41///
42/// # Examples
43/// ```ignore
44/// assert_eq!(parse_queue_stat("queue_0_rx_bytes"), Some((0, "rx_bytes")));
45/// assert_eq!(parse_queue_stat("queue"), None);
46/// assert_eq!(parse_queue_stat("queue_0"), None);
47/// ```
48fn 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(); // we want the queue stats to be sorted by queue id
81    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    /// Read the list of interface names.
120    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    /// Read stats for a single NIC identified by `if_name`
136    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}