Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add PID column to Process table #379

Merged
merged 12 commits into from
Mar 18, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

## Added
* CI: include generated assets in release archive #359 - @cyqsimon
* Add PID column to the process table #379 - @notjedi

## Changed
* CI: strip release binaries for all targets #358 - @cyqsimon
Expand Down
52 changes: 47 additions & 5 deletions src/display/components/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,36 +28,46 @@ pub enum DisplayLayout {
C2([u16; 2]),
/// Show 3 columns.
C3([u16; 3]),
/// Show 4 columns.
C4([u16; 4]),
}

impl Index<usize> for DisplayLayout {
type Output = u16;

fn index(&self, i: usize) -> &Self::Output {
match self {
Self::C2(arr) => &arr[i],
Self::C3(arr) => &arr[i],
Self::C4(arr) => &arr[i],
}
}
}

impl DisplayLayout {
#[inline]
fn columns_count(&self) -> usize {
match self {
Self::C2(_) => 2,
Self::C3(_) => 3,
Self::C4(_) => 4,
}
}

#[inline]
fn iter(&self) -> impl Iterator<Item = &u16> {
match self {
Self::C2(ws) => ws.iter(),
Self::C3(ws) => ws.iter(),
Self::C4(ws) => ws.iter(),
}
}

#[inline]
fn widths_sum(&self) -> u16 {
self.iter().sum()
}

/// Returns the computed actual width and the spacer width.
///
/// See [`Table`] for layout rules.
Expand Down Expand Up @@ -87,6 +97,17 @@ impl DisplayLayout {
let w2_new = (w2 as f64 * m).trunc() as u16;
Self::C3([available_without_spacers - w1_new - w2_new, w1_new, w2_new])
}
Self::C4([_w0, w1, w2, w3]) => {
let w1_new = (w1 as f64 * m).trunc() as u16;
let w2_new = (w2 as f64 * m).trunc() as u16;
let w3_new = (w3 as f64 * m).trunc() as u16;
Self::C4([
available_without_spacers - w1_new - w2_new - w3_new,
w1_new,
w2_new,
w3_new,
])
}
};

