From ac6b0755b18d0e172627ea0a424ede73642b0a4f Mon Sep 17 00:00:00 2001 From: =?utf8?q?Marek=20Vavrus=CC=8Ca?= Date: Wed, 20 May 2015 17:07:20 +0200 Subject: [PATCH] module/stats: collect various metrics for statistics this hooks into query resolution and collects counters on answers, queries and iterator --- modules/modules.mk | 1 + modules/stats/README.rst | 25 +++++ modules/stats/stats.c | 224 +++++++++++++++++++++++++++++++++++++++ modules/stats/stats.mk | 4 + 4 files changed, 254 insertions(+) create mode 100644 modules/stats/README.rst create mode 100644 modules/stats/stats.c create mode 100644 modules/stats/stats.mk diff --git a/modules/modules.mk b/modules/modules.mk index 3d285f300..d0b08484a 100644 --- a/modules/modules.mk +++ b/modules/modules.mk @@ -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 index 000000000..7fb2d1b06 --- /dev/null +++ b/modules/stats/README.rst @@ -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 index 000000000..f162ea1ff --- /dev/null +++ b/modules/stats/stats.c @@ -0,0 +1,224 @@ +/* Copyright (C) 2014 CZ.NIC, z.s.p.o. + + 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 . + */ + +/** + * @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 + +#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 = ¶m->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 index 000000000..fe15669ef --- /dev/null +++ b/modules/stats/stats.mk @@ -0,0 +1,4 @@ +stats_SOURCES := modules/stats/stats.c +stats_DEPEND := $(libkresolve) +stats_LIBS := $(libkresolve_TARGET) $(libkresolve_LIBS) +$(call make_c_module,stats) -- 2.47.2