]> git.ipfire.org Git - thirdparty/collectd.git/commitdiff
src/daemon/metric.[ch]: Data types and functions representing metrics.
authorFlorian Forster <octo@collectd.org>
Sun, 21 Jun 2020 16:33:58 +0000 (18:33 +0200)
committerFlorian Forster <octo@google.com>
Tue, 21 Jul 2020 15:29:10 +0000 (17:29 +0200)
This implementation closely mirrors the Prometheus / OpenMetrics
protocol buffer.

A "metric family" is a set of metrics, all with the same name (but
different labels and/or label values). The identity is split between the
two types: the "metric family" holds the "name" while the "metric" holds
the labels. Likewise, the "metric family" holds the metric type, the
"metric" holds the metric value.

This commit contains all required changes to compile the daemon, but
pretty much everything else still fails to build. The tests have not yet
updated either.

Makefile.am
src/daemon/metric.c [new file with mode: 0644]
src/daemon/metric.h [new file with mode: 0644]
src/daemon/metric_test.c [new file with mode: 0644]
src/daemon/plugin.h
src/utils/format_stackdriver/format_stackdriver.c

index 5beddafe37fa95d55175f92696007e67bddc7b6f..fdc2908f535f1921412bcb00c8ddec793c1635c2 100644 (file)
@@ -139,6 +139,7 @@ noinst_LTLIBRARIES = \
        libllist.la \
        liblookup.la \
        libmetadata.la \
+       libmetric.la \
        libmount.la \
        liboconfig.la \
        libstrbuf.la
@@ -152,6 +153,7 @@ check_PROGRAMS = \
        test_common \
        test_format_graphite \
        test_meta_data \
+       test_metric \
        test_utils_avltree \
        test_utils_cmds \
        test_utils_heap \
@@ -268,6 +270,7 @@ collectd_LDADD = \
        libcommon.la \
        libheap.la \
        libllist.la \
+       libmetric.la \
        liboconfig.la \
        -lm \
        $(COMMON_LIBS) \
@@ -353,6 +356,11 @@ test_meta_data_SOURCES = \
        src/testing.h
 test_meta_data_LDADD = libmetadata.la libplugin_mock.la
 
+test_metric_SOURCES = \
+       src/daemon/metric_test.c \
+       src/testing.h
+test_metric_LDADD = libmetric.la libplugin_mock.la
+
 test_utils_avltree_SOURCES = \
        src/utils/avltree/avltree_test.c \
        src/testing.h
@@ -417,6 +425,11 @@ libmetadata_la_SOURCES = \
        src/utils/metadata/meta_data.c \
        src/utils/metadata/meta_data.h
 
+libmetric_la_SOURCES = \
+                      src/daemon/metric.c \
+                      src/daemon/metric.h
+libmetric_la_LIBADD = libmetadata.la $(COMMON_LIBS)
+
 libplugin_mock_la_SOURCES = \
        src/daemon/plugin_mock.c \
        src/daemon/utils_cache_mock.c \
