From: Yaping Li <202858510+YapingLi04@users.noreply.github.com> Date: Thu, 16 Oct 2025 06:21:36 +0000 (-0700) Subject: [metrics] Introduce metrics API framework X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fd73cd6c912a4d31f404d54700654d8398ea8f27;p=thirdparty%2Fsystemd.git [metrics] Introduce metrics API framework This commit introduces the shared code for the metrics API framework: - Metrics API definitions - Code to set up the varlink server - The describe method which shows all the metrics families - The list method which lists all the metrics - Type definitions related to MetricFamily - Common code to build json objects --- diff --git a/src/shared/meson.build b/src/shared/meson.build index 9fa976925a6..cc953b9e3ad 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -126,6 +126,7 @@ shared_sources = files( 'machine-id-setup.c', 'macvlan-util.c', 'main-func.c', + 'metrics.c', 'mkdir-label.c', 'mkfs-util.c', 'module-util.c', @@ -206,6 +207,7 @@ shared_sources = files( 'varlink-io.systemd.MachineImage.c', 'varlink-io.systemd.ManagedOOM.c', 'varlink-io.systemd.Manager.c', + 'varlink-io.systemd.Metrics.c', 'varlink-io.systemd.MountFileSystem.c', 'varlink-io.systemd.MuteConsole.c', 'varlink-io.systemd.NamespaceResource.c', diff --git a/src/shared/metrics.c b/src/shared/metrics.c new file mode 100644 index 00000000000..3cda00f9262 --- /dev/null +++ b/src/shared/metrics.c @@ -0,0 +1,270 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "json-util.h" +#include "log.h" +#include "metrics.h" +#include "string-table.h" +#include "strv.h" +#include "varlink-io.systemd.Metrics.h" +#include "varlink-util.h" + +static void metric_family_context_done(MetricFamilyContext *ctx) { + assert(ctx); + + sd_json_variant_unref(ctx->previous); +} + +int metrics_setup_varlink_server( + sd_varlink_server **server, /* in and out param */ + sd_varlink_server_flags_t flags, + sd_event *event, + sd_varlink_method_t vl_method_list_cb, + sd_varlink_method_t vl_method_describe_cb, + void *userdata) { + + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL; + int r; + + assert(server); + assert(event); + + if (*server) + return 0; + + r = varlink_server_new(&s, flags, userdata); + if (r < 0) + return log_error_errno(r, "Failed to allocate Varlink server: %m"); + + r = sd_varlink_server_add_interface(s, &vl_interface_io_systemd_Metrics); + if (r < 0) + return log_debug_errno(r, "Failed to add varlink metrics interface to varlink server: %m"); + + r = sd_varlink_server_bind_method_many( + s, + "io.systemd.Metrics.List", + vl_method_list_cb, + "io.systemd.Metrics.Describe", + vl_method_describe_cb); + if (r < 0) + return log_debug_errno(r, "Failed to register varlink metrics methods: %m"); + + r = sd_varlink_server_set_description(s, "systemd varlink metrics server"); + if (r < 0) + return log_debug_errno(r, "Failed to set varlink metrics server description: %m"); + + r = sd_varlink_server_attach_event(s, event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return log_debug_errno(r, "Failed to attach varlink metrics connection to event loop: %m"); + + *server = TAKE_PTR(s); + + return 0; +} + +static const char * const metric_family_type_table[_METRIC_FAMILY_TYPE_MAX] = { + [METRIC_FAMILY_TYPE_COUNTER] = "counter", + [METRIC_FAMILY_TYPE_GAUGE] = "gauge", + [METRIC_FAMILY_TYPE_STRING] = "string", +}; + +DEFINE_STRING_TABLE_LOOKUP_TO_STRING(metric_family_type, MetricFamilyType); + +static int metric_family_build_send(sd_varlink *link, const MetricFamily *mf, bool more) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + int r; + + assert(link); + assert(mf); + + r = sd_json_buildo( + &v, + SD_JSON_BUILD_PAIR_STRING("name", mf->name), + SD_JSON_BUILD_PAIR_STRING("description", mf->description), + SD_JSON_BUILD_PAIR_STRING("type", metric_family_type_to_string(mf->type))); + if (r < 0) + return r; + + if (more) + return sd_varlink_notify(link, v); + + return sd_varlink_reply(link, v); +} + +int metrics_method_describe( + const MetricFamily metric_family_table[], + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + int r; + + assert(metric_family_table); + assert(link); + assert(parameters); + + r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, /* userdata= */ NULL); + if (r != 0) + return r; + + if (!FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)) + return sd_varlink_error(link, SD_VARLINK_ERROR_EXPECTED_MORE, NULL); + + const MetricFamily *previous = NULL; + for (const MetricFamily *mf = metric_family_table; mf && mf->name; mf++) { + if (previous) { + r = metric_family_build_send(link, previous, /* more= */ true); + if (r < 0) + return log_debug_errno( + r, "Failed to describe metric family '%s': %m", previous->name); + } + + previous = mf; + } + + if (!previous) + return sd_varlink_error(link, "io.systemd.Metrics.NoSuchMetric", NULL); + + r = metric_family_build_send(link, previous, /* more= */ false); + if (r < 0) + return log_debug_errno(r, "Failed to describe metric family '%s': %m", previous->name); + + return 0; +} + +int metrics_method_list( + const MetricFamily metric_family_table[], + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + + int r; + + assert(metric_family_table); + assert(link); + assert(parameters); + + r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, /* userdata= */ NULL); + if (r != 0) + return r; + + if (!FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)) + return sd_varlink_error(link, SD_VARLINK_ERROR_EXPECTED_MORE, NULL); + + _cleanup_(metric_family_context_done) MetricFamilyContext ctx = { .link = link }; + for (const MetricFamily *mf = metric_family_table; mf && mf->name; mf++) { + assert(mf->generate_cb); + + ctx.metric_family = mf; + r = mf->generate_cb(&ctx, userdata); + if (r < 0) + return log_debug_errno( + r, "Failed to list metrics for metric family '%s': %m", mf->name); + } + + if (!ctx.previous) + return sd_varlink_error(link, "io.systemd.Metrics.NoSuchMetric", NULL); + + /* produce the last metric */ + return sd_varlink_reply(link, ctx.previous); +} + +static int metric_set_fields(sd_json_variant **v, char **field_pairs) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *w = NULL; + size_t n; + int r; + + assert(v); + + n = strv_length(field_pairs); + if (n == 0) + return 0; + + if (n % 2 != 0) + return log_debug_errno(SYNTHETIC_ERRNO(ERANGE), "Odd number of field pairs: %zu", n); + + sd_json_variant **array = new0(sd_json_variant *, n); + if (!array) + return log_oom(); + + CLEANUP_ARRAY(array, n, sd_json_variant_unref_many); + + int i = 0; + STRV_FOREACH_PAIR(key, value, field_pairs) { + r = sd_json_variant_new_string(&array[i++], *key); + if (r < 0) + return log_debug_errno(r, "Failed to create key variant: %m"); + + r = sd_json_variant_new_string(&array[i++], *value); + if (r < 0) + return log_debug_errno(r, "Failed to create value variant: %m"); + } + + r = sd_json_variant_new_object(&w, array, n); + if (r < 0) + return log_debug_errno(r, "Failed to allocate JSON object: %m"); + + return sd_json_variant_set_field(v, "fields", w); +} + +static int metric_build_send(MetricFamilyContext *context, const char *object, sd_json_variant *value, char **field_pairs) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + int r; + + assert(context); + assert(value); + assert(context->link); + assert(context->metric_family); + + r = sd_json_buildo( + &v, + SD_JSON_BUILD_PAIR_STRING("name", context->metric_family->name), + JSON_BUILD_PAIR_STRING_NON_EMPTY("object", object), + SD_JSON_BUILD_PAIR("value", SD_JSON_BUILD_VARIANT(value))); + /* TODO JSON_BUILD_PAIR_OBJECT_STRV_NOT_NULL */ + if (r < 0) + return r; + + if (field_pairs) { /* NULL => no fields object, empty strv => fields:{} */ + r = metric_set_fields(&v, field_pairs); + if (r < 0) + return r; + } + + if (context->previous) { + r = sd_varlink_notify(context->link, context->previous); + if (r < 0) + return r; + + context->previous = sd_json_variant_unref(context->previous); + } + + context->previous = TAKE_PTR(v); + + return 0; +} + +int metric_build_send_string(MetricFamilyContext *context, const char *object, const char *value, char **field_pairs) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + int r; + + assert(value); + + r = sd_json_variant_new_string(&v, value); + if (r < 0) + return log_debug_errno(r, "Failed to allocate JSON string: %m"); + + return metric_build_send(context, object, v, field_pairs); +} + +int metric_build_send_unsigned(MetricFamilyContext *context, const char *object, uint64_t value, char **field_pairs) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + int r; + + r = sd_json_variant_new_unsigned(&v, value); + if (r < 0) + return log_debug_errno(r, "Failed to allocate JSON unsigned: %m"); + + return metric_build_send(context, object, v, field_pairs); +} diff --git a/src/shared/metrics.h b/src/shared/metrics.h new file mode 100644 index 00000000000..a953ae57794 --- /dev/null +++ b/src/shared/metrics.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-varlink.h" + +#include "macro-fundamental.h" + +typedef enum MetricFamilyType { + METRIC_FAMILY_TYPE_COUNTER, + METRIC_FAMILY_TYPE_GAUGE, + METRIC_FAMILY_TYPE_STRING, + _METRIC_FAMILY_TYPE_MAX, + _METRIC_FAMILY_TYPE_INVALID = -EINVAL, +} MetricFamilyType; + +typedef struct MetricFamily MetricFamily; + +typedef struct MetricFamilyContext { + const MetricFamily* metric_family; + sd_varlink *link; + sd_json_variant *previous; +} MetricFamilyContext; + +typedef int (*metric_family_generate_cb_t) (MetricFamilyContext *mfc, void *userdata); + +typedef struct MetricFamily { + const char *name; + const char *description; + MetricFamilyType type; + metric_family_generate_cb_t generate_cb; +} MetricFamily; + +int metrics_setup_varlink_server( + sd_varlink_server **server, /* in and out param */ + sd_varlink_server_flags_t flags, + sd_event *event, + sd_varlink_method_t vl_method_list_cb, + sd_varlink_method_t vl_method_describe_cb, + void *userdata); + +const char* metric_family_type_to_string(MetricFamilyType i) _const_; +int metrics_method_describe(const MetricFamily metric_family_table[], sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int metrics_method_list(const MetricFamily metric_family_table[], sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); + +int metric_build_send_string(MetricFamilyContext* context, const char *object, const char *value, char **field_pairs); +int metric_build_send_unsigned(MetricFamilyContext* context, const char *object, uint64_t value, char **field_pairs); diff --git a/src/shared/varlink-io.systemd.Metrics.c b/src/shared/varlink-io.systemd.Metrics.c new file mode 100644 index 00000000000..c0522667407 --- /dev/null +++ b/src/shared/varlink-io.systemd.Metrics.c @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-varlink-idl.h" + +#include "varlink-io.systemd.Metrics.h" + +static SD_VARLINK_DEFINE_ENUM_TYPE( + MetricFamilyType, + SD_VARLINK_FIELD_COMMENT("A counter metric family type which is a monotonically increasing value"), + SD_VARLINK_DEFINE_ENUM_VALUE(counter), + SD_VARLINK_FIELD_COMMENT("A gauge metric family type which is a value that can go up and down"), + SD_VARLINK_DEFINE_ENUM_VALUE(gauge), + SD_VARLINK_FIELD_COMMENT("A string metric family type"), + SD_VARLINK_DEFINE_ENUM_VALUE(string)); + +static SD_VARLINK_DEFINE_ERROR(NoSuchMetric); + +static SD_VARLINK_DEFINE_METHOD_FULL( + List, + SD_VARLINK_REQUIRES_MORE, + SD_VARLINK_FIELD_COMMENT("Metric name, e.g. io.systemd.Manager.units_by_type_total or io.systemd.Manager.unit_active_state"), + SD_VARLINK_DEFINE_OUTPUT(name, SD_VARLINK_STRING, 0), + /* metric value has various types depending on MetricFamilyType and actual data double/int/uint */ + SD_VARLINK_FIELD_COMMENT("Metric value"), + SD_VARLINK_DEFINE_OUTPUT(value, SD_VARLINK_ANY, 0), + SD_VARLINK_FIELD_COMMENT("Metric object name can be unit name, process name, etc, e.g. dev-hvc0.device"), + SD_VARLINK_DEFINE_OUTPUT(object, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Metric fields are values to deferentiate between different metrics in the same metric family"), + SD_VARLINK_DEFINE_OUTPUT(fields, SD_VARLINK_OBJECT, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD_FULL( + Describe, + SD_VARLINK_REQUIRES_MORE, + SD_VARLINK_FIELD_COMMENT("Metric family name"), + SD_VARLINK_DEFINE_OUTPUT(name, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Metric family description"), + SD_VARLINK_DEFINE_OUTPUT(description, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Metric family type"), + SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(type, MetricFamilyType, 0)); + +SD_VARLINK_DEFINE_INTERFACE( + io_systemd_Metrics, + "io.systemd.Metrics", + SD_VARLINK_INTERFACE_COMMENT("Metrics APIs"), + SD_VARLINK_SYMBOL_COMMENT("An enum representing various metric family types"), + &vl_type_MetricFamilyType, + SD_VARLINK_SYMBOL_COMMENT("Method to get a list of metrics among which the collection of related metrics forms a metric family"), + &vl_method_List, + SD_VARLINK_SYMBOL_COMMENT("Method to get the metric families"), + &vl_method_Describe, + SD_VARLINK_SYMBOL_COMMENT("No such metric found"), + &vl_error_NoSuchMetric); diff --git a/src/shared/varlink-io.systemd.Metrics.h b/src/shared/varlink-io.systemd.Metrics.h new file mode 100644 index 00000000000..d8458a500df --- /dev/null +++ b/src/shared/varlink-io.systemd.Metrics.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-varlink-idl.h" + +extern const sd_varlink_interface vl_interface_io_systemd_Metrics;