From: Michael Tremer Date: Fri, 29 May 2026 11:11:13 +0000 (+0000) Subject: sources: Collect metrics for Knot Resolver X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=562c1317694e3737858d4b11d78c61b88674c898;p=telemetry.git sources: Collect metrics for Knot Resolver Signed-off-by: Michael Tremer --- diff --git a/Makefile.am b/Makefile.am index fccdcb1..851a354 100644 --- a/Makefile.am +++ b/Makefile.am @@ -206,6 +206,8 @@ dist_telemetryd_SOURCES = \ src/daemon/sources/ipfrag4.h \ src/daemon/sources/iptables.c \ src/daemon/sources/iptables.h \ + src/daemon/sources/knot-resolver.c \ + src/daemon/sources/knot-resolver.h \ src/daemon/sources/legacy-gateway-latency4.c \ src/daemon/sources/legacy-gateway-latency4.h \ src/daemon/sources/loadavg.c \ diff --git a/configure.ac b/configure.ac index 863c9c5..6f17f99 100644 --- a/configure.ac +++ b/configure.ac @@ -227,6 +227,7 @@ AC_SOURCE([hostapd]) AC_SOURCE([interface], [$have_libnl3 $have_libnl3_route]) AC_SOURCE([ipfrag4]) AC_SOURCE([iptables], [$have_libiptc]) +AC_SOURCE([knot-resolver]) AC_SOURCE([legacy-gateway-latency4], [$have_libnl3 $have_libnl3_route]) AC_SOURCE([loadavg]) AC_SOURCE([memory]) diff --git a/src/daemon/sources.c b/src/daemon/sources.c index 0dfb0fa..6334877 100644 --- a/src/daemon/sources.c +++ b/src/daemon/sources.c @@ -38,6 +38,7 @@ #include "sources/interface.h" #include "sources/ipfrag4.h" #include "sources/iptables.h" +#include "sources/knot-resolver.h" #include "sources/legacy-gateway-latency4.h" #include "sources/loadavg.h" #include "sources/memory.h" @@ -99,6 +100,10 @@ static const td_source_impl* source_impls[] = { &iptables_source, #endif /* BUILD_SOURCE_IPTABLES */ +#ifdef BUILD_SOURCE_KNOT_RESOLVER + &knot_resolver_source, +#endif /* BUILD_SOURCE_KNOT_RESOLVER */ + #ifdef BUILD_SOURCE_LEGACY_GATEWAY_LATENCY4 &legacy_gateway_latency4_source, #endif /* BUILD_SOURCE_LEGACY_GATEWAY_LATENCY4 */ diff --git a/src/daemon/sources/knot-resolver.c b/src/daemon/sources/knot-resolver.c new file mode 100644 index 0000000..933968d --- /dev/null +++ b/src/daemon/sources/knot-resolver.c @@ -0,0 +1,276 @@ +/*############################################################################# +# # +# telemetryd - The IPFire Telemetry Collection Service # +# Copyright (C) 2025 IPFire Development Team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU 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 . # +# # +#############################################################################*/ + +#ifdef BUILD_SOURCE_KNOT_RESOLVER + +#include +#include + +#include "../command.h" +#include "../ctx.h" +#include "../source.h" +#include "../string.h" +#include "knot-resolver.h" + +typedef struct knot_resolver_metric { + sd_json_variant* json; + const char* key; + const char* field; +} knot_resolver_metric; + +static int knot_resolver_parse_worker(td_ctx* ctx, + td_metrics* metrics, sd_json_variant* json) { + sd_json_variant* request = NULL; + sd_json_variant* answer = NULL; + sd_json_variant* query = NULL; + int r = 0; + + // Fetch the request object + request = sd_json_variant_by_key(json, "request"); + if (!request) + return -EBADMSG; + + // Fetch the answer object + answer = sd_json_variant_by_key(json, "answer"); + if (!answer) + return -EBADMSG; + + // Fetch the query object + query = sd_json_variant_by_key(json, "query"); + if (!query) + return -EBADMSG; + + const knot_resolver_metric keys[] = { + // Query + { query, "dnssec", "query_dnssec", }, + { query, "edns", "query_edns", }, + + // Request + { request, "total", "request_total", }, + { request, "total6", "request_total6", }, + { request, "total4", "request_total4", }, + { request, "internal", "request_internal", }, + { request, "udp", "request_udp", }, + { request, "udp6", "request_udp6", }, + { request, "udp4", "request_udp4", }, + { request, "tcp", "request_tcp", }, + { request, "tcp6", "request_tcp6", }, + { request, "tcp4", "request_tcp4", }, + { request, "dot", "request_dot", }, + { request, "dot6", "request_dot6", }, + { request, "dot4", "request_dot4", }, + { request, "doh", "request_doh", }, + { request, "doh6", "request_doh6", }, + { request, "doh4", "request_doh4", }, + { request, "doq", "request_doq", }, + { request, "doq6", "request_doq6", }, + { request, "doq4", "request_doq4", }, + { request, "xdp", "request_xdp", }, + { request, "xdp6", "request_xdp6", }, + { request, "xdp4", "request_xdp4", }, + + // Answer + { answer, "total", "answer_total", }, + { answer, "noerror", "answer_noerror", }, + { answer, "nxdomain", "answer_nxdomain", }, + { answer, "servfail", "answer_servfail", }, + { answer, "nodata", "answer_nodata", }, + { answer, "cached", "answer_cached", }, + { answer, "stale", "answer_stale", }, + + // Answer Flags + { answer, "aa", "answer_aa", }, + { answer, "ad", "answer_ad", }, + { answer, "cd", "answer_cd", }, + { answer, "do", "answer_do", }, + { answer, "ra", "answer_ra", }, + { answer, "rd", "answer_rd", }, + { answer, "tc", "answer_tc", }, + { answer, "edns0", "answer_edns0", }, + + // Answer Response Time + { answer, "1ms", "answer_1ms", }, + { answer, "10ms", "answer_10ms", }, + { answer, "50ms", "answer_50ms", }, + { answer, "100ms", "answer_100ms", }, + { answer, "250ms", "answer_250ms", }, + { answer, "500ms", "answer_500ms", }, + { answer, "1000ms", "answer_1000ms", }, + { answer, "1500ms", "answer_1500ms", }, + { answer, "slow", "answer_slow", }, + { answer, "sum_ms", "answer_sum_ms", }, + + { NULL }, + }; + + // Extract all metrics from JSON and push them into our metrics object + for (const knot_resolver_metric* key = keys; key->field; key++) { + r = td_metrics_push_uint64_from_json(metrics, + key->field, key->json, key->key); + if (r < 0) + return r; + } + + return 0; +} + +static int knot_resolver_parse_metrics(td_ctx* ctx, + td_metrics* metrics, sd_json_variant* json) { + sd_json_variant* worker = NULL; + char name[NAME_MAX]; + int r; + + int i = 0; + + for (;;) { + // Format the field name + r = td_string_format(name, "kresd:kresd%d", i++); + if (r < 0) + return r; + + // Fetch the worker object + worker = sd_json_variant_by_key(json, name); + if (!worker) + break; + + // Parse the worker + r = knot_resolver_parse_worker(ctx, metrics, worker); + if (r < 0) + return r; + } + + return 0; +} + +static int knot_resolver_on_success(td_ctx* ctx, + int rc, td_file* stdout, void* data) { + sd_json_variant* json = NULL; + td_metrics* metrics = NULL; + td_source* source = data; + int r; + + // Parse the output as JSON + r = td_file_parse_json(stdout, &json, 0); + if (r < 0) + goto ERROR; + + // Create a new metrics object + r = td_source_create_metrics(source, &metrics, NULL); + if (r < 0) + goto ERROR; + + // Parse the message + r = knot_resolver_parse_metrics(ctx, metrics, json); + if (r < 0) + goto ERROR; + + // Submit all collected metrics + r = td_source_submit_metrics(source, metrics); + +ERROR: + if (json) + sd_json_variant_unref(json); + if (metrics) + td_metrics_unref(metrics); + + return r; +} + +static int knot_resolver_heartbeat(td_ctx* ctx, td_source* source) { + // Run kresctl to fetch metrics + const char* argv[] = { + "kresctl", "metrics", NULL, + }; + + return td_source_run_command(source, NULL, argv, knot_resolver_on_success, source); +} + +const td_source_impl knot_resolver_source = { + .name = "knot-resolver", + + // RRD Data Sources + .rrd_dss = { + // Query + { "query_dnssec", "DERIVE", 0, -1, }, + { "query_edns", "DERIVE", 0, -1, }, + + // Request + { "request_total", "DERIVE", 0, -1, }, + { "request_total6", "DERIVE", 0, -1, }, + { "request_total4", "DERIVE", 0, -1, }, + { "request_internal", "DERIVE", 0, -1, }, + { "request_udp", "DERIVE", 0, -1, }, + { "request_udp6", "DERIVE", 0, -1, }, + { "request_udp4", "DERIVE", 0, -1, }, + { "request_tcp", "DERIVE", 0, -1, }, + { "request_tcp6", "DERIVE", 0, -1, }, + { "request_tcp4", "DERIVE", 0, -1, }, + { "request_dot", "DERIVE", 0, -1, }, + { "request_dot6", "DERIVE", 0, -1, }, + { "request_dot4", "DERIVE", 0, -1, }, + { "request_doh", "DERIVE", 0, -1, }, + { "request_doh6", "DERIVE", 0, -1, }, + { "request_doh4", "DERIVE", 0, -1, }, + { "request_doq", "DERIVE", 0, -1, }, + { "request_doq6", "DERIVE", 0, -1, }, + { "request_doq4", "DERIVE", 0, -1, }, + { "request_xdp", "DERIVE", 0, -1, }, + { "request_xdp6", "DERIVE", 0, -1, }, + { "request_xdp4", "DERIVE", 0, -1, }, + + // Answer + { "answer_total", "DERIVE", 0, -1, }, + { "answer_noerror", "DERIVE", 0, -1, }, + { "answer_nxdomain", "DERIVE", 0, -1, }, + { "answer_servfail", "DERIVE", 0, -1, }, + { "answer_nodata", "DERIVE", 0, -1, }, + { "answer_cached", "DERIVE", 0, -1, }, + { "answer_stale", "DERIVE", 0, -1, }, + + // Answer Flags + { "answer_aa", "DERIVE", 0, -1, }, + { "answer_ad", "DERIVE", 0, -1, }, + { "answer_cd", "DERIVE", 0, -1, }, + { "answer_do", "DERIVE", 0, -1, }, + { "answer_ra", "DERIVE", 0, -1, }, + { "answer_rd", "DERIVE", 0, -1, }, + { "answer_tc", "DERIVE", 0, -1, }, + { "answer_edns0", "DERIVE", 0, -1, }, + + // Answer Response Time + { "answer_1ms", "DERIVE", 0, -1, }, + { "answer_10ms", "DERIVE", 0, -1, }, + { "answer_50ms", "DERIVE", 0, -1, }, + { "answer_100ms", "DERIVE", 0, -1, }, + { "answer_250ms", "DERIVE", 0, -1, }, + { "answer_500ms", "DERIVE", 0, -1, }, + { "answer_1000ms", "DERIVE", 0, -1, }, + { "answer_1500ms", "DERIVE", 0, -1, }, + { "answer_slow", "DERIVE", 0, -1, }, + { "answer_sum_ms", "DERIVE", 0, -1, }, + + { NULL }, + }, + + // Methods + .heartbeat = knot_resolver_heartbeat, +}; + +#endif /* BUILD_SOURCE_KNOT_RESOLVER */ diff --git a/src/daemon/sources/knot-resolver.h b/src/daemon/sources/knot-resolver.h new file mode 100644 index 0000000..4806774 --- /dev/null +++ b/src/daemon/sources/knot-resolver.h @@ -0,0 +1,30 @@ +/*############################################################################# +# # +# telemetryd - The IPFire Telemetry Collection Service # +# Copyright (C) 2025 IPFire Development Team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU 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 . # +# # +#############################################################################*/ + +#ifndef TELEMETRY_SOURCE_KNOT_RESOLVER_H +#define TELEMETRY_SOURCE_KNOT_RESOLVER_H +#ifdef BUILD_SOURCE_KNOT_RESOLVER + +#include "../source.h" + +extern const td_source_impl knot_resolver_source; + +#endif /* BUILD_SOURCE_KNOT_RESOLVER */ +#endif /* TELEMETRY_SOURCE_KNOT_RESOLVER_H */