Skip to main content

Expansion

In the previous step we've created a single metric whose value does not change after it has been set. That's nice, but also a bit boring and not really what we're after when creating a plugin.

So in this section we will adapt and expand our plugin to contain multiple metrics and update them periodically.

Refactor

First we're going to refactor our plugin and split up the creation and registration part of our metrics from the part that is updating the values.
And while we're at it, let's also change the metric to something more meaningful and add two more.

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 number metric (e.g. CPU temperature, load, etc..)
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 text metric (e.g. CPU name, operating system)
var osName = MoBroItem
.CreateMetric()
.WithId("os_name")
.WithLabel("Operating system")
.OfType(CoreMetricType.Text)
.OfCategory(CoreCategory.System)
.OfNoGroup()
.AsStaticValue()
.Build();

// register the metrics to MoBro
_mobro.Register(cpuUsage);
_mobro.Register(memoryInUse);
_mobro.Register(osName);
}

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

We've now changed the metrics to 'CPU usage', 'Memory in use' and 'Operating system', just as an example for some actually meaningful metrics.
As you can see, the metric types and categories have also changed (more on metric types in the Metric reference).

We've also moved the invocation of the metric registration and update functions from the constructor to the Init function. This function will automatically be invoked exactly once after an instance of our plugin has been created (More on that in In depth: IMoBroPlugin).

tip

While calling the CreateAndRegisterMetrics and UpdateMetricValues functions from the constructor would have worked just fine as well, it's nicer to separate the actual business logic (creating and updating metrics) from the plugin creation (the constructor call).

Running the refactored plugin now gives us:

PS C:\dev\mobro-data-plugins\Plugin.Example> dotnet run
01:17:22.500 [INF] Creating new plugin instance
01:17:22.523 [INF] Invoking 'init' function on plugin
01:17:22.557 [DBG] Registered Metric: cpu_usage
01:17:22.558 [DBG] Registered Metric: ram_in_use
01:17:22.559 [DBG] Registered Metric: os_name
01:17:22.564 [DBG] Value of metric os_name updated to: Microsoft Windows 10.0.19045
01:17:22.567 [DBG] Value of metric cpu_usage updated to: 0
01:17:22.569 [DBG] Value of metric ram_in_use updated to: 1000

We're now setting the value for all our metrics.
But currently those values do not change after we've set them once. Let's change that!

Scheduling

While setting the value for the 'Operating system' metric just once is fine (as this value can not change), we want to constantly update our 'CPU usage' and 'Memory usage' metrics to always provide the current values.
The easiest way to achieve that is to use a scheduler and just schedule an update function for our dynamic metrics to be invoked every few seconds.

Since this is a very common use case, the SDK already provides a scheduler. So let's inject the IMoBroScheduler in the constructor (More details on the service under Reference: Scheduler).
Instead of calling our UpdateMetricValues function only once in Init, we will schedule it to be called periodically for a given interval instead.

And since we do not need to periodically update the 'Operating system' metric, we will move that to a separate function:

Plugin.Example/Plugin.cs
public class Plugin : IMoBroPlugin
{
private readonly IMoBroService _mobro;
private readonly IMoBroScheduler _scheduler;
private readonly Random _random;

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

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

// set the value for the static metrics
SetStaticMetricValues();

// schedule a recurring task to update the dynamic metrics
_scheduler.Interval(UpdateDynamicMetrics, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(5));
}

[...]

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

private void UpdateDynamicMetrics()
{
// update the changing dynamic metrics
// normally we would retrieve the new value first by e.g. reading a sensor or calling an external API
_mobro.UpdateMetricValue("cpu_usage", _random.NextDouble() * 100);
_mobro.UpdateMetricValue("ram_in_use", _random.NextDouble() * 1_000_000_000);
}

[...]
}
tip

If your plugin provides metrics whose values are guaranteed to not change until the next system reboot, always create
them .AsStaticValue() and only set/update the value once.

Now our SetStaticMetricValues function will only be called once on plugin initialization, since it's enough to set that value once (as it won't change).
Whereas our UpdateDynamicMetrics function will be called first after an initial delay of 5 seconds and then periodically every 2 seconds as long as the plugin is running (or gets paused).

To get some changing metric values we're returning a random value for now. Normally you would read the actual sensor instead at this point and return the retrieved value.

Since our CPU usage metric is of type CoreMetricType.Usage, we need to return a percentage value in range 0 - 100.
Our memory metric is of type CoreMetricType.Data, so we need to return the data value in bytes.
More on metric types in Reference: Metrics.

Running the plugin now will give us:

