Skip to main content

Expansion

In the previous step, we created a single metric whose value remains constant after being set. While functional, it's not particularly exciting.

In this section, we will expand our plugin to provide multiple metrics and update them periodically.


Refactor

To improve the structure of our plugin, we will:

  1. Refactor: Separate the creation and registration of the metrics from the logic updating their values.
  2. Enhance: Replace the placeholder metric with three more meaningful ones.

Below is the refactored code:

Plugin.Example/Plugin.cs
using System.Runtime.InteropServices;
using MoBro.Plugin.SDK;
using MoBro.Plugin.SDK.Builders;
using MoBro.Plugin.SDK.Enums;
using MoBro.Plugin.SDK.Services;

namespace Plugin.Example;

public class Plugin : IMoBroPlugin
{
private readonly IMoBroService _mobro;

public Plugin(IMoBroService mobro)
{
_mobro = mobro;
}

public void Init()
{
// Create and register all metrics
CreateAndRegisterMetrics();

// Update the values of all metrics
UpdateMetricValues();
}

private void CreateAndRegisterMetrics()
{
// Dynamic metrics: values change over time (e.g., CPU usage, temperature)
var cpuUsage = MoBroItem
.CreateMetric()
.WithId("cpu_usage")
.WithLabel("CPU Usage")
.OfType(CoreMetricType.Usage)
.OfCategory(CoreCategory.Cpu)
.OfNoGroup()
.Build();

var memoryInUse = MoBroItem
.CreateMetric()
.WithId("ram_in_use")
.WithLabel("Memory In Use")
.OfType(CoreMetricType.Data)
.OfCategory(CoreCategory.Ram)
.OfNoGroup()
.Build();

// Static metric: value does not change until the next reboot (e.g., operating system, CPU model name)
var osName = MoBroItem
.CreateMetric()
.WithId("os_name")
.WithLabel("Operating System")
.OfType(CoreMetricType.Text)
.OfCategory(CoreCategory.System)
.OfNoGroup()
.AsStaticValue()
.Build();

// Register all metrics with MoBro at once
_mobro.Register([cpuUsage, memoryInUse, osName]);
}

private void UpdateMetricValues()
{
_mobro.UpdateMetricValue("os_name", RuntimeInformation.OSDescription);
_mobro.UpdateMetricValue("cpu_usage", 0); // Placeholder value
_mobro.UpdateMetricValue("ram_in_use", 1000); // Placeholder value
}
}

Key Changes

  1. We now define and register three meaningful metrics: CPU Usage, Memory In Use, and Operating System.
  2. The metric types and categories have been chosen accordingly (refer to the Metric Reference for details).
  3. The Operating System metric is marked as static using .AsStaticValue(), since its value doesn't change while the system is running.

Expected Output

Running the plugin now produces the following output:

PS C:\dev\Plugin.Example> dotnet run
16:23:04.991 [INF] Creating new plugin instance
16:23:04.991 [INF] Invoking 'init' function on plugin
16:23:04.992 [INF] Registered Metric: cpu_usage
16:23:04.992 [INF] Registered Metric: ram_in_use
16:23:04.992 [INF] Registered Metric: os_name
16:23:04.993 [DBG] Value of metric os_name updated to: Microsoft Windows 10.0.26200
16:23:04.993 [DBG] Value of metric cpu_usage updated to: 0
16:23:04.993 [DBG] Value of metric ram_in_use updated to: 1000

We're now setting the values of all our metrics.
However, these values still remain the same after their initial assignment. Let's make them dynamic!


Scheduling Updates

While setting the value of the Operating System metric once is sufficient, the CPU Usage and Memory In Use metrics must be updated continuously to always reflect the current values.

The simplest way to achieve this is a scheduler that periodically calls an update function. Since this is a common use case, the SDK provides a built-in scheduler: the IMoBroScheduler service. Like the IMoBroService, we simply add it to the constructor (see Reference: Scheduler).

We'll set the value of the static metric once, and schedule a separate function to update the dynamic metrics:

Plugin.Example/Plugin.cs
using System.Runtime.InteropServices;
using MoBro.Plugin.SDK;
using MoBro.Plugin.SDK.Builders;
using MoBro.Plugin.SDK.Enums;
using MoBro.Plugin.SDK.Services;

namespace Plugin.Example;

