diff --git a/utils/kr_cache_gc/kr_cache_gc.c b/utils/kr_cache_gc/kr_cache_gc.c index 772aed6341c62db11fa1945f0ad63a569e4c6a0a..802c9ed5065231dee56d700864e312af83085580 100644 --- a/utils/kr_cache_gc/kr_cache_gc.c +++ b/utils/kr_cache_gc/kr_cache_gc.c @@ -12,6 +12,8 @@ #include <lib/cache/impl.h> #include <lib/defines.h> +#include "kr_cache_gc.h" + // TODO remove and use time(NULL) ! this is just for debug with pre-generated cache int64_t now = 1523701784; @@ -34,6 +36,14 @@ static double gc_timer_end(gc_timer_t *t) return (((double)end.tv_sec - (double)start->tv_sec) + ((double)end.tv_nsec - (double)start->tv_nsec) / 1e9); } +static unsigned long gc_timer_usecs(gc_timer_t *t) +{ + gc_timer_t *start = t == NULL ? &gc_timer_internal : t; + gc_timer_t end = { 0 }; + (void)clock_gettime(CLOCK_MONOTONIC, &end); + return ((end.tv_sec - start->tv_sec) * 1000000UL + (end.tv_nsec - start->tv_nsec) / 1000UL); +} + // section: function key_consistent static const uint16_t *key_consistent(knot_db_val_t key) @@ -91,6 +101,19 @@ static knot_db_t *knot_db_t_kres2libknot(const knot_db_t *db) return libknot_db; } +// section: dbval_copy + +static knot_db_val_t *dbval_copy(const knot_db_val_t *from) +{ + knot_db_val_t *to = malloc(sizeof(knot_db_val_t) + from->len); + if (to != NULL) { + memcpy(to, from, sizeof(knot_db_val_t)); + to->data = to + 1; // == ((uit8_t *)to) + sizeof(knot_db_val_t) + memcpy(to->data, from->data, from->len); + } + return to; +} + // section: rrtype list dynarray_declare(rrtype, uint16_t, DYNARRAY_VISIBILITY_STATIC, 64); @@ -122,24 +145,24 @@ static void rrtypelist_print(rrtype_dynarray_t *arr) // section: main -dynarray_declare(entry, knot_db_val_t, DYNARRAY_VISIBILITY_STATIC, 256); -dynarray_define(entry, knot_db_val_t, DYNARRAY_VISIBILITY_STATIC); +dynarray_declare(entry, knot_db_val_t*, DYNARRAY_VISIBILITY_STATIC, 256); +dynarray_define(entry, knot_db_val_t*, DYNARRAY_VISIBILITY_STATIC); -int kr_cache_gc(const char *cache) +int kr_cache_gc(kr_cache_gc_cfg_t *cfg) { - char cache_data[strlen(cache) + 10]; - snprintf(cache_data, sizeof(cache_data), "%s/data.mdb", cache); + char cache_data[strlen(cfg->cache_path) + 10]; + snprintf(cache_data, sizeof(cache_data), "%s/data.mdb", cfg->cache_path); struct stat st = { 0 }; - if (stat(cache, &st) || !(st.st_mode & S_IFDIR) || stat(cache_data, &st)) { - printf("Error: %s does not exist or is not a LMDB.\n", cache); + if (stat(cfg->cache_path, &st) || !(st.st_mode & S_IFDIR) || stat(cache_data, &st)) { + printf("Error: %s does not exist or is not a LMDB.\n", cfg->cache_path); return -ENOENT; } size_t cache_size = st.st_size; - struct kr_cdb_opts opts = { cache, cache_size }; + struct kr_cdb_opts opts = { cfg->cache_path, cache_size }; struct kr_cache krc = { 0 }; int ret = kr_cache_open(&krc, NULL, &opts, NULL); @@ -148,8 +171,14 @@ int kr_cache_gc(const char *cache) return -EINVAL; } + entry_dynarray_t to_del = { 0 }; + rrtype_dynarray_t cache_rrtypes = { 0 }; + + gc_timer_t timer_analyze = { 0 }, timer_delete = { 0 }, timer_rw_txn = { 0 }; + const knot_db_api_t *api = knot_db_lmdb_api(); knot_db_txn_t txn = { 0 }; + knot_db_iter_t *it = NULL; knot_db_t *db = knot_db_t_kres2libknot(krc.db); if (db == NULL) { printf("Out of memory.\n"); @@ -160,13 +189,14 @@ int kr_cache_gc(const char *cache) size_t real_size = knot_db_lmdb_get_mapsize(db), usage = knot_db_lmdb_get_usage(db); printf("Cache size: %zu, Usage: %zu (%.2lf%%)\n", real_size, usage, (double)usage / real_size * 100.0); - ret = api->txn_begin(db, &txn, 0); + gc_timer_start(&timer_analyze); + + ret = api->txn_begin(db, &txn, KNOT_DB_RDONLY); if (ret != KNOT_EOK) { printf("Error starting DB transaction (%s).\n", knot_strerror(ret)); goto fail; } - knot_db_iter_t *it = NULL; it = api->iter_begin(&txn, KNOT_DB_FIRST); if (it == NULL) { printf("Error iterating DB.\n"); @@ -174,10 +204,9 @@ int kr_cache_gc(const char *cache) goto fail; } - entry_dynarray_t to_del = { 0 }; - rrtype_dynarray_t cache_rrtypes = { 0 }; - gc_timer_start(NULL); size_t cache_records = 0, deleted_records = 0; + size_t oversize_records = 0, already_gone = 0;; + size_t used_space = 0, rw_txn_count = 1; while (it != NULL) { knot_db_val_t key = { 0 }, val = { 0 }; @@ -194,44 +223,87 @@ int kr_cache_gc(const char *cache) int64_t over = entry->time + entry->ttl; over -= now; if (over < 0) { - entry_dynarray_add(&to_del, &key); + knot_db_val_t *todelete; + if ((cfg->temp_keys_space > 0 && + used_space + key.len + sizeof(key) > cfg->temp_keys_space) || + (todelete = dbval_copy(&key)) == NULL) { + oversize_records++; + } else { + used_space += todelete->len + sizeof(*todelete); + entry_dynarray_add(&to_del, &todelete); + } } } it = api->iter_next(it); } - printf("Cache analyzed in %.2lf secs, %zu records types", gc_timer_end(NULL), cache_records); + api->txn_abort(&txn); + + printf("Cache analyzed in %.2lf secs, %zu records types", gc_timer_end(&timer_analyze), cache_records); rrtypelist_print(&cache_rrtypes); + printf("%zu records to be deleted using %.2lf MBytes of temporary memory, %zu records skipped due to memory limit.\n", + to_del.size, ((double)used_space / 1048576.0), oversize_records); rrtype_dynarray_free(&cache_rrtypes); - gc_timer_start(NULL); + gc_timer_start(&timer_delete); + gc_timer_start(&timer_rw_txn); rrtype_dynarray_t deleted_rrtypes = { 0 }; - dynarray_foreach(entry, knot_db_val_t, i, to_del) { - ret = api->del(&txn, i); - if (ret != KNOT_EOK) { - printf("Warning: skipping deleting because of error (%s)\n", knot_strerror(ret)); - } else { + ret = api->txn_begin(db, &txn, 0); + if (ret != KNOT_EOK) { + printf("Error starting DB transaction (%s).\n", knot_strerror(ret)); + goto fail; + } + + dynarray_foreach(entry, knot_db_val_t*, i, to_del) { + ret = api->del(&txn, *i); + switch (ret) { + case KNOT_EOK: deleted_records++; - const uint16_t *entry_type = ret == KNOT_EOK ? key_consistent(*i) : NULL; + const uint16_t *entry_type = ret == KNOT_EOK ? key_consistent(**i) : NULL; assert(entry_type != NULL); rrtypelist_add(&deleted_rrtypes, *entry_type); + break; + case KNOT_ENOENT: + already_gone++; + break; + default: + printf("Warning: skipping deleting because of error (%s)\n", knot_strerror(ret)); + continue; + } + if ((cfg->rw_txn_items > 0 && + (deleted_records + already_gone) % cfg->rw_txn_items == 0) || + (cfg->rw_txn_duration > 0 && + gc_timer_usecs(&timer_rw_txn) > cfg->rw_txn_duration)) { + ret = api->txn_commit(&txn); + if (ret == KNOT_EOK) { + rw_txn_count++; + usleep(cfg->rw_txn_delay); + gc_timer_start(&timer_rw_txn); + ret = api->txn_begin(db, &txn, 0); + } + if (ret != KNOT_EOK) { + printf("Error restarting DB transaction (%s)\n", knot_strerror(ret)); + goto fail; + } } } - printf("Deleted in %.2lf secs %zu records types", gc_timer_end(NULL), deleted_records); + printf("Deleted %zu records (%zu already gone) types", deleted_records, already_gone); rrtypelist_print(&deleted_rrtypes); - rrtype_dynarray_free(&deleted_rrtypes); - - entry_dynarray_free(&to_del); + printf("It took %.2lf secs, %zu transactions \n", gc_timer_end(&timer_delete), rw_txn_count); - //api->iter_finish(it); - //it = NULL; ret = api->txn_commit(&txn); txn.txn = NULL; fail: + rrtype_dynarray_free(&deleted_rrtypes); + dynarray_foreach(entry, knot_db_val_t*, i, to_del) { + free(*i); + } + entry_dynarray_free(&to_del); + api->iter_finish(it); if (txn.txn) { api->txn_abort(&txn); diff --git a/utils/kr_cache_gc/kr_cache_gc.h b/utils/kr_cache_gc/kr_cache_gc.h index b492f8b3b71216e3dedee062fda99f0ce22934f6..8c2e3b2e4fe92c41639f177297428ac96855f334 100644 --- a/utils/kr_cache_gc/kr_cache_gc.h +++ b/utils/kr_cache_gc/kr_cache_gc.h @@ -1,3 +1,19 @@ -int kr_cache_gc(const char *cache); +#pragma once + +#include <stddef.h> + +typedef struct { + const char *cache_path; // path to the LMDB with resolver cache + unsigned long gc_interval; // waiting time between two whole garbage collections in usecs (0 = just one-time cleanup) + + size_t temp_keys_space; // maximum amount of temporary memory for copied keys in bytes (0 = unlimited) + + size_t rw_txn_items; // maximum number of deleted records per RW transaction (0 = unlimited) + unsigned long rw_txn_duration; // maximum duration of RW transaction in usecs (0 = unlimited) + unsigned long rw_txn_delay; // waiting time between two RW transactions in usecs +} kr_cache_gc_cfg_t; + + +int kr_cache_gc(kr_cache_gc_cfg_t *cfg); #define KR_CACHE_GC_VERSION "0.1" diff --git a/utils/kr_cache_gc/main.c b/utils/kr_cache_gc/main.c index bacea1742e3716fa986d08da122c11e234bf9e0d..808f66bbf8b774d2ba6697f7f2846c45645b5fd9 100644 --- a/utils/kr_cache_gc/main.c +++ b/utils/kr_cache_gc/main.c @@ -28,7 +28,13 @@ static void got_killed(int signum) static void print_help() { - printf("Usage: kr_cache_gc -c <resolver_cache> [ -d <garbage_interval(ms)> ]\n"); + printf("Usage: kr_cache_gc -c <resolver_cache> [ optional params... ]\n"); + printf("Optional params:\n"); + printf(" -d <garbage_interval(millis)>\n"); + printf(" -l <deletes_per_txn>\n"); + printf(" -m <rw_txn_duration(usecs)>\n"); + printf(" -w <wait_next_rw_txn(usecs)>\n"); + printf(" -t <temporary_memory(MBytes)>\n"); } int main(int argc, char *argv[]) @@ -41,22 +47,33 @@ int main(int argc, char *argv[]) signal(SIGCHLD, got_killed); signal(SIGINT, got_killed); - const char *cache_path = NULL; - unsigned long interval = 0; + kr_cache_gc_cfg_t cfg = { 0 }; int o; - while ((o = getopt(argc, argv, "hc:d:")) != -1) { + while ((o = getopt(argc, argv, "hc:d:l:m:w:t:")) != -1) { switch (o) { case 'c': - cache_path = optarg; + cfg.cache_path = optarg; break; +#define get_nonneg_optarg(to) do { if (atol(optarg) < 0) { print_help(); return 2; } to = atol(optarg); } while (0) case 'd': - if (atol(optarg) < 0) { - print_help(); - return 2; - } - interval = atol(optarg) * 1000; + get_nonneg_optarg(cfg.gc_interval); + cfg.gc_interval *= 1000; break; + case 'l': + get_nonneg_optarg(cfg.rw_txn_items); + break; + case 'm': + get_nonneg_optarg(cfg.rw_txn_duration); + break; + case 'w': + get_nonneg_optarg(cfg.rw_txn_delay); + break; + case 't': + get_nonneg_optarg(cfg.temp_keys_space); + cfg.temp_keys_space *= 1048576; + break; +#undef get_nonneg_optarg case ':': case '?': case 'h': @@ -67,20 +84,20 @@ int main(int argc, char *argv[]) } } - if (cache_path == NULL) { + if (cfg.cache_path == NULL) { print_help(); return 1; } do { - int ret = kr_cache_gc(cache_path); + int ret = kr_cache_gc(&cfg); if (ret) { printf("Error (%s)\n", kr_strerror(ret)); return 10; } - usleep(interval); - } while (interval > 0 && !killed); + usleep(cfg.gc_interval); + } while (cfg.gc_interval > 0 && !killed); return 0; }