1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
|
/* SPDX-License-Identifier: GPL-3.0-or-later */
// standard includes
#include <inttypes.h>
#include <limits.h>
#include <stdio.h>
#include <time.h>
// libknot includes
#include <libknot/libknot.h>
// resolver includes
#include <lib/cache/api.h>
#include <lib/cache/impl.h>
#include <lib/defines.h>
#include "lib/cache/cdb_lmdb.h"
#include "lib/generic/array.h"
#include "lib/utils.h"
#include "kr_cache_gc.h"
#include "categories.h"
#include "db.h"
// 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
typedef array_t(uint16_t) rrtype_array_t;
static void rrtypelist_add(rrtype_array_t *arr, uint16_t add_type)
{
bool already_present = false;
for (size_t i = 0; i < arr->len; i++) {
if (arr->at[i] == add_type) {
already_present = true;
break;
}
}
if (!already_present) {
kr_require(array_push(*arr, add_type) >= 0);
}
}
static void rrtypelist_print(rrtype_array_t *arr)
{
char type_s[32] = { 0 };
for (size_t i = 0; i < arr->len; i++) {
knot_rrtype_to_string(arr->at[i], type_s, sizeof(type_s));
printf(" %s", type_s);
}
printf("\n");
}
typedef array_t(knot_db_val_t *) entry_array_t;
static void entry_array_deep_free(entry_array_t *d)
{
for (size_t i = 0; i < d->len; i++) {
free(d->at[i]);
}
array_clear(*d);
}
typedef struct {
size_t categories_sizes[CATEGORIES];
size_t records;
} ctx_compute_categories_t;
int cb_compute_categories(const knot_db_val_t * key, gc_record_info_t * info,
void *vctx)
{
ctx_compute_categories_t *ctx = vctx;
category_t cat = kr_gc_categorize(info);
(void)key;
ctx->categories_sizes[cat] += info->entry_size;
ctx->records++;
return KNOT_EOK;
}
typedef struct {
category_t limit_category;
entry_array_t to_delete;
size_t cfg_temp_keys_space;
size_t used_space;
size_t oversize_records;
} ctx_delete_categories_t;
int cb_delete_categories(const knot_db_val_t * key, gc_record_info_t * info,
void *vctx)
{
ctx_delete_categories_t *ctx = vctx;
category_t cat = kr_gc_categorize(info);
if (cat >= ctx->limit_category) {
knot_db_val_t *todelete = dbval_copy(key);
size_t used = ctx->used_space + key->len + sizeof(*key);
if ((ctx->cfg_temp_keys_space > 0 &&
used > ctx->cfg_temp_keys_space) || todelete == NULL) {
ctx->oversize_records++;
free(todelete);
} else {
kr_require(array_push(ctx->to_delete, todelete) >= 0);
ctx->used_space = used;
}
}
return KNOT_EOK;
}
struct kr_cache_gc_state {
struct kr_cache kres_db;
knot_db_t *db;
};
void kr_cache_gc_free_state(kr_cache_gc_state_t **state)
{
if (kr_fails_assert(state))
return;
if (!*state) { // not open
return;
}
kr_gc_cache_close(&(*state)->kres_db, (*state)->db);
free(*state);
*state = NULL;
}
int kr_cache_gc(kr_cache_gc_cfg_t *cfg, kr_cache_gc_state_t **state)
{
// The whole function works in four "big phases":
//// 1. find out whether we should even do analysis and deletion.
if (kr_fails_assert(cfg && state))
return KNOT_EINVAL;
int ret;
// Ensure that we have open and "healthy" cache.
if (!*state) {
*state = calloc(1, sizeof(**state));
if (!*state) {
return KNOT_ENOMEM;
}
ret = kr_gc_cache_open(cfg->cache_path, &(*state)->kres_db,
&(*state)->db);
} else { // To be sure, we guard against the file getting replaced.
ret = kr_gc_cache_check_health(&(*state)->kres_db, &(*state)->db);
// In particular, missing data.mdb gives us kr_error(ENOENT) == KNOT_ENOENT
}
if (ret) {
free(*state);
*state = NULL;
return ret;
}
knot_db_t *const db = (*state)->db; // frequently used shortcut
const double db_usage = kr_cdb_lmdb()->usage_percent((*state)->kres_db.db);
const bool large_usage = db_usage >= cfg->cache_max_usage;
if (cfg->dry_run || large_usage || VERBOSE_STATUS) { // don't print this on every size check
printf("Usage: %.2lf%%\n", db_usage);
}
if (cfg->dry_run || !large_usage) {
return KNOT_EOK;
}
//// 2. classify all cache items into categories
// and compute which categories to delete.
kr_timer_t timer_analyze = { 0 }, timer_choose = { 0 }, timer_delete =
{ 0 }, timer_rw_txn = { 0 };
kr_timer_start(&timer_analyze);
ctx_compute_categories_t cats = { { 0 }
};
ret = kr_gc_cache_iter(db, cfg, cb_compute_categories, &cats);
if (ret != KNOT_EOK) {
kr_cache_gc_free_state(state);
return ret;
}
//ssize_t amount_tofree = knot_db_lmdb_get_mapsize(db) * cfg->cache_to_be_freed / 100;
// Mixing ^^ page usage and entry sizes (key+value lengths) didn't work
// too well, probably due to internal fragmentation after some GC cycles.
// Therefore let's scale this by the ratio of these two sums.
size_t cats_sumsize = 0;
for (int i = 0; i < CATEGORIES; ++i) {
cats_sumsize += cats.categories_sizes[i];
}
/* use less precise variant to avoid 32-bit overflow */
size_t amount_tofree = cats_sumsize / 100 * cfg->cache_to_be_freed;
kr_log_debug(CACHE, "tofree: %zd / %zd\n", amount_tofree, cats_sumsize);
if (VERBOSE_STATUS) {
for (int i = 0; i < CATEGORIES; i++) {
if (cats.categories_sizes[i] > 0) {
printf("category %.2d size %zu\n", i,
cats.categories_sizes[i]);
}
}
}
category_t limit_category = CATEGORIES;
while (limit_category > 0) {
size_t cat_size = cats.categories_sizes[--limit_category];
if (cat_size > amount_tofree)
break;
amount_tofree -= cat_size;
}
printf("Cache analyzed in %.0lf msecs, %zu records, limit category is %d.\n",
kr_timer_elapsed(&timer_analyze) * 1000, cats.records, limit_category);
//// 3. pass whole cache again to collect a list of keys that should be deleted.
kr_timer_start(&timer_choose);
ctx_delete_categories_t to_del = { 0 };
to_del.cfg_temp_keys_space = cfg->temp_keys_space;
to_del.limit_category = limit_category;
ret = kr_gc_cache_iter(db, cfg, cb_delete_categories, &to_del);
if (ret != KNOT_EOK) {
entry_array_deep_free(&to_del.to_delete);
kr_cache_gc_free_state(state);
return ret;
}
printf
("%zu records to be deleted using %.2lf MBytes of temporary memory, %zu records skipped due to memory limit.\n",
to_del.to_delete.len, ((double)to_del.used_space / 1048576.0),
to_del.oversize_records);
//// 4. execute the planned deletions.
const knot_db_api_t *api = knot_db_lmdb_api();
knot_db_txn_t txn = { 0 };
size_t deleted_records = 0, already_gone = 0, rw_txn_count = 0;
kr_timer_start(&timer_delete);
kr_timer_start(&timer_rw_txn);
rrtype_array_t deleted_rrtypes = { 0 };
ret = api->txn_begin(db, &txn, 0);
if (ret != KNOT_EOK) {
printf("Error starting R/W DB transaction (%s).\n",
knot_strerror(ret));
entry_array_deep_free(&to_del.to_delete);
kr_cache_gc_free_state(state);
return ret;
}
for (size_t i = 0; i < to_del.to_delete.len; i++) {
knot_db_val_t *val = to_del.to_delete.at[i];
ret = api->del(&txn, val);
switch (ret) {
case KNOT_EOK:
deleted_records++;
const int entry_type = kr_gc_key_consistent(*val);
if (entry_type >= 0) // some "inconsistent" entries are OK
rrtypelist_add(&deleted_rrtypes, entry_type);
break;
case KNOT_ENOENT:
already_gone++;
if (VERBOSE_STATUS) {
// kresd normally only inserts (or overwrites),
// so it's generally suspicious when a key goes missing.
printf("Record already gone (key len %zu): ", val->len);
debug_printbin(val->data, val->len);
printf("\n");
}
break;
case KNOT_ESPACE:
printf("Warning: out of space, bailing out to retry later.\n");
api->txn_abort(&txn);
goto finish;
default:
printf("Warning: skipping deletion because of error (%s)\n",
knot_strerror(ret));
api->txn_abort(&txn);
ret = api->txn_begin(db, &txn, 0);
if (ret != KNOT_EOK) {
printf
("Error: can't begin txn because of error (%s)\n",
knot_strerror(ret));
goto finish;
}
continue;
}
if ((cfg->rw_txn_items > 0 &&
(deleted_records + already_gone) % cfg->rw_txn_items == 0) ||
(cfg->rw_txn_duration > 0 &&
kr_timer_elapsed_us(&timer_rw_txn) > cfg->rw_txn_duration)) {
ret = api->txn_commit(&txn);
if (ret == KNOT_EOK) {
rw_txn_count++;
usleep(cfg->rw_txn_delay);
kr_timer_start(&timer_rw_txn);
ret = api->txn_begin(db, &txn, 0);
}
if (ret != KNOT_EOK) {
printf("Error: transaction failed (%s)\n",
knot_strerror(ret));
goto finish;
}
}
}
ret = api->txn_commit(&txn);
finish:
printf("Deleted %zu records (%zu already gone) types", deleted_records,
already_gone);
rrtypelist_print(&deleted_rrtypes);
printf("It took %.0lf msecs, %zu transactions (%s)\n\n",
kr_timer_elapsed(&timer_delete) * 1000, rw_txn_count, knot_strerror(ret));
array_clear(deleted_rrtypes);
entry_array_deep_free(&to_del.to_delete);
// OK, let's close it in this case.
kr_cache_gc_free_state(state);
return ret;
}
|