//! 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>, } 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> = 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 = 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 = 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 = 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::>() { 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 = self.plugins.keys().cloned().collect(); let enabled: Vec = 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 { 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 { 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) {}