diff --git a/src/daemon/metric.c b/src/daemon/metric.c
new file mode 100644 (file)
index 0000000..8f76242
--- /dev/null
@@ -0,0 +1,325 @@
+/**
+ * collectd - src/daemon/metric.c
+ * Copyright (C) 2019-2020  Google LLC
+ *
+ * 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>
+ *   Manoj Srivastava <srivasta at google.com>
+ **/
+
+#include "collectd.h"
+
+#include "metric.h"
+#include "plugin.h"
+
+/* Label names must match the regex `[a-zA-Z_][a-zA-Z0-9_]*`. Label names
+ * beginning with __ are reserved for internal use.
+ *
+ * Source:
+ * https://prometheus.io/docs/concepts/data_model/#metric-names-and-labels */
+#define VALID_LABEL_CHARS                                                      \
+  "abcdefghijklmnopqrstuvwxyz"                                                 \
+  "ABCDEFGHIJKLMNOPQRSTUVWXYZ"                                                 \
+  "0123456789_"
+/* Metric names must match the regex `[a-zA-Z_:][a-zA-Z0-9_:]*` */
+#define VALID_NAME_CHARS VALID_LABEL_CHARS ":"
+
+int value_marshal_text(strbuf_t *buf, value_t v, metric_type_t type) {
+  switch (type) {
+  case METRIC_TYPE_GAUGE:
+  case METRIC_TYPE_UNTYPED:
+    return strbuf_printf(buf, GAUGE_FORMAT, v.gauge);
+  case METRIC_TYPE_COUNTER:
+    return strbuf_printf(buf, "%" PRIu64, v.counter);
+  default:
+    ERROR("Unknown metric value type: %d", type);
+    return EINVAL;
+  }
+}
+
+static int label_pair_compare(void const *a, void const *b) {
+  return strcmp(((label_pair_t const *)a)->name,
+                ((label_pair_t const *)b)->name);
+}
+
+label_pair_t *label_set_get(label_set_t labels, char const *name) {
+  if (name == NULL) {
+    errno = EINVAL;
+    return NULL;
+  }
+
+  label_t label = {
+      .name = name,
+  };
+
+  label_pair_t *ret = bsearch(&label, labels.ptr, labels.num,
+                              sizeof(*labels.ptr), label_pair_compare);
+  if (ret == NULL) {
+    errno = ENOENT;
+    return NULL;
+  }
+
+  return ret;
+}
+
+int label_set_add(label_set_t *labels, char const *name, char const *value) {
+  if ((labels == NULL) || (name == NULL) || (value == NULL)) {
+    return EINVAL;
+  }
+
+  size_t name_len = strlen(name);
+  if (name_len == 0) {
+    return EINVAL;
+  }
+
+  size_t valid_len = strspn(name, VALID_LABEL_CHARS);
+  if ((valid_len != name_len) || isdigit((int)name[0])) {
+    return EINVAL;
+  }
+
+  if (label_set_get(*labels, name) != NULL) {
+    return EEXIST;
+  }
+
+  if (strlen(value) == 0) {
+    return 0;
+  }
+
+  label_pair_t *tmp =
+      realloc(labels->ptr, sizeof(*labels->ptr) * (labels->num + 1));
+  if (tmp == NULL) {
+    return errno;
+  }
+  labels->ptr = tmp;
+
+  label_pair_t pair = {
+      .name = strdup(name),
+      .value = strdup(value),
+  };
+  if ((pair.name == NULL) || (pair.value == NULL)) {
+    free(pair.name);
+    free(pair.value);
+    return ENOMEM;
+  }
+
+  labels->ptr[labels->num] = pair;
+  labels->num++;
+
+  qsort(labels->ptr, labels->num, sizeof(*labels->ptr), label_pair_compare);
+  return 0;
+}
+
+static int label_set_clone(label_set_t *dest, label_set_t src) {
+  if (src.num == 0) {
+    return 0;
+  }
+
+  label_set_t ret = {
+      .ptr = calloc(src.num, sizeof(*ret.ptr)),
+      .num = src.num,
+  };
+  if (ret.ptr == NULL) {
+    return ENOMEM;
+  }
+
+  for (size_t i = 0; i < src.num; i++) {
+    ret.ptr[i].name = strdup(src.ptr[i].name);
+    ret.ptr[i].value = strdup(src.ptr[i].value);
+    if ((ret.ptr[i].name == NULL) || (ret.ptr[i].value == NULL)) {
+      label_set_reset(&ret);
+      return ENOMEM;
+    }
+  }
+
+  *dest = ret;
+  return 0;
+}
+
+void label_set_reset(label_set_t *labels) {
+  if (labels == NULL) {
+    return;
+  }
+  for (size_t i = 0; i < labels->num; i++) {
+    free(labels->ptr[i].name);
+    free(labels->ptr[i].value);
+  }
+  free(labels->ptr);
+
+  labels->ptr = NULL;
+  labels->num = 0;
+}
+
+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) {
+    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, "\"");
+  }
+
+  return status || strbuf_print(buf, "}");
+}
+
+int metric_list_add(metric_list_t *metrics, metric_t m) {
+  if (metrics == NULL) {
+    return EINVAL;
+  }
+
+  metric_t *tmp =
+      realloc(metrics->ptr, sizeof(*metrics->ptr) * (metrics->num + 1));
+  if (tmp == NULL) {
+    return errno;
+  }
+  metrics->ptr = tmp;
+
+  metrics->ptr[metrics->num] = m;
+  metrics->num++;
+
+  return 0;
+}
+
+static int metric_list_clone(metric_list_t *dest, metric_list_t src,
+                             metric_family_t *fam) {
+  if (src.num == 0) {
+    return 0;
+  }
+
+  metric_list_t ret = {
+      .ptr = calloc(src.num, sizeof(*ret.ptr)),
+      .num = src.num,
+  };
+  if (ret.ptr == NULL) {
+    return ENOMEM;
+  }
+
+  for (size_t i = 0; i < src.num; i++) {
+    ret.ptr[i] = (metric_t){
+        .family = fam,
+        .value = src.ptr[i].value,
+        .time = src.ptr[i].time,
+        .interval = src.ptr[i].interval,
+    };
+
+    int status = label_set_clone(&ret.ptr[i].label, src.ptr[i].label);
+    if (status != 0) {
+      metric_list_reset(&ret);
+      return status;
+    }
+  }
+
+  *dest = ret;
+  return 0;
+}
+
+void metric_list_reset(metric_list_t *metrics) {
+  if (metrics == NULL) {
+    return;
+  }
+
+  for (size_t i = 0; i < metrics->num; i++) {
+    label_set_reset(&metrics->ptr[i].label);
+  }
+  free(metrics->ptr);
+
+  metrics->ptr = NULL;
+  metrics->num = 0;
+}
+
+int metric_family_metrics_append(metric_family_t *fam, value_t v,
+                                 label_t const *label, size_t label_num) {
+  if ((fam == NULL) || ((label_num != 0) && (label == NULL))) {
+    return EINVAL;
+  }
+
+  metric_t m = {
+      .family = fam,
+      .value = v,
+  };
+
+  for (size_t i = 0; i < label_num; i++) {
+    int status = label_set_add(&m.label, label[i].name, label[i].value);
+    if (status != 0) {
+      label_set_reset(&m.label);
+      return status;
+    }
+  }
+
+  int status = metric_list_add(&fam->metric, m);
+  if (status != 0) {
+    label_set_reset(&m.label);
+    return status;
+  }
+
+  return 0;
+}
+
+void metric_family_free(metric_family_t *fam) {
+  if (fam == NULL) {
+    return;
+  }
+
+  free(fam->name);
+  free(fam->help);
+  metric_list_reset(&fam->metric);
+  free(fam);
+}
+
+metric_family_t *metric_family_clone(metric_family_t const *fam) {
+  if (fam == NULL) {
+    errno = EINVAL;
+    return NULL;
+  }
+
+  metric_family_t *ret = calloc(1, sizeof(*ret));
+  if (ret == NULL) {
+    return NULL;
+  }
+
+  ret->name = strdup(fam->name);
+  if (fam->help != NULL) {
+    ret->help = strdup(fam->help);
+  }
+  ret->type = fam->type;
+
+  int status = metric_list_clone(&ret->metric, fam->metric, ret);
+  if (status != 0) {
+    metric_family_free(ret);
+    errno = status;
+    return NULL;
+  }
+
+  return ret;
+}
diff --git a/src/daemon/metric.h b/src/daemon/metric.h
new file mode 100644 (file)
index 0000000..4c318ea
--- /dev/null
@@ -0,0 +1,162 @@
+/**
+ * collectd - src/daemon/metric.h
+ * Copyright (C) 2019-2020  Google LLC
+ *
+ * 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>
+ *   Manoj Srivastava <srivasta at google.com>
+ **/
+
+#ifndef METRIC_H
+#define METRIC_H 1
+
+#include "utils/metadata/meta_data.h"
+#include "utils/strbuf/strbuf.h"
+#include "utils_time.h"
+
+#define VALUE_TYPE_GAUGE 1
+#define VALUE_TYPE_DERIVE 2
+
+typedef enum {
+  METRIC_TYPE_COUNTER = 0,
+  METRIC_TYPE_GAUGE = 1,
+  METRIC_TYPE_UNTYPED = 2,
+} metric_type_t;
+
+typedef uint64_t counter_t;
+typedef double gauge_t;
+typedef int64_t derive_t;
+
+union value_u {
+  counter_t counter;
+  gauge_t gauge;
+  derive_t derive;
+};
+typedef union value_u value_t;
+
+/* value_marshal_text prints a text representation of v to buf. */
+int value_marshal_text(strbuf_t *buf, value_t v, metric_type_t type);
+
+/*
+ * Labels
+ */
+/* label_pair_t represents a label, i.e. a key/value pair. */
+typedef struct {
+  char *name;
+  char *value;
+} label_pair_t;
+
+/* label_t represents a constant label, i.e. a key/value pair. It is similar to
+ * label_pair_t, except that is has const fields. label_t is used in function
+ * arguments to prevent the called function from modifying its argument.
+ * Internally labels are stored as label_pair_t to allow modification, e.g. by
+ * targets in the "filter chain". */
+typedef struct {
+  char const *name;
+  char const *value;
+} label_t;
+
+/* label_set_t is a sorted set of labels. */
+typedef struct {
+  label_pair_t *ptr;
+  size_t num;
+} label_set_t;
+
+/* label_set_get efficiently looks up and returns the "name" label. If a label
+ * does not exist, NULL is returned and errno is set to ENOENT. */
+label_pair_t *label_set_get(label_set_t labels, char const *name);
+
+/* label_set_add adds a new label to the set of labels. If a label with "name"
+ * already exists, EEXIST is returned. If "value" is the empty string, no label
+ * is added and zero is returned. */
+int label_set_add(label_set_t *labels, char const *name, char const *value);
+
+/* label_set_reset frees all the labels in the label set. It does *not* free
+ * the passed "label_set_t*" itself. */
+void label_set_reset(label_set_t *labels);
+
+/*
+ * Metric
+ */
+/* forward declaration since metric_family_t and metric_t refer to each other.
+ */
+struct metric_family_s;
+typedef struct metric_family_s metric_family_t;
+
+/* metric_t is a metric inside a metric family. */
+typedef struct {
+  metric_family_t *family; /* for family->name and family->type */
+
+  label_set_t label;
+  value_t value;
+  cdtime_t time; /* TODO(octo): use ms or µs instead? */
+  cdtime_t interval;
+  /* TODO(octo): target labels */
+  meta_data_t *meta; /* TODO(octo): free in metric_list_reset() */
+} metric_t;
+
+/* metric_identity writes the identity of the metric "m" to "buf". An example
+ * string is:
+ *
+ *   "http_requests_total{method=\"post\",code=\"200\"}"
+ */
+int metric_identity(strbuf_t *buf, metric_t const *m);
+
+/* metric_list_t is an unordered list of metrics. */
+typedef struct {
+  metric_t *ptr;
+  size_t num;
+} metric_list_t;
+
+/* metric_list_add appends a metric to the metric list. */
+int metric_list_add(metric_list_t *metrics, metric_t m);
+
+/* metric_list_reset frees all the metrics in the metric list. It does *not*
+ * free the passed "metric_list_t*" itself. */
+void metric_list_reset(metric_list_t *metrics);
+
+/*
+ * Metric Family
+ */
+/* metric_family_t is a group of metrics of the same type. */
+struct metric_family_s {
+  char *name;
+  char *help;
+  metric_type_t type;
+
+  metric_list_t metric;
+};
+
+/* metric_family_metrics_append appends a new metric to the metric family. This
+ * allocates memory which must be freed using metric_family_metrics_reset. */
+int metric_family_metrics_append(metric_family_t *fam, value_t v,
+                                 label_t const *label, size_t label_num);
+
+/* metric_family_free frees a "metric_family_t" that was allocated with
+ * metric_family_clone(). */
+void metric_family_free(metric_family_t *fam);
+
+/* metric_family_clone returns a copy of the provided metric family. On error,
+ * errno is set and NULL is returned. The returned pointer must be freed with
+ * metric_family_free(). */
+metric_family_t *metric_family_clone(metric_family_t const *fam);
+
+#endif
diff --git a/src/daemon/metric_test.c b/src/daemon/metric_test.c
new file mode 100644 (file)
index 0000000..36ace47
--- /dev/null
@@ -0,0 +1,100 @@
+/**
+ * collectd - src/daemon/metric_test.c
+ * Copyright (C) 2020       Google LLC
+ *
+ * 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 "metric.h"
+#include "testing.h"
+
+DEF_TEST(metric_identity) {
+  struct {
+    char *name;
+    label_t *labels;
+    size_t labels_num;
+    char const *want;
+  } cases[] = {
+      {
+          .name = "metric_without_labels",
+          .want = "metric_without_labels",
+      },
+      {
+          .name = "metric_with_labels",
+          .labels =
+              (label_t[]){
+                  {"sorted", "yes"},
+                  {"alphabetically", "true"},
+              },
+          .labels_num = 2,
+          .want = "metric_with_labels{alphabetically=\"true\",sorted=\"yes\"}",
+      },
+      {
+          .name = "escape_sequences",
+          .labels =
+              (label_t[]){
+                  {"newline", "\n"},
+                  {"quote", "\""},
+                  {"tab", "\t"},
+                  {"cardridge_return", "\r"},
+              },
+          .labels_num = 4,
+          .want = "escape_sequences{cardridge_return=\"\\r\",newline=\"\\n\","
+                  "quote=\"\\\"\",tab=\"\\t\"}",
+      },
+  };
+
+  for (size_t i = 0; i < (sizeof(cases) / sizeof(cases[0])); i++) {
+    printf("## Case %zu: %s\n", i, cases[i].name);
+
+    metric_family_t fam = {
+        .name = cases[i].name,
+        .type = METRIC_TYPE_UNTYPED,
+    };
+    metric_t m = {
+        .family = &fam,
+    };
+    for (size_t j = 0; j < cases[i].labels_num; j++) {
+      CHECK_ZERO(metric_label_set(&m, cases[i].labels[j].name,
+                                  cases[i].labels[j].value));
+    }
+
+    strbuf_t buf = STRBUF_CREATE;
+    CHECK_ZERO(metric_identity(&buf, &m));
+
+    EXPECT_EQ_STR(cases[i].want, buf.ptr);
+
+    STRBUF_DESTROY(buf);
+    metric_family_metric_reset(&fam);
+    metric_reset(&m);
+  }
+
+  return 0;
+}
+
+int main(void) {
+  RUN_TEST(metric_identity);
+
+  END_TEST;
+}
index 9eb3b0dba26ec013da261d0ecad90ca791a23b17..38f8802c23304f4033efc4d72e4b33f2ad42da83 100644 (file)
@@ -31,6 +31,7 @@
 #include "collectd.h"
 
 #include "configfile.h"
+#include "metric.h"
 #include "utils/metadata/meta_data.h"
 #include "utils_time.h"
 
@@ -83,17 +84,6 @@ struct identifier_s {
 };
 typedef struct identifier_s identifier_t;
 
-typedef unsigned long long counter_t;
-typedef double gauge_t;
-typedef int64_t derive_t;
-
-union value_u {
-  counter_t counter;
-  gauge_t gauge;
-  derive_t derive;
-};
-typedef union value_u value_t;
-
 struct value_list_s {
   value_t *values;
   size_t values_len;
index 1c27119bb5e44e24982a1b5d74e09e1d0648ed89..555ac5044f1e538673d3253e0a1942ee552051f8 100644 (file)
@@ -150,7 +150,7 @@ static int format_typed_value(yajl_gen gen, int ds_type, value_t v,
   }
   case DS_TYPE_COUNTER: {
     counter_t diff = counter_diff((counter_t)start_value, v.counter);
-    ssnprintf(integer, sizeof(integer), "%llu", diff);
+    ssnprintf(integer, sizeof(integer), "%" PRIu64, diff);
     break;
   }
   default: {