- 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
407 lines
14 KiB
Rust
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) {}
|