collectd_plugin/
plugins.rs

1use crate::api::{ConfigItem, LogLevel, ValueList};
2use crate::errors::NotImplemented;
3use bitflags::bitflags;
4use chrono::Duration;
5use std::error;
6use std::panic::{RefUnwindSafe, UnwindSafe};
7
8bitflags! {
9    /// Bitflags of capabilities that a plugin advertises to collectd.
10    #[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
11    pub struct PluginCapabilities: u32 {
12        const READ =   0b0000_0001;
13        const LOG =    0b0000_0010;
14        const WRITE =  0b0000_0100;
15        const FLUSH =  0b0000_1000;
16    }
17}
18
19bitflags! {
20    /// Bitflags of capabilities that a plugin manager advertises to collectd
21    #[derive(Default)]
22    pub struct PluginManagerCapabilities: u32 {
23        const INIT = 0b0000_0001;
24    }
25}
26
27/// How many instances of the plugin will be registered
28pub enum PluginRegistration {
29    /// Our module will only register a single plugin
30    Single(Box<dyn Plugin>),
31
32    /// Our module registers several modules. The String in the tuple must be unique identifier
33    Multiple(Vec<(String, Box<dyn Plugin>)>),
34}
35
36impl PluginCapabilities {
37    pub fn has_read(self) -> bool {
38        self.intersects(PluginCapabilities::READ)
39    }
40
41    pub fn has_log(self) -> bool {
42        self.intersects(PluginCapabilities::LOG)
43    }
44
45    pub fn has_write(self) -> bool {
46        self.intersects(PluginCapabilities::WRITE)
47    }
48
49    pub fn has_flush(self) -> bool {
50        self.intersects(PluginCapabilities::FLUSH)
51    }
52}
53
54/// Defines the entry point for a collectd plugin. Based on collectd's configuration, a
55/// `PluginManager` will register any number of plugins (or return an error)
56pub trait PluginManager {
57    /// Name of the plugin. Must not contain null characters or panic.
58    fn name() -> &'static str;
59
60    /// Defines the capabilities of the plugin manager. Must not panic.
61    fn capabilities() -> PluginManagerCapabilities {
62        PluginManagerCapabilities::default()
63    }
64
65    /// Returns one or many instances of a plugin that is configured from collectd's configuration
66    /// file. If parameter is `None`, a configuration section for the plugin was not found, so
67    /// default values should be used.
68    fn plugins(
69        _config: Option<&[ConfigItem<'_>]>,
70    ) -> Result<PluginRegistration, Box<dyn error::Error>>;
71
72    /// Initialize any socket, files, event loops, or any other resources that will be shared
73    /// between multiple plugin instances.
74    fn initialize() -> Result<(), Box<dyn error::Error>> {
75        Err(NotImplemented.into())
76    }
77
78    /// Cleanup any resources or glodal data, allocated during initialize()
79    fn shutdown() -> Result<(), Box<dyn error::Error>> {
80        Ok(())
81    }
82}
83
84/// An individual plugin that is capable of reporting values to collectd, receiving values from
85/// other plugins, or logging messages. A plugin must implement `Sync + Send` as collectd could be sending
86/// values to be written or logged concurrently. The Rust compiler will ensure that everything
87/// not thread safe is wrapped in a Mutex (or another compatible datastructure)
88pub trait Plugin: Send + Sync + UnwindSafe + RefUnwindSafe {
89    /// A plugin's capabilities. By default a plugin does nothing, but can advertise that it can
90    /// configure itself and / or report values.
91    fn capabilities(&self) -> PluginCapabilities {
92        PluginCapabilities::default()
93    }
94
95    /// Customizes how a message of a given level is logged. If the message isn't valid UTF-8, an
96    /// allocation is done to replace all invalid characters with the UTF-8 replacement character
97    fn log(&self, _lvl: LogLevel, _msg: &str) -> Result<(), Box<dyn error::Error>> {
98        Err(NotImplemented.into())
99    }
100
101    /// This function is called when collectd expects the plugin to report values, which will occur
102    /// at the `Interval` defined in the global config (but can be overridden). Implementations
103    /// that expect to report values need to have at least have a capability of `READ`. An error in
104    /// reporting values will cause collectd to backoff exponentially until a delay of a day is
105    /// reached.
106    fn read_values(&self) -> Result<(), Box<dyn error::Error>> {
107        Err(NotImplemented.into())
108    }
109
110    /// Collectd is giving you reported values, do with them as you please. If writing values is
111    /// expensive, prefer to buffer them in some way and register a `flush` callback to write.
112    fn write_values(&self, _list: ValueList<'_>) -> Result<(), Box<dyn error::Error>> {
113        Err(NotImplemented.into())
114    }
115
116    /// Flush values to be written that are older than given duration. If an identifier is given,
117    /// then only those buffered values should be flushed.
118    fn flush(
119        &self,
120        _timeout: Option<Duration>,
121        _identifier: Option<&str>,
122    ) -> Result<(), Box<dyn error::Error>> {
123        Err(NotImplemented.into())
124    }
125}
126
127/// Sets up all the ffi entry points that collectd expects when given a `PluginManager`.
128#[macro_export]
129macro_rules! collectd_plugin {
130    ($type:ty) => {
131        // Let's us know if we've seen our config section before
132        static CONFIG_SEEN: ::std::sync::atomic::AtomicBool =
133            ::std::sync::atomic::AtomicBool::new(false);
134
135        // This is the main entry point that collectd looks for. Our plugin manager will register
136        // callbacks for configuration related to our name. It also registers a callback for
137        // initialization for when configuration is absent or a single plugin wants to hold global
138        // data
139        #[no_mangle]
140        pub extern "C" fn module_register() {
141            use std::ffi::CString;
142            use $crate::bindings::{
143                plugin_register_complex_config, plugin_register_init, plugin_register_shutdown,
144            };
145
146            $crate::internal::register_panic_handler();
147
148            let s = CString::new(<$type as $crate::PluginManager>::name())
149                .expect("Plugin name to not contain nulls");
150
151            unsafe {
152                plugin_register_complex_config(s.as_ptr(), Some(collectd_plugin_complex_config));
153
154                plugin_register_init(s.as_ptr(), Some(collectd_plugin_init));
155
156                plugin_register_shutdown(s.as_ptr(), Some(collectd_plugin_shutdown));
157            }
158        }
159
160        extern "C" fn collectd_plugin_init() -> ::std::os::raw::c_int {
161            $crate::internal::plugin_init::<$type>(&CONFIG_SEEN)
162        }
163
164        extern "C" fn collectd_plugin_shutdown() -> ::std::os::raw::c_int {
165            $crate::internal::plugin_shutdown::<$type>()
166        }
167
168        unsafe extern "C" fn collectd_plugin_complex_config(
169            config: *mut $crate::bindings::oconfig_item_t,
170        ) -> ::std::os::raw::c_int {
171            $crate::internal::plugin_complex_config::<$type>(&CONFIG_SEEN, config)
172        }
173    };
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179
180    #[test]
181    fn test_plugin_capabilities() {
182        let capabilities = PluginCapabilities::READ | PluginCapabilities::WRITE;
183        assert_eq!(capabilities.has_read(), true);
184        assert_eq!(capabilities.has_write(), true);
185
186        let capabilities = PluginCapabilities::READ;
187        assert_eq!(capabilities.has_read(), true);
188        assert_eq!(capabilities.has_write(), false);
189    }
190}