Skip to main content

retina_core/port/
statistics.rs

1use super::PortId;
2use crate::dpdk;
3
4use indexmap::IndexMap;
5use std::ffi::CStr;
6use std::mem;
7
8use anyhow::{bail, Result};
9use colored::*;
10use prettytable::{color, format, Attr, Cell, Row, Table};
11
12/// Collects extended statistics
13#[derive(Debug)]
14pub(crate) struct PortStats {
15    pub(crate) stats: IndexMap<String, u64>,
16    pub(crate) port_id: PortId,
17}
18
19impl PortStats {
20    /// Retrieve port statistics at current time
21    pub(crate) fn collect(port_id: PortId) -> Result<Self> {
22        // temporary table used to get number of available statistics
23        let mut table: Vec<dpdk::rte_eth_xstat> = vec![];
24        let len = unsafe { dpdk::rte_eth_xstats_get(port_id.raw(), table.as_mut_ptr(), 0) };
25        if len < 0 {
26            bail!("Invalid Port ID: {}", port_id);
27        }
28
29        let mut labels = Vec::with_capacity(len as usize);
30        for _ in 0..len {
31            let xstat_name: dpdk::rte_eth_xstat_name = unsafe { mem::zeroed() };
32            labels.push(xstat_name);
33        }
34
35        let nb_labels = unsafe {
36            dpdk::rte_eth_xstats_get_names(port_id.raw(), labels.as_mut_ptr(), len as u32)
37        };
38        if nb_labels < 0 || nb_labels > len {
39            bail!("Failed to retrieve port statistics labels.");
40        }
41
42        let mut xstats = Vec::with_capacity(len as usize);
43        for _ in 0..len {
44            let xstat: dpdk::rte_eth_xstat = unsafe { mem::zeroed() };
45            xstats.push(xstat);
46        }
47        let nb_xstats =
48            unsafe { dpdk::rte_eth_xstats_get(port_id.raw(), xstats.as_mut_ptr(), len as u32) };
49        if nb_xstats < 0 || nb_xstats > len {
50            bail!("Failed to retrieve port statistics.");
51        }
52
53        if nb_labels != nb_xstats {
54            bail!("Number of labels does not match number of retrieved statistics.");
55        }
56
57        let mut stats = IndexMap::new();
58        for i in 0..nb_xstats {
59            let label = unsafe { CStr::from_ptr(labels[i as usize].name.as_ptr()) };
60            let value = xstats[i as usize].value;
61            stats.insert(label.to_string_lossy().into_owned(), value);
62        }
63        Ok(PortStats { stats, port_id })
64    }
65
66    /// Displays all statistics with keyword in list of keywords
67    pub(crate) fn display(&self, keywords: &[String]) {
68        if keywords.is_empty() {
69            return;
70        }
71        println!("Port {} statistics", self.port_id);
72        self.display_capture_rate();
73        self.display_out_of_buffer_rate();
74        self.display_discard_rate();
75
76        let mut table = Table::new();
77        table.set_format(*format::consts::FORMAT_NO_LINESEP);
78        for (label, value) in self.stats.iter() {
79            if keywords.iter().any(|k| label.contains(k)) {
80                let value_cell = if *value > 0
81                    && (label.contains("error")
82                        || label.contains("discard")
83                        || label.contains("out_of_buffer"))
84                {
85                    Cell::new_align(&value.to_string(), format::Alignment::RIGHT)
86                        .with_style(Attr::ForegroundColor(color::RED))
87                } else {
88                    Cell::new_align(&value.to_string(), format::Alignment::RIGHT)
89                };
90
91                table.add_row(Row::new(vec![value_cell, Cell::new(label)]));
92            }
93        }
94        table.printstd();
95    }
96
97    /// Prints fraction of packets received in software.
98    /// If no hardware filters are configured, then a value less than one implies
99    /// that incoming traffic is arriving too fast for the CPU to handle.
100    /// If there are hardware filters configured, then this value indicates that
101    /// fraction of total traffic that was filtered by hardware and successfully
102    /// delivered to the processing cores.
103    pub(super) fn display_capture_rate(&self) {
104        let captured = self.stats.get("rx_good_packets");
105        let total = self.stats.get("rx_phy_packets");
106
107        match (captured, total) {
108            (Some(captured), Some(total)) => {
109                let capture_rate = *captured as f64 / *total as f64;
110                println!("SW Capture %: {}", capture_rate.to_string().bright_cyan());
111            }
112            _ => println!("SW Capture %: UNKNOWN"),
113        }
114    }
115
116    /// Prints fraction of packets discarded by the NIC due to lack of software buffers
117    /// available for the incoming packets, aggregated over all RX queues. A non-zero
118    /// value implies that the CPU is not consuming packets fast enough. If there are
119    /// no hardware filters configured, this value should be 1 - SW Capture %.
120    pub(super) fn display_out_of_buffer_rate(&self) {
121        let discards = self.stats.get("rx_out_of_buffer");
122        let total = self.stats.get("rx_phy_packets");
123
124        match (discards, total) {
125            (Some(discards), Some(total)) => {
126                let discard_rate = *discards as f64 / *total as f64;
127
128                // arbitrary threshold
129                if discard_rate > 0.0001 {
130                    println!("Out of Buffer %: {}", discard_rate.to_string().bright_red());
131                } else if discard_rate > 0.0 {
132                    println!(
133                        "Out of Buffer %: {}",
134                        discard_rate.to_string().bright_yellow()
135                    );
136                } else {
137                    println!(
138                        "Out of Buffer %: {}",
139                        discard_rate.to_string().bright_green()
140                    );
141                }
142            }
143            _ => println!("Out of Buffer %: UNKNOWN"),
144        }
145    }
146
147    /// Prints fraction of packets discarded by the NIC due to lack of buffers on
148    /// the physical port. A non-zero value implies that the NIC or bus is congested and
149    /// cannot absorb the traffic coming from the network. A value of zero may still
150    /// indicate that the CPU is not consuming packets fast enough.
151    pub(super) fn display_discard_rate(&self) {
152        let discards = self.stats.get("rx_phy_discard_packets");
153        let total = self.stats.get("rx_phy_packets");
154
155        match (discards, total) {
156            (Some(discards), Some(total)) => {
157                let discard_rate = *discards as f64 / *total as f64;
158
159                // arbitrary threshold
160                if discard_rate > 0.0001 {
161                    println!("HW Discard %: {}", discard_rate.to_string().bright_red());
162                } else if discard_rate > 0.0 {
163                    println!("HW Discard %: {}", discard_rate.to_string().bright_yellow());
164                } else {
165                    println!("HW Discard %: {}", discard_rate.to_string().bright_green());
166                }
167            }
168            _ => println!("HW Discard %: UNKNOWN"),
169        }
170    }
171}