public class Plugin : IMoBroPlugin
{
private readonly IMoBroService _mobro;
private readonly IMoBroScheduler _scheduler;
private readonly Random _random = new();

public Plugin(IMoBroService mobro, IMoBroScheduler scheduler)
{
_mobro = mobro;
_scheduler = scheduler;
}

public void Init()
{
// Create and register all metrics
CreateAndRegisterMetrics();

// Set the value of the static metric once
SetStaticMetricValues();

// Update the dynamic metrics every 2 seconds, starting after a delay of 5 seconds
_scheduler.Interval(UpdateDynamicMetrics, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(5));
}

private void CreateAndRegisterMetrics()
{
// Dynamic metrics: values change over time (e.g., CPU usage, temperature)
var cpuUsage = MoBroItem
.CreateMetric()
.WithId("cpu_usage")
.WithLabel("CPU Usage")
.OfType(CoreMetricType.Usage)
.OfCategory(CoreCategory.Cpu)
.OfNoGroup()
.Build();

var memoryInUse = MoBroItem
.CreateMetric()
.WithId("ram_in_use")
.WithLabel("Memory In Use")
.OfType(CoreMetricType.Data)
.OfCategory(CoreCategory.Ram)
.OfNoGroup()
.Build();

// Static metric: value does not change until the next reboot (e.g., operating system, CPU model name)
var osName = MoBroItem
.CreateMetric()
.WithId("os_name")
.WithLabel("Operating System")
.OfType(CoreMetricType.Text)
.OfCategory(CoreCategory.System)
.OfNoGroup()
.AsStaticValue()
.Build();

// Register all metrics with MoBro at once
_mobro.Register([cpuUsage, memoryInUse, osName]);
}

private void SetStaticMetricValues()
{
// This value won't change over time, so it's sufficient to set it once
_mobro.UpdateMetricValue("os_name", RuntimeInformation.OSDescription);
}

private void UpdateDynamicMetrics()
{
// Normally, the new values would be read from a sensor or an external API here
_mobro.UpdateMetricValue("cpu_usage", _random.NextDouble() * 100);
_mobro.UpdateMetricValue("ram_in_use", _random.NextDouble() * 16_000_000_000);
}
}

The UpdateDynamicMetrics function is now called for the first time after a delay of 5 seconds, and then every 2 seconds for as long as the plugin is running.

To simulate changing values, we use random numbers for now. In a real plugin, you would read the actual sensor values or call an external API at this point.

Handle errors in scheduled tasks

An exception escaping a scheduled function stops the entire plugin. When reading real sensors or calling APIs, catch expected failures inside the function:

private void UpdateDynamicMetrics()
{
try
{
_mobro.UpdateMetricValue("cpu_usage", ReadCpuUsage());
}
catch (IOException e)
{
// log the error and try again on the next execution
}
}

See In-depth: Error Handling for details.

Metric Value Requirements

  • CPU Usage: Metrics of type CoreMetricType.Usage expect a percentage value between 0 and 100.
  • Memory In Use: Metrics of type CoreMetricType.Data expect the value in bytes.

For more details on metric types, refer to Reference: MetricType.

Expected Output

Running the plugin now produces the following output:

PS C:\dev\Plugin.Example> dotnet run
16:23:05.994 [INF] Creating new plugin instance
16:23:05.994 [INF] Invoking 'init' function on plugin
16:23:05.994 [INF] Registered Metric: cpu_usage
16:23:05.994 [INF] Registered Metric: ram_in_use
16:23:05.994 [INF] Registered Metric: os_name
16:23:05.995 [DBG] Value of metric os_name updated to: Microsoft Windows 10.0.26200
16:23:05.996 [DBG] Scheduling 'interval' job for interval 00:00:02 with a delay of 00:00:05
16:23:06.024 [DBG] Starting scheduler
16:23:11.037 [DBG] Value of metric cpu_usage updated to: 5.081945207483307
16:23:11.037 [DBG] Value of metric ram_in_use updated to: 3516780988.8381906
16:23:13.029 [DBG] Value of metric cpu_usage updated to: 23.271456377820165
16:23:13.030 [DBG] Value of metric ram_in_use updated to: 3820181831.672766
16:23:15.029 [DBG] Value of metric cpu_usage updated to: 31.988658670009183
16:23:15.029 [DBG] Value of metric ram_in_use updated to: 7052605527.253476

Success!
Our metric values are now updated dynamically.


Settings

Currently, the metric values are updated every two seconds.
Different users may have different preferences, though: some prefer shorter intervals for more up-to-date values, while others prefer longer intervals to minimize the performance impact of frequently reading sensors.

Instead of enforcing a fixed interval, we'll let the user configure it.

Adding a Setting

Settings are defined in the settings array of the mobro_plugin_config.json file. Update the file as follows:

Plugin.Example/mobro_plugin_config.json
{
"name": "example_plugin",
"displayName": "Example Plugin",
"author": "Me",
"description": "My first MoBro plugin",
"assembly": "Plugin.Example.dll",
"settings": [
{
"type": "number",
"name": "update_frequency",
"label": "Update Frequency",
"description": "Frequency (in seconds) at which metric values are updated",
"required": true,
"defaultValue": 2,
"min": 1
}
]
}
  • We added a setting of type number named update_frequency.
  • It's marked as required, so the user must provide a value.
  • A sensible default value of 2 seconds is provided.
  • The minimum allowed value is 1. A maximum could be defined as well, but isn't necessary in this case.

MoBro automatically displays this setting to users, validates the entered value, and persists it.

Best Practice

Always provide sensible default values for plugin settings. This ensures the plugin works immediately after installation, without forcing users to configure it first.

Using the Setting

Next, we'll use the value of the setting as the scheduler's interval. Settings values are accessed using the IMoBroSettings service, which we add to the constructor as well:

