Skip to content

Plugin architecture

Florian Forster edited this page Nov 21, 2023 · 1 revision

This article describes the collectd plugin architecture. It is aimed at people who want to write a plugin in C.

The plugin structure in itself is not very complicated, but it's easy to forget something anyway. We recommend copying a simple plugin (the Load plugin, for example) and start from there.

Here's a walk-through or a checklist of things a plugin needs.

Copyright notice

All *.c-files must include a copyright notice and license information. The license must be compatible to collectd's own license, the GPL 2. Unless you have a good reason to split up your plugin into multiple files, please put everything into one .c-file.

Includes / Header files

Usually plugins include at least the following header-files:

 #include "collectd.h"
 #include "common.h" /* auxiliary functions */
 #include "plugin.h" /* plugin_register_*, plugin_dispatch_values */

Callback functions

The initial step for a plugin is to register one or more callback functions. After the shared object has been loaded, the daemon will call the function

 void module_register (void);

that has to be exported by the plugin. The purpose of this function is to register callback functions with the daemon (and nothing else). All functions but module_register, especially all callback functions, should be declared static.

There are the following types of callback functions, that can be registered. Of course, they are all optional.

Configuration callbacks

If a plugin needs configuration it must provide a configuration callback. There are actually two types of configuration callbacks: “simple” and “complex”. Both register functions are declared in src/plugin.h.

Simple configuration callbacks

The simple configuration callbacks are called for each “configuration item”, i. e. each key-value-pair read from the config file.

The accepted options (keys) must be passed to the registration function. Usually, the options are defined near the beginning of the file similar to this:

 static const char *config_keys[] =
 {
   "OptionOne",
   "OptionTwo",
   "SomethingElse"
 };
 static int config_keys_num = STATIC_ARRAY_SIZE (config_keys);

The callback function must have the following prototype:

 static int my_config (const char *key, const char *value);

The return value must be zero upon success, greater than zero if it failed or less than zero if key has an invalid value.

To register the callback use the plugin_register_config function like this:

 void module_register (void)
 {
   plugin_register_config ("my_plugin", my_config,
       config_keys, config_keys_num);
 }

The callback function is called once for each config-item found. There's no guarantee that it's called only once for each option / key or that it's called at all.

Complex configuration callbacks

The complex configuration callbacks are called once for each <Plugin /> block parsed for that plugin. It is passed the entire tree which allows plugins to use nested blocks and provides access to the parsed data types.

The callback function must have the following prototype:

 static int my_config (oconfig_item_t *ci);

The return value must be zero upon success and non-zero if the configuration failed.

To register the callback use the plugin_register_complex_config function like this:

 void module_register (void)
 {
   plugin_register_complex_config ("my_plugin", my_config);
 }

For example code on how to handle the configuration tree see the source code of the SNMP plugin, src/snmp.c.

Initialization callbacks

The init-function is used to set up a plugin. It is first called after the configuration file has been read (i.e. all calls to the config-callbacks have been made) and before any calls to the read- and write-functions. However, for historical reasons, it may be called again during the lifetime of the process and plugins need to be written in a way to handle this gracefully. #911 is tracking work to change this, so that init functions are called only once, as one would expect.

The callback function must have the following prototype:

 static int my_init (void);

The function has to return zero if successful and non-zero otherwise. If non-zero is returned all functions the plugin registered will be unregistered.

Use the plugin_register_init function to register the callback like this:

 void module_register (void)
 {
   plugin_register_init ("my_plugin", my_init);
 }

Read callbacks

  • → See also: :Category:Callback-read

Read callbacks come in two variations: “simple” and “complex” read callbacks.

Both callbacks are called periodically, but only once for each registration. So they must be thread-safe but not reentrant-safe (unless registered two or more times).

Simple read callbacks

The callback function must have the following prototype:

 static int my_read (void);

The function has to return zero if successful and non-zero otherwise. If non-zero is returned, the value will be suspended for an increasing interval, but no longer than 86,400 seconds (one day).

Use the plugin_register_read function to register the callback like this:

 void module_register (void)
 {
   plugin_register_read ("my_plugin", my_read);
 }

Complex read callbacks

