Prometheus client

The lib-common provides a simple implementation of a Prometheus client.

Resources

Its header file is src/prometheus-client.h. All structures and APIs are documented there.

Unit tests are in tests/zchk-prometheus.blk.

An example program using the prometheus client to expose dummy metrics is in examples/ex-prometheus-client.c.

Limitations

It tries to stick to the spirit of the Writing client libraries page, but simplifications were made with our use-cases in mind:

  • there is a unique collector (so no collector registry), and all metrics are automatically registered in it when using the high-level helpers.

  • only counter, gauge and histogram metrics are implemented (at least for now).

  • parent metrics creation/deletion is NOT thread safe, so they must be created from the thread of the event loop; however, modifying the value of an existing metric (using the provided helpers) and creating children metrics using the labels() method and variants is thread safe.

Error handling

Errors at metrics creation (ie. invalid metric or label name, invalid number of labels when creating a child, …​) are fatal errors: the program stops when it happens, with a proper error and generating a core dump.

We have made this choice because this kind of errors are programmatic errors, and are supposed to happen as soon as the program is launched, so cannot happen in production if the code was properly tested.

Thanks to that, the users of the API do not have to handle these error cases, which simplifies the code.

Memory management

User’s code usually do not have to manually destroy created metrics since all the allocated memory is automatically destroyed when the prometheus_client module is released.

However, it is still possible to call obj_delete on created objects if needed.

Usage

Initializing module

In order to use the Prometheus client, you must first activate the prometheus_client module. So either you work in a module that depends on prometheus_client, or you have to manually require it:

#include <lib-common/prometeus-client.h>

MODULE_REQUIRE(prometheus_client);

/* YOUR CODE HERE */

MODULE_RELEASE(prometheus_client);

Running HTTP scraping server

Then, you can call prom_http_start_server to start the scraping HTTP server. It requires to build a configuration structure first, with the address to listen on:

#include <lib-common/iop.h>

SB_1k(err);
core__httpd_cfg__t httpd_cfg;

iop_init(core__httpd_cfg, &httpd_cfg);
httpd_cfg.bind_addr = "0.0.0.0:8080";
if (prom_http_start_server(&httpd_cfg, &err) < 0) {
    e_fatal("cannot start HTTP server: %*pM", SB_FMT_ARG(&err));
}

Creating and using counter/gauge metrics

You can create counter/gauge metrics using respectively prom_counter_new and prom_gauge_new. These helpers take as argument the name of the metric, its description, and a variable number of label names (or no label name for simple metrics):

prom_counter_t *counter_no_label;
prom_counter_t *counter_labels;
prom_gauge_t   *gauge_labels;

counter_no_label = prom_counter_new(
    "counter_no_label",
    "A simple counter without label",
);

counter_labels = prom_counter_new(
    "counter_labels",
    "A counter with two labels"
    "label1", "label2",
);

gauge_no_label = prom_gauge_new(
    "gauge_no_label",
    "A simple gauge without label",
);

The methods add and inc can be used to respectively add a value to a counter or increment it. The method get_value can be used to get a metric value in a thread-safe manner:

double value;

obj_vcall(counter_no_label, add, 2.);
obj_vcall(counter_no_label, inc);

value = obj_vcall(counter_no_label, get_value);
/* value == 3 here */

The gauge metrics support the methods add, inc, sub, dec and set:

double value;

obj_vcall(gauge_no_label, add, 4.);
obj_vcall(gauge_no_label, inc);
obj_vcall(gauge_no_label, sub, 3);
obj_vcall(gauge_no_label, dec);

value = obj_vcall(gauge_no_label, get_value);
/* value == 1 here */

obj_vcall(gauge_no_label, set, -12.5);

value = obj_vcall(gauge_no_label, get_value);
/* value == -12.5 here */

The helpers prom_counter_labels and prom_gauge_labels can be used to get children metrics, for metrics having labels. It must be called with the same number of label values as the number of label names in the parent metric. The result is also a metric pointer, that can be cached for later use, and supports the same methods to modify the value:

prom_counter_t *counter_child;

counter_child = prom_counter_labels(counter_labels, "value 1", "value 2");
obj_vcall(counter_child, inc);

Creating and using histogram metrics

Histogram metrics can be created with prom_histogram_new, just as counter/gauge. Once created, the buckets MUST be set using one of the provided helpers:

  • prom_histogram_set_default_buckets to set the default buckets cf PROM_DEFAULT_BUCKETS define).

  • prom_histogram_set_buckets to manually specify the buckets.

  • prom_histogram_set_linear_buckets to use a linear distribution as buckets.

  • prom_histogram_set_exponential_buckets to use an exponential distribution as buckets.

prom_histogram_t *histo_manual;
prom_histogram_t *histo_linear;

histo_manual = prom_histogram_new(
    "histogram_manual_buckets",
    "An histogram with manually-defined buckets (and no label)",
);
prom_histogram_set_buckets(histo_manual, 0.5, 1, 3, 6, 10);

histo_linear = prom_histogram_new(
    "histogram_linear_buckets",
    "An histogram with linear buckets (and two label)",
    "label1", "label2",
);
prom_histogram_set_linear_buckets(histo_linear, 10, 10, 5);

Then the method observe is used to observe a value:

prom_histogram_t *histo_child;

obj_vcall(histo_manual, observe, 3.14);

histo_child = prom_histogram_labels(histo_linear, "value 1", "value 2");
obj_vcall(histo_child, observe, 25);

The library also provides tools to observe the execution time of code. The functions prom_histogram_timer_start / prom_histogram_timer_finish can be used manually:

prom_histogram_t *histo_timing = prom_histogram_new(...);
prom_histogram_timer_ctx_t timer_ctx;

timer_ctx = prom_histogram_timer_start(histo_timing);
/* ... code to time ... */
prom_histogram_timer_finish(&timer_ctx);

Or directly use prom_histogram_timer_scope to measure the execution time of a block of code:

{
    prom_histogram_timer_scope(histo_timing);

    /* ... code to time ... */
}

Full example program

You can also read examples/ex-prometheus-client.c for a full example program, with an event loop integration.