mirror of
https://github.com/open5gs/open5gs.git
synced 2025-10-23 07:41:57 +00:00
Instead of predetermined endpoints in the metrics library, each NF can now set it's own endpoints on which it listens for requests to dump info (UE/PDU/gNB/eNB).
616 lines
20 KiB
C
616 lines
20 KiB
C
/*
|
|
* Copyright (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
|
|
* Copyright (C) 2023 by Sukchan Lee <acetcom@gmail.com>
|
|
* Copyright (C) 2025 by Juraj Elias <juraj.elias@gmail.com>
|
|
*
|
|
* This file is part of Open5GS.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/*
|
|
* Prometheus HTTP server (MicroHTTPD) with optional JSON endpoints:
|
|
* - / (provide health check)
|
|
* - /metrics (provide prometheus metrics metrics according to the relevant NF)
|
|
* - /pdu-info (provided by NF registering ogs_metrics_pdu_info_dumper)
|
|
* - /gnb-info (provided by NF registering ogs_metrics_gnb_info_dumper)
|
|
* - /enb-info (provided by NF registering ogs_metrics_enb_info_dumper)
|
|
* - /ue-info (provided by NF registering ogs_metrics_ue_info_dumper)
|
|
*/
|
|
|
|
#include "ogs-core.h"
|
|
#include "metrics/ogs-metrics.h"
|
|
|
|
#include <netdb.h> /* AI_PASSIVE */
|
|
#include "prom.h"
|
|
#include "microhttpd.h"
|
|
#include <string.h>
|
|
|
|
|
|
extern int __ogs_metrics_domain;
|
|
#define MAX_LABELS 8
|
|
|
|
#if MHD_VERSION >= 0x00096100
|
|
static void free_callback(void *cls) { ogs_free(cls); }
|
|
#endif
|
|
|
|
typedef struct ogs_metrics_server_s {
|
|
ogs_socknode_t node;
|
|
struct MHD_Daemon *mhd;
|
|
} ogs_metrics_server_t;
|
|
|
|
typedef struct ogs_metrics_spec_s {
|
|
ogs_metrics_context_t *ctx; /* backpointer */
|
|
ogs_list_t entry; /* included in ogs_metrics_context_t */
|
|
ogs_metrics_metric_type_t type;
|
|
char *name;
|
|
char *description;
|
|
int initial_val;
|
|
ogs_list_t inst_list; /* list of ogs_metrics_instance_t */
|
|
unsigned int num_labels;
|
|
char *labels[MAX_LABELS];
|
|
prom_metric_t *prom;
|
|
} ogs_metrics_spec_t;
|
|
|
|
typedef struct ogs_metrics_inst_s {
|
|
ogs_metrics_spec_t *spec; /* backpointer */
|
|
ogs_list_t entry; /* included in ogs_metrics_spec_t spec */
|
|
unsigned int num_labels;
|
|
char *label_values[MAX_LABELS];
|
|
} ogs_metrics_inst_t;
|
|
|
|
static OGS_POOL(metrics_spec_pool, ogs_metrics_spec_t);
|
|
static OGS_POOL(metrics_server_pool, ogs_metrics_server_t);
|
|
|
|
/* Forward decls */
|
|
static int ogs_metrics_context_server_start(ogs_metrics_server_t *server);
|
|
static int ogs_metrics_context_server_stop(ogs_metrics_server_t *server);
|
|
|
|
static size_t get_query_size_t(struct MHD_Connection *connection,
|
|
const char *key, size_t default_val)
|
|
{
|
|
const char *val = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, key);
|
|
if (!val || !*val) return default_val;
|
|
char *end = NULL;
|
|
unsigned long long v = strtoull(val, &end, 10);
|
|
if (end == val || *end != '\0') return default_val;
|
|
return (size_t)v;
|
|
}
|
|
|
|
void ogs_metrics_server_init(ogs_metrics_context_t *ctx)
|
|
{
|
|
ogs_list_init(&ctx->server_list);
|
|
ogs_pool_init(&metrics_server_pool, ogs_app()->pool.nf);
|
|
}
|
|
|
|
void ogs_metrics_server_open(ogs_metrics_context_t *ctx)
|
|
{
|
|
ogs_metrics_server_t *server = NULL;
|
|
ogs_list_for_each(&ctx->server_list, server)
|
|
ogs_metrics_context_server_start(server);
|
|
}
|
|
|
|
void ogs_metrics_server_close(ogs_metrics_context_t *ctx)
|
|
{
|
|
ogs_metrics_server_t *server = NULL, *next = NULL;
|
|
ogs_list_for_each_safe(&ctx->server_list, next, server)
|
|
ogs_metrics_context_server_stop(server);
|
|
}
|
|
|
|
void ogs_metrics_server_final(ogs_metrics_context_t *ctx)
|
|
{
|
|
ogs_metrics_server_t *server = NULL, *next = NULL;
|
|
|
|
ogs_list_for_each_safe(&ctx->server_list, next, server)
|
|
ogs_metrics_server_remove(server);
|
|
|
|
ogs_pool_final(&metrics_server_pool);
|
|
}
|
|
|
|
ogs_metrics_server_t *ogs_metrics_server_add(ogs_sockaddr_t *addr, ogs_sockopt_t *option)
|
|
{
|
|
ogs_metrics_server_t *server = NULL;
|
|
|
|
ogs_assert(addr);
|
|
ogs_pool_alloc(&metrics_server_pool, &server);
|
|
ogs_assert(server);
|
|
memset(server, 0, sizeof *server);
|
|
|
|
ogs_assert(OGS_OK == ogs_copyaddrinfo(&server->node.addr, addr));
|
|
if (option) server->node.option = ogs_memdup(option, sizeof *option);
|
|
|
|
ogs_list_add(&ogs_metrics_self()->server_list, server);
|
|
return server;
|
|
}
|
|
|
|
void ogs_metrics_server_remove(ogs_metrics_server_t *server)
|
|
{
|
|
ogs_assert(server);
|
|
|
|
ogs_list_remove(&ogs_metrics_self()->server_list, server);
|
|
|
|
ogs_assert(server->node.addr);
|
|
ogs_freeaddrinfo(server->node.addr);
|
|
if (server->node.option) ogs_free(server->node.option);
|
|
|
|
ogs_pool_free(&metrics_server_pool, server);
|
|
}
|
|
|
|
static void mhd_server_run(short when, ogs_socket_t fd, void *data)
|
|
{
|
|
(void)when; (void)fd;
|
|
struct MHD_Daemon *mhd_daemon = data;
|
|
ogs_assert(mhd_daemon);
|
|
MHD_run(mhd_daemon);
|
|
}
|
|
|
|
static void mhd_server_notify_connection(void *cls,
|
|
struct MHD_Connection *connection, void **socket_context,
|
|
enum MHD_ConnectionNotificationCode toe)
|
|
{
|
|
(void)cls;
|
|
struct MHD_Daemon *mhd_daemon = NULL;
|
|
MHD_socket mhd_socket = INVALID_SOCKET;
|
|
|
|
const union MHD_ConnectionInfo *mhd_info = NULL;
|
|
struct { ogs_poll_t *read; } poll = {0};
|
|
|
|
switch (toe) {
|
|
case MHD_CONNECTION_NOTIFY_STARTED:
|
|
mhd_info = MHD_get_connection_info(connection, MHD_CONNECTION_INFO_DAEMON);
|
|
ogs_assert(mhd_info); mhd_daemon = mhd_info->daemon; ogs_assert(mhd_daemon);
|
|
|
|
mhd_info = MHD_get_connection_info(connection, MHD_CONNECTION_INFO_CONNECTION_FD);
|
|
ogs_assert(mhd_info); mhd_socket = mhd_info->connect_fd; ogs_assert(mhd_socket != INVALID_SOCKET);
|
|
|
|
poll.read = ogs_pollset_add(ogs_app()->pollset, OGS_POLLIN, mhd_socket, mhd_server_run, mhd_daemon);
|
|
ogs_assert(poll.read);
|
|
*socket_context = poll.read;
|
|
break;
|
|
|
|
case MHD_CONNECTION_NOTIFY_CLOSED:
|
|
poll.read = *socket_context;
|
|
if (poll.read) ogs_pollset_remove(poll.read);
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if MHD_VERSION >= 0x00097001
|
|
typedef enum MHD_Result _MHD_Result;
|
|
#else
|
|
typedef int _MHD_Result;
|
|
#endif
|
|
|
|
/* Small helper to serve JSON from a registered dumper */
|
|
static _MHD_Result serve_json_from_dumper(struct MHD_Connection *connection,
|
|
ogs_metrics_custom_ep_hdlr_t handler,
|
|
size_t page, size_t page_size)
|
|
{
|
|
ogs_assert(connection);
|
|
ogs_assert(handler);
|
|
|
|
size_t cap = 512 * 1024;
|
|
char *bufjson = (char *)ogs_malloc(cap);
|
|
if (!bufjson) {
|
|
const char *msg = "Out of memory\n";
|
|
struct MHD_Response *rsp = MHD_create_response_from_buffer(strlen(msg),
|
|
(void*)msg, MHD_RESPMEM_PERSISTENT);
|
|
if (!rsp) return (_MHD_Result)MHD_NO;
|
|
int ret = MHD_queue_response(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, rsp);
|
|
MHD_destroy_response(rsp);
|
|
return (_MHD_Result)ret;
|
|
}
|
|
|
|
size_t n = handler(bufjson, cap, page, page_size);
|
|
if (n >= cap - 1) {
|
|
/* grow once */
|
|
size_t newcap = cap * 2;
|
|
char *tmp = (char *)ogs_realloc(bufjson, newcap);
|
|
if (!tmp) {
|
|
ogs_free(bufjson);
|
|
const char *msg = "Out of memory\n";
|
|
struct MHD_Response *rsp = MHD_create_response_from_buffer(strlen(msg),
|
|
(void*)msg, MHD_RESPMEM_PERSISTENT);
|
|
if (!rsp) return (_MHD_Result)MHD_NO;
|
|
int ret = MHD_queue_response(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, rsp);
|
|
MHD_destroy_response(rsp);
|
|
return (_MHD_Result)ret;
|
|
}
|
|
bufjson = tmp; cap = newcap;
|
|
n = handler(bufjson, cap, page, page_size);
|
|
if (n >= cap - 1) {
|
|
/* graceful fallback */
|
|
n = ogs_snprintf(bufjson, cap, "[]");
|
|
}
|
|
}
|
|
|
|
struct MHD_Response *rsp;
|
|
#if MHD_VERSION >= 0x00096100
|
|
rsp = MHD_create_response_from_buffer_with_free_callback(n, (void*)bufjson, free_callback);
|
|
bufjson = NULL; /* ownership moved to MHD */
|
|
#else
|
|
rsp = MHD_create_response_from_buffer(n, (void*)bufjson, MHD_RESPMEM_MUST_COPY);
|
|
#endif
|
|
if (!rsp) {
|
|
#if MHD_VERSION < 0x00096100
|
|
ogs_free(bufjson);
|
|
#endif
|
|
return (_MHD_Result)MHD_NO;
|
|
}
|
|
|
|
MHD_add_response_header(rsp, "Content-Type", "application/json");
|
|
MHD_add_response_header(rsp, "Access-Control-Allow-Origin", "*");
|
|
int ret = MHD_queue_response(connection, MHD_HTTP_OK, rsp);
|
|
MHD_destroy_response(rsp);
|
|
#if MHD_VERSION < 0x00096100
|
|
ogs_free(bufjson);
|
|
#endif
|
|
return (_MHD_Result)ret;
|
|
}
|
|
|
|
static _MHD_Result
|
|
mhd_server_access_handler(void *cls, struct MHD_Connection *connection,
|
|
const char *url, const char *method, const char *version,
|
|
const char *upload_data, size_t *upload_data_size, void **con_cls)
|
|
{
|
|
(void)cls; (void)version; (void)upload_data; (void)upload_data_size; (void)con_cls;
|
|
|
|
const char *buf = NULL;
|
|
struct MHD_Response *rsp = NULL;
|
|
int ret = MHD_NO;
|
|
|
|
/* Only GET is supported */
|
|
if (strcmp(method, "GET") != 0) {
|
|
buf = "Invalid HTTP Method\n";
|
|
rsp = MHD_create_response_from_buffer(strlen(buf), (void *)buf, MHD_RESPMEM_PERSISTENT);
|
|
ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, rsp);
|
|
MHD_destroy_response(rsp);
|
|
return (_MHD_Result)ret;
|
|
}
|
|
|
|
/* Health */
|
|
if (strcmp(url, "/") == 0) {
|
|
buf = "OK\n";
|
|
rsp = MHD_create_response_from_buffer(strlen(buf), (void *)buf, MHD_RESPMEM_PERSISTENT);
|
|
ret = MHD_queue_response(connection, MHD_HTTP_OK, rsp);
|
|
MHD_destroy_response(rsp);
|
|
return (_MHD_Result)ret;
|
|
}
|
|
|
|
/* Prometheus metrics plain-text */
|
|
if (strcmp(url, "/metrics") == 0) {
|
|
buf = prom_collector_registry_bridge(PROM_COLLECTOR_REGISTRY_DEFAULT);
|
|
rsp = MHD_create_response_from_buffer(strlen(buf), (void *)buf, MHD_RESPMEM_MUST_COPY);
|
|
MHD_add_response_header(rsp, "Content-Type", "text/plain; version=0.0.4; charset=utf-8");
|
|
ret = MHD_queue_response(connection, MHD_HTTP_OK, rsp);
|
|
MHD_destroy_response(rsp);
|
|
return (_MHD_Result)ret;
|
|
}
|
|
|
|
size_t page = get_query_size_t(connection, "page", 0);
|
|
size_t page_size = get_query_size_t(connection, "page_size", 100);
|
|
|
|
ogs_metrics_custom_ep_t *node = NULL;
|
|
ogs_list_for_each(&ogs_metrics_self()->custom_eps, node) {
|
|
if (!strcmp(node->endpoint, url)) {
|
|
return serve_json_from_dumper(connection,
|
|
node->handler,
|
|
page, page_size);
|
|
}
|
|
}
|
|
|
|
/* No matching route */
|
|
buf = "Bad Request\n";
|
|
rsp = MHD_create_response_from_buffer(strlen(buf), (void *)buf, MHD_RESPMEM_PERSISTENT);
|
|
ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, rsp);
|
|
MHD_destroy_response(rsp);
|
|
return (_MHD_Result)ret;
|
|
}
|
|
|
|
static int ogs_metrics_context_server_start(ogs_metrics_server_t *server)
|
|
{
|
|
#define MAX_NUM_OF_MHD_OPTION_ITEM 8
|
|
struct MHD_OptionItem mhd_ops[MAX_NUM_OF_MHD_OPTION_ITEM];
|
|
const union MHD_DaemonInfo *mhd_info = NULL;
|
|
int index = 0;
|
|
char buf[OGS_ADDRSTRLEN];
|
|
ogs_sockaddr_t *addr = NULL;
|
|
char *hostname = NULL;
|
|
|
|
ogs_assert(server);
|
|
addr = server->node.addr;
|
|
ogs_assert(addr);
|
|
|
|
#if MHD_VERSION >= 0x00095300
|
|
unsigned int mhd_flags = MHD_USE_ERROR_LOG;
|
|
#else
|
|
unsigned int mhd_flags = MHD_USE_DEBUG;
|
|
#endif
|
|
|
|
#if MHD_VERSION >= 0x00095300
|
|
mhd_flags |= MHD_ALLOW_SUSPEND_RESUME;
|
|
#elif MHD_VERSION >= 0x00093400
|
|
mhd_flags |= MHD_USE_SUSPEND_RESUME;
|
|
#else
|
|
mhd_flags |= MHD_USE_PIPE_FOR_SHUTDOWN;
|
|
#endif
|
|
|
|
if (addr->ogs_sa_family == AF_INET6)
|
|
mhd_flags |= MHD_USE_IPv6;
|
|
|
|
mhd_ops[index].option = MHD_OPTION_NOTIFY_CONNECTION;
|
|
mhd_ops[index].value = (intptr_t)&mhd_server_notify_connection;
|
|
mhd_ops[index].ptr_value = NULL; index++;
|
|
|
|
mhd_ops[index].option = MHD_OPTION_SOCK_ADDR;
|
|
mhd_ops[index].value = 0;
|
|
mhd_ops[index].ptr_value = (void *)&addr->sa; index++;
|
|
|
|
mhd_ops[index].option = MHD_OPTION_END;
|
|
mhd_ops[index].value = 0;
|
|
mhd_ops[index].ptr_value = NULL; index++;
|
|
|
|
if (server->mhd) {
|
|
ogs_error("Prometheus HTTP server is already opened!");
|
|
return OGS_ERROR;
|
|
}
|
|
|
|
server->mhd = MHD_start_daemon(mhd_flags, 0,
|
|
NULL, NULL,
|
|
mhd_server_access_handler, server,
|
|
MHD_OPTION_ARRAY, mhd_ops,
|
|
MHD_OPTION_END);
|
|
if (!server->mhd) {
|
|
ogs_error("Cannot start Prometheus HTTP server");
|
|
return OGS_ERROR;
|
|
}
|
|
|
|
/* Setup poll for server listening socket */
|
|
mhd_info = MHD_get_daemon_info(server->mhd, MHD_DAEMON_INFO_LISTEN_FD);
|
|
ogs_assert(mhd_info);
|
|
|
|
server->node.poll = ogs_pollset_add(ogs_app()->pollset, OGS_POLLIN,
|
|
mhd_info->listen_fd, mhd_server_run, server->mhd);
|
|
ogs_assert(server->node.poll);
|
|
|
|
hostname = ogs_gethostname(addr);
|
|
if (hostname)
|
|
ogs_info("metrics_server() [http://%s]:%d", hostname, OGS_PORT(addr));
|
|
else
|
|
ogs_info("metrics_server() [http://%s]:%d", OGS_ADDR(addr, buf), OGS_PORT(addr));
|
|
|
|
return OGS_OK;
|
|
}
|
|
|
|
static int ogs_metrics_context_server_stop(ogs_metrics_server_t *server)
|
|
{
|
|
ogs_assert(server);
|
|
|
|
if (server->node.poll)
|
|
ogs_pollset_remove(server->node.poll);
|
|
|
|
if (server->mhd) {
|
|
MHD_stop_daemon(server->mhd);
|
|
server->mhd = NULL;
|
|
}
|
|
return OGS_OK;
|
|
}
|
|
|
|
/* ---- Metric spec/inst API (unchanged) ---------------------------------- */
|
|
|
|
void ogs_metrics_spec_init(ogs_metrics_context_t *ctx)
|
|
{
|
|
ogs_list_init(&ctx->spec_list);
|
|
ogs_pool_init(&metrics_spec_pool, ogs_app()->metrics.max_specs);
|
|
prom_collector_registry_default_init();
|
|
}
|
|
|
|
void ogs_metrics_spec_final(ogs_metrics_context_t *ctx)
|
|
{
|
|
ogs_metrics_spec_t *spec = NULL, *next = NULL;
|
|
|
|
ogs_list_for_each_entry_safe(&ctx->spec_list, next, spec, entry)
|
|
ogs_metrics_spec_free(spec);
|
|
|
|
prom_collector_registry_destroy(PROM_COLLECTOR_REGISTRY_DEFAULT);
|
|
ogs_pool_final(&metrics_spec_pool);
|
|
}
|
|
|
|
ogs_metrics_spec_t *ogs_metrics_spec_new(
|
|
ogs_metrics_context_t *ctx, ogs_metrics_metric_type_t type,
|
|
const char *name, const char *description,
|
|
int initial_val, unsigned int num_labels, const char ** labels,
|
|
ogs_metrics_histogram_params_t *histogram_params)
|
|
{
|
|
ogs_metrics_spec_t *spec;
|
|
unsigned int i;
|
|
prom_histogram_buckets_t *buckets = NULL;
|
|
double *upper_bounds;
|
|
|
|
ogs_assert(name);
|
|
ogs_assert(description);
|
|
ogs_assert(num_labels <= MAX_LABELS);
|
|
|
|
ogs_pool_alloc(&metrics_spec_pool, &spec);
|
|
ogs_assert(spec);
|
|
memset(spec, 0, sizeof *spec);
|
|
spec->ctx = ctx;
|
|
ogs_list_init(&spec->inst_list);
|
|
spec->type = type;
|
|
spec->name = ogs_strdup(name);
|
|
spec->description = ogs_strdup(description);
|
|
spec->initial_val = initial_val;
|
|
spec->num_labels = num_labels;
|
|
for (i = 0; i < num_labels; i++) {
|
|
ogs_assert(labels[i]);
|
|
spec->labels[i] = ogs_strdup(labels[i]);
|
|
}
|
|
|
|
switch (type) {
|
|
case OGS_METRICS_METRIC_TYPE_COUNTER:
|
|
spec->prom = prom_counter_new(spec->name, spec->description,
|
|
spec->num_labels, (const char **)spec->labels);
|
|
break;
|
|
case OGS_METRICS_METRIC_TYPE_GAUGE:
|
|
spec->prom = prom_gauge_new(spec->name, spec->description,
|
|
spec->num_labels, (const char **)spec->labels);
|
|
break;
|
|
case OGS_METRICS_METRIC_TYPE_HISTOGRAM:
|
|
ogs_assert(histogram_params);
|
|
switch (histogram_params->type) {
|
|
case OGS_METRICS_HISTOGRAM_BUCKET_TYPE_EXPONENTIAL:
|
|
buckets = prom_histogram_buckets_exponential(histogram_params->exp.start,
|
|
histogram_params->exp.factor, histogram_params->count);
|
|
ogs_assert(buckets);
|
|
break;
|
|
case OGS_METRICS_HISTOGRAM_BUCKET_TYPE_LINEAR:
|
|
buckets = prom_histogram_buckets_linear(histogram_params->lin.start,
|
|
histogram_params->lin.width, histogram_params->count);
|
|
ogs_assert(buckets);
|
|
break;
|
|
case OGS_METRICS_HISTOGRAM_BUCKET_TYPE_VARIABLE:
|
|
buckets = (prom_histogram_buckets_t *)prom_malloc(sizeof(prom_histogram_buckets_t));
|
|
ogs_assert(buckets);
|
|
ogs_assert(histogram_params->count <= OGS_METRICS_HIST_VAR_BUCKETS_MAX);
|
|
buckets->count = histogram_params->count;
|
|
|
|
upper_bounds = (double *)prom_malloc(sizeof(double) * histogram_params->count);
|
|
ogs_assert(upper_bounds);
|
|
for (i = 0; i < histogram_params->count; i++) {
|
|
upper_bounds[i] = histogram_params->var.buckets[i];
|
|
if (i > 0) ogs_assert(upper_bounds[i] > upper_bounds[i - 1]);
|
|
}
|
|
buckets->upper_bounds = upper_bounds;
|
|
break;
|
|
default:
|
|
ogs_assert_if_reached();
|
|
break;
|
|
}
|
|
spec->prom = prom_histogram_new(spec->name, spec->description,
|
|
buckets, spec->num_labels, (const char **)spec->labels);
|
|
ogs_assert(spec->prom);
|
|
break;
|
|
default:
|
|
ogs_assert_if_reached();
|
|
break;
|
|
}
|
|
prom_collector_registry_must_register_metric(spec->prom);
|
|
|
|
ogs_list_add(&ctx->spec_list, &spec->entry);
|
|
return spec;
|
|
}
|
|
|
|
void ogs_metrics_spec_free(ogs_metrics_spec_t *spec)
|
|
{
|
|
ogs_metrics_inst_t *inst = NULL, *next = NULL;
|
|
unsigned int i;
|
|
|
|
ogs_list_remove(&spec->ctx->spec_list, &spec->entry);
|
|
|
|
ogs_list_for_each_entry_safe(&spec->inst_list, next, inst, entry)
|
|
ogs_metrics_inst_free(inst);
|
|
|
|
ogs_free(spec->name);
|
|
ogs_free(spec->description);
|
|
for (i = 0; i < spec->num_labels; i++)
|
|
ogs_free(spec->labels[i]);
|
|
|
|
ogs_pool_free(&metrics_spec_pool, spec);
|
|
}
|
|
|
|
ogs_metrics_inst_t *ogs_metrics_inst_new(
|
|
ogs_metrics_spec_t *spec,
|
|
unsigned int num_labels, const char **label_values)
|
|
{
|
|
ogs_metrics_inst_t *inst;
|
|
unsigned int i;
|
|
|
|
ogs_assert(spec);
|
|
ogs_assert(num_labels == spec->num_labels);
|
|
|
|
inst = ogs_calloc(1, sizeof *inst);
|
|
ogs_assert(inst);
|
|
inst->spec = spec;
|
|
inst->num_labels = num_labels;
|
|
for (i = 0; i < num_labels; i++) {
|
|
ogs_assert(label_values[i]);
|
|
inst->label_values[i] = ogs_strdup(label_values[i]);
|
|
}
|
|
ogs_list_add(&spec->inst_list, &inst->entry);
|
|
ogs_metrics_inst_reset(inst);
|
|
return inst;
|
|
}
|
|
|
|
void ogs_metrics_inst_free(ogs_metrics_inst_t *inst)
|
|
{
|
|
unsigned int i;
|
|
|
|
ogs_list_remove(&inst->spec->inst_list, &inst->entry);
|
|
|
|
for (i = 0; i < inst->num_labels; i++)
|
|
ogs_free(inst->label_values[i]);
|
|
|
|
ogs_free(inst);
|
|
}
|
|
|
|
void ogs_metrics_inst_set(ogs_metrics_inst_t *inst, int val)
|
|
{
|
|
switch (inst->spec->type) {
|
|
case OGS_METRICS_METRIC_TYPE_GAUGE:
|
|
prom_gauge_set(inst->spec->prom, (double)val, (const char **)inst->label_values);
|
|
break;
|
|
default:
|
|
ogs_assert_if_reached();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ogs_metrics_inst_reset(ogs_metrics_inst_t *inst)
|
|
{
|
|
switch (inst->spec->type) {
|
|
case OGS_METRICS_METRIC_TYPE_COUNTER:
|
|
prom_counter_add(inst->spec->prom, 0.0, (const char **)inst->label_values);
|
|
break;
|
|
case OGS_METRICS_METRIC_TYPE_GAUGE:
|
|
prom_gauge_set(inst->spec->prom, (double)inst->spec->initial_val, (const char **)inst->label_values);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ogs_metrics_inst_add(ogs_metrics_inst_t *inst, int val)
|
|
{
|
|
switch (inst->spec->type) {
|
|
case OGS_METRICS_METRIC_TYPE_COUNTER:
|
|
ogs_assert(val >= 0);
|
|
prom_counter_add(inst->spec->prom, (double)val, (const char **)inst->label_values);
|
|
break;
|
|
case OGS_METRICS_METRIC_TYPE_GAUGE:
|
|
if (val >= 0)
|
|
prom_gauge_add(inst->spec->prom, (double)val, (const char **)inst->label_values);
|
|
else
|
|
prom_gauge_sub(inst->spec->prom, (double)-1.0*(double)val, (const char **)inst->label_values);
|
|
break;
|
|
case OGS_METRICS_METRIC_TYPE_HISTOGRAM:
|
|
ogs_assert(val >= 0);
|
|
prom_histogram_observe(inst->spec->prom, (double)val, (const char **)inst->label_values);
|
|
break;
|
|
default:
|
|
ogs_assert_if_reached();
|
|
break;
|
|
}
|
|
}
|
|
|