Prometheus 3+: create calculation function for your custom metric module

Hello!

In this article we will continue to talk about adding your own custom metrics. You can find the previous article here.

You can find the video for this article here.

You can find the final source code here.

This time we will discuss how to create a calculation function for a custom metric module and then use the results of this calculation function in your module.

Why do we need it?

Suppose we want to get the following metrics for each application (core, software and service desk):

jira_get_remaining_seats – it shows how many spare licenses each application has

jira_is_role_installed_and_licensed – it shows if a role is installed and licensed

To get these metrics we need to use the ApplicationRoleManager class. To get the mentioned metrics we need to use the following methods from the class:

boolean isRoleInstalledAndLicensed(@Nonnull ApplicationKey var1), int getRemainingSeats(@Nonnull ApplicationKey var1)

To accomplish it we need to add a new module collector src/main/java/ru/matveev/alexey/prometheus/jira/extension/metrics/applicationmanager/ApplicationManagerMetricCollector.java:

public class ApplicationManagerMetricCollector extends PrometheusMetricCollector {

    public ApplicationManagerMetricCollector(GetRemainingSeatsGauge getRemainingSeatsGauge,
                                             IsRoleInstalledAndLicensedGauge isRoleInstalledAndLicensedGauge) {
        this.getMetrics().add(getRemainingSeatsGauge);
        this.getMetrics().add(isRoleInstalledAndLicensedGauge);
    }

}

Then we register our new collector in atlassian-plugin.xml file:

<prometheusMetric key="application-manager-metric-module" name="Application manager metric module"
                      class="ru.matveev.alexey.prometheus.jira.extension.metrics.applicationmanager.ApplicationManagerMetricCollector"/>

And add two our metrics.

src/main/java/ru/matveev/alexey/prometheus/jira/extension/metrics/applicationmanager/GetRemainingSeatsGauge.java

@Named
public class GetRemainingSeatsGauge extends PrometheusMetric {
    private final ApplicationManager applicationManager;
    private final ApplicationRoleManager applicationRoleManager;

    private final Gauge jira_get_remaining_seats = Gauge.build()
            .name("jira_get_remaining_seats")
            .help("How many remaining seats left")
            .labelNames("licenseType")
            .create();

    public GetRemainingSeatsGauge(@ComponentImport ApplicationManager applicationManager,
                                  @ComponentImport ApplicationRoleManager applicationRoleManager) {
        super("jira_get_remaining_seats", "How many remaining seats left", true, true);
        this.applicationManager = applicationManager;
        this.applicationRoleManager = applicationRoleManager;
    }

    @Override
    public PrometheusMetricResult collect() {
        for (Application application : this.applicationManager.getApplications()) {
            if (application != null) {
                SingleProductLicenseDetailsView singleProductLicenseDetailsView = application.getLicense().getOrNull();
                if (singleProductLicenseDetailsView != null) {
                    int value = applicationRoleManager.getRemainingSeats(application.getKey());
                    this.jira_get_remaining_seats.labels(application.getName()).set(value);
                }
            }
        }
        return new PrometheusMetricResult(this.jira_get_remaining_seats.collect());
    }

}

src/main/java/ru/matveev/alexey/prometheus/jira/extension/metrics/applicationmanager/IsRoleInstalledAndLicensedGauge.java:

@Named
public class IsRoleInstalledAndLicensedGauge extends PrometheusMetric {
    private final ApplicationManager applicationManager;
    private final ApplicationRoleManager applicationRoleManager;

    private final Gauge isRoleInstalledAndLicensedMetric = Gauge.build()
            .name("jira_is_role_installed_and_licensed")
            .help("if the role installed and licensed")
            .labelNames("licenseType")
            .create();

    public IsRoleInstalledAndLicensedGauge(@ComponentImport ApplicationManager applicationManager,
                                           @ComponentImport ApplicationRoleManager applicationRoleManager) {
        super("jira_is_role_installed_and_licensed", "if the role installed and licensed", true, true);
        this.applicationManager = applicationManager;
        this.applicationRoleManager = applicationRoleManager;
    }

    @Override
    public PrometheusMetricResult collect() {
        for (Application application : this.applicationManager.getApplications()) {
            if (application != null) {
                SingleProductLicenseDetailsView singleProductLicenseDetailsView = application.getLicense().getOrNull();
                if (singleProductLicenseDetailsView != null) {
                    boolean value = applicationRoleManager.isRoleInstalledAndLicensed(application.getKey());
                    this.isRoleInstalledAndLicensedMetric.labels(application.getName()).set(getBoolean(value));
                }
            }
        }
        return new PrometheusMetricResult(this.isRoleInstalledAndLicensedMetric.collect());
    }

}

