diff --git a/Cargo.lock b/Cargo.lock index e107f9b..0df5431 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -129,9 +129,9 @@ checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "bollard" -version = "0.19.1" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899ca34eb6924d6ec2a77c6f7f5c7339e60fd68235eaf91edd5a15f12958bb06" +checksum = "97ccca1260af6a459d75994ad5acc1651bcabcbdbc41467cc9786519ab854c30" dependencies = [ "base64", "bollard-stubs", @@ -162,12 +162,11 @@ dependencies = [ [[package]] name = "bollard-stubs" -version = "1.48.3-rc.28.0.4" +version = "1.47.1-rc.27.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ea257e555d16a2c01e5593f40b73865cdf12efbceda33c6d14a2d8d1490368" +checksum = "3f179cfbddb6e77a5472703d4b30436bff32929c0aa8a9008ecf23d1d3cdd0da" dependencies = [ "serde", - "serde_json", "serde_repr", "serde_with", ] diff --git a/Cargo.toml b/Cargo.toml index 07124b6..440f0bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ similar_names = "allow" [dependencies] anyhow = "1.0" -bollard = "0.19" +bollard = "0.18" cansi = "2.2" clap = { version = "4.5", features = ["color", "derive", "unicode"] } crossterm = "0.29" diff --git a/docker/Dockerfile.unhealthy b/docker/Dockerfile.unhealthy index e0021a9..4a6d10a 100644 --- a/docker/Dockerfile.unhealthy +++ b/docker/Dockerfile.unhealthy @@ -2,7 +2,7 @@ FROM alpine:latest # Install a simple utility (e.g., curl) to run as a health check -RUN apk --no-cache add curl +RUN apk --no-cache add curl speedtest-cli # Create a dummy file that we will use in our health check RUN touch /tmp/healthy @@ -12,6 +12,6 @@ HEALTHCHECK --interval=5s --timeout=3s --retries=3 \ CMD [ ! -f /tmp/healthy ] || exit 1 # Start a basic loop that keeps the container running -CMD ["sh", "-c", "while :; do >&2 echo 'Container is running but will be unhealthy (also printing to stderr)'; sleep 30; done"] +CMD ["sh", "-c", "speedtest-cli; while :; do >&2 echo 'Container is running but will be unhealthy (also printing to stderr)'; sleep 30; done"] # docker build -t unhealthy-container . -f Dockerfile.unhealthy; docker run -d --name unhealthy unhealthy-container \ No newline at end of file diff --git a/src/app_data/container_state.rs b/src/app_data/container_state.rs index 15f4cd8..de27aac 100644 --- a/src/app_data/container_state.rs +++ b/src/app_data/container_state.rs @@ -342,54 +342,6 @@ impl From<(&str, &ContainerStatus)> for State { } } -/// Need status, to check if container is unhealthy or not -impl - From<( - &bollard::secret::ContainerSummaryStateEnum, - &ContainerStatus, - )> for State -{ - fn from( - (input, status): ( - &bollard::secret::ContainerSummaryStateEnum, - &ContainerStatus, - ), - ) -> Self { - match input { - bollard::secret::ContainerSummaryStateEnum::DEAD => Self::Dead, - bollard::secret::ContainerSummaryStateEnum::EXITED => Self::Exited, - bollard::secret::ContainerSummaryStateEnum::PAUSED => Self::Paused, - bollard::secret::ContainerSummaryStateEnum::REMOVING => Self::Removing, - bollard::secret::ContainerSummaryStateEnum::RESTARTING => Self::Restarting, - bollard::secret::ContainerSummaryStateEnum::RUNNING => { - if status.unhealthy() { - Self::Running(RunningState::Unhealthy) - } else { - Self::Running(RunningState::Healthy) - } - } - _ => Self::Unknown, - } - } -} - -/// Again, need status, to check if container is unhealthy or not -impl - From<( - Option<&bollard::secret::ContainerSummaryStateEnum>, - &ContainerStatus, - )> for State -{ - fn from( - (input, status): ( - Option<&bollard::secret::ContainerSummaryStateEnum>, - &ContainerStatus, - ), - ) -> Self { - input.map_or(Self::Unknown, |input| Self::from((input, status))) - } -} - /// Again, need status, to check if container is unhealthy or not impl From<(Option, &ContainerStatus)> for State { fn from((input, status): (Option, &ContainerStatus)) -> Self { diff --git a/src/app_data/mod.rs b/src/app_data/mod.rs index 5fc9f7a..3301476 100644 --- a/src/app_data/mod.rs +++ b/src/app_data/mod.rs @@ -881,13 +881,7 @@ impl AppData { .as_ref() .map_or(String::new(), std::clone::Clone::clone), ); - - let state = State::from(( - i.state - .as_ref() - .map_or(&bollard::secret::ContainerSummaryStateEnum::DEAD, |z| z), - &status, - )); + let state = State::from((i.state.as_ref().map_or("dead", |z| z), &status)); let image = i .image .as_ref() diff --git a/src/docker_data/mod.rs b/src/docker_data/mod.rs index 18025f9..e8c0237 100644 --- a/src/docker_data/mod.rs +++ b/src/docker_data/mod.rs @@ -1,7 +1,9 @@ use bollard::{ Docker, - query_parameters::{ListContainersOptions, LogsOptions, RemoveContainerOptions, StatsOptions}, - secret::ContainerStatsResponse, + container::{ + ListContainersOptions, LogsOptions, MemoryStatsStats, RemoveContainerOptions, + StartContainerOptions, Stats, StatsOptions, + }, service::ContainerSummary, }; use futures_util::StreamExt; @@ -73,44 +75,31 @@ pub struct DockerData { impl DockerData { /// Use docker stats to calculate current cpu usage #[allow(clippy::cast_precision_loss)] - fn calculate_usage(stats: &ContainerStatsResponse) -> f64 { + fn calculate_usage(stats: &Stats) -> f64 { let mut cpu_percentage = 0.0; + let cpu_delta = stats + .cpu_stats + .cpu_usage + .total_usage + .saturating_sub(stats.precpu_stats.cpu_usage.total_usage) + as f64; - let total_usage = stats.precpu_stats.as_ref().map_or(0, |i| { - i.cpu_usage - .as_ref() - .map_or(0, |i| i.total_usage.unwrap_or_default()) - }); - - let cpu_delta = stats.cpu_stats.as_ref().map_or(0, |i| { - i.cpu_usage.as_ref().map_or(0, |i| { - i.total_usage - .unwrap_or_default() - .saturating_sub(total_usage) - }) - }) as f64; - - if let (Some(Some(cpu_stats_usage)), Some(Some(precpu_stats_usage))) = ( - stats.cpu_stats.as_ref().map(|i| i.system_cpu_usage), - stats.precpu_stats.as_ref().map(|i| i.system_cpu_usage), + if let (Some(cpu_stats_usage), Some(precpu_stats_usage)) = ( + stats.cpu_stats.system_cpu_usage, + stats.precpu_stats.system_cpu_usage, ) { let system_delta = cpu_stats_usage.saturating_sub(precpu_stats_usage) as f64; - let online_cpus = f64::from(stats.cpu_stats.as_ref().map_or(0, |i| { - i.online_cpus.unwrap_or_else(|| { - u32::try_from( - stats - .cpu_stats - .clone() - .unwrap_or_default() - .cpu_usage - .unwrap_or_default() - .percpu_usage - .as_ref() - .map_or(0, std::vec::Vec::len), - ) - .unwrap_or_default() - }) - })); + let online_cpus = stats.cpu_stats.online_cpus.unwrap_or_else(|| { + u64::try_from( + stats + .cpu_stats + .cpu_usage + .percpu_usage + .as_ref() + .map_or(0, std::vec::Vec::len), + ) + .unwrap_or_default() + }) as f64; if system_delta > 0.0 && cpu_delta > 0.0 { cpu_percentage = (cpu_delta / system_delta) * online_cpus * 100.0; } @@ -142,23 +131,20 @@ impl DockerData { ) .take(1); - // some err here while let Some(Ok(stats)) = stream.next().await { // Memory stats are only collected if the container is alive - is this the behaviour we want? - let (mem_stat, cpu_stats) = if state.is_alive() { - let mem_cache = stats.memory_stats.as_ref().map_or(&0, |i| { - i.stats - .as_ref() - .map_or(&0, |i| i.get("inactive_file").unwrap_or(&0)) + let mem_cache = stats.memory_stats.stats.map_or(0, |i| match i { + MemoryStatsStats::V1(x) => x.inactive_file, + MemoryStatsStats::V2(x) => x.inactive_file, }); ( Some( stats .memory_stats - .as_ref() - .map_or(0, |i| i.usage.unwrap_or_default()) - .saturating_sub(*mem_cache), + .usage + .unwrap_or_default() + .saturating_sub(mem_cache), ), Some(Self::calculate_usage(&stats)), ) @@ -166,22 +152,26 @@ impl DockerData { (None, None) }; - let (rx, tx) = stats.networks.as_ref().map_or((0, 0), |i| { - ( - i.rx_bytes.unwrap_or_default(), - i.tx_bytes.unwrap_or_default(), - ) - }); + let op_key = stats + .networks + .as_ref() + .and_then(|networks| networks.keys().next().cloned()); + + let (rx, tx) = if let Some(key) = op_key { + stats + .networks + .unwrap_or_default() + .get(&key) + .map_or((0, 0), |f| (f.rx_bytes, f.tx_bytes)) + } else { + (0, 0) + }; app_data.lock().update_stats_by_id( id, cpu_stats, mem_stat, - stats - .memory_stats - .unwrap_or_default() - .limit - .unwrap_or_default(), + stats.memory_stats.limit.unwrap_or_default(), rx, tx, ); @@ -216,7 +206,7 @@ impl DockerData { async fn update_all_containers(&self) { let containers = self .docker - .list_containers(Some(ListContainersOptions { + .list_containers(Some(ListContainersOptions:: { all: true, ..Default::default() })) @@ -241,7 +231,6 @@ impl DockerData { None => None, }) .collect::>(); - self.app_data.lock().update_containers(output); } @@ -255,11 +244,11 @@ impl DockerData { spawns: Arc>>>, stderr: bool, ) { - let options = Some(LogsOptions { + let options = Some(LogsOptions:: { stdout: true, stderr, timestamps: true, - since: i32::try_from(since).unwrap_or_default(), + since: i64::try_from(since).unwrap_or_default(), ..Default::default() }); @@ -376,31 +365,14 @@ impl DockerData { .await } DockerCommand::Pause => docker.pause_container(id.get()).await, - DockerCommand::Restart => { - docker - .restart_container( - id.get(), - None::, - ) - .await - } + DockerCommand::Restart => docker.restart_container(id.get(), None).await, DockerCommand::Resume => docker.unpause_container(id.get()).await, DockerCommand::Start => { docker - .start_container( - id.get(), - None::, - ) - .await - } - DockerCommand::Stop => { - docker - .stop_container( - id.get(), - None::, - ) + .start_container(id.get(), None::>) .await } + DockerCommand::Stop => docker.stop_container(id.get(), None).await, } .is_err() { @@ -476,72 +448,119 @@ impl DockerData { #[allow(clippy::float_cmp)] mod tests { - use bollard::secret::{ContainerCpuStats, ContainerCpuUsage}; + use bollard::container::{ + BlkioStats, CPUStats, CPUUsage, MemoryStats, PidsStats, Stats, StorageStats, ThrottlingData, + }; use super::*; - fn gen_stats() -> ContainerStatsResponse { - ContainerStatsResponse { - read: None, - preread: None, - num_procs: Some(1), - pids_stats: None, + fn gen_stats() -> Stats { + Stats { + read: String::new(), + preread: String::new(), + num_procs: 1, + pids_stats: PidsStats { + current: None, + limit: None, + }, + network: None, networks: None, - memory_stats: None, - blkio_stats: None, - cpu_stats: Some(ContainerCpuStats { - cpu_usage: Some(ContainerCpuUsage { + memory_stats: MemoryStats { + stats: None, + max_usage: None, + usage: None, + failcnt: None, + limit: None, + commit: None, + commit_peak: None, + commitbytes: None, + commitpeakbytes: None, + privateworkingset: None, + }, + blkio_stats: BlkioStats { + io_service_bytes_recursive: None, + io_serviced_recursive: None, + io_queue_recursive: None, + io_service_time_recursive: None, + io_wait_time_recursive: None, + io_merged_recursive: None, + io_time_recursive: None, + sectors_recursive: None, + }, + cpu_stats: CPUStats { + cpu_usage: CPUUsage { percpu_usage: Some(vec![50]), - usage_in_usermode: Some(10), - total_usage: Some(100), - usage_in_kernelmode: Some(20), - }), + usage_in_usermode: 10, + total_usage: 100, + usage_in_kernelmode: 20, + }, system_cpu_usage: Some(400), online_cpus: Some(1), - throttling_data: None, - }), - precpu_stats: Some(ContainerCpuStats { - cpu_usage: Some(ContainerCpuUsage { + throttling_data: ThrottlingData { + periods: 0, + throttled_periods: 0, + throttled_time: 0, + }, + }, + precpu_stats: CPUStats { + cpu_usage: CPUUsage { percpu_usage: Some(vec![50]), - usage_in_usermode: Some(10), - total_usage: Some(100), - usage_in_kernelmode: Some(20), - }), + usage_in_usermode: 10, + total_usage: 100, + usage_in_kernelmode: 20, + }, system_cpu_usage: Some(400), online_cpus: Some(1), - throttling_data: None, - }), - storage_stats: None, - name: None, - id: None, + throttling_data: ThrottlingData { + periods: 0, + throttled_periods: 0, + throttled_time: 0, + }, + }, + storage_stats: StorageStats { + read_count_normalized: None, + read_size_bytes: None, + write_count_normalized: None, + write_size_bytes: None, + }, + name: String::new(), + id: String::new(), } } #[test] fn test_calculate_usage_50() { let mut stats = gen_stats(); - stats.precpu_stats = Some(ContainerCpuStats { - cpu_usage: Some(ContainerCpuUsage { + stats.precpu_stats = CPUStats { + cpu_usage: CPUUsage { percpu_usage: Some(vec![50]), - usage_in_usermode: Some(10), - total_usage: Some(100), - usage_in_kernelmode: Some(20), - }), + usage_in_usermode: 10, + total_usage: 100, + usage_in_kernelmode: 20, + }, system_cpu_usage: Some(400), online_cpus: Some(1), - throttling_data: None, - }); - stats.cpu_stats = Some(ContainerCpuStats { - cpu_usage: Some(ContainerCpuUsage { + throttling_data: ThrottlingData { + periods: 0, + throttled_periods: 0, + throttled_time: 0, + }, + }; + stats.cpu_stats = CPUStats { + cpu_usage: CPUUsage { percpu_usage: Some(vec![150]), - usage_in_usermode: Some(20), - total_usage: Some(150), - usage_in_kernelmode: Some(30), - }), + usage_in_usermode: 20, + total_usage: 150, + usage_in_kernelmode: 30, + }, system_cpu_usage: Some(500), online_cpus: Some(1), - throttling_data: None, - }); + throttling_data: ThrottlingData { + periods: 0, + throttled_periods: 0, + throttled_time: 0, + }, + }; let cpu_percentage = DockerData::calculate_usage(&stats); assert_eq!(50.0, cpu_percentage); } @@ -549,28 +568,37 @@ mod tests { #[test] fn test_calculate_usage_25() { let mut stats = gen_stats(); - stats.precpu_stats = Some(ContainerCpuStats { - cpu_usage: Some(ContainerCpuUsage { + stats.precpu_stats = CPUStats { + cpu_usage: CPUUsage { percpu_usage: Some(vec![50]), - usage_in_usermode: Some(10), - total_usage: Some(100), - usage_in_kernelmode: Some(20), - }), + usage_in_usermode: 10, + total_usage: 100, + usage_in_kernelmode: 20, + }, system_cpu_usage: Some(400), online_cpus: Some(1), - throttling_data: None, - }); - stats.cpu_stats = Some(ContainerCpuStats { - cpu_usage: Some(ContainerCpuUsage { + throttling_data: ThrottlingData { + periods: 0, + throttled_periods: 0, + throttled_time: 0, + }, + }; + stats.cpu_stats = CPUStats { + cpu_usage: CPUUsage { percpu_usage: Some(vec![75]), - usage_in_usermode: Some(20), - total_usage: Some(125), - usage_in_kernelmode: Some(30), - }), + usage_in_usermode: 20, + total_usage: 125, + usage_in_kernelmode: 30, + }, system_cpu_usage: Some(500), online_cpus: Some(1), - throttling_data: None, - }); + throttling_data: ThrottlingData { + periods: 0, + throttled_periods: 0, + throttled_time: 0, + }, + }; + let cpu_percentage = DockerData::calculate_usage(&stats); assert_eq!(25.0, cpu_percentage); } @@ -578,28 +606,38 @@ mod tests { #[test] fn test_calculate_usage_75() { let mut stats = gen_stats(); - stats.precpu_stats = Some(ContainerCpuStats { - cpu_usage: Some(ContainerCpuUsage { + stats.precpu_stats = CPUStats { + cpu_usage: CPUUsage { percpu_usage: Some(vec![50]), - usage_in_usermode: Some(10), - total_usage: Some(100), - usage_in_kernelmode: Some(20), - }), + usage_in_usermode: 10, + total_usage: 100, + usage_in_kernelmode: 20, + }, system_cpu_usage: Some(400), online_cpus: Some(1), - throttling_data: None, - }); - stats.cpu_stats = Some(ContainerCpuStats { - cpu_usage: Some(ContainerCpuUsage { + throttling_data: ThrottlingData { + periods: 0, + throttled_periods: 0, + throttled_time: 0, + }, + }; + + stats.cpu_stats = CPUStats { + cpu_usage: CPUUsage { percpu_usage: Some(vec![175]), - usage_in_usermode: Some(20), - total_usage: Some(175), - usage_in_kernelmode: Some(30), - }), + usage_in_usermode: 20, + total_usage: 175, + usage_in_kernelmode: 30, + }, system_cpu_usage: Some(500), online_cpus: Some(1), - throttling_data: None, - }); + throttling_data: ThrottlingData { + periods: 0, + throttled_periods: 0, + throttled_time: 0, + }, + }; + let cpu_percentage = DockerData::calculate_usage(&stats); assert_eq!(75.0, cpu_percentage); } @@ -607,28 +645,36 @@ mod tests { #[test] fn test_calculate_usage_100() { let mut stats = gen_stats(); - stats.precpu_stats = Some(ContainerCpuStats { - cpu_usage: Some(ContainerCpuUsage { + stats.precpu_stats = CPUStats { + cpu_usage: CPUUsage { percpu_usage: Some(vec![50]), - usage_in_usermode: Some(10), - total_usage: Some(100), - usage_in_kernelmode: Some(20), - }), + usage_in_usermode: 10, + total_usage: 100, + usage_in_kernelmode: 20, + }, system_cpu_usage: Some(400), online_cpus: Some(1), - throttling_data: None, - }); - stats.cpu_stats = Some(ContainerCpuStats { - cpu_usage: Some(ContainerCpuUsage { + throttling_data: ThrottlingData { + periods: 0, + throttled_periods: 0, + throttled_time: 0, + }, + }; + stats.cpu_stats = CPUStats { + cpu_usage: CPUUsage { percpu_usage: Some(vec![200]), - usage_in_usermode: Some(20), - total_usage: Some(200), - usage_in_kernelmode: Some(30), - }), + usage_in_usermode: 20, + total_usage: 200, + usage_in_kernelmode: 30, + }, system_cpu_usage: Some(500), online_cpus: Some(1), - throttling_data: None, - }); + throttling_data: ThrottlingData { + periods: 0, + throttled_periods: 0, + throttled_time: 0, + }, + }; let cpu_percentage = DockerData::calculate_usage(&stats); assert_eq!(100.0, cpu_percentage); } @@ -636,28 +682,38 @@ mod tests { #[test] fn test_calculate_usage_175() { let mut stats = gen_stats(); - stats.precpu_stats = Some(ContainerCpuStats { - cpu_usage: Some(ContainerCpuUsage { + stats.precpu_stats = CPUStats { + cpu_usage: CPUUsage { percpu_usage: Some(vec![50]), - usage_in_usermode: Some(10), - total_usage: Some(100), - usage_in_kernelmode: Some(20), - }), + usage_in_usermode: 10, + total_usage: 100, + usage_in_kernelmode: 20, + }, system_cpu_usage: Some(400), online_cpus: Some(1), - throttling_data: None, - }); - stats.cpu_stats = Some(ContainerCpuStats { - cpu_usage: Some(ContainerCpuUsage { + throttling_data: ThrottlingData { + periods: 0, + throttled_periods: 0, + throttled_time: 0, + }, + }; + + stats.cpu_stats = CPUStats { + cpu_usage: CPUUsage { percpu_usage: Some(vec![275]), - usage_in_usermode: Some(20), - total_usage: Some(275), - usage_in_kernelmode: Some(30), - }), + usage_in_usermode: 20, + total_usage: 275, + usage_in_kernelmode: 30, + }, system_cpu_usage: Some(500), online_cpus: Some(1), - throttling_data: None, - }); + throttling_data: ThrottlingData { + periods: 0, + throttled_periods: 0, + throttled_time: 0, + }, + }; + let cpu_percentage = DockerData::calculate_usage(&stats); assert_eq!(175.0, cpu_percentage); } diff --git a/src/input_handler/mod.rs b/src/input_handler/mod.rs index 4c17a9a..077d5c3 100644 --- a/src/input_handler/mod.rs +++ b/src/input_handler/mod.rs @@ -5,7 +5,7 @@ use std::{ time::SystemTime, }; -use bollard::query_parameters::LogsOptions; +use bollard::container::LogsOptions; // use bollard::container::LogsOptions; use cansi::v3::categorise_text; use crossterm::{ @@ -188,7 +188,7 @@ impl InputHandler { let path = log_path.join(format!("{name}_{now}.log")); - let options = Some(LogsOptions { + let options = Some(LogsOptions:: { stderr: true, stdout: true, timestamps: args.show_timestamp, diff --git a/src/main.rs b/src/main.rs index 64ed988..66c642b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -149,7 +149,7 @@ async fn main() { #[allow(clippy::unwrap_used)] mod tests { - use std::{str::FromStr, sync::Arc}; + use std::sync::Arc; use bollard::service::{ContainerSummary, Port}; @@ -228,7 +228,6 @@ mod tests { pub fn gen_container_summary(index: usize, state: &str) -> ContainerSummary { ContainerSummary { - image_manifest_descriptor: None, id: Some(format!("{index}")), names: Some(vec![format!("container_{}", index)]), image: Some(format!("image_{index}")), @@ -244,7 +243,7 @@ mod tests { size_rw: None, size_root_fs: None, labels: None, - state: Some(bollard::secret::ContainerSummaryStateEnum::from_str(state).unwrap()), + state: Some(state.to_owned()), status: Some(format!("Up {index} hour")), host_config: None, network_settings: None,