summaryrefslogtreecommitdiffstats
path: root/fluent-bit/lib/cmetrics/src/cmt_encode_prometheus.c
diff options
context:
space:
mode:
Diffstat (limited to 'fluent-bit/lib/cmetrics/src/cmt_encode_prometheus.c')
-rw-r--r--fluent-bit/lib/cmetrics/src/cmt_encode_prometheus.c543
1 files changed, 543 insertions, 0 deletions
diff --git a/fluent-bit/lib/cmetrics/src/cmt_encode_prometheus.c b/fluent-bit/lib/cmetrics/src/cmt_encode_prometheus.c
new file mode 100644
index 00000000..287d701b
--- /dev/null
+++ b/fluent-bit/lib/cmetrics/src/cmt_encode_prometheus.c
@@ -0,0 +1,543 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* CMetrics
+ * ========
+ * Copyright 2021-2022 The CMetrics Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdbool.h>
+
+#include <cmetrics/cmetrics.h>
+#include <cmetrics/cmt_metric.h>
+#include <cmetrics/cmt_map.h>
+#include <cmetrics/cmt_counter.h>
+#include <cmetrics/cmt_gauge.h>
+#include <cmetrics/cmt_summary.h>
+#include <cmetrics/cmt_histogram.h>
+
+#include <cmetrics/cmt_untyped.h>
+#include <cmetrics/cmt_compat.h>
+
+#define PROM_FMT_VAL_FROM_VAL 0
+#define PROM_FMT_VAL_FROM_BUCKET_ID 1
+#define PROM_FMT_VAL_FROM_QUANTILE 2
+#define PROM_FMT_VAL_FROM_SUM 3
+#define PROM_FMT_VAL_FROM_COUNT 4
+
+struct prom_fmt {
+ int metric_name; /* metric name already set ? */
+ int brace_open; /* first brace open ? */
+ int labels_count; /* number of labels aready added */
+ int value_from;
+
+ /*
+ * For value_from 'PROM_FMT_VAL_FROM_BUCKET_ID', the 'id' belongs to a bucket
+ * id position, if is 'PROM_FMT_VAL_FROM_QUANTILE' the value represents a
+ * sum_quantiles position.
+ */
+ int id;
+};
+
+static void prom_fmt_init(struct prom_fmt *fmt)
+{
+ fmt->metric_name = CMT_FALSE;
+ fmt->brace_open = CMT_FALSE;
+ fmt->labels_count = 0;
+ fmt->value_from = PROM_FMT_VAL_FROM_VAL;
+ fmt->id = -1;
+}
+
+/*
+ * Prometheus Exposition Format
+ * ----------------------------
+ * https://github.com/prometheus/docs/blob/master/content/docs/instrumenting/exposition_formats.md
+ */
+
+static void metric_escape(cfl_sds_t *buf, cfl_sds_t description, bool escape_quote)
+{
+ int i;
+ size_t len;
+
+ len = cfl_sds_len(description);
+
+ for (i = 0; i < len; i++) {
+ switch (description[i]) {
+ case '\\':
+ cfl_sds_cat_safe(buf, "\\\\", 2);
+ break;
+ case '\n':
+ cfl_sds_cat_safe(buf, "\\n", 2);
+ break;
+ case '"':
+ if (escape_quote) {
+ cfl_sds_cat_safe(buf, "\\\"", 2);
+ break;
+ }
+ /* FALLTHROUGH */
+ default:
+ cfl_sds_cat_safe(buf, description + i, 1);
+ break;
+ }
+ }
+}
+
+static void metric_banner(cfl_sds_t *buf, struct cmt_map *map,
+ struct cmt_metric *metric)
+{
+ struct cmt_opts *opts;
+
+ opts = map->opts;
+
+ /* HELP */
+ cfl_sds_cat_safe(buf, "# HELP ", 7);
+ cfl_sds_cat_safe(buf, opts->fqname, cfl_sds_len(opts->fqname));
+
+ if (cfl_sds_len(opts->description) > 1 || opts->description[0] != ' ') {
+ /* only append description if it is not empty. the parser uses a single whitespace
+ * string to signal that no HELP was provided */
+ cfl_sds_cat_safe(buf, " ", 1);
+ metric_escape(buf, opts->description, false);
+ }
+ cfl_sds_cat_safe(buf, "\n", 1);
+
+ /* TYPE */
+ cfl_sds_cat_safe(buf, "# TYPE ", 7);
+ cfl_sds_cat_safe(buf, opts->fqname, cfl_sds_len(opts->fqname));
+
+ if (map->type == CMT_COUNTER) {
+ cfl_sds_cat_safe(buf, " counter\n", 9);
+ }
+ else if (map->type == CMT_GAUGE) {
+ cfl_sds_cat_safe(buf, " gauge\n", 7);
+ }
+ else if (map->type == CMT_SUMMARY) {
+ cfl_sds_cat_safe(buf, " summary\n", 9);
+ }
+ else if (map->type == CMT_HISTOGRAM) {
+ cfl_sds_cat_safe(buf, " histogram\n", 11);
+ }
+ else if (map->type == CMT_UNTYPED) {
+ cfl_sds_cat_safe(buf, " untyped\n", 9);
+ }
+}
+
+static void append_metric_value(cfl_sds_t *buf,
+ struct cmt_map *map,
+ struct cmt_metric *metric,
+ struct prom_fmt *fmt, int add_timestamp)
+{
+ int len;
+ double val;
+ uint64_t ts;
+ char tmp[128];
+
+ /*
+ * Retrieve metric value
+ * ---------------------
+ * the formatter 'fmt->value_from' specifies from 'where' the value must
+ * be retrieved from, note the 'metric' structure contains one generic
+ * value field plus others associated to histograms.
+ */
+ if (fmt->value_from == PROM_FMT_VAL_FROM_VAL) {
+ /* get 'normal' metric value */
+ val = cmt_metric_get_value(metric);
+ }
+ else if (fmt->value_from == PROM_FMT_VAL_FROM_BUCKET_ID) {
+ /* retrieve the value from a bucket */
+ val = cmt_metric_hist_get_value(metric, fmt->id);
+ }
+ else if (fmt->value_from == PROM_FMT_VAL_FROM_QUANTILE) {
+ /* retrieve the value from a bucket */
+ val = cmt_summary_quantile_get_value(metric, fmt->id);
+ }
+ else {
+ if (map->type == CMT_HISTOGRAM) {
+ if (fmt->value_from == PROM_FMT_VAL_FROM_SUM) {
+ val = cmt_metric_hist_get_sum_value(metric);
+ }
+ else if (fmt->value_from == PROM_FMT_VAL_FROM_COUNT) {
+ val = cmt_metric_hist_get_count_value(metric);
+ }
+ }
+ else if (map->type == CMT_SUMMARY) {
+ if (fmt->value_from == PROM_FMT_VAL_FROM_SUM) {
+ val = cmt_summary_get_sum_value(metric);
+ }
+ else if (fmt->value_from == PROM_FMT_VAL_FROM_COUNT) {
+ val = cmt_summary_get_count_value(metric);
+ }
+ }
+ }
+
+ if (add_timestamp) {
+ ts = cmt_metric_get_timestamp(metric);
+
+ /* convert from nanoseconds to milliseconds */
+ ts /= 1000000;
+
+ len = snprintf(tmp, sizeof(tmp) - 1, " %.17g %" PRIu64 "\n", val, ts);
+ }
+ else {
+ len = snprintf(tmp, sizeof(tmp) - 1, " %.17g\n", val);
+ }
+ cfl_sds_cat_safe(buf, tmp, len);
+}
+
+static int add_label(cfl_sds_t *buf, cfl_sds_t key, cfl_sds_t val)
+{
+ cfl_sds_cat_safe(buf, key, cfl_sds_len(key));
+ cfl_sds_cat_safe(buf, "=\"", 2);
+ metric_escape(buf, val, true);
+ cfl_sds_cat_safe(buf, "\"", 1);
+
+ return 1;
+}
+
+static int add_static_labels(struct cmt *cmt, cfl_sds_t *buf)
+{
+ int count = 0;
+ int total = 0;
+ struct cfl_list *head;
+ struct cmt_label *label;
+
+ total = cfl_list_size(&cmt->static_labels->list);
+ cfl_list_foreach(head, &cmt->static_labels->list) {
+ label = cfl_list_entry(head, struct cmt_label, _head);
+
+ count += add_label(buf, label->key, label->val);
+ if (count < total) {
+ cfl_sds_cat_safe(buf, ",", 1);
+ }
+ }
+
+ return count;
+}
+
+static void format_metric(struct cmt *cmt,
+ cfl_sds_t *buf, struct cmt_map *map,
+ struct cmt_metric *metric, int add_timestamp,
+ struct prom_fmt *fmt)
+{
+ int i;
+ int static_labels = 0;
+ int defined_labels = 0;
+ struct cmt_map_label *label_k;
+ struct cmt_map_label *label_v;
+ struct cfl_list *head;
+ struct cmt_opts *opts;
+
+ opts = map->opts;
+
+ /* Metric info */
+ if (!fmt->metric_name) {
+ cfl_sds_cat_safe(buf, opts->fqname, cfl_sds_len(opts->fqname));
+ }
+
+ /* Static labels */
+ static_labels = cmt_labels_count(cmt->static_labels);
+ cfl_list_foreach(head, &metric->labels) {
+ label_v = cfl_list_entry(head, struct cmt_map_label, _head);
+ if (strlen(label_v->name)) {
+ defined_labels++;
+ }
+ }
+
+ if (!fmt->brace_open && (static_labels + defined_labels > 0)) {
+ cfl_sds_cat_safe(buf, "{", 1);
+ }
+
+ if (static_labels > 0) {
+ /* if some labels were added before, add the separator */
+ if (fmt->labels_count > 0) {
+ cfl_sds_cat_safe(buf, ",", 1);
+ }
+ fmt->labels_count += add_static_labels(cmt, buf);
+ }
+
+ /* Append api defined labels */
+ if (defined_labels > 0) {
+ if (fmt->labels_count > 0) {
+ cfl_sds_cat_safe(buf, ",", 1);
+ }
+
+ i = 1;
+ label_k = cfl_list_entry_first(&map->label_keys, struct cmt_map_label, _head);
+ cfl_list_foreach(head, &metric->labels) {
+ label_v = cfl_list_entry(head, struct cmt_map_label, _head);
+
+ if (strlen(label_v->name)) {
+ fmt->labels_count += add_label(buf, label_k->name, label_v->name);
+ if (i < defined_labels) {
+ cfl_sds_cat_safe(buf, ",", 1);
+ }
+
+ i++;
+ }
+
+ label_k = cfl_list_entry_next(&label_k->_head, struct cmt_map_label,
+ _head, &map->label_keys);
+ }
+ }
+
+ if (fmt->labels_count > 0) {
+ cfl_sds_cat_safe(buf, "}", 1);
+ }
+
+ append_metric_value(buf, map, metric, fmt, add_timestamp);
+}
+
+static cfl_sds_t bucket_value_to_string(double val)
+{
+ int len;
+ cfl_sds_t str;
+
+ str = cfl_sds_create_size(64);
+ if (!str) {
+ return NULL;
+ }
+
+ len = snprintf(str, 64, "%g", val);
+ cfl_sds_len_set(str, len);
+
+ if (!strchr(str, '.')) {
+ cfl_sds_cat_safe(&str, ".0", 2);
+ }
+
+ return str;
+}
+
+static void format_histogram_bucket(struct cmt *cmt,
+ cfl_sds_t *buf, struct cmt_map *map,
+ struct cmt_metric *metric, int add_timestamp)
+{
+ int i;
+ cfl_sds_t val;
+ struct cmt_histogram *histogram;
+ struct cmt_histogram_buckets *bucket;
+ struct cmt_opts *opts;
+ struct prom_fmt fmt = {0};
+
+ histogram = (struct cmt_histogram *) map->parent;
+ bucket = histogram->buckets;
+ opts = map->opts;
+
+ for (i = 0; i <= bucket->count; i++) {
+ /* metric name */
+ cfl_sds_cat_safe(buf, opts->fqname, cfl_sds_len(opts->fqname));
+ cfl_sds_cat_safe(buf, "_bucket", 7);
+
+ /* upper bound */
+ cfl_sds_cat_safe(buf, "{le=\"", 5);
+
+ if (i < bucket->count) {
+ val = bucket_value_to_string(bucket->upper_bounds[i]);
+ cfl_sds_cat_safe(buf, val, cfl_sds_len(val));
+ cfl_sds_destroy(val);
+ }
+ else {
+ cfl_sds_cat_safe(buf, "+Inf", 4);
+ }
+ cfl_sds_cat_safe(buf, "\"", 1);
+
+ /* configure formatter */
+ fmt.metric_name = CMT_TRUE;
+ fmt.brace_open = CMT_TRUE;
+ fmt.labels_count = 1;
+ fmt.value_from = PROM_FMT_VAL_FROM_BUCKET_ID;
+ fmt.id = i;
+
+ /* append metric labels, value and timestamp */
+ format_metric(cmt, buf, map, metric, add_timestamp, &fmt);
+ }
+
+ /* sum */
+ prom_fmt_init(&fmt);
+ fmt.metric_name = CMT_TRUE;
+ fmt.value_from = PROM_FMT_VAL_FROM_SUM;
+
+ cfl_sds_cat_safe(buf, opts->fqname, cfl_sds_len(opts->fqname));
+ cfl_sds_cat_safe(buf, "_sum", 4);
+ format_metric(cmt, buf, map, metric, add_timestamp, &fmt);
+
+ /* count */
+ fmt.labels_count = 0;
+ fmt.value_from = PROM_FMT_VAL_FROM_COUNT;
+
+ cfl_sds_cat_safe(buf, opts->fqname, cfl_sds_len(opts->fqname));
+ cfl_sds_cat_safe(buf, "_count", 6);
+ format_metric(cmt, buf, map, metric, add_timestamp, &fmt);
+}
+
+static void format_summary_quantiles(struct cmt *cmt,
+ cfl_sds_t *buf, struct cmt_map *map,
+ struct cmt_metric *metric, int add_timestamp)
+{
+ int i;
+ cfl_sds_t val;
+ struct cmt_summary *summary;
+ struct cmt_opts *opts;
+ struct prom_fmt fmt = {0};
+
+ summary = (struct cmt_summary *) map->parent;
+ opts = map->opts;
+
+ if (metric->sum_quantiles_set) {
+ for (i = 0; i < summary->quantiles_count; i++) {
+ /* metric name */
+ cfl_sds_cat_safe(buf, opts->fqname, cfl_sds_len(opts->fqname));
+
+ /* quantiles */
+ cfl_sds_cat_safe(buf, "{quantile=\"", 11);
+ val = bucket_value_to_string(summary->quantiles[i]);
+ cfl_sds_cat_safe(buf, val, cfl_sds_len(val));
+ cfl_sds_destroy(val);
+ cfl_sds_cat_safe(buf, "\"", 1);
+
+ /* configure formatter */
+ fmt.metric_name = CMT_TRUE;
+ fmt.brace_open = CMT_TRUE;
+ fmt.labels_count = 1;
+ fmt.value_from = PROM_FMT_VAL_FROM_QUANTILE;
+ fmt.id = i;
+
+ /* append metric labels, value and timestamp */
+ format_metric(cmt, buf, map, metric, add_timestamp, &fmt);
+ }
+ }
+
+ /* sum */
+ prom_fmt_init(&fmt);
+ fmt.metric_name = CMT_TRUE;
+ fmt.value_from = PROM_FMT_VAL_FROM_SUM;
+
+ cfl_sds_cat_safe(buf, opts->fqname, cfl_sds_len(opts->fqname));
+ cfl_sds_cat_safe(buf, "_sum", 4);
+ format_metric(cmt, buf, map, metric, add_timestamp, &fmt);
+
+ /* count */
+ fmt.labels_count = 0;
+ fmt.value_from = PROM_FMT_VAL_FROM_COUNT;
+
+ cfl_sds_cat_safe(buf, opts->fqname, cfl_sds_len(opts->fqname));
+ cfl_sds_cat_safe(buf, "_count", 6);
+ format_metric(cmt, buf, map, metric, add_timestamp, &fmt);
+}
+
+static void format_metrics(struct cmt *cmt, cfl_sds_t *buf, struct cmt_map *map,
+ int add_timestamp)
+{
+ int banner_set = CMT_FALSE;
+ struct cfl_list *head;
+ struct cmt_metric *metric;
+ struct prom_fmt fmt = {0};
+
+ /* Simple metric, no labels */
+ if (map->metric_static_set) {
+ metric_banner(buf, map, &map->metric);
+ banner_set = CMT_TRUE;
+
+ if (map->type == CMT_HISTOGRAM) {
+ /* Histogram needs to format the buckets, one line per bucket */
+ format_histogram_bucket(cmt, buf, map, &map->metric, add_timestamp);
+ }
+ else if (map->type == CMT_SUMMARY) {
+ /* Histogram needs to format the buckets, one line per bucket */
+ format_summary_quantiles(cmt, buf, map, &map->metric, add_timestamp);
+ }
+ else {
+ prom_fmt_init(&fmt);
+ format_metric(cmt, buf, map, &map->metric, add_timestamp, &fmt);
+ }
+ }
+
+ if (cfl_list_size(&map->metrics) > 0) {
+ metric = cfl_list_entry_first(&map->metrics, struct cmt_metric, _head);
+ if (!banner_set) {
+ metric_banner(buf, map, metric);
+ }
+ }
+
+ cfl_list_foreach(head, &map->metrics) {
+ metric = cfl_list_entry(head, struct cmt_metric, _head);
+
+ /* Format the metric based on its type */
+ if (map->type == CMT_HISTOGRAM) {
+ /* Histogram needs to format the buckets, one line per bucket */
+ format_histogram_bucket(cmt, buf, map, metric, add_timestamp);
+ }
+ else if (map->type == CMT_SUMMARY) {
+ format_summary_quantiles(cmt, buf, map, metric, add_timestamp);
+ }
+ else {
+ prom_fmt_init(&fmt);
+ format_metric(cmt, buf, map, metric, add_timestamp, &fmt);
+ }
+ }
+}
+
+/* Format all the registered metrics in Prometheus Text format */
+cfl_sds_t cmt_encode_prometheus_create(struct cmt *cmt, int add_timestamp)
+{
+ cfl_sds_t buf;
+ struct cfl_list *head;
+ struct cmt_counter *counter;
+ struct cmt_gauge *gauge;
+ struct cmt_summary *summary;
+ struct cmt_histogram *histogram;
+ struct cmt_untyped *untyped;
+
+ /* Allocate a 1KB of buffer */
+ buf = cfl_sds_create_size(1024);
+ if (!buf) {
+ return NULL;
+ }
+
+ /* Counters */
+ cfl_list_foreach(head, &cmt->counters) {
+ counter = cfl_list_entry(head, struct cmt_counter, _head);
+ format_metrics(cmt, &buf, counter->map, add_timestamp);
+ }
+
+ /* Gauges */
+ cfl_list_foreach(head, &cmt->gauges) {
+ gauge = cfl_list_entry(head, struct cmt_gauge, _head);
+ format_metrics(cmt, &buf, gauge->map, add_timestamp);
+ }
+
+ /* Summaries */
+ cfl_list_foreach(head, &cmt->summaries) {
+ summary = cfl_list_entry(head, struct cmt_summary, _head);
+ format_metrics(cmt, &buf, summary->map, add_timestamp);
+ }
+
+ /* Histograms */
+ cfl_list_foreach(head, &cmt->histograms) {
+ histogram = cfl_list_entry(head, struct cmt_histogram, _head);
+ format_metrics(cmt, &buf, histogram->map, add_timestamp);
+ }
+
+ /* Untyped */
+ cfl_list_foreach(head, &cmt->untypeds) {
+ untyped = cfl_list_entry(head, struct cmt_untyped, _head);
+ format_metrics(cmt, &buf, untyped->map, add_timestamp);
+ }
+
+ return buf;
+}
+
+void cmt_encode_prometheus_destroy(cfl_sds_t text)
+{
+ cfl_sds_destroy(text);
+}