]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
modules/stats: sample list of most frequent iterative queries
authorMarek Vavruša <marek.vavrusa@nic.cz>
Mon, 13 Jul 2015 13:57:20 +0000 (15:57 +0200)
committerMarek Vavruša <marek.vavrusa@nic.cz>
Mon, 13 Jul 2015 13:57:20 +0000 (15:57 +0200)
the stats module keeps track of frequent iterative queries using
probabilistic sampling. the list includes subrequests.
it is useful for tracking over time, prefetching and anomaly reporting

daemon/README.rst
modules/stats/README.rst
modules/stats/stats.c

index ae2992ba375df4861fc3eaf482542ca79c12ff87..019dc5056dd6d34b0a68158b84ecf440b70a65d1 100644 (file)
@@ -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
index 3937c96b07ac1b5a3c6df208336f663b22f1f3b9..0f61e79da5efd23c90032d4cd89b6a0a1b64e733 100644 (file)
@@ -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.
index 11e2bd89eaee55566976ec74eae33c91f5a5d857..24d07d96f3205dd4eb0df7efb0a9d0a2169b8e54 100644 (file)
@@ -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;