X-Git-Url: https://git.octo.it/?a=blobdiff_plain;f=src%2Fwrite_prometheus.c;h=526499849c2f27a5040f8d8b7febc00a107c9494;hb=5ff74d56067ac64db801df5184eb8b97f4b2b645;hp=4a9761a0ac9650746b746819a444d0a69b87165a;hpb=649a826ba0792bf4f48968c879011c6e1bcbc64a;p=collectd.git diff --git a/src/write_prometheus.c b/src/write_prometheus.c index 4a9761a0..52649984 100644 --- a/src/write_prometheus.c +++ b/src/write_prometheus.c @@ -37,7 +37,7 @@ #include #ifndef PROMETHEUS_DEFAULT_STALENESS_DELTA -#define PROMETHEUS_DEFAULT_STALENESS_DELTA TIME_T_TO_CDTIME_T(300) +#define PROMETHEUS_DEFAULT_STALENESS_DELTA TIME_T_TO_CDTIME_T_STATIC(300) #endif #define VARINT_UINT32_BYTES 5 @@ -98,6 +98,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 +146,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; @@ -210,8 +253,13 @@ static int http_handler(void *cls, struct MHD_Connection *connection, else format_text(buffer); +#if defined(MHD_VERSION) && MHD_VERSION >= 0x00090500 + struct MHD_Response *res = MHD_create_response_from_buffer( + simple.len, simple.data, MHD_RESPMEM_MUST_COPY); +#else struct MHD_Response *res = MHD_create_response_from_data( simple.len, simple.data, /* must_free = */ 0, /* must_copy = */ 1); +#endif MHD_add_response_header(res, MHD_HTTP_HEADER_CONTENT_TYPE, want_proto ? CONTENT_TYPE_PROTO : CONTENT_TYPE_TEXT); @@ -297,17 +345,37 @@ static int metric_cmp(void const *a, void const *b) { return 1; /* Prometheus does not care about the order of labels. All labels in this - * plugin are created by metric_add_labels(), though, and therefore always + * plugin are created by METRIC_ADD_LABELS(), though, and therefore always * appear in the same order. We take advantage of this and simplify the check - * by making sure all labels are the same in each position. */ + * by making sure all labels are the same in each position. + * + * We also only need to check the label values, because the label names are + * the same for all metrics in a metric family. + * + * 3 labels: + * [0] $plugin="$plugin_instance" => $plugin is the same within a family + * [1] type="$type_instance" => "type" is a static string + * [2] instance="$host" => "instance" is a static string + * + * 2 labels, variant 1: + * [0] $plugin="$plugin_instance" => $plugin is the same within a family + * [1] instance="$host" => "instance" is a static string + * + * 2 labels, variant 2: + * [0] $plugin="$type_instance" => $plugin is the same within a family + * [1] instance="$host" => "instance" is a static string + * + * 1 label: + * [1] instance="$host" => "instance" is a static string + */ for (size_t i = 0; i < m_a->n_label; i++) { - int status = strcmp(m_a->label[i]->name, m_b->label[i]->name); + int status = strcmp(m_a->label[i]->value, m_b->label[i]->value); if (status != 0) return status; - status = strcmp(m_a->label[i]->value, m_b->label[i]->value); - if (status != 0) - return status; +#if COLLECT_DEBUG + assert(strcmp(m_a->label[i]->name, m_b->label[i]->name) == 0); +#endif } return 0; @@ -478,9 +546,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; @@ -526,8 +599,8 @@ static int metric_family_update(Io__Prometheus__Client__MetricFamily *fam, if (m == NULL) return -1; - return metric_update(m, vl->values[ds_index], ds->ds[ds_index].type, vl->time, - vl->interval); + return metric_update(m, vl->values[ds_index], ds->ds[ds_index].type, + vl->time, vl->interval); } /* metric_family_destroy frees the memory used by a metric family. */ @@ -611,8 +684,8 @@ static char *metric_family_name(data_set_t const *ds, value_list_t const *vl, /* metric_family_get looks up the matching metric family, allocating it if * necessary. */ static Io__Prometheus__Client__MetricFamily * -metric_family_get(data_set_t const *ds, value_list_t const *vl, - size_t ds_index) { +metric_family_get(data_set_t const *ds, value_list_t const *vl, size_t ds_index, + _Bool allocate) { char *name = metric_family_name(ds, vl, ds_index); if (name == NULL) { ERROR("write_prometheus plugin: Allocating metric family name failed."); @@ -626,6 +699,11 @@ metric_family_get(data_set_t const *ds, value_list_t const *vl, return fam; } + if (!allocate) { + sfree(name); + return NULL; + } + fam = metric_family_create(name, ds, vl, ds_index); if (fam == NULL) { ERROR("write_prometheus plugin: Allocating metric family failed."); @@ -707,7 +785,8 @@ static int prom_write(data_set_t const *ds, value_list_t const *vl, pthread_mutex_lock(&metrics_lock); for (size_t i = 0; i < ds->ds_num; i++) { - Io__Prometheus__Client__MetricFamily *fam = metric_family_get(ds, vl, i); + Io__Prometheus__Client__MetricFamily *fam = + metric_family_get(ds, vl, i, /* allocate = */ 1); if (fam == NULL) continue; @@ -733,7 +812,8 @@ static int prom_missing(value_list_t const *vl, pthread_mutex_lock(&metrics_lock); for (size_t i = 0; i < ds->ds_num; i++) { - Io__Prometheus__Client__MetricFamily *fam = metric_family_get(ds, vl, i); + Io__Prometheus__Client__MetricFamily *fam = + metric_family_get(ds, vl, i, /* allocate = */ 0); if (fam == NULL) continue; @@ -742,6 +822,7 @@ static int prom_missing(value_list_t const *vl, ERROR("write_prometheus plugin: Deleting a metric in family \"%s\" " "failed with status %d", fam->name, status); + continue; } @@ -794,5 +875,3 @@ void module_register() { /* user data = */ NULL); plugin_register_shutdown("write_prometheus", prom_shutdown); } - -/* vim: set sw=2 sts=2 et fdm=marker : */