Files
glances-rs/crates/glances-plugins/src/engine.rs
T
glances-rs ea9debf71b 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
2026-10-10 00:24:39 +00:00

407 lines
14 KiB
Rust

//! Plugin registry and update engine.
use crate::plugins::*;
use glances_core::plugin::{Plugin, PluginModel, UpdateContext};
use glances_core::processes::processes;
use glances_core::{Args, Config, Runtime};
use indexmap::IndexMap;
use serde_json::Value;
use tracing::debug;
/// The update engine: holds the configuration, the runtime state and every
/// plugin instance.
pub struct Engine {
pub args: Args,
pub config: Config,
pub runtime: Runtime,
pub plugins: IndexMap<String, Box<dyn Plugin>>,
}
impl Engine {
pub fn new(args: Args, config: Config) -> Self {
let refresh_time = args.time.unwrap_or(2.0);
let fs_free_space_config = config.get_bool_value("fs", "free_space", false);
let runtime = Runtime::new(&args, fs_free_space_config);
let mut plugins: IndexMap<String, Box<dyn Plugin>> = IndexMap::new();
// Registration order mirrors the alphabetical plugin loading of the
// Python implementation (in particular processcount runs before
// processlist and programlist).
plugins.insert(
"amps".to_string(),
Box::new(AmpsPlugin::new(&config, &args)),
);
plugins.insert(
"containers".to_string(),
Box::new(ContainersPlugin::new(&config, &args)),
);
plugins.insert(
"core".to_string(),
Box::new(CorePlugin::new(&config, &args)),
);
plugins.insert("cpu".to_string(), Box::new(CpuPlugin::new(&config, &args)));
plugins.insert(
"diskio".to_string(),
Box::new(DiskioPlugin::new(&config, &args)),
);
plugins.insert(
"folders".to_string(),
Box::new(FoldersPlugin::new(&config, &args)),
);
plugins.insert("fs".to_string(), Box::new(FsPlugin::new(&config, &args)));
plugins.insert("ip".to_string(), Box::new(IpPlugin::new(&config, &args)));
plugins.insert("irq".to_string(), Box::new(IrqPlugin::new(&config, &args)));
plugins.insert(
"load".to_string(),
Box::new(LoadPlugin::new(&config, &args)),
);
plugins.insert("mem".to_string(), Box::new(MemPlugin::new(&config, &args)));
plugins.insert(
"memswap".to_string(),
Box::new(MemSwapPlugin::new(&config, &args)),
);
plugins.insert(
"network".to_string(),
Box::new(NetworkPlugin::new(&config, &args)),
);
plugins.insert("now".to_string(), Box::new(NowPlugin::new(&config, &args)));
plugins.insert(
"percpu".to_string(),
Box::new(PerCpuPlugin::new(&config, &args)),
);
plugins.insert(
"ports".to_string(),
Box::new(PortsPlugin::new(&config, &args)),
);
plugins.insert(
"processcount".to_string(),
Box::new(ProcessCountPlugin::new(&config, &args)),
);
plugins.insert(
"processlist".to_string(),
Box::new(ProcessListPlugin::new(&config, &args)),
);
plugins.insert(
"programlist".to_string(),
Box::new(ProgramListPlugin::new(&config, &args)),
);
plugins.insert(
"psutilversion".to_string(),
Box::new(PsutilVersionPlugin::new(&config, &args)),
);
plugins.insert(
"quicklook".to_string(),
Box::new(QuicklookPlugin::new(&config, &args)),
);
plugins.insert(
"raid".to_string(),
Box::new(RaidPlugin::new(&config, &args)),
);
plugins.insert(
"sensors".to_string(),
Box::new(SensorsPlugin::new(&config, &args)),
);
plugins.insert(
"system".to_string(),
Box::new(SystemPlugin::new(&config, &args)),
);
plugins.insert(
"uptime".to_string(),
Box::new(UptimePlugin::new(&config, &args)),
);
plugins.insert(
"version".to_string(),
Box::new(VersionPlugin::new(&config, &args)),
);
plugins.insert(
"wifi".to_string(),
Box::new(WifiPlugin::new(&config, &args)),
);
// The refresh time is set on the model, which was built with the
// command line value already.
let _ = refresh_time;
let mut engine = Self {
args,
config,
runtime,
plugins,
};
engine.apply_plugin_options();
engine.apply_process_options();
engine.sync_enabled();
engine
}
/// Refresh the runtime set of enabled plugins.
pub fn sync_enabled(&mut self) {
self.runtime.enabled_plugins = self
.plugins
.iter()
.filter(|(_, plugin)| plugin.is_enabled())
.map(|(name, _)| name.clone())
.collect();
}
fn apply_plugin_options(&mut self) {
// Disable plugins from the configuration file.
for section in self.config.sections() {
if self.config.get_bool_value(section, "disable", false)
&& let Some(plugin) = self.plugins.get_mut(section)
{
debug!("{} disabled by the configuration file", section);
plugin.set_enabled(false);
}
}
// Availability (hardware/kernel dependent plugins).
let names: Vec<String> = self.plugins.keys().cloned().collect();
let mut available = std::collections::HashMap::new();
for name in &names {
let plugin = self.plugins.get_mut(name).unwrap();
available.insert(name.clone(), plugin.available());
}
for (name, is_available) in &available {
if !is_available {
debug!("{name} plugin is not available on this system");
self.plugins.get_mut(name).unwrap().set_enabled(false);
}
}
// --disable-plugin / --enable-plugin.
let mut explicit_enable: Vec<String> = Vec::new();
if let Some(enable) = &self.args.enable_plugin {
explicit_enable = enable
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
}
if let Some(disable) = &self.args.disable_plugin {
let tokens: Vec<String> = disable
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
if tokens.iter().any(|t| t == "all") {
for name in self.plugins.keys().cloned().collect::<Vec<_>>() {
self.plugins.get_mut(&name).unwrap().set_enabled(false);
}
} else {
for token in tokens {
if let Some(plugin) = self.plugins.get_mut(&token) {
plugin.set_enabled(false);
}
}
}
}
for token in &explicit_enable {
if let Some(plugin) = self.plugins.get_mut(token) {
plugin.set_enabled(true);
}
}
// --disable-process disables the process plugins.
if self.args.disable_process {
for name in ["processcount", "processlist", "programlist"] {
if let Some(plugin) = self.plugins.get_mut(name) {
plugin.set_enabled(false);
}
}
}
// Processlist needs processcount to update the shared process list.
let processcount_enabled = self
.plugins
.get("processcount")
.map(|p| p.is_enabled())
.unwrap_or(false);
if !processcount_enabled {
for name in ["processlist", "programlist"] {
if let Some(plugin) = self.plugins.get_mut(name) {
if plugin.is_enabled() {
tracing::warn!(
"Processcount is disabled, so processlist (updated by processcount) is also disabled"
);
}
plugin.set_enabled(false);
}
}
}
// --light disables the left sidebar plugins.
if self.args.enable_light {
self.args.disable_left_sidebar = true;
for name in ["processlist", "programlist", "amps", "containers", "vms"] {
if let Some(plugin) = self.plugins.get_mut(name) {
plugin.set_enabled(false);
}
}
}
// --full-quicklook: only quicklook and load.
if self.args.full_quicklook {
self.runtime.help_tag = false;
for name in ["cpu", "mem", "memswap"] {
if let Some(plugin) = self.plugins.get_mut(name) {
plugin.set_enabled(false);
}
}
for name in ["quicklook", "load"] {
if let Some(plugin) = self.plugins.get_mut(name) {
plugin.set_enabled(true);
}
}
}
// --disable-top: disable the top menu plugins.
if self.args.disable_top {
for name in ["quicklook", "cpu", "mem", "memswap", "load"] {
if let Some(plugin) = self.plugins.get_mut(name) {
plugin.set_enabled(false);
}
}
}
// --enable-irq is required for the irq plugin.
if !self.args.enable_irq {
if let Some(plugin) = self.plugins.get_mut("irq") {
plugin.set_enabled(false);
}
} else if let Some(plugin) = self.plugins.get_mut("irq") {
plugin.set_enabled(true);
plugin.model_mut().refresh_timer = glances_core::timer::Timer::new(0.0);
}
}
fn apply_process_options(&mut self) {
let mut procs = processes().lock().unwrap();
procs.no_kernel_threads = self.args.no_kernel_threads;
if let Some(filter) = &self.args.process_filter {
procs.filter.set_filter(Some(filter));
}
if let Some(focus) = &self.args.process_focus {
procs.filter_focus.set_filter(Some(focus));
}
if self.args.enable_process_extended {
procs.enable_extended();
} else {
procs.disable_extended();
}
}
/// Update every enabled plugin.
pub fn update(&mut self) {
let names: Vec<String> = self.plugins.keys().cloned().collect();
let enabled: Vec<String> = names
.into_iter()
.filter(|n| self.plugins[n].is_enabled())
.collect();
self.update_plugins(&enabled);
}
/// Update the given plugins.
pub fn update_plugins(&mut self, names: &[String]) {
for name in names {
self.update_plugin(name);
}
}
fn update_plugin(&mut self, name: &str) {
let Engine {
plugins,
args,
config,
runtime,
..
} = self;
let Some(plugin) = plugins.get_mut(name) else {
return;
};
if !plugin.is_enabled() {
return;
}
// Check the refresh timer: return the previous stats if too early.
let init_value = plugin.init_value();
let model = plugin.model();
if !(model.refresh_timer.finished() || model.stats == init_value) {
return;
}
let key = plugin.get_key();
let mut ctx = UpdateContext {
args,
runtime,
config,
cursor_position: runtime.cursor_position,
};
let raw = match plugin.collect(&mut ctx) {
Ok(raw) => raw,
Err(err) => {
tracing::error!("Error while updating the {name} plugin ({err})");
return;
}
};
let refresh = plugin.model().get_refresh();
let mut raw = raw;
{
let model = plugin.model_mut();
model.time_since_last_update = glances_core::timer::get_time_since_last_update(name);
model.compute_rate(&mut raw, key);
model.update_mmm_fields(&mut raw);
model.stats = raw;
}
if let Err(err) = plugin.post_collect(&mut ctx) {
tracing::error!("Error while post-processing the {name} plugin ({err})");
}
plugin.update_views();
plugin.model_mut().update_stats_history();
let timer = &mut plugin.model_mut().refresh_timer;
timer.set(refresh);
timer.reset(None);
}
pub fn get_plugin(&self, name: &str) -> Option<&dyn Plugin> {
self.plugins.get(name).map(|p| p.as_ref())
}
pub fn get_plugins_list(&self, enabled_only: bool) -> Vec<String> {
self.plugins
.iter()
.filter(|(_, p)| !enabled_only || p.is_enabled())
.map(|(name, _)| name.clone())
.collect()
}
/// Return all the stats as a dict (like `getAllAsDict`).
pub fn all_as_dict(&self, plugin_list: Option<&[String]>) -> serde_json::Map<String, Value> {
let names = match plugin_list {
Some(list) => list.to_vec(),
None => self.get_plugins_list(true),
};
let mut result = serde_json::Map::new();
for name in names {
if let Some(plugin) = self.plugins.get(&name)
&& plugin.is_enabled()
{
result.insert(name, plugin.stats().clone());
}
}
result
}
pub fn end(&mut self) {
for plugin in self.plugins.values_mut() {
plugin.exit();
}
}
}
/// One-shot update of one plugin (used by tests).
pub fn update_single(engine: &mut Engine, name: &str) {
engine.update_plugins(&[name.to_string()]);
}
#[allow(dead_code)]
fn unused(_: &PluginModel) {}