> 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
^^^^^^^^^^
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.
/* 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) \
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;
/* 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);
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.
*/
}
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();
}
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();
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;