diff --git a/WatcherAgent/src/docker/container.rs b/WatcherAgent/src/docker/container.rs index eac7c3a..87daa6a 100644 --- a/WatcherAgent/src/docker/container.rs +++ b/WatcherAgent/src/docker/container.rs @@ -172,7 +172,7 @@ pub async fn get_network_stats( docker: &Docker, container_id: &str, ) -> Result> { - let (_, net_info, _) = stats::get_single_container_stats(docker, container_id).await?; + let (_, net_info, _, _) = stats::get_single_container_stats(docker, container_id).await?; if let Some(net_info) = net_info { Ok(net_info) @@ -196,7 +196,7 @@ pub async fn get_cpu_stats( docker: &Docker, container_id: &str, ) -> Result> { - let (cpu_info, _, _) = stats::get_single_container_stats(docker, container_id).await?; + let (cpu_info, _, _, _) = stats::get_single_container_stats(docker, container_id).await?; if let Some(cpu_info) = cpu_info { Ok(cpu_info) diff --git a/WatcherAgent/src/docker/mod.rs b/WatcherAgent/src/docker/mod.rs index 9f809df..dbdd1d5 100644 --- a/WatcherAgent/src/docker/mod.rs +++ b/WatcherAgent/src/docker/mod.rs @@ -14,6 +14,7 @@ pub mod stats; use crate::models::{ DockerCollectMetricDto, DockerContainer, DockerContainerCpuDto, DockerContainerInfo, DockerContainerNetworkDto, DockerContainerRamDto, DockerMetricDto, DockerRegistrationDto, + DockerContainerStatusDto }; use bollard::Docker; use std::error::Error; @@ -129,28 +130,25 @@ impl DockerManager { /// Collects Docker metrics for all containers pub async fn collect_metrics(&self) -> Result> { let containers = self.get_containers().await?; - if let Some(first_container) = containers.first() { - println!("Debug: Testing stats for container {}", first_container.id); - let _ = self.debug_container_stats(&first_container.id).await; - } - // Get stats with proper error handling + // Get stats with status information let stats_result = stats::get_container_stats(&self.docker).await; - let (cpu_stats, net_stats, mem_stats) = match stats_result { + let (cpu_stats, net_stats, mem_stats, status_stats) = match stats_result { Ok(stats) => stats, Err(e) => { eprintln!("Warning: Failed to get container stats: {}", e); // Return empty stats instead of failing completely - (Vec::new(), Vec::new(), Vec::new()) + (Vec::new(), Vec::new(), Vec::new(), Vec::new()) } }; println!( - "Debug: Found {} containers, {} CPU stats, {} network stats, {} memory stats", + "Debug: Found {} containers, {} CPU stats, {} network stats, {} memory stats, {} status stats", containers.len(), cpu_stats.len(), net_stats.len(), - mem_stats.len() + mem_stats.len(), + status_stats.len(), ); let container_infos_total: Vec<_> = containers @@ -193,26 +191,49 @@ impl DockerManager { }) .cloned(); + let status = status_stats + .iter() + .find(|s| { + s.container_id + .as_ref() + .map(|id| id.starts_with(container_short_id)) + .unwrap_or(false) + }) + .cloned(); // Clone the entire ContainerStatusInfo + // Debug output for this container if cpu.is_none() || network.is_none() || ram.is_none() { println!( - "Debug: Container {} - CPU: {:?}, Network: {:?}, RAM: {:?}", + "Debug: Container {} - CPU: {:?}, Network: {:?}, RAM: {:?}, Status {:?}", container_short_id, cpu.is_some(), network.is_some(), - ram.is_some() + ram.is_some(), + status.is_some() ); } - DockerContainerInfo { - container: Some(container), - status: None, - cpu, - network, - ram, - } - }) - .collect(); + // Debug output for this container + if cpu.is_none() || network.is_none() || ram.is_none() || status.is_none() { + println!( + "Debug: Container {} - CPU: {:?}, Network: {:?}, RAM: {:?}, Status: {:?}", + container_short_id, + cpu.is_some(), + network.is_some(), + ram.is_some(), + status.is_some() + ); + } + + DockerContainerInfo { + container: Some(container), + status, + cpu, + network, + ram, + } + }) + .collect(); let container_infos: Vec = container_infos_total .into_iter() @@ -256,8 +277,17 @@ impl DockerManager { } }; + let status_dto = if let Some(status_info) = info.status { + DockerContainerStatusDto { + status: status_info.status, // Extract the status string + } + } else { + DockerContainerStatusDto { status: None } + }; + Some(DockerCollectMetricDto { id: container.id, + status: status_dto, cpu: cpu_dto, ram: ram_dto, network: network_dto, @@ -267,7 +297,7 @@ impl DockerManager { let dto = DockerMetricDto { server_id: 0, // This should be set by the caller - containers: serde_json::to_string(&container_infos)?, + containers: serde_json::to_value(&container_infos)?, }; Ok(dto) @@ -287,42 +317,6 @@ impl DockerManager { Ok(dto) } - - /// Debug function to check stats collection for a specific container - pub async fn debug_container_stats( - &self, - container_id: &str, - ) -> Result<(), Box> { - println!("=== DEBUG STATS FOR CONTAINER {} ===", container_id); - - let (cpu_info, net_info, mem_info) = - stats::get_single_container_stats(&self.docker, container_id).await?; - - println!("CPU Info: {:?}", cpu_info); - println!("Network Info: {:?}", net_info); - println!("Memory Info: {:?}", mem_info); - - // Also try the individual stats functions - println!("--- Individual CPU Stats ---"); - match stats::cpu::get_single_container_cpu_stats(&self.docker, container_id).await { - Ok(cpu) => println!("CPU: {:?}", cpu), - Err(e) => println!("CPU Error: {}", e), - } - - println!("--- Individual Network Stats ---"); - match stats::network::get_single_container_network_stats(&self.docker, container_id).await { - Ok(net) => println!("Network: {:?}", net), - Err(e) => println!("Network Error: {}", e), - } - - println!("--- Individual Memory Stats ---"); - match stats::ram::get_single_container_memory_stats(&self.docker, container_id).await { - Ok(mem) => println!("Memory: {:?}", mem), - Err(e) => println!("Memory Error: {}", e), - } - - Ok(()) - } } // Keep these as utility functions if needed, but they should use DockerManager internally diff --git a/WatcherAgent/src/docker/stats/mod.rs b/WatcherAgent/src/docker/stats/mod.rs index 098ca1d..336f4b1 100644 --- a/WatcherAgent/src/docker/stats/mod.rs +++ b/WatcherAgent/src/docker/stats/mod.rs @@ -1,9 +1,19 @@ pub mod cpu; pub mod network; pub mod ram; +pub mod status; use serde::{Deserialize, Serialize}; +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct ContainerStatusInfo { + pub container_id: Option, + pub status: Option, // "running", "stopped", "paused", "exited", etc. + pub state: Option, // More detailed state information + pub started_at: Option, + pub finished_at: Option, +} + #[derive(Debug, Serialize, Deserialize, Clone)] pub struct ContainerCpuInfo { pub container_id: Option, @@ -43,33 +53,34 @@ pub async fn get_container_stats( Vec, Vec, Vec, + Vec, ), Box, > { let cpu_infos = cpu::get_all_containers_cpu_stats(docker).await?; let net_infos = network::get_all_containers_network_stats(docker).await?; let mem_infos = ram::get_all_containers_memory_stats(docker).await?; + let status_infos = status::get_all_containers_status(docker).await?; - Ok((cpu_infos, net_infos, mem_infos)) + Ok((cpu_infos, net_infos, mem_infos, status_infos)) } /// Get container statistics for a specific container pub async fn get_single_container_stats( docker: &Docker, container_id: &str, -) -> Result< - ( - Option, - Option, - Option, - ), - Box, -> { +) -> Result<( + Option, + Option, + Option, + Option, +), Box> { let cpu_info = cpu::get_single_container_cpu_stats(docker, container_id).await?; let net_info = network::get_single_container_network_stats(docker, container_id).await?; let mem_info = ram::get_single_container_memory_stats(docker, container_id).await?; + let status_info = status::get_single_container_status(docker, container_id).await?; - Ok((cpu_info, net_info, mem_info)) + Ok((cpu_info, net_info, mem_info, status_info)) } /// Get total network statistics across all containers diff --git a/WatcherAgent/src/docker/stats/status.rs b/WatcherAgent/src/docker/stats/status.rs new file mode 100644 index 0000000..846ddd0 --- /dev/null +++ b/WatcherAgent/src/docker/stats/status.rs @@ -0,0 +1,126 @@ +use super::ContainerStatusInfo; +use std::error::Error; +use bollard::Docker; +use bollard::query_parameters::{ListContainersOptions, InspectContainerOptions}; +use bollard::models::{ContainerSummaryStateEnum, ContainerStateStatusEnum}; + +/// Get status information for all containers +pub async fn get_all_containers_status( + docker: &Docker, +) -> Result, Box> { + + let containers = docker + .list_containers(Some(ListContainersOptions { + all: true, // Include stopped containers + ..Default::default() + })) + .await?; + + let mut status_infos = Vec::new(); + + for container in containers { + let id = container.id.unwrap_or_default(); + + if id.is_empty() { + continue; + } + + // Convert ContainerSummaryStateEnum to String + let status = container.state.map(|state| match state { + ContainerSummaryStateEnum::CREATED => "created".to_string(), + ContainerSummaryStateEnum::RUNNING => "running".to_string(), + ContainerSummaryStateEnum::PAUSED => "paused".to_string(), + ContainerSummaryStateEnum::RESTARTING => "restarting".to_string(), + ContainerSummaryStateEnum::REMOVING => "removing".to_string(), + ContainerSummaryStateEnum::EXITED => "exited".to_string(), + ContainerSummaryStateEnum::DEAD => "dead".to_string(), + _ => "unknown".to_string(), + }); + + // Convert timestamp from i64 to String + let started_at = container.created.map(|timestamp| timestamp.to_string()); + + status_infos.push(ContainerStatusInfo { + container_id: Some(id.clone()), + status, + state: container.status, + started_at, + finished_at: None, // Docker API doesn't provide finished_at in list + }); + } + + Ok(status_infos) +} + +/// Get status information for a specific container +pub async fn get_single_container_status( + docker: &Docker, + container_id: &str, +) -> Result, Box> { + // First try to get from list (faster) + let containers = docker + .list_containers(Some(ListContainersOptions { + all: true, + ..Default::default() + })) + .await?; + + if let Some(container) = containers.into_iter().find(|c| { + c.id.as_ref().map(|id| id == container_id).unwrap_or(false) + }) { + // Convert ContainerSummaryStateEnum to String + let status = container.state.map(|state| match state { + ContainerSummaryStateEnum::CREATED => "created".to_string(), + ContainerSummaryStateEnum::RUNNING => "running".to_string(), + ContainerSummaryStateEnum::PAUSED => "paused".to_string(), + ContainerSummaryStateEnum::RESTARTING => "restarting".to_string(), + ContainerSummaryStateEnum::REMOVING => "removing".to_string(), + ContainerSummaryStateEnum::EXITED => "exited".to_string(), + ContainerSummaryStateEnum::DEAD => "dead".to_string(), + _ => "unknown".to_string(), + }); + + // Convert timestamp from i64 to String + let started_at = container.created.map(|timestamp| timestamp.to_string()); + + return Ok(Some(ContainerStatusInfo { + container_id: Some(container_id.to_string()), + status, + state: container.status, + started_at, + finished_at: None, + })); + } + + // Fallback to inspect for more detailed info + match docker.inspect_container(container_id, None::).await { + Ok(container_details) => { + let state = container_details.state.unwrap_or_default(); + + // Convert ContainerStateStatusEnum to String + let status = state.status.map(|status_enum| match status_enum { + ContainerStateStatusEnum::CREATED => "created".to_string(), + ContainerStateStatusEnum::RUNNING => "running".to_string(), + ContainerStateStatusEnum::PAUSED => "paused".to_string(), + ContainerStateStatusEnum::RESTARTING => "restarting".to_string(), + ContainerStateStatusEnum::REMOVING => "removing".to_string(), + ContainerStateStatusEnum::EXITED => "exited".to_string(), + ContainerStateStatusEnum::DEAD => "dead".to_string(), + _ => "unknown".to_string(), + }); + + // These are already Option from the Docker API + let started_at = state.clone().started_at; + let finished_at = state.clone().finished_at; + + Ok(Some(ContainerStatusInfo { + container_id: Some(container_id.to_string()), + status, + state: Some(format!("{:?}", state)), // Convert state to string + started_at, + finished_at, + })) + } + Err(_) => Ok(None), // Container not found + } +} \ No newline at end of file diff --git a/WatcherAgent/src/models.rs b/WatcherAgent/src/models.rs index 12019ec..92e762f 100644 --- a/WatcherAgent/src/models.rs +++ b/WatcherAgent/src/models.rs @@ -219,18 +219,24 @@ pub struct DockerMetricDto { /// network: network stats /// cpu: cpu stats /// ram: ram stats - pub containers: String, // Vec, + pub containers: Value, // Vec, } #[derive(Debug, Serialize, Clone)] pub struct DockerCollectMetricDto { pub id: String, + pub status: DockerContainerStatusDto, pub cpu: DockerContainerCpuDto, pub ram: DockerContainerRamDto, pub network: DockerContainerNetworkDto, } +#[derive(Debug, Serialize, Clone)] +pub struct DockerContainerStatusDto { + pub status: Option, +} + #[derive(Debug, Serialize, Clone)] pub struct DockerContainerCpuDto { pub cpu_load: Option, @@ -251,7 +257,7 @@ pub struct DockerContainerNetworkDto { #[derive(Debug, Serialize, Clone)] pub struct DockerContainerInfo { pub container: Option, - pub status: Option, // "running";"stopped";others + pub status: Option, // "running";"stopped";others pub network: Option, pub cpu: Option, pub ram: Option,