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>
This commit is contained in:
David Goulet 2020-10-19 15:15:47 -04:00
parent bd582583f6
commit ec731290a5
11 changed files with 553 additions and 3 deletions

View File

@ -73,7 +73,8 @@ TOR_UTIL_LIBS = \
src/lib/libtor-version.a \
src/lib/libtor-llharden.a \
src/lib/libtor-intmath.a \
src/lib/libtor-ctime.a
src/lib/libtor-ctime.a \
src/lib/libtor-metrics.a
# Variants of the above for linking the testing variant of tor (for coverage
# and tests)
@ -108,7 +109,8 @@ TOR_UTIL_TESTING_LIBS = \
src/lib/libtor-version-testing.a \
src/lib/libtor-llharden-testing.a \
src/lib/libtor-intmath.a \
src/lib/libtor-ctime-testing.a
src/lib/libtor-ctime-testing.a \
src/lib/libtor-metrics-testing.a
endif
# Internal crypto libraries used in Tor

View File

@ -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

View File

@ -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

View File

@ -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();
}
}

View File

@ -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) */

View File

@ -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
}
}

View File

@ -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) */

View File

@ -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);
}

View File

@ -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) */

View File

@ -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));
}

View File

@ -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) */