]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
modules/cachectl: list, prune and clear cache
authorMarek Vavruša <marek.vavrusa@nic.cz>
Thu, 19 Mar 2015 23:08:50 +0000 (00:08 +0100)
committerMarek Vavruša <marek.vavrusa@nic.cz>
Thu, 19 Mar 2015 23:08:50 +0000 (00:08 +0100)
refs #7

modules/cachectl/cachectl.c [new file with mode: 0644]
modules/cachectl/cachectl.mk [new file with mode: 0644]
modules/modules.mk

diff --git a/modules/cachectl/cachectl.c b/modules/cachectl/cachectl.c
new file mode 100644 (file)
index 0000000..18105ba
--- /dev/null
@@ -0,0 +1,170 @@
+/*  Copyright (C) 2014 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/>.
+ */
+
+/**
+ * Partial sweep is not feasible as we don't have a list of items sorted
+ * by age nor any sort of LRU/MRU, completely random replace is not possible
+ * as well.
+ * - Idea - make poor man's LRU with two databases doing following:
+ *   - Fill up 1, mark that it's unwritable
+ *   - Fill up 2, mark that it's unwritable
+ *   - Clear 1, all writes will now go in there
+ *   - This gives us LR(written) with resolution 2
+ */
+
+#include <time.h>
+
+#include "lib/module.h"
+#include "lib/context.h"
+#include "lib/cache.h"
+
+/*
+ * Properties.
+ */
+
+/**
+ * Return number of cached records.
+ *
+ * Input:  N/A
+ * Output: { result: [int] size }
+ * 
+ */
+static char* get_size(struct kr_context *ctx, struct kr_module *module, const char *args)
+{
+       char *result = NULL;
+       const namedb_api_t *storage = kr_cache_storage();
+
+       /* Fetch item count */
+       namedb_txn_t txn;
+       int ret = kr_cache_txn_begin(ctx->cache, &txn, NAMEDB_RDONLY);
+       if (ret == 0) {
+               asprintf(&result, "{ \"result\": %d }", storage->count(&txn));
+               kr_cache_txn_abort(&txn);
+       }
+       
+       return result;
+}
+
+/** Return boolean true if a record in the RR set is expired. */
+static int is_expired(struct kr_cache_rrset *rr, uint32_t drift)
+{
+       /* Initialize set. */
+       knot_rdataset_t rrs;
+       rrs.rr_count = rr->count;
+       rrs.data =  rr->data;
+
+       for (unsigned i = 0; i < rrs.rr_count; ++i) {
+               const knot_rdata_t *rd = knot_rdataset_at(&rrs, i);
+               if (knot_rdata_ttl(rd) <= drift) {
+                       return 1;
+               }
+       }
+
+       return 0;
+}
+
+/**
+ * Prune expired/invalid records.
+ *
+ * Input:  N/A
+ * Output: { result: [int] nr_pruned }
+ * 
+ */
+static char* prune(struct kr_context *ctx, struct kr_module *module, const char *args)
+{
+       const namedb_api_t *storage = kr_cache_storage();
+
+       namedb_txn_t txn;
+       int ret = kr_cache_txn_begin(ctx->cache, &txn, 0);
+       if (ret != 0) {
+               return NULL;
+       }
+
+       /* Iterate cache and find expired records. */
+       int pruned = 0;
+       uint32_t now = time(NULL);
+       namedb_iter_t *it = storage->iter_begin(&txn, 0);
+       while (it) {
+               /* Fetch RR from cache */
+               namedb_val_t key, val;
+               if (storage->iter_key(it, &key) != 0 ||
+                   storage->iter_val(it, &val)) {
+                       break;
+               }
+               /* Prune expired records. */
+               struct kr_cache_rrset *rr = val.data;
+               if (is_expired(rr, now - rr->timestamp)) {
+                       storage->del(&txn, &key);
+                       pruned += 1;
+               }
+               it = storage->iter_next(it);
+       }
+
+       /* Commit and format result. */
+       char *result = NULL;
+       if (kr_cache_txn_commit(&txn) != 0) {
+               asprintf(&result, "{ \"result\": %d, \"error\": \"%s\" }", pruned, knot_strerror(ret));
+       } else {
+               asprintf(&result, "{ \"result\": %d }", pruned);
+       }
+       
+       return result;
+}
+
+/**
+ * Clear all records.
+ *
+ * Input:  N/A
+ * Output: { result: [bool] success }
+ * 
+ */
+static char* clear(struct kr_context *ctx, struct kr_module *module, const char *args)
+{
+       namedb_txn_t txn;
+       int ret = kr_cache_txn_begin(ctx->cache, &txn, 0);
+       if (ret != 0) {
+               return NULL;
+       }
+
+       /* Clear cache and commit. */
+       ret = kr_cache_clear(&txn);
+       if (ret == 0) {
+               ret = kr_cache_txn_commit(&txn);
+       } else {
+               kr_cache_txn_abort(&txn);
+       }
+
+       char *result = NULL;
+       asprintf(&result, "{ \"result\": %s }", ret == 0 ? "true" : "false");
+       return result;
+}
+
+/*
+ * Module implementation.
+ */
+
+struct kr_prop *cachectl_props(void)
+{
+    static struct kr_prop prop_list[] = {
+        { &get_size, "size",  "Return number of cached records.", },
+        { &prune,    "prune", "Prune expired/invalid records.", },
+        { &clear,    "clear", "Clear all cache records.", },
+        { NULL, NULL, NULL }
+    };
+    return prop_list;
+}
+
+KR_MODULE_EXPORT(cachectl);
diff --git a/modules/cachectl/cachectl.mk b/modules/cachectl/cachectl.mk
new file mode 100644 (file)
index 0000000..c089349
--- /dev/null
@@ -0,0 +1,4 @@
+cachectl_SOURCES := modules/cachectl/cachectl.c
+cachectl_DEPEND := libkresolve
+cachectl_LIBS := $(libkresolve_TARGET) $(libkresolve_LIBS)
+$(call make_c_module,cachectl)
\ No newline at end of file
index b4fdcda4ae9b0e2be8cc571a6987b5d38d78e6a0..60a9ef80a068bc2faa1a1cbeee594f337f9fc5a9 100644 (file)
@@ -1,5 +1,6 @@
 # List of built-in modules
-modules_TARGETS := hints
+modules_TARGETS := hints \
+                   cachectl
 
 # List of Golang modules
 $(eval $(call find_bin,gccgo))
@@ -41,4 +42,4 @@ endef
 modules: $(modules_TARGETS)
 modules-clean: $(addsuffix -clean,$(modules_TARGETS))
 modules-install: $(addsuffix -install,$(modules_TARGETS))
-$(foreach module,$(modules_TARGETS),$(eval include modules/$(module)/$(module).mk))
\ No newline at end of file
+$(foreach module,$(modules_TARGETS),$(eval include modules/$(module)/$(module).mk))