diff --git a/daemon/README.rst b/daemon/README.rst index ae2992ba375df4861fc3eaf482542ca79c12ff87..019dc5056dd6d34b0a68158b84ecf440b70a65d1 100644 --- a/daemon/README.rst +++ b/daemon/README.rst @@ -1,3 +1,4 @@ + ************************ Knot DNS Resolver daemon ************************ @@ -508,6 +509,10 @@ you can see the statistics or schedule new queries. Return table of statistics. + * ``udp`` - number of outbound queries over UDP + * ``tcp`` - number of outbound queries over TCP + * ``concurrent`` - number of concurrent queries at the moment + Example: .. code-block:: lua diff --git a/modules/stats/README.rst b/modules/stats/README.rst index 3937c96b07ac1b5a3c6df208336f663b22f1f3b9..0f61e79da5efd23c90032d4cd89b6a0a1b64e733 100644 --- a/modules/stats/README.rst +++ b/modules/stats/README.rst @@ -28,6 +28,17 @@ in new ones. > stats['filter.match'] 5 + -- Fetch most common queries + > stats.queries() + [1] => { + [type] => 2 + [count] => 4 + [name] => cz. + } + + -- Fetch most common queries (sorted by frequency) + > table.sort(stats.queries(), function (a, b) return a.count > b.count end) + Properties ^^^^^^^^^^ @@ -51,19 +62,22 @@ Set nominal value of given metric. Outputs collected metrics as a JSON dictionary. +.. function:: stats.queries() + +Outputs list of most frequent iterative queries as a JSON array. The queries are sampled probabilistically, +and include subrequests. The list maximum size is 1000 entries, make diffs if you want to track it over time. + +.. function:: stats.queries_clear() + +Clear the list of most frequent iterative queries. + Built-in statistics ^^^^^^^^^^^^^^^^^^^ * ``answer.total`` - total number of answerered queries * ``answer.cached`` - number of queries answered from cache -* ``answer.unresolved`` - number of unresolved queries (likely unresolvable path) * ``answer.noerror`` - number of **NOERROR** answers * ``answer.nxdomain`` - number of **NXDOMAIN** answers * ``answer.servfail`` - number of **SERVFAIL** answers -* ``query.concurrent`` - number of concurrent queries at the moment * ``query.edns`` - number of queries with EDNS * ``query.dnssec`` - number of queries with DNSSEC DO=1 -* ``iterator.udp`` - number of outbound queries over UDP -* ``iterator.tcp`` - number of outbound queries over TCP - - * Note that the iterator tracks **completed** queries over given protocol, total number of outbound requests must be tracked by the I/O layer. diff --git a/modules/stats/stats.c b/modules/stats/stats.c index 11e2bd89eaee55566976ec74eae33c91f5a5d857..24d07d96f3205dd4eb0df7efb0a9d0a2169b8e54 100644 --- a/modules/stats/stats.c +++ b/modules/stats/stats.c @@ -37,6 +37,9 @@ /* Defaults */ #define DEBUG_MSG(qry, fmt...) QRDEBUG(qry, "stat", fmt) +#define FREQUENT_COUNT 1000 /* Size of frequent tables */ +#define FREQUENT_PSAMPLE 100 /* Sampling rate, 1 in N */ + /** @cond internal Fixed-size map of predefined metrics. */ #define CONST_METRICS(X) \ X(answer,total) X(answer,noerror) X(answer,nxdomain) X(answer,servfail) \ @@ -112,6 +115,33 @@ static int collect_answer(struct stat_data *data, knot_pkt_t *pkt) return kr_ok(); } +static inline int collect_key(char *key, const knot_dname_t *name, uint16_t type) +{ + memcpy(key, &type, sizeof(type)); + int key_len = knot_dname_to_wire((uint8_t *)key + sizeof(type), name, KNOT_DNAME_MAXLEN); + if (key_len > 0) { + return key_len + sizeof(type); + } + return key_len; +} + +static void collect_sample(struct stat_data *data, struct kr_rplan *rplan, knot_pkt_t *pkt) +{ + /* Sample key = {[2] type, [1-255] owner} */ + char key[sizeof(uint16_t) + KNOT_DNAME_MAXLEN]; + /* Sample queries leading to iteration */ + struct kr_query *qry = NULL; + WALK_LIST(qry, rplan->resolved) { + if (!(qry->flags & QUERY_CACHED)) { + int key_len = collect_key(key, qry->sname, qry->stype); + unsigned *count = lru_set(data->frequent.names, key, key_len); + if (count) { + *count += 1; + } + } + } +} + static int collect(knot_layer_t *ctx) { struct kr_request *param = ctx->data; @@ -121,6 +151,10 @@ static int collect(knot_layer_t *ctx) /* Collect data on final answer */ collect_answer(data, param->answer); + /* Probabilistic sampling of queries */ + if (kr_rand_uint(FREQUENT_PSAMPLE) <= 1) { + collect_sample(data, rplan, param->answer); + } /* Count cached and unresolved */ if (!EMPTY_LIST(rplan->resolved)) { struct kr_query *last = TAIL(rplan->resolved); @@ -236,6 +270,53 @@ static char* stats_list(void *env, struct kr_module *module, const char *args) return ret; } +/** + * List frequent names. + * + * Output: [{ count: <counter>, name: <qname>, type: <qtype>}, ... ] + */ +static char* freq_list(void *env, struct kr_module *module, const char *args) +{ + struct stat_data *data = module->data; + namehash_t *freq_table = data->frequent.names; + if (!freq_table) { + return NULL; + } + uint16_t key_type = 0; + char key_name[KNOT_DNAME_MAXLEN]; + JsonNode *root = json_mkarray(); + for (unsigned i = 0; i < freq_table->size; ++i) { + struct lru_slot *slot = lru_slot_at((struct lru_hash_base *)freq_table, i); + if (slot->key) { + /* Extract query name, type and counter */ + memcpy(&key_type, slot->key, sizeof(key_type)); + knot_dname_to_str(key_name, (uint8_t *)slot->key + sizeof(key_type), sizeof(key_name)); + unsigned *slot_val = lru_slot_val(slot, lru_slot_offset(freq_table)); + /* Convert to JSON object */ + JsonNode *json_val = json_mkobject(); + json_append_member(json_val, "count", json_mknumber(*slot_val)); + json_append_member(json_val, "name", json_mkstring(key_name)); + json_append_member(json_val, "type", json_mknumber(key_type)); + json_append_element(root, json_val); + } + } + char *ret = json_encode(root); + json_delete(root); + return ret; +} + +static char* freq_clear(void *env, struct kr_module *module, const char *args) +{ + struct stat_data *data = module->data; + namehash_t *freq_table = data->frequent.names; + if (!freq_table) { + return NULL; + } + lru_deinit(freq_table); + lru_init(freq_table, FREQUENT_COUNT); + return NULL; +} + /* * Module implementation. */ @@ -259,6 +340,10 @@ int stats_init(struct kr_module *module) } data->map = map_make(); module->data = data; + data->frequent.names = malloc(lru_size(namehash_t, FREQUENT_COUNT)); + if (data->frequent.names) { + lru_init(data->frequent.names, FREQUENT_COUNT); + } return kr_ok(); } @@ -267,6 +352,8 @@ int stats_deinit(struct kr_module *module) struct stat_data *data = module->data; if (data) { map_clear(&data->map); + lru_deinit(data->frequent.names); + free(data->frequent.names); free(data); } return kr_ok(); @@ -275,9 +362,11 @@ int stats_deinit(struct kr_module *module) 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.", }, + { &stats_set, "set", "Set {key, val} metrics.", }, + { &stats_get, "get", "Get metrics for given key.", }, + { &stats_list, "list", "List observed metrics.", }, + { &freq_list, "queries", "List most frequent queries.", }, + { &freq_clear, "queries_clear", "Clear most frequent queries.", }, { NULL, NULL, NULL } }; return prop_list;