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 cfPROM_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 ... */
}