mirror of
https://github.com/ClementTsang/bottom.git
synced 2026-08-28 14:26:35 +00:00
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:
@@ -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
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user