ifstat_rs/
output.rs

1// This file contains standalone-useful functions.
2
3use indexmap::IndexMap;
4
5use crate::net_stats::get_device_string_to_name_map;
6
7#[macro_export]
8macro_rules! test_debug {
9    ($($arg:tt)*) => {{
10        if std::env::var("RUST_TEST").is_ok() {
11            println!($($arg)*);
12        }
13    }};
14}
15
16/// Filters out interfaces that have zero RX and TX counters.
17pub fn filter_zero_counters(
18    stats: &IndexMap<String, (u64, u64)>,
19    interfaces: &[String],
20) -> Vec<String> {
21    interfaces
22        .iter()
23        .filter(|iface| {
24            if let Some(&(rx, tx)) = stats.get(*iface) {
25                rx != 0 || tx != 0
26            } else {
27                false
28            }
29        })
30        .cloned()
31        .collect()
32}
33
34/// Shortens the interface name if it exceeds 16 characters, following specific rules.
35pub fn shorten_name(name: &str) -> String {
36    if name.len() > 16 {
37        let start = name.find('\\').map(|i| i + 1).unwrap_or(0);
38        let name = &name[start..];
39
40        // assume form like \DEVICE\TCPIP_{2EE2C70C-A092-4D88-A654-98C8D7645CD5}
41        if let Some(start_idx) = name.find("TCPIP_{") {
42            let prefix_len = start_idx + 11; // length of "TCPIP_{0737"
43            if prefix_len < name.len() {
44                let suffix_start = name.len().saturating_sub(5);
45                let prefix = &name[start_idx..prefix_len];
46                let suffix = &name[suffix_start..];
47
48                return format!("{}..{}", prefix, suffix);
49            }
50        }
51
52        // If the name doesn't match the expected pattern or prefix_len check fails
53        if name.len() > 13 {
54            return format!("{}...", &name[..13]);
55        }
56    }
57    // If the name length is 16 or less, or all other conditions fail
58    name.to_string()
59}
60
61/// Prints headers for the network interface statistics table.
62pub fn print_headers(
63    interfaces: &[String],
64    writer: &mut dyn std::io::Write,
65    hide_zero_counters: bool,
66    stats: &IndexMap<String, (u64, u64)>,
67) -> std::io::Result<()> {
68    let interfaces = if hide_zero_counters {
69        filter_zero_counters(stats, interfaces)
70    } else {
71        interfaces.to_vec()
72    };
73
74    if interfaces.is_empty() {
75        return Ok(());
76    }
77
78    let width = 18; // Width for each interface field including space for in/out
79    for (i, interface) in interfaces.iter().enumerate() {
80        let short_interface = shorten_name(interface);
81        let padded_name = format!("{:^width$}", short_interface, width = width);
82        write!(writer, "{}", padded_name)?;
83        if i < interfaces.len() - 1 {
84            write!(writer, "  ")?; // Additional spaces between columns
85        }
86    }
87    writeln!(writer)?;
88
89    for (i, _) in interfaces.iter().enumerate() {
90        write!(writer, "{:>8}  {:>8}", "KB/s in", "KB/s out")?;
91        if i < interfaces.len() - 1 {
92            write!(writer, "  ")?; // Additional spaces between columns
93        }
94    }
95    writeln!(writer)?;
96
97    Ok(())
98}
99
100/// Prints the network interface statistics.
101pub fn print_stats(
102    previous: &IndexMap<String, (u64, u64)>,
103    current: &IndexMap<String, (u64, u64)>,
104    interfaces: &[String],
105    writer: &mut dyn std::io::Write,
106    hide_zero_counters: bool,
107) -> std::io::Result<()> {
108    let interfaces = if hide_zero_counters {
109        filter_zero_counters(current, interfaces)
110    } else {
111        interfaces.to_vec()
112    };
113
114    for (i, interface) in interfaces.iter().enumerate() {
115        if let (Some(&(prev_rx, prev_tx)), Some(&(cur_rx, cur_tx))) =
116            (previous.get(interface), current.get(interface))
117        {
118            let rx_kbps = (cur_rx.saturating_sub(prev_rx)) as f64 / 1024.0;
119            let tx_kbps = (cur_tx.saturating_sub(prev_tx)) as f64 / 1024.0;
120            write!(writer, "{:>8.2}  {:>8.2}", rx_kbps, tx_kbps)?;
121            if i < interfaces.len() - 1 {
122                write!(writer, "  ")?; // Additional spaces between columns
123            }
124        }
125    }
126    writeln!(writer)?;
127
128    Ok(())
129}
130
131// Prints the names of network devices.
132pub fn print_net_devices(stats: &IndexMap<String, (u64, u64)>) {
133    // Get the map of device strings to human-readable names.
134    let adapter_name_map = get_device_string_to_name_map();
135    if adapter_name_map.len() > 0 {
136        println!("{} adapters:", adapter_name_map.len());
137        for guid in adapter_name_map.keys() {
138            if let Some(friendly_name) = adapter_name_map.get(guid) {
139                println!("{} {}", guid, friendly_name);
140            }
141        }
142    }
143
144    // Print the number of interfaces.
145    println!("{} interfaces:", stats.len());
146
147    // Iterate over the keys (interface names) in the stats HashMap.
148    for iface in stats.keys() {
149        // Try to get the human-readable name from the adapter_name_map.
150        match adapter_name_map.get(iface) {
151            Some(adapter_name) => println!("{} ({})", iface, adapter_name),
152            None => println!("{}", iface),
153        }
154    }
155}