This example demonstrates how to create and use dynamically loaded collector plugins.
Overview
The monitoring system supports loading collector plugins from shared libraries at runtime. This allows:
- Third-party plugins without recompilation
- Plugin distribution as separate libraries
- On-demand plugin loading
- Plugin updates without rebuilding the main application
Files
Building
Build the Plugin Library
cd examples/plugin_example
mkdir build
cd build
cmake ..
make
This creates:
- Linux:
libexample_plugin.so
- macOS:
libexample_plugin.dylib
- Windows:
example_plugin.dll
Build the Example Program
From the project root:
mkdir build
cd build
cmake ..
make
Running
# Linux
./plugin_loader_example ./libexample_plugin.so
# macOS
./plugin_loader_example ./libexample_plugin.dylib
# Windows
plugin_loader_example.exe example_plugin.dll
Expected Output
=== Dynamic Plugin Loading Example ===
Loading plugin from: ./libexample_plugin.so
Plugin loaded successfully
Plugin Metadata:
Name: example_plugin
Description: Example dynamically loaded collector plugin
Version: 1.0.0
Available: yes
Initializing plugin...
Plugin initialized
Collecting metrics (5 iterations)...
Iteration 1:
example.cpu_usage: 45.23 % [plugin=example, type=cpu]
example.memory_usage: 512.67 MB [plugin=example, type=memory]
example.request_count: 1 requests [plugin=example, type=counter]
Iteration 2:
example.cpu_usage: 38.91 % [plugin=example, type=cpu]
example.memory_usage: 487.32 MB [plugin=example, type=memory]
example.request_count: 2 requests [plugin=example, type=counter]
...
Shutting down plugin...
Unloading plugin...
Plugin unloaded successfully
=== Example Complete ===
Creating Your Own Plugin
1. Implement the collector_plugin Interface
public:
auto name() const -> std::string_view
override {
return "my_plugin";
}
auto initialize(
const config_map& config) ->
bool override {
return true;
}
}
auto collect() -> std::vector<metric_data>
override {
std::vector<metric_data> metrics;
return metrics;
}
return true;
}
return plugin_metadata_t{
plugin_type::collector,
"My Plugin",
"1.0.0",
"Description"
};
}
};
Pure virtual interface for metric collector plugins.
virtual auto initialize(const config_map &) -> bool
Initialize plugin with configuration.
virtual auto collect() -> std::vector< metric >=0
Collect current metrics from this plugin.
virtual void shutdown()
Shutdown plugin and release resources.
virtual auto is_available() const -> bool=0
Check if this plugin is available on the current system.
virtual auto get_metadata() const -> plugin_metadata
Get plugin metadata.
virtual auto name() const -> std::string_view=0
Get the unique name of this plugin.
Plugin interface for metric collectors.
@ custom
User-defined plugins.
C API for dynamically loaded collector plugins.
2. Export the Plugin
Use the IMPLEMENT_PLUGIN macro at the end of your source file:
IMPLEMENT_PLUGIN(
my_plugin,
"my_plugin",
"1.0.0",
"My custom plugin",
"Your Name",
"custom"
)
3. Build as Shared Library
Create a CMakeLists.txt:
cmake_minimum_required(VERSION 3.20)
project(my_plugin VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_library(my_plugin SHARED my_plugin.cpp)
target_include_directories(my_plugin PRIVATE
${MONITORING_SYSTEM_INCLUDE_DIR}
)
set_target_properties(my_plugin PROPERTIES
POSITION_INDEPENDENT_CODE ON
)
Build:
mkdir build
cd build
cmake ..
make
4. Load and Use
if (!registry.load_plugin("/path/to/libmy_plugin.so")) {
std::cerr << "Load failed: " << registry.get_plugin_loader_error() << "\n";
return;
}
auto* plugin = registry.get_plugin("my_plugin");
if (plugin) {
plugin->initialize({});
auto metrics = plugin->collect();
}
registry.unload_plugin("my_plugin");
static auto instance() -> collector_registry &
Get the singleton instance.
Registry for managing collector plugin lifecycle.
Plugin API Version Compatibility
Plugins must be compiled with the same PLUGIN_API_VERSION as the monitoring system. If the versions don't match, the plugin will fail to load with an error message:
Incompatible API version: plugin=2, expected=1
To check the API version:
#define PLUGIN_API_VERSION
Platform-Specific Notes
Linux
- Use
.so extension
- Compile with
-fPIC flag
- Link with
-ldl for dynamic loading
macOS
- Use
.dylib extension
- Compile with
-fPIC flag
- May need to set
DYLD_LIBRARY_PATH environment variable
Windows
- Use
.dll extension
- Compile with
/DEXPORT_PLUGIN or set WINDOWS_EXPORT_ALL_SYMBOLS
- Put DLL in same directory as executable or in PATH
Error Handling
Check for errors after loading:
if (!registry.load_plugin(path)) {
std::string error = registry.get_plugin_loader_error();
}
Common errors:
file_not_found - Plugin file doesn't exist
library_load_failed - Failed to load shared library (missing dependencies)
symbol_not_found - Required export function not found
incompatible_api_version - Plugin compiled with different API version
plugin_unavailable - Plugin's is_available() returned false
already_loaded - Plugin with same name already loaded
Security Considerations
- Validate plugin paths before loading
- Only load plugins from trusted sources
- Check API version compatibility
- Consider implementing plugin signing/verification
- Be aware that plugins run with full application permissions
Performance
- Plugin loading incurs one-time cost for library loading
- Subsequent metric collection has same performance as built-in collectors
- Unloading plugins releases all associated resources