(computed, spacer)
Expand All @@ -101,26 +122,41 @@ impl DisplayLayout {
enum TableData {
/// A table with 3 columns.
C3(NColsTableData<3>),
/// A table with 4 columns.
C4(NColsTableData<4>),
}

impl From<NColsTableData<3>> for TableData {
fn from(data: NColsTableData<3>) -> Self {
Self::C3(data)
}
}

impl From<NColsTableData<4>> for TableData {
fn from(data: NColsTableData<4>) -> Self {
Self::C4(data)
}
}

impl TableData {
fn column_names(&self) -> &[&str] {
match self {
Self::C3(inner) => &inner.column_names,
Self::C4(inner) => &inner.column_names,
}
}

fn rows(&self) -> Vec<&[String]> {
match self {
Self::C3(inner) => inner.rows.iter().map(|r| r.as_slice()).collect(),
Self::C4(inner) => inner.rows.iter().map(|r| r.as_slice()).collect(),
}
}

fn column_selector(&self) -> &dyn Fn(&DisplayLayout) -> Vec<usize> {
match self {
Self::C3(inner) => inner.column_selector.as_ref(),
Self::C4(inner) => inner.column_selector.as_ref(),
}
}
}
Expand Down Expand Up @@ -171,6 +207,7 @@ pub struct Table {
width_cutoffs: Vec<(u16, DisplayLayout)>,
data: TableData,
}

impl Table {
pub fn create_connections_table(state: &UIState, ip_to_host: &HashMap<IpAddr, String>) -> Self {
use DisplayLayout as D;
Expand Down Expand Up @@ -214,6 +251,7 @@ impl Table {
let column_selector = Rc::new(|layout: &D| match layout {
D::C2(_) => vec![0, 2],
D::C3(_) => vec![0, 1, 2],
D::C4(_) => unreachable!(),
});

Table {
Expand All @@ -236,11 +274,12 @@ impl Table {
(0, D::C2([16, 18])),
(50, D::C3([16, 12, 20])),
(60, D::C3([24, 12, 20])),
(80, D::C3([36, 16, 24])),
(80, D::C4([28, 12, 12, 24])),
];

let column_names = [
"Process",
"PID",
"Connections",
if state.cumulative_mode {
"Data (Up / Down)"
Expand All @@ -251,9 +290,10 @@ impl Table {
let rows = state
.processes
.iter()
.map(|(process_name, data_for_process)| {
.map(|(proc_info, data_for_process)| {
[
(*process_name).to_string(),
proc_info.name.to_string(),
proc_info.pid.to_string(),
data_for_process.connection_count.to_string(),
display_upload_and_download(
data_for_process,
Expand All @@ -264,8 +304,9 @@ impl Table {
})
.collect();
let column_selector = Rc::new(|layout: &D| match layout {
D::C2(_) => vec![0, 2],
D::C3(_) => vec![0, 1, 2],
D::C2(_) => vec![0, 3],
D::C3(_) => vec![0, 2, 3],
D::C4(_) => vec![0, 1, 2, 3],
});

Table {
Expand Down Expand Up @@ -322,6 +363,7 @@ impl Table {
let column_selector = Rc::new(|layout: &D| match layout {
D::C2(_) => vec![0, 2],
D::C3(_) => vec![0, 1, 2],
D::C4(_) => unreachable!(),
});

Table {
Expand Down
10 changes: 6 additions & 4 deletions src/display/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::{
UIState,
},
network::{display_connection_string, display_ip_or_host, LocalSocket, Utilization},
os::ProcessInfo,
};

pub struct Ui<B>
Expand All @@ -32,7 +33,7 @@ where
terminal.hide_cursor().unwrap();
let state = {
let mut state = UIState::default();
state.interface_name = opts.interface.clone();
state.interface_name.clone_from(&opts.interface);
state.unit_family = opts.render_opts.unit_family.into();
state.cumulative_mode = opts.render_opts.total_utilization;
state
Expand All @@ -53,9 +54,10 @@ where

let output_process_data = |write_to_stdout: &mut (dyn FnMut(String) + Send),
no_traffic: &mut bool| {
for (process, process_network_data) in &state.processes {
for (proc_info, process_network_data) in &state.processes {
write_to_stdout(format!(
"process: <{timestamp}> \"{process}\" up/down Bps: {}/{} connections: {}",
"process: <{timestamp}> \"{}\" up/down Bps: {}/{} connections: {}",
proc_info.name,
process_network_data.total_bytes_uploaded,
process_network_data.total_bytes_downloaded,
process_network_data.connection_count
Expand Down Expand Up @@ -173,7 +175,7 @@ where

pub fn update_state(
&mut self,
connections_to_procs: HashMap<LocalSocket, String>,
connections_to_procs: HashMap<LocalSocket, ProcessInfo>,
utilization: Utilization,
ip_to_host: HashMap<IpAddr, String>,
) {
Expand Down
44 changes: 24 additions & 20 deletions src/display/ui_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::{
display::BandwidthUnitFamily,
mt_log,
network::{Connection, LocalSocket, Utilization},
os::ProcessInfo,
};

static RECALL_LENGTH: usize = 5;
Expand Down Expand Up @@ -72,23 +73,23 @@ impl Bandwidth for ConnectionData {
}

pub struct UtilizationData {
connections_to_procs: HashMap<LocalSocket, String>,
connections_to_procs: HashMap<LocalSocket, ProcessInfo>,
network_utilization: Utilization,
}

#[derive(Default)]
pub struct UIState {
/// The interface name in single-interface mode. `None` means all interfaces.
pub interface_name: Option<String>,
pub processes: Vec<(String, NetworkData)>,
pub processes: Vec<(ProcessInfo, NetworkData)>,
pub remote_addresses: Vec<(IpAddr, NetworkData)>,
pub connections: Vec<(Connection, ConnectionData)>,
pub total_bytes_downloaded: u128,
pub total_bytes_uploaded: u128,
pub cumulative_mode: bool,
pub unit_family: BandwidthUnitFamily,
pub utilization_data: VecDeque<UtilizationData>,
pub processes_map: HashMap<String, NetworkData>,
pub processes_map: HashMap<ProcessInfo, NetworkData>,
pub remote_addresses_map: HashMap<IpAddr, NetworkData>,
pub connections_map: HashMap<Connection, ConnectionData>,
/// Used for reducing logging noise.
Expand All @@ -98,7 +99,7 @@ pub struct UIState {
impl UIState {
pub fn update(
&mut self,
connections_to_procs: HashMap<LocalSocket, String>,
connections_to_procs: HashMap<LocalSocket, ProcessInfo>,
network_utilization: Utilization,
) {
self.utilization_data.push_back(UtilizationData {
Expand All @@ -108,7 +109,7 @@ impl UIState {
if self.utilization_data.len() > RECALL_LENGTH {
self.utilization_data.pop_front();
}
let mut processes: HashMap<String, NetworkData> = HashMap::new();
let mut processes: HashMap<ProcessInfo, NetworkData> = HashMap::new();
let mut remote_addresses: HashMap<IpAddr, NetworkData> = HashMap::new();
let mut connections: HashMap<Connection, ConnectionData> = HashMap::new();
let mut total_bytes_downloaded: u128 = 0;
Expand All @@ -127,7 +128,9 @@ impl UIState {
.or_default();
connection_data.total_bytes_downloaded += connection_info.total_bytes_downloaded;
connection_data.total_bytes_uploaded += connection_info.total_bytes_uploaded;
connection_data.interface_name = connection_info.interface_name.clone();
connection_data
.interface_name
.clone_from(&connection_info.interface_name);
data_for_remote_address.total_bytes_downloaded +=
connection_info.total_bytes_downloaded;
data_for_remote_address.total_bytes_uploaded +=
Expand All @@ -140,11 +143,10 @@ impl UIState {

let data_for_process = {
let local_socket = connection.local_socket;
let process_name = get_proc_name(connections_to_procs, &local_socket);
let proc_info = get_proc_info(connections_to_procs, &local_socket);

// only log each orphan connection once
if process_name.is_none() && !self.known_orphan_sockets.contains(&local_socket)
{
if proc_info.is_none() && !self.known_orphan_sockets.contains(&local_socket) {
// newer connections go in the front so that searches are faster
// basically recency bias
self.known_orphan_sockets.push_front(local_socket);
Expand All @@ -155,17 +157,18 @@ impl UIState {
.find(|(&LocalSocket { port, protocol, .. }, _)| {
port == local_socket.port && protocol == local_socket.protocol
})
.and_then(|(local_conn_lookalike, name)| {
.and_then(|(local_conn_lookalike, info)| {
network_utilization
.connections
.keys()
.find(|conn| &conn.local_socket == local_conn_lookalike)
.map(|conn| (conn, name))
.map(|conn| (conn, info))
}) {
Some((lookalike, name)) => {
Some((lookalike, proc_info)) => {
mt_log!(
warn,
r#""{name}" owns a similar looking connection, but its local ip doesn't match."#
r#""{0}" owns a similar looking connection, but its local ip doesn't match."#,
proc_info.name
);
mt_log!(warn, "Looking for: {connection:?}; found: {lookalike:?}");
}
Expand All @@ -175,9 +178,11 @@ impl UIState {
};
}

let process_display_name = process_name.unwrap_or("<UNKNOWN>").to_owned();
connection_data.process_name = process_display_name.clone();
processes.entry(process_display_name).or_default()
let proc_info = proc_info
.cloned()
.unwrap_or_else(|| ProcessInfo::new("<UNKNOWN>", 0));
connection_data.process_name = proc_info.name.clone();
processes.entry(proc_info).or_default()
};

data_for_process.total_bytes_downloaded += connection_info.total_bytes_downloaded;
Expand Down Expand Up @@ -221,10 +226,10 @@ impl UIState {
}
}

fn get_proc_name<'a>(
connections_to_procs: &'a HashMap<LocalSocket, String>,
fn get_proc_info<'a>(
connections_to_procs: &'a HashMap<LocalSocket, ProcessInfo>,
local_socket: &LocalSocket,
) -> Option<&'a str> {
) -> Option<&'a ProcessInfo> {
connections_to_procs
// direct match
.get(local_socket)
Expand Down Expand Up @@ -252,7 +257,6 @@ fn get_proc_name<'a>(
..*local_socket
})
})
.map(String::as_str)
}

fn merge_bandwidth<K, V>(self_map: &mut HashMap<K, V>, other_map: HashMap<K, V>)
Expand Down
3 changes: 2 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ use ratatui::backend::{Backend, CrosstermBackend};
use simplelog::WriteLogger;

use crate::cli::Opt;
use crate::os::ProcessInfo;

const DISPLAY_DELTA: Duration = Duration::from_millis(1000);

Expand Down Expand Up @@ -89,7 +90,7 @@ fn main() -> anyhow::Result<()> {
}

pub struct OpenSockets {
sockets_to_procs: HashMap<LocalSocket, String>,
sockets_to_procs: HashMap<LocalSocket, ProcessInfo>,
}

pub struct OsInputOutput {
Expand Down
Loading