1
0
mirror of https://github.com/systemd/systemd synced 2026-03-08 06:04:45 +01:00

Compare commits

...

3 Commits

Author SHA1 Message Date
Yu Watanabe
710742270b
report: enforce metric prefix (#40647)
Addressing
> we should enforce that metrics ids begin with the varlink service name

from #40633
2026-02-17 06:47:50 +09:00
Ivan Kruglov
cc14f78c02 report: validate metric name by socket 2026-02-16 05:46:24 -08:00
Ivan Kruglov
b465e848f3 report: rearrange handling connection drop when skipping metrics 2026-02-16 02:34:11 -08:00

View File

@ -20,7 +20,6 @@
#include "sort-util.h"
#include "string-util.h"
#include "time-util.h"
#include "varlink-util.h"
#define METRICS_MAX 1024U
#define METRICS_LINKS_MAX 128U
@ -31,11 +30,44 @@ static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO
typedef struct Context {
sd_event *event;
Set *links;
Set *link_infos;
sd_json_variant **metrics; /* Collected metrics for sorting */
size_t n_metrics, n_skipped_metrics;
size_t n_metrics, n_skipped_metrics, n_invalid_metrics;
} Context;
typedef struct LinkInfo {
Context *context;
sd_varlink *link;
char *metric_prefix;
} LinkInfo;
static LinkInfo* link_info_free(LinkInfo *li) {
if (!li)
return NULL;
sd_varlink_close_unref(li->link);
free(li->metric_prefix);
return mfree(li);
}
static void context_done(Context *context) {
if (!context)
return;
set_free(context->link_infos);
sd_json_variant_unref_many(context->metrics, context->n_metrics);
sd_event_unref(context->event);
}
DEFINE_TRIVIAL_CLEANUP_FUNC(LinkInfo*, link_info_free);
DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
link_info_hash_ops,
void,
trivial_hash_func,
trivial_compare_func,
LinkInfo,
link_info_free);
static int metric_compare(sd_json_variant *const *a, sd_json_variant *const *b) {
const char *name_a, *name_b, *object_a, *object_b;
sd_json_variant *fields_a, *fields_b;
@ -67,6 +99,35 @@ static int metric_compare(sd_json_variant *const *a, sd_json_variant *const *b)
return strcmp_ptr(fields_str_a, fields_str_b);
}
static inline bool metric_startswith_prefix(const char *metric_name, const char *prefix) {
if (isempty(metric_name) || isempty(prefix))
return false;
const char *m = startswith(metric_name, prefix);
return !isempty(m) && m[0] == '.';
}
static bool metrics_validate_one(LinkInfo *li, sd_json_variant *metric) {
int r;
assert(li);
assert(metric);
static const sd_json_dispatch_field dispatch_table[] = {
{ "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, 0, SD_JSON_MANDATORY },
{}
};
const char *metric_name = NULL;
r = sd_json_dispatch(metric, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &metric_name);
if (r < 0) {
log_debug_errno(r, "Failed to get metric name, assuming name is not valid: %m");
return false;
}
return metric_startswith_prefix(metric_name, li->metric_prefix);
}
static int metrics_on_query_reply(
sd_varlink *link,
sd_json_variant *parameters,
@ -76,7 +137,8 @@ static int metrics_on_query_reply(
assert(link);
Context *context = ASSERT_PTR(userdata);
LinkInfo *li = ASSERT_PTR(userdata);
Context *context = ASSERT_PTR(li->context);
if (error_id) {
if (streq(error_id, SD_VARLINK_ERROR_DISCONNECTED))
@ -85,20 +147,31 @@ static int metrics_on_query_reply(
log_info("Varlink timed out");
else
log_error("Varlink error: %s", error_id);
} else if (context->n_metrics >= METRICS_MAX)
context->n_skipped_metrics++;
else {
/* Collect metrics for later sorting */
if (!GREEDY_REALLOC(context->metrics, context->n_metrics + 1))
return log_oom();
context->metrics[context->n_metrics++] = sd_json_variant_ref(parameters);
goto finish;
}
if (!FLAGS_SET(flags, SD_VARLINK_REPLY_CONTINUES)) {
assert_se(set_remove(context->links, link) == link);
link = sd_varlink_close_unref(link);
if (context->n_metrics >= METRICS_MAX) {
context->n_skipped_metrics++;
goto finish;
}
if (set_isempty(context->links))
if (!metrics_validate_one(li, parameters)) {
context->n_invalid_metrics++;
goto finish;
}
/* Collect metrics for later sorting */
if (!GREEDY_REALLOC(context->metrics, context->n_metrics + 1))
return log_oom();
context->metrics[context->n_metrics++] = sd_json_variant_ref(parameters);
finish:
if (!FLAGS_SET(flags, SD_VARLINK_REPLY_CONTINUES)) {
assert_se(set_remove(context->link_infos, li) == li);
link_info_free(li);
if (set_isempty(context->link_infos))
(void) sd_event_exit(context->event, EXIT_SUCCESS);
}
@ -116,8 +189,6 @@ static int metrics_call(Context *context, const char *path) {
if (r < 0)
return log_error_errno(r, "Unable to connect to %s: %m", path);
(void) sd_varlink_set_userdata(vl, context);
r = sd_varlink_set_relative_timeout(vl, TIMEOUT_USEC);
if (r < 0)
return log_error_errno(r, "Failed to set varlink timeout: %m");
@ -134,21 +205,27 @@ static int metrics_call(Context *context, const char *path) {
if (r < 0)
return log_error_errno(r, "Failed to issue io.systemd.Metrics.List call: %m");
if (set_ensure_put(&context->links, &varlink_hash_ops, vl) < 0)
_cleanup_(link_info_freep) LinkInfo *li = new(LinkInfo, 1);
if (!li)
return log_oom();
TAKE_PTR(vl);
*li = (LinkInfo) {
.context = context,
.link = sd_varlink_ref(vl),
};
r = path_extract_filename(path, &li->metric_prefix);
if (r < 0)
return log_error_errno(r, "Failed to extract metric name from path %s: %m", path);
(void) sd_varlink_set_userdata(vl, li);
if (set_ensure_put(&context->link_infos, &link_info_hash_ops, li) < 0)
return log_oom();
TAKE_PTR(li);
return 0;
}
static void context_done(Context *context) {
assert(context);
set_free(context->links);
sd_json_variant_unref_many(context->metrics, context->n_metrics);
sd_event_unref(context->event);
}
static int metrics_output_sorted(Context *context) {
int r;
@ -204,7 +281,7 @@ static int metrics_query(void) {
if (!IN_SET(de->d_type, DT_SOCK, DT_UNKNOWN))
continue;
if (set_size(context.links) >= METRICS_LINKS_MAX) {
if (set_size(context.link_infos) >= METRICS_LINKS_MAX) {
n_skipped_sources++;
break;
}
@ -216,7 +293,7 @@ static int metrics_query(void) {
(void) metrics_call(&context, p);
}
if (set_isempty(context.links))
if (set_isempty(context.link_infos))
log_info("No metrics sources found.");
else {
r = sd_event_loop(context.event);
@ -231,7 +308,11 @@ static int metrics_query(void) {
if (n_skipped_sources > 0)
return log_warning_errno(SYNTHETIC_ERRNO(EUCLEAN),
"Too many metrics sources, only %u sources contacted, %zu sources skipped.",
set_size(context.links), n_skipped_sources);
set_size(context.link_infos), n_skipped_sources);
if (context.n_invalid_metrics > 0)
return log_warning_errno(SYNTHETIC_ERRNO(EUCLEAN),
"%zu metrics are not valid.",
context.n_invalid_metrics);
if (context.n_skipped_metrics > 0)
return log_warning_errno(SYNTHETIC_ERRNO(EUCLEAN),
"Too many metrics, only %zu metrics collected, %zu metrics skipped.",