diff options
author | David Goulet <dgoulet@torproject.org> | 2020-10-19 15:15:47 -0400 |
---|---|---|
committer | David Goulet <dgoulet@torproject.org> | 2020-10-27 10:43:42 -0400 |
commit | ec731290a5a790093961f0fdb06cf69000194adf (patch) | |
tree | da6a2351d3bb17071c64f493edfa292e4257c7ec /src | |
parent | bd582583f610d568bb61d1a108d1ff4f38ef08b5 (diff) | |
download | tor-ec731290a5a790093961f0fdb06cf69000194adf.tar.gz tor-ec731290a5a790093961f0fdb06cf69000194adf.zip |
lib: New metrics library
Used to provide an interface to create metrics store and update the entries.
Related to #40063
Signed-off-by: David Goulet <dgoulet@torproject.org>
Diffstat (limited to 'src')
-rw-r--r-- | src/include.am | 3 | ||||
-rw-r--r-- | src/lib/metrics/include.am | 25 | ||||
-rw-r--r-- | src/lib/metrics/metrics_common.c | 29 | ||||
-rw-r--r-- | src/lib/metrics/metrics_common.h | 40 | ||||
-rw-r--r-- | src/lib/metrics/metrics_store.c | 140 | ||||
-rw-r--r-- | src/lib/metrics/metrics_store.h | 42 | ||||
-rw-r--r-- | src/lib/metrics/metrics_store_entry.c | 129 | ||||
-rw-r--r-- | src/lib/metrics/metrics_store_entry.h | 68 | ||||
-rw-r--r-- | src/lib/metrics/prometheus.c | 56 | ||||
-rw-r--r-- | src/lib/metrics/prometheus.h | 18 |
10 files changed, 549 insertions, 1 deletions
diff --git a/src/include.am b/src/include.am index 657f6e823a..95acecb057 100644 --- a/src/include.am +++ b/src/include.am @@ -22,10 +22,11 @@ include src/lib/intmath/include.am include src/lib/llharden/include.am include src/lib/lock/include.am include src/lib/log/include.am +include src/lib/malloc/include.am include src/lib/math/include.am include src/lib/memarea/include.am include src/lib/meminfo/include.am -include src/lib/malloc/include.am +include src/lib/metrics/include.am include src/lib/net/include.am include src/lib/osinfo/include.am include src/lib/process/include.am diff --git a/src/lib/metrics/include.am b/src/lib/metrics/include.am new file mode 100644 index 0000000000..62c289446e --- /dev/null +++ b/src/lib/metrics/include.am @@ -0,0 +1,25 @@ + +noinst_LIBRARIES += src/lib/libtor-metrics.a + +if UNITTESTS_ENABLED +noinst_LIBRARIES += src/lib/libtor-metrics-testing.a +endif + +# ADD_C_FILE: INSERT SOURCES HERE. +src_lib_libtor_metrics_a_SOURCES = \ + src/lib/metrics/metrics_store.c \ + src/lib/metrics/metrics_store_entry.c \ + src/lib/metrics/metrics_common.c \ + src/lib/metrics/prometheus.c + +src_lib_libtor_metrics_testing_a_SOURCES = \ + $(src_lib_libtor_metrics_a_SOURCES) +src_lib_libtor_metrics_testing_a_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) +src_lib_libtor_metrics_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) + +# ADD_C_FILE: INSERT HEADERS HERE. +noinst_HEADERS += \ + src/lib/metrics/metrics_store.h \ + src/lib/metrics/metrics_store_entry.h \ + src/lib/metrics/metrics_common.h \ + src/lib/metrics/prometheus.h diff --git a/src/lib/metrics/metrics_common.c b/src/lib/metrics/metrics_common.c new file mode 100644 index 0000000000..5941a4d892 --- /dev/null +++ b/src/lib/metrics/metrics_common.c @@ -0,0 +1,29 @@ +/* 2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file metrics_common.c + * @brief Common code for the metrics library + **/ + +#include <stddef.h> + +#include "orconfig.h" + +#include "lib/log/util_bug.h" + +#include "lib/metrics/metrics_common.h" + +/** Return string representation of a metric type. */ +const char * +metrics_type_to_str(const metrics_type_t type) +{ + switch (type) { + case METRICS_TYPE_COUNTER: + return "counter"; + case METRICS_TYPE_GAUGE: + return "gauge"; + default: + tor_assert_unreached(); + } +} diff --git a/src/lib/metrics/metrics_common.h b/src/lib/metrics/metrics_common.h new file mode 100644 index 0000000000..5d1a32ea6c --- /dev/null +++ b/src/lib/metrics/metrics_common.h @@ -0,0 +1,40 @@ +/* Copyright (c) 2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file metrics_common.h + * @brief Header for lib/metrics/metrics_common.c + **/ + +#ifndef TOR_LIB_METRICS_METRICS_COMMON_H +#define TOR_LIB_METRICS_METRICS_COMMON_H + +#include "lib/cc/torint.h" + +/** Format output type. */ +typedef enum { + /** Prometheus data output format. */ + METRICS_FORMAT_PROMETHEUS = 1, +} metrics_format_t; + +/** Metric type. */ +typedef enum { + /* Increment only. */ + METRICS_TYPE_COUNTER, + /* Can go up or down. */ + METRICS_TYPE_GAUGE, +} metrics_type_t; + +/** Metric counter object (METRICS_TYPE_COUNTER). */ +typedef struct metrics_counter_t { + uint64_t value; +} metrics_counter_t; + +/** Metric gauge object (METRICS_TYPE_GAUGE). */ +typedef struct metrics_gauge_t { + int64_t value; +} metrics_gauge_t; + +const char *metrics_type_to_str(const metrics_type_t type); + +#endif /* !defined(TOR_LIB_METRICS_METRICS_COMMON_H) */ diff --git a/src/lib/metrics/metrics_store.c b/src/lib/metrics/metrics_store.c new file mode 100644 index 0000000000..4f048e103d --- /dev/null +++ b/src/lib/metrics/metrics_store.c @@ -0,0 +1,140 @@ +/* Copyright (c) 2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file metrics_store.c + * @brief Metrics interface to store them based on specific store type and get + * their MetricsPort output. + **/ + +#define METRICS_STORE_ENTRY_PRIVATE + +#include "orconfig.h" + +#include "lib/container/map.h" +#include "lib/log/util_bug.h" +#include "lib/malloc/malloc.h" + +#include "lib/metrics/metrics_store.h" +#include "lib/metrics/metrics_store_entry.h" + +/* Format Drivers. */ +#include "lib/metrics/prometheus.h" + +/** A metric store which contains a map of entries. */ +typedef struct metrics_store_t { + /** Indexed by metrics entry name. An entry is a smartlist_t of one or more + * metrics_store_entry_t allowing for multiple metrics of the same name. + * + * The reason we allow multiple entires is because there are cases where one + * metrics can be used twice by the same entity but with different labels. + * One example is an onion service with multiple ports, the port specific + * metrics will have a port value as a label. */ + strmap_t *entries; +} metrics_store_t; + +/** Function pointer to the format function of a specific driver. */ +typedef void (fmt_driver_fn_t)(const metrics_store_entry_t *, buf_t *); + +/** Helper: Free a single entry in a metrics_store_t taking a void pointer + * parameter. */ +static void +metrics_store_free_void(void *p) +{ + smartlist_t *list = p; + SMARTLIST_FOREACH(list, metrics_store_entry_t *, entry, + metrics_store_entry_free(entry)); + smartlist_free(list); +} + +/** Put the given store output in the buffer data and use the format function + * given in fmt to get it for each entry. */ +static void +get_output(const metrics_store_t *store, buf_t *data, fmt_driver_fn_t fmt) +{ + tor_assert(store); + tor_assert(data); + tor_assert(fmt); + + STRMAP_FOREACH(store->entries, key, const smartlist_t *, entries) { + SMARTLIST_FOREACH_BEGIN(entries, const metrics_store_entry_t *, entry) { + fmt(entry, data); + } SMARTLIST_FOREACH_END(entry); + } STRMAP_FOREACH_END; +} + +/** Return a newly allocated and initialized store of the given type. */ +metrics_store_t * +metrics_store_new(void) +{ + metrics_store_t *store = tor_malloc_zero(sizeof(*store)); + + store->entries = strmap_new(); + + return store; +} + +/** Free the given store including all its entries. */ +void +metrics_store_free_(metrics_store_t *store) +{ + if (store == NULL) { + return; + } + + strmap_free(store->entries, metrics_store_free_void); + tor_free(store); +} + +/** Find all metrics entry in the given store identified by name. If not found, + * NULL is returned. */ +smartlist_t * +metrics_store_get_all(const metrics_store_t *store, const char *name) +{ + tor_assert(store); + tor_assert(name); + + return strmap_get(store->entries, name); +} + +/** Add a new metrics entry to the given store and type. The name MUST be the + * unique identifier. The help string can be omitted. */ +metrics_store_entry_t * +metrics_store_add(metrics_store_t *store, metrics_type_t type, + const char *name, const char *help) +{ + smartlist_t *entries; + metrics_store_entry_t *entry; + + tor_assert(store); + tor_assert(name); + + entries = metrics_store_get_all(store, name); + if (!entries) { + entries = smartlist_new(); + strmap_set(store->entries, name, entries); + } + entry = metrics_store_entry_new(type, name, help); + smartlist_add(entries, entry); + + return entry; +} + +/** Set the output of the given store of the format fmt into the given buffer + * data. */ +void +metrics_store_get_output(const metrics_format_t fmt, + const metrics_store_t *store, buf_t *data) +{ + tor_assert(store); + + switch (fmt) { + case METRICS_FORMAT_PROMETHEUS: + get_output(store, data, prometheus_format_store_entry); + break; + default: + // LCOV_EXCL_START + tor_assert_unreached(); + // LCOV_EXCL_STOP + } +} diff --git a/src/lib/metrics/metrics_store.h b/src/lib/metrics/metrics_store.h new file mode 100644 index 0000000000..9640a5e016 --- /dev/null +++ b/src/lib/metrics/metrics_store.h @@ -0,0 +1,42 @@ +/* Copyright (c) 2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file metrics_store.h + * @brief Header for lib/metrics/metrics_store.c + **/ + +#ifndef TOR_LIB_METRICS_METRICS_STORE_H +#define TOR_LIB_METRICS_METRICS_STORE_H + +#include "lib/buf/buffers.h" +#include "lib/container/smartlist.h" + +#include "lib/metrics/metrics_common.h" +#include "lib/metrics/metrics_store_entry.h" + +/* Stub. */ +typedef struct metrics_store_t metrics_store_t; + +/* Allocators. */ +void metrics_store_free_(metrics_store_t *store); +#define metrics_store_free(store) \ + FREE_AND_NULL(metrics_store_t, metrics_store_free_, (store)) +metrics_store_t *metrics_store_new(void); + +/* Modifiers. */ +metrics_store_entry_t *metrics_store_add(metrics_store_t *store, + metrics_type_t type, + const char *name, const char *help); + +/* Accessors. */ +smartlist_t *metrics_store_get_all(const metrics_store_t *store, + const char *name); +void metrics_store_get_output(const metrics_format_t fmt, + const metrics_store_t *store, buf_t *data); + +#ifdef METRICS_METRICS_STORE_PRIVATE + +#endif /* METRICS_METRICS_STORE_PRIVATE. */ + +#endif /* !defined(TOR_LIB_METRICS_METRICS_STORE_H) */ diff --git a/src/lib/metrics/metrics_store_entry.c b/src/lib/metrics/metrics_store_entry.c new file mode 100644 index 0000000000..44ebb5cb84 --- /dev/null +++ b/src/lib/metrics/metrics_store_entry.c @@ -0,0 +1,129 @@ +/* Copyright (c) 2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file metrics_store_entry.c + * @brief Metrics store entry which contains the gathered data. + **/ + +#define METRICS_STORE_ENTRY_PRIVATE + +#include <string.h> + +#include "orconfig.h" + +#include "lib/container/smartlist.h" +#include "lib/log/util_bug.h" +#include "lib/malloc/malloc.h" + +#include "lib/metrics/metrics_store_entry.h" + +/* + * Public API. + */ + +/** Return newly allocated store entry of type COUNTER. */ +metrics_store_entry_t * +metrics_store_entry_new(const metrics_type_t type, const char *name, + const char *help) +{ + metrics_store_entry_t *entry = tor_malloc_zero(sizeof(*entry)); + + tor_assert(name); + + entry->type = type; + entry->name = tor_strdup(name); + entry->labels = smartlist_new(); + if (help) { + entry->help = tor_strdup(help); + } + + return entry; +} + +/** Free a store entry. */ +void +metrics_store_entry_free_(metrics_store_entry_t *entry) +{ + if (!entry) { + return; + } + SMARTLIST_FOREACH(entry->labels, char *, l, tor_free(l)); + smartlist_free(entry->labels); + tor_free(entry->name); + tor_free(entry->help); + tor_free(entry); +} + +/** Update a store entry with value. */ +void +metrics_store_entry_update(metrics_store_entry_t *entry, const int64_t value) +{ + tor_assert(entry); + + switch (entry->type) { + case METRICS_TYPE_COUNTER: + /* Counter can ONLY be positive. */ + if (BUG(value < 0)) { + return; + } + entry->u.counter.value += value; + break; + case METRICS_TYPE_GAUGE: + /* Gauge can increment or decrement. And can be positive or negative. */ + entry->u.gauge.value += value; + break; + } +} + +/** Reset a store entry that is set its metric data to 0. */ +void +metrics_store_entry_reset(metrics_store_entry_t *entry) +{ + tor_assert(entry); + /* Everything back to 0. */ + memset(&entry->u, 0, sizeof(entry->u)); +} + +/** Return store entry value. */ +int64_t +metrics_store_entry_get_value(const metrics_store_entry_t *entry) +{ + tor_assert(entry); + + switch (entry->type) { + case METRICS_TYPE_COUNTER: + if (entry->u.counter.value > INT64_MAX) { + return INT64_MAX; + } + return entry->u.counter.value; + case METRICS_TYPE_GAUGE: + return entry->u.gauge.value; + } + + // LCOV_EXCL_START + tor_assert_unreached(); + // LCOV_EXCL_STOP +} + +/** Add a label into the given entry.*/ +void +metrics_store_entry_add_label(metrics_store_entry_t *entry, + const char *label) +{ + tor_assert(entry); + tor_assert(label); + + smartlist_add(entry->labels, tor_strdup(label)); +} + +/** Return true iff the given entry has the given label. */ +bool +metrics_store_entry_has_label(const metrics_store_entry_t *entry, + const char *label) +{ + tor_assert(entry); + tor_assert(label); + + return smartlist_contains_string(entry->labels, label); +} diff --git a/src/lib/metrics/metrics_store_entry.h b/src/lib/metrics/metrics_store_entry.h new file mode 100644 index 0000000000..6fff9d10eb --- /dev/null +++ b/src/lib/metrics/metrics_store_entry.h @@ -0,0 +1,68 @@ +/* Copyright (c) 2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file metrics_store_entry.h + * @brief Header for lib/metrics/metrics_store_entry.c + **/ + +#ifndef TOR_LIB_METRICS_METRICS_STORE_ENTRY_H +#define TOR_LIB_METRICS_METRICS_STORE_ENTRY_H + +#include "lib/cc/torint.h" + +#include "lib/metrics/metrics_common.h" + +#ifdef METRICS_STORE_ENTRY_PRIVATE + +/** Metrics store entry. They reside in a metrics_store_t object and are + * opaque to the outside world. */ +typedef struct metrics_store_entry_t { + /** Type of entry. */ + metrics_type_t type; + + /** Name. */ + char *name; + + /** Help comment string. */ + char *help; + + /** Labels attached to that entry. If NULL, no labels. + * + * Labels are used to add extra context to a metrics. For example, a label + * could be an onion address so the metrics can be differentiate. */ + smartlist_t *labels; + + /* Actual data. */ + union { + metrics_counter_t counter; + metrics_gauge_t gauge; + } u; +} metrics_store_entry_t; + +#endif /* METRICS_STORE_ENTRY_PRIVATE */ + +typedef struct metrics_store_entry_t metrics_store_entry_t; + +/* Allocators. */ +metrics_store_entry_t *metrics_store_entry_new(const metrics_type_t type, + const char *name, + const char *help); + +void metrics_store_entry_free_(metrics_store_entry_t *entry); +#define metrics_store_entry_free(entry) \ + FREE_AND_NULL(metrics_store_entry_t, metrics_store_entry_free_, (entry)); + +/* Accessors. */ +int64_t metrics_store_entry_get_value(const metrics_store_entry_t *entry); +bool metrics_store_entry_has_label(const metrics_store_entry_t *entry, + const char *label); + +/* Modifiers. */ +void metrics_store_entry_add_label(metrics_store_entry_t *entry, + const char *label); +void metrics_store_entry_reset(metrics_store_entry_t *entry); +void metrics_store_entry_update(metrics_store_entry_t *entry, + const int64_t value); + +#endif /* !defined(TOR_LIB_METRICS_METRICS_STORE_ENTRY_H) */ diff --git a/src/lib/metrics/prometheus.c b/src/lib/metrics/prometheus.c new file mode 100644 index 0000000000..c2b54e436f --- /dev/null +++ b/src/lib/metrics/prometheus.c @@ -0,0 +1,56 @@ +/* Copyright (c) 2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file prometheus.c + * @brief Metrics format driver for Prometheus data model. + **/ + +#define METRICS_STORE_ENTRY_PRIVATE + +#include "orconfig.h" + +#include "lib/container/smartlist.h" +#include "lib/log/util_bug.h" +#include "lib/malloc/malloc.h" +#include "lib/string/printf.h" + +#include "lib/metrics/prometheus.h" + +/** Return a static buffer containing all the labels properly formatted + * for the output as a string. + * + * Subsequent calls to this invalidates the previous result. */ +static const char * +format_labels(smartlist_t *labels) +{ + static char buf[1024]; + char *line = NULL; + + if (smartlist_len(labels) == 0) { + buf[0] = '\0'; + goto end; + } + + line = smartlist_join_strings(labels, ",", 0, NULL); + tor_snprintf(buf, sizeof(buf), "{%s}", line); + + end: + tor_free(line); + return buf; +} + +/** Format the given entry in to the buffer data. */ +void +prometheus_format_store_entry(const metrics_store_entry_t *entry, buf_t *data) +{ + tor_assert(entry); + tor_assert(data); + + buf_add_printf(data, "# HELP %s %s\n", entry->name, entry->help); + buf_add_printf(data, "# TYPE %s %s\n", entry->name, + metrics_type_to_str(entry->type)); + buf_add_printf(data, "%s%s %" PRIi64 "\n", entry->name, + format_labels(entry->labels), + metrics_store_entry_get_value(entry)); +} diff --git a/src/lib/metrics/prometheus.h b/src/lib/metrics/prometheus.h new file mode 100644 index 0000000000..eea26e8ac4 --- /dev/null +++ b/src/lib/metrics/prometheus.h @@ -0,0 +1,18 @@ +/* Copyright (c) 2020, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * @file prometheus.h + * @brief Header for feature/metrics/prometheus.c + **/ + +#ifndef TOR_LIB_METRICS_PROMETHEUS_H +#define TOR_LIB_METRICS_PROMETHEUS_H + +#include "lib/buf/buffers.h" +#include "lib/metrics/metrics_store_entry.h" + +void prometheus_format_store_entry(const metrics_store_entry_t *entry, + buf_t *data); + +#endif /* !defined(TOR_LIB_METRICS_PROMETHEUS_H) */ |