Commit 707befeb authored by Daniel Salzman's avatar Daniel Salzman

knot: add statistics support + mod-stats

Based on work by:
  Filip Siroky <filip.siroky@nic.cz>
  Vítězslav Kříž <vitezslav.kriz@nic.cz>
  Dominik Taborsky <dominik.taborsky@nic.cz>
parent fe732375
......@@ -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
......
......@@ -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 \
......
/* 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));
}
/* 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);
......@@ -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,
......
......@@ -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"
......
......@@ -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 },
......
......@@ -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,
......
This diff is collapsed.
/* 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);
......@@ -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 },