Complex read callbacks differ from simple read callbacks in that they are passed a user-data-t pointer. This is used for example by the Java plugin (and should be used by Perl plugin) to decide which registration was called and call the appropriate opcode / script implementation.

The callback function must have the following prototype:

 static int plugin_read_cb (user_data_t *ud);

The function has to return zero if successful and non-zero otherwise. If non-zero is returned, the value will be suspended for an increasing interval, but no longer than 86,400 seconds (one day).

Use the plugin_register_complex_read function to register the callback. You can pass a struct timespec to the registration function to have the callback called in a specific interval. If you pass NULL, the global Interval setting will be used.

 void module_register (void)
 {
   struct timespec ts = { 13, 370000000 }; /* call every 13,37 seconds */
   plugin_register_complex_read ("my_plugin", my_read
       /* interval = */ &ts, /* user data = */ NULL);
 }

Write callbacks

Plugins that implement writing of values to somewhere may register a write-function. When a read-function calls plugin_dispatch_values that value-list will be passed to all write-functions together with a data-set, a description of the values. See data-set-t, value-list-t and user-data-t for a description of the data-types. Please note that this function needs to be thread-safe.

The callback function must have the following prototype:

 static int my_write (const data_set_t *ds, const value_list_t *vl, user_data_t *ud);

The function has to return zero if successful and non-zero otherwise.

Use the plugin_register_write function to register the callback like this:

 void module_register (void)
 {
   plugin_register_write ("my_plugin", my_write, /* user data = */ NULL);
 }

Flush callbacks

Many write callbacks do some sort of caching to improve IO-performance. The RRDtool plugin is a prominent example for this. If possible, plugin that implement a write function should also provide a flush callback to empty those buffers / caches.

The callback function must have the following prototype:

 static int my_flush (int timeout, const char *identifier, user_data_t *ud);

The callback is expected to flush values that are older than timeout seconds. Values smaller than or equal to zero indicate that the age should not be checked. If identifier is not NULL, it is okay only to flush the specified data. The last argument is an optional pointer to state information. See the register function below.

The function has to return zero if successful and non-zero otherwise.

Use the plugin_register_flush function to register the callback like this:

 void module_register (void)
 {
   plugin_register_flush ("my_plugin", my_flush, /* user data = */ NULL);
 }

Shutdown callbacks

Many plugins wish to clean up after themselves. The shutdown-functions can be used to implement that and terminate threads, close files or similar tasks before exiting.

The callback function must have the following prototype:

 static int my_shutdown (void);

The function has to return zero if successful and non-zero otherwise.

Use the plugin_register_shutdown function to register the callback like this:

 void module_register (void)
 {
   plugin_register_shutdown ("my_plugin", my_shutdown);
 }

Log callbacks

Plugins that implement logging may register a log-function. The severity constants LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG are defined in src/plugin.h. The last argument is an optional pointer to state information, see user-data-t for the data-type description. Please note that log-function needs to be reentrant-safe!

The callback function must have the following prototype:

 static void my_log (int severity, const char *message, user_data_t *ud);

Use the plugin_register_log() function to register the callback like this:

 void module_register (void)
 {
   plugin_register_log ("my_plugin", my_log, /* user data = */ NULL);
 }

Notification callbacks

todo

Data types

There are two complex data types that plugins may encounter. The first one, value-list-t, is used when passing values from the plugin to the plugin_dispatch_values function from the plugin interface. The second one, data-set-t occurs in the prototype of write-functions and may come up when dynamically loading data-set-definitions. The declarations can be found in src/plugin.h.

The interface's stability

The C API is considered internal and no guarantees about its backwards compatibility to previous minor versions are made, i.e. the API may change pretty much at any time. It will certainly change with the next major version.

We have done changes accross the entire code base in the past. For example, we used to have a function called ssnprintf() which was very widely used before being removed. Such changes will surely happen again. If you want to be sure that your plugin works next year, create a pull request. We will then take care of it and adapt the interface should it change.

This does not apply to other APIs, for example the plain-text-protocol, e.g. used by the Exec plugin, the APIs provided by the Perl, Python and Java plugins, etc. These APIs are explicitly external and will not be broken by minor versions.

Clone this wiki locally