As you can see these two modules contain about the same code:

 for (Application application : this.applicationManager.getApplications()) {
            if (application != null) {
                SingleProductLicenseDetailsView singleProductLicenseDetailsView = application.getLicense().getOrNull();
                if (singleProductLicenseDetailsView != null) {
                    boolean value = applicationRoleManager.isRoleInstalledAndLicensed(application.getKey());

As you can see we iterate over all installed applications and then we get the value for the metric. We have 2 metrics which means that we have to iterate twice. If we had 5 metrics to get within this iteration then we would iterate 5 times. It is not good for performance. It would be better to do the iteration once and get the values for the both metrics.

I will show how you can do it.

Refactor the metrics

First we need to add our iteration to the module level that is why we will change src/main/java/ru/matveev/alexey/prometheus/jira/extension/metrics/applicationmanager/ApplicationManagerMetricCollector.java

public class ApplicationManagerMetricCollector extends PrometheusMetricCollector {
    private final ApplicationManager applicationManager;
    private final ApplicationRoleManager applicationRoleManager;

    public ApplicationManagerMetricCollector(@ComponentImport ApplicationManager applicationManager,
                                             @ComponentImport ApplicationRoleManager applicationRoleManager,
                                             GetRemainingSeatsGauge getRemainingSeatsGauge,
                                             IsRoleInstalledAndLicensedGauge isRoleInstalledAndLicensedGauge) {
        this.getMetrics().add(getRemainingSeatsGauge);
        this.getMetrics().add(isRoleInstalledAndLicensedGauge);
        this.applicationManager = applicationManager;
        this.applicationRoleManager = applicationRoleManager;
    }

    @Override
    protected List<PrecalculatedValue> getPrecalculatedValues() {
        List<PrecalculatedValue> result = new ArrayList<>();
        for (Application application : this.applicationManager.getApplications()) {
            if (application != null) {
                SingleProductLicenseDetailsView singleProductLicenseDetailsView = application.getLicense().getOrNull();
                if (singleProductLicenseDetailsView != null) {
                    double remainingSeatsValue = applicationRoleManager.getRemainingSeats(application.getKey());
                    double isRoleInstalledAndLicensedValue = getBoolean(applicationRoleManager.isRoleInstalledAndLicensed(application.getKey()));

                    result.add(new PrecalculatedValue("jira_get_remaining_seats", Arrays.asList(application.getName()), remainingSeatsValue));
                    result.add(new PrecalculatedValue("jira_is_role_installed_and_licensed", Arrays.asList(application.getName()), isRoleInstalledAndLicensedValue));

                }
            }
        }
        return result;
    }
}

As you can see we added the protected List<PrecalculatedValue> getPrecalculatedValues() method.

This methods calculates all metrics and puts the result to the list of PrecalculatedValue.

PrecalculatedValue contains all necessary information about metrics:

@Data
@RequiredArgsConstructor
public class PrecalculatedValue {
    @NonNull String name;
    @NonNull List<String> labels;
    @NonNull double value;


}

name – the name of the value to retrieve ( you can name it as you want)

labels – labels for the value

value – value itself.

As you can see we iterate in the getPrecalculatedValues method and create two PrecalculatedValue objects for each metric. That is all we need to do in the collector.

Now let’s change the code for our metrics:

/main/java/ru/matveev/alexey/prometheus/jira/extension/metrics/applicationmanager/GetRemainingSeatsGauge.java

@Named
public class GetRemainingSeatsGauge extends PrometheusMetric {

    private final Gauge jira_get_remaining_seats = Gauge.build()
            .name("jira_get_remaining_seats")
            .help("How many remaining seats left")
            .labelNames("licenseType")
            .create();

    public GetRemainingSeatsGauge() {
        super("jira_get_remaining_seats", "How many remaining seats left", true, true);
    }

    @Override
    public PrometheusMetricResult collect(List<PrecalculatedValue> precalculatedValues) {
        PrecalculatedValue value = this.getPrecalculatedValuesForMetric(precalculatedValues, "jira_get_remaining_seats").get(0);
        this.jira_get_remaining_seats.labels( value.getLabels().get(0)).set(value.getValue());
        return new PrometheusMetricResult(this.jira_get_remaining_seats.collect());
    }

}

We use an overloaded collect method:

public PrometheusMetricResult collect(List<PrecalculatedValue> precalculatedValues)

This method accepts the list of PrecalculatedValue which we calculated in our collector. Then we retrieve values for our metric and set the value to our metric. Which means we do not do any calculation any more in our metric.

That is all what should be done.

That is how you can do calculation only once for all metrics.

If you have found a spelling error, please, notify us by selecting that text and pressing Ctrl+Enter.

Leave a Reply

%d bloggers like this:

Spelling error report

The following text will be sent to our editors: