libllist.la \
liblookup.la \
libmetadata.la \
+ libmetric.la \
libmount.la \
liboconfig.la \
libstrbuf.la
test_common \
test_format_graphite \
test_meta_data \
+ test_metric \
test_utils_avltree \
test_utils_cmds \
test_utils_heap \
libcommon.la \
libheap.la \
libllist.la \
+ libmetric.la \
liboconfig.la \
-lm \
$(COMMON_LIBS) \
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
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 \
--- /dev/null
+/**
+ * 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;
+}
--- /dev/null
+/**
+ * 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
--- /dev/null
+/**
+ * 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;
+}
#include "collectd.h"
#include "configfile.h"
+#include "metric.h"
#include "utils/metadata/meta_data.h"
#include "utils_time.h"
};
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;
}
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: {