feat: Display current bandwidth based on recent activity

This commit is contained in:
Marco Cadetg (aider)
2025-05-10 12:51:56 +02:00
parent 5801e5cd15
commit d01bbf90cc
3 changed files with 80 additions and 20 deletions

View File

@@ -333,7 +333,7 @@ impl App {
/// Update application state on tick
pub fn on_tick(&mut self) -> Result<()> {
// Store currently selected connection (if any)
let selected = self.selected_connection.clone();
let selected_conn_key = self.selected_connection.as_ref().map(|sc| self.get_connection_key(sc));
// Update connections from shared data updated by the background thread
if let Some(shared_data_arc) = &self.connections_data_shared {
@@ -370,11 +370,35 @@ impl App {
});
// Update connections with the sorted list
self.connections = new_connections;
self.connections = new_connections; // self.connections is now updated
// Calculate current rates for each connection
let now = Instant::now();
for conn in &mut self.connections {
let time_delta = now.duration_since(conn.last_rate_update_time);
let time_delta_secs = time_delta.as_secs_f64();
if time_delta_secs > 0.0 {
let bytes_sent_delta = conn.bytes_sent.saturating_sub(conn.prev_bytes_sent);
let bytes_received_delta = conn.bytes_received.saturating_sub(conn.prev_bytes_received);
conn.current_outgoing_rate_bps = bytes_sent_delta as f64 / time_delta_secs;
conn.current_incoming_rate_bps = bytes_received_delta as f64 / time_delta_secs;
} else {
// Avoid division by zero if time_delta is too small or zero
conn.current_outgoing_rate_bps = 0.0;
conn.current_incoming_rate_bps = 0.0;
}
conn.prev_bytes_sent = conn.bytes_sent;
conn.prev_bytes_received = conn.bytes_received;
conn.last_rate_update_time = now;
}
// Restore selected connection position if possible
if let Some(ref conn) = selected {
if let Some(idx) = self.find_connection_index(conn) {
if let Some(key) = selected_conn_key {
if let Some(idx) = self.find_connection_index_by_key(&key) {
self.selected_connection_idx = idx;
self.selected_connection = Some(self.connections[idx].clone());
} else if !self.connections.is_empty() {
@@ -394,6 +418,26 @@ impl App {
} else {
// connections_data_shared is None, likely before start_capture fully initializes it.
// self.connections will not be updated this tick.
// Still, update rates for existing connections if any (e.g. from initial load)
let now = Instant::now();
for conn in &mut self.connections {
let time_delta = now.duration_since(conn.last_rate_update_time);
let time_delta_secs = time_delta.as_secs_f64();
if time_delta_secs > 0.0 {
let bytes_sent_delta = conn.bytes_sent.saturating_sub(conn.prev_bytes_sent);
let bytes_received_delta = conn.bytes_received.saturating_sub(conn.prev_bytes_received);
conn.current_outgoing_rate_bps = bytes_sent_delta as f64 / time_delta_secs;
conn.current_incoming_rate_bps = bytes_received_delta as f64 / time_delta_secs;
} else {
conn.current_outgoing_rate_bps = 0.0;
conn.current_incoming_rate_bps = 0.0;
}
conn.prev_bytes_sent = conn.bytes_sent;
conn.prev_bytes_received = conn.bytes_received;
conn.last_rate_update_time = now;
}
}
Ok(())
@@ -510,17 +554,13 @@ impl App {
)
}
/// Find the index of a connection that matches the selected connection
fn find_connection_index(&self, selected: &Connection) -> Option<usize> {
let selected_key = self.get_connection_key(selected);
/// Find the index of a connection by its key
fn find_connection_index_by_key(&self, target_key: &str) -> Option<usize> {
for (i, conn) in self.connections.iter().enumerate() {
let key = self.get_connection_key(conn);
if key == selected_key {
if self.get_connection_key(conn) == target_key {
return Some(i);
}
}
None
}

View File

@@ -92,8 +92,13 @@ pub struct Connection {
pub packets_received: u64,
pub created_at: SystemTime,
pub last_activity: SystemTime,
// pub service_port: Option<u16>, // Field removed as it's unused
pub service_name: Option<String>,
// Fields for current rate calculation
pub prev_bytes_sent: u64,
pub prev_bytes_received: u64,
pub last_rate_update_time: Instant,
pub current_incoming_rate_bps: f64,
pub current_outgoing_rate_bps: f64,
}
// get_service_name_raw function is removed.
@@ -121,6 +126,12 @@ impl Connection {
created_at: now,
last_activity: now,
service_name: None, // Service name will be set by NetworkMonitor
// Initialize new fields for rate calculation
prev_bytes_sent: 0,
prev_bytes_received: 0,
last_rate_update_time: Instant::now(),
current_incoming_rate_bps: 0.0,
current_outgoing_rate_bps: 0.0,
};
new_conn
}

View File

@@ -170,8 +170,8 @@ fn draw_connections_list(f: &mut Frame, app: &mut App, area: Rect) {
let (local_display, remote_display) = formatted_addresses[idx].clone();
let service_display = conn.service_name.clone().unwrap_or_else(|| "-".to_string());
let incoming_rate_str = format_rate(conn.bytes_received, conn.age());
let outgoing_rate_str = format_rate(conn.bytes_sent, conn.age());
let incoming_rate_str = format_rate_from_bps(conn.current_incoming_rate_bps);
let outgoing_rate_str = format_rate_from_bps(conn.current_outgoing_rate_bps);
let bandwidth_display = format!("{} / {}", incoming_rate_str, outgoing_rate_str);
@@ -267,12 +267,12 @@ fn draw_side_panel(f: &mut Frame, app: &App, area: Rect) -> Result<()> {
Line::from(format!(
"{}: {}",
app.i18n.get("total_incoming"),
format_rate(app.connections.iter().map(|c| c.bytes_received).sum(), app.start_time.elapsed())
format_rate_from_bps(app.connections.iter().map(|c| c.current_incoming_rate_bps).sum())
)),
Line::from(format!(
"{}: {}",
app.i18n.get("total_outgoing"),
format_rate(app.connections.iter().map(|c| c.bytes_sent).sum(), app.start_time.elapsed())
format_rate_from_bps(app.connections.iter().map(|c| c.current_outgoing_rate_bps).sum())
)),
];
@@ -754,26 +754,35 @@ fn draw_status_bar(f: &mut Frame, app: &App, area: Rect) {
f.render_widget(status_bar, area);
}
/// Format rate to human readable form (KB/s, MB/s, etc.)
/// Format rate (given as bytes and duration) to human readable form (KB/s, MB/s, etc.)
fn format_rate(bytes: u64, duration: std::time::Duration) -> String {
if duration.as_secs() == 0 {
if duration.as_secs_f64() < 0.001 { // Avoid division by zero or extremely small durations
return "-".to_string();
}
let bytes_per_second = bytes as f64 / duration.as_secs_f64();
format_rate_from_bps(bytes_per_second)
}
/// Format rate (given as f64 bytes_per_second) to human readable form (KB/s, MB/s, etc.)
fn format_rate_from_bps(bytes_per_second: f64) -> String {
const KB_PER_SEC: f64 = 1024.0;
const MB_PER_SEC: f64 = KB_PER_SEC * 1024.0;
const GB_PER_SEC: f64 = MB_PER_SEC * 1024.0;
if bytes_per_second.is_nan() || bytes_per_second.is_infinite() {
return "-".to_string();
}
if bytes_per_second >= GB_PER_SEC {
format!("{:.2} GB/s", bytes_per_second / GB_PER_SEC)
} else if bytes_per_second >= MB_PER_SEC {
format!("{:.2} MB/s", bytes_per_second / MB_PER_SEC)
} else if bytes_per_second >= KB_PER_SEC {
format!("{:.2} KB/s", bytes_per_second / KB_PER_SEC)
} else {
} else if bytes_per_second > 0.1 || bytes_per_second == 0.0 { // Show B/s for very small rates or zero
format!("{:.0} B/s", bytes_per_second)
} else { // For very small, non-zero rates, indicate less than 1 B/s
"<1 B/s".to_string()
}
}