]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
module/stats: collect various metrics for statistics
authorMarek Vavruša <marek.vavrusa@nic.cz>
Wed, 20 May 2015 15:07:20 +0000 (17:07 +0200)
committerMarek Vavruša <marek.vavrusa@nic.cz>
Mon, 25 May 2015 11:49:17 +0000 (13:49 +0200)
this hooks into query resolution and collects counters on answers, queries and iterator

modules/modules.mk
modules/stats/README.rst [new file with mode: 0644]
modules/stats/stats.c [new file with mode: 0644]
modules/stats/stats.mk [new file with mode: 0644]

index 3d285f300bab53e9a6536e065910a07bcf93d489..d0b08484adf9ba44e963a546054735e6b15e6e11 100644 (file)
@@ -1,5 +1,6 @@
 # List of built-in modules
 modules_TARGETS := hints \
+                   stats \
                    cachectl
 
 # List of Lua modules
diff --git a/modules/stats/README.rst b/modules/stats/README.rst
new file mode 100644 (file)
index 0000000..7fb2d1b
--- /dev/null
@@ -0,0 +1,25 @@
+.. _mod-stats:
+
+Statistics collector
+--------------------
+
+This modules gathers various counters from the query resolution and server internals,
+and offers them as a key-value storage.
+
+Properties
+^^^^^^^^^^
+
+.. function:: stats.get(key)
+
+  :param string key: i.e. ``"queries"``
+  :return: ``number``
+
+Return nominal value of given key. 
+
+.. function:: stats.set(key, val)
+
+  :param string key:  i.e. ``"queries"``
+  :param number val:  i.e. ``5``
+
+Set nominal value of given key.
+
diff --git a/modules/stats/stats.c b/modules/stats/stats.c
new file mode 100644 (file)
index 0000000..f162ea1
--- /dev/null
@@ -0,0 +1,224 @@
+/*  Copyright (C) 2014 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 <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file stats.c
+ * @brief Storage for various counters and metrics from query resolution. 
+ *
+ * You can either reuse this module to compute statistics or store custom metrics
+ * in it via the extensions.
+ */
+
+#include <libknot/packet/pkt.h>
+
+#include "lib/layer/iterate.h"
+#include "lib/rplan.h"
+#include "lib/module.h"
+#include "lib/layer.h"
+
+/* Defaults */
+#define DEBUG_MSG(qry, fmt...) QRDEBUG(qry, "stat",  fmt)
+
+/** @internal Add to map counter */
+static inline void stat_add(map_t *map, const char *key, ssize_t incr)
+{
+       void *val = map_get(map, key);
+       map_set(map, key, (void *)((size_t)val + incr));
+}
+
+static int begin(knot_layer_t *ctx, void *module_param)
+{
+       struct kr_module *module = ctx->api->data;
+       map_t *map = module->data;
+       stat_add(map, "query.concurrent", 1);
+       ctx->data = module_param;
+       return ctx->state;
+}
+
+static int collect_answer(map_t *map, knot_pkt_t *pkt)
+{
+       stat_add(map, "answer.total", 1);
+       /* Count per-rcode */
+       switch(knot_wire_get_rcode(pkt->wire)) {
+       case KNOT_RCODE_NOERROR:  stat_add(map, "answer.noerror", 1); break;
+       case KNOT_RCODE_NXDOMAIN: stat_add(map, "answer.nxdomain", 1); break;
+       case KNOT_RCODE_SERVFAIL: stat_add(map, "answer.servfail", 1); break;
+       default: break;
+       }
+
+       return kr_ok();
+}
+
+static int collect(knot_layer_t *ctx)
+{
+       struct kr_request *param = ctx->data;
+       struct kr_module *module = ctx->api->data;
+       struct kr_rplan *rplan = &param->rplan;
+       map_t *map = module->data;
+
+       /* Collect data on final answer */
+       collect_answer(map, param->answer);
+       /* Count cached and unresolved */
+       if (!EMPTY_LIST(rplan->resolved)) {
+               struct kr_query *qry = TAIL(rplan->resolved);
+               if (qry->flags & QUERY_CACHED) {
+                       stat_add(map, "answer.cached", 1);
+               }
+       } else {
+               stat_add(map, "answer.unresolved", 1);
+       }
+       /* Query parameters and transport mode */
+       stat_add(map, "query.concurrent", -1);
+       if (knot_pkt_has_edns(param->answer)) {
+               stat_add(map, "query.edns", -1);
+               if (knot_pkt_has_dnssec(param->answer)) {
+                       stat_add(map, "query.dnssec", -1);
+               }
+       }
+       /* Collect data from iterator queries */
+       struct kr_query *qry = NULL;
+       WALK_LIST(qry, rplan->resolved) {
+               if (!(qry->flags & QUERY_CACHED) && qry != TAIL(rplan->resolved)) {
+                       if (qry->flags & QUERY_TCP) {
+                               stat_add(map, "iterator.tcp", 1);
+                       } else {
+                               stat_add(map, "iterator.udp", 1);
+                       }
+               }
+       }
+       return ctx->state;
+}
+
+/**
+ * Set nominal value of a key.
+ *
+ * Input:  { key, val }
+ *
+ */
+static char* stats_set(void *env, struct kr_module *module, const char *args)
+{
+       map_t *map = module->data;
+       auto_free char *pair = strdup(args);
+       char *val = strchr(pair, ' ');
+       if (val) {
+               *val = '\0';
+               size_t number = strtoul(val + 1, NULL, 10);
+               map_set(map, pair, (void *)number);
+       }
+
+       return NULL;
+}
+
+/**
+ * Retrieve metrics by key.
+ *
+ * Input:  string key
+ * Output: number value
+ */
+static char* stats_get(void *env, struct kr_module *module, const char *args)
+{
+       map_t *map = module->data;
+       if (!map_contains(map, args)) {
+               return NULL;
+       }
+
+       /* Expecting CHAR_BIT to be 8, this is a safe bet */
+       char *ret = malloc(3 * sizeof(ret) + 2);
+       if (!ret) {
+               return NULL;
+       }
+
+       void *val = map_get(map, args);
+       sprintf(ret, "%zu", (size_t) val);
+       return ret;
+}
+
+static int list_entry(const char *key, void *val, void *baton)
+{
+       char **strval = (char **)baton;
+       size_t number = (size_t) val;
+       char buf[512];
+       snprintf(buf, sizeof(buf), "'%s': %zu,\n", key, number);
+       char *ret = kr_strcatdup(2, *strval, buf);
+       free(*strval);
+       *strval = ret;
+       return 0;
+}
+
+/**
+ * List observed metrics.
+ *
+ * Output: { key: val, ... }
+ */
+static char* stats_list(void *env, struct kr_module *module, const char *args)
+{
+       map_t *map = module->data;
+       char *strval = NULL;
+       /* @todo This is very inefficient with memory */
+       map_walk_prefixed(map, args ? args : "", list_entry, &strval);
+       char *ret = kr_strcatdup(3, "{\n", strval, "}");
+       free(strval);
+       return ret;
+}
+
+/*
+ * Module implementation.
+ */
+
+const knot_layer_api_t *stats_layer(struct kr_module *module)
+{
+       static knot_layer_api_t _layer = {
+               .begin = &begin,
+               .finish = &collect,
+       };
+       /* Store module reference */
+       _layer.data = module;
+       return &_layer;
+}
+
+int stats_init(struct kr_module *module)
+{
+       map_t *map = malloc(sizeof(*map));
+       if (!map) {
+               return kr_error(ENOMEM);
+       }
+       *map = map_make();
+       module->data = map;
+       return kr_ok();
+}
+
+int stats_deinit(struct kr_module *module)
+{
+       map_t *map = module->data;
+       if (map) {
+               map_clear(map);
+               free(map);
+       }
+       return kr_ok();
+}
+
+struct kr_prop *stats_props(void)
+{
+       static struct kr_prop prop_list[] = {
+           { &stats_set,    "set", "Set {key, val} metrics.", },
+           { &stats_get,    "get", "Get metrics for given key.", },
+           { &stats_list,   "list", "List observed metrics.", },
+           { NULL, NULL, NULL }
+       };
+       return prop_list;
+}
+
+KR_MODULE_EXPORT(stats);
diff --git a/modules/stats/stats.mk b/modules/stats/stats.mk
new file mode 100644 (file)
index 0000000..fe15669
--- /dev/null
@@ -0,0 +1,4 @@
+stats_SOURCES := modules/stats/stats.c
+stats_DEPEND := $(libkresolve)
+stats_LIBS := $(libkresolve_TARGET) $(libkresolve_LIBS)
+$(call make_c_module,stats)