Skip to content
Arxo Arxo

Plugin System Documentation

The architecture analyzer uses a plugin-based system for metrics computation. This allows you to add new metrics without modifying the core system.

All metric plugins implement the MetricPlugin trait:

#[async_trait]
pub trait MetricPlugin: Send + Sync {
fn id(&self) -> &str;
fn version(&self) -> &str;
fn requires(&self) -> Vec<DataRequirement>;
async fn compute(&self, ctx: &MetricContext) -> anyhow::Result<MetricResult>;
}
  • id(): Returns a unique identifier for the plugin (e.g., "scc", "propagation_cost")
  • version(): Returns the plugin version string
  • requires(): Lists the data artifacts this plugin needs (e.g., ImportGraph, SccDag, Reachability)
  • compute(): Computes the metric and returns a MetricResult

Create a new directory under crates/arxo-engine/src/metrics/:

crates/arxo-engine/src/metrics/
└── your_metric/
├── mod.rs
└── plugin.rs

Example plugin structure:

use async_trait::async_trait;
use std::collections::HashMap;
use crate::core::types::{DataRequirement, MetricContext, MetricPlugin, MetricResult};
pub struct YourMetricPlugin;
#[async_trait]
impl MetricPlugin for YourMetricPlugin {
fn id(&self) -> &str {
"your_metric"
}
fn version(&self) -> &str {
"1.0.0"
}
fn requires(&self) -> Vec<DataRequirement> {
vec![DataRequirement::ImportGraph]
}
async fn compute(&self, ctx: &MetricContext) -> anyhow::Result<MetricResult> {
// Access data through context
let graph = ctx.data.import_graph().await?;
// Compute your metric
let value = compute_your_metric(&graph);
// Return result
let mut values = HashMap::new();
values.insert("your_metric.value".to_string(), value);
Ok(MetricResult {
id: self.id().to_string(),
version: self.version().to_string(),
values,
details: None,
})
}
}

Add your plugin to the registry in crates/arxo-engine/src/core/registry.rs:

use crate::metrics::your_metric::plugin::YourMetricPlugin;
impl PluginRegistry {
fn register_defaults(&mut self) {
self.register(Box::new(SccMetricPlugin));
self.register(Box::new(YourMetricPlugin)); // Add your plugin
}
}

Add to crates/arxo-engine/src/metrics/mod.rs:

pub mod scc;
pub mod your_metric; // Add your module

Plugins can request these data artifacts through DataRequirement:

  • ImportGraph: The dependency graph with nodes and edges
  • SccDag: Strongly connected components (cycles) and condensed DAG
  • Reachability: Transitive reachability matrix (future)
  • ExportsIndex: Public API surface analysis (future)

The SCC (Strongly Connected Components) plugin demonstrates the pattern:

pub struct SccMetricPlugin;
#[async_trait]
impl MetricPlugin for SccMetricPlugin {
fn id(&self) -> &str { "scc" }
fn version(&self) -> &str { "1.0.0" }
fn requires(&self) -> Vec<DataRequirement> {
vec![DataRequirement::SccDag]
}
async fn compute(&self, ctx: &MetricContext) -> anyhow::Result<MetricResult> {
let scc_dag = ctx.data.scc_dag().await?;
let mut values = HashMap::new();
values.insert("scc.component_count".to_string(), scc_dag.component_count() as f64);
values.insert("scc.cycle_count".to_string(), scc_dag.cycle_count() as f64);
values.insert("scc.max_cycle_size".to_string(), scc_dag.max_cycle_size() as f64);
values.insert("scc.total_nodes_in_cycles".to_string(),
scc_dag.total_in_cycles() as f64);
Ok(MetricResult {
id: self.id().to_string(),
version: self.version().to_string(),
values,
details: None,
})
}
}

The MetricResult contains:

  • id: Plugin identifier
  • version: Plugin version
  • values: HashMap of metric keys to values (e.g., "scc.cycle_count"5.0)
  • details: Optional additional diagnostic information

Metric keys follow the pattern: {plugin_id}.{metric_name} (e.g., scc.cycle_count, scc.max_cycle_size, propagation_cost.system.ratio).

Plugins are enabled/disabled in the YAML configuration:

metrics:
- id: scc
enabled: true
- id: your_metric
enabled: true
  1. Idempotency: Plugins should produce consistent results for the same input
  2. Error Handling: Use anyhow::Result for proper error propagation
  3. Performance: Cache expensive computations in the MetricContext.cache if needed
  4. Naming: Use descriptive metric keys that include the plugin ID
  5. Versioning: Update version when plugin behavior changes

The plugin system is designed to support:

  • Dynamic plugin loading (future)
  • Plugin dependencies
  • Plugin-specific configuration from YAML
  • Parallel metric computation