Plugin.Example/Plugin.cs
using System.Runtime.InteropServices;
using MoBro.Plugin.SDK;
using MoBro.Plugin.SDK.Builders;
using MoBro.Plugin.SDK.Enums;
using MoBro.Plugin.SDK.Services;

namespace Plugin.Example;

public class Plugin : IMoBroPlugin
{
private readonly IMoBroService _mobro;
private readonly IMoBroScheduler _scheduler;
private readonly IMoBroSettings _settings;
private readonly Random _random = new();

public Plugin(IMoBroService mobro, IMoBroScheduler scheduler, IMoBroSettings settings)
{
_mobro = mobro;
_scheduler = scheduler;
_settings = settings;
}

public void Init()
{
// Create and register all metrics
CreateAndRegisterMetrics();

// Set the value of the static metric once
SetStaticMetricValues();

// Read the update frequency (in seconds) configured by the user
var updateFrequency = _settings.GetValue<int>("update_frequency");

// Update the dynamic metrics periodically, starting after a delay of 5 seconds
_scheduler.Interval(UpdateDynamicMetrics, TimeSpan.FromSeconds(updateFrequency), TimeSpan.FromSeconds(5));
}

private void CreateAndRegisterMetrics()
{
// Dynamic metrics: values change over time (e.g., CPU usage, temperature)
var cpuUsage = MoBroItem
.CreateMetric()
.WithId("cpu_usage")
.WithLabel("CPU Usage")
.OfType(CoreMetricType.Usage)
.OfCategory(CoreCategory.Cpu)
.OfNoGroup()
.Build();

var memoryInUse = MoBroItem
.CreateMetric()
.WithId("ram_in_use")
.WithLabel("Memory In Use")
.OfType(CoreMetricType.Data)
.OfCategory(CoreCategory.Ram)
.OfNoGroup()
.Build();

// Static metric: value does not change until the next reboot (e.g., operating system, CPU model name)
var osName = MoBroItem
.CreateMetric()
.WithId("os_name")
.WithLabel("Operating System")
.OfType(CoreMetricType.Text)
.OfCategory(CoreCategory.System)
.OfNoGroup()
.AsStaticValue()
.Build();

// Register all metrics with MoBro at once
_mobro.Register([cpuUsage, memoryInUse, osName]);
}

private void SetStaticMetricValues()
{
// This value won't change over time, so it's sufficient to set it once
_mobro.UpdateMetricValue("os_name", RuntimeInformation.OSDescription);
}

private void UpdateDynamicMetrics()
{
// Normally, the new values would be read from a sensor or an external API here
_mobro.UpdateMetricValue("cpu_usage", _random.NextDouble() * 100);
_mobro.UpdateMetricValue("ram_in_use", _random.NextDouble() * 16_000_000_000);
}
}

To retrieve the value of a setting, call GetValue on the IMoBroSettings service with the name defined in mobro_plugin_config.json. For more details, refer to Reference: Settings.

What happens when the user changes the setting?

When a user changes a setting in MoBro, MoBro re-creates the plugin: the scheduler is cleared, the current plugin instance is shut down, and a new instance is created and initialized with the new settings. So there's no need to watch for changes — reading the setting in Init() is all it takes.
See Plugin Lifecycle for details.

Testing the Setting Locally

When running the plugin locally, there's no user to configure the settings. Instead, we provide the value in Program.cs:

Plugin.Example/Program.cs
using MoBro.Plugin.SDK;

// Create and start the plugin to test it locally
using var plugin = MoBroPluginBuilder
.Create<Plugin.Example.Plugin>()
.WithSetting("update_frequency", "1")
.Build();

// Keep the plugin running until enter is pressed
Console.ReadLine();

When we run the plugin again, the metrics are now updated every second:

PS C:\dev\Plugin.Example> dotnet run
16:23:15.535 [INF] Creating new plugin instance
16:23:15.536 [INF] Invoking 'init' function on plugin
16:23:15.536 [INF] Registered Metric: cpu_usage
16:23:15.536 [INF] Registered Metric: ram_in_use
16:23:15.536 [INF] Registered Metric: os_name
16:23:15.536 [DBG] Value of metric os_name updated to: Microsoft Windows 10.0.26200
16:23:15.536 [DBG] Scheduling 'interval' job for interval 00:00:01 with a delay of 00:00:05
16:23:15.537 [DBG] Starting scheduler
16:23:20.537 [DBG] Value of metric cpu_usage updated to: 48.152672564214825
16:23:20.538 [DBG] Value of metric ram_in_use updated to: 10432078041.490463
16:23:21.537 [DBG] Value of metric cpu_usage updated to: 59.191700728813444
16:23:21.537 [DBG] Value of metric ram_in_use updated to: 4799556187.588496
16:23:22.538 [DBG] Value of metric cpu_usage updated to: 30.89538455338913
16:23:22.538 [DBG] Value of metric ram_in_use updated to: 11258452051.975262

Feel free to experiment with different values.

Success!
The update frequency of the metrics can now be configured by the user.