3 Commits

Author SHA1 Message Date
66428863e6 moved stats into own folder
Some checks failed
Rust Cross-Platform Build / Detect Rust Project (push) Successful in 4s
Rust Cross-Platform Build / Run Tests (push) Failing after 1m9s
Rust Cross-Platform Build / Build (x86_64-unknown-linux-gnu) (push) Has been skipped
Rust Cross-Platform Build / Build (x86_64-pc-windows-gnu) (push) Has been skipped
Rust Cross-Platform Build / Set Tag Name (push) Has been skipped
Rust Cross-Platform Build / Build and Push Docker Image (push) Has been skipped
Rust Cross-Platform Build / Create Tag (push) Has been skipped
Rust Cross-Platform Build / Workflow Summary (push) Successful in 2s
2025-10-05 15:21:24 +02:00
b35cac0dbe changed Dtos and Docker structs 2025-10-04 22:05:28 +02:00
bb55b46c34 fixed image version detectionr
All checks were successful
Rust Cross-Platform Build / Detect Rust Project (push) Successful in 4s
Rust Cross-Platform Build / Run Tests (push) Successful in 1m4s
Rust Cross-Platform Build / Build (x86_64-unknown-linux-gnu) (push) Successful in 2m49s
Rust Cross-Platform Build / Build (x86_64-pc-windows-gnu) (push) Successful in 3m35s
Rust Cross-Platform Build / Set Tag Name (push) Successful in 4s
Rust Cross-Platform Build / Build and Push Docker Image (push) Successful in 2m8s
Rust Cross-Platform Build / Workflow Summary (push) Successful in 1s
Rust Cross-Platform Build / Create Tag (push) Successful in 5s
2025-10-04 20:38:20 +02:00
10 changed files with 469 additions and 320 deletions

View File

