summaryrefslogtreecommitdiffstats
path: root/src/knot/modules/stats/stats.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/knot/modules/stats/stats.c')
-rw-r--r--src/knot/modules/stats/stats.c676
1 files changed, 676 insertions, 0 deletions
diff --git a/src/knot/modules/stats/stats.c b/src/knot/modules/stats/stats.c
new file mode 100644
index 0000000..26262ac
--- /dev/null
+++ b/src/knot/modules/stats/stats.c
@@ -0,0 +1,676 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "contrib/macros.h"
+#include "contrib/wire_ctx.h"
+#include "knot/include/module.h"
+#include "knot/nameserver/xfr.h" // Dependency on qdata->extra!
+
+#define MOD_PROTOCOL "\x10""request-protocol"
+#define MOD_OPERATION "\x10""server-operation"
+#define MOD_REQ_BYTES "\x0D""request-bytes"
+#define MOD_RESP_BYTES "\x0E""response-bytes"
+#define MOD_EDNS "\x0D""edns-presence"
+#define MOD_FLAG "\x0D""flag-presence"
+#define MOD_RCODE "\x0D""response-code"
+#define MOD_REQ_EOPT "\x13""request-edns-option"
+#define MOD_RESP_EOPT "\x14""response-edns-option"
+#define MOD_NODATA "\x0C""reply-nodata"
+#define MOD_QTYPE "\x0A""query-type"
+#define MOD_QSIZE "\x0A""query-size"
+#define MOD_RSIZE "\x0A""reply-size"
+
+#define OTHER "other"
+
+const yp_item_t stats_conf[] = {
+ { MOD_PROTOCOL, YP_TBOOL, YP_VBOOL = { true } },
+ { MOD_OPERATION, YP_TBOOL, YP_VBOOL = { true } },
+ { MOD_REQ_BYTES, YP_TBOOL, YP_VBOOL = { true } },
+ { MOD_RESP_BYTES, YP_TBOOL, YP_VBOOL = { true } },
+ { MOD_EDNS, YP_TBOOL, YP_VNONE },
+ { MOD_FLAG, YP_TBOOL, YP_VNONE },
+ { MOD_RCODE, YP_TBOOL, YP_VBOOL = { true } },
+ { MOD_REQ_EOPT, YP_TBOOL, YP_VNONE },
+ { MOD_RESP_EOPT, YP_TBOOL, YP_VNONE },
+ { MOD_NODATA, YP_TBOOL, YP_VNONE },
+ { MOD_QTYPE, YP_TBOOL, YP_VNONE },
+ { MOD_QSIZE, YP_TBOOL, YP_VNONE },
+ { MOD_RSIZE, YP_TBOOL, YP_VNONE },
+ { NULL }
+};
+
+enum {
+ CTR_PROTOCOL,
+ CTR_OPERATION,
+ CTR_REQ_BYTES,
+ CTR_RESP_BYTES,
+ CTR_EDNS,
+ CTR_FLAG,
+ CTR_RCODE,
+ CTR_REQ_EOPT,
+ CTR_RESP_EOPT,
+ CTR_NODATA,
+ CTR_QTYPE,
+ CTR_QSIZE,
+ CTR_RSIZE,
+};
+
+typedef struct {
+ bool protocol;
+ bool operation;
+ bool req_bytes;
+ bool resp_bytes;
+ bool edns;
+ bool flag;
+ bool rcode;
+ bool req_eopt;
+ bool resp_eopt;
+ bool nodata;
+ bool qtype;
+ bool qsize;
+ bool rsize;
+} stats_t;
+
+typedef struct {
+ yp_name_t *conf_name;
+ size_t conf_offset;
+ uint32_t count;
+ knotd_mod_idx_to_str_f fcn;
+} ctr_desc_t;
+
+enum {
+ OPERATION_QUERY = 0,
+ OPERATION_UPDATE,
+ OPERATION_NOTIFY,
+ OPERATION_AXFR,
+ OPERATION_IXFR,
+ OPERATION_INVALID,
+ OPERATION__COUNT
+};
+
+static char *operation_to_str(uint32_t idx, uint32_t count)
+{
+ switch (idx) {
+ case OPERATION_QUERY: return strdup("query");
+ case OPERATION_UPDATE: return strdup("update");
+ case OPERATION_NOTIFY: return strdup("notify");
+ case OPERATION_AXFR: return strdup("axfr");
+ case OPERATION_IXFR: return strdup("ixfr");
+ case OPERATION_INVALID: return strdup("invalid");
+ default: assert(0); return NULL;
+ }
+}
+
+enum {
+ PROTOCOL_UDP4 = 0,
+ PROTOCOL_TCP4,
+ PROTOCOL_QUIC4,
+ PROTOCOL_UDP6,
+ PROTOCOL_TCP6,
+ PROTOCOL_QUIC6,
+ PROTOCOL_UDP4_XDP,
+ PROTOCOL_TCP4_XDP,
+ PROTOCOL_QUIC4_XDP,
+ PROTOCOL_UDP6_XDP,
+ PROTOCOL_TCP6_XDP,
+ PROTOCOL_QUIC6_XDP,
+ PROTOCOL__COUNT
+};
+
+static char *protocol_to_str(uint32_t idx, uint32_t count)
+{
+ switch (idx) {
+ case PROTOCOL_UDP4: return strdup("udp4");
+ case PROTOCOL_TCP4: return strdup("tcp4");
+ case PROTOCOL_QUIC4: return strdup("quic4");
+ case PROTOCOL_UDP6: return strdup("udp6");
+ case PROTOCOL_TCP6: return strdup("tcp6");
+ case PROTOCOL_QUIC6: return strdup("quic6");
+ case PROTOCOL_UDP4_XDP: return strdup("udp4-xdp");
+ case PROTOCOL_TCP4_XDP: return strdup("tcp4-xdp");
+ case PROTOCOL_QUIC4_XDP: return strdup("quic4-xdp");
+ case PROTOCOL_UDP6_XDP: return strdup("udp6-xdp");
+ case PROTOCOL_TCP6_XDP: return strdup("tcp6-xdp");
+ case PROTOCOL_QUIC6_XDP: return strdup("quic6-xdp");
+ default: assert(0); return NULL;
+ }
+}
+
+enum {
+ REQ_BYTES_QUERY = 0,
+ REQ_BYTES_UPDATE,
+ REQ_BYTES_OTHER,
+ REQ_BYTES__COUNT
+};
+
+static char *req_bytes_to_str(uint32_t idx, uint32_t count)
+{
+ switch (idx) {
+ case REQ_BYTES_QUERY: return strdup("query");
+ case REQ_BYTES_UPDATE: return strdup("update");
+ case REQ_BYTES_OTHER: return strdup(OTHER);
+ default: assert(0); return NULL;
+ }
+}
+
+enum {
+ RESP_BYTES_REPLY = 0,
+ RESP_BYTES_TRANSFER,
+ RESP_BYTES_OTHER,
+ RESP_BYTES__COUNT
+};
+
+static char *resp_bytes_to_str(uint32_t idx, uint32_t count)
+{
+ switch (idx) {
+ case RESP_BYTES_REPLY: return strdup("reply");
+ case RESP_BYTES_TRANSFER: return strdup("transfer");
+ case RESP_BYTES_OTHER: return strdup(OTHER);
+ default: assert(0); return NULL;
+ }
+}
+
+enum {
+ EDNS_REQ = 0,
+ EDNS_RESP,
+ EDNS__COUNT
+};
+
+static char *edns_to_str(uint32_t idx, uint32_t count)
+{
+ switch (idx) {
+ case EDNS_REQ: return strdup("request");
+ case EDNS_RESP: return strdup("response");
+ default: assert(0); return NULL;
+ }
+}
+
+enum {
+ FLAG_DO = 0,
+ FLAG_TC,
+ FLAG__COUNT
+};
+
+static char *flag_to_str(uint32_t idx, uint32_t count)
+{
+ switch (idx) {
+ case FLAG_TC: return strdup("TC");
+ case FLAG_DO: return strdup("DO");
+ default: assert(0); return NULL;
+ }
+}
+
+enum {
+ NODATA_A = 0,
+ NODATA_AAAA,
+ NODATA_OTHER,
+ NODATA__COUNT
+};
+
+static char *nodata_to_str(uint32_t idx, uint32_t count)
+{
+ switch (idx) {
+ case NODATA_A: return strdup("A");
+ case NODATA_AAAA: return strdup("AAAA");
+ case NODATA_OTHER: return strdup(OTHER);
+ default: assert(0); return NULL;
+ }
+}
+
+#define RCODE_BADSIG 15 // Unassigned code internally used for BADSIG.
+#define RCODE_OTHER (KNOT_RCODE_BADCOOKIE + 1) // Other RCODES.
+
+static char *rcode_to_str(uint32_t idx, uint32_t count)
+{
+ const knot_lookup_t *rcode = NULL;
+
+ switch (idx) {
+ case RCODE_BADSIG:
+ rcode = knot_lookup_by_id(knot_tsig_rcode_names, KNOT_RCODE_BADSIG);
+ break;
+ case RCODE_OTHER:
+ return strdup(OTHER);
+ default:
+ rcode = knot_lookup_by_id(knot_rcode_names, idx);
+ break;
+ }
+
+ if (rcode != NULL) {
+ return strdup(rcode->name);
+ } else {
+ return NULL;
+ }
+}
+
+#define EOPT_OTHER (KNOT_EDNS_MAX_OPTION_CODE + 1)
+#define req_eopt_to_str eopt_to_str
+#define resp_eopt_to_str eopt_to_str
+
+static char *eopt_to_str(uint32_t idx, uint32_t count)
+{
+ if (idx >= EOPT_OTHER) {
+ return strdup(OTHER);
+ }
+
+ char str[32];
+ if (knot_opt_code_to_string(idx, str, sizeof(str)) < 0) {
+ return NULL;
+ } else {
+ return strdup(str);
+ }
+}
+
+enum {
+ QTYPE_OTHER = 0,
+ QTYPE_MIN1 = 1,
+ QTYPE_MAX1 = 65,
+ QTYPE_MIN2 = 99,
+ QTYPE_MAX2 = 110,
+ QTYPE_MIN3 = 255,
+ QTYPE_MAX3 = 260,
+ QTYPE_SHIFT2 = QTYPE_MIN2 - QTYPE_MAX1 - 1,
+ QTYPE_SHIFT3 = QTYPE_SHIFT2 + QTYPE_MIN3 - QTYPE_MAX2 - 1,
+ QTYPE__COUNT = QTYPE_MAX3 - QTYPE_SHIFT3 + 1
+};
+
+static char *qtype_to_str(uint32_t idx, uint32_t count)
+{
+ if (idx == QTYPE_OTHER) {
+ return strdup(OTHER);
+ }
+
+ uint16_t qtype;
+
+ if (idx <= QTYPE_MAX1) {
+ qtype = idx;
+ assert(qtype >= QTYPE_MIN1 && qtype <= QTYPE_MAX1);
+ } else if (idx <= QTYPE_MAX2 - QTYPE_SHIFT2) {
+ qtype = idx + QTYPE_SHIFT2;
+ assert(qtype >= QTYPE_MIN2 && qtype <= QTYPE_MAX2);
+ } else {
+ qtype = idx + QTYPE_SHIFT3;
+ assert(qtype >= QTYPE_MIN3 && qtype <= QTYPE_MAX3);
+ }
+
+ char str[32];
+ if (knot_rrtype_to_string(qtype, str, sizeof(str)) < 0) {
+ return NULL;
+ } else {
+ return strdup(str);
+ }
+}
+
+#define BUCKET_SIZE 16
+#define QSIZE_MAX_IDX (288 / BUCKET_SIZE)
+#define RSIZE_MAX_IDX (4096 / BUCKET_SIZE)
+
+static char *size_to_str(uint32_t idx, uint32_t count)
+{
+ char str[16];
+
+ int ret;
+ if (idx < count - 1) {
+ ret = snprintf(str, sizeof(str), "%u-%u", idx * BUCKET_SIZE,
+ (idx + 1) * BUCKET_SIZE - 1);
+ } else {
+ ret = snprintf(str, sizeof(str), "%u-65535", idx * BUCKET_SIZE);
+ }
+
+ if (ret <= 0 || (size_t)ret >= sizeof(str)) {
+ return NULL;
+ } else {
+ return strdup(str);
+ }
+}
+
+static char *qsize_to_str(uint32_t idx, uint32_t count)
+{
+ return size_to_str(idx, count);
+}
+
+static char *rsize_to_str(uint32_t idx, uint32_t count)
+{
+ return size_to_str(idx, count);
+}
+
+static const ctr_desc_t ctr_descs[] = {
+ #define item(macro, name, count) \
+ [CTR_##macro] = { MOD_##macro, offsetof(stats_t, name), (count), name##_to_str }
+ item(PROTOCOL, protocol, PROTOCOL__COUNT),
+ item(OPERATION, operation, OPERATION__COUNT),
+ item(REQ_BYTES, req_bytes, REQ_BYTES__COUNT),
+ item(RESP_BYTES, resp_bytes, RESP_BYTES__COUNT),
+ item(EDNS, edns, EDNS__COUNT),
+ item(FLAG, flag, FLAG__COUNT),
+ item(RCODE, rcode, RCODE_OTHER + 1),
+ item(REQ_EOPT, req_eopt, EOPT_OTHER + 1),
+ item(RESP_EOPT, resp_eopt, EOPT_OTHER + 1),
+ item(NODATA, nodata, NODATA__COUNT),
+ item(QTYPE, qtype, QTYPE__COUNT),
+ item(QSIZE, qsize, QSIZE_MAX_IDX + 1),
+ item(RSIZE, rsize, RSIZE_MAX_IDX + 1),
+ { NULL }
+};
+
+static void incr_edns_option(knotd_mod_t *mod, unsigned thr_id, const knot_pkt_t *pkt, unsigned ctr_name)
+{
+ if (!knot_pkt_has_edns(pkt)) {
+ return;
+ }
+
+ knot_rdata_t *rdata = pkt->opt_rr->rrs.rdata;
+ if (rdata == NULL || rdata->len == 0) {
+ return;
+ }
+
+ wire_ctx_t wire = wire_ctx_init_const(rdata->data, rdata->len);
+ while (wire_ctx_available(&wire) > 0) {
+ uint16_t opt_code = wire_ctx_read_u16(&wire);
+ uint16_t opt_len = wire_ctx_read_u16(&wire);
+ wire_ctx_skip(&wire, opt_len);
+ if (wire.error != KNOT_EOK) {
+ break;
+ }
+ knotd_mod_stats_incr(mod, thr_id, ctr_name, MIN(opt_code, EOPT_OTHER), 1);
+ }
+}
+
+static knotd_state_t update_counters(knotd_state_t state, knot_pkt_t *pkt,
+ knotd_qdata_t *qdata, knotd_mod_t *mod)
+{
+ assert(pkt && qdata);
+
+ stats_t *stats = knotd_mod_ctx(mod);
+
+ uint16_t operation;
+ unsigned xfr_packets = 0;
+ unsigned tid = qdata->params->thread_id;
+
+ // Get the server operation.
+ switch (qdata->type) {
+ case KNOTD_QUERY_TYPE_NORMAL:
+ operation = OPERATION_QUERY;
+ break;
+ case KNOTD_QUERY_TYPE_UPDATE:
+ operation = OPERATION_UPDATE;
+ break;
+ case KNOTD_QUERY_TYPE_NOTIFY:
+ operation = OPERATION_NOTIFY;
+ break;
+ case KNOTD_QUERY_TYPE_AXFR:
+ operation = OPERATION_AXFR;
+ if (qdata->extra->ext != NULL) {
+ xfr_packets = ((struct xfr_proc *)qdata->extra->ext)->stats.messages;
+ }
+ break;
+ case KNOTD_QUERY_TYPE_IXFR:
+ operation = OPERATION_IXFR;
+ if (qdata->extra->ext != NULL) {
+ xfr_packets = ((struct xfr_proc *)qdata->extra->ext)->stats.messages;
+ }
+ break;
+ default:
+ operation = OPERATION_INVALID;
+ break;
+ }
+
+ // Count request bytes.
+ if (stats->req_bytes) {
+ switch (operation) {
+ case OPERATION_QUERY:
+ knotd_mod_stats_incr(mod, tid, CTR_REQ_BYTES, REQ_BYTES_QUERY,
+ knot_pkt_size(qdata->query));
+ break;
+ case OPERATION_UPDATE:
+ knotd_mod_stats_incr(mod, tid, CTR_REQ_BYTES, REQ_BYTES_UPDATE,
+ knot_pkt_size(qdata->query));
+ break;
+ default:
+ if (xfr_packets <= 1) {
+ knotd_mod_stats_incr(mod, tid, CTR_REQ_BYTES, REQ_BYTES_OTHER,
+ knot_pkt_size(qdata->query));
+ }
+ break;
+ }
+ }
+
+ // Count response bytes.
+ if (stats->resp_bytes && state != KNOTD_STATE_NOOP) {
+ switch (operation) {
+ case OPERATION_QUERY:
+ knotd_mod_stats_incr(mod, tid, CTR_RESP_BYTES, RESP_BYTES_REPLY,
+ knot_pkt_size(pkt));
+ break;
+ case OPERATION_AXFR:
+ case OPERATION_IXFR:
+ knotd_mod_stats_incr(mod, tid, CTR_RESP_BYTES, RESP_BYTES_TRANSFER,
+ knot_pkt_size(pkt));
+ break;
+ default:
+ knotd_mod_stats_incr(mod, tid, CTR_RESP_BYTES, RESP_BYTES_OTHER,
+ knot_pkt_size(pkt));
+ break;
+ }
+ }
+
+ // Get the extended response code.
+ uint16_t rcode = qdata->rcode;
+ if (qdata->rcode_tsig != KNOT_RCODE_NOERROR) {
+ rcode = qdata->rcode_tsig;
+ }
+
+ // Count the response code.
+ if (stats->rcode && state != KNOTD_STATE_NOOP) {
+ if (xfr_packets <= 1 || rcode != KNOT_RCODE_NOERROR) {
+ if (xfr_packets > 1) {
+ assert(rcode != KNOT_RCODE_NOERROR);
+ // Ignore the leading XFR message NOERROR.
+ knotd_mod_stats_decr(mod, tid, CTR_RCODE,
+ KNOT_RCODE_NOERROR, 1);
+ }
+
+ if (qdata->rcode_tsig == KNOT_RCODE_BADSIG) {
+ knotd_mod_stats_incr(mod, tid, CTR_RCODE, RCODE_BADSIG, 1);
+ } else {
+ knotd_mod_stats_incr(mod, tid, CTR_RCODE,
+ MIN(rcode, RCODE_OTHER), 1);
+ }
+ }
+ }
+
+ // Return if non-first transfer message.
+ if (xfr_packets > 1) {
+ return state;
+ }
+
+ // Count the server operation.
+ if (stats->operation) {
+ knotd_mod_stats_incr(mod, tid, CTR_OPERATION, operation, 1);
+ }
+
+ // Count the request protocol.
+ if (stats->protocol) {
+ bool xdp = qdata->params->xdp_msg != NULL;
+ if (knotd_qdata_remote_addr(qdata)->ss_family == AF_INET) {
+ if (qdata->params->proto == KNOTD_QUERY_PROTO_UDP) {
+ if (xdp) {
+ knotd_mod_stats_incr(mod, tid, CTR_PROTOCOL,
+ PROTOCOL_UDP4_XDP, 1);
+ } else {
+ knotd_mod_stats_incr(mod, tid, CTR_PROTOCOL,
+ PROTOCOL_UDP4, 1);
+ }
+ } else if (qdata->params->proto == KNOTD_QUERY_PROTO_QUIC) {
+ if (xdp) {
+ knotd_mod_stats_incr(mod, tid, CTR_PROTOCOL,
+ PROTOCOL_QUIC4_XDP, 1);
+ } else {
+ knotd_mod_stats_incr(mod, tid, CTR_PROTOCOL,
+ PROTOCOL_QUIC4, 1);
+ }
+ } else {
+ if (xdp) {
+ knotd_mod_stats_incr(mod, tid, CTR_PROTOCOL,
+ PROTOCOL_TCP4_XDP, 1);
+ } else {
+ knotd_mod_stats_incr(mod, tid, CTR_PROTOCOL,
+ PROTOCOL_TCP4, 1);
+ }
+ }
+ } else {
+ if (qdata->params->proto == KNOTD_QUERY_PROTO_UDP) {
+ if (xdp) {
+ knotd_mod_stats_incr(mod, tid, CTR_PROTOCOL,
+ PROTOCOL_UDP6_XDP, 1);
+ } else {
+ knotd_mod_stats_incr(mod, tid, CTR_PROTOCOL,
+ PROTOCOL_UDP6, 1);
+ }
+ } else if (qdata->params->proto == KNOTD_QUERY_PROTO_QUIC) {
+ if (xdp) {
+ knotd_mod_stats_incr(mod, tid, CTR_PROTOCOL,
+ PROTOCOL_QUIC6_XDP, 1);
+ } else {
+ knotd_mod_stats_incr(mod, tid, CTR_PROTOCOL,
+ PROTOCOL_QUIC6, 1);
+ }
+ } else {
+ if (xdp) {
+ knotd_mod_stats_incr(mod, tid, CTR_PROTOCOL,
+ PROTOCOL_TCP6_XDP, 1);
+ } else {
+ knotd_mod_stats_incr(mod, tid, CTR_PROTOCOL,
+ PROTOCOL_TCP6, 1);
+ }
+ }
+ }
+ }
+
+ // Count EDNS occurrences.
+ if (stats->edns) {
+ if (knot_pkt_has_edns(qdata->query)) {
+ knotd_mod_stats_incr(mod, tid, CTR_EDNS, EDNS_REQ, 1);
+ }
+ if (knot_pkt_has_edns(pkt) && state != KNOTD_STATE_NOOP) {
+ knotd_mod_stats_incr(mod, tid, CTR_EDNS, EDNS_RESP, 1);
+ }
+ }
+
+ // Count interesting message header flags.
+ if (stats->flag) {
+ if (state != KNOTD_STATE_NOOP && knot_wire_get_tc(pkt->wire)) {
+ knotd_mod_stats_incr(mod, tid, CTR_FLAG, FLAG_TC, 1);
+ }
+ if (knot_pkt_has_dnssec(pkt)) {
+ knotd_mod_stats_incr(mod, tid, CTR_FLAG, FLAG_DO, 1);
+ }
+ }
+
+ // Count EDNS options.
+ if (stats->req_eopt) {
+ incr_edns_option(mod, tid, qdata->query, CTR_REQ_EOPT);
+ }
+ if (stats->resp_eopt) {
+ incr_edns_option(mod, tid, pkt, CTR_RESP_EOPT);
+ }
+
+ // Return if not query operation.
+ if (operation != OPERATION_QUERY) {
+ return state;
+ }
+
+ // Count NODATA reply (RFC 2308, Section 2.2).
+ if (stats->nodata && rcode == KNOT_RCODE_NOERROR && state != KNOTD_STATE_NOOP &&
+ knot_wire_get_ancount(pkt->wire) == 0 && !knot_wire_get_tc(pkt->wire) &&
+ (knot_wire_get_nscount(pkt->wire) == 0 ||
+ knot_pkt_rr(knot_pkt_section(pkt, KNOT_AUTHORITY), 0)->type == KNOT_RRTYPE_SOA)) {
+ switch (knot_pkt_qtype(qdata->query)) {
+ case KNOT_RRTYPE_A:
+ knotd_mod_stats_incr(mod, tid, CTR_NODATA, NODATA_A, 1);
+ break;
+ case KNOT_RRTYPE_AAAA:
+ knotd_mod_stats_incr(mod, tid, CTR_NODATA, NODATA_AAAA, 1);
+ break;
+ default:
+ knotd_mod_stats_incr(mod, tid, CTR_NODATA, NODATA_OTHER, 1);
+ break;
+ }
+ }
+
+ // Count the query type.
+ if (stats->qtype) {
+ uint16_t qtype = knot_pkt_qtype(qdata->query);
+
+ uint16_t idx;
+ switch (qtype) {
+ case QTYPE_MIN1 ... QTYPE_MAX1: idx = qtype; break;
+ case QTYPE_MIN2 ... QTYPE_MAX2: idx = qtype - QTYPE_SHIFT2; break;
+ case QTYPE_MIN3 ... QTYPE_MAX3: idx = qtype - QTYPE_SHIFT3; break;
+ default: idx = QTYPE_OTHER; break;
+ }
+
+ knotd_mod_stats_incr(mod, tid, CTR_QTYPE, idx, 1);
+ }
+
+ // Count the query size.
+ if (stats->qsize) {
+ uint64_t idx = knot_pkt_size(qdata->query) / BUCKET_SIZE;
+ knotd_mod_stats_incr(mod, tid, CTR_QSIZE, MIN(idx, QSIZE_MAX_IDX), 1);
+ }
+
+ // Count the reply size.
+ if (stats->rsize && state != KNOTD_STATE_NOOP) {
+ uint64_t idx = knot_pkt_size(pkt) / BUCKET_SIZE;
+ knotd_mod_stats_incr(mod, tid, CTR_RSIZE, MIN(idx, RSIZE_MAX_IDX), 1);
+ }
+
+ return state;
+}
+
+int stats_load(knotd_mod_t *mod)
+{
+ stats_t *stats = calloc(1, sizeof(*stats));
+ if (stats == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ for (const ctr_desc_t *desc = ctr_descs; desc->conf_name != NULL; desc++) {
+ knotd_conf_t conf = knotd_conf_mod(mod, desc->conf_name);
+ bool enabled = conf.single.boolean;
+
+ // Initialize corresponding configuration item.
+ *(bool *)((uint8_t *)stats + desc->conf_offset) = enabled;
+
+ int ret = knotd_mod_stats_add(mod, enabled ? desc->conf_name + 1 : NULL,
+ enabled ? desc->count : 1, desc->fcn);
+ if (ret != KNOT_EOK) {
+ free(stats);
+ return ret;
+ }
+ }
+
+ knotd_mod_ctx_set(mod, stats);
+
+ return knotd_mod_hook(mod, KNOTD_STAGE_END, update_counters);
+}
+
+void stats_unload(knotd_mod_t *mod)
+{
+ free(knotd_mod_ctx(mod));
+}
+
+KNOTD_MOD_API(stats, KNOTD_MOD_FLAG_SCOPE_ANY | KNOTD_MOD_FLAG_OPT_CONF,
+ stats_load, stats_unload, stats_conf, NULL);