X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=src%2Fwrite_prometheus.c;h=ba186a7e73f1ea9b0d0f2d7eec1a99c1b7aedd70;hb=2c78bdf94c0fdd6240d5d202cf5aba12ac8c5a20;hp=fe7a5b5996edab97ee12b34d08558d6a26c522ed;hpb=604607d3463fbe13eac252308f8a2497fb190167;p=collectd.git diff --git a/src/write_prometheus.c b/src/write_prometheus.c index fe7a5b59..ba186a7e 100644 --- a/src/write_prometheus.c +++ b/src/write_prometheus.c @@ -36,6 +36,10 @@ #include +#include +#include +#include + #ifndef PROMETHEUS_DEFAULT_STALENESS_DELTA #define PROMETHEUS_DEFAULT_STALENESS_DELTA TIME_T_TO_CDTIME_T_STATIC(300) #endif @@ -98,6 +102,43 @@ static void format_protobuf(ProtobufCBuffer *buffer) { pthread_mutex_unlock(&metrics_lock); } +static char const *escape_label_value(char *buffer, size_t buffer_size, + char const *value) { + /* shortcut for values that don't need escaping. */ + if (strpbrk(value, "\n\"\\") == NULL) + return value; + + size_t value_len = strlen(value); + size_t buffer_len = 0; + + for (size_t i = 0; i < value_len; i++) { + switch (value[i]) { + case '\n': + case '"': + case '\\': + if ((buffer_size - buffer_len) < 3) { + break; + } + buffer[buffer_len] = '\\'; + buffer[buffer_len + 1] = (value[i] == '\n') ? 'n' : value[i]; + buffer_len += 2; + break; + + default: + if ((buffer_size - buffer_len) < 2) { + break; + } + buffer[buffer_len] = value[i]; + buffer_len++; + break; + } + } + + assert(buffer_len < buffer_size); + buffer[buffer_len] = 0; + return buffer; +} + /* format_labels formats a metric's labels in Prometheus-compatible format. This * format looks like this: * @@ -109,16 +150,22 @@ static char *format_labels(char *buffer, size_t buffer_size, assert(m->n_label >= 1); assert(m->n_label <= 3); -#define LABEL_BUFFER_SIZE (2 * DATA_MAX_NAME_LEN + 4) +#define LABEL_KEY_SIZE DATA_MAX_NAME_LEN +#define LABEL_VALUE_SIZE (2 * DATA_MAX_NAME_LEN - 1) +#define LABEL_BUFFER_SIZE (LABEL_KEY_SIZE + LABEL_VALUE_SIZE + 4) char *labels[3] = { (char[LABEL_BUFFER_SIZE]){0}, (char[LABEL_BUFFER_SIZE]){0}, (char[LABEL_BUFFER_SIZE]){0}, }; - for (size_t i = 0; i < m->n_label; i++) + /* N.B.: the label *names* are hard-coded by this plugin and therefore we + * know that they are sane. */ + for (size_t i = 0; i < m->n_label; i++) { + char value[LABEL_VALUE_SIZE]; ssnprintf(labels[i], LABEL_BUFFER_SIZE, "%s=\"%s\"", m->label[i]->name, - m->label[i]->value); + escape_label_value(value, sizeof(value), m->label[i]->value)); + } strjoin(buffer, buffer_size, labels, m->n_label, ","); return buffer; @@ -503,9 +550,14 @@ metric_family_delete_metric(Io__Prometheus__Client__MetricFamily *fam, ((fam->n_metric - 1) - i) * sizeof(fam->metric[i])); fam->n_metric--; + if (fam->n_metric == 0) { + sfree(fam->metric); + return 0; + } + Io__Prometheus__Client__Metric **tmp = realloc(fam->metric, fam->n_metric * sizeof(*fam->metric)); - if ((tmp != NULL) || (fam->n_metric == 0)) + if (tmp != NULL) fam->metric = tmp; return 0; @@ -651,8 +703,10 @@ metric_family_get(data_set_t const *ds, value_list_t const *vl, size_t ds_index, return fam; } - if (!allocate) + if (!allocate) { + sfree(name); return NULL; + } fam = metric_family_create(name, ds, vl, ds_index); if (fam == NULL) { @@ -677,6 +731,110 @@ metric_family_get(data_set_t const *ds, value_list_t const *vl, size_t ds_index, } /* }}} */ +static void prom_logger(__attribute__((unused)) void *arg, char const *fmt, + va_list ap) { + /* {{{ */ + char errbuf[1024]; + vsnprintf(errbuf, sizeof(errbuf), fmt, ap); + + ERROR("write_prometheus plugin: %s", errbuf); +} /* }}} prom_logger */ + +#if MHD_VERSION >= 0x00090000 +static int prom_open_socket(int addrfamily) { + /* {{{ */ + char service[NI_MAXSERV]; + snprintf(service, sizeof(service), "%hu", httpd_port); + + struct addrinfo *res; + int status = getaddrinfo(NULL, service, + &(struct addrinfo){ + .ai_flags = AI_PASSIVE | AI_ADDRCONFIG, + .ai_family = addrfamily, + .ai_socktype = SOCK_STREAM, + }, + &res); + if (status != 0) { + return -1; + } + + int fd = -1; + for (struct addrinfo *ai = res; ai != NULL; ai = ai->ai_next) { + fd = socket(ai->ai_family, ai->ai_socktype | SOCK_CLOEXEC, 0); + if (fd == -1) + continue; + + int tmp = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &tmp, sizeof(tmp)) != 0) { + char errbuf[1024]; + WARNING("write_prometheus: setsockopt(SO_REUSEADDR) failed: %s", + sstrerror(errno, errbuf, sizeof(errbuf))); + close(fd); + fd = -1; + continue; + } + + if (bind(fd, ai->ai_addr, ai->ai_addrlen) != 0) { + close(fd); + fd = -1; + continue; + } + + if (listen(fd, /* backlog = */ 16) != 0) { + close(fd); + fd = -1; + continue; + } + + break; + } + + freeaddrinfo(res); + + return fd; +} /* }}} int prom_open_socket */ + +static struct MHD_Daemon *prom_start_daemon() { + /* {{{ */ + int fd = prom_open_socket(PF_INET6); + if (fd == -1) + fd = prom_open_socket(PF_INET); + if (fd == -1) { + ERROR("write_prometheus plugin: Opening a listening socket failed."); + return NULL; + } + + struct MHD_Daemon *d = MHD_start_daemon( + MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG, httpd_port, + /* MHD_AcceptPolicyCallback = */ NULL, + /* MHD_AcceptPolicyCallback arg = */ NULL, http_handler, NULL, + MHD_OPTION_LISTEN_SOCKET, fd, MHD_OPTION_EXTERNAL_LOGGER, prom_logger, + NULL, MHD_OPTION_END); + if (d == NULL) { + ERROR("write_prometheus plugin: MHD_start_daemon() failed."); + close(fd); + return NULL; + } + + return d; +} /* }}} struct MHD_Daemon *prom_start_daemon */ +#else /* if MHD_VERSION < 0x00090000 */ +static struct MHD_Daemon *prom_start_daemon() { + /* {{{ */ + struct MHD_Daemon *d = MHD_start_daemon( + MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DEBUG, httpd_port, + /* MHD_AcceptPolicyCallback = */ NULL, + /* MHD_AcceptPolicyCallback arg = */ NULL, http_handler, NULL, + MHD_OPTION_EXTERNAL_LOGGER, prom_logger, NULL, MHD_OPTION_END); + if (d == NULL) { + ERROR("write_prometheus plugin: MHD_start_daemon() failed."); + return NULL; + } + + return d; +} /* }}} struct MHD_Daemon *prom_start_daemon */ +#endif + /* * collectd callbacks */ @@ -710,15 +868,7 @@ static int prom_init() { } if (httpd == NULL) { - unsigned int flags = MHD_USE_THREAD_PER_CONNECTION; -#if MHD_VERSION >= 0x00093300 - flags |= MHD_USE_DUAL_STACK; -#endif - - httpd = MHD_start_daemon(flags, httpd_port, - /* MHD_AcceptPolicyCallback = */ NULL, - /* MHD_AcceptPolicyCallback arg = */ NULL, - http_handler, NULL, MHD_OPTION_END); + httpd = prom_start_daemon(); if (httpd == NULL) { ERROR("write_prometheus plugin: MHD_start_daemon() failed."); return -1;