containers: Docker (and best-effort Podman) plugin

- minimal Docker Engine API client over Unix/TCP sockets with version
  negotiation and chunked response decoding
- drop-in stats schema: active/stopped containers, CPU/memory/IO/network
  rates, ports, uptime (pretty_date), image, engine field
- Docker + Podman engines, config (all, podman_sock, max_name_size,
  disable_stats, show/hide, per-container thresholds)
- export strips the computation sub-dicts (--stdout/--stdout-csv)
- TUI block above the process count
- CI-safe fake Docker socket test + live differential parity
This commit is contained in:
glances-rs
2026-10-10 00:24:39 +00:00
parent a522d21b0b
commit ea9debf71b
13 changed files with 2036 additions and 6 deletions
+58
View File
@@ -115,6 +115,64 @@ pub fn bar_custom(
bar
}
/// Get a datetime or a timestamp and return a pretty string like
/// 'an hour ago', 'yesterday', '3 months ago', 'just now'...
/// (port of `glances.globals.pretty_date`).
pub fn pretty_date(
started: chrono::DateTime<chrono::Local>,
now: chrono::DateTime<chrono::Local>,
) -> String {
let diff = now.signed_duration_since(started);
let day_diff = diff.num_days();
if day_diff < 0 {
return String::new();
}
// Python's timedelta.seconds excludes the days.
let second_diff = diff.num_seconds() - day_diff * 86_400;
if day_diff == 0 {
let thresholds: [(i64, i64, Option<&str>, Option<&str>); 6] = [
(10, 1, Some("just now"), None),
(60, 1, None, Some(" secs")),
(120, 1, Some("a min"), None),
(3600, 60, None, Some(" mins")),
(7200, 1, Some("an hour"), None),
(86_400, 3600, None, Some(" hours")),
];
for (max_value, divisor, singular, plural) in thresholds {
if second_diff < max_value {
if let Some(singular) = singular
&& plural.is_none()
{
return singular.to_string();
}
let value = second_diff / divisor;
return format!("{value}{}", plural.unwrap_or(""));
}
}
}
let day_thresholds: [(i64, i64, Option<&str>, Option<&str>); 5] = [
(2, 1, Some("yesterday"), None),
(7, 1, Some("a day"), Some(" days")),
(31, 7, Some("a week"), Some(" weeks")),
(365, 30, Some("a month"), Some(" months")),
(i64::MAX, 365, Some("an year"), Some(" years")),
];
for (max_days, divisor, singular, plural) in day_thresholds {
if day_diff < max_days {
let value = day_diff / divisor;
if let Some(singular) = singular
&& (value <= 1 || plural.is_none())
{
return singular.to_string();
}
return format!("{value}{}", plural.unwrap_or(""));
}
}
String::new()
}
/// Parse a value like `'12.5 MB'` into a float (port of `string_value_to_float`).
pub fn string_value_to_float(s: &str) -> Option<f64> {
let cleaned: String = s.chars().filter(|c| *c != ' ').collect();