]> git.ipfire.org Git - thirdparty/collectd.git/commitdiff
Add resource labels to `metric_t`.
authorFlorian Forster <octo@collectd.org>
Wed, 6 Dec 2023 07:51:31 +0000 (08:51 +0100)
committerFlorian Forster <octo@collectd.org>
Wed, 13 Dec 2023 21:39:11 +0000 (22:39 +0100)
Makefile.am
src/daemon/metric.c
src/daemon/metric.h
src/daemon/metric_test.c
src/daemon/plugin.c
src/daemon/resource.c [new file with mode: 0644]
src/daemon/resource.h [new file with mode: 0644]

index 8dd6a1a82b9e2fb758af80272e9c38f9c51e7093..9e610e337bb7d81b55ad0c12f8768342a8109706 100644 (file)
@@ -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 \
index 344bb4c538f5daf2137092e26c5b1ca8d5b2d0a9..44e7659f7198b4d4ded7452fa668a1416e3bceb2 100644 (file)
 #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;
index dbd24e08af4c92fd6b5c520ded2db5aa76a1709c..b12dabc2d392df02c481ebd2a54d2a2ca016a3a7 100644 (file)
@@ -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
index a3e26be448c5b33c02d28f22c53e26ac2daedfbb..e1362b859dc703ae0c65e4d293426ea3979b2d3c 100644 (file)
@@ -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));
index 0827d3f787d6d67bee66a7534af0b395de792a90..436a8aa3c73b636ccf5c240a13bea1bbd56faf16 100644 (file)
@@ -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 (file)
index 0000000..6a132bd
--- /dev/null
@@ -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 <octo at collectd.org>
+ **/
+
+#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 (file)
index 0000000..fb94153
--- /dev/null
@@ -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 <octo at collectd.org>
+ **/
+
+#ifndef DAEMON_RESOURCE_H
+#define DAEMON_RESOURCE_H 1
+
+#include "collectd.h"
+#include "daemon/metric.h"
+
+label_set_t default_resource_attributes(void);
+
+#endif