diff --git a/Cargo.lock b/Cargo.lock index 26f97ca..db398c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -607,6 +607,7 @@ dependencies = [ "directories", "fuzzy-matcher", "http-test-server", + "itertools", "listeners", "ratatui", "serde", diff --git a/Cargo.toml b/Cargo.toml index 1c86b32..09a8ede 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ directories = "5.0" toml = "0.8" serde = { version = "1.0", features = ["derive"] } fuzzy-matcher = "0.3.7" +itertools = "0.13" [dev-dependencies] http-test-server = "2.1.1" diff --git a/src/processes.rs b/src/processes.rs index c7d2adb..7e8b441 100644 --- a/src/processes.rs +++ b/src/processes.rs @@ -1,8 +1,10 @@ use std::cmp::Ordering; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::time::SystemTime; use anyhow::Result; +use itertools::Itertools; +use listeners::Listener; use sysinfo::{Pid, System, Uid, Users}; use sysinfo::{ProcessRefreshKind, RefreshKind}; @@ -25,7 +27,7 @@ pub struct ProcessManager { use self::filters::OptionsFilter; use self::utils::{ - find_current_process_user, get_process_args, process_run_time, process_start_time, + find_current_process_user, get_process_args, process_run_time, to_system_local_time, }; pub trait ProcessInfo { @@ -198,7 +200,9 @@ impl ProcessManager { user_name, ports: ports.cloned(), memory: prc.memory(), - start_time: process_start_time(prc.start_time()), + start_time: to_system_local_time(prc.start_time()) + .format("%H:%M:%S") + .to_string(), run_time: process_run_time(prc.run_time(), SystemTime::now()), } } @@ -227,21 +231,21 @@ fn process_refresh_kind() -> ProcessRefreshKind { } fn refresh_ports() -> HashMap { - listeners::get_all() + let ports = listeners::get_all() //NOTE: we ignore errors comming from listeners - .unwrap_or_default() + .unwrap_or_default(); + create_sorted_process_ports(ports) +} + +//NOTE: we sort this so order of ports is deterministic and doesn't change durig refresh +fn create_sorted_process_ports(ports: HashSet) -> ProcessPorts { + ports .into_iter() - .fold(HashMap::new(), |mut acc: ProcessPorts, l| { - match acc.get_mut(&l.process.pid) { - Some(ports) => { - ports.push_str(&format!(", {}", l.socket.port())); - } - None => { - acc.insert(l.process.pid, format!("{}", l.socket.port())); - } - } - acc - }) + .map(|l| (l.process.pid, l.socket.port())) + .into_group_map() + .into_iter() + .map(|(pid, ports)| (pid, ports.into_iter().sorted_by(|a, b| a.cmp(b)).join(", "))) + .collect() } #[derive(Debug)] @@ -356,8 +360,24 @@ impl Ord for MatchType { #[cfg(test)] mod tests { + use listeners::{Listener, Process}; + use super::*; + #[test] + fn should_create_sorted_process_ports() { + let value = [ + create_listener(1, 8080), + create_listener(1, 100), + create_listener(1, 50), + create_listener(2, 1234), + ]; + let process_ports = create_sorted_process_ports(HashSet::from(value)); + assert_eq!(process_ports.len(), 2); + assert_eq!(process_ports.get(&1).unwrap(), "50, 100, 8080"); + assert_eq!(process_ports.get(&2).unwrap(), "1234"); + } + #[test] fn match_type_sort_in_correct_order() { let mut vec_to_sort = vec![ @@ -399,4 +419,14 @@ mod tests { ] ); } + + fn create_listener(pid: u32, port: u16) -> Listener { + Listener { + process: Process { + pid, + name: format!("p1{pid}"), + }, + socket: format!("127.0.0.1:{port}").parse().unwrap(), + } + } } diff --git a/src/processes/utils.rs b/src/processes/utils.rs index b60971e..196ba8d 100644 --- a/src/processes/utils.rs +++ b/src/processes/utils.rs @@ -1,7 +1,7 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; use anyhow::{anyhow, Context, Result}; -use chrono::{DateTime, Utc}; +use chrono::{DateTime, Local}; use sysinfo::{System, Uid}; use super::ProcessInfo; @@ -35,10 +35,9 @@ pub(super) fn process_run_time(run_duration_since_epoch: u64, now: SystemTime) - format!("{}s", seconds) } -pub(super) fn process_start_time(seconds_since_epoch: u64) -> String { +pub(super) fn to_system_local_time(seconds_since_epoch: u64) -> DateTime { let system_time = UNIX_EPOCH + Duration::from_secs(seconds_since_epoch); - let datetime: DateTime = system_time.into(); - datetime.format("%H:%M:%S").to_string() + system_time.into() } pub(super) fn find_current_process_user(sys: &System) -> Result { @@ -170,14 +169,18 @@ pub mod tests { } #[test] - fn test_process_start_time() { - let start_time = |hours: u64, minutes: u64, seconds: u64| { + fn test_to_system_local_time() { + let system_time_utc = |hours: u64, minutes: u64, seconds: u64| { let seconds_since_epoch = as_duration(hours, minutes, seconds).as_secs(); - process_start_time(seconds_since_epoch) + to_system_local_time(seconds_since_epoch) + //NOTE: without this it will fail at CI/CD pipeline where local time is different + .to_utc() + .format("%H:%M:%S") + .to_string() }; - assert_eq!(start_time(0, 0, 0), "00:00:00"); - assert_eq!(start_time(1, 45, 15), "01:45:15"); - assert_eq!(start_time(5, 29, 59), "05:29:59"); + assert_eq!(system_time_utc(0, 0, 0), "00:00:00"); + assert_eq!(system_time_utc(1, 45, 15), "01:45:15"); + assert_eq!(system_time_utc(5, 29, 59), "05:29:59"); } fn as_duration(hours: u64, minutes: u64, seconds: u64) -> Duration {