changeset 22:83f8cbf8cc58

Add net (interface) metric
author Lewin Bormann <lbo@spheniscida.de>
date Sun, 14 Feb 2016 17:54:31 +0100
parents 229a4f04038c
children d588dc82eacc
files src/main.rs src/metrics/mod.rs src/metrics/net.rs
diffstat 3 files changed, 151 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/src/main.rs	Sun Feb 14 17:54:13 2016 +0100
+++ b/src/main.rs	Sun Feb 14 17:54:31 2016 +0100
@@ -121,12 +121,18 @@
 
 fn register_metrics(registry: &mut AvailableMetrics) {
     use metrics::time;
+    use metrics::net;
 
     // List of codes: https://lifthrasiir.github.io/rust-chrono/chrono/format/strftime/index.html
     registry.register_metric("clock",
                              "A timestamp clock. Uses format codes like date(1)",
                              "%H:%M",
                              time::clock_metric());
+    registry.register_metric("netif",
+                             "Shows total received/transmitted bytes for network interaces",
+                             "eth0,lo",
+                             net::make_net_metric());
+
 }
 
 fn main() {
--- a/src/metrics/mod.rs	Sun Feb 14 17:54:13 2016 +0100
+++ b/src/metrics/mod.rs	Sun Feb 14 17:54:31 2016 +0100
@@ -1,1 +1,2 @@
+pub mod net;
 pub mod time;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/metrics/net.rs	Sun Feb 14 17:54:31 2016 +0100
@@ -0,0 +1,144 @@
+use framework::*;
+use helper::commaseparated_to_vec;
+use helper::get_procfs_file_lines;
+
+extern crate regex;
+use self::regex::Regex;
+
+use std::collections::BTreeSet;
+
+// Interface name, transmitted bytes, received bytes. Used for both rates and counters!
+type IFStat = (String, u64, u64);
+
+struct NetInterfaceMetric;
+
+impl NetInterfaceMetric {
+    /// Obtain current counters from /proc/net/dev
+    fn get_stats(ifs: BTreeSet<String>) -> Vec<IFStat> {
+        let ifstats;
+        let mut processed_stats = Vec::with_capacity(ifs.len());
+
+        match get_procfs_file_lines(String::from("net/dev")) {
+            None => ifstats = Vec::new(),
+            Some(st) => ifstats = st,
+        }
+
+        //               RX                                                             TX
+        //           *           *                                                    *         *
+        //  iface |bytes       packets  errs drop fifo frame   compressed multicast|bytes     packets errs drop fifo colls   carrier compressed
+        //  eth0:  1037503524  872642    0    0    0     0          0     10482     40971427  300143    0    1    0     0       0          0
+        let re = Regex::new(r"^\s*([a-z0-9]+):\s+(\d+)\s+(\d+)\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+(\d+)\s+(\d+)\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+$").unwrap();
+
+        for line in ifstats {
+            match re.captures(&line) {
+                None => continue,
+                Some(caps) => {
+                    if ifs.contains(caps.at(1).unwrap()) {
+                        processed_stats.push((
+                            String::from(caps.at(1).unwrap()),
+                            u64::from_str_radix(caps.at(2).unwrap(), 10).unwrap(),
+                            u64::from_str_radix(caps.at(4).unwrap(), 10).unwrap()
+                        ));
+                    }
+                }
+            }
+        }
+
+        processed_stats
+    }
+
+    /// Convert a number into a string with nice unit
+    fn make_nice_rate(i: u64) -> String {
+        let units = vec!["", "K", "M", "G"];
+        let mut u = 0;
+        let mut f = i as f64;
+        loop {
+            if f / 1024. > 1. {
+                f = f / 1024.;
+                u += 1;
+            } else {
+                break;
+            }
+        }
+        assert!(u < 4);
+        format!("{:6.1}{:1}", f, units[u])
+    }
+
+    /// Format a series of IFStat tuples
+    fn format_stats(stats: Vec<IFStat>) -> String {
+        stats.into_iter()
+             .fold(String::new(), |mut acc, (i, rx, tx)| {
+                 acc.push_str(&format!("{}: rx:{} tx:{} ",
+                                       i,
+                                       NetInterfaceMetric::make_nice_rate(rx),
+                                       NetInterfaceMetric::make_nice_rate(tx)));
+                 acc
+             })
+    }
+
+    /// Parse a "{rx} {tx}" string into an IFStat tuple
+    fn str_to_ifstat(name: String, s: String) -> IFStat {
+        let re = Regex::new(r"^(\d+) (\d+)$").unwrap();
+
+        match re.captures(&s) {
+            None => (name, 0, 0),
+            Some(caps) => {
+                let rx = u64::from_str_radix(caps.at(1).unwrap(), 10).unwrap();
+                let tx = u64::from_str_radix(caps.at(2).unwrap(), 10).unwrap();
+                (name, rx, tx)
+            }
+        }
+    }
+}
+
+impl Metric for NetInterfaceMetric {
+    fn init(&self, st: &mut MetricState, initarg: Option<String>) {
+        match initarg {
+            None => (),
+            Some(s) => {
+                let wanted_ifs: BTreeSet<String> = commaseparated_to_vec(s).into_iter().collect();
+                st.set(String::from("ifs"), State::BTS(wanted_ifs));
+            }
+        }
+    }
+
+    fn render(&self, st: &mut MetricState) -> RenderResult {
+        let interval = (MetricState::now() - st.last_called) as u64;
+
+        let interfaces;
+        match st.get(String::from("ifs")) {
+            State::BTS(ifs) => interfaces = ifs,
+            _ => return RenderResult::new(String::from("n/a"), Color::Red),
+        }
+
+        // Get current counters
+        let newstats = NetInterfaceMetric::get_stats(interfaces);
+        let mut rates: Vec<IFStat> = Vec::new(); // this is the final output
+
+        for (ifname, rx, tx) in newstats {
+            // Obtain previous rx/tx counts from state
+            let oldstat;
+            match st.get(ifname.clone()) {
+                State::S(o) => oldstat = NetInterfaceMetric::str_to_ifstat(ifname.clone(), o),
+                _ => oldstat = (ifname.clone(), rx, tx),
+            }
+
+            // calculate rate over last interval
+            match oldstat {
+                (ifname, oldrx, oldtx) => {
+                    rates.push((ifname.clone(),
+                                1000 * (rx - oldrx) / interval,
+                                1000 * (tx - oldtx) / interval));
+                    // Store current counters in state
+                    st.set(ifname.clone(), State::S(format!("{} {}", rx, tx)));
+                }
+            }
+        }
+
+        RenderResult::new(NetInterfaceMetric::format_stats(rates), Color::Green)
+    }
+}
+
+pub fn make_net_metric() -> Box<Metric> {
+    Box::new(NetInterfaceMetric)
+}