Plugin API
The Plugin API is available in HDDS Viewer Enterprise Edition.
The Plugin API allows you to extend hdds_viewer with custom analyzers, exporters, and UI panels. Plugins are loaded as dynamic libraries (.so/.dll/.dylib) or WebAssembly modules.
Overview
Plugins can:
- Analyze frames in real-time (
on_framecallback) - Detect custom patterns and anomalies
- Add custom QoS rules specific to your domain
- Export data to external systems
- Render custom UI panels
Quick Start
1. Create a Plugin Project
cargo new --lib my-hdds-plugin
cd my-hdds-plugin
2. Configure Cargo.toml
[package]
name = "my-hdds-plugin"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
viewer-plugin = "0.1"
log = "0.4"
3. Implement the Plugin
use viewer_plugin::prelude::*;
pub struct MyPlugin {
threshold: usize,
alert_count: usize,
}
impl Default for MyPlugin {
fn default() -> Self {
Self {
threshold: 10_000,
alert_count: 0,
}
}
}
impl ViewerPlugin for MyPlugin {
fn name(&self) -> &str { "my-custom-analyzer" }
fn version(&self) -> &str { "1.0.0" }
fn description(&self) -> &str { "Custom analysis for my domain" }
fn author(&self) -> &str { "My Company <dev@example.com>" }
fn initialize(&mut self, ctx: &PluginContext) -> Result<()> {
// Load configuration
if let Some(t) = ctx.get_config_i64("threshold") {
self.threshold = t as usize;
}
log::info!("MyPlugin initialized with threshold={}", self.threshold);
Ok(())
}
fn on_frame(&mut self, frame: &PluginFrame) -> Result<PluginAction> {
// Analyze each frame
if frame.payload_size > self.threshold {
self.alert_count += 1;
return Ok(PluginAction::Alert {
severity: Severity::Warning,
message: format!(
"Large payload on {}: {} bytes",
frame.topic_name, frame.payload_size
),
});
}
Ok(PluginAction::Continue)
}
fn on_session_stop(&mut self) -> Result<()> {
log::info!("Session ended. Total alerts: {}", self.alert_count);
Ok(())
}
}
// Export the plugin
viewer_plugin::export_plugin!(MyPlugin);
4. Build the Plugin
cargo build --release
# Output: target/release/libmy_hdds_plugin.so
5. Configure the Plugin
Create ~/.config/hdds-viewer/plugins/my-plugin.toml:
[plugin]
name = "my-custom-analyzer"
path = "/path/to/libmy_hdds_plugin.so"
enabled = true
[settings]
threshold = "5000"
6. Launch hdds_viewer
The plugin will be loaded automatically on startup.
Plugin Trait Reference
pub trait ViewerPlugin: Send + Sync {
// Required: Metadata
fn name(&self) -> &str;
fn version(&self) -> &str;
fn description(&self) -> &str;
fn author(&self) -> &str;
// Required: Initialization
fn initialize(&mut self, ctx: &PluginContext) -> Result<()>;
// Optional: Session lifecycle
fn on_session_start(&mut self, mode: SessionMode) -> Result<()>;
fn on_session_stop(&mut self) -> Result<()>;
// Optional: Frame processing (hot path!)
fn on_frame(&mut self, frame: &PluginFrame) -> Result<PluginAction>;
// Optional: Topology analysis
fn analyze_topology(&self, topology: &PluginTopology) -> Vec<RuleDetection>;
// Optional: Shutdown
fn shutdown(&mut self) -> Result<()>;
}
Types
PluginFrame
Read-only view of a captured frame:
pub struct PluginFrame {
pub timestamp_nanos: u64,
pub topic_id: u16,
pub topic_name: String,
pub type_id: u16,
pub type_name: String,
pub qos_hash: u32,
pub payload_size: usize,
pub payload: Vec<u8>, // CDR2 encoded
}
PluginAction
Return value from on_frame():
pub enum PluginAction {
Continue, // Normal processing
Drop, // Filter out this frame
Alert { severity, message }, // Trigger notification
Export { destination, data }, // Export to external system
Detection(RuleDetection), // Report a rule detection
}
RuleDetection
Custom rule detection result:
pub struct RuleDetection {
pub rule_id: String, // e.g., "AEROSPACE_TIMING_001"
pub description: String,
pub severity: Severity,
pub topic: Option<String>,
pub confidence: f64, // 0.0 to 1.0
pub suggested_fix: Option<String>,
pub timestamp_nanos: u64,
}
Severity
pub enum Severity {
Info, // Informational
Warning, // Potential issue
Error, // Definite problem
Critical, // Requires immediate attention
}
Configuration
Plugin Context
The PluginContext provides access to configuration and shared state:
impl PluginContext {
pub fn get_config(&self, key: &str) -> Option<&str>;
pub fn get_config_i64(&self, key: &str) -> Option<i64>;
pub fn get_config_f64(&self, key: &str) -> Option<f64>;
pub fn get_config_bool(&self, key: &str) -> Option<bool>;
}
Configuration File
[plugin]
name = "my-plugin"
path = "/opt/hdds-plugins/libmy_plugin.so"
enabled = true
priority = 10 # Lower = loaded first
[settings]
# Plugin-specific settings (string key-value)
threshold = "1000"
export_url = "https://monitoring.example.com/api"
debug_mode = "false"
Plugin Directories
Plugins are scanned from these directories:
| Platform | System Directory | User Directory |
|---|---|---|
| Linux | /usr/share/hdds-viewer/plugins/ | ~/.config/hdds-viewer/plugins/ |
| macOS | /Library/Application Support/HDDS Viewer/Plugins/ | ~/Library/Application Support/HDDS Viewer/plugins/ |
| Windows | %PROGRAMDATA%\HDDS Viewer\Plugins\ | %APPDATA%\HDDS Viewer\plugins\ |
Security
Signature Verification
In production, plugins must be signed with Ed25519:
# Sign a plugin (build tool)
hdds-plugin-sign --key private.key libmy_plugin.so
# Output: libmy_plugin.so.sig
The viewer verifies the signature before loading.
Dev Mode
For development, signature verification can be skipped:
HDDS_PLUGIN_DEV_MODE=1 hdds-viewer
Never use dev mode in production!
Best Practices
Performance
The on_frame() method is called for every frame (potentially millions). Keep it fast:
fn on_frame(&mut self, frame: &PluginFrame) -> Result<PluginAction> {
// Good: O(1) operations
if frame.payload_size > self.threshold {
return Ok(PluginAction::Alert { ... });
}
// Bad: Heavy computation on every frame
// let analysis = expensive_ml_inference(&frame.payload);
Ok(PluginAction::Continue)
}
For heavy computation, use analyze_topology() which is called periodically.
Thread Safety
Plugins must be Send + Sync. Use Arc<RwLock<...>> for shared state:
use std::sync::Arc;
use parking_lot::RwLock;
pub struct MyPlugin {
shared_state: Arc<RwLock<SharedState>>,
}
Error Handling
Never panic in plugin code. Return Err(...) instead:
fn on_frame(&mut self, frame: &PluginFrame) -> Result<PluginAction> {
let data = parse_payload(&frame.payload)
.map_err(|e| PluginError::other(format!("Parse failed: {}", e)))?;
Ok(PluginAction::Continue)
}
Example: Aerospace Timing Analyzer
Complete example for DO-178C timing compliance:
use viewer_plugin::prelude::*;
use std::collections::HashMap;
pub struct AerospaceTimingPlugin {
max_latency_ms: f64,
topic_latencies: HashMap<String, Vec<f64>>,
violations: Vec<RuleDetection>,
}
impl Default for AerospaceTimingPlugin {
fn default() -> Self {
Self {
max_latency_ms: 10.0, // DO-178C requirement
topic_latencies: HashMap::new(),
violations: Vec::new(),
}
}
}
impl ViewerPlugin for AerospaceTimingPlugin {
fn name(&self) -> &str { "aerospace-timing" }
fn version(&self) -> &str { "1.0.0" }
fn description(&self) -> &str { "DO-178C timing compliance checker" }
fn author(&self) -> &str { "Aerospace Corp" }
fn initialize(&mut self, ctx: &PluginContext) -> Result<()> {
if let Some(max) = ctx.get_config_f64("max_latency_ms") {
self.max_latency_ms = max;
}
Ok(())
}
fn on_session_start(&mut self, _mode: SessionMode) -> Result<()> {
self.topic_latencies.clear();
self.violations.clear();
Ok(())
}
fn analyze_topology(&self, _topology: &PluginTopology) -> Vec<RuleDetection> {
let mut detections = Vec::new();
for (topic, latencies) in &self.topic_latencies {
if latencies.is_empty() {
continue;
}
let max = latencies.iter().cloned().fold(0.0_f64, f64::max);
let p99_idx = (latencies.len() as f64 * 0.99) as usize;
let mut sorted = latencies.clone();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
let p99 = sorted.get(p99_idx).copied().unwrap_or(max);
if p99 > self.max_latency_ms {
detections.push(RuleDetection {
rule_id: "AEROSPACE_TIMING_001".to_string(),
description: format!(
"Topic '{}' P99 latency {:.2}ms exceeds {:.2}ms limit",
topic, p99, self.max_latency_ms
),
severity: Severity::Critical,
topic: Some(topic.clone()),
confidence: 0.95,
suggested_fix: Some(
"Review network path and QoS settings for this topic".to_string()
),
timestamp_nanos: 0,
});
}
}
detections
}
fn get_detections(&self) -> Vec<RuleDetection> {
self.violations.clone()
}
}
viewer_plugin::export_plugin!(AerospaceTimingPlugin);
See Also
- QoS Advisor - Built-in detection rules
- Enterprise Edition - Contact sales@naskel.com for licensing