@@ -308,7 +308,7 @@ pub async fn listening_to_server(
///
/// # Returns
/// * `Result<(), Box<dyn Error + Send + Sync>>` - Ok if acknowledgment is sent successfully.
async fn send_acknowledgment(
pub async fn send_acknowledgment(
client: &reqwest::Client,
base_url: &str,
message_id: &str,
@@ -339,3 +339,27 @@ async fn send_acknowledgment(
Ok(())
}
pub async fn send_docker_metrics(
base_url: &str,
docker_metrics: &MetricDto,
) -> Result<(), Box<dyn Error + Send + Sync>> {
let client = Client::new();
let url = format!("{}/monitoring/docker-metric", base_url);
println!("Metrics: {:?}", docker_metrics);
match client.post(&url).json(&docker_metrics).send().await {
Ok(res) => println!(
"✅ Sent metrics for server {} | Status: {}",
docker_metrics.server_id,
res.status()
),
Err(err) => eprintln!("❌ Failed to send metrics: {}", err),
}
Ok(())
}
pub async fn broadcast_docker_containers() {
// Placeholder for future implementation
}

View File

@@ -4,7 +4,7 @@
//!
use crate::docker::stats;
use crate::docker::stats::{ContainerCpuInfo, ContainerNetworkInfo};
use crate::models::{DockerContainerDto, DockerContainerRegistrationDto};
use crate::models::{DockerRegistrationDto, DockerMetricDto, DockerContainer};
use bollard::query_parameters::{
CreateImageOptions, ListContainersOptions, RestartContainerOptions,
@@ -20,7 +20,7 @@ use std::error::Error;
///
/// # Returns
/// * `Vec<DockerContainer>` - Vector of Docker container info.
pub async fn get_available_containers(docker: &Docker) -> Vec<DockerContainerDto> {
pub async fn get_available_containers(docker: &Docker) -> Vec<DockerContainer> {
println!("=== DOCKER CONTAINER LIST ===");
let options = Some(ListContainersOptions {
@@ -51,29 +51,10 @@ pub async fn get_available_containers(docker: &Docker) -> Vec<DockerContainerDto
.map(|img| img.to_string())
.unwrap_or_else(|| "unknown".to_string());
/*let status = container
.status
.as_ref()
.map(|s| match s.to_lowercase().as_str() {
s if s.contains("up") || s.contains("running") => "running".to_string(),
s if s.contains("exited") || s.contains("stopped") => {
"stopped".to_string()
}
_ => s.to_string(),
})
.unwrap_or_else(|| "unknown".to_string());
println!(
" - ID: {}, Image: {}, Name: {}",
short_id,
container.image.unwrap(),
name
);*/
Some(DockerContainerDto {
Some(DockerContainer {
id: short_id.to_string(),
image,
name: name,
image: Some(image),
name: Some(name),
})
})
.collect()
@@ -191,7 +172,7 @@ pub async fn get_network_stats(
docker: &Docker,
container_id: &str,
) -> Result<ContainerNetworkInfo, Box<dyn Error + Send + Sync>> {
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)
@@ -214,7 +195,7 @@ pub async fn get_cpu_stats(
docker: &Docker,
container_id: &str,
) -> Result<ContainerCpuInfo, Box<dyn Error + Send + Sync>> {
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)

View File

@@ -11,7 +11,7 @@ pub mod container;
pub mod serverclientcomm;
pub mod stats;
use crate::models::{DockerContainerDto, DockerContainerMetricDto};
use crate::models::{DockerRegistrationDto, DockerMetricDto, DockerContainer};
use bollard::{query_parameters::InspectContainerOptions, Docker};
use std::error::Error;
@@ -49,24 +49,25 @@ impl DockerManager {
/// Finds the Docker container running the agent by image name
pub async fn get_client_container(
&self,
) -> Result<Option<DockerContainerDto>, Box<dyn Error + Send + Sync>> {
) -> Result<Option<DockerContainer>, Box<dyn Error + Send + Sync>> {
let containers = container::get_available_containers(&self.docker).await;
let client_image = "watcher-agent";
Ok(containers
.into_iter()
.find(|c| c.image.contains(client_image))
.map(|container| DockerContainerDto {
.find(|c| c.clone().image.unwrap().contains(client_image))
.map(|container| DockerContainer {
id: container.id,
image: container.image,
name: container.name,
}))
}
/// Gets the current client version (image name) if running in Docker
pub async fn get_client_version(&self) -> String {
match self.get_client_container().await {
Ok(Some(container)) => container.image,
Ok(Some(container)) => container.image.clone().unwrap().split(':').next().unwrap_or("unknown").to_string(),
Ok(None) => {
println!("Warning: No WatcherAgent container found");
"unknown".to_string()
@@ -89,12 +90,12 @@ impl DockerManager {
/// Gets all available containers as DTOs for registration
pub async fn get_containers_for_registration(
&self,
) -> Result<Vec<DockerContainerDto>, Box<dyn Error + Send + Sync>> {
) -> Result<Vec<DockerContainer>, Box<dyn Error + Send + Sync>> {
let containers = container::get_available_containers(&self.docker).await;
Ok(containers
.into_iter()
.map(|container| DockerContainerDto {
.map(|container| DockerContainer {
id: container.id,
image: container.image,
name: container.name,
@@ -102,60 +103,7 @@ impl DockerManager {
.collect())
}
/// Gets container metrics for all containers
pub async fn get_container_metrics(
&self,
) -> Result<Vec<DockerContainerMetricDto>, Box<dyn Error + Send + Sync>> {
let containers = container::get_available_containers(&self.docker).await;
let mut metrics = Vec::new();
for container in containers {
// Get network stats (you'll need to implement this in container.rs)
let network_stats = container::get_network_stats(&self.docker, &container.id).await?;
// Get CPU stats (you'll need to implement this in container.rs)
let cpu_stats = container::get_cpu_stats(&self.docker, &container.id).await?;
// Get current status by inspecting the container
let status = match self
.docker
.inspect_container(&container.id, None::<InspectContainerOptions>)
.await
{
Ok(container_info) => {
// Extract status from container state and convert to string
container_info
.state
.and_then(|state| state.status)
.map(|status_enum| {
match status_enum {
bollard::models::ContainerStateStatusEnum::CREATED => "created",
bollard::models::ContainerStateStatusEnum::RUNNING => "running",
bollard::models::ContainerStateStatusEnum::PAUSED => "paused",
bollard::models::ContainerStateStatusEnum::RESTARTING => {
"restarting"
}
bollard::models::ContainerStateStatusEnum::REMOVING => "removing",
bollard::models::ContainerStateStatusEnum::EXITED => "exited",
bollard::models::ContainerStateStatusEnum::DEAD => "dead",
bollard::secret::ContainerStateStatusEnum::EMPTY => todo!(),
}
.to_string()
})
.unwrap_or_else(|| "unknown".to_string())
}
Err(_) => "unknown".to_string(),
};
metrics.push(DockerContainerMetricDto {
id: container.id,
status: status,
network: network_stats,
cpu: cpu_stats,
});
}
Ok(metrics)
}
/// Gets the number of running containers
pub async fn get_container_count(&self) -> Result<usize, Box<dyn Error + Send + Sync>> {
@@ -170,22 +118,10 @@ impl DockerManager {
) -> Result<(), Box<dyn Error + Send + Sync>> {
container::restart_container(&self.docker, container_id).await
}
/// Gets total network statistics across all containers
pub async fn get_total_network_stats(
&self,
) -> Result<(u64, u64), Box<dyn Error + Send + Sync>> {
let metrics = self.get_container_metrics().await?;
let net_in_total: u64 = metrics.iter().map(|m| m.network.rx_bytes).sum();
let net_out_total: u64 = metrics.iter().map(|m| m.network.tx_bytes).sum();
Ok((net_in_total, net_out_total))
}
}
// Keep these as utility functions if needed, but they should use DockerManager internally
impl DockerContainerDto {
impl DockerContainer {
/// Returns the container ID
pub fn id(&self) -> &str {
&self.id
@@ -193,11 +129,11 @@ impl DockerContainerDto {
/// Returns the image name
pub fn image(&self) -> &str {
&self.image
&self.image.as_deref().unwrap_or("unknown")
}
/// Returns the container name
pub fn name(&self) -> &str {
&self.name
&self.name.as_deref().unwrap_or("unknown")
}
}

View File

@@ -1,206 +0,0 @@
use bollard::query_parameters::{ListContainersOptions, StatsOptions};
use bollard::Docker;
use futures_util::stream::TryStreamExt;
use serde::{Deserialize, Serialize};
use std::error::Error;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ContainerCpuInfo {
pub container_id: String,
pub cpu_usage_percent: f64,
pub system_cpu_usage: u64,
pub container_cpu_usage: u64,
pub online_cpus: u32,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ContainerNetworkInfo {
pub container_id: String,
pub rx_bytes: u64,
pub tx_bytes: u64,
pub rx_packets: u64,
pub tx_packets: u64,
pub rx_errors: u64,
pub tx_errors: u64,
}
/// Get container statistics for all containers using an existing Docker client
pub async fn get_container_stats(
docker: &Docker,
) -> Result<(Vec<ContainerCpuInfo>, Vec<ContainerNetworkInfo>), Box<dyn Error + Send + Sync>> {
let containers = docker
.list_containers(Some(ListContainersOptions {
all: true,
..Default::default()
}))
.await?;
let mut cpu_infos = Vec::new();
let mut net_infos = Vec::new();
for container in containers {
let id = container.id.unwrap_or_default();
// Skip if no ID
if id.is_empty() {
continue;
}
let mut stats_stream = docker.stats(
&id,
Some(StatsOptions {
stream: false,
one_shot: true,
}),
);
if let Some(stats) = stats_stream.try_next().await? {
// CPU Info
if let (Some(cpu_stats), Some(precpu_stats)) = (&stats.cpu_stats, &stats.precpu_stats) {
if let (Some(cpu_usage), Some(pre_cpu_usage)) =
(&cpu_stats.cpu_usage, &precpu_stats.cpu_usage)
{
let cpu_delta = cpu_usage
.total_usage
.unwrap_or(0)
.saturating_sub(pre_cpu_usage.total_usage.unwrap_or(0));
let system_delta = cpu_stats
.system_cpu_usage
.unwrap_or(0)
.saturating_sub(precpu_stats.system_cpu_usage.unwrap_or(0));
let online_cpus = cpu_stats.online_cpus.unwrap_or(1);
let cpu_percent = if system_delta > 0 && online_cpus > 0 {
(cpu_delta as f64 / system_delta as f64) * online_cpus as f64 * 100.0
} else {
0.0
};
cpu_infos.push(ContainerCpuInfo {
container_id: id.clone(),
cpu_usage_percent: cpu_percent,
system_cpu_usage: cpu_stats.system_cpu_usage.unwrap_or(0),
container_cpu_usage: cpu_usage.total_usage.unwrap_or(0),
online_cpus,
});
}
}
// Network Info
if let Some(networks) = stats.networks {
for (_name, net) in networks {
net_infos.push(ContainerNetworkInfo {
container_id: id.clone(),
rx_bytes: net.rx_bytes.unwrap(),
tx_bytes: net.tx_bytes.unwrap(),
rx_packets: net.rx_packets.unwrap(),
tx_packets: net.tx_packets.unwrap(),
rx_errors: net.rx_errors.unwrap(),
tx_errors: net.tx_errors.unwrap(),
});
}
}
}
}
Ok((cpu_infos, net_infos))
}
/// Get container statistics for a specific container
pub async fn get_single_container_stats(
docker: &Docker,
container_id: &str,
) -> Result<(Option<ContainerCpuInfo>, Option<ContainerNetworkInfo>), Box<dyn Error + Send + Sync>>
{
let mut stats_stream = docker.stats(
container_id,
Some(StatsOptions {
stream: false,
one_shot: true,
}),
);
if let Some(stats) = stats_stream.try_next().await? {
let mut cpu_info = None;
let mut net_info = None;
// CPU Info
if let (Some(cpu_stats), Some(precpu_stats)) = (&stats.cpu_stats, &stats.precpu_stats) {
if let (Some(cpu_usage), Some(pre_cpu_usage)) =
(&cpu_stats.cpu_usage, &precpu_stats.cpu_usage)
{
let cpu_delta = cpu_usage
.total_usage
.unwrap_or(0)
.saturating_sub(pre_cpu_usage.total_usage.unwrap_or(0));
let system_delta = cpu_stats
.system_cpu_usage
.unwrap_or(0)
.saturating_sub(precpu_stats.system_cpu_usage.unwrap_or(0));
let online_cpus = cpu_stats.online_cpus.unwrap_or(1);
let cpu_percent = if system_delta > 0 && online_cpus > 0 {
(cpu_delta as f64 / system_delta as f64) * online_cpus as f64 * 100.0
} else {
0.0
};
cpu_info = Some(ContainerCpuInfo {
container_id: container_id.to_string(),
cpu_usage_percent: cpu_percent,
system_cpu_usage: cpu_stats.system_cpu_usage.unwrap_or(0),
container_cpu_usage: cpu_usage.total_usage.unwrap_or(0),
online_cpus,
});
}
}
// Network Info
if let Some(networks) = stats.networks {
// Take the first network interface (usually eth0)
if let Some((_name, net)) = networks.into_iter().next() {
net_info = Some(ContainerNetworkInfo {
container_id: container_id.to_string(),
rx_bytes: net.rx_bytes.unwrap(),
tx_bytes: net.tx_bytes.unwrap(),
rx_packets: net.rx_packets.unwrap(),
tx_packets: net.tx_packets.unwrap(),
rx_errors: net.rx_errors.unwrap(),
tx_errors: net.tx_errors.unwrap(),
});
}
}
Ok((cpu_info, net_info))
} else {
Ok((None, None))
}
}
/// Get total network statistics across all containers
pub async fn get_total_network_stats(
docker: &Docker,
) -> Result<(u64, u64), Box<dyn Error + Send + Sync>> {
let (_, net_infos) = get_container_stats(docker).await?;
let total_rx: u64 = net_infos.iter().map(|net| net.rx_bytes).sum();
let total_tx: u64 = net_infos.iter().map(|net| net.tx_bytes).sum();
Ok((total_rx, total_tx))
}
/// Get average CPU usage across all containers
pub async fn get_average_cpu_usage(docker: &Docker) -> Result<f64, Box<dyn Error + Send + Sync>> {
let (cpu_infos, _) = get_container_stats(docker).await?;
if cpu_infos.is_empty() {
return Ok(0.0);
}
let total_cpu: f64 = cpu_infos.iter().map(|cpu| cpu.cpu_usage_percent).sum();
Ok(total_cpu / cpu_infos.len() as f64)
}

View File

@@ -0,0 +1,96 @@
use super::ContainerCpuInfo;
use bollard::query_parameters::{ListContainersOptions, StatsOptions};
use bollard::Docker;
use futures_util::stream::TryStreamExt;
use std::error::Error;
/// Get CPU statistics for all containers
pub async fn get_all_containers_cpu_stats(
docker: &Docker,
) -> Result<Vec<ContainerCpuInfo>, Box<dyn Error + Send + Sync>> {
let containers = docker
.list_containers(Some(ListContainersOptions {
all: true,
..Default::default()
}))
.await?;
let mut cpu_infos = Vec::new();
for container in containers {
let id = container.id.unwrap_or_default();
// Skip if no ID
if id.is_empty() {
continue;
}
if let Some(cpu_info) = get_single_container_cpu_stats(docker, &id).await? {
cpu_infos.push(cpu_info);
}
}
Ok(cpu_infos)
}
/// Get CPU statistics for a specific container
pub async fn get_single_container_cpu_stats(
docker: &Docker,
container_id: &str,
) -> Result<Option<ContainerCpuInfo>, Box<dyn Error + Send + Sync>> {
let mut stats_stream = docker.stats(
container_id,
Some(StatsOptions {
stream: false,
one_shot: true,
}),
);
if let Some(stats) = stats_stream.try_next().await? {
if let (Some(cpu_stats), Some(precpu_stats)) = (&stats.cpu_stats, &stats.precpu_stats) {
if let (Some(cpu_usage), Some(pre_cpu_usage)) =
(&cpu_stats.cpu_usage, &precpu_stats.cpu_usage)
{
let cpu_delta = cpu_usage
.total_usage
.unwrap_or(0)
.saturating_sub(pre_cpu_usage.total_usage.unwrap_or(0));
let system_delta = cpu_stats
.system_cpu_usage
.unwrap_or(0)
.saturating_sub(precpu_stats.system_cpu_usage.unwrap_or(0));
let online_cpus = cpu_stats.online_cpus.unwrap_or(1);
let cpu_percent = if system_delta > 0 && online_cpus > 0 {
(cpu_delta as f64 / system_delta as f64) * online_cpus as f64 * 100.0
} else {
0.0
};
return Ok(Some(ContainerCpuInfo {
container_id: container_id.to_string(),
cpu_usage_percent: cpu_percent,
system_cpu_usage: cpu_stats.system_cpu_usage.unwrap_or(0),
container_cpu_usage: cpu_usage.total_usage.unwrap_or(0),
online_cpus,
}));
}
}
}
Ok(None)
}
/// Get average CPU usage across all containers
pub async fn get_average_cpu_usage(docker: &Docker) -> Result<f64, Box<dyn Error + Send + Sync>> {
let cpu_infos = get_all_containers_cpu_stats(docker).await?;
if cpu_infos.is_empty() {
return Ok(0.0);
}
let total_cpu: f64 = cpu_infos.iter().map(|cpu| cpu.cpu_usage_percent).sum();
Ok(total_cpu / cpu_infos.len() as f64)
}

View File

@@ -0,0 +1,77 @@
pub mod cpu;
pub mod network;
pub mod ram;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ContainerCpuInfo {
pub container_id: String,
pub cpu_usage_percent: f64,
pub system_cpu_usage: u64,
pub container_cpu_usage: u64,
pub online_cpus: u32,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ContainerNetworkInfo {
pub container_id: String,
pub rx_bytes: u64,
pub tx_bytes: u64,
pub rx_packets: u64,
pub tx_packets: u64,
pub rx_errors: u64,
pub tx_errors: u64,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ContainerMemoryInfo {
pub container_id: String,
pub memory_usage: u64,
pub memory_limit: u64,
pub memory_usage_percent: f64,
}
use bollard::Docker;
use std::error::Error;
/// Get container statistics for all containers using an existing Docker client
pub async fn get_container_stats(
docker: &Docker,
) -> Result<(Vec<ContainerCpuInfo>, Vec<ContainerNetworkInfo>, Vec<ContainerMemoryInfo>), Box<dyn Error + Send + Sync>> {
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?;
Ok((cpu_infos, net_infos, mem_infos))
}
/// Get container statistics for a specific container
pub async fn get_single_container_stats(
docker: &Docker,
container_id: &str,
) -> Result<(Option<ContainerCpuInfo>, Option<ContainerNetworkInfo>, Option<ContainerMemoryInfo>), Box<dyn Error + Send + Sync>>
{
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?;
Ok((cpu_info, net_info, mem_info))
}
/// Get total network statistics across all containers
pub async fn get_total_network_stats(
docker: &Docker,
) -> Result<(u64, u64), Box<dyn Error + Send + Sync>> {
network::get_total_network_stats(docker).await
}
/// Get average CPU usage across all containers
pub async fn get_average_cpu_usage(docker: &Docker) -> Result<f64, Box<dyn Error + Send + Sync>> {
cpu::get_average_cpu_usage(docker).await
}
/// Get total memory usage across all containers
pub async fn get_total_memory_usage(docker: &Docker) -> Result<u64, Box<dyn Error + Send + Sync>> {
ram::get_total_memory_usage(docker).await
}

View File

@@ -0,0 +1,79 @@
use super::ContainerNetworkInfo;
use bollard::query_parameters::{ListContainersOptions, StatsOptions};
use bollard::Docker;
use futures_util::stream::TryStreamExt;
use std::error::Error;
/// Get network statistics for all containers
pub async fn get_all_containers_network_stats(
docker: &Docker,
) -> Result<Vec<ContainerNetworkInfo>, Box<dyn Error + Send + Sync>> {
let containers = docker
.list_containers(Some(ListContainersOptions {
all: true,
..Default::default()
}))
.await?;
let mut net_infos = Vec::new();
for container in containers {
let id = container.id.unwrap_or_default();
// Skip if no ID
if id.is_empty() {
continue;
}
if let Some(net_info) = get_single_container_network_stats(docker, &id).await? {
net_infos.push(net_info);
}
}
Ok(net_infos)
}
/// Get network statistics for a specific container
pub async fn get_single_container_network_stats(
docker: &Docker,
container_id: &str,
) -> Result<Option<ContainerNetworkInfo>, Box<dyn Error + Send + Sync>> {
let mut stats_stream = docker.stats(
container_id,
Some(StatsOptions {
stream: false,
one_shot: true,
}),
);
if let Some(stats) = stats_stream.try_next().await? {
if let Some(networks) = stats.networks {
// Take the first network interface (usually eth0)
if let Some((_name, net)) = networks.into_iter().next() {
return Ok(Some(ContainerNetworkInfo {
container_id: container_id.to_string(),
rx_bytes: net.rx_bytes.unwrap(),
tx_bytes: net.tx_bytes.unwrap(),
rx_packets: net.rx_packets.unwrap(),
tx_packets: net.tx_packets.unwrap(),
rx_errors: net.rx_errors.unwrap(),
tx_errors: net.tx_errors.unwrap(),
}));
}
}
}
Ok(None)
}
/// Get total network statistics across all containers
pub async fn get_total_network_stats(
docker: &Docker,
) -> Result<(u64, u64), Box<dyn Error + Send + Sync>> {
let net_infos = get_all_containers_network_stats(docker).await?;
let total_rx: u64 = net_infos.iter().map(|net| net.rx_bytes).sum();
let total_tx: u64 = net_infos.iter().map(|net| net.tx_bytes).sum();
Ok((total_rx, total_tx))
}

View File

@@ -0,0 +1,77 @@
use super::ContainerMemoryInfo;
use bollard::query_parameters::{ListContainersOptions, StatsOptions};
use bollard::Docker;
use futures_util::stream::TryStreamExt;
use std::error::Error;
/// Get memory statistics for all containers
pub async fn get_all_containers_memory_stats(
docker: &Docker,
) -> Result<Vec<ContainerMemoryInfo>, Box<dyn Error + Send + Sync>> {
let containers = docker
.list_containers(Some(ListContainersOptions {
all: true,
..Default::default()
}))
.await?;
let mut mem_infos = Vec::new();
for container in containers {
let id = container.id.unwrap_or_default();
// Skip if no ID
if id.is_empty() {
continue;
}
if let Some(mem_info) = get_single_container_memory_stats(docker, &id).await? {
mem_infos.push(mem_info);
}
}
Ok(mem_infos)
}
/// Get memory statistics for a specific container
pub async fn get_single_container_memory_stats(
docker: &Docker,
container_id: &str,
) -> Result<Option<ContainerMemoryInfo>, Box<dyn Error + Send + Sync>> {
let mut stats_stream = docker.stats(
container_id,
Some(StatsOptions {
stream: false,
one_shot: true,
}),
);
if let Some(stats) = stats_stream.try_next().await? {
if let Some(memory_stats) = &stats.memory_stats {
let memory_usage = memory_stats.usage.unwrap_or(0);
let memory_limit = memory_stats.limit.unwrap_or(1); // Avoid division by zero
let memory_usage_percent = if memory_limit > 0 {
(memory_usage as f64 / memory_limit as f64) * 100.0
} else {
0.0
};
return Ok(Some(ContainerMemoryInfo {
container_id: container_id.to_string(),
memory_usage,
memory_limit,
memory_usage_percent,
}));
}
}
Ok(None)
}
/// Get total memory usage across all containers
pub async fn get_total_memory_usage(docker: &Docker) -> Result<u64, Box<dyn Error + Send + Sync>> {
let mem_infos = get_all_containers_memory_stats(docker).await?;
let total_memory: u64 = mem_infos.iter().map(|mem| mem.memory_usage).sum();
Ok(total_memory)
}

View File

@@ -112,4 +112,59 @@ impl Collector {
net_tx: hardware.network.tx_rate.unwrap_or_default(),
})
}
/// Gets container metrics for all containers
pub async fn docker_collect(
&self,
) -> Result<Vec<DockerContainer>, Box<dyn Error + Send + Sync>> {
let containers = container::get_available_containers(&self.docker).await;
let mut metrics = Vec::new();
for container in containers {
// Get network stats (you'll need to implement this in container.rs)
let network_stats = container::get_network_stats(&self.docker, &container.id).await?;
// Get CPU stats (you'll need to implement this in container.rs)
let cpu_stats = container::get_cpu_stats(&self.docker, &container.id).await?;
// Get current status by inspecting the container
let status = match self
.docker
.inspect_container(&container.id, None::<InspectContainerOptions>)
.await
{
Ok(container_info) => {
// Extract status from container state and convert to string
container_info
.state
.and_then(|state| state.status)
.map(|status_enum| {
match status_enum {
bollard::models::ContainerStateStatusEnum::CREATED => "created",
bollard::models::ContainerStateStatusEnum::RUNNING => "running",
bollard::models::ContainerStateStatusEnum::PAUSED => "paused",
bollard::models::ContainerStateStatusEnum::RESTARTING => {
"restarting"
}
bollard::models::ContainerStateStatusEnum::REMOVING => "removing",
bollard::models::ContainerStateStatusEnum::EXITED => "exited",
bollard::models::ContainerStateStatusEnum::DEAD => "dead",
bollard::secret::ContainerStateStatusEnum::EMPTY => todo!(),
}
.to_string()
})
.unwrap_or_else(|| "unknown".to_string())
}
Err(_) => "unknown".to_string(),
};
metrics.push(DockerContainerMetricDto {
server_id: container.id,
status: status,
network: network_stats,
cpu: cpu_stats,
});
}
Ok(metrics)
}
}

View File

@@ -186,22 +186,52 @@ pub struct Acknowledgment {
/// - `_net_out`: Network transmit rate in **bytes per second (B/s)**
/// - `_cpu_load`: CPU usage as a percentage (**0.0100.0**)
#[derive(Debug, Serialize, Clone)]
pub struct DockerContainerRegistrationDto {
pub struct DockerRegistrationDto {
/// Unique server identifier (integer)
pub server_id: u32,
pub containers: Vec<DockerContainerDto>,
/// json stringified array of DockerContainer
///
/// ## Json Example
/// json format: [{"id":"234dsf234","image":"nginx:latest","name":"webserver"},...]
///
/// ## Fields
/// id: unique container ID (first 12 hex digits)
/// image: docker image name
/// name: container name
pub containers: String,
}
#[derive(Debug, Serialize, Clone)]
pub struct DockerContainerDto {
pub id: String,
pub image: String,
pub name: String,
pub struct DockerMetricDto {
pub server_id: String,
/// json stringified array of DockerContainer
///
/// ## Json Example
/// json format: [{"id":"234dsf234","status":"running","image":"nginx:latest","name":"webserver","network":{"net_in":1024,"net_out":2048},"cpu":{"cpu_load":12.5},"ram":{"ram_load":10.0}},...]
///
/// ## Fields
/// id: unique container ID (first 12 hex digits)
/// status: "running";"stopped";others
/// image: docker image name
/// name: container name
/// network: network stats
/// cpu: cpu stats
/// ram: ram stats
pub containers: String,
}
#[derive(Debug, Serialize, Clone)]
pub struct DockerContainerMetricDto {
pub id: String,
pub status: String, // "running";"stopped";others
pub network: stats::ContainerNetworkInfo,
pub cpu: stats::ContainerCpuInfo,
pub struct DockerContainerInfo {
pub container: Option<DockerContainer>,
pub status: Option<String>, // "running";"stopped";others
pub network: Option<stats::ContainerNetworkInfo>,
pub cpu: Option<stats::ContainerCpuInfo>,
pub ram: Option<stats::ContainerMemoryInfo>,
}
#[derive(Debug, Serialize, Clone)]
pub struct DockerContainer {
pub id: String,
pub image: Option<String>,
pub name: Option<String>,
}