From f57e098105a096fc6f6d3a6da586eb0160f63a55 Mon Sep 17 00:00:00 2001 From: Yaping Li <202858510+YapingLi04@users.noreply.github.com> Date: Fri, 25 Jul 2025 14:19:52 -0700 Subject: [PATCH 1/6] [metrics] Add varlink definition for Metrics interface/API --- src/shared/meson.build | 1 + src/shared/varlink-io.systemd.Metrics.c | 50 +++++++++++++++++++++++++ src/shared/varlink-io.systemd.Metrics.h | 5 +++ 3 files changed, 56 insertions(+) create mode 100644 src/shared/varlink-io.systemd.Metrics.c create mode 100644 src/shared/varlink-io.systemd.Metrics.h diff --git a/src/shared/meson.build b/src/shared/meson.build index 768f446d20196..4126b6677ea88 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -200,6 +200,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.NamespaceResource.c', 'varlink-io.systemd.Network.c', diff --git a/src/shared/varlink-io.systemd.Metrics.c b/src/shared/varlink-io.systemd.Metrics.c new file mode 100644 index 0000000000000..4f795c9b0cd02 --- /dev/null +++ b/src/shared/varlink-io.systemd.Metrics.c @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.Metrics.h" + +static SD_VARLINK_DEFINE_ENUM_TYPE( + MetricFamilyType, + SD_VARLINK_FIELD_COMMENT("A counter metric family type"), + SD_VARLINK_DEFINE_ENUM_VALUE(counter), + SD_VARLINK_FIELD_COMMENT("A gauge metric family type"), + 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_SUPPORTS_MORE, + SD_VARLINK_FIELD_COMMENT("Metric name"), + SD_VARLINK_DEFINE_FIELD(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_FIELD(value, SD_VARLINK_OBJECT, 0), + SD_VARLINK_FIELD_COMMENT("Metric object name"), + SD_VARLINK_DEFINE_FIELD(object, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Metric fields"), + SD_VARLINK_DEFINE_FIELD(fields, SD_VARLINK_OBJECT, SD_VARLINK_MAP|SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD_FULL( + Describe, + SD_VARLINK_SUPPORTS_MORE, + SD_VARLINK_FIELD_COMMENT("Metric family name"), + SD_VARLINK_DEFINE_FIELD(name, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Metric family description"), + SD_VARLINK_DEFINE_FIELD(description, SD_VARLINK_STRING, 0), + SD_VARLINK_FIELD_COMMENT("Metric family type"), + SD_VARLINK_DEFINE_FIELD_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 list of metrics"), + &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 0000000000000..dbd3d6972310e --- /dev/null +++ b/src/shared/varlink-io.systemd.Metrics.h @@ -0,0 +1,5 @@ +#pragma once + +#include "sd-varlink-idl.h" + +extern const sd_varlink_interface vl_interface_io_systemd_Metrics; From fd6d6b4784730a9306955e1f8a5537d44b9f4d0a Mon Sep 17 00:00:00 2001 From: Yaping Li <202858510+YapingLi04@users.noreply.github.com> Date: Fri, 25 Jul 2025 14:23:42 -0700 Subject: [PATCH 2/6] [metrics] Add metrics-specific varlink sockets and List method to get the number of active units --- src/core/manager.h | 2 ++ src/core/meson.build | 1 + src/core/varlink-metrics.c | 26 +++++++++++++++++ src/core/varlink-metrics.h | 6 ++++ src/core/varlink.c | 46 +++++++++++++++++++++++++++++ src/shared/meson.build | 1 + src/shared/metrics.c | 59 ++++++++++++++++++++++++++++++++++++++ src/shared/metrics.h | 13 +++++++++ 8 files changed, 154 insertions(+) create mode 100644 src/core/varlink-metrics.c create mode 100644 src/core/varlink-metrics.h create mode 100644 src/shared/metrics.c create mode 100644 src/shared/metrics.h diff --git a/src/core/manager.h b/src/core/manager.h index a7009a49d791b..050803cd9ec9c 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -463,6 +463,8 @@ typedef struct Manager { * systemd-oomd to report changes in ManagedOOM settings (systemd client - oomd server). */ sd_varlink *managed_oom_varlink; + sd_varlink_server *metrics_varlink_server; + /* Reference to RestrictFileSystems= BPF program */ struct restrict_fs_bpf *restrict_fs; diff --git a/src/core/meson.build b/src/core/meson.build index 8bb0054c146aa..6ffc9b7b07b70 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -67,6 +67,7 @@ libcore_sources = files( 'varlink-common.c', 'varlink-dynamic-user.c', 'varlink-manager.c', + 'varlink-metrics.c', 'varlink-unit.c', ) diff --git a/src/core/varlink-metrics.c b/src/core/varlink-metrics.c new file mode 100644 index 0000000000000..8ac19d7ae9dfd --- /dev/null +++ b/src/core/varlink-metrics.c @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "json-util.h" +#include "manager.h" +#include "sd-json.h" +#include "sd-varlink.h" +#include "varlink-metrics.h" + +int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + Manager *m = ASSERT_PTR(userdata); + int r; + + assert(link); + + r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, /* userdata= */ NULL); + if (r != 0) + return r; + + return sd_varlink_replybo( + link, + SD_JSON_BUILD_PAIR_STRING("name", "units"), + SD_JSON_BUILD_PAIR_UNSIGNED("value", hashmap_size(m->units)), + SD_JSON_BUILD_PAIR("fields", + SD_JSON_BUILD_OBJECT( + SD_JSON_BUILD_PAIR_STRING("state", "active")))); +} diff --git a/src/core/varlink-metrics.h b/src/core/varlink-metrics.h new file mode 100644 index 0000000000000..efe866d86c4c1 --- /dev/null +++ b/src/core/varlink-metrics.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "forward.h" + +int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/core/varlink.c b/src/core/varlink.c index 17f5ac4fad95c..f1882d91e826f 100644 --- a/src/core/varlink.c +++ b/src/core/varlink.c @@ -1,11 +1,14 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include + #include "sd-varlink.h" #include "constants.h" #include "errno-util.h" #include "json-util.h" #include "manager.h" +#include "metrics.h" #include "path-util.h" #include "pidref.h" #include "string-util.h" @@ -20,6 +23,7 @@ #include "varlink-io.systemd.service.h" #include "varlink-manager.h" #include "varlink-serialize.h" +#include "varlink-metrics.h" #include "varlink-unit.h" #include "varlink-util.h" @@ -421,6 +425,39 @@ int manager_setup_varlink_server(Manager *m) { return 1; } +static int manager_varlink_metrics_init(Manager *m) { + sd_varlink_server_flags_t flags = SD_VARLINK_SERVER_INHERIT_USERDATA; + _cleanup_free_ char *user_address = NULL; + const char *address; + int r; + + assert(m); + + if (MANAGER_IS_SYSTEM(m)) { + address = "/run/systemd/metrics/io.systemd.Manager"; + flags |= SD_VARLINK_SERVER_ACCOUNT_UID; + } else { + user_address = path_join(m->prefix[EXEC_DIRECTORY_RUNTIME], "metrics/io.systemd.Manager"); + if (!user_address) + return -ENOMEM; + address = user_address; + } + + r = metrics_setup_varlink_server( + &m->metrics_varlink_server, + flags, + m->event, + vl_method_list, + m); + if (r < 0) + return r; + + if (MANAGER_IS_TEST_RUN(m)) + return 0; + + return metrics_listen_varlink_address(m->metrics_varlink_server, address); +} + static int manager_varlink_init_system(Manager *m) { int r; @@ -449,6 +486,10 @@ static int manager_varlink_init_system(Manager *m) { } } + r = manager_varlink_metrics_init(m); + if (r < 0) + return log_error_errno(r, "Failed to set up metrics varlink server: %m"); + return 1; } @@ -483,6 +524,10 @@ static int manager_varlink_init_user(Manager *m) { return log_error_errno(r, "Failed to bind to varlink socket '%s': %m", address); } + r = manager_varlink_metrics_init(m); + if (r < 0) + return log_error_errno(r, "Failed to set up metrics varlink server: %m"); + return manager_varlink_managed_oom_connect(m); } @@ -501,4 +546,5 @@ void manager_varlink_done(Manager *m) { m->varlink_server = sd_varlink_server_unref(m->varlink_server); m->managed_oom_varlink = sd_varlink_close_unref(m->managed_oom_varlink); + m->metrics_varlink_server = sd_varlink_server_unref(m->metrics_varlink_server); } diff --git a/src/shared/meson.build b/src/shared/meson.build index 4126b6677ea88..ea1acbf51c278 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -125,6 +125,7 @@ shared_sources = files( 'machine-pool.c', 'macvlan-util.c', 'main-func.c', + 'metrics.c', 'mkdir-label.c', 'mkfs-util.c', 'module-util.c', diff --git a/src/shared/metrics.c b/src/shared/metrics.c new file mode 100644 index 0000000000000..807d1e7536031 --- /dev/null +++ b/src/shared/metrics.c @@ -0,0 +1,59 @@ +#include + +#include "log.h" +#include "metrics.h" +#include "varlink-serialize.h" +#include "varlink-io.systemd.Metrics.h" +#include "varlink-util.h" + +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, + 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_debug_errno(r, "Failed to allocate varlink metrics server object: %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(s, "io.systemd.Metrics.List", vl_method_list_cb); + if (r < 0) + return log_debug_errno(r, "Failed to register varlink metrics methods: %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; +} + +int metrics_listen_varlink_address(sd_varlink_server *server, const char *address) { + int r; + + assert(server); + assert(address); + + /* a new server will have empty list of addresses anyway */ + if (varlink_server_contains_socket(server, address)) + return 0; + + r = sd_varlink_server_listen_address(server, address, 0666 | SD_VARLINK_SERVER_MODE_MKDIR_0755); + if (r < 0) + return log_debug_errno(r, "Failed to bind to metrics varlink socket '%s': %m", address); + + return 0; +} diff --git a/src/shared/metrics.h b/src/shared/metrics.h new file mode 100644 index 0000000000000..326c64542ba61 --- /dev/null +++ b/src/shared/metrics.h @@ -0,0 +1,13 @@ +#pragma once + +#include "sd-varlink.h" + + +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, + void *userdata); + +int metrics_listen_varlink_address(sd_varlink_server *server, const char *address); From 91c247d6a09c725a24e1fc229ef3b5c2f31eff6a Mon Sep 17 00:00:00 2001 From: Yaping Li <202858510+YapingLi04@users.noreply.github.com> Date: Sun, 27 Jul 2025 02:19:13 -0700 Subject: [PATCH 3/6] [metrics] Add streaming for per-unit metrics --- src/core/varlink-metrics.c | 164 ++++++++++++++++++++- src/core/varlink-metrics.h | 3 + src/shared/varlink-io.systemd.Metrics.c | 2 + test/units/TEST-74-AUX-UTILS.varlinkctl.sh | 4 + 4 files changed, 165 insertions(+), 8 deletions(-) diff --git a/src/core/varlink-metrics.c b/src/core/varlink-metrics.c index 8ac19d7ae9dfd..89f3c7fa102f6 100644 --- a/src/core/varlink-metrics.c +++ b/src/core/varlink-metrics.c @@ -1,26 +1,174 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include +#include + +#include "hashmap.h" +#include "install.h" #include "json-util.h" #include "manager.h" +#include "set.h" #include "sd-json.h" #include "sd-varlink.h" +#include "unit.h" +#include "unit-def.h" #include "varlink-metrics.h" +static int list_units_count_one(sd_varlink *link, Manager *m, const char *state, bool more) { + int r, count; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + + assert(m); + assert(state); + + if (0 == strcmp(state, "active")) + count = hashmap_size(m->units); + else if (0 == strcmp(state, "failed")) + count = set_size(m->failed_units); + else + return sd_varlink_error(NULL, VARLINK_ERROR_METRICS_NO_SUCH_METRIC, NULL); + + r = sd_json_buildo( + &v, + SD_JSON_BUILD_PAIR_STRING("name", "units"), + SD_JSON_BUILD_PAIR_UNSIGNED("value", count), + SD_JSON_BUILD_PAIR( + "fields", + SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_STRING("state", state)))); + if (r < 0) + return log_error_errno(r, "Failed to list unit status: %m"); + + if (more) + return sd_varlink_notify(link, v); + + return sd_varlink_reply(link, v); +} + +static int list_unit_type_one(sd_varlink *link, Manager *m, UnitType *t, bool more) { + int r; + unsigned count = 0; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + + assert(link); + assert(m); + assert(t); + + // TODO: This needs a rework/improvement + LIST_FOREACH(units_by_type, _u, m->units_by_type[*t]) + count++; + + r = sd_json_buildo( + &v, + SD_JSON_BUILD_PAIR_STRING("name", "units"), + SD_JSON_BUILD_PAIR_UNSIGNED("value", count), + SD_JSON_BUILD_PAIR( + "fields", + SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_STRING("type", unit_type_to_string(*t))))); + if (r < 0) + return r; + + if (more) + return sd_varlink_notify(link, v); + + return sd_varlink_reply(link, v); +} + +static int list_per_unit_metrics_one(sd_varlink *link, Unit *unit, bool more) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + int r; + + assert(link); + assert(unit); + + r = sd_json_buildo( + &v, + SD_JSON_BUILD_PAIR_STRING("name", IO_SYSTEMD_MANAGER"unit_state"), + SD_JSON_BUILD_PAIR_UNSIGNED("value", 0), /* 0 is a placeholder and has no meaning */ + SD_JSON_BUILD_PAIR( + "fields", + SD_JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_STRING_NON_EMPTY("unit", unit->id), + JSON_BUILD_PAIR_STRING_NON_EMPTY("state", unit_active_state_to_string(unit_active_state(unit))), + JSON_BUILD_PAIR_STRING_NON_EMPTY("load_state", unit_load_state_to_string(unit->load_state)), + JSON_BUILD_PAIR_STRING_NON_EMPTY("sub_state", unit_sub_state_to_string(unit)), + JSON_BUILD_PAIR_STRING_NON_EMPTY("freezer_state", freezer_state_to_string(unit->freezer_state)), + JSON_BUILD_PAIR_STRING_NON_EMPTY("unit_file_state", unit_file_state_to_string(unit_get_unit_file_state(unit)))))); + + if (r < 0) + return r; + + if (more) + return sd_varlink_notify(link, v); + + return sd_varlink_reply(link, v); +} + int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { - Manager *m = ASSERT_PTR(userdata); + Manager *manager = ASSERT_PTR(userdata); int r; assert(link); + assert(parameters); r = sd_varlink_dispatch(link, parameters, /* dispatch_table= */ NULL, /* userdata= */ NULL); if (r != 0) return r; - return sd_varlink_replybo( - link, - SD_JSON_BUILD_PAIR_STRING("name", "units"), - SD_JSON_BUILD_PAIR_UNSIGNED("value", hashmap_size(m->units)), - SD_JSON_BUILD_PAIR("fields", - SD_JSON_BUILD_OBJECT( - SD_JSON_BUILD_PAIR_STRING("state", "active")))); + if (!FLAGS_SET(flags, SD_VARLINK_METHOD_MORE)) + return sd_varlink_error(link, SD_VARLINK_ERROR_EXPECTED_MORE, NULL); + + const char *states[] = { + "active", + "failed", + NULL + }; + + for (int i = 0; states[i] != NULL; i++) { + r = list_units_count_one(link, manager, states[i], /* more = */ true); + if (r < 0) + return r; + } + + UnitType *t, *previous_type = NULL; + for (int i = 0; i < _UNIT_TYPE_MAX; i++) { + UnitType type = (UnitType) i; + t = &type; + + if (previous_type) { + r = list_unit_type_one(link, manager, previous_type, /* more = */ true); + if (r < 0) + return r; + } + + previous_type = t; + } + + if (previous_type) { + r = list_unit_type_one(link, manager, previous_type, /* more = */ true); + if (r < 0) + return r; + } else + return sd_varlink_error(link, VARLINK_ERROR_METRICS_NO_SUCH_METRIC, NULL); + + const char *k; + Unit *u, *previous = NULL; + HASHMAP_FOREACH_KEY(u, k, manager->units) { + /* ignore aliases */ + if (k != u->id) + continue; + + if (previous) { + r = list_per_unit_metrics_one(link, previous, /* more = */ true); + if (r < 0) + return r; + } + + previous = u; + } + + if (previous) + return list_per_unit_metrics_one(link, previous, /* more = */ false); + + return sd_varlink_error(link, VARLINK_ERROR_METRICS_NO_SUCH_METRIC, NULL); + } diff --git a/src/core/varlink-metrics.h b/src/core/varlink-metrics.h index efe866d86c4c1..cf399cb9d2226 100644 --- a/src/core/varlink-metrics.h +++ b/src/core/varlink-metrics.h @@ -3,4 +3,7 @@ #include "forward.h" +#define VARLINK_ERROR_METRICS_NO_SUCH_METRIC "io.systemd.Metrics.NoSuchMetric" +#define IO_SYSTEMD_MANAGER "io.systemd.Manager." + int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/shared/varlink-io.systemd.Metrics.c b/src/shared/varlink-io.systemd.Metrics.c index 4f795c9b0cd02..9b6af67fe13e4 100644 --- a/src/shared/varlink-io.systemd.Metrics.c +++ b/src/shared/varlink-io.systemd.Metrics.c @@ -1,5 +1,7 @@ /* 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( diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh index d00d2e65d83da..4bbf3a35f060c 100755 --- a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh @@ -196,6 +196,10 @@ varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"pid": {" (! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"name": "non-existent.service"}') (! varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"pid": {"pid": -1}}' ) +# test io.systemd.Metrics +varlinkctl info /run/systemd/metrics/io.systemd.Manager +varlinkctl --more call /run/systemd/metrics/io.systemd.Manager io.systemd.Metrics.List {} + # test io.systemd.Manager in user manager testuser_uid=$(id -u testuser) systemd-run --wait --pipe --user --machine testuser@ \ From 30a43cf8dedeb99f3930faa9c4405d54590dcef4 Mon Sep 17 00:00:00 2001 From: Yaping Li <202858510+YapingLi04@users.noreply.github.com> Date: Tue, 5 Aug 2025 02:38:40 -0700 Subject: [PATCH 4/6] [metrics] Add metrics vtable --- src/core/varlink-metrics.c | 203 ++++++++++++++++++++++++------------- src/core/varlink-metrics.h | 6 +- src/shared/metrics.c | 82 ++++++++++++++- src/shared/metrics.h | 21 ++++ 4 files changed, 240 insertions(+), 72 deletions(-) diff --git a/src/core/varlink-metrics.c b/src/core/varlink-metrics.c index 89f3c7fa102f6..9a85b039738cd 100644 --- a/src/core/varlink-metrics.c +++ b/src/core/varlink-metrics.c @@ -1,12 +1,14 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include #include "hashmap.h" #include "install.h" #include "json-util.h" #include "manager.h" +#include "metrics.h" #include "set.h" #include "sd-json.h" #include "sd-varlink.h" @@ -14,29 +16,26 @@ #include "unit-def.h" #include "varlink-metrics.h" -static int list_units_count_one(sd_varlink *link, Manager *m, const char *state, bool more) { - int r, count; +static int unit_states_total_build_json_one( + sd_varlink *link, + UnitActiveState state, + unsigned count, + bool more) { + int r; _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; - assert(m); - assert(state); - - if (0 == strcmp(state, "active")) - count = hashmap_size(m->units); - else if (0 == strcmp(state, "failed")) - count = set_size(m->failed_units); - else - return sd_varlink_error(NULL, VARLINK_ERROR_METRICS_NO_SUCH_METRIC, NULL); + assert(link); - r = sd_json_buildo( - &v, - SD_JSON_BUILD_PAIR_STRING("name", "units"), - SD_JSON_BUILD_PAIR_UNSIGNED("value", count), - SD_JSON_BUILD_PAIR( - "fields", - SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_STRING("state", state)))); + r = METRIC_JSON_BUILD_UNSIGNED( + &v, + METRIC_IO_SYSTEMD_MANAGER_UNIT_STATES_TOTAL, + /* object= */ NULL, + count, + /* fields */ + "state", + unit_active_state_to_string(state)); if (r < 0) - return log_error_errno(r, "Failed to list unit status: %m"); + return r; if (more) return sd_varlink_notify(link, v); @@ -44,26 +43,26 @@ static int list_units_count_one(sd_varlink *link, Manager *m, const char *state, return sd_varlink_reply(link, v); } -static int list_unit_type_one(sd_varlink *link, Manager *m, UnitType *t, bool more) { +static int unit_types_total_build_json_one(sd_varlink *link, Manager *manager, UnitType *type, bool more) { int r; unsigned count = 0; _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; assert(link); - assert(m); - assert(t); + assert(manager); + assert(type); // TODO: This needs a rework/improvement - LIST_FOREACH(units_by_type, _u, m->units_by_type[*t]) + LIST_FOREACH(units_by_type, _u, manager->units_by_type[*type]) count++; - r = sd_json_buildo( + r = METRIC_JSON_BUILD_UNSIGNED( &v, - SD_JSON_BUILD_PAIR_STRING("name", "units"), - SD_JSON_BUILD_PAIR_UNSIGNED("value", count), - SD_JSON_BUILD_PAIR( - "fields", - SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR_STRING("type", unit_type_to_string(*t))))); + METRIC_IO_SYSTEMD_MANAGER_UNIT_TYPES_TOTAL, + /* object= */ NULL, + count, + /* fields */ + "type", unit_type_to_string(*type)); if (r < 0) return r; @@ -73,27 +72,25 @@ static int list_unit_type_one(sd_varlink *link, Manager *m, UnitType *t, bool mo return sd_varlink_reply(link, v); } -static int list_per_unit_metrics_one(sd_varlink *link, Unit *unit, bool more) { +static int unit_state_build_json_one(sd_varlink *link, Unit *unit, bool more) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; int r; assert(link); assert(unit); - r = sd_json_buildo( - &v, - SD_JSON_BUILD_PAIR_STRING("name", IO_SYSTEMD_MANAGER"unit_state"), - SD_JSON_BUILD_PAIR_UNSIGNED("value", 0), /* 0 is a placeholder and has no meaning */ - SD_JSON_BUILD_PAIR( - "fields", - SD_JSON_BUILD_OBJECT( - JSON_BUILD_PAIR_STRING_NON_EMPTY("unit", unit->id), - JSON_BUILD_PAIR_STRING_NON_EMPTY("state", unit_active_state_to_string(unit_active_state(unit))), - JSON_BUILD_PAIR_STRING_NON_EMPTY("load_state", unit_load_state_to_string(unit->load_state)), - JSON_BUILD_PAIR_STRING_NON_EMPTY("sub_state", unit_sub_state_to_string(unit)), - JSON_BUILD_PAIR_STRING_NON_EMPTY("freezer_state", freezer_state_to_string(unit->freezer_state)), - JSON_BUILD_PAIR_STRING_NON_EMPTY("unit_file_state", unit_file_state_to_string(unit_get_unit_file_state(unit)))))); + r = METRIC_JSON_BUILD_UNSIGNED( + &v, + METRIC_IO_SYSTEMD_MANAGER_UNIT_STATE, + unit->id, + /* value= */ 0, /* 0 is a placeholder and has no meaning */ + /* fields */ + "state", unit_active_state_to_string(unit_active_state(unit)), + "load_state", unit_load_state_to_string(unit->load_state), + "sub_state", unit_sub_state_to_string(unit), + "freezer_state", freezer_state_to_string(unit->freezer_state), + "unit_file_state", unit_file_state_to_string(unit_get_unit_file_state(unit))); if (r < 0) return r; @@ -103,31 +100,12 @@ static int list_per_unit_metrics_one(sd_varlink *link, Unit *unit, bool more) { return sd_varlink_reply(link, v); } -int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { - Manager *manager = ASSERT_PTR(userdata); +static int unit_types_total_build_json(sd_varlink *link, void *userdata, bool more) { int r; + Manager *manager = ASSERT_PTR(userdata); 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 char *states[] = { - "active", - "failed", - NULL - }; - - for (int i = 0; states[i] != NULL; i++) { - r = list_units_count_one(link, manager, states[i], /* more = */ true); - if (r < 0) - return r; - } + assert(manager); UnitType *t, *previous_type = NULL; for (int i = 0; i < _UNIT_TYPE_MAX; i++) { @@ -135,7 +113,7 @@ int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varlink_met t = &type; if (previous_type) { - r = list_unit_type_one(link, manager, previous_type, /* more = */ true); + r = unit_types_total_build_json_one(link, manager, previous_type, more); if (r < 0) return r; } @@ -144,21 +122,50 @@ int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varlink_met } if (previous_type) { - r = list_unit_type_one(link, manager, previous_type, /* more = */ true); + r = unit_types_total_build_json_one(link, manager, previous_type, more); if (r < 0) return r; } else return sd_varlink_error(link, VARLINK_ERROR_METRICS_NO_SUCH_METRIC, NULL); + return 0; +} + +static int unit_states_total_build_json(sd_varlink *link, void *userdata, bool more) { + int r; + Manager *manager = ASSERT_PTR(userdata); + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + + assert(link); + + r = unit_states_total_build_json_one( + link, + UNIT_ACTIVE, + hashmap_size(manager->units), + more); + if (r < 0) + return r; + + return unit_states_total_build_json_one( + link, + UNIT_FAILED, + set_size(manager->failed_units), + more); +} + +static int unit_state_build_json(sd_varlink *link, void *userdata, bool more) { + int r; const char *k; Unit *u, *previous = NULL; + Manager *manager = ASSERT_PTR(userdata); + HASHMAP_FOREACH_KEY(u, k, manager->units) { /* ignore aliases */ if (k != u->id) continue; if (previous) { - r = list_per_unit_metrics_one(link, previous, /* more = */ true); + r = unit_state_build_json_one(link, previous, /* more = */ true); if (r < 0) return r; } @@ -167,8 +174,66 @@ int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varlink_met } if (previous) - return list_per_unit_metrics_one(link, previous, /* more = */ false); + return unit_state_build_json_one(link, previous, more); return sd_varlink_error(link, VARLINK_ERROR_METRICS_NO_SUCH_METRIC, NULL); +} + + +const MetricFamily metric_family_table[] = { + METRIC_FAMILY( + METRIC_IO_SYSTEMD_MANAGER_UNIT_STATES_TOTAL, + + "Total counts of units of different states", + METRIC_FAMILY_TYPE_GAUGE, + unit_states_total_build_json), + METRIC_FAMILY( + METRIC_IO_SYSTEMD_MANAGER_UNIT_TYPES_TOTAL, + "Total counts of units of different types", + METRIC_FAMILY_TYPE_GAUGE, + unit_types_total_build_json), + METRIC_FAMILY( + METRIC_IO_SYSTEMD_MANAGER_UNIT_STATE, + "Per unit metrics", + METRIC_FAMILY_TYPE_GAUGE, + unit_state_build_json), + {}, + +}; + +int vl_method_list( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + int r; + + 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 = previous->cb(link, userdata, true); + if (r < 0) + return log_debug_errno(r, "Failed to list metrics for metric family '%s': %m", previous->name); + } + + previous = mf; + } + + if (previous) { + r = previous->cb(link, userdata, false); + if (r < 0) + return log_debug_errno(r, "Failed to list metrics for metric family '%s': %m", previous->name); + } + return 0; } diff --git a/src/core/varlink-metrics.h b/src/core/varlink-metrics.h index cf399cb9d2226..e40e3ed507ce0 100644 --- a/src/core/varlink-metrics.h +++ b/src/core/varlink-metrics.h @@ -4,6 +4,10 @@ #include "forward.h" #define VARLINK_ERROR_METRICS_NO_SUCH_METRIC "io.systemd.Metrics.NoSuchMetric" -#define IO_SYSTEMD_MANAGER "io.systemd.Manager." + +#define METRIC_IO_SYSTEMD_MANAGER_PREFIX "io.systemd.Manager." +#define METRIC_IO_SYSTEMD_MANAGER_UNIT_STATES_TOTAL METRIC_IO_SYSTEMD_MANAGER_PREFIX "unit_states_total" +#define METRIC_IO_SYSTEMD_MANAGER_UNIT_TYPES_TOTAL METRIC_IO_SYSTEMD_MANAGER_PREFIX "unit_types_total" +#define METRIC_IO_SYSTEMD_MANAGER_UNIT_STATE METRIC_IO_SYSTEMD_MANAGER_PREFIX "unit_state" int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/shared/metrics.c b/src/shared/metrics.c index 807d1e7536031..2356fc89fca90 100644 --- a/src/shared/metrics.c +++ b/src/shared/metrics.c @@ -1,9 +1,12 @@ -#include +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "json-util.h" #include "log.h" #include "metrics.h" -#include "varlink-serialize.h" +#include "string-table.h" +#include "strv.h" #include "varlink-io.systemd.Metrics.h" +#include "varlink-serialize.h" #include "varlink-util.h" int metrics_setup_varlink_server( @@ -57,3 +60,78 @@ int metrics_listen_varlink_address(sd_varlink_server *server, const char *addres 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); + +int metric_family_json_build(sd_json_variant **v, const MetricFamily *metric_family) { + assert(metric_family); + return sd_json_buildo( + ASSERT_PTR(v), + SD_JSON_BUILD_PAIR_STRING("name", metric_family->name), + SD_JSON_BUILD_PAIR_STRING("description", metric_family->description), + SD_JSON_BUILD_PAIR_STRING("type", metric_family_type_to_string(metric_family->type))); +} + +static int metric_fields_build_json(sd_json_variant **ret, const char *name, void *userdata) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + char **fields = ASSERT_PTR(userdata); + int r; + + assert(ret); + assert(name); + + if (strv_isempty(fields)) { + *ret = NULL; + return 0; + } + + STRV_FOREACH_PAIR(fk, fv, fields) { + r = sd_json_variant_merge_objectbo(&v, SD_JSON_BUILD_PAIR_STRING(*fk, *fv)); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(v); + return 0; +} + +static int metric_json_build_body(sd_json_variant **v, const char *name, const char *object, char **fields) { + return sd_json_buildo( + ASSERT_PTR(v), + SD_JSON_BUILD_PAIR_STRING("name", ASSERT_PTR(name)), + JSON_BUILD_PAIR_STRING_NON_EMPTY("object", object), + JSON_BUILD_PAIR_CALLBACK_NON_NULL("fields", metric_fields_build_json, fields)); +} + +int metric_json_build_unsigned(sd_json_variant **v, const char *name, const char *object, uint64_t value, char **fields) { + int r; + r = metric_json_build_body(v, name, object, fields); + if (r < 0) + return r; + + return sd_json_variant_set_field_unsigned(v, "value", value); +} + +int metric_json_build_integer(sd_json_variant **v, const char *name, const char *object, int64_t value, char **fields) { + int r; + r = metric_json_build_body(v, name, object, fields); + if (r < 0) + return r; + + return sd_json_variant_set_field_integer(v, "value", value); +} + +int metric_json_build_string(sd_json_variant **v, const char *name, const char *object, const char *value, char **fields) { + int r; + r = metric_json_build_body(v, name, object, fields); + if (r < 0) + return r; + + return sd_json_variant_set_field_string(v, "value", value); +} diff --git a/src/shared/metrics.h b/src/shared/metrics.h index 326c64542ba61..b54598d1d9823 100644 --- a/src/shared/metrics.h +++ b/src/shared/metrics.h @@ -3,6 +3,25 @@ #include "sd-varlink.h" +#include "forward.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 int (*metric_family_get_t) (sd_varlink *link, void *userdata, bool more); + +typedef struct MetricFamily { + const char *name; + const char *description; + MetricFamilyType type; + metric_family_get_t cb; +} MetricFamily; + int metrics_setup_varlink_server( sd_varlink_server **server, /* in and out param */ sd_varlink_server_flags_t flags, @@ -11,3 +30,5 @@ int metrics_setup_varlink_server( void *userdata); int metrics_listen_varlink_address(sd_varlink_server *server, const char *address); + +const char* metric_family_type_to_string(MetricFamilyType t) _const_;int metric_family_json_build(sd_json_variant **v, const MetricFamily *metric_family);#define METRIC_FAMILY(_name, _description, _type, _cb) \ { \ .name = _name, \ .description = _description, \ .type = _type, \ .cb = _cb, \ }#define METRIC_JSON_BUILD_UNSIGNED(_v, _name, _object, _value, ...) metric_json_build_unsigned(_v, _name, _object, _value, STRV_MAKE(__VA_ARGS__))#define METRIC_JSON_BUILD_INTEGER(_v, _name, object, value, ...) metric_json_build_integer(_v, _name, _object, _value, STRV_MAKE(__VA_ARGS__))#define METRIC_JSON_BUILD_STRING(_v, _name, object, value, ...) metric_json_build_string(_v, _name, _object, _value, STRV_MAKE(__VA_ARGS__))int metric_json_build_unsigned(sd_json_variant **v, const char *name, const char *object, uint64_t value, char **fields);int metric_json_build_integer(sd_json_variant **v, const char *name, const char *object, int64_t value, char **fields);int metric_json_build_string(sd_json_variant **v, const char *name, const char *object, const char *value, char **fields); From e2c1002126402a53ade5d7c1299ec1c210ecdf02 Mon Sep 17 00:00:00 2001 From: Yaping Li <202858510+YapingLi04@users.noreply.github.com> Date: Sun, 10 Aug 2025 01:54:26 -0700 Subject: [PATCH 5/6] [metrics] Add Describe() Tags: --- src/core/varlink-metrics.c | 55 +++++++++++++++++++++- src/core/varlink-metrics.h | 6 +++ src/core/varlink.c | 1 + src/shared/metrics.c | 6 ++- src/shared/metrics.h | 2 + test/units/TEST-74-AUX-UTILS.varlinkctl.sh | 1 + 6 files changed, 69 insertions(+), 2 deletions(-) diff --git a/src/core/varlink-metrics.c b/src/core/varlink-metrics.c index 9a85b039738cd..362d1cfff6ffa 100644 --- a/src/core/varlink-metrics.c +++ b/src/core/varlink-metrics.c @@ -134,7 +134,6 @@ static int unit_types_total_build_json(sd_varlink *link, void *userdata, bool mo static int unit_states_total_build_json(sd_varlink *link, void *userdata, bool more) { int r; Manager *manager = ASSERT_PTR(userdata); - _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; assert(link); @@ -179,6 +178,23 @@ static int unit_state_build_json(sd_varlink *link, void *userdata, bool more) { return sd_varlink_error(link, VARLINK_ERROR_METRICS_NO_SUCH_METRIC, NULL); } +static int vtable_describe_metrics_build_json_one(sd_varlink *link, const MetricFamily* metric_family, bool more) { + int r; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + + assert(link); + assert(metric_family); + + + r = metric_family_json_build(&v, metric_family); + if (r < 0) + return r; + + if (more) + return sd_varlink_notify(link, v); + + return sd_varlink_reply(link, v); +} const MetricFamily metric_family_table[] = { METRIC_FAMILY( @@ -237,3 +253,40 @@ int vl_method_list( return 0; } + +int vl_method_describe( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata) { + int r; + + 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 = vtable_describe_metrics_build_json_one(link, previous, true); + if (r < 0) + return log_debug_errno(r, "Failed to describe metric family '%s': %m", previous->name); + } + + previous = mf; + } + + if (previous) { + r = vtable_describe_metrics_build_json_one(link, previous, false); + if (r < 0) + return log_debug_errno(r, "Failed to describe metric family '%s': %m", previous->name); + } + + return 0; +} diff --git a/src/core/varlink-metrics.h b/src/core/varlink-metrics.h index e40e3ed507ce0..93d008a1d0a19 100644 --- a/src/core/varlink-metrics.h +++ b/src/core/varlink-metrics.h @@ -11,3 +11,9 @@ #define METRIC_IO_SYSTEMD_MANAGER_UNIT_STATE METRIC_IO_SYSTEMD_MANAGER_PREFIX "unit_state" int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); + +int vl_method_describe( + sd_varlink *link, + sd_json_variant *parameters, + sd_varlink_method_flags_t flags, + void *userdata); diff --git a/src/core/varlink.c b/src/core/varlink.c index f1882d91e826f..fb1d058179e49 100644 --- a/src/core/varlink.c +++ b/src/core/varlink.c @@ -448,6 +448,7 @@ static int manager_varlink_metrics_init(Manager *m) { flags, m->event, vl_method_list, + vl_method_describe, m); if (r < 0) return r; diff --git a/src/shared/metrics.c b/src/shared/metrics.c index 2356fc89fca90..e7cedca4dce86 100644 --- a/src/shared/metrics.c +++ b/src/shared/metrics.c @@ -14,6 +14,7 @@ int metrics_setup_varlink_server( 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; @@ -32,7 +33,10 @@ int metrics_setup_varlink_server( if (r < 0) return log_debug_errno(r, "Failed to add varlink metrics interface to varlink server: %m"); - r = sd_varlink_server_bind_method(s, "io.systemd.Metrics.List", vl_method_list_cb); + 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"); diff --git a/src/shared/metrics.h b/src/shared/metrics.h index b54598d1d9823..d8abf5bb2903e 100644 --- a/src/shared/metrics.h +++ b/src/shared/metrics.h @@ -1,5 +1,6 @@ #pragma once +#include "forward.h" #include "sd-varlink.h" @@ -27,6 +28,7 @@ int metrics_setup_varlink_server( 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); int metrics_listen_varlink_address(sd_varlink_server *server, const char *address); diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh index 4bbf3a35f060c..8e52529481c80 100755 --- a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh @@ -199,6 +199,7 @@ varlinkctl call /run/systemd/io.systemd.Manager io.systemd.Unit.List '{"pid": {" # test io.systemd.Metrics varlinkctl info /run/systemd/metrics/io.systemd.Manager varlinkctl --more call /run/systemd/metrics/io.systemd.Manager io.systemd.Metrics.List {} +varlinkctl --more call /run/systemd/metrics/io.systemd.Manager io.systemd.Metrics.Describe {} # test io.systemd.Manager in user manager testuser_uid=$(id -u testuser) From 58870f4beb00b12b120bab1c6be720944ed2db49 Mon Sep 17 00:00:00 2001 From: Yaping Li <202858510+YapingLi04@users.noreply.github.com> Date: Thu, 14 Aug 2025 07:46:36 -0700 Subject: [PATCH 6/6] [metrics] Add basic CLI --- meson.build | 1 + src/core/varlink-metrics.c | 234 ++++++++++++++------- src/core/varlink-metrics.h | 9 +- src/report/meson.build | 9 + src/report/report.c | 182 ++++++++++++++++ src/shared/metrics.h | 22 +- test/units/TEST-74-AUX-UTILS.varlinkctl.sh | 3 + 7 files changed, 379 insertions(+), 81 deletions(-) create mode 100644 src/report/meson.build create mode 100644 src/report/report.c diff --git a/meson.build b/meson.build index f3d0a4b51534e..8c48d8c7e1b0e 100644 --- a/meson.build +++ b/meson.build @@ -2383,6 +2383,7 @@ subdir('src/random-seed') subdir('src/rc-local-generator') subdir('src/remount-fs') subdir('src/repart') +subdir('src/report') subdir('src/reply-password') subdir('src/resolve') subdir('src/rfkill') diff --git a/src/core/varlink-metrics.c b/src/core/varlink-metrics.c index 362d1cfff6ffa..b94151407e9ad 100644 --- a/src/core/varlink-metrics.c +++ b/src/core/varlink-metrics.c @@ -1,14 +1,13 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ - #include #include #include - #include "hashmap.h" #include "install.h" #include "json-util.h" #include "manager.h" #include "metrics.h" +#include "service.h" #include "set.h" #include "sd-json.h" #include "sd-varlink.h" @@ -16,7 +15,7 @@ #include "unit-def.h" #include "varlink-metrics.h" -static int unit_states_total_build_json_one( +static int units_by_state_total_build_json_one( sd_varlink *link, UnitActiveState state, unsigned count, @@ -27,13 +26,12 @@ static int unit_states_total_build_json_one( assert(link); r = METRIC_JSON_BUILD_UNSIGNED( - &v, - METRIC_IO_SYSTEMD_MANAGER_UNIT_STATES_TOTAL, - /* object= */ NULL, - count, - /* fields */ - "state", - unit_active_state_to_string(state)); + &v, + METRIC_IO_SYSTEMD_MANAGER_UNITS_BY_STATE_TOTAL, + /* object= */ NULL, + count, + /* fields */ "state", + unit_active_state_to_string(state)); if (r < 0) return r; @@ -43,7 +41,29 @@ static int unit_states_total_build_json_one( return sd_varlink_reply(link, v); } -static int unit_types_total_build_json_one(sd_varlink *link, Manager *manager, UnitType *type, bool more) { +static int units_by_state_total_build_json(sd_varlink *link, void *userdata, bool more) { + int r; + Manager *manager = ASSERT_PTR(userdata); + + assert(link); + + r = units_by_state_total_build_json_one( + link, + UNIT_ACTIVE, + hashmap_size(manager->units), + more); + + if (r < 0) + return r; + + return units_by_state_total_build_json_one( + link, + UNIT_FAILED, + set_size(manager->failed_units), + more); +} + +static int units_by_type_total_build_json_one(sd_varlink *link, Manager *manager, UnitType *type, bool more) { int r; unsigned count = 0; _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; @@ -58,11 +78,11 @@ static int unit_types_total_build_json_one(sd_varlink *link, Manager *manager, U r = METRIC_JSON_BUILD_UNSIGNED( &v, - METRIC_IO_SYSTEMD_MANAGER_UNIT_TYPES_TOTAL, + METRIC_IO_SYSTEMD_MANAGER_UNITS_BY_TYPE_TOTAL, /* object= */ NULL, count, - /* fields */ - "type", unit_type_to_string(*type)); + /* fields */ "type", + unit_type_to_string(*type)); if (r < 0) return r; @@ -72,25 +92,43 @@ static int unit_types_total_build_json_one(sd_varlink *link, Manager *manager, U return sd_varlink_reply(link, v); } -static int unit_state_build_json_one(sd_varlink *link, Unit *unit, bool more) { +static int units_by_type_total_build_json(sd_varlink *link, void *userdata, bool more) { + int r; + Manager *manager = ASSERT_PTR(userdata); + + assert(link); + + UnitType *t, *previous_type = NULL; + UnitType type; + for (int i = 0; i < _UNIT_TYPE_MAX; i++) { + type = (UnitType) i; + t = &type; + + if (previous_type) { + r = units_by_type_total_build_json_one(link, manager, previous_type, more); + if (r < 0) + return r; + } + + previous_type = t; + } + + return units_by_type_total_build_json_one(link, manager, previous_type, more); +} + +static int unit_active_state_build_json_one(sd_varlink *link, Unit *unit, bool more) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; int r; assert(link); assert(unit); - - r = METRIC_JSON_BUILD_UNSIGNED( + r = METRIC_JSON_BUILD_STRING( &v, - METRIC_IO_SYSTEMD_MANAGER_UNIT_STATE, + METRIC_IO_SYSTEMD_MANAGER_UNIT_ACTIVE_STATE, unit->id, - /* value= */ 0, /* 0 is a placeholder and has no meaning */ - /* fields */ - "state", unit_active_state_to_string(unit_active_state(unit)), - "load_state", unit_load_state_to_string(unit->load_state), - "sub_state", unit_sub_state_to_string(unit), - "freezer_state", freezer_state_to_string(unit->freezer_state), - "unit_file_state", unit_file_state_to_string(unit_get_unit_file_state(unit))); + unit_active_state_to_string(unit_active_state(unit)), + /* fields */ NULL); if (r < 0) return r; @@ -100,71 +138,69 @@ static int unit_state_build_json_one(sd_varlink *link, Unit *unit, bool more) { return sd_varlink_reply(link, v); } -static int unit_types_total_build_json(sd_varlink *link, void *userdata, bool more) { +static int unit_active_state_build_json(sd_varlink *link, void *userdata, bool more) { int r; + const char *k; + Unit *u, *previous = NULL; Manager *manager = ASSERT_PTR(userdata); assert(link); - assert(manager); - - UnitType *t, *previous_type = NULL; - for (int i = 0; i < _UNIT_TYPE_MAX; i++) { - UnitType type = (UnitType) i; - t = &type; - if (previous_type) { - r = unit_types_total_build_json_one(link, manager, previous_type, more); + HASHMAP_FOREACH_KEY(u, k, manager->units) { + /* ignore aliases */ + if (k != u->id) + continue; + if (previous) { + r = unit_active_state_build_json_one(link, previous, /* more = */ true); if (r < 0) return r; } - previous_type = t; - } + previous = u; + } - if (previous_type) { - r = unit_types_total_build_json_one(link, manager, previous_type, more); - if (r < 0) - return r; - } else - return sd_varlink_error(link, VARLINK_ERROR_METRICS_NO_SUCH_METRIC, NULL); + if (previous) + return unit_active_state_build_json_one(link, previous, more); - return 0; + return sd_varlink_error(link, VARLINK_ERROR_METRICS_NO_SUCH_METRIC, NULL); } -static int unit_states_total_build_json(sd_varlink *link, void *userdata, bool more) { +static int unit_load_state_build_json_one(sd_varlink *link, Unit *unit, bool more) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; int r; - Manager *manager = ASSERT_PTR(userdata); assert(link); + assert(unit); - r = unit_states_total_build_json_one( - link, - UNIT_ACTIVE, - hashmap_size(manager->units), - more); + r = METRIC_JSON_BUILD_STRING( + &v, + METRIC_IO_SYSTEMD_MANAGER_UNIT_LOAD_STATE, + unit->id, + unit_load_state_to_string(unit->load_state), + /* fields */ NULL); if (r < 0) return r; - return unit_states_total_build_json_one( - link, - UNIT_FAILED, - set_size(manager->failed_units), - more); + if (more) + return sd_varlink_notify(link, v); + + return sd_varlink_reply(link, v); } -static int unit_state_build_json(sd_varlink *link, void *userdata, bool more) { +static int unit_load_state_build_json(sd_varlink *link, void *userdata, bool more) { int r; const char *k; Unit *u, *previous = NULL; Manager *manager = ASSERT_PTR(userdata); + assert(link); + HASHMAP_FOREACH_KEY(u, k, manager->units) { /* ignore aliases */ if (k != u->id) continue; - if (previous) { - r = unit_state_build_json_one(link, previous, /* more = */ true); + r = unit_load_state_build_json_one(link, previous, /* more = */ true); if (r < 0) return r; } @@ -173,7 +209,52 @@ static int unit_state_build_json(sd_varlink *link, void *userdata, bool more) { } if (previous) - return unit_state_build_json_one(link, previous, more); + return unit_load_state_build_json_one(link, previous, more); + + return sd_varlink_error(link, VARLINK_ERROR_METRICS_NO_SUCH_METRIC, NULL); +} + +static int nrestarts_build_json_one(sd_varlink *link, Unit *unit, bool more) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + int r; + + assert(link); + assert(unit); + + Service *service = SERVICE(unit); + + r = METRIC_JSON_BUILD_UNSIGNED( + &v, + METRIC_IO_SYSTEMD_MANAGER_SERVICE_NRESTARTS, + service->meta.id, + service->n_restarts, + /* fields */ NULL); + if (r < 0) + return r; + + if (more) + return sd_varlink_notify(link, v); + + return sd_varlink_reply(link, v); +} + +static int nrestarts_build_json(sd_varlink *link, void *userdata, bool more) { + Unit *previous = NULL; + Manager *manager = ASSERT_PTR(userdata); + + assert(link); + + LIST_FOREACH(units_by_type, u, manager->units_by_type[UNIT_SERVICE]) { + if (previous) { + int r = nrestarts_build_json_one(link, previous, /* more = */ true); + if (r < 0) + return r; + } + previous = u; + } + + if (previous) + return nrestarts_build_json_one(link, previous, more); return sd_varlink_error(link, VARLINK_ERROR_METRICS_NO_SUCH_METRIC, NULL); } @@ -185,7 +266,6 @@ static int vtable_describe_metrics_build_json_one(sd_varlink *link, const Metric assert(link); assert(metric_family); - r = metric_family_json_build(&v, metric_family); if (r < 0) return r; @@ -198,23 +278,31 @@ static int vtable_describe_metrics_build_json_one(sd_varlink *link, const Metric const MetricFamily metric_family_table[] = { METRIC_FAMILY( - METRIC_IO_SYSTEMD_MANAGER_UNIT_STATES_TOTAL, - + METRIC_IO_SYSTEMD_MANAGER_UNITS_BY_STATE_TOTAL, "Total counts of units of different states", METRIC_FAMILY_TYPE_GAUGE, - unit_states_total_build_json), + units_by_state_total_build_json), METRIC_FAMILY( - METRIC_IO_SYSTEMD_MANAGER_UNIT_TYPES_TOTAL, + METRIC_IO_SYSTEMD_MANAGER_UNITS_BY_TYPE_TOTAL, "Total counts of units of different types", METRIC_FAMILY_TYPE_GAUGE, - unit_types_total_build_json), + units_by_type_total_build_json), METRIC_FAMILY( - METRIC_IO_SYSTEMD_MANAGER_UNIT_STATE, - "Per unit metrics", + METRIC_IO_SYSTEMD_MANAGER_UNIT_ACTIVE_STATE, + "Per unit metric: active state", + METRIC_FAMILY_TYPE_STRING, + unit_active_state_build_json), + METRIC_FAMILY( + METRIC_IO_SYSTEMD_MANAGER_UNIT_LOAD_STATE, + "Per unit metric: load state", + METRIC_FAMILY_TYPE_STRING, + unit_load_state_build_json), + METRIC_FAMILY( + METRIC_IO_SYSTEMD_MANAGER_SERVICE_NRESTARTS, + "Per service metric: n_restarts state", METRIC_FAMILY_TYPE_GAUGE, - unit_state_build_json), + nrestarts_build_json), {}, - }; int vl_method_list( @@ -237,7 +325,7 @@ int vl_method_list( const MetricFamily *previous = NULL; for (const MetricFamily *mf = metric_family_table; mf && mf->name; mf++) { if (previous) { - r = previous->cb(link, userdata, true); + r = previous->cb(link, userdata, /* more */ true); if (r < 0) return log_debug_errno(r, "Failed to list metrics for metric family '%s': %m", previous->name); } @@ -246,7 +334,7 @@ int vl_method_list( } if (previous) { - r = previous->cb(link, userdata, false); + r = previous->cb(link, userdata, /* more */ false); if (r < 0) return log_debug_errno(r, "Failed to list metrics for metric family '%s': %m", previous->name); } @@ -274,7 +362,7 @@ int vl_method_describe( const MetricFamily *previous = NULL; for (const MetricFamily *mf = metric_family_table; mf && mf->name; mf++) { if (previous) { - r = vtable_describe_metrics_build_json_one(link, previous, true); + r = vtable_describe_metrics_build_json_one(link, previous, /* more */ true); if (r < 0) return log_debug_errno(r, "Failed to describe metric family '%s': %m", previous->name); } @@ -283,7 +371,7 @@ int vl_method_describe( } if (previous) { - r = vtable_describe_metrics_build_json_one(link, previous, false); + r = vtable_describe_metrics_build_json_one(link, previous, /* more */ false); if (r < 0) return log_debug_errno(r, "Failed to describe metric family '%s': %m", previous->name); } diff --git a/src/core/varlink-metrics.h b/src/core/varlink-metrics.h index 93d008a1d0a19..70a913599bc8d 100644 --- a/src/core/varlink-metrics.h +++ b/src/core/varlink-metrics.h @@ -6,10 +6,11 @@ #define VARLINK_ERROR_METRICS_NO_SUCH_METRIC "io.systemd.Metrics.NoSuchMetric" #define METRIC_IO_SYSTEMD_MANAGER_PREFIX "io.systemd.Manager." -#define METRIC_IO_SYSTEMD_MANAGER_UNIT_STATES_TOTAL METRIC_IO_SYSTEMD_MANAGER_PREFIX "unit_states_total" -#define METRIC_IO_SYSTEMD_MANAGER_UNIT_TYPES_TOTAL METRIC_IO_SYSTEMD_MANAGER_PREFIX "unit_types_total" -#define METRIC_IO_SYSTEMD_MANAGER_UNIT_STATE METRIC_IO_SYSTEMD_MANAGER_PREFIX "unit_state" - +#define METRIC_IO_SYSTEMD_MANAGER_UNITS_BY_STATE_TOTAL METRIC_IO_SYSTEMD_MANAGER_PREFIX "units_by_state_total" +#define METRIC_IO_SYSTEMD_MANAGER_UNITS_BY_TYPE_TOTAL METRIC_IO_SYSTEMD_MANAGER_PREFIX "units_by_type_total" +#define METRIC_IO_SYSTEMD_MANAGER_UNIT_ACTIVE_STATE METRIC_IO_SYSTEMD_MANAGER_PREFIX "unit_active_state" +#define METRIC_IO_SYSTEMD_MANAGER_UNIT_LOAD_STATE METRIC_IO_SYSTEMD_MANAGER_PREFIX "unit_load_state" +#define METRIC_IO_SYSTEMD_MANAGER_SERVICE_NRESTARTS METRIC_IO_SYSTEMD_MANAGER_PREFIX "service_nrestarts" int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int vl_method_describe( diff --git a/src/report/meson.build b/src/report/meson.build new file mode 100644 index 0000000000000..ab1ac45acce86 --- /dev/null +++ b/src/report/meson.build @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +executables += [ + executable_template + { + 'name' : 'systemd-report', + 'public' : true, + 'sources' : files('report.c'), + }, +] diff --git a/src/report/report.c b/src/report/report.c new file mode 100644 index 0000000000000..ec6ea62e9e4c5 --- /dev/null +++ b/src/report/report.c @@ -0,0 +1,182 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "alloc-util.h" +#include "build.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "log.h" +#include "main-func.h" +#include "sd-varlink.h" + +static int metrics_on_query_reply( + sd_varlink *link, + sd_json_variant *parameters, + const char *error, + sd_varlink_reply_flags_t flags, + void *userdata) { + + int *ret = ASSERT_PTR(userdata), r; + + assert(link); + + if (error) { + /* If we can translate this to an errno, let's print that as errno and return it, otherwise, return a generic error code */ + r = sd_varlink_error_to_errno(error, parameters); + if (r != -EBADR) + *ret = log_error_errno(r, "Method call failed: %m"); + else + r = *ret = log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call failed: %s", error); + } else + r = 0; + + sd_json_variant_dump(parameters, SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO, stdout, NULL); + + return r; +} + +static int metrics_call(const char *path) { + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + int r; + + assert(path); + + r = sd_varlink_connect_address(&vl, path); + if (r < 0) + return log_debug_errno(r, "Unable to connect to %s: %m", path); + + int ret = 0; + sd_varlink_set_userdata(vl, &ret); + + r = sd_varlink_bind_reply(vl, metrics_on_query_reply); + if (r < 0) + return log_debug_errno(r, "Failed to bind reply callback: %m"); + + r = sd_varlink_observe(vl, "io.systemd.Metrics.List", /* parameter */ NULL); + if (r < 0) + return log_debug_errno(r, "Failed to invoke varlink method: %m"); + + for (;;) { + r = sd_varlink_is_idle(vl); + + if (r < 0) + return log_error_errno(r, "Failed to check if varlink connection is idle: %m"); + if (r > 0) { + break; + } + + r = sd_varlink_process(vl); + if (r < 0) + return log_error_errno(r, "Failed to process varlink connection: %m"); + if (r != 0) + continue; + + r = sd_varlink_wait(vl, USEC_INFINITY); + if (r < 0) + return log_error_errno(r, "Failed to wait for varlink connection events: %m"); + } + + return ret; +} + +static int metrics_start_query(void) { + _cleanup_closedir_ DIR *d = NULL; + int r; + + d = opendir("/run/systemd/metrics/"); + if (!d) { + if (errno == ENOENT) + return -ESRCH; + + return -errno; + } + + FOREACH_DIRENT(de, d, return -errno) { + _cleanup_free_ char *p = NULL; + + p = path_join("/run/systemd/metrics/", de->d_name); + if (!p) + return -ENOMEM; + + r = metrics_call(p); + if (r < 0) + return r; + } + + return 0; +} + +static int help(void) { + printf("%s [OPTIONS...]\n\n" + "Print metrics for all systemd components .\n\n" + " -h --help Show this help\n" + " --version Show package version\n", + program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100 + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hp", options, NULL)) >= 0) + + switch (c) { + + case 'h': + return help(); + + case ARG_VERSION: + return version(); + + case '?': + return -EINVAL; + + default: + assert_not_reached(); + } + + if (optind < argc) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "%s takes no arguments.", + program_invocation_short_name); + + return 1; +} + +static int run(int argc, char *argv[]) { + int r; + + /* This is mostly intended to be used for scripts which want + * to detect whether we are being run in a virtualized + * environment or not */ + + log_setup(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + r = metrics_start_query(); + if (r < 0) + return r; + + return 0; +} + +DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/shared/metrics.h b/src/shared/metrics.h index d8abf5bb2903e..e60c046301230 100644 --- a/src/shared/metrics.h +++ b/src/shared/metrics.h @@ -1,9 +1,8 @@ #pragma once -#include "forward.h" +#include "sd-json.h" #include "sd-varlink.h" - #include "forward.h" typedef enum MetricFamilyType { @@ -32,5 +31,20 @@ int metrics_setup_varlink_server( void *userdata); int metrics_listen_varlink_address(sd_varlink_server *server, const char *address); - -const char* metric_family_type_to_string(MetricFamilyType t) _const_;int metric_family_json_build(sd_json_variant **v, const MetricFamily *metric_family);#define METRIC_FAMILY(_name, _description, _type, _cb) \ { \ .name = _name, \ .description = _description, \ .type = _type, \ .cb = _cb, \ }#define METRIC_JSON_BUILD_UNSIGNED(_v, _name, _object, _value, ...) metric_json_build_unsigned(_v, _name, _object, _value, STRV_MAKE(__VA_ARGS__))#define METRIC_JSON_BUILD_INTEGER(_v, _name, object, value, ...) metric_json_build_integer(_v, _name, _object, _value, STRV_MAKE(__VA_ARGS__))#define METRIC_JSON_BUILD_STRING(_v, _name, object, value, ...) metric_json_build_string(_v, _name, _object, _value, STRV_MAKE(__VA_ARGS__))int metric_json_build_unsigned(sd_json_variant **v, const char *name, const char *object, uint64_t value, char **fields);int metric_json_build_integer(sd_json_variant **v, const char *name, const char *object, int64_t value, char **fields);int metric_json_build_string(sd_json_variant **v, const char *name, const char *object, const char *value, char **fields); +const char* metric_family_type_to_string(MetricFamilyType t) _const_; +int metric_family_json_build(sd_json_variant **v, const MetricFamily *metric_family); + +#define METRIC_FAMILY(_name, _description, _type, _cb) \ + { \ + .name = _name, \ + .description = _description, \ + .type = _type, \ + .cb = _cb, \ + } + +#define METRIC_JSON_BUILD_UNSIGNED(_v, _name, _object, _value, ...) metric_json_build_unsigned(_v, _name, _object, _value, STRV_MAKE(__VA_ARGS__)) +#define METRIC_JSON_BUILD_INTEGER(_v, _name, _object, _value, ...) metric_json_build_integer(_v, _name, _object, _value, STRV_MAKE(__VA_ARGS__)) +#define METRIC_JSON_BUILD_STRING(_v, _name, _object, _value, ...) metric_json_build_string(_v, _name, _object, _value, STRV_MAKE(__VA_ARGS__)) +int metric_json_build_unsigned(sd_json_variant **v, const char *name, const char *object, uint64_t value, char **fields); +int metric_json_build_integer(sd_json_variant **v, const char *name, const char *object, int64_t value, char **fields); +int metric_json_build_string(sd_json_variant **v, const char *name, const char *object, const char *value, char **fields); diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh index 8e52529481c80..8d59cadd65fa0 100755 --- a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh +++ b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh @@ -210,6 +210,9 @@ systemd-run --wait --pipe --user --machine testuser@ \ systemd-run --wait --pipe --user --machine testuser@ \ varlinkctl call "/run/user/$testuser_uid/systemd/io.systemd.Manager" io.systemd.Manager.Describe '{}' +# test report in user manager +systemd-report + # test io.systemd.Unit in user manager systemd-run --wait --pipe --user --machine testuser@ \ varlinkctl --more call "/run/user/$testuser_uid/systemd/io.systemd.Manager" io.systemd.Unit.List '{}'