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:
- Refactor: Separate the creation and registration of the metrics from the logic updating their values.
- Enhance: Replace the placeholder metric with three more meaningful ones.
Below is the refactored code:
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
- We now define and register three meaningful metrics: CPU Usage, Memory In Use, and Operating System.
- The metric types and categories have been chosen accordingly (refer to the Metric Reference for details).
- 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:
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.
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.Usageexpect a percentage value between 0 and 100. - Memory In Use: Metrics of type
CoreMetricType.Dataexpect 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:
{
"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
numbernamedupdate_frequency. - It's marked as
required, so the user must provide a value. - A sensible default value of
2seconds 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.
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:
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.
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:
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.