diff --git a/Knot.files b/Knot.files index cbd9629f5e31ee0130b20a3e446d3c4d5af23c96..6ad59293bd7f08bc519201636ebfb1207921686f 100644 --- a/Knot.files +++ b/Knot.files @@ -208,6 +208,8 @@ src/knot/common/process.c src/knot/common/process.h src/knot/common/ref.c src/knot/common/ref.h +src/knot/common/stats.c +src/knot/common/stats.h src/knot/conf/base.c src/knot/conf/base.h src/knot/conf/conf.c @@ -267,6 +269,8 @@ src/knot/modules/online_sign/online_sign.h src/knot/modules/rosedb/rosedb.c src/knot/modules/rosedb/rosedb.h src/knot/modules/rosedb/rosedb_tool.c +src/knot/modules/stats/stats.c +src/knot/modules/stats/stats.h src/knot/modules/synth_record/synth_record.c src/knot/modules/synth_record/synth_record.h src/knot/modules/whoami/whoami.c diff --git a/src/Makefile.am b/src/Makefile.am index 308e1206cae692738ac2c170dc0176895733f701..406b0089a490c35dbc49cb64b9db5140ecac85bd 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -280,6 +280,8 @@ libknotd_la_SOURCES = \ knot/modules/online_sign/online_sign.h \ knot/modules/online_sign/nsec_next.c \ knot/modules/online_sign/nsec_next.h \ + knot/modules/stats/stats.c \ + knot/modules/stats/stats.h \ knot/modules/synth_record/synth_record.c\ knot/modules/synth_record/synth_record.h\ knot/modules/whoami/whoami.c \ @@ -323,6 +325,8 @@ libknotd_la_SOURCES = \ knot/common/process.h \ knot/common/ref.c \ knot/common/ref.h \ + knot/common/stats.c \ + knot/common/stats.h \ knot/server/dthreads.c \ knot/server/dthreads.h \ knot/server/journal.c \ diff --git a/src/knot/common/stats.c b/src/knot/common/stats.c new file mode 100644 index 0000000000000000000000000000000000000000..2c507f9fe92b5b2585f6b2cb1ec972297ca660c2 --- /dev/null +++ b/src/knot/common/stats.c @@ -0,0 +1,277 @@ +/* Copyright (C) 2016 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/>. + */ + +#include <inttypes.h> +#include <sys/stat.h> +#include <time.h> +#include <unistd.h> +#include <urcu.h> + +#include "contrib/files.h" +#include "knot/common/stats.h" +#include "knot/common/log.h" +#include "knot/nameserver/query_module.h" + +struct { + bool active_dumper; + pthread_t dumper; + uint32_t timer; + server_t *server; +} stats = { 0 }; + +#define DUMP_STR(fd, level, name, ...) do { \ + fprintf(fd, "%-.*s"name": %s\n", level, " ", ##__VA_ARGS__); \ + } while (0) +#define DUMP_CTR(fd, level, name, ...) do { \ + fprintf(fd, "%-.*s"name": %"PRIu64"\n", level, " ", ##__VA_ARGS__); \ + } while (0) + +uint64_t server_zone_count(server_t *server) +{ + return knot_zonedb_size(server->zone_db); +} + +const stats_item_t server_stats[] = { + { "zone-count", server_zone_count }, + { 0 } +}; + +static void dump_counters(FILE *fd, int level, mod_ctr_t *ctr) +{ + for (uint32_t j = 0; j < ctr->count; j++) { + // Skip empty counters. + if (ctr->counters[j] == 0) { + continue; + } + + if (ctr->idx_to_str != NULL) { + char *str = ctr->idx_to_str(j, ctr->count); + if (str != NULL) { + DUMP_CTR(fd, level, "%s", str, ctr->counters[j]); + free(str); + } + } else { + DUMP_CTR(fd, level, "%u", j, ctr->counters[j]); + } + } +} + +static void dump_modules(FILE *fd, list_t *query_modules, const knot_dname_t *zone) +{ + static int level = 0; + struct query_module *mod = NULL; + WALK_LIST(mod, *query_modules) { + // Skip modules without statistics. + if (mod->stats_count == 0) { + continue; + } + + // Dump zone name. + if (zone != NULL) { + // Prevent from zone section override. + if (level == 0) { + DUMP_STR(fd, level++, "zone", ""); + } else { + level = 1; + } + + char name[KNOT_DNAME_TXT_MAXLEN + 1]; + if (knot_dname_to_str(name, zone, sizeof(name)) == NULL) { + return; + } + DUMP_STR(fd, level++, "\"%s\"", name, ""); + } else { + level = 0; + } + + // Dump module counters. + DUMP_STR(fd, level, "%s", mod->id->name + 1, ""); + for (int i = 0; i < mod->stats_count; i++) { + mod_ctr_t *ctr = mod->stats + i; + if (ctr->name == NULL) { + // Empty counter. + continue; + } + if (ctr->count == 1) { + // Simple counter. + DUMP_CTR(fd, level + 1, "%s", ctr->name, ctr->counter); + } else { + // Array of counters. + DUMP_STR(fd, level + 1, "%s", ctr->name, ""); + dump_counters(fd, level + 2, ctr); + } + } + } +} + +static void zone_stats_dump(zone_t *zone, FILE *fd) +{ + if (EMPTY_LIST(zone->query_modules)) { + return; + } + + dump_modules(fd, &zone->query_modules, zone->name); +} + +static void dump_to_file(FILE *fd, server_t *server) +{ + char date[64] = ""; + + // Get formated current time string. + struct tm tm; + time_t now = time(NULL); + localtime_r(&now, &tm); + strftime(date, sizeof(date), "%Y-%m-%dT%H:%M:%S%z", &tm); + + // Get the server identity. + conf_val_t val = conf_get(conf(), C_SRV, C_IDENT); + const char *ident = conf_str(&val); + if (val.code != KNOT_EOK || ident[0] == '\0') { + ident = conf()->hostname; + } + + // Dump record header. + fprintf(fd, + "---\n" + "time: %s\n" + "identity: %s\n", + date, ident); + + // Dump server statistics. + DUMP_STR(fd, 0, "server", ""); + for (const stats_item_t *item = server_stats; item->name != NULL; item++) { + DUMP_CTR(fd, 1, "%s", item->name, item->val(server)); + } + + // Dump global statistics. + dump_modules(fd, &conf()->query_modules, NULL); + + // Dump zone statistics. + knot_zonedb_foreach(server->zone_db, zone_stats_dump, fd); +} + +static void dump_stats(server_t *server) +{ + conf_val_t val = conf_get(conf(), C_SRV, C_RUNDIR); + char *rundir = conf_abs_path(&val, NULL); + val = conf_get(conf(), C_STATS, C_FILE); + char *file_name = conf_abs_path(&val, rundir); + free(rundir); + + val = conf_get(conf(), C_STATS, C_APPEND); + bool append = conf_bool(&val); + + // Open or create output file. + FILE *fd = NULL; + char *tmp_name = NULL; + if (append) { + fd = fopen(file_name, "a"); + if (fd == NULL) { + log_error("stats, failed to append file '%s' (%s)", + file_name, knot_strerror(knot_map_errno())); + free(file_name); + return; + } + } else { + int ret = open_tmp_file(file_name, &tmp_name, &fd, + S_IRUSR | S_IWUSR | S_IRGRP); + if (ret != KNOT_EOK) { + log_error("stats, failed to open file '%s' (%s)", + file_name, knot_strerror(ret)); + free(file_name); + return; + } + } + assert(fd); + + // Dump stats into the file. + dump_to_file(fd, server); + + fflush(fd); + fclose(fd); + + // Switch the file contents. + if (!append) { + int ret = rename(tmp_name, file_name); + if (ret != 0) { + log_error("stats, failed to access file '%s' (%s)", + file_name, knot_strerror(knot_map_errno())); + unlink(tmp_name); + } + free(tmp_name); + } + + log_debug("stats, dumped into file '%s'", file_name); + free(file_name); +} + +static void *dumper(void *data) +{ + while (true) { + assert(stats.timer > 0); + sleep(stats.timer); + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); + rcu_read_lock(); + dump_stats(stats.server); + rcu_read_unlock(); + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); + } + + return NULL; +} + +void stats_reconfigure(conf_t *conf, server_t *server) +{ + if (conf == NULL || server == NULL) { + return; + } + + // Update server context. + stats.server = server; + + conf_val_t val = conf_get(conf, C_STATS, C_TIMER); + stats.timer = conf_int(&val); + if (stats.timer > 0) { + // Check if dumping is already running. + if (stats.active_dumper) { + return; + } + + int ret = pthread_create(&stats.dumper, NULL, dumper, NULL); + if (ret != 0) { + log_error("stats, failed to launch periodic dumping (%s)", + knot_strerror(knot_map_errno_code(ret))); + } else { + stats.active_dumper = true; + } + // Stop current dumping. + } else if (stats.active_dumper) { + pthread_cancel(stats.dumper); + pthread_join(stats.dumper, NULL); + stats.active_dumper = false; + } +} + +void stats_deinit(void) +{ + if (stats.active_dumper) { + pthread_cancel(stats.dumper); + pthread_join(stats.dumper, NULL); + } + + memset(&stats, 0, sizeof(stats)); +} diff --git a/src/knot/common/stats.h b/src/knot/common/stats.h new file mode 100644 index 0000000000000000000000000000000000000000..9cbdd8b2d0e9f30c45c9f3a4bd1c2c42b666bae8 --- /dev/null +++ b/src/knot/common/stats.h @@ -0,0 +1,44 @@ +/* Copyright (C) 2016 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/>. + */ + +#pragma once + +#include "knot/server/server.h" + +typedef uint64_t (*stats_val_f)(server_t *server); + +/*! + * \brief Statistics metrics item. + */ +typedef struct { + const char *name; /*!< Metrics name. */ + stats_val_f val; /*!< Metrics value getter. */ +} stats_item_t; + +/*! + * \brief Basic server metrics. + */ +extern const stats_item_t server_stats[]; + +/*! + * \brief Reconfigures the statistics facility. + */ +void stats_reconfigure(conf_t *conf, server_t *server); + +/*! + * \brief Deinitializes the statistics facility. + */ +void stats_deinit(void); diff --git a/src/knot/conf/scheme.c b/src/knot/conf/scheme.c index 4ed01413e8ecebdcabc406a16806630cc793bcf8..af4e0596295563f94e67ec06701a50712ae7b440 100644 --- a/src/knot/conf/scheme.c +++ b/src/knot/conf/scheme.c @@ -29,6 +29,7 @@ #include "dnssec/lib/dnssec/tsig.h" #include "dnssec/lib/dnssec/key.h" +#include "knot/modules/stats/stats.h" #include "knot/modules/synth_record/synth_record.h" #include "knot/modules/dnsproxy/dnsproxy.h" #include "knot/modules/online_sign/online_sign.h" @@ -148,6 +149,13 @@ static const yp_item_t desc_log[] = { { NULL } }; +static const yp_item_t desc_stats[] = { + { C_TIMER, YP_TINT, YP_VINT = { 1, UINT32_MAX, 0, YP_STIME } }, + { C_FILE, YP_TSTR, YP_VSTR = { "stats.yaml" } }, + { C_APPEND, YP_TBOOL, YP_VNONE }, + { NULL } +}; + static const yp_item_t desc_keystore[] = { { C_ID, YP_TSTR, YP_VNONE }, { C_BACKEND, YP_TOPT, YP_VOPT = { keystore_backends, KEYSTORE_BACKEND_PEM }, @@ -261,12 +269,14 @@ const yp_item_t conf_scheme[] = { { C_SRV, YP_TGRP, YP_VGRP = { desc_server }, CONF_IO_FRLD_SRV }, { C_CTL, YP_TGRP, YP_VGRP = { desc_control } }, { C_LOG, YP_TGRP, YP_VGRP = { desc_log }, YP_FMULTI | CONF_IO_FRLD_LOG }, + { C_STATS, YP_TGRP, YP_VGRP = { desc_stats }, CONF_IO_FRLD_SRV }, { C_KEYSTORE, YP_TGRP, YP_VGRP = { desc_keystore }, YP_FMULTI, { check_keystore } }, { C_POLICY, YP_TGRP, YP_VGRP = { desc_policy }, YP_FMULTI, { check_policy } }, { C_KEY, YP_TGRP, YP_VGRP = { desc_key }, YP_FMULTI, { check_key } }, { C_ACL, YP_TGRP, YP_VGRP = { desc_acl }, YP_FMULTI, { check_acl } }, { C_RMT, YP_TGRP, YP_VGRP = { desc_remote }, YP_FMULTI, { check_remote } }, /* MODULES */ + { C_MOD_STATS, YP_TGRP, YP_VGRP = { scheme_mod_stats }, FMOD }, { C_MOD_SYNTH_RECORD, YP_TGRP, YP_VGRP = { scheme_mod_synth_record }, FMOD, { check_mod_synth_record } }, { C_MOD_DNSPROXY, YP_TGRP, YP_VGRP = { scheme_mod_dnsproxy }, FMOD, diff --git a/src/knot/conf/scheme.h b/src/knot/conf/scheme.h index 87546961cb865c8909e8b227b374f2c5cf301360..26820e917ac1c466ebcd18a1b055802f34c3cfeb 100644 --- a/src/knot/conf/scheme.h +++ b/src/knot/conf/scheme.h @@ -33,6 +33,7 @@ #define C_ADDR "\x07""address" #define C_ALG "\x09""algorithm" #define C_ANY "\x03""any" +#define C_APPEND "\x06""append" #define C_ASYNC_START "\x0B""async-start" #define C_BACKEND "\x07""backend" #define C_BG_WORKERS "\x12""background-workers" @@ -91,6 +92,8 @@ #define C_SERIAL_POLICY "\x0D""serial-policy" #define C_SERVER "\x06""server" #define C_SRV "\x06""server" +#define C_STATS "\x0A""statistics" +#define C_TIMER "\x05""timer" #define C_STORAGE "\x07""storage" #define C_TARGET "\x06""target" #define C_TCP_HSHAKE_TIMEOUT "\x15""tcp-handshake-timeout" diff --git a/src/knot/ctl/commands.c b/src/knot/ctl/commands.c index a70e34ff49b76eb375a8928e12f8fb860c1a01da..609c185f458764337d34ab80346f0c201195d65f 100644 --- a/src/knot/ctl/commands.c +++ b/src/knot/ctl/commands.c @@ -18,9 +18,11 @@ #include <unistd.h> #include "knot/common/log.h" +#include "knot/common/stats.h" #include "knot/conf/confio.h" #include "knot/ctl/commands.h" #include "knot/events/handlers.h" +#include "knot/nameserver/query_module.h" #include "knot/updates/zone-update.h" #include "knot/zone/timers.h" #include "libknot/libknot.h" @@ -29,6 +31,7 @@ #include "contrib/mempattern.h" #include "contrib/string.h" #include "zscanner/scanner.h" +#include "contrib/strtonum.h" void ctl_log_data(knot_ctl_data_t *data) { @@ -938,6 +941,150 @@ static int zone_purge(zone_t *zone, ctl_args_t *args) return KNOT_EOK; } +static int send_stats_ctr(mod_ctr_t *ctr, ctl_args_t *args, knot_ctl_data_t *data) +{ + char index[128]; + char value[32]; + + if (ctr->count == 1) { + int ret = snprintf(value, sizeof(value), "%"PRIu64, ctr->counter); + if (ret <= 0 || ret >= sizeof(value)) { + return ret; + } + + (*data)[KNOT_CTL_IDX_ID] = NULL; + (*data)[KNOT_CTL_IDX_DATA] = value; + + ret = knot_ctl_send(args->ctl, KNOT_CTL_TYPE_DATA, data); + if (ret != KNOT_EOK) { + return ret; + } + } else { + bool force = ctl_has_flag(args->data[KNOT_CTL_IDX_FLAGS], + CTL_FLAG_FORCE); + + for (uint32_t i = 0; i < ctr->count; i++) { + // Skip empty counters. + if (ctr->counters[i] == 0 && !force) { + continue; + } + + int ret; + if (ctr->idx_to_str) { + char *str = ctr->idx_to_str(i, ctr->count); + if (str == NULL) { + continue; + } + ret = snprintf(index, sizeof(index), "%s", str); + free(str); + } else { + ret = snprintf(index, sizeof(index), "%u", i); + } + if (ret <= 0 || ret >= sizeof(index)) { + return ret; + } + + ret = snprintf(value, sizeof(value), "%"PRIu64, + ctr->counters[i]); + if (ret <= 0 || ret >= sizeof(value)) { + return ret; + } + + (*data)[KNOT_CTL_IDX_ID] = index; + (*data)[KNOT_CTL_IDX_DATA] = value; + + knot_ctl_type_t type = (i == 0) ? KNOT_CTL_TYPE_DATA : + KNOT_CTL_TYPE_EXTRA; + ret = knot_ctl_send(args->ctl, type, data); + if (ret != KNOT_EOK) { + return ret; + } + } + } + + return KNOT_EOK; +} + +static int modules_stats(list_t *query_modules, ctl_args_t *args, knot_dname_t *zone) +{ + if (query_modules == NULL) { + return KNOT_EOK; + } + + const char *section = args->data[KNOT_CTL_IDX_SECTION]; + const char *item = args->data[KNOT_CTL_IDX_ITEM]; + + char name[KNOT_DNAME_TXT_MAXLEN + 1] = { 0 }; + knot_ctl_data_t data = { 0 }; + + bool section_found = (section == NULL) ? true : false; + bool item_found = (item == NULL) ? true : false; + + struct query_module *mod = NULL; + WALK_LIST(mod, *query_modules) { + // Skip modules without statistics. + if (mod->stats_count == 0) { + continue; + } + + // Check for specific module. + if (section != NULL) { + if (section_found) { + break; + } else if (strcasecmp(mod->id->name + 1, section) == 0) { + section_found = true; + } else { + continue; + } + } + + data[KNOT_CTL_IDX_SECTION] = mod->id->name + 1; + + for (int i = 0; i < mod->stats_count; i++) { + mod_ctr_t *ctr = mod->stats + i; + + // Skip empty counter. + if (ctr->name == NULL) { + continue; + } + + // Check for specific counter. + if (item != NULL) { + if (item_found) { + break; + } else if (strcasecmp(ctr->name, item) == 0) { + item_found = true; + } else { + continue; + } + } + + // Prepare zone name if not already prepared. + if (zone != NULL && name[0] == '\0') { + if (knot_dname_to_str(name, zone, sizeof(name)) == NULL) { + return KNOT_EINVAL; + } + data[KNOT_CTL_IDX_ZONE] = name; + } + + data[KNOT_CTL_IDX_ITEM] = ctr->name; + + // Send the counters. + int ret = send_stats_ctr(ctr, args, &data); + if (ret != KNOT_EOK) { + return ret; + } + } + } + + return (section_found && item_found) ? KNOT_EOK : KNOT_ENOENT; +} + +static int zone_stats(zone_t *zone, ctl_args_t *args) +{ + return modules_stats(&zone->query_modules, args, zone->name); +} + static int ctl_zone(ctl_args_t *args, ctl_cmd_t cmd) { switch (cmd) { @@ -971,6 +1118,8 @@ static int ctl_zone(ctl_args_t *args, ctl_cmd_t cmd) return zones_apply(args, zone_txn_unset); case CTL_ZONE_PURGE: return zones_apply(args, zone_purge); + case CTL_ZONE_STATS: + return zones_apply(args, zone_stats); default: assert(0); return KNOT_EINVAL; @@ -1002,6 +1151,69 @@ static int ctl_server(ctl_args_t *args, ctl_cmd_t cmd) return ret; } +static int ctl_stats(ctl_args_t *args, ctl_cmd_t cmd) +{ + const char *section = args->data[KNOT_CTL_IDX_SECTION]; + const char *item = args->data[KNOT_CTL_IDX_ITEM]; + + bool found = (section == NULL) ? true : false; + + // Process server metrics. + if (section == NULL || strcasecmp(section, "server") == 0) { + char value[32]; + knot_ctl_data_t data = { + [KNOT_CTL_IDX_SECTION] = "server", + [KNOT_CTL_IDX_DATA] = value + }; + + for (const stats_item_t *i = server_stats; i->name != NULL; i++) { + if (item != NULL) { + if (found) { + break; + } else if (strcmp(i->name, item) == 0) { + found = true; + } else { + continue; + } + } else { + found = true; + } + + data[KNOT_CTL_IDX_ITEM] = i->name; + int ret = snprintf(value, sizeof(value), "%"PRIu64, + i->val(args->server)); + if (ret <= 0 || ret >= sizeof(value)) { + send_error(args, knot_strerror(ret)); + return ret; + } + + ret = knot_ctl_send(args->ctl, KNOT_CTL_TYPE_DATA, &data); + if (ret != KNOT_EOK) { + send_error(args, knot_strerror(ret)); + return ret; + } + } + } + + // Process modules metrics. + if (section == NULL || strncasecmp(section, "mod-", strlen("mod-")) == 0) { + int ret = modules_stats(&conf()->query_modules, args, NULL); + if (ret != KNOT_EOK) { + send_error(args, knot_strerror(ret)); + return ret; + } + + found = true; + } + + if (!found) { + send_error(args, knot_strerror(KNOT_EINVAL)); + return KNOT_EINVAL; + } + + return KNOT_EOK; +} + static int send_block_data(conf_io_t *io, knot_ctl_data_t *data) { knot_ctl_t *ctl = (knot_ctl_t *)io->misc; @@ -1266,6 +1478,7 @@ static const desc_t cmd_table[] = { [CTL_STATUS] = { "status", ctl_server }, [CTL_STOP] = { "stop", ctl_server }, [CTL_RELOAD] = { "reload", ctl_server }, + [CTL_STATS] = { "stats", ctl_stats }, [CTL_ZONE_STATUS] = { "zone-status", ctl_zone }, [CTL_ZONE_RELOAD] = { "zone-reload", ctl_zone }, @@ -1283,6 +1496,7 @@ static const desc_t cmd_table[] = { [CTL_ZONE_SET] = { "zone-set", ctl_zone }, [CTL_ZONE_UNSET] = { "zone-unset", ctl_zone }, [CTL_ZONE_PURGE] = { "zone-purge", ctl_zone }, + [CTL_ZONE_STATS] = { "zone-stats", ctl_zone }, [CTL_CONF_LIST] = { "conf-list", ctl_conf_read }, [CTL_CONF_READ] = { "conf-read", ctl_conf_read }, diff --git a/src/knot/ctl/commands.h b/src/knot/ctl/commands.h index 204bb17d604f0ed572fcd60415baf7037e304074..430fc8212281ba6883c34e0cd86c445ae9a50ff4 100644 --- a/src/knot/ctl/commands.h +++ b/src/knot/ctl/commands.h @@ -38,6 +38,7 @@ typedef enum { CTL_STATUS, CTL_STOP, CTL_RELOAD, + CTL_STATS, CTL_ZONE_STATUS, CTL_ZONE_RELOAD, @@ -45,6 +46,7 @@ typedef enum { CTL_ZONE_RETRANSFER, CTL_ZONE_FLUSH, CTL_ZONE_SIGN, + CTL_ZONE_STATS, CTL_ZONE_READ, CTL_ZONE_BEGIN, diff --git a/src/knot/modules/stats/stats.c b/src/knot/modules/stats/stats.c new file mode 100644 index 0000000000000000000000000000000000000000..325f6b61053c7ee334e7c9856e71b79bfba56ff8 --- /dev/null +++ b/src/knot/modules/stats/stats.c @@ -0,0 +1,566 @@ +/* Copyright (C) 2016 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/>. + */ + +#include "contrib/mempattern.h" +#include "libknot/libknot.h" +#include "knot/modules/stats/stats.h" +#include "knot/nameserver/axfr.h" + +#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_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 scheme_mod_stats[] = { + { C_ID, YP_TSTR, YP_VNONE }, + { 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_NODATA, YP_TBOOL, YP_VNONE }, + { MOD_QTYPE, YP_TBOOL, YP_VNONE }, + { MOD_QSIZE, YP_TBOOL, YP_VNONE }, + { MOD_RSIZE, YP_TBOOL, YP_VNONE }, + { C_COMMENT, YP_TSTR, YP_VNONE }, + { NULL } +}; + +enum { + CTR_PROTOCOL, + CTR_OPERATION, + CTR_REQ_BYTES, + CTR_RESP_BYTES, + CTR_EDNS, + CTR_FLAG, + CTR_RCODE, + CTR_NODATA, + CTR_QTYPE, + CTR_QSIZE, + CTR_RSIZE, +}; + +typedef struct { + mod_ctr_t *counters; + bool protocol; + bool operation; + bool req_bytes; + bool resp_bytes; + bool edns; + bool flag; + bool rcode; + bool nodata; + bool qtype; + bool qsize; + bool rsize; +} stats_t; + +typedef struct { + yp_name_t *conf_name; + size_t conf_offset; + uint32_t count; + 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_UDP6, + PROTOCOL_TCP6, + 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_UDP6: return strdup("udp6"); + case PROTOCOL_TCP6: return strdup("tcp6"); + 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; + } +} + +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 + +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(NODATA, nodata, NODATA__COUNT), + item(QTYPE, qtype, QTYPE__COUNT), + item(QSIZE, qsize, 288 / BUCKET_SIZE + 1), + item(RSIZE, rsize, 4096 / BUCKET_SIZE + 1), + { NULL } +}; + +static int update_counters(int state, knot_pkt_t *pkt, struct query_data *qdata, void *ctx) +{ + assert(pkt && qdata && ctx); + + stats_t *stats = ctx; + + uint16_t operation; + unsigned xfr_packets = 0; + + // Get the server operation. + switch (qdata->packet_type) { + case KNOT_QUERY_NORMAL: + operation = OPERATION_QUERY; + break; + case KNOT_QUERY_UPDATE: + operation = OPERATION_UPDATE; + break; + case KNOT_QUERY_NOTIFY: + operation = OPERATION_NOTIFY; + break; + case KNOT_QUERY_AXFR: + operation = OPERATION_AXFR; + if (qdata->ext != NULL) { + xfr_packets = ((struct xfr_proc *)qdata->ext)->npkts; + } + break; + case KNOT_QUERY_IXFR: + operation = OPERATION_IXFR; + if (qdata->ext != NULL) { + xfr_packets = ((struct xfr_proc *)qdata->ext)->npkts; + } + break; + default: + operation = OPERATION_INVALID; + break; + } + + // Count request bytes. + if (stats->req_bytes) { + switch (operation) { + case OPERATION_QUERY: + mod_ctrs_incr(stats->counters, CTR_REQ_BYTES, + REQ_BYTES_QUERY, qdata->query->size); + break; + case OPERATION_UPDATE: + mod_ctrs_incr(stats->counters, CTR_REQ_BYTES, + REQ_BYTES_UPDATE, qdata->query->size); + break; + default: + if (xfr_packets <= 1) { + mod_ctrs_incr(stats->counters, CTR_REQ_BYTES, + REQ_BYTES_OTHER, qdata->query->size); + } + break; + } + } + + // Count response bytes. + if (stats->resp_bytes) { + switch (operation) { + case OPERATION_QUERY: + mod_ctrs_incr(stats->counters, CTR_RESP_BYTES, + RESP_BYTES_REPLY, pkt->size); + break; + case OPERATION_AXFR: + case OPERATION_IXFR: + mod_ctrs_incr(stats->counters, CTR_RESP_BYTES, + RESP_BYTES_TRANSFER, pkt->size); + break; + default: + mod_ctrs_incr(stats->counters, CTR_RESP_BYTES, + RESP_BYTES_OTHER, pkt->size); + 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 && pkt->size > 0) { + if (xfr_packets <= 1 || rcode != KNOT_RCODE_NOERROR) { + if (xfr_packets > 1) { + assert(rcode != KNOT_RCODE_NOERROR); + // Ignore the leading XFR message NOERROR. + mod_ctrs_decr(stats->counters, CTR_RCODE, + KNOT_RCODE_NOERROR, 1); + } + + if (qdata->rcode_tsig == KNOT_RCODE_BADSIG) { + mod_ctrs_incr(stats->counters, CTR_RCODE, + RCODE_BADSIG, 1); + } else { + mod_ctrs_incr(stats->counters, CTR_RCODE, + rcode, 1); + } + } + } + + // Return if non-first transfer message. + if (xfr_packets > 1) { + return state; + } + + // Count the server opearation. + if (stats->operation) { + mod_ctrs_incr(stats->counters, CTR_OPERATION, operation, 1); + } + + // Count the request protocol. + if (stats->protocol) { + if (qdata->param->remote->ss_family == AF_INET) { + if (qdata->param->proc_flags & NS_QUERY_LIMIT_SIZE) { + mod_ctrs_incr(stats->counters, CTR_PROTOCOL, + PROTOCOL_UDP4, 1); + } else { + mod_ctrs_incr(stats->counters, CTR_PROTOCOL, + PROTOCOL_TCP4, 1); + } + } else { + if (qdata->param->proc_flags & NS_QUERY_LIMIT_SIZE) { + mod_ctrs_incr(stats->counters, CTR_PROTOCOL, + PROTOCOL_UDP6, 1); + } else { + mod_ctrs_incr(stats->counters, CTR_PROTOCOL, + PROTOCOL_TCP6, 1); + } + } + } + + // Count EDNS occurrences. + if (stats->edns) { + if (qdata->query->opt_rr != NULL) { + mod_ctrs_incr(stats->counters, CTR_EDNS, EDNS_REQ, 1); + } + if (pkt->opt_rr != NULL && pkt->size > 0) { + mod_ctrs_incr(stats->counters, CTR_EDNS, EDNS_RESP, 1); + } + } + + // Count interesting message header flags. + if (stats->flag) { + if (pkt->size > 0 && knot_wire_get_tc(pkt->wire)) { + mod_ctrs_incr(stats->counters, CTR_FLAG, FLAG_TC, 1); + } + if (pkt->opt_rr != NULL && knot_edns_do(pkt->opt_rr)) { + mod_ctrs_incr(stats->counters, CTR_FLAG, FLAG_DO, 1); + } + } + + // 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 && pkt->size > 0 && + 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: + mod_ctrs_incr(stats->counters, CTR_NODATA, NODATA_A, 1); + break; + case KNOT_RRTYPE_AAAA: + mod_ctrs_incr(stats->counters, CTR_NODATA, NODATA_AAAA, 1); + break; + default: + mod_ctrs_incr(stats->counters, 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; + } + + mod_ctrs_incr(stats->counters, CTR_QTYPE, idx, 1); + } + + // Count the query size. + if (stats->qsize) { + mod_ctrs_incr(stats->counters, CTR_QSIZE, + qdata->query->size / BUCKET_SIZE, 1); + } + + // Count the reply size. + if (stats->rsize && pkt->size > 0) { + mod_ctrs_incr(stats->counters, CTR_RSIZE, + pkt->size / BUCKET_SIZE, 1); + } + + return state; +} + +int stats_load(struct query_plan *plan, struct query_module *self, + const knot_dname_t *zone) +{ + assert(self); + + stats_t *stats = mm_alloc(self->mm, sizeof(*stats)); + if (stats == NULL) { + return KNOT_ENOMEM; + } + + for (const ctr_desc_t *desc = ctr_descs; desc->conf_name != NULL; desc++) { + conf_val_t val = conf_mod_get(self->config, desc->conf_name, self->id); + bool enabled = conf_bool(&val); + + // Initialize corresponding configuration item. + *(bool *)((uint8_t *)stats + desc->conf_offset) = enabled; + + int ret = mod_stats_add(self, enabled ? desc->conf_name + 1 : NULL, + desc->count, desc->fcn); + if (ret != KNOT_EOK) { + return ret; + } + } + + stats->counters = self->stats; + self->ctx = stats; + + return query_plan_step(plan, QPLAN_END, update_counters, self->ctx); +} + +void stats_unload(struct query_module *self) +{ + assert(self); + + stats_t *stats = self->ctx; + + mm_free(self->mm, stats); +} diff --git a/src/knot/modules/stats/stats.h b/src/knot/modules/stats/stats.h new file mode 100644 index 0000000000000000000000000000000000000000..bb8449d255b232eeb1997f514c3b9d23a459ef5d --- /dev/null +++ b/src/knot/modules/stats/stats.h @@ -0,0 +1,28 @@ +/* Copyright (C) 2016 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/>. + */ + +#pragma once + +#include "knot/nameserver/query_module.h" + +/*! \brief Module scheme. */ +#define C_MOD_STATS "\x09""mod-stats" +extern const yp_item_t scheme_mod_stats[]; + +/*! \brief Module interface. */ +int stats_load(struct query_plan *plan, struct query_module *self, + const knot_dname_t *zone); +void stats_unload(struct query_module *self); diff --git a/src/knot/nameserver/query_module.c b/src/knot/nameserver/query_module.c index cc721e166aa0be508a5ee81adc1404fcd5aa49c9..8e0ca7d7c55a4b607d5569015f3c54bd0f85e715 100644 --- a/src/knot/nameserver/query_module.c +++ b/src/knot/nameserver/query_module.c @@ -14,9 +14,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include <assert.h> + #include "knot/nameserver/query_module.h" #include "contrib/mempattern.h" +#include "knot/modules/stats/stats.h" #include "knot/modules/synth_record/synth_record.h" #include "knot/modules/dnsproxy/dnsproxy.h" #include "knot/modules/online_sign/online_sign.h" @@ -34,6 +37,7 @@ static_module_t MODULES[] = { { C_MOD_SYNTH_RECORD, &synth_record_load, &synth_record_unload, MOD_SCOPE_ANY }, { C_MOD_DNSPROXY, &dnsproxy_load, &dnsproxy_unload, MOD_SCOPE_ANY }, { C_MOD_ONLINE_SIGN, &online_sign_load, &online_sign_unload, MOD_SCOPE_ZONE, true }, + { C_MOD_STATS, &stats_load, &stats_unload, MOD_SCOPE_ANY, true }, #ifdef HAVE_ROSEDB { C_MOD_ROSEDB, &rosedb_load, &rosedb_unload, MOD_SCOPE_ANY }, #endif @@ -104,6 +108,67 @@ int query_plan_step(struct query_plan *plan, int stage, qmodule_process_t proces return KNOT_EOK; } +int mod_stats_add(struct query_module *module, const char *name, uint32_t count, + mod_idx_to_str_f idx) +{ + if (module == NULL || count < 1) { + return KNOT_EINVAL; + } + + mod_ctr_t *stats = NULL; + if (module->stats == NULL) { + assert(module->stats_count == 0); + stats = mm_alloc(module->mm, sizeof(*stats)); + if (stats == NULL) { + return KNOT_ENOMEM; + } + module->stats = stats; + } else { + assert(module->stats_count > 0); + size_t old_size = module->stats_count * sizeof(*stats); + size_t new_size = old_size + sizeof(*stats); + stats = mm_realloc(module->mm, module->stats, new_size, old_size); + if (stats == NULL) { + mod_stats_free(module); + return KNOT_ENOMEM; + } + module->stats = stats; + stats += module->stats_count; + } + + module->stats_count++; + + if (count > 1) { + size_t size = count * sizeof(((mod_ctr_t *)0)->counter); + stats->counters = mm_alloc(module->mm, size); + if (stats->counters == NULL) { + mod_stats_free(module); + return KNOT_ENOMEM; + } + memset(stats->counters, 0, size); + stats->idx_to_str = idx; + } + stats->name = name; + stats->count = count; + + return KNOT_EOK; +} + +void mod_stats_free(struct query_module *module) +{ + if (module == NULL || module->stats == NULL) { + return; + } + + for (int i = 0; i < module->stats_count; i++) { + if (module->stats[i].count > 1) { + mm_free(module->mm, module->stats[i].counters); + } + } + + mm_free(module->mm, module->stats); +} + static_module_t *find_module(const yp_name_t *name) { /* Search for the module by name. */ @@ -155,6 +220,7 @@ void query_module_close(struct query_module *module) return; } + mod_stats_free(module); conf_free_mod_id(module->id); mm_free(module->mm, module); } diff --git a/src/knot/nameserver/query_module.h b/src/knot/nameserver/query_module.h index 888b44387d2a90ce75f8d883028ff42248ba794f..2a006d6b6c45bacf313703d668c7c69ce9bd45c8 100644 --- a/src/knot/nameserver/query_module.h +++ b/src/knot/nameserver/query_module.h @@ -89,6 +89,20 @@ typedef struct static_module { bool opt_conf; } static_module_t; +typedef char* (*mod_idx_to_str_f)(uint32_t idx, uint32_t count); + +typedef struct { + const char *name; + union { + uint64_t counter; + struct { + uint64_t *counters; + mod_idx_to_str_f idx_to_str; + }; + }; + uint32_t count; +} mod_ctr_t; + /*! * Query module is a dynamically loadable unit that can alter query processing plan. * Module requires load and unload callback handlers and is provided with a context @@ -102,9 +116,58 @@ struct query_module { conf_mod_id_t *id; qmodule_load_t load; qmodule_unload_t unload; + mod_ctr_t *stats; + uint32_t stats_count; unsigned scope; }; +int mod_stats_add(struct query_module *module, const char *name, uint32_t count, + mod_idx_to_str_f idx); + +void mod_stats_free(struct query_module *module); + +inline static void mod_ctr_incr(mod_ctr_t *stats, uint32_t idx, uint64_t val) +{ + mod_ctr_t *ctr = stats + idx; + assert(ctr->count == 1); + + __sync_fetch_and_add(&ctr->counter, val); +} + +inline static void mod_ctr_decr(mod_ctr_t *stats, uint32_t idx, uint64_t val) +{ + mod_ctr_t *ctr = stats + idx; + assert(ctr->count == 1); + + __sync_fetch_and_sub(&ctr->counter, val); +} + +inline static void mod_ctrs_incr(mod_ctr_t *stats, uint32_t idx, uint32_t offset, uint64_t val) +{ + mod_ctr_t *ctr = stats + idx; + assert(ctr->count > 1); + + // Increment the last counter if offset overflows. + if (offset < ctr->count) { + __sync_fetch_and_add(&ctr->counters[offset], val); + } else { + __sync_fetch_and_add(&ctr->counters[ctr->count - 1], val); + } +} + +inline static void mod_ctrs_decr(mod_ctr_t *stats, uint32_t idx, uint32_t offset, uint64_t val) +{ + mod_ctr_t *ctr = stats + idx; + assert(ctr->count > 1); + + // Increment the last counter if offset overflows. + if (offset < ctr->count) { + __sync_fetch_and_sub(&ctr->counters[offset], val); + } else { + __sync_fetch_and_sub(&ctr->counters[ctr->count - 1], val); + } +} + /*! \brief Single processing step in query processing. */ struct query_step { node_t node; diff --git a/src/knot/server/server.c b/src/knot/server/server.c index eb2e4d603a91a10a8b7a2cda6f5038cc1864b97e..c1bff958cdd6363c8704438353bf1e7cbe9e9e71 100644 --- a/src/knot/server/server.c +++ b/src/knot/server/server.c @@ -22,6 +22,7 @@ #include "libknot/errcode.h" #include "knot/common/log.h" +#include "knot/common/stats.h" #include "knot/conf/confio.h" #include "knot/server/server.h" #include "knot/server/udp-handler.h" @@ -585,6 +586,7 @@ int server_reload(server_t *server) } if (full || (flags & CONF_IO_FRLD_SRV)) { server_reconfigure(conf(), server); + stats_reconfigure(conf(), server); } if (full || (flags & (CONF_IO_FRLD_ZONES | CONF_IO_FRLD_ZONE))) { server_update_zones(conf(), server); diff --git a/src/utils/knotc/commands.c b/src/utils/knotc/commands.c index 9636d0316f1eb4f616200ff166f9336e305e2668..afcd8fe1e48783e37c2dfb960d434ab1dbcddc86 100644 --- a/src/utils/knotc/commands.c +++ b/src/utils/knotc/commands.c @@ -36,6 +36,7 @@ #define CMD_STATUS "status" #define CMD_STOP "stop" #define CMD_RELOAD "reload" +#define CMD_STATS "stats" #define CMD_ZONE_CHECK "zone-check" #define CMD_ZONE_MEMSTATS "zone-memstats" @@ -55,6 +56,7 @@ #define CMD_ZONE_SET "zone-set" #define CMD_ZONE_UNSET "zone-unset" #define CMD_ZONE_PURGE "zone-purge" +#define CMD_ZONE_STATS "zone-stats" #define CMD_CONF_INIT "conf-init" #define CMD_CONF_CHECK "conf-check" @@ -270,26 +272,43 @@ static void format_data(ctl_cmd_t cmd, knot_ctl_type_t data_type, case CTL_ZONE_GET: case CTL_ZONE_SET: case CTL_ZONE_UNSET: - if (data_type == KNOT_CTL_TYPE_DATA) { - printf("%s%s%s%s%s%s%s%s%s%s%s%s%s", - (!(*empty) ? "\n" : ""), - (error != NULL ? "error: (" : ""), - (error != NULL ? error : ""), - (error != NULL ? ") " : ""), - (zone != NULL ? "[" : ""), - (zone != NULL ? zone : ""), - (zone != NULL ? "] " : ""), - (sign != NULL ? sign : ""), - (owner != NULL ? owner : ""), - (ttl != NULL ? " " : ""), - (ttl != NULL ? ttl : ""), - (type != NULL ? " " : ""), - (type != NULL ? type : "")); - *empty = false; - } - if (value != NULL) { - printf(" %s", value); - } + printf("%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", + (!(*empty) ? "\n" : ""), + (error != NULL ? "error: (" : ""), + (error != NULL ? error : ""), + (error != NULL ? ") " : ""), + (zone != NULL ? "[" : ""), + (zone != NULL ? zone : ""), + (zone != NULL ? "] " : ""), + (sign != NULL ? sign : ""), + (owner != NULL ? owner : ""), + (ttl != NULL ? " " : ""), + (ttl != NULL ? ttl : ""), + (type != NULL ? " " : ""), + (type != NULL ? type : ""), + (value != NULL ? " " : ""), + (value != NULL ? value : "")); + *empty = false; + break; + case CTL_STATS: + case CTL_ZONE_STATS: + printf("%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", + (!(*empty) ? "\n" : ""), + (error != NULL ? "error: (" : ""), + (error != NULL ? error : ""), + (error != NULL ? ") " : ""), + (zone != NULL ? "[" : ""), + (zone != NULL ? zone : ""), + (zone != NULL ? "] " : ""), + (key0 != NULL ? key0 : ""), + (key1 != NULL ? "." : ""), + (key1 != NULL ? key1 : ""), + (id != NULL ? "[" : ""), + (id != NULL ? id : ""), + (id != NULL ? "]" : ""), + (value != NULL ? " = " : ""), + (value != NULL ? value : "")); + *empty = false; break; default: assert(0); @@ -334,6 +353,8 @@ static void format_block(ctl_cmd_t cmd, bool failed, bool empty) case CTL_CONF_READ: case CTL_CONF_DIFF: case CTL_CONF_GET: + case CTL_ZONE_STATS: + case CTL_STATS: printf("%s", empty ? "" : "\n"); break; default: @@ -409,6 +430,75 @@ static int cmd_ctl(cmd_args_t *args) return ctl_receive(args); } +static int set_stats_items(cmd_args_t *args, knot_ctl_data_t *data) +{ + int min_args, max_args; + switch (args->desc->cmd) { + case CTL_STATS: min_args = 0; max_args = 1; break; + case CTL_ZONE_STATS: min_args = 1; max_args = 2; break; + default: + assert(0); + return KNOT_EINVAL; + } + + // Check the number of arguments. + int ret = check_args(args, min_args, max_args); + if (ret != KNOT_EOK) { + return ret; + } + + int idx = 0; + + // Set ZONE name. + if (args->argc > idx && args->desc->cmd == CTL_ZONE_STATS) { + if (strcmp(args->argv[idx], "--") != 0) { + (*data)[KNOT_CTL_IDX_ZONE] = args->argv[idx]; + } + idx++; + } + + if (args->argc > idx) { + (*data)[KNOT_CTL_IDX_SECTION] = args->argv[idx]; + + char *item = strchr(args->argv[idx], '.'); + if (item != NULL) { + // Separate section and item. + *item++ = '\0'; + (*data)[KNOT_CTL_IDX_ITEM] = item; + } + } + + return KNOT_EOK; +} + +static int cmd_stats_ctl(cmd_args_t *args) +{ + knot_ctl_data_t data = { + [KNOT_CTL_IDX_CMD] = ctl_cmd_to_str(args->desc->cmd), + [KNOT_CTL_IDX_FLAGS] = args->force ? CTL_FLAG_FORCE : NULL + }; + + int ret = set_stats_items(args, &data); + if (ret != KNOT_EOK) { + return ret; + } + + ret = knot_ctl_send(args->ctl, KNOT_CTL_TYPE_DATA, &data); + if (ret != KNOT_EOK) { + log_error(CTL_LOG_STR" (%s)", knot_strerror(ret)); + return ret; + } + + // Finish the input block. + ret = knot_ctl_send(args->ctl, KNOT_CTL_TYPE_BLOCK, NULL); + if (ret != KNOT_EOK) { + log_error(CTL_LOG_STR" (%s)", knot_strerror(ret)); + return ret; + } + + return ctl_receive(args); +} + static int zone_exec(cmd_args_t *args, int (*fcn)(const knot_dname_t *, void *), void *data) { @@ -870,6 +960,7 @@ const cmd_desc_t cmd_table[] = { { CMD_STATUS, cmd_ctl, CTL_STATUS }, { CMD_STOP, cmd_ctl, CTL_STOP }, { CMD_RELOAD, cmd_ctl, CTL_RELOAD }, + { CMD_STATS, cmd_stats_ctl, CTL_STATS }, { CMD_ZONE_CHECK, cmd_zone_check, CTL_NONE, CMD_FOPT_ZONE | CMD_FREAD }, { CMD_ZONE_MEMSTATS, cmd_zone_memstats, CTL_NONE, CMD_FOPT_ZONE | CMD_FREAD }, @@ -889,6 +980,7 @@ const cmd_desc_t cmd_table[] = { { CMD_ZONE_SET, cmd_zone_node_ctl, CTL_ZONE_SET, CMD_FREQ_ZONE }, { CMD_ZONE_UNSET, cmd_zone_node_ctl, CTL_ZONE_UNSET, CMD_FREQ_ZONE }, { CMD_ZONE_PURGE, cmd_zone_ctl, CTL_ZONE_PURGE, CMD_FREQ_ZONE }, + { CMD_ZONE_STATS, cmd_stats_ctl, CTL_ZONE_STATS, CMD_FREQ_ZONE }, { CMD_CONF_INIT, cmd_conf_init, CTL_NONE, CMD_FWRITE }, { CMD_CONF_CHECK, cmd_conf_check, CTL_NONE, CMD_FREAD }, @@ -912,6 +1004,7 @@ static const cmd_help_t cmd_help_table[] = { { CMD_STATUS, "", "Check if the server is running." }, { CMD_STOP, "", "Stop the server if running." }, { CMD_RELOAD, "", "Reload the server configuration and modified zones." }, + { CMD_STATS, "[<module>[.<counter>]]", "Show global statistics counter(s)." }, { "", "", "" }, { CMD_ZONE_CHECK, "[<zone>...]", "Check if the zone can be loaded. (*)" }, { CMD_ZONE_MEMSTATS, "[<zone>...]", "Estimate memory use for the zone. (*)" }, @@ -931,6 +1024,7 @@ static const cmd_help_t cmd_help_table[] = { { CMD_ZONE_SET, "<zone> <owner> [<ttl>] <type> <rdata>", "Add zone record within the transaction." }, { CMD_ZONE_UNSET, "<zone> <owner> [<type> [<rdata>]]", "Remove zone data within the transaction." }, { CMD_ZONE_PURGE, "<zone>...", "Purge zone data, file, journal, and timers." }, + { CMD_ZONE_STATS, "<zone> [<module>[.<counter>]]", "Show zone statistics counter(s)."}, { "", "", "" }, { CMD_CONF_INIT, "", "Initialize the confdb. (*)" }, { CMD_CONF_CHECK, "", "Check the server configuration. (*)" }, diff --git a/src/utils/knotd/main.c b/src/utils/knotd/main.c index 3a2be5ff81cd4c0a1d826c529234c54394ff7ef2..dd2bec909975e5e5735b408ed368dc47d9461b86 100644 --- a/src/utils/knotd/main.c +++ b/src/utils/knotd/main.c @@ -38,6 +38,7 @@ #include "knot/conf/conf.h" #include "knot/common/log.h" #include "knot/common/process.h" +#include "knot/common/stats.h" #include "knot/server/server.h" #include "knot/server/tcp-handler.h" #include "knot/zone/timers.h" @@ -525,6 +526,8 @@ int main(int argc, char **argv) log_warning("no zones loaded"); } + stats_reconfigure(conf(), &server); + /* Start it up. */ log_info("starting server"); conf_val_t async_val = conf_get(conf(), C_SRV, C_ASYNC_START); @@ -532,6 +535,7 @@ int main(int argc, char **argv) if (ret != KNOT_EOK) { log_fatal("failed to start server (%s)", knot_strerror(ret)); server_wait(&server); + stats_deinit(); server_deinit(&server); rcu_unregister_thread(); pid_cleanup(); @@ -553,6 +557,7 @@ int main(int argc, char **argv) /* Teardown server. */ server_stop(&server); server_wait(&server); + stats_deinit(); log_info("updating zone timers database"); write_timer_db(server.timers_db, server.zone_db);