feat: add packet size & packet rate (#1980)

* feat: add packet size & packet rate

* feat: add packet size & packet rate for network basic panel

* update wording

* some text shifting fixes

* fix most things

---------

Co-authored-by: WqyJh <781345688@qq.com>
This commit is contained in:
Clement Tsang
2026-03-11 23:41:07 -04:00
committed by GitHub
co-authored by WqyJh
parent bd201af507
commit 42ee4c79af
13 changed files with 468 additions and 95 deletions
+1
View File
@@ -68,6 +68,7 @@ pub struct AppConfigFields {
pub network_legend_position: Option<LegendPosition>,
pub network_scale_type: AxisScaling,
pub network_use_binary_prefix: bool,
pub network_show_packets: bool,
pub retention_ms: u64,
pub dedicated_average_row: bool,
pub default_tree_collapse: bool,
+11 -9
View File
@@ -238,13 +238,9 @@ impl Painter {
rect[0],
app_state.current_widget.widget_id,
),
Net => self.draw_network_graph(
f,
app_state,
rect[0],
app_state.current_widget.widget_id,
false,
),
Net => {
self.draw_network(f, app_state, rect[0], app_state.current_widget.widget_id)
}
Proc | ProcSearch | ProcSort => {
let widget_id = app_state.current_widget.widget_id
- match &app_state.current_widget.widget_type {
@@ -311,8 +307,14 @@ impl Painter {
mem_rows += data.gpu_harvest.len() as u16; // add row(s) for gpu
}
if mem_rows == 1 {
mem_rows += 1; // need at least 2 rows for RX and TX
let network_rows = if app_state.app_config_fields.network_show_packets {
4 // 4 rows for RX/TX and Packet Rates (Avg sizes moved to right side)
} else {
2 // 2 rows for RX and TX
};
if mem_rows < network_rows {
mem_rows += network_rows - mem_rows; // min rows
}
let vertical_chunks = Layout::default()
+48
View File
@@ -1,3 +1,5 @@
use crate::{collection::network::NetworkHarvest, utils::data_units::convert_bytes};
pub mod cpu_basic;
pub mod cpu_graph;
pub mod disk_table;
@@ -10,3 +12,49 @@ pub mod temperature_table;
#[cfg(feature = "battery")]
pub mod battery_display;
/// Helper struct to hold packet-related data
pub(super) struct PacketInfo {
/// Current received packet rate.
pub(super) rx_packet_rate: u64,
/// Current transmitted packet rate.
pub(super) tx_packet_rate: u64,
/// Average received packet size in bytes, converted to the nearest unit.
pub(super) avg_rx_packet_size: (f64, &'static str),
/// Average transmitted packet size in bytes, converted to the nearest unit.
pub(super) avg_tx_packet_size: (f64, &'static str),
}
/// Calculate packet information from network data.
pub(super) fn calculate_packet_info(
network_latest_data: &NetworkHarvest, use_binary_prefix: bool,
) -> PacketInfo {
let rx_packet_rate = network_latest_data.rx_packets;
let tx_packet_rate = network_latest_data.tx_packets;
// Calculate average packet size (bytes per packet)
let avg_rx_packet_size = if network_latest_data.rx_packets > 0 {
(network_latest_data.rx as f64 / 8.0) / network_latest_data.rx_packets as f64 // Convert bits to bytes
} else {
0.0
};
let avg_tx_packet_size = if network_latest_data.tx_packets > 0 {
(network_latest_data.tx as f64 / 8.0) / network_latest_data.tx_packets as f64 // Convert bits to bytes
} else {
0.0
};
let avg_rx_packet_size = convert_bytes(avg_rx_packet_size.round() as u64, use_binary_prefix);
let avg_tx_packet_size = convert_bytes(avg_tx_packet_size.round() as u64, use_binary_prefix);
PacketInfo {
rx_packet_rate,
tx_packet_rate,
avg_rx_packet_size,
avg_tx_packet_size,
}
}
+236 -32
View File
@@ -7,7 +7,11 @@ use tui::{
use crate::{
app::App,
canvas::{Painter, drawing_utils::widget_block},
canvas::{
Painter,
drawing_utils::widget_block,
widgets::{PacketInfo, calculate_packet_info},
},
utils::data_units::{convert_bits, get_unit_prefix},
};
@@ -15,22 +19,7 @@ impl Painter {
pub fn draw_basic_network(
&self, f: &mut Frame<'_>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
) {
let divided_loc = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(draw_loc);
let net_loc = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(100)])
.horizontal_margin(1)
.split(divided_loc[0]);
let total_loc = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(100)])
.horizontal_margin(1)
.split(divided_loc[1]);
let show_packets = app_state.app_config_fields.network_show_packets;
if app_state.current_widget.widget_id == widget_id {
f.render_widget(
@@ -47,27 +36,242 @@ impl Painter {
let total_rx = convert_bits(network_data.total_rx, use_binary_prefix);
let total_tx = convert_bits(network_data.total_tx, use_binary_prefix);
let rx_label = format!("RX: {:.1}{}", rx.0, rx.1);
let tx_label = format!("TX: {:.1}{}", tx.0, tx.1);
let rx_label = format!("RX: {:.1}{}/s", rx.0, rx.1);
let tx_label = format!("TX: {:.1}{}/s", tx.0, tx.1);
let total_rx_label = format!("Total RX: {:.1}{}", total_rx.0, total_rx.1);
let total_tx_label = format!("Total TX: {:.1}{}", total_tx.0, total_tx.1);
let net_text = vec![
Line::from(Span::styled(rx_label, self.styles.rx_style)),
Line::from(Span::styled(tx_label, self.styles.tx_style)),
];
// Determine if we need grid layout based on available width
// Assume we need at least ~15 chars per column for horizontal layout
// With 4 columns, that's ~60 chars minimum
// If width is less than 60, use grid layout (4 rows x 2 columns)
if show_packets {
let PacketInfo {
rx_packet_rate,
tx_packet_rate,
avg_rx_packet_size,
avg_tx_packet_size,
} = calculate_packet_info(network_data, use_binary_prefix);
let total_net_text = vec![
Line::from(Span::styled(total_rx_label, self.styles.total_rx_style)),
Line::from(Span::styled(total_tx_label, self.styles.total_tx_style)),
];
// TODO: Stylize packet stuff later with something else? Or maybe make it so total is now (by default) just bolded RX/TX? I doubt anyone cares...
let rx_packet_rate_label = format!("RX Pkt: {}pkt/s", rx_packet_rate);
let tx_packet_rate_label = format!("TX Pkt: {}pkt/s", tx_packet_rate);
let avg_rx_packet_size_label = format!(
"Avg RX Pkt: {:.1}{}",
avg_rx_packet_size.0, avg_rx_packet_size.1
);
let avg_tx_packet_size_label = format!(
"Avg TX Pkt: {:.1}{}",
avg_tx_packet_size.0, avg_tx_packet_size.1
);
f.render_widget(Paragraph::new(net_text).block(Block::default()), net_loc[0]);
if draw_loc.width < 60 {
// 4 rows x 2 columns layout
// Column 1: RX, TX, Total RX, Total TX (top to bottom)
// Column 2: RX Packets, TX Packets, AVG RX, AVG TX (top to bottom)
let grid_loc = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
.split(draw_loc);
f.render_widget(
Paragraph::new(total_net_text).block(Block::default()),
total_loc[0],
);
// Column 1: RX, TX, Total RX, Total TX
let col1_loc = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Percentage(25),
Constraint::Percentage(25),
Constraint::Percentage(25),
Constraint::Percentage(25),
])
.split(grid_loc[0]);
f.render_widget(
Paragraph::new(Line::from(Span::styled(rx_label, self.styles.rx_style)))
.block(Block::default()),
col1_loc[0],
);
f.render_widget(
Paragraph::new(Line::from(Span::styled(tx_label, self.styles.tx_style)))
.block(Block::default()),
col1_loc[1],
);
f.render_widget(
Paragraph::new(Line::from(Span::styled(
total_rx_label,
self.styles.total_rx_style,
)))
.block(Block::default()),
col1_loc[2],
);
f.render_widget(
Paragraph::new(Line::from(Span::styled(
total_tx_label,
self.styles.total_tx_style,
)))
.block(Block::default()),
col1_loc[3],
);
// Column 2: RX Packets, TX Packets, AVG RX, AVG TX
let col2_loc = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Percentage(25),
Constraint::Percentage(25),
Constraint::Percentage(25),
Constraint::Percentage(25),
])
.split(grid_loc[1]);
f.render_widget(
Paragraph::new(Line::from(Span::styled(
rx_packet_rate_label,
self.styles.rx_style,
)))
.block(Block::default()),
col2_loc[0],
);
f.render_widget(
Paragraph::new(Line::from(Span::styled(
tx_packet_rate_label,
self.styles.tx_style,
)))
.block(Block::default()),
col2_loc[1],
);
f.render_widget(
Paragraph::new(Line::from(Span::styled(
avg_rx_packet_size_label,
self.styles.total_rx_style,
)))
.block(Block::default()),
col2_loc[2],
);
f.render_widget(
Paragraph::new(Line::from(Span::styled(
avg_tx_packet_size_label,
self.styles.total_tx_style,
)))
.block(Block::default()),
col2_loc[3],
);
} else {
// Horizontal 4-column layout
let constraints = [
Constraint::Percentage(25),
Constraint::Percentage(25),
Constraint::Percentage(25),
Constraint::Percentage(25),
];
let divided_loc = Layout::default()
.direction(Direction::Horizontal)
.constraints(constraints)
.split(draw_loc);
// Column 1: RX/TX
let col1_loc = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(100)])
.horizontal_margin(1)
.split(divided_loc[0]);
let col1_text = vec![
Line::from(Span::styled(rx_label, self.styles.rx_style)),
Line::from(Span::styled(tx_label, self.styles.tx_style)),
];
f.render_widget(
Paragraph::new(col1_text).block(Block::default()),
col1_loc[0],
);
// Column 2: Total RX/TX
let col2_loc = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(100)])
.horizontal_margin(1)
.split(divided_loc[1]);
let col2_text = vec![
Line::from(Span::styled(total_rx_label, self.styles.total_rx_style)),
Line::from(Span::styled(total_tx_label, self.styles.total_tx_style)),
];
f.render_widget(
Paragraph::new(col2_text).block(Block::default()),
col2_loc[0],
);
// Column 3: RX/TX packets
let col3_loc = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(100)])
.horizontal_margin(1)
.split(divided_loc[2]);
let col3_text = vec![
Line::from(Span::styled(rx_packet_rate_label, self.styles.rx_style)),
Line::from(Span::styled(tx_packet_rate_label, self.styles.tx_style)),
];
f.render_widget(
Paragraph::new(col3_text).block(Block::default()),
col3_loc[0],
);
// Column 4: AVG RX/TX packets
let col4_loc = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(100)])
.horizontal_margin(1)
.split(divided_loc[3]);
let col4_text = vec![
Line::from(Span::styled(
avg_rx_packet_size_label,
self.styles.total_rx_style,
)),
Line::from(Span::styled(
avg_tx_packet_size_label,
self.styles.total_tx_style,
)),
];
f.render_widget(
Paragraph::new(col4_text).block(Block::default()),
col4_loc[0],
);
}
} else {
// No packets, 2-column layout
let constraints = [Constraint::Percentage(50), Constraint::Percentage(50)];
let divided_loc = Layout::default()
.direction(Direction::Horizontal)
.constraints(constraints)
.split(draw_loc);
// Column 1: RX/TX
let col1_loc = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(100)])
.horizontal_margin(1)
.split(divided_loc[0]);
let col1_text = vec![
Line::from(Span::styled(rx_label, self.styles.rx_style)),
Line::from(Span::styled(tx_label, self.styles.tx_style)),
];
f.render_widget(
Paragraph::new(col1_text).block(Block::default()),
col1_loc[0],
);
// Column 2: Total RX/TX
let col2_loc = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Percentage(100)])
.horizontal_margin(1)
.split(divided_loc[1]);
let col2_text = vec![
Line::from(Span::styled(total_rx_label, self.styles.total_rx_style)),
Line::from(Span::styled(total_tx_label, self.styles.total_tx_style)),
];
f.render_widget(
Paragraph::new(col2_text).block(Block::default()),
col2_loc[0],
);
}
// Update draw loc in widget map
if app_state.should_get_widget_bounds() {
+113 -50
View File
@@ -14,6 +14,7 @@ use crate::{
Painter,
components::time_graph::{AxisBound, ChartScaling, GraphData, TimeGraph},
drawing_utils::should_hide_x_label,
widgets::{PacketInfo, calculate_packet_info},
},
utils::{
data_units::*,
@@ -37,10 +38,10 @@ impl Painter {
])
.split(draw_loc);
self.draw_network_graph(f, app_state, network_chunk[0], widget_id, true);
self.draw_network_labels(f, app_state, network_chunk[1], widget_id);
self.draw_network_graph(f, app_state, network_chunk[0], widget_id);
self.draw_old_network_labels(f, app_state, network_chunk[1], widget_id);
} else {
self.draw_network_graph(f, app_state, draw_loc, widget_id, false);
self.draw_network_graph(f, app_state, draw_loc, widget_id);
}
if app_state.should_get_widget_bounds() {
@@ -57,7 +58,6 @@ impl Painter {
pub fn draw_network_graph(
&self, f: &mut Frame<'_>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
full_screen: bool,
) {
if let Some(network_widget_state) =
app_state.states.net_state.widget_states.get_mut(&widget_id)
@@ -147,14 +147,17 @@ impl Painter {
adjust_network_data_point(y_max, &app_state.app_config_fields);
let y_bounds = AxisBound::Max(adjusted_y_max);
let legend_constraints = if full_screen {
(Constraint::Ratio(0, 1), Constraint::Ratio(0, 1))
let use_old_network_legend = app_state.app_config_fields.use_old_network_legend;
let legend_constraints = if use_old_network_legend {
// Always hide it. Note that I could pass in `None` to the position as well but eh this works.
(Constraint::Length(0), Constraint::Length(0))
} else {
(Constraint::Ratio(1, 1), Constraint::Ratio(3, 4))
// Hide the legend if the width is 75% of the total widget width
// or the height is greater than 75% of the total widget hight.
(Constraint::Ratio(3, 4), Constraint::Ratio(3, 4))
};
// TODO: Add support for clicking on legend to only show that value on chart.
let use_binary_prefix = app_state.app_config_fields.network_use_binary_prefix;
let unit_type = app_state.app_config_fields.network_unit_type;
let unit = match unit_type {
@@ -167,50 +170,77 @@ impl Painter {
let total_rx = convert_bits(network_latest_data.total_rx, use_binary_prefix);
let total_tx = convert_bits(network_latest_data.total_tx, use_binary_prefix);
// TODO: This behaviour is pretty weird, we should probably just make it so if you use old network legend
// you don't do whatever this is...
let graph_data = if app_state.app_config_fields.use_old_network_legend && !full_screen {
let rx_label = format!("RX: {:.1}{}{}", rx.0, rx.1, unit);
let tx_label = format!("TX: {:.1}{}{}", tx.0, tx.1, unit);
let total_rx_label = format!("Total RX: {:.1}{}", total_rx.0, total_rx.1);
let total_tx_label = format!("Total TX: {:.1}{}", total_tx.0, total_tx.1);
vec![
let graph_data = if use_old_network_legend {
let mut graph_data = vec![
GraphData::default()
.name(rx_label.into())
.time(times)
.values(rx_points)
.style(self.styles.rx_style),
GraphData::default()
.name(tx_label.into())
.time(times)
.values(tx_points)
.style(self.styles.tx_style),
GraphData::default()
.style(self.styles.total_rx_style)
.name(total_rx_label.into()),
GraphData::default()
.style(self.styles.total_tx_style)
.name(total_tx_label.into()),
]
];
graph_data.extend(vec![
GraphData::default().style(self.styles.total_rx_style),
GraphData::default().style(self.styles.total_tx_style),
]);
graph_data
} else {
let rx_label = format!("{:.1}{}{}", rx.0, rx.1, unit);
let tx_label = format!("{:.1}{}{}", tx.0, tx.1, unit);
let total_rx_label = format!("{:.1}{}", total_rx.0, total_rx.1);
let total_tx_label = format!("{:.1}{}", total_tx.0, total_tx.1);
vec![
GraphData::default()
.name(format!("RX: {rx_label:<10} All: {total_rx_label}").into())
.time(times)
.values(rx_points)
.style(self.styles.rx_style),
GraphData::default()
.name(format!("TX: {tx_label:<10} All: {total_tx_label}").into())
.time(times)
.values(tx_points)
.style(self.styles.tx_style),
]
// Add packets information if enabled and there's enough room.
const MAX_LEGEND_WIDTH: u16 = 70;
let approx_legend_width = draw_loc.width * 3 / 4;
// FIXME: I'm not really a huge fan of this - I think it may be better to just not support this and
// allow for more easily spawning a separate legend table (basically old legend).
if app_state.app_config_fields.network_show_packets
&& approx_legend_width > MAX_LEGEND_WIDTH
{
let PacketInfo {
rx_packet_rate,
tx_packet_rate,
avg_rx_packet_size,
avg_tx_packet_size,
} = calculate_packet_info(network_latest_data, use_binary_prefix);
let avg_rx_packet_size_label =
format!("{:.1}{}", avg_rx_packet_size.0, avg_rx_packet_size.1);
let avg_tx_packet_size_label =
format!("{:.1}{}", avg_tx_packet_size.0, avg_tx_packet_size.1);
vec![
GraphData::default()
.name(format!("RX: {rx_label:<10} All: {total_rx_label:<8} Packets: {rx_packet_rate:>8}pkt/s Avg: {avg_rx_packet_size_label}").into())
.time(times)
.values(rx_points)
.style(self.styles.rx_style),
GraphData::default()
.name(format!("TX: {tx_label:<10} All: {total_tx_label:<8} Packets: {tx_packet_rate:>8}pkt/s Avg: {avg_tx_packet_size_label}").into())
.time(times)
.values(tx_points)
.style(self.styles.tx_style),
]
} else {
vec![
GraphData::default()
.name(format!("RX: {rx_label:<10} All: {total_rx_label}").into())
.time(times)
.values(rx_points)
.style(self.styles.rx_style),
GraphData::default()
.name(format!("TX: {tx_label:<10} All: {total_tx_label}").into())
.time(times)
.values(tx_points)
.style(self.styles.tx_style),
]
}
};
let marker = if app_state.app_config_fields.use_dot {
@@ -252,11 +282,9 @@ impl Painter {
}
}
fn draw_network_labels(
fn draw_old_network_labels(
&self, f: &mut Frame<'_>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
) {
const NETWORK_HEADERS: [&str; 4] = ["RX", "TX", "Total RX", "Total TX"];
let network_latest_data = &(app_state.data_store.get_data().network_harvest);
let use_binary_prefix = app_state.app_config_fields.network_use_binary_prefix;
let unit_type = app_state.app_config_fields.network_unit_type;
@@ -276,23 +304,58 @@ impl Painter {
let total_rx_label = format!("{:.1}{}", total_rx.0, total_rx.1);
let total_tx_label = format!("{:.1}{}", total_tx.0, total_tx.1);
// Gross but I need it to work...
let total_network = vec![Row::new([
Text::styled(rx_label, self.styles.rx_style),
Text::styled(tx_label, self.styles.tx_style),
Text::styled(total_rx_label, self.styles.total_rx_style),
Text::styled(total_tx_label, self.styles.total_tx_style),
])];
let total_network = if app_state.app_config_fields.network_show_packets {
let PacketInfo {
rx_packet_rate,
tx_packet_rate,
avg_rx_packet_size,
avg_tx_packet_size,
} = calculate_packet_info(network_latest_data, use_binary_prefix);
let avg_rx_packet_size_label =
format!("{:.1}{}", avg_rx_packet_size.0, avg_rx_packet_size.1);
let avg_tx_packet_size_label =
format!("{:.1}{}", avg_tx_packet_size.0, avg_tx_packet_size.1);
vec![Row::new([
Text::styled(rx_label, self.styles.rx_style),
Text::styled(tx_label, self.styles.tx_style),
Text::styled(total_rx_label, self.styles.total_rx_style),
Text::styled(total_tx_label, self.styles.total_tx_style),
Text::styled(format!("{rx_packet_rate}pkt/s"), self.styles.rx_style),
Text::styled(format!("{tx_packet_rate}pkt/s"), self.styles.tx_style),
Text::styled(avg_rx_packet_size_label, self.styles.rx_style),
Text::styled(avg_tx_packet_size_label, self.styles.tx_style),
])]
} else {
vec![Row::new([
Text::styled(rx_label, self.styles.rx_style),
Text::styled(tx_label, self.styles.tx_style),
Text::styled(total_rx_label, self.styles.total_rx_style),
Text::styled(total_tx_label, self.styles.total_tx_style),
])]
};
let headers = if app_state.app_config_fields.network_show_packets {
vec![
"RX", "TX", "Total RX", "Total TX", "RX Pkts", "TX Pkts", "Avg RX", "Avg TX",
]
} else {
vec!["RX", "TX", "Total RX", "Total TX"]
};
let num_columns = headers.len();
let column_width = draw_loc.width.saturating_sub(2) / num_columns as u16;
// Draw
f.render_widget(
Table::new(
total_network,
&((std::iter::repeat_n(draw_loc.width.saturating_sub(2) / 4, 4))
&((std::iter::repeat_n(column_width, num_columns))
.map(Constraint::Length)
.collect::<Vec<_>>()),
)
.header(Row::new(NETWORK_HEADERS).style(self.styles.table_header_style))
.header(Row::new(headers).style(self.styles.table_header_style))
.block(Block::default().borders(Borders::ALL).border_style(
if app_state.current_widget.widget_id == widget_id {
self.styles.highlighted_border_style
+8
View File
@@ -156,6 +156,8 @@ pub struct DataCollector {
total_rx: u64,
total_tx: u64,
total_rx_packets: u64,
total_tx_packets: u64,
unnormalized_cpu: bool,
use_current_cpu_total: bool,
@@ -211,6 +213,8 @@ impl DataCollector {
last_collection_time,
total_rx: 0,
total_tx: 0,
total_rx_packets: 0,
total_tx_packets: 0,
show_average_cpu: false,
widgets_to_harvest: UsedWidgets::default(),
#[cfg(feature = "battery")]
@@ -522,12 +526,16 @@ impl DataCollector {
self.last_collection_time,
&mut self.total_rx,
&mut self.total_tx,
&mut self.total_rx_packets,
&mut self.total_tx_packets,
self.data.collection_time,
&self.filters.net_filter,
);
self.total_rx = net_data.total_rx;
self.total_tx = net_data.total_tx;
self.total_rx_packets = net_data.total_rx_packets;
self.total_tx_packets = net_data.total_tx_packets;
self.data.network = Some(net_data);
}
}
+4
View File
@@ -10,6 +10,10 @@ pub struct NetworkHarvest {
pub tx: u64,
pub total_rx: u64,
pub total_tx: u64,
pub rx_packets: u64,
pub tx_packets: u64,
pub total_rx_packets: u64,
pub total_tx_packets: u64,
}
impl NetworkHarvest {
+16 -3
View File
@@ -11,10 +11,13 @@ use crate::app::filter::Filter;
// account, so we can show per-interface!
pub fn get_network_data(
networks: &Networks, prev_net_access_time: Instant, prev_net_rx: &mut u64,
prev_net_tx: &mut u64, curr_time: Instant, filter: &Option<Filter>,
prev_net_tx: &mut u64, prev_net_rx_packets: &mut u64, prev_net_tx_packets: &mut u64,
curr_time: Instant, filter: &Option<Filter>,
) -> NetworkHarvest {
let mut total_rx: u64 = 0;
let mut total_tx: u64 = 0;
let mut total_rx_packets: u64 = 0;
let mut total_tx_packets: u64 = 0;
for (name, network) in networks {
let to_keep = if let Some(filter) = filter {
@@ -26,26 +29,36 @@ pub fn get_network_data(
if to_keep {
total_rx += network.total_received() * 8;
total_tx += network.total_transmitted() * 8;
total_rx_packets += network.total_packets_received();
total_tx_packets += network.total_packets_transmitted();
}
}
let elapsed_time = curr_time.duration_since(prev_net_access_time).as_secs_f64();
let (rx, tx) = if elapsed_time == 0.0 {
(0, 0)
let (rx, tx, rx_packets, tx_packets) = if elapsed_time == 0.0 {
(0, 0, 0, 0)
} else {
(
((total_rx.saturating_sub(*prev_net_rx)) as f64 / elapsed_time) as u64,
((total_tx.saturating_sub(*prev_net_tx)) as f64 / elapsed_time) as u64,
((total_rx_packets.saturating_sub(*prev_net_rx_packets)) as f64 / elapsed_time) as u64,
((total_tx_packets.saturating_sub(*prev_net_tx_packets)) as f64 / elapsed_time) as u64,
)
};
*prev_net_rx = total_rx;
*prev_net_tx = total_tx;
*prev_net_rx_packets = total_rx_packets;
*prev_net_tx_packets = total_tx_packets;
NetworkHarvest {
rx,
tx,
total_rx,
total_tx,
rx_packets,
tx_packets,
total_rx_packets,
total_tx_packets,
}
}
+14
View File
@@ -276,6 +276,7 @@ pub(crate) fn init_app(args: BottomArgs, config: Config) -> Result<(App, BottomL
let network_scale_type = get_network_scale_type(args, config);
let network_use_binary_prefix =
is_flag_enabled!(network_use_binary_prefix, args.network, config);
let network_show_packets = get_network_show_packets(args, config);
let proc_columns: Option<IndexSet<ProcWidgetColumn>> = {
config.processes.as_ref().and_then(|cfg| {
@@ -330,6 +331,7 @@ pub(crate) fn init_app(args: BottomArgs, config: Config) -> Result<(App, BottomL
network_scale_type,
network_unit_type,
network_use_binary_prefix,
network_show_packets,
retention_ms,
dedicated_average_row: get_dedicated_avg_row(config),
default_tree_collapse: is_default_tree_collapsed,
@@ -977,6 +979,18 @@ fn get_network_scale_type(args: &BottomArgs, config: &Config) -> AxisScaling {
AxisScaling::Linear
}
fn get_network_show_packets(args: &BottomArgs, config: &Config) -> bool {
if args.network.show_packets {
return true;
} else if let Some(network_config) = &config.network {
if let Some(show_packets) = network_config.show_packets {
return show_packets;
}
}
false
}
fn get_retention(args: &BottomArgs, config: &Config) -> OptionResult<u64> {
const DEFAULT_RETENTION_MS: u64 = 600 * 1000; // Keep 10 minutes of data.
+9
View File
@@ -590,6 +590,15 @@ pub struct NetworkArgs {
alias = "use-old-network-legend"
)]
pub use_old_network_legend: bool,
#[arg(
long,
action = ArgAction::SetTrue,
help = "Displays packets information (packet rate and average packet size) in the network widget.",
long_help = "Displays packets information including packet rate (packets per second) and average packet size in the network widget.",
alias = "show-packets"
)]
pub show_packets: bool,
}
/// Battery arguments/config options.
+1
View File
@@ -48,6 +48,7 @@ pub(crate) struct GeneralConfig {
pub(crate) network_use_bytes: Option<bool>,
pub(crate) network_use_log: Option<bool>,
pub(crate) network_use_binary_prefix: Option<bool>,
pub(crate) show_packets: Option<bool>,
pub(crate) disable_gpu: Option<bool>,
pub(crate) enable_cache_memory: Option<bool>,
pub(crate) retention: Option<StringOrNum>,
+2
View File
@@ -9,4 +9,6 @@ use super::IgnoreList;
pub(crate) struct NetworkConfig {
/// A filter over the network interface names.
pub(crate) interface_filter: Option<IgnoreList>,
/// Whether to show packets information (packet rate and average packet size).
pub(crate) show_packets: Option<bool>,
}
+5 -1
View File
@@ -64,8 +64,12 @@ pub fn get_decimal_bytes(bytes: u64) -> (f64, &'static str) {
/// Given a value in _bits_, turn a tuple containing the value and a unit.
#[inline]
pub fn convert_bits(bits: u64, base_two: bool) -> (f64, &'static str) {
let bytes = bits / 8;
convert_bytes(bits / 8, base_two)
}
/// Given a value in _bytes_, turn a tuple containing the value and a unit.
#[inline]
pub fn convert_bytes(bytes: u64, base_two: bool) -> (f64, &'static str) {
if base_two {
get_binary_bytes(bytes)
} else {