PS C:\dev\mobro-data-plugins\Plugin.Example> dotnet run
01:24:34.364 [INF] Creating new plugin instance
01:24:34.383 [INF] Invoking 'init' function on plugin
01:24:34.413 [DBG] Registered Metric: cpu_usage
01:24:34.415 [DBG] Registered Metric: ram_in_use
01:24:34.416 [DBG] Registered Metric: os_name
01:24:34.419 [DBG] Value of metric os_name updated to: Microsoft Windows 10.0.19045
01:24:34.424 [DBG] Scheduling 'interval' job for interval 00:00:02 with a delay of 00:00:05
01:24:34.527 [DBG] Starting scheduler
01:24:39.550 [DBG] Value of metric cpu_usage updated to: 39.63311068798713
01:24:39.551 [DBG] Value of metric ram_in_use updated to: 892389951.7236712
01:24:41.536 [DBG] Value of metric cpu_usage updated to: 2.415082483011388
01:24:41.537 [DBG] Value of metric ram_in_use updated to: 123213804.21178019
01:24:43.535 [DBG] Value of metric cpu_usage updated to: 67.0902048404516
01:24:43.536 [DBG] Value of metric ram_in_use updated to: 220452476.57903522

Success!
Our metric values are finally changing.

Settings

Our metric values are currently changing every two seconds.
That's nice, but some users may want shorter update intervals whilst others may prefer longer intervals. As usual, it comes down to personal preference and the trade-off between more up-to-date metrics and a potential performance impact as a result of polling the sensors too often.

We can't make that decision for everyone, so let's just make the interval configurable by the user.
To achieve that we will add a setting to our plugin by adapting the settings array in our mobro_plugin_config.json file:

Plugin.Example/mobro_plugin_config.json
{
"settings": [
{
"type": "number",
"name": "update_frequency",
"label": "Update frequency",
"description": "Metric value update frequency",
"required": true,
"defaultValue": 2,
"min": 1
}
]
}

We've just added a setting of type number for our update frequency. We've also marked it as required, provided a default value and set the minimum value to 1. We could set a maximum as well, but it doesn't really make a lot of sense in this case.

tip

It's always a good idea to provide sensible default values for plugin settings (where possible). This way a plugin can be started after installation without forcing the user to configure it first.

Now MoBro knows about our setting and will handle everything from exposing it to the user, validating and persisting it.
Now we just need to retrieve the current settings value and adapt our scheduler interval based on it. For that we will inject the IMoBroSettings service in the constructor and get the value of the setting in the Init function:

Plugin.Example/Plugin.cs
public class Plugin : IMoBroPlugin
{
private readonly IMoBroService _mobro;
private readonly IMoBroScheduler _scheduler;
private readonly IMoBroSettings _settings;
private readonly Random _random;

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

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

// set the value for the static metrics
SetStaticMetricValues();

// get the value of the setting for the update frequency
var updateFrequencySetting = _settings.GetValue<int>("update_frequency");

// schedule a recurring task to update the dynamic metrics
_scheduler.Interval(UpdateDynamicMetrics, TimeSpan.FromSeconds(updateFrequencySetting), TimeSpan.FromSeconds(5));
}

[...]
}

As you can see we can simply get the value for our setting by calling GetValue on the IMoBroSettings service and passing in our name we've defined in mobro_plugin_config.json.
More details on the IMoBroSettings service under Reference: Settings.

Now since we're testing the plugin locally, there is no user that can configure the settings. So we need to adjust the Progam.cs as well and provide a value for our setting:

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

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

// prevent the program from exiting immediately
Console.ReadLine();

Now when we run our plugin again, the metrics should be updated every second.
This is also a simple way to test settings values, so feel free to try a few different values.

But let's have a look at our output:

PS C:\dev\mobro-data-plugins\Plugin.Example> dotnet run
01:28:51.975 [INF] Creating new plugin instance
01:28:51.994 [INF] Invoking 'init' function on plugin
01:28:52.028 [DBG] Registered Metric: cpu_usage
01:28:52.030 [DBG] Registered Metric: ram_in_use
01:28:52.032 [DBG] Registered Metric: os_name
01:28:52.043 [DBG] Value of metric os_name updated to: Microsoft Windows 10.0.19045
01:28:52.048 [DBG] Scheduling 'interval' job for interval 00:00:01 with a delay of 00:00:05
01:28:52.170 [DBG] Starting scheduler
01:28:57.193 [DBG] Value of metric cpu_usage updated to: 18.314520086015506
01:28:57.194 [DBG] Value of metric ram_in_use updated to: 432407624.6621394
01:28:58.179 [DBG] Value of metric cpu_usage updated to: 74.36047971197121
01:28:58.181 [DBG] Value of metric ram_in_use updated to: 117872170.93206614

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