diff --git a/CHANGELOG.md b/CHANGELOG.md index c7ea22b..ea0d653 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,11 @@ + github action publish to crates.io, [90b2e3f6db0d5f63840cd80888a30da6ecc22f20] + derive Eq where appropriate, [d7c2601f959bc12a64cd25cef59c837e1e8c2b2a] + ignore containers 'oxker' containers, [1be9f52ad4a68f93142784e9df630c59cdec0a79] ++ update container info if container is either running OR restarting, [5f12362db7cb61ca68f75b99ecfc9725380d87d2] ### Fixes + devcontainer updated, [3bde4f5629539cab3dbb57556663ab81685f9d7a] ++ Use Binate value to enable two cycles of cpu/mem update to be executed (for each container) at the same time, refactor hashmap spawn sinsertions, [7ec58e79a1316ad1f7e50a2781dea0fe8422c588] ### Refactors + improved way to remove leading '/' of container name, [832e9782d7765872cbb84df6b3703fc08cb353c9] diff --git a/src/app_data/mod.rs b/src/app_data/mod.rs index 19a4a57..76d68c0 100644 --- a/src/app_data/mod.rs +++ b/src/app_data/mod.rs @@ -443,7 +443,7 @@ impl AppData { if f.starts_with('/') { f.remove(0); } - f.to_string() + f.clone() }) }); @@ -457,7 +457,7 @@ impl AppData { .as_ref() .map_or("".to_owned(), |f| f.trim().to_owned()); - let image = i.image.as_ref().map_or("".to_owned(), |f| f.clone()); + let image = i.image.as_ref().map_or("".to_owned(), std::clone::Clone::clone); if let Some(current_container) = self.get_container_by_id(id) { if current_container.name != name { diff --git a/src/docker_data/mod.rs b/src/docker_data/mod.rs index 6fbc287..f874aa1 100644 --- a/src/docker_data/mod.rs +++ b/src/docker_data/mod.rs @@ -7,7 +7,6 @@ use futures_util::StreamExt; use parking_lot::Mutex; use std::{ collections::HashMap, - fmt, sync::{ atomic::{AtomicBool, Ordering}, Arc, @@ -24,19 +23,28 @@ use crate::{ mod message; pub use message::DockerMessage; + #[derive(Debug, Hash, Clone, PartialEq, Eq)] enum SpawnId { - Stats(String), + Stats((String, Binate)), Log(String), } -impl fmt::Display for SpawnId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let disp = match self { - Self::Stats(id) => format!("stats::{id}"), - Self::Log(id) => format!("logs::{id}"), +/// Cpu & Mem stats take twice as long as the update interval to get a value, so will have two being executed at the same time +/// SpawnId::Stats takes container_id and binate value to enable both cycles of the same container_id to be inserted into the hashmap +/// Binate value is toggled when all join handles have been spawned off +#[derive(Debug, Hash, Clone, PartialEq, Eq)] +enum Binate { + One, + Two +} + +impl Binate { + fn toggle(&mut self) { + *self = match self { + Self::One => Self::Two, + Self::Two => Self::One, }; - write!(f, "{}", disp) } } @@ -49,6 +57,7 @@ pub struct DockerData { receiver: Receiver, spawns: Arc>>>, timestamps: bool, + binate: Binate } impl DockerData { @@ -81,13 +90,14 @@ impl DockerData { /// Get a single docker stat in order to update mem and cpu usage /// don't take &self, so that can tokio::spawn into it's own thread - /// remove from spawns hashmap when complete + /// remove if from spawns hashmap when complete async fn update_container_stat( docker: Arc, id: String, app_data: Arc>, is_running: bool, spawns: Arc>>>, + spawn_id: SpawnId, ) { let mut stream = docker .stats( @@ -110,14 +120,15 @@ impl DockerData { let cpu_stats = Self::calculate_usage(&stats); - let no_bytes = (0, 0); + let no_bytes = || (0, 0); + let (rx, tx) = if let Some(key) = some_key { match stats.networks.unwrap_or_default().get(&key) { - Some(data) => (data.rx_bytes.to_owned(), data.tx_bytes.to_owned()), - None => no_bytes, + Some(data) => (data.rx_bytes, data.tx_bytes), + None => no_bytes(), } } else { - no_bytes + no_bytes() }; if is_running { @@ -134,7 +145,7 @@ impl DockerData { .lock() .update_stats(&id, None, None, mem_limit, rx, tx); } - spawns.lock().remove(&SpawnId::Stats(id.clone())); + spawns.lock().remove(&spawn_id); } } @@ -146,8 +157,10 @@ impl DockerData { let spawns = Arc::clone(&self.spawns); let is_running = *is_running; let id = id.clone(); - let key = SpawnId::Stats(id.clone()); + let key = SpawnId::Stats((id.clone(), self.binate.clone())); + + let spawn_key = key.clone(); self.spawns.lock().entry(key).or_insert_with(|| { tokio::spawn(Self::update_container_stat( docker, @@ -155,9 +168,11 @@ impl DockerData { app_data, is_running, spawns, + spawn_key )) }); } + self.binate.toggle(); } /// Get all current containers, handle into ContainerItem in the app_data struct rather than here @@ -250,39 +265,34 @@ impl DockerData { let app_data = Arc::clone(&self.app_data); let spawns = Arc::clone(&self.spawns); let key = SpawnId::Log(id.clone()); - self.spawns.lock().insert( - key, - tokio::spawn(Self::update_log( - docker, id, timestamps, 0, app_data, spawns, - )), - ); + self.spawns.lock().insert(key, tokio::spawn(Self::update_log( + docker, id, timestamps, 0, app_data, spawns, + ))); } } + /// Update all cpu_mem, and selected container log (if a log update join_handle isn't currently being executed) async fn update_everything(&mut self) { let all_ids = self.update_all_containers().await; let optional_index = self.app_data.lock().get_selected_log_index(); if let Some(index) = optional_index { + // this could be neater let id = self.app_data.lock().containers.items[index].id.clone(); - let key = SpawnId::Log(id.clone()); - let running = self.spawns.lock().contains_key(&key); - if !running { + self.spawns.lock().entry(key).or_insert_with(|| { let since = self.app_data.lock().containers.items[index].last_updated as i64; let docker = Arc::clone(&self.docker); let timestamps = self.timestamps; - let app_data = Arc::clone(&self.app_data); let spawns = Arc::clone(&self.spawns); - let s = tokio::spawn(Self::update_log( + tokio::spawn(Self::update_log( docker, id, timestamps, since, app_data, spawns, - )); - self.spawns.lock().insert(key, s); - } + )) + }); }; - self.update_all_container_stats(&all_ids).await; + } /// Animate the loading icon @@ -413,6 +423,7 @@ impl DockerData { spawns: Arc::new(Mutex::new(HashMap::new())), timestamps: args.timestamp, is_running, + binate: Binate::One }; inner.initialise_container_data().await; diff --git a/src/input_handler/mod.rs b/src/input_handler/mod.rs index 6fa5132..731b005 100644 --- a/src/input_handler/mod.rs +++ b/src/input_handler/mod.rs @@ -143,8 +143,8 @@ impl InputHandler { if show_error { match key_code { - KeyCode::Char('q') | KeyCode::Char('Q') => self.quit().await, - KeyCode::Char('c') | KeyCode::Char('C') => { + KeyCode::Char('q' | 'Q') => self.quit().await, + KeyCode::Char('c' | 'C') => { self.app_data.lock().show_error = false; self.app_data.lock().remove_error(); } @@ -152,9 +152,9 @@ impl InputHandler { } } else if show_info { match key_code { - KeyCode::Char('q') | KeyCode::Char('Q') => self.quit().await, - KeyCode::Char('h') | KeyCode::Char('H') => self.gui_state.lock().show_help = false, - KeyCode::Char('m') | KeyCode::Char('M') => self.m_button(), + KeyCode::Char('q' | 'Q') => self.quit().await, + KeyCode::Char('h' | 'H') => self.gui_state.lock().show_help = false, + KeyCode::Char('m' | 'M') => self.m_button(), _ => (), } } else { @@ -169,9 +169,9 @@ impl InputHandler { KeyCode::Char('7') => self.sort(Header::Image), KeyCode::Char('8') => self.sort(Header::Rx), KeyCode::Char('9') => self.sort(Header::Tx), - KeyCode::Char('q') | KeyCode::Char('Q') => self.quit().await, - KeyCode::Char('h') | KeyCode::Char('H') => self.gui_state.lock().show_help = true, - KeyCode::Char('m') | KeyCode::Char('M') => self.m_button(), + KeyCode::Char('q' | 'Q') => self.quit().await, + KeyCode::Char('h' | 'H') => self.gui_state.lock().show_help = true, + KeyCode::Char('m' | 'M') => self.m_button(), KeyCode::Tab => { // Skip control panel if no containers, could be refactored let has_containers = self.app_data.lock().get_container_len() == 0; @@ -216,13 +216,13 @@ impl InputHandler { SelectablePanel::Commands => locked_data.docker_command_end(), } } - KeyCode::Up | KeyCode::Char('k') | KeyCode::Char('K') => self.previous(), + KeyCode::Up | KeyCode::Char('k' | 'K') => self.previous(), KeyCode::PageUp => { for _ in 0..=6 { self.previous(); } } - KeyCode::Down | KeyCode::Char('j') | KeyCode::Char('J') => self.next(), + KeyCode::Down | KeyCode::Char('j' | 'J') => self.next(), KeyCode::PageDown => { for _ in 0..=6 { self.next(); diff --git a/src/ui/mod.rs b/src/ui/mod.rs index d717910..589f918 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -106,6 +106,7 @@ async fn run_app( } else { let mut now = Instant::now(); loop { + if terminal.draw(|f| ui(f, &app_data, &gui_state)).is_err() { return Err(AppError::Terminal); }