From: Florian Forster Date: Wed, 6 Dec 2023 07:51:31 +0000 (+0100) Subject: Add resource labels to `metric_t`. X-Git-Tag: 6.0.0-rc0~37^2~6 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=46cb3d770dd76dc1ade61c5dab3890c425e9602b;p=thirdparty%2Fcollectd.git Add resource labels to `metric_t`. --- diff --git a/Makefile.am b/Makefile.am index 8dd6a1a82..9e610e337 100644 --- a/Makefile.am +++ b/Makefile.am @@ -243,6 +243,8 @@ collectd_SOURCES = \ src/daemon/globals.h \ src/daemon/plugin.c \ src/daemon/plugin.h \ + src/daemon/resource.c \ + src/daemon/resource.h \ src/daemon/types_list.c \ src/daemon/types_list.h \ src/daemon/utils_cache.c \ diff --git a/src/daemon/metric.c b/src/daemon/metric.c index 344bb4c53..44e7659f7 100644 --- a/src/daemon/metric.c +++ b/src/daemon/metric.c @@ -38,9 +38,13 @@ #define VALID_LABEL_CHARS \ "abcdefghijklmnopqrstuvwxyz" \ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ - "0123456789_" + "0123456789_.-" + /* Metric names must match the regex `[a-zA-Z_:][a-zA-Z0-9_:]*` */ -#define VALID_NAME_CHARS VALID_LABEL_CHARS ":" +// instrument-name = ALPHA 0*254 ("_" / "." / "-" / "/" / ALPHA / DIGIT) +#define VALID_NAME_CHARS VALID_LABEL_CHARS "/" + +#define RESOURCE_LABEL_PREFIX "resource:" int value_marshal_text(strbuf_t *buf, value_t v, metric_type_t type) { switch (type) { @@ -60,7 +64,8 @@ static int label_pair_compare(void const *a, void const *b) { ((label_pair_t const *)b)->name); } -static label_pair_t *label_set_read(label_set_t labels, char const *name) { +static label_pair_t *label_set_read(label_set_t const *labels, + char const *name) { if (name == NULL) { errno = EINVAL; return NULL; @@ -73,8 +78,8 @@ static label_pair_t *label_set_read(label_set_t labels, char const *name) { .name = name, }; - label_pair_t *ret = bsearch(&label, labels.ptr, labels.num, - sizeof(*labels.ptr), label_pair_compare); + label_pair_t *ret = bsearch(&label, labels->ptr, labels->num, + sizeof(*labels->ptr), label_pair_compare); if (ret == NULL) { errno = ENOENT; return NULL; @@ -83,8 +88,7 @@ static label_pair_t *label_set_read(label_set_t labels, char const *name) { return ret; } -static int label_set_create(label_set_t *labels, char const *name, - char const *value) { +int label_set_add(label_set_t *labels, char const *name, char const *value) { if ((labels == NULL) || (name == NULL) || (value == NULL)) { return EINVAL; } @@ -99,7 +103,7 @@ static int label_set_create(label_set_t *labels, char const *name, return EINVAL; } - if (label_set_read(*labels, name) != NULL) { + if (label_set_read(labels, name) != NULL) { return EEXIST; } errno = 0; @@ -161,7 +165,41 @@ static int label_set_delete(label_set_t *labels, label_pair_t *elem) { return 0; } -static void label_set_reset(label_set_t *labels) { +static int label_set_update(label_set_t *labels, char const *name, + char const *value) { + if ((labels == NULL) || (name == NULL)) { + return EINVAL; + } + + label_pair_t *label = label_set_read(labels, name); + if ((label == NULL) && (errno != ENOENT)) { + return errno; + } + errno = 0; + + if (label == NULL) { + if ((value == NULL) || strlen(value) == 0) { + return 0; + } + return label_set_add(labels, name, value); + } + + if ((value == NULL) || strlen(value) == 0) { + return label_set_delete(labels, label); + } + + char *new_value = strdup(value); + if (new_value == NULL) { + return errno; + } + + free(label->value); + label->value = new_value; + + return 0; +} + +void label_set_reset(label_set_t *labels) { if (labels == NULL) { return; } @@ -175,7 +213,7 @@ static void label_set_reset(label_set_t *labels) { labels->num = 0; } -static int label_set_clone(label_set_t *dest, label_set_t src) { +int label_set_clone(label_set_t *dest, label_set_t src) { if (src.num == 0) { return 0; } @@ -207,6 +245,7 @@ int metric_reset(metric_t *m) { } label_set_reset(&m->label); + label_set_reset(&m->resource); meta_data_destroy(m->meta); memset(m, 0, sizeof(*m)); @@ -214,63 +253,62 @@ int metric_reset(metric_t *m) { return 0; } +static int format_label_set(strbuf_t *buf, label_set_t const *labels, + char const *prefix, bool first_label) { + int status = 0; + for (size_t i = 0; i < labels->num; i++) { + if (!first_label) { + status = status || strbuf_print(buf, ","); + } + + status = status || strbuf_print(buf, prefix); + status = status || strbuf_print(buf, labels->ptr[i].name); + status = status || strbuf_print(buf, "=\""); + status = status || strbuf_print_escaped(buf, labels->ptr[i].value, + "\\\"\n\r\t", '\\'); + status = status || strbuf_print(buf, "\""); + first_label = false; + } + return status; +} + int metric_identity(strbuf_t *buf, metric_t const *m) { if ((buf == NULL) || (m == NULL) || (m->family == NULL)) { return EINVAL; } int status = strbuf_print(buf, m->family->name); - if (m->label.num == 0) { + if (m->resource.num == 0 && m->label.num == 0) { return status; } status = status || strbuf_print(buf, "{"); - for (size_t i = 0; i < m->label.num; i++) { - if (i != 0) { - status = status || strbuf_print(buf, ","); - } - status = status || strbuf_print(buf, m->label.ptr[i].name); - status = status || strbuf_print(buf, "=\""); - status = status || strbuf_print_escaped(buf, m->label.ptr[i].value, - "\\\"\n\r\t", '\\'); - status = status || strbuf_print(buf, "\""); - } + bool first_label = true; + status = status || format_label_set(buf, &m->resource, RESOURCE_LABEL_PREFIX, + first_label); + + first_label = (m->resource.num == 0); + status = status || format_label_set(buf, &m->label, "", first_label); return status || strbuf_print(buf, "}"); } int metric_label_set(metric_t *m, char const *name, char const *value) { - if ((m == NULL) || (name == NULL)) { + if (m == NULL) { return EINVAL; } - label_pair_t *label = label_set_read(m->label, name); - if ((label == NULL) && (errno != ENOENT)) { - return errno; - } - errno = 0; - - if (label == NULL) { - if ((value == NULL) || strlen(value) == 0) { - return 0; - } - return label_set_create(&m->label, name, value); - } - - if ((value == NULL) || strlen(value) == 0) { - return label_set_delete(&m->label, label); - } + return label_set_update(&m->label, name, value); +} - char *new_value = strdup(value); - if (new_value == NULL) { - return errno; +int metric_resource_attribute_update(metric_t *m, char const *name, + char const *value) { + if (m == NULL) { + return EINVAL; } - free(label->value); - label->value = new_value; - - return 0; + return label_set_update(&m->resource, name, value); } char const *metric_label_get(metric_t const *m, char const *name) { @@ -279,7 +317,7 @@ char const *metric_label_get(metric_t const *m, char const *name) { return NULL; } - label_pair_t *set = label_set_read(m->label, name); + label_pair_t *set = label_set_read(&m->label, name); if (set == NULL) { return NULL; } @@ -355,7 +393,13 @@ static int metric_list_clone(metric_list_t *dest, metric_list_t src, .interval = src.ptr[i].interval, }; - int status = label_set_clone(&ret.ptr[i].label, src.ptr[i].label); + int status = label_set_clone(&ret.ptr[i].resource, src.ptr[i].resource); + if (status != 0) { + metric_list_reset(&ret); + return status; + } + + status = label_set_clone(&ret.ptr[i].label, src.ptr[i].label); if (status != 0) { metric_list_reset(&ret); return status; @@ -545,6 +589,12 @@ static int metric_family_unmarshal_identity(metric_family_t *fam, while ((ptr[0] == '{') || (ptr[0] == ',')) { ptr++; + bool is_resource_label = + strncmp(ptr, RESOURCE_LABEL_PREFIX, strlen(RESOURCE_LABEL_PREFIX)) == 0; + if (is_resource_label) { + ptr += strlen(RESOURCE_LABEL_PREFIX); + } + size_t key_len = strspn(ptr, VALID_LABEL_CHARS); if (key_len == 0) { ret = EINVAL; @@ -572,7 +622,11 @@ static int metric_family_unmarshal_identity(metric_family_t *fam, /* one metric is added to the family by metric_family_unmarshal_text. */ assert(fam->metric.num >= 1); - status = metric_label_set(m, key, value.ptr); + if (is_resource_label) { + status = label_set_add(&m->resource, key, value.ptr); + } else { + status = metric_label_set(m, key, value.ptr); + } STRBUF_DESTROY(value); if (status != 0) { ret = status; diff --git a/src/daemon/metric.h b/src/daemon/metric.h index dbd24e08a..b12dabc2d 100644 --- a/src/daemon/metric.h +++ b/src/daemon/metric.h @@ -70,6 +70,19 @@ typedef struct { size_t num; } label_set_t; +/* label_set_clone copies all the laels in src into dest. If dest contains + * any labels prior to calling label_set_clone, the associated memory is + * leaked. */ +int label_set_clone(label_set_t *dest, label_set_t src); + +/* label_set_add adds a label to the label set. If a label with name already + * exists, EEXIST is returned. The set of labels is sorted by label name. */ +int label_set_add(label_set_t *labels, char const *name, char const *value); + +/* label_set_reset frees all the memory referenced by the label set and + * initializes the label set to zero. */ +void label_set_reset(label_set_t *labels); + /* * Metric */ @@ -83,10 +96,11 @@ typedef struct { metric_family_t *family; /* backreference for family->name and family->type */ label_set_t label; + label_set_t resource; + value_t value; cdtime_t time; /* TODO(octo): use ms or µs instead? */ cdtime_t interval; - /* TODO(octo): target labels */ meta_data_t *meta; } metric_t; @@ -108,6 +122,12 @@ metric_t *metric_parse_identity(char const *s); * label that does not exist is *not* an error. */ int metric_label_set(metric_t *m, char const *name, char const *value); +/* metric_resource_attribute_update adds, updates, or deleted a resource + * attribute. If "value" is NULL or the empty string, the attribute is removed. + * Removing an attribute that does not exist is *not* an error. */ +int metric_resource_attribute_update(metric_t *m, char const *name, + char const *value); + /* metric_label_get efficiently looks up and returns the value of the "name" * label. If a label does not exist, NULL is returned and errno is set to * ENOENT. The returned pointer may not be valid after a subsequent call to diff --git a/src/daemon/metric_test.c b/src/daemon/metric_test.c index a3e26be44..e1362b859 100644 --- a/src/daemon/metric_test.c +++ b/src/daemon/metric_test.c @@ -100,6 +100,8 @@ DEF_TEST(metric_identity) { char *name; label_pair_t *labels; size_t labels_num; + label_pair_t *rattr; + size_t rattr_num; char const *want; } cases[] = { { @@ -129,6 +131,33 @@ DEF_TEST(metric_identity) { .want = "escape_sequences{cardridge_return=\"\\r\",newline=\"\\n\"," "quote=\"\\\"\",tab=\"\\t\"}", }, + { + .name = "metric_with_resource", + .rattr = + (label_pair_t[]){ + {"host.name", "example.com"}, + }, + .rattr_num = 1, + .want = "metric_with_resource{resource:host.name=\"example.com\"}", + }, + { + .name = "metric_with_resource_and_labels", + .rattr = + (label_pair_t[]){ + {"omega", "always"}, + {"alpha", "resources"}, + }, + .rattr_num = 2, + .labels = + (label_pair_t[]){ + {"gamma", "first"}, + {"beta", "come"}, + }, + .labels_num = 2, + .want = + "metric_with_resource_and_labels{resource:alpha=\"resources\"," + "resource:omega=\"always\",beta=\"come\",gamma=\"first\"}", + }, }; for (size_t i = 0; i < (sizeof(cases) / sizeof(cases[0])); i++) { @@ -145,6 +174,10 @@ DEF_TEST(metric_identity) { CHECK_ZERO(metric_label_set(&m, cases[i].labels[j].name, cases[i].labels[j].value)); } + for (size_t j = 0; j < cases[i].rattr_num; j++) { + CHECK_ZERO(metric_resource_attribute_update(&m, cases[i].rattr[j].name, + cases[i].rattr[j].value)); + } strbuf_t buf = STRBUF_CREATE; CHECK_ZERO(metric_identity(&buf, &m)); diff --git a/src/daemon/plugin.c b/src/daemon/plugin.c index 0827d3f78..436a8aa3c 100644 --- a/src/daemon/plugin.c +++ b/src/daemon/plugin.c @@ -34,6 +34,7 @@ #include "configfile.h" #include "filter_chain.h" #include "plugin.h" +#include "resource.h" #include "utils/avltree/avltree.h" #include "utils/common/common.h" #include "utils/heap/heap.h" @@ -2197,6 +2198,14 @@ static int plugin_dispatch_metric_internal(metric_family_t const *fam) { return 0; } /* int plugin_dispatch_values_internal */ +static void set_default_resource_attributes(metric_t *m) { + if (m->resource.num > 0) { + return; + } + + label_set_clone(&m->resource, default_resource_attributes()); +} + EXPORT int plugin_dispatch_metric_family(metric_family_t const *fam) { if ((fam == NULL) || (fam->metric.num == 0)) { return EINVAL; @@ -2217,14 +2226,15 @@ EXPORT int plugin_dispatch_metric_family(metric_family_t const *fam) { cdtime_t interval = plugin_get_interval(); for (size_t i = 0; i < fam_copy->metric.num; i++) { - if (fam_copy->metric.ptr[i].time == 0) { - fam_copy->metric.ptr[i].time = time; + metric_t *m = fam_copy->metric.ptr + i; + if (m->time == 0) { + m->time = time; } - if (fam_copy->metric.ptr[i].interval == 0) { - fam_copy->metric.ptr[i].interval = interval; + if (m->interval == 0) { + m->interval = interval; } - /* TODO(octo): set target labels here. */ + set_default_resource_attributes(m); } int status = plugin_dispatch_metric_internal(fam_copy); diff --git a/src/daemon/resource.c b/src/daemon/resource.c new file mode 100644 index 000000000..6a132bd3e --- /dev/null +++ b/src/daemon/resource.c @@ -0,0 +1,122 @@ +/** + * collectd - src/daemon/resource.c + * Copyright (C) 2023 Florian octo Forster + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + * Florian octo Forster + **/ + +#include "collectd.h" +#include "daemon/resource.h" + +#include "utils/common/common.h" + +static label_set_t default_resource; + +static void otel_service_name(void) { + char *sn = getenv("OTEL_SERVICE_NAME"); + if (sn != NULL) { + label_set_add(&default_resource, "service.name", sn); + return; + } + + strbuf_t buf = STRBUF_CREATE; + strbuf_print(&buf, "unknown_service:" PACKAGE_NAME); + label_set_add(&default_resource, "service.name", buf.ptr); + STRBUF_DESTROY(buf); +} + +static void otel_resource_attributes(void) { + char *ra = getenv("OTEL_RESOURCE_ATTRIBUTES"); + if (ra == NULL) { + return; + } + + size_t tmp_sz = strlen(ra) + 1; + char tmp[tmp_sz]; + sstrncpy(tmp, ra, sizeof(tmp)); + + char *str = &tmp[0]; + char *saveptr = NULL; + char *key; + while ((key = strtok_r(str, ",", &saveptr)) != NULL) { + str = NULL; + char *value = strchr(key, '='); + if (value == NULL) { + continue; + } + *value = 0; + value++; + + label_set_add(&default_resource, key, value); + } +} + +static void host_name(void) { + if (strlen(hostname_g) > 0) { + label_set_add(&default_resource, "host.name", hostname_g); + } +} + +static int machine_id(void) { + char *files[] = { + "/etc/machine-id", + "/etc/hostid", + "/var/lib/dbus/machine-id", + }; + + for (size_t i = 0; i < STATIC_ARRAY_SIZE(files); i++) { + char *f = files[i]; + if (access(f, R_OK) != 0) { + continue; + } + + char buf[1024] = {0}; + ssize_t status = read_text_file_contents(f, buf, sizeof(buf)); + if (status <= 0) { + NOTICE("machine_id: reading \"%s\" failed: %zd", f, status); + continue; + } + strstripnewline(buf); + + label_set_add(&default_resource, "host.id", buf); + return 0; + } + + return ENOENT; +} + +static void init_default_resource(void) { + if (default_resource.num != 0) { + return; + } + + otel_service_name(); + otel_resource_attributes(); + host_name(); + machine_id(); +} + +label_set_t default_resource_attributes(void) { + init_default_resource(); + + return default_resource; +} diff --git a/src/daemon/resource.h b/src/daemon/resource.h new file mode 100644 index 000000000..fb94153ef --- /dev/null +++ b/src/daemon/resource.h @@ -0,0 +1,35 @@ +/** + * collectd - src/daemon/resource.c + * Copyright (C) 2023 Florian octo Forster + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + * Florian octo Forster + **/ + +#ifndef DAEMON_RESOURCE_H +#define DAEMON_RESOURCE_H 1 + +#include "collectd.h" +#include "daemon/metric.h" + +label_set_t default_resource_attributes(void); + +#endif