mirror of
https://github.com/netdata/netdata.git
synced 2025-04-06 06:25:32 +00:00

* rrdset - in progress * rrdset optimal constructor; rrdset conflict * rrdset final touches * re-organization of rrdset object members * prevent use-after-free * dictionary dfe supports also counting of iterations * rrddim managed by dictionary * rrd.h cleanup * DICTIONARY_ITEM now is referencing actual dictionary items in the code * removed rrdset linked list * Revert "removed rrdset linked list" This reverts commit 690d6a588b4b99619c2c5e10f84e8f868ae6def5. * removed rrdset linked list * added comments * Switch chart uuid to static allocation in rrdset Remove unused functions * rrdset_archive() and friends... * always create rrdfamily * enable ml_free_dimension * rrddim_foreach done with dfe * most custom rrddim loops replaced with rrddim_foreach * removed accesses to rrddim->dimensions * removed locks that are no longer needed * rrdsetvar is now managed by the dictionary * set rrdset is rrdsetvar, fixes https://github.com/netdata/netdata/pull/13646#issuecomment-1242574853 * conflict callback of rrdsetvar now properly checks if it has to reset the variable * dictionary registered callbacks accept as first parameter the DICTIONARY_ITEM * dictionary dfe now uses internal counter to report; avoided excess variables defined with dfe * dictionary walkthrough callbacks get dictionary acquired items * dictionary reference counters that can be dupped from zero * added advanced functions for get and del * rrdvar managed by dictionaries * thread safety for rrdsetvar * faster rrdvar initialization * rrdvar string lengths should match in all add, del, get functions * rrdvar internals hidden from the rest of the world * rrdvar is now acquired throughout netdata * hide the internal structures of rrdsetvar * rrdsetvar is now acquired through out netdata * rrddimvar managed by dictionary; rrddimvar linked list removed; rrddimvar structures hidden from the rest of netdata * better error handling * dont create variables if not initialized for health * dont create variables if not initialized for health again * rrdfamily is now managed by dictionaries; references of it are acquired dictionary items * type checking on acquired objects * rrdcalc renaming of functions * type checking for rrdfamily_acquired * rrdcalc managed by dictionaries * rrdcalc double free fix * host rrdvars is always needed * attempt to fix deadlock 1 * attempt to fix deadlock 2 * Remove unused variable * attempt to fix deadlock 3 * snprintfz * rrdcalc index in rrdset fix * Stop storing active charts and computing chart hashes * Remove store active chart function * Remove compute chart hash function * Remove sql_store_chart_hash function * Remove store_active_dimension function * dictionary delayed destruction * formatting and cleanup * zero dictionary base on rrdsetvar * added internal error to log delayed destructions of dictionaries * typo in rrddimvar * added debugging info to dictionary * debug info * fix for rrdcalc keys being empty * remove forgotten unlock * remove deadlock * Switch to metadata version 5 and drop chart_hash chart_hash_map chart_active dimension_active v_chart_hash * SQL cosmetic changes * do not busy wait while destroying a referenced dictionary * remove deadlock * code cleanup; re-organization; * fast cleanup and flushing of dictionaries * number formatting fixes * do not delete configured alerts when archiving a chart * rrddim obsolete linked list management outside dictionaries * removed duplicate contexts call * fix crash when rrdfamily is not initialized * dont keep rrddimvar referenced * properly cleanup rrdvar * removed some locks * Do not attempt to cleanup chart_hash / chart_hash_map * rrdcalctemplate managed by dictionary * register callbacks on the right dictionary * removed some more locks * rrdcalc secondary index replaced with linked-list; rrdcalc labels updates are now executed by health thread * when looking up for an alarm look using both chart id and chart name * host initialization a bit more modular * init rrdlabels on host update * preparation for dictionary views * improved comment * unused variables without internal checks * service threads isolation and worker info * more worker info in service thread * thread cancelability debugging with internal checks * strings data races addressed; fixes https://github.com/netdata/netdata/issues/13647 * dictionary modularization * Remove unused SQL statement definition * unit-tested thread safety of dictionaries; removed data race conditions on dictionaries and strings; dictionaries now can detect if the caller is holds a write lock and automatically all the calls become their unsafe versions; all direct calls to unsafe version is eliminated * remove worker_is_idle() from the exit of service functions, because we lose the lock time between loops * rewritten dictionary to have 2 separate locks, one for indexing and another for traversal * Update collectors/cgroups.plugin/sys_fs_cgroup.c Co-authored-by: Vladimir Kobal <vlad@prokk.net> * Update collectors/cgroups.plugin/sys_fs_cgroup.c Co-authored-by: Vladimir Kobal <vlad@prokk.net> * Update collectors/proc.plugin/proc_net_dev.c Co-authored-by: Vladimir Kobal <vlad@prokk.net> * fix memory leak in rrdset cache_dir * minor dictionary changes * dont use index locks in single threaded * obsolete dict option * rrddim options and flags separation; rrdset_done() optimization to keep array of reference pointers to rrddim; * fix jump on uninitialized value in dictionary; remove double free of cache_dir * addressed codacy findings * removed debugging code * use the private refcount on dictionaries * make dictionary item desctructors work on dictionary destruction; strictier control on dictionary API; proper cleanup sequence on rrddim; * more dictionary statistics * global statistics about dictionary operations, memory, items, callbacks * dictionary support for views - missing the public API * removed warning about unused parameter * chart and context name for cloud * chart and context name for cloud, again * dictionary statistics fixed; first implementation of dictionary views - not currently used * only the master can globally delete an item * context needs netdata prefix * fix context and chart it of spins * fix for host variables when health is not enabled * run garbage collector on item insert too * Fix info message; remove extra "using" * update dict unittest for new placement of garbage collector * we need RRDHOST->rrdvars for maintaining custom host variables * Health initialization needs the host->host_uuid * split STRING to its own files; no code changes other than that * initialize health unconditionally * unit tests do not pollute the global scope with their variables * Skip initialization when creating archived hosts on startup. When a child connects it will initialize properly Co-authored-by: Stelios Fragkakis <52996999+stelfrag@users.noreply.github.com> Co-authored-by: Vladimir Kobal <vlad@prokk.net>
906 lines
31 KiB
C
906 lines
31 KiB
C
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
#define EXPORTINGS_INTERNALS
|
|
#include "prometheus.h"
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// PROMETHEUS
|
|
// /api/v1/allmetrics?format=prometheus and /api/v1/allmetrics?format=prometheus_all_hosts
|
|
|
|
static int is_matches_rrdset(struct instance *instance, RRDSET *st, SIMPLE_PATTERN *filter) {
|
|
if (instance->config.options & EXPORTING_OPTION_SEND_NAMES) {
|
|
return simple_pattern_matches(filter, rrdset_name(st));
|
|
}
|
|
return simple_pattern_matches(filter, rrdset_id(st));
|
|
}
|
|
|
|
/**
|
|
* Check if a chart can be sent to Prometheus
|
|
*
|
|
* @param instance an instance data structure.
|
|
* @param st a chart.
|
|
* @param filter a simple pattern to match against.
|
|
* @return Returns 1 if the chart can be sent, 0 otherwise.
|
|
*/
|
|
inline int can_send_rrdset(struct instance *instance, RRDSET *st, SIMPLE_PATTERN *filter)
|
|
{
|
|
#ifdef NETDATA_INTERNAL_CHECKS
|
|
RRDHOST *host = st->rrdhost;
|
|
#endif
|
|
|
|
// Do not send anomaly rates charts.
|
|
if (rrdset_is_ar_chart(st))
|
|
return 0;
|
|
|
|
if (unlikely(rrdset_flag_check(st, RRDSET_FLAG_EXPORTING_IGNORE)))
|
|
return 0;
|
|
|
|
if (filter) {
|
|
if (!is_matches_rrdset(instance, st, filter)) {
|
|
return 0;
|
|
}
|
|
} else if (unlikely(!rrdset_flag_check(st, RRDSET_FLAG_EXPORTING_SEND))) {
|
|
// we have not checked this chart
|
|
if (is_matches_rrdset(instance, st, instance->config.charts_pattern)) {
|
|
rrdset_flag_set(st, RRDSET_FLAG_EXPORTING_SEND);
|
|
} else {
|
|
rrdset_flag_set(st, RRDSET_FLAG_EXPORTING_IGNORE);
|
|
debug(
|
|
D_EXPORTING,
|
|
"EXPORTING: not sending chart '%s' of host '%s', because it is disabled for exporting.",
|
|
rrdset_id(st),
|
|
rrdhost_hostname(host));
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (unlikely(!rrdset_is_available_for_exporting_and_alarms(st))) {
|
|
debug(
|
|
D_EXPORTING,
|
|
"EXPORTING: not sending chart '%s' of host '%s', because it is not available for exporting.",
|
|
rrdset_id(st),
|
|
rrdhost_hostname(host));
|
|
return 0;
|
|
}
|
|
|
|
if (unlikely(
|
|
st->rrd_memory_mode == RRD_MEMORY_MODE_NONE &&
|
|
!(EXPORTING_OPTIONS_DATA_SOURCE(instance->config.options) == EXPORTING_SOURCE_DATA_AS_COLLECTED))) {
|
|
debug(
|
|
D_EXPORTING,
|
|
"EXPORTING: not sending chart '%s' of host '%s' because its memory mode is '%s' and the exporting connector requires database access.",
|
|
rrdset_id(st),
|
|
rrdhost_hostname(host),
|
|
rrd_memory_mode_name(host->rrd_memory_mode));
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static struct prometheus_server {
|
|
const char *server;
|
|
uint32_t hash;
|
|
RRDHOST *host;
|
|
time_t last_access;
|
|
struct prometheus_server *next;
|
|
} *prometheus_server_root = NULL;
|
|
|
|
static netdata_mutex_t prometheus_server_root_mutex = NETDATA_MUTEX_INITIALIZER;
|
|
|
|
/**
|
|
* Clean server root local structure
|
|
*/
|
|
void prometheus_clean_server_root()
|
|
{
|
|
if (prometheus_server_root) {
|
|
netdata_mutex_lock(&prometheus_server_root_mutex);
|
|
|
|
struct prometheus_server *ps;
|
|
for (ps = prometheus_server_root; ps; ) {
|
|
struct prometheus_server *current = ps;
|
|
ps = ps->next;
|
|
if(current->server)
|
|
freez((void *)current->server);
|
|
|
|
freez(current);
|
|
}
|
|
prometheus_server_root = NULL;
|
|
netdata_mutex_unlock(&prometheus_server_root_mutex);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the last time when a Prometheus server scraped the Netdata Prometheus exporter.
|
|
*
|
|
* @param server the name of the Prometheus server.
|
|
* @param host a data collecting host.
|
|
* @param now actual time.
|
|
* @return Returns the last time when the server accessed Netdata, or 0 if it is the first occurrence.
|
|
*/
|
|
static inline time_t prometheus_server_last_access(const char *server, RRDHOST *host, time_t now)
|
|
{
|
|
#ifdef UNIT_TESTING
|
|
return 0;
|
|
#endif
|
|
uint32_t hash = simple_hash(server);
|
|
|
|
netdata_mutex_lock(&prometheus_server_root_mutex);
|
|
|
|
struct prometheus_server *ps;
|
|
for (ps = prometheus_server_root; ps; ps = ps->next) {
|
|
if (host == ps->host && hash == ps->hash && !strcmp(server, ps->server)) {
|
|
time_t last = ps->last_access;
|
|
ps->last_access = now;
|
|
netdata_mutex_unlock(&prometheus_server_root_mutex);
|
|
return last;
|
|
}
|
|
}
|
|
|
|
ps = callocz(1, sizeof(struct prometheus_server));
|
|
ps->server = strdupz(server);
|
|
ps->hash = hash;
|
|
ps->host = host;
|
|
ps->last_access = now;
|
|
ps->next = prometheus_server_root;
|
|
prometheus_server_root = ps;
|
|
|
|
netdata_mutex_unlock(&prometheus_server_root_mutex);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Copy and sanitize name.
|
|
*
|
|
* @param d a destination string.
|
|
* @param s a source string.
|
|
* @param usable the number of characters to copy.
|
|
* @return Returns the length of the copied string.
|
|
*/
|
|
inline size_t prometheus_name_copy(char *d, const char *s, size_t usable)
|
|
{
|
|
size_t n;
|
|
|
|
for (n = 0; *s && n < usable; d++, s++, n++) {
|
|
register char c = *s;
|
|
|
|
if (!isalnum(c))
|
|
*d = '_';
|
|
else
|
|
*d = c;
|
|
}
|
|
*d = '\0';
|
|
|
|
return n;
|
|
}
|
|
|
|
/**
|
|
* Copy and sanitize label.
|
|
*
|
|
* @param d a destination string.
|
|
* @param s a source string.
|
|
* @param usable the number of characters to copy.
|
|
* @return Returns the length of the copied string.
|
|
*/
|
|
inline size_t prometheus_label_copy(char *d, const char *s, size_t usable)
|
|
{
|
|
size_t n;
|
|
|
|
// make sure we can escape one character without overflowing the buffer
|
|
usable--;
|
|
|
|
for (n = 0; *s && n < usable; d++, s++, n++) {
|
|
register char c = *s;
|
|
|
|
if (unlikely(c == '"' || c == '\\' || c == '\n')) {
|
|
*d++ = '\\';
|
|
n++;
|
|
}
|
|
*d = c;
|
|
}
|
|
*d = '\0';
|
|
|
|
return n;
|
|
}
|
|
|
|
/**
|
|
* Copy and sanitize units.
|
|
*
|
|
* @param d a destination string.
|
|
* @param s a source string.
|
|
* @param usable the number of characters to copy.
|
|
* @param showoldunits set this flag to 1 to show old (before v1.12) units.
|
|
* @return Returns the destination string.
|
|
*/
|
|
inline char *prometheus_units_copy(char *d, const char *s, size_t usable, int showoldunits)
|
|
{
|
|
const char *sorig = s;
|
|
char *ret = d;
|
|
size_t n;
|
|
|
|
// Fix for issue 5227
|
|
if (unlikely(showoldunits)) {
|
|
static struct {
|
|
const char *newunit;
|
|
uint32_t hash;
|
|
const char *oldunit;
|
|
} units[] = { { "KiB/s", 0, "kilobytes/s" },
|
|
{ "MiB/s", 0, "MB/s" },
|
|
{ "GiB/s", 0, "GB/s" },
|
|
{ "KiB", 0, "KB" },
|
|
{ "MiB", 0, "MB" },
|
|
{ "GiB", 0, "GB" },
|
|
{ "inodes", 0, "Inodes" },
|
|
{ "percentage", 0, "percent" },
|
|
{ "faults/s", 0, "page faults/s" },
|
|
{ "KiB/operation", 0, "kilobytes per operation" },
|
|
{ "milliseconds/operation", 0, "ms per operation" },
|
|
{ NULL, 0, NULL } };
|
|
static int initialized = 0;
|
|
int i;
|
|
|
|
if (unlikely(!initialized)) {
|
|
for (i = 0; units[i].newunit; i++)
|
|
units[i].hash = simple_hash(units[i].newunit);
|
|
initialized = 1;
|
|
}
|
|
|
|
uint32_t hash = simple_hash(s);
|
|
for (i = 0; units[i].newunit; i++) {
|
|
if (unlikely(hash == units[i].hash && !strcmp(s, units[i].newunit))) {
|
|
// info("matched extension for filename '%s': '%s'", filename, last_dot);
|
|
s = units[i].oldunit;
|
|
sorig = s;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
*d++ = '_';
|
|
for (n = 1; *s && n < usable; d++, s++, n++) {
|
|
register char c = *s;
|
|
|
|
if (!isalnum(c))
|
|
*d = '_';
|
|
else
|
|
*d = c;
|
|
}
|
|
|
|
if (n == 2 && sorig[0] == '%') {
|
|
n = 0;
|
|
d = ret;
|
|
s = "_percent";
|
|
for (; *s && n < usable; n++)
|
|
*d++ = *s++;
|
|
} else if (n > 3 && sorig[n - 3] == '/' && sorig[n - 2] == 's') {
|
|
n = n - 2;
|
|
d -= 2;
|
|
s = "_persec";
|
|
for (; *s && n < usable; n++)
|
|
*d++ = *s++;
|
|
}
|
|
|
|
*d = '\0';
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Format host labels for the Prometheus exporter
|
|
*
|
|
* @param instance an instance data structure.
|
|
* @param host a data collecting host.
|
|
*/
|
|
|
|
struct format_prometheus_label_callback {
|
|
struct instance *instance;
|
|
size_t count;
|
|
};
|
|
|
|
static int format_prometheus_label_callback(const char *name, const char *value, RRDLABEL_SRC ls, void *data) {
|
|
struct format_prometheus_label_callback *d = (struct format_prometheus_label_callback *)data;
|
|
|
|
if (!should_send_label(d->instance, ls)) return 0;
|
|
|
|
char k[PROMETHEUS_ELEMENT_MAX + 1];
|
|
char v[PROMETHEUS_ELEMENT_MAX + 1];
|
|
|
|
prometheus_name_copy(k, name, PROMETHEUS_ELEMENT_MAX);
|
|
prometheus_label_copy(v, value, PROMETHEUS_ELEMENT_MAX);
|
|
|
|
if (*k && *v) {
|
|
if (d->count > 0) buffer_strcat(d->instance->labels_buffer, ",");
|
|
buffer_sprintf(d->instance->labels_buffer, "%s=\"%s\"", k, v);
|
|
d->count++;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void format_host_labels_prometheus(struct instance *instance, RRDHOST *host)
|
|
{
|
|
if (unlikely(!sending_labels_configured(instance)))
|
|
return;
|
|
|
|
if (!instance->labels_buffer)
|
|
instance->labels_buffer = buffer_create(1024);
|
|
|
|
struct format_prometheus_label_callback tmp = {
|
|
.instance = instance,
|
|
.count = 0
|
|
};
|
|
rrdlabels_walkthrough_read(host->rrdlabels, format_prometheus_label_callback, &tmp);
|
|
}
|
|
|
|
struct host_variables_callback_options {
|
|
RRDHOST *host;
|
|
BUFFER *wb;
|
|
EXPORTING_OPTIONS exporting_options;
|
|
PROMETHEUS_OUTPUT_OPTIONS output_options;
|
|
const char *prefix;
|
|
const char *labels;
|
|
time_t now;
|
|
int host_header_printed;
|
|
char name[PROMETHEUS_VARIABLE_MAX + 1];
|
|
};
|
|
|
|
/**
|
|
* Print host variables.
|
|
*
|
|
* @param rv a variable.
|
|
* @param data callback options.
|
|
* @return Returns 1 if the chart can be sent, 0 otherwise.
|
|
*/
|
|
static int print_host_variables_callback(const DICTIONARY_ITEM *item __maybe_unused, void *rv_ptr __maybe_unused, void *data) {
|
|
const RRDVAR_ACQUIRED *rv = (const RRDVAR_ACQUIRED *)item;
|
|
|
|
struct host_variables_callback_options *opts = data;
|
|
|
|
if (rrdvar_flags(rv) & (RRDVAR_FLAG_CUSTOM_HOST_VAR | RRDVAR_FLAG_CUSTOM_CHART_VAR)) {
|
|
if (!opts->host_header_printed) {
|
|
opts->host_header_printed = 1;
|
|
|
|
if (opts->output_options & PROMETHEUS_OUTPUT_HELP) {
|
|
buffer_sprintf(opts->wb, "\n# COMMENT global host and chart variables\n");
|
|
}
|
|
}
|
|
|
|
NETDATA_DOUBLE value = rrdvar2number(rv);
|
|
if (isnan(value) || isinf(value)) {
|
|
if (opts->output_options & PROMETHEUS_OUTPUT_HELP)
|
|
buffer_sprintf(
|
|
opts->wb, "# COMMENT variable \"%s\" is %s. Skipped.\n", rrdvar_name(rv), (isnan(value)) ? "NAN" : "INF");
|
|
|
|
return 0;
|
|
}
|
|
|
|
char *label_pre = "";
|
|
char *label_post = "";
|
|
if (opts->labels && *opts->labels) {
|
|
label_pre = "{";
|
|
label_post = "}";
|
|
}
|
|
|
|
prometheus_name_copy(opts->name, rrdvar_name(rv), sizeof(opts->name));
|
|
|
|
if (opts->output_options & PROMETHEUS_OUTPUT_TIMESTAMPS)
|
|
buffer_sprintf(
|
|
opts->wb,
|
|
"%s_%s%s%s%s " NETDATA_DOUBLE_FORMAT " %llu\n",
|
|
opts->prefix,
|
|
opts->name,
|
|
label_pre,
|
|
opts->labels,
|
|
label_post,
|
|
value,
|
|
opts->now * 1000ULL);
|
|
else
|
|
buffer_sprintf(
|
|
opts->wb,
|
|
"%s_%s%s%s%s " NETDATA_DOUBLE_FORMAT "\n",
|
|
opts->prefix,
|
|
opts->name,
|
|
label_pre,
|
|
opts->labels,
|
|
label_post,
|
|
value);
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct gen_parameters {
|
|
const char *prefix;
|
|
char *context;
|
|
char *suffix;
|
|
|
|
char *chart;
|
|
char *dimension;
|
|
char *family;
|
|
char *labels;
|
|
|
|
PROMETHEUS_OUTPUT_OPTIONS output_options;
|
|
RRDSET *st;
|
|
RRDDIM *rd;
|
|
|
|
const char *relation;
|
|
const char *type;
|
|
};
|
|
|
|
/**
|
|
* Write an as-collected help comment to a buffer.
|
|
*
|
|
* @param wb the buffer to write the comment to.
|
|
* @param p parameters for generating the comment string.
|
|
* @param homogeneous a flag for homogeneous charts.
|
|
* @param prometheus_collector a flag for metrics from prometheus collector.
|
|
*/
|
|
static void generate_as_collected_prom_help(BUFFER *wb, struct gen_parameters *p, int homogeneous, int prometheus_collector)
|
|
{
|
|
buffer_sprintf(wb, "# COMMENT %s_%s", p->prefix, p->context);
|
|
|
|
if (!homogeneous)
|
|
buffer_sprintf(wb, "_%s", p->dimension);
|
|
|
|
buffer_sprintf(
|
|
wb,
|
|
"%s: chart \"%s\", context \"%s\", family \"%s\", dimension \"%s\", value * ",
|
|
p->suffix,
|
|
(p->output_options & PROMETHEUS_OUTPUT_NAMES && p->st->name) ? rrdset_name(p->st) : rrdset_id(p->st),
|
|
rrdset_context(p->st),
|
|
rrdset_family(p->st),
|
|
(p->output_options & PROMETHEUS_OUTPUT_NAMES && p->rd->name) ? rrddim_name(p->rd) : rrddim_id(p->rd));
|
|
|
|
if (prometheus_collector)
|
|
buffer_sprintf(wb, "1 / 1");
|
|
else
|
|
buffer_sprintf(wb, COLLECTED_NUMBER_FORMAT " / " COLLECTED_NUMBER_FORMAT, p->rd->multiplier, p->rd->divisor);
|
|
|
|
buffer_sprintf(wb, " %s %s (%s)\n", p->relation, rrdset_units(p->st), p->type);
|
|
}
|
|
|
|
/**
|
|
* Write an as-collected metric to a buffer.
|
|
*
|
|
* @param wb the buffer to write the metric to.
|
|
* @param p parameters for generating the metric string.
|
|
* @param homogeneous a flag for homogeneous charts.
|
|
* @param prometheus_collector a flag for metrics from prometheus collector.
|
|
*/
|
|
static void generate_as_collected_prom_metric(BUFFER *wb, struct gen_parameters *p, int homogeneous, int prometheus_collector)
|
|
{
|
|
buffer_sprintf(wb, "%s_%s", p->prefix, p->context);
|
|
|
|
if (!homogeneous)
|
|
buffer_sprintf(wb, "_%s", p->dimension);
|
|
|
|
buffer_sprintf(wb, "%s{chart=\"%s\",family=\"%s\"", p->suffix, p->chart, p->family);
|
|
|
|
if (homogeneous)
|
|
buffer_sprintf(wb, ",dimension=\"%s\"", p->dimension);
|
|
|
|
buffer_sprintf(wb, "%s} ", p->labels);
|
|
|
|
if (prometheus_collector)
|
|
buffer_sprintf(
|
|
wb,
|
|
NETDATA_DOUBLE_FORMAT,
|
|
(NETDATA_DOUBLE)p->rd->last_collected_value * (NETDATA_DOUBLE)p->rd->multiplier /
|
|
(NETDATA_DOUBLE)p->rd->divisor);
|
|
else
|
|
buffer_sprintf(wb, COLLECTED_NUMBER_FORMAT, p->rd->last_collected_value);
|
|
|
|
if (p->output_options & PROMETHEUS_OUTPUT_TIMESTAMPS)
|
|
buffer_sprintf(wb, " %llu\n", timeval_msec(&p->rd->last_collected_time));
|
|
else
|
|
buffer_sprintf(wb, "\n");
|
|
}
|
|
|
|
/**
|
|
* Write metrics in Prometheus format to a buffer.
|
|
*
|
|
* @param instance an instance data structure.
|
|
* @param host a data collecting host.
|
|
* @param filter_string a simple pattern filter.
|
|
* @param wb the buffer to fill with metrics.
|
|
* @param prefix a prefix for every metric.
|
|
* @param exporting_options options to configure what data is exported.
|
|
* @param allhosts set to 1 if host instance should be in the output for tags.
|
|
* @param output_options options to configure the format of the output.
|
|
*/
|
|
static void rrd_stats_api_v1_charts_allmetrics_prometheus(
|
|
struct instance *instance,
|
|
RRDHOST *host,
|
|
const char *filter_string,
|
|
BUFFER *wb,
|
|
const char *prefix,
|
|
EXPORTING_OPTIONS exporting_options,
|
|
int allhosts,
|
|
PROMETHEUS_OUTPUT_OPTIONS output_options)
|
|
{
|
|
SIMPLE_PATTERN *filter = simple_pattern_create(filter_string, NULL, SIMPLE_PATTERN_EXACT);
|
|
|
|
char hostname[PROMETHEUS_ELEMENT_MAX + 1];
|
|
prometheus_label_copy(hostname, rrdhost_hostname(host), PROMETHEUS_ELEMENT_MAX);
|
|
|
|
format_host_labels_prometheus(instance, host);
|
|
|
|
buffer_sprintf(
|
|
wb,
|
|
"netdata_info{instance=\"%s\",application=\"%s\",version=\"%s\"",
|
|
hostname,
|
|
rrdhost_program_name(host),
|
|
rrdhost_program_version(host));
|
|
|
|
if (instance->labels_buffer && *buffer_tostring(instance->labels_buffer)) {
|
|
buffer_sprintf(wb, ",%s", buffer_tostring(instance->labels_buffer));
|
|
}
|
|
|
|
if (output_options & PROMETHEUS_OUTPUT_TIMESTAMPS)
|
|
buffer_sprintf(wb, "} 1 %llu\n", now_realtime_usec() / USEC_PER_MS);
|
|
else
|
|
buffer_sprintf(wb, "} 1\n");
|
|
|
|
char labels[PROMETHEUS_LABELS_MAX + 1] = "";
|
|
if (allhosts) {
|
|
snprintfz(labels, PROMETHEUS_LABELS_MAX, ",instance=\"%s\"", hostname);
|
|
}
|
|
|
|
if (instance->labels_buffer)
|
|
buffer_flush(instance->labels_buffer);
|
|
|
|
// send custom variables set for the host
|
|
if (output_options & PROMETHEUS_OUTPUT_VARIABLES) {
|
|
|
|
struct host_variables_callback_options opts = {
|
|
.host = host,
|
|
.wb = wb,
|
|
.labels = (labels[0] == ',') ? &labels[1] : labels,
|
|
.exporting_options = exporting_options,
|
|
.output_options = output_options,
|
|
.prefix = prefix,
|
|
.now = now_realtime_sec(),
|
|
.host_header_printed = 0
|
|
};
|
|
|
|
rrdvar_walkthrough_read(host->rrdvars, print_host_variables_callback, &opts);
|
|
}
|
|
|
|
// for each chart
|
|
RRDSET *st;
|
|
rrdset_foreach_read(st, host) {
|
|
|
|
if (likely(can_send_rrdset(instance, st, filter))) {
|
|
char chart[PROMETHEUS_ELEMENT_MAX + 1];
|
|
char context[PROMETHEUS_ELEMENT_MAX + 1];
|
|
char family[PROMETHEUS_ELEMENT_MAX + 1];
|
|
char units[PROMETHEUS_ELEMENT_MAX + 1] = "";
|
|
|
|
prometheus_label_copy(chart, (output_options & PROMETHEUS_OUTPUT_NAMES && st->name) ? rrdset_name(st) : rrdset_id(st), PROMETHEUS_ELEMENT_MAX);
|
|
prometheus_label_copy(family, rrdset_family(st), PROMETHEUS_ELEMENT_MAX);
|
|
prometheus_name_copy(context, rrdset_context(st), PROMETHEUS_ELEMENT_MAX);
|
|
|
|
int as_collected = (EXPORTING_OPTIONS_DATA_SOURCE(exporting_options) == EXPORTING_SOURCE_DATA_AS_COLLECTED);
|
|
int homogeneous = 1;
|
|
int prometheus_collector = 0;
|
|
if (as_collected) {
|
|
if (rrdset_flag_check(st, RRDSET_FLAG_HOMOGENEOUS_CHECK))
|
|
rrdset_update_heterogeneous_flag(st);
|
|
|
|
if (rrdset_flag_check(st, RRDSET_FLAG_HETEROGENEOUS))
|
|
homogeneous = 0;
|
|
|
|
if (!strcmp(rrdset_module_name(st), "prometheus"))
|
|
prometheus_collector = 1;
|
|
} else {
|
|
if (EXPORTING_OPTIONS_DATA_SOURCE(exporting_options) == EXPORTING_SOURCE_DATA_AVERAGE &&
|
|
!(output_options & PROMETHEUS_OUTPUT_HIDEUNITS))
|
|
prometheus_units_copy(
|
|
units, rrdset_units(st), PROMETHEUS_ELEMENT_MAX, output_options & PROMETHEUS_OUTPUT_OLDUNITS);
|
|
}
|
|
|
|
if (unlikely(output_options & PROMETHEUS_OUTPUT_HELP))
|
|
buffer_sprintf(
|
|
wb,
|
|
"\n# COMMENT %s chart \"%s\", context \"%s\", family \"%s\", units \"%s\"\n",
|
|
(homogeneous) ? "homogeneous" : "heterogeneous",
|
|
(output_options & PROMETHEUS_OUTPUT_NAMES && st->name) ? rrdset_name(st) : rrdset_id(st),
|
|
rrdset_context(st),
|
|
rrdset_family(st),
|
|
rrdset_units(st));
|
|
|
|
// for each dimension
|
|
RRDDIM *rd;
|
|
rrddim_foreach_read(rd, st) {
|
|
if (rd->collections_counter && !rrddim_flag_check(rd, RRDDIM_FLAG_OBSOLETE)) {
|
|
char dimension[PROMETHEUS_ELEMENT_MAX + 1];
|
|
char *suffix = "";
|
|
|
|
if (as_collected) {
|
|
// we need as-collected / raw data
|
|
|
|
struct gen_parameters p;
|
|
p.prefix = prefix;
|
|
p.context = context;
|
|
p.suffix = suffix;
|
|
p.chart = chart;
|
|
p.dimension = dimension;
|
|
p.family = family;
|
|
p.labels = labels;
|
|
p.output_options = output_options;
|
|
p.st = st;
|
|
p.rd = rd;
|
|
|
|
if (unlikely(rd->last_collected_time.tv_sec < instance->after))
|
|
continue;
|
|
|
|
p.type = "gauge";
|
|
p.relation = "gives";
|
|
if (rd->algorithm == RRD_ALGORITHM_INCREMENTAL ||
|
|
rd->algorithm == RRD_ALGORITHM_PCENT_OVER_DIFF_TOTAL) {
|
|
p.type = "counter";
|
|
p.relation = "delta gives";
|
|
if (!prometheus_collector)
|
|
p.suffix = "_total";
|
|
}
|
|
|
|
if (homogeneous) {
|
|
// all the dimensions of the chart, has the same algorithm, multiplier and divisor
|
|
// we add all dimensions as labels
|
|
|
|
prometheus_label_copy(
|
|
dimension,
|
|
(output_options & PROMETHEUS_OUTPUT_NAMES && rd->name) ? rrddim_name(rd) : rrddim_id(rd),
|
|
PROMETHEUS_ELEMENT_MAX);
|
|
|
|
if (unlikely(output_options & PROMETHEUS_OUTPUT_HELP))
|
|
generate_as_collected_prom_help(wb, &p, homogeneous, prometheus_collector);
|
|
|
|
if (unlikely(output_options & PROMETHEUS_OUTPUT_TYPES))
|
|
buffer_sprintf(wb, "# TYPE %s_%s%s %s\n", prefix, context, suffix, p.type);
|
|
|
|
generate_as_collected_prom_metric(wb, &p, homogeneous, prometheus_collector);
|
|
}
|
|
else {
|
|
// the dimensions of the chart, do not have the same algorithm, multiplier or divisor
|
|
// we create a metric per dimension
|
|
|
|
prometheus_name_copy(
|
|
dimension,
|
|
(output_options & PROMETHEUS_OUTPUT_NAMES && rd->name) ? rrddim_name(rd) : rrddim_id(rd),
|
|
PROMETHEUS_ELEMENT_MAX);
|
|
|
|
if (unlikely(output_options & PROMETHEUS_OUTPUT_HELP))
|
|
generate_as_collected_prom_help(wb, &p, homogeneous, prometheus_collector);
|
|
|
|
if (unlikely(output_options & PROMETHEUS_OUTPUT_TYPES))
|
|
buffer_sprintf(
|
|
wb, "# TYPE %s_%s_%s%s %s\n", prefix, context, dimension, suffix, p.type);
|
|
|
|
generate_as_collected_prom_metric(wb, &p, homogeneous, prometheus_collector);
|
|
}
|
|
}
|
|
else {
|
|
// we need average or sum of the data
|
|
|
|
time_t first_time = instance->after;
|
|
time_t last_time = instance->before;
|
|
NETDATA_DOUBLE value = exporting_calculate_value_from_stored_data(instance, rd, &last_time);
|
|
|
|
if (!isnan(value) && !isinf(value)) {
|
|
if (EXPORTING_OPTIONS_DATA_SOURCE(exporting_options) == EXPORTING_SOURCE_DATA_AVERAGE)
|
|
suffix = "_average";
|
|
else if (EXPORTING_OPTIONS_DATA_SOURCE(exporting_options) == EXPORTING_SOURCE_DATA_SUM)
|
|
suffix = "_sum";
|
|
|
|
prometheus_label_copy(
|
|
dimension,
|
|
(output_options & PROMETHEUS_OUTPUT_NAMES && rd->name) ? rrddim_name(rd) : rrddim_id(rd),
|
|
PROMETHEUS_ELEMENT_MAX);
|
|
|
|
if (unlikely(output_options & PROMETHEUS_OUTPUT_HELP))
|
|
buffer_sprintf(
|
|
wb,
|
|
"# COMMENT %s_%s%s%s: dimension \"%s\", value is %s, gauge, dt %llu to %llu inclusive\n",
|
|
prefix,
|
|
context,
|
|
units,
|
|
suffix,
|
|
(output_options & PROMETHEUS_OUTPUT_NAMES && rd->name) ? rrddim_name(rd) : rrddim_id(rd),
|
|
rrdset_units(st),
|
|
(unsigned long long)first_time,
|
|
(unsigned long long)last_time);
|
|
|
|
if (unlikely(output_options & PROMETHEUS_OUTPUT_TYPES))
|
|
buffer_sprintf(wb, "# TYPE %s_%s%s%s gauge\n", prefix, context, units, suffix);
|
|
|
|
if (output_options & PROMETHEUS_OUTPUT_TIMESTAMPS)
|
|
buffer_sprintf(
|
|
wb,
|
|
"%s_%s%s%s{chart=\"%s\",family=\"%s\",dimension=\"%s\"%s} " NETDATA_DOUBLE_FORMAT
|
|
" %llu\n",
|
|
prefix,
|
|
context,
|
|
units,
|
|
suffix,
|
|
chart,
|
|
family,
|
|
dimension,
|
|
labels,
|
|
value,
|
|
last_time * MSEC_PER_SEC);
|
|
else
|
|
buffer_sprintf(
|
|
wb,
|
|
"%s_%s%s%s{chart=\"%s\",family=\"%s\",dimension=\"%s\"%s} " NETDATA_DOUBLE_FORMAT
|
|
"\n",
|
|
prefix,
|
|
context,
|
|
units,
|
|
suffix,
|
|
chart,
|
|
family,
|
|
dimension,
|
|
labels,
|
|
value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
rrddim_foreach_done(rd);
|
|
}
|
|
}
|
|
rrdset_foreach_done(st);
|
|
|
|
simple_pattern_free(filter);
|
|
}
|
|
|
|
/**
|
|
* Get the last time time when a server accessed Netdata. Write information about an API request to a buffer.
|
|
*
|
|
* @param instance an instance data structure.
|
|
* @param host a data collecting host.
|
|
* @param wb the buffer to write to.
|
|
* @param exporting_options options to configure what data is exported.
|
|
* @param server the name of a Prometheus server..
|
|
* @param now actual time.
|
|
* @param output_options options to configure the format of the output.
|
|
* @return Returns the last time when the server accessed Netdata.
|
|
*/
|
|
static inline time_t prometheus_preparation(
|
|
struct instance *instance,
|
|
RRDHOST *host,
|
|
BUFFER *wb,
|
|
EXPORTING_OPTIONS exporting_options,
|
|
const char *server,
|
|
time_t now,
|
|
PROMETHEUS_OUTPUT_OPTIONS output_options)
|
|
{
|
|
#ifndef UNIT_TESTING
|
|
analytics_log_prometheus();
|
|
#endif
|
|
if (!server || !*server)
|
|
server = "default";
|
|
|
|
time_t after = prometheus_server_last_access(server, host, now);
|
|
|
|
int first_seen = 0;
|
|
if (!after) {
|
|
after = now - instance->config.update_every;
|
|
first_seen = 1;
|
|
}
|
|
|
|
if (after > now) {
|
|
// oops! this should never happen
|
|
after = now - instance->config.update_every;
|
|
}
|
|
|
|
if (output_options & PROMETHEUS_OUTPUT_HELP) {
|
|
char *mode;
|
|
if (EXPORTING_OPTIONS_DATA_SOURCE(exporting_options) == EXPORTING_SOURCE_DATA_AS_COLLECTED)
|
|
mode = "as collected";
|
|
else if (EXPORTING_OPTIONS_DATA_SOURCE(exporting_options) == EXPORTING_SOURCE_DATA_AVERAGE)
|
|
mode = "average";
|
|
else if (EXPORTING_OPTIONS_DATA_SOURCE(exporting_options) == EXPORTING_SOURCE_DATA_SUM)
|
|
mode = "sum";
|
|
else
|
|
mode = "unknown";
|
|
|
|
buffer_sprintf(
|
|
wb,
|
|
"# COMMENT netdata \"%s\" to %sprometheus \"%s\", source \"%s\", last seen %lu %s, time range %lu to %lu\n\n",
|
|
rrdhost_hostname(host),
|
|
(first_seen) ? "FIRST SEEN " : "",
|
|
server,
|
|
mode,
|
|
(unsigned long)((first_seen) ? 0 : (now - after)),
|
|
(first_seen) ? "never" : "seconds ago",
|
|
(unsigned long)after,
|
|
(unsigned long)now);
|
|
}
|
|
|
|
return after;
|
|
}
|
|
|
|
/**
|
|
* Write metrics and auxiliary information for one host to a buffer.
|
|
*
|
|
* @param host a data collecting host.
|
|
* @param filter_string a simple pattern filter.
|
|
* @param wb the buffer to write to.
|
|
* @param server the name of a Prometheus server.
|
|
* @param prefix a prefix for every metric.
|
|
* @param exporting_options options to configure what data is exported.
|
|
* @param output_options options to configure the format of the output.
|
|
*/
|
|
void rrd_stats_api_v1_charts_allmetrics_prometheus_single_host(
|
|
RRDHOST *host,
|
|
const char *filter_string,
|
|
BUFFER *wb,
|
|
const char *server,
|
|
const char *prefix,
|
|
EXPORTING_OPTIONS exporting_options,
|
|
PROMETHEUS_OUTPUT_OPTIONS output_options)
|
|
{
|
|
if (unlikely(!prometheus_exporter_instance || !prometheus_exporter_instance->config.initialized))
|
|
return;
|
|
|
|
prometheus_exporter_instance->before = now_realtime_sec();
|
|
|
|
// we start at the point we had stopped before
|
|
prometheus_exporter_instance->after = prometheus_preparation(
|
|
prometheus_exporter_instance,
|
|
host,
|
|
wb,
|
|
exporting_options,
|
|
server,
|
|
prometheus_exporter_instance->before,
|
|
output_options);
|
|
|
|
rrd_stats_api_v1_charts_allmetrics_prometheus(
|
|
prometheus_exporter_instance, host, filter_string, wb, prefix, exporting_options, 0, output_options);
|
|
}
|
|
|
|
/**
|
|
* Write metrics and auxiliary information for all hosts to a buffer.
|
|
*
|
|
* @param host a data collecting host.
|
|
* @param filter_string a simple pattern filter.
|
|
* @param wb the buffer to write to.
|
|
* @param server the name of a Prometheus server.
|
|
* @param prefix a prefix for every metric.
|
|
* @param exporting_options options to configure what data is exported.
|
|
* @param output_options options to configure the format of the output.
|
|
*/
|
|
void rrd_stats_api_v1_charts_allmetrics_prometheus_all_hosts(
|
|
RRDHOST *host,
|
|
const char *filter_string,
|
|
BUFFER *wb,
|
|
const char *server,
|
|
const char *prefix,
|
|
EXPORTING_OPTIONS exporting_options,
|
|
PROMETHEUS_OUTPUT_OPTIONS output_options)
|
|
{
|
|
if (unlikely(!prometheus_exporter_instance || !prometheus_exporter_instance->config.initialized))
|
|
return;
|
|
|
|
prometheus_exporter_instance->before = now_realtime_sec();
|
|
|
|
// we start at the point we had stopped before
|
|
prometheus_exporter_instance->after = prometheus_preparation(
|
|
prometheus_exporter_instance,
|
|
host,
|
|
wb,
|
|
exporting_options,
|
|
server,
|
|
prometheus_exporter_instance->before,
|
|
output_options);
|
|
|
|
rrd_rdlock();
|
|
rrdhost_foreach_read(host)
|
|
{
|
|
rrd_stats_api_v1_charts_allmetrics_prometheus(
|
|
prometheus_exporter_instance, host, filter_string, wb, prefix, exporting_options, 1, output_options);
|
|
}
|
|
rrd_unlock();
|
|
}
|