| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "rrdengine.h" |
| 4 | #include "pdc.h" |
| 5 | #include "dbengine-compression.h" |
| 6 | |
| 7 | struct rrdeng_global_stats global_stats = { 0 }; |
| 8 | |
| 9 | unsigned rrdeng_pages_per_extent = DEFAULT_PAGES_PER_EXTENT; |
| 10 | |
| 11 | #if WORKER_UTILIZATION_MAX_JOB_TYPES < (RRDENG_OPCODE_MAX + 2) |
| 12 | #error Please increase WORKER_UTILIZATION_MAX_JOB_TYPES to at least (RRDENG_MAX_OPCODE + 2) |
| 13 | #endif |
| 14 | |
| 15 | struct rrdeng_cmd { |
| 16 | struct rrdengine_instance *ctx; |
| 17 | enum rrdeng_opcode opcode; |
| 18 | void *data; |
| 19 | struct completion *completion; |
| 20 | enum storage_priority priority; |
| 21 | dequeue_callback_t dequeue_cb; |
| 22 | |
| 23 | struct { |
| 24 | struct rrdeng_cmd *prev; |
| 25 | struct rrdeng_cmd *next; |
| 26 | } queue; |
| 27 | }; |
| 28 | |
| 29 | static inline struct rrdeng_cmd rrdeng_deq_cmd(bool from_worker); |
| 30 | static inline void worker_dispatch_extent_read(struct rrdeng_cmd cmd, bool from_worker); |
| 31 | static inline void worker_dispatch_query_prep(struct rrdeng_cmd cmd, bool from_worker); |
| 32 | |
| 33 | struct rrdeng_main { |
| 34 | ND_THREAD *thread; |
| 35 | uv_loop_t loop; |
| 36 | uv_async_t async; |
| 37 | #if defined(OS_WINDOWS) |
| 38 | bool async_ready; |
| 39 | #endif |
| 40 | uv_timer_t timer; |
| 41 | uv_timer_t retention_timer; |
| 42 | pid_t tid; |
| 43 | bool shutdown; |
| 44 | |
| 45 | size_t flushes_running; |
| 46 | size_t evict_main_running; |
| 47 | size_t evict_open_running; |
| 48 | size_t evict_extent_running; |
| 49 | size_t cleanup_running; |
| 50 | |
| 51 | struct { |
| 52 | ARAL *ar; |
| 53 | |
| 54 | struct { |
| 55 | SPINLOCK spinlock; |
| 56 | |
| 57 | size_t waiting; |
| 58 | struct rrdeng_cmd *waiting_items_by_priority[STORAGE_PRIORITY_INTERNAL_MAX_DONT_USE]; |
| 59 | size_t executed_by_priority[STORAGE_PRIORITY_INTERNAL_MAX_DONT_USE]; |
| 60 | } unsafe; |
| 61 | } cmd_queue; |
| 62 | |
| 63 | struct { |
| 64 | ARAL *ar; |
| 65 | |
| 66 | struct { |
| 67 | size_t dispatched; |
| 68 | size_t executing; |
| 69 | } atomics; |
| 70 | } work_cmd; |
| 71 | |
| 72 | struct { |
| 73 | ARAL *ar; |
| 74 | } handles; |
| 75 | |
| 76 | struct { |
| 77 | ARAL *ar; |
| 78 | } descriptors; |
| 79 | |
| 80 | struct { |
| 81 | ARAL *ar; |
| 82 | } xt_io_descr; |
| 83 | |
| 84 | } rrdeng_main = { |
| 85 | .thread = 0, |
| 86 | .loop = {}, |
| 87 | .async = {}, |
| 88 | .timer = {}, |
| 89 | .retention_timer = {}, |
| 90 | .flushes_running = 0, |
| 91 | .evict_main_running = 0, |
| 92 | .cleanup_running = 0, |
| 93 | |
| 94 | .cmd_queue = { |
| 95 | .unsafe = { |
| 96 | .spinlock = SPINLOCK_INITIALIZER, |
| 97 | }, |
| 98 | } |
| 99 | }; |
| 100 | |
| 101 | #if defined(OS_WINDOWS) |
| 102 | netdata_mutex_t rrdeng_async_mutex; |
| 103 | |
| 104 | __attribute__((constructor)) void initialize_rrdeng_async_mutex(void) |
| 105 | { |
| 106 | netdata_mutex_init(&rrdeng_async_mutex); |
| 107 | } |
| 108 | |
| 109 | __attribute__((destructor)) void destroy_rrdeng_async_mutex(void) |
| 110 | { |
| 111 | netdata_mutex_destroy(&rrdeng_async_mutex); |
| 112 | } |
| 113 | |
| 114 | void rrdeng_async_wakeup() |
| 115 | { |
| 116 | |
| 117 | if (__atomic_load_n(&rrdeng_main.async_ready, __ATOMIC_RELAXED)) { |
| 118 | netdata_mutex_lock(&rrdeng_async_mutex); |
| 119 | int rc = uv_async_send(&rrdeng_main.async); |
| 120 | if (rc) |
| 121 | nd_log_daemon(NDLP_ERR,"DBENGINE: wakeup async error = %d", rc); |
| 122 | |
| 123 | netdata_mutex_unlock(&rrdeng_async_mutex); |
| 124 | } else { |
| 125 | nd_log_daemon(NDLP_WARNING,"DBENGINE: wakeup async handler is being reset"); |
| 126 | } |
| 127 | } |
| 128 | #else |
| 129 | void rrdeng_async_wakeup() |
| 130 | { |
| 131 | int rc = uv_async_send(&rrdeng_main.async); |
| 132 | if (rc) |
| 133 | nd_log_daemon(NDLP_ERR,"DBENGINE: wakeup async error = %d", rc); |
| 134 | } |
| 135 | #endif |
| 136 | |
| 137 | static void sanity_check(void) |
| 138 | { |
| 139 | BUILD_BUG_ON(WORKER_UTILIZATION_MAX_JOB_TYPES < (RRDENG_OPCODE_MAX + 2)); |
| 140 | |
| 141 | /* Magic numbers must fit in the super-blocks */ |
| 142 | BUILD_BUG_ON(sizeof(RRDENG_DF_MAGIC) - 1 > RRDENG_MAGIC_SZ); |
| 143 | BUILD_BUG_ON(sizeof(RRDENG_JF_MAGIC) - 1 > RRDENG_MAGIC_SZ); |
| 144 | |
| 145 | /* Version strings must fit in the super-blocks */ |
| 146 | BUILD_BUG_ON(sizeof(RRDENG_DF_VER) - 1 > RRDENG_VER_SZ); |
| 147 | BUILD_BUG_ON(sizeof(RRDENG_JF_VER) - 1 > RRDENG_VER_SZ); |
| 148 | |
| 149 | /* Data file super-block cannot be larger than RRDENG_BLOCK_SIZE */ |
| 150 | BUILD_BUG_ON(RRDENG_DF_SB_PADDING_SZ < 0); |
| 151 | |
| 152 | BUILD_BUG_ON(sizeof(nd_uuid_t) != UUID_SZ); /* check UUID size */ |
| 153 | |
| 154 | /* page count must fit in 8 bits */ |
| 155 | BUILD_BUG_ON(MAX_PAGES_PER_EXTENT > 255); |
| 156 | } |
| 157 | |
| 158 | // ---------------------------------------------------------------------------- |
| 159 | // work request cache |
| 160 | |
| 161 | typedef void *(*work_cb)(struct rrdengine_instance *ctx, void *data, struct completion *completion, uv_work_t* req); |
| 162 | typedef void (*after_work_cb)(struct rrdengine_instance *ctx, void *data, struct completion *completion, uv_work_t* req, int status); |
| 163 | |
| 164 | struct rrdeng_work { |
| 165 | uv_work_t req; |
| 166 | |
| 167 | struct rrdengine_instance *ctx; |
| 168 | void *data; |
| 169 | struct completion *completion; |
| 170 | |
| 171 | work_cb work_cb; |
| 172 | after_work_cb after_work_cb; |
| 173 | enum rrdeng_opcode opcode; |
| 174 | }; |
| 175 | |
| 176 | static void work_request_init(void) { |
| 177 | rrdeng_main.work_cmd.ar = aral_create( |
| 178 | "dbengine-work-cmd", |
| 179 | sizeof(struct rrdeng_work), |
| 180 | 0, |
| 181 | 0, |
| 182 | NULL, |
| 183 | NULL, NULL, false, false, true |
| 184 | ); |
| 185 | |
| 186 | pulse_aral_register(rrdeng_main.work_cmd.ar, "workers"); |
| 187 | } |
| 188 | |
| 189 | enum LIBUV_WORKERS_STATUS { |
| 190 | LIBUV_WORKERS_RELAXED, |
| 191 | LIBUV_WORKERS_STRESSED, |
| 192 | LIBUV_WORKERS_CRITICAL, |
| 193 | }; |
| 194 | |
| 195 | static inline enum LIBUV_WORKERS_STATUS work_request_full(void) { |
| 196 | size_t dispatched = __atomic_load_n(&rrdeng_main.work_cmd.atomics.dispatched, __ATOMIC_RELAXED); |
| 197 | |
| 198 | if(dispatched >= (size_t)(libuv_worker_threads)) |
| 199 | return LIBUV_WORKERS_CRITICAL; |
| 200 | |
| 201 | else if(dispatched >= (size_t)(libuv_worker_threads - RESERVED_LIBUV_WORKER_THREADS)) |
| 202 | return LIBUV_WORKERS_STRESSED; |
| 203 | |
| 204 | return LIBUV_WORKERS_RELAXED; |
| 205 | } |
| 206 | |
| 207 | // This needs to be called from event loop thread only (callback) |
| 208 | static inline void check_and_schedule_db_rotation(struct rrdengine_instance *ctx) |
| 209 | { |
| 210 | internal_fatal(rrdeng_main.tid != gettid_cached(), "check_and_schedule_db_rotation() can only be run from the event loop thread"); |
| 211 | |
| 212 | if (__atomic_load_n(&ctx->atomic.needs_indexing, __ATOMIC_RELAXED)) { |
| 213 | if (ctx->datafiles.pending_index == false) { |
| 214 | ctx->datafiles.pending_index = true; |
| 215 | rrdeng_enq_cmd(ctx, RRDENG_OPCODE_JOURNAL_INDEX, NULL, NULL, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL); |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | if (ctx->datafiles.pending_rotate) { |
| 220 | nd_log_daemon(NDLP_DEBUG, "DBENGINE: tier %d is already pending rotation", ctx->config.tier); |
| 221 | return; |
| 222 | } |
| 223 | |
| 224 | if(rrdeng_ctx_tier_cap_exceeded(ctx)) { |
| 225 | ctx->datafiles.pending_rotate = true; |
| 226 | rrdeng_enq_cmd(ctx, RRDENG_OPCODE_DATABASE_ROTATE, NULL, NULL, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL); |
| 227 | } |
| 228 | } |
| 229 | |
| 230 | static inline void work_done(struct rrdeng_work *work_request) { |
| 231 | aral_freez(rrdeng_main.work_cmd.ar, work_request); |
| 232 | } |
| 233 | |
| 234 | static void work_standard_worker(uv_work_t *req) { |
| 235 | __atomic_add_fetch(&rrdeng_main.work_cmd.atomics.executing, 1, __ATOMIC_RELAXED); |
| 236 | |
| 237 | register_libuv_worker_jobs(); |
| 238 | worker_is_busy(UV_EVENT_WORKER_INIT); |
| 239 | |
| 240 | struct rrdeng_work *work_request = req->data; |
| 241 | |
| 242 | work_request->data = work_request->work_cb(work_request->ctx, work_request->data, work_request->completion, req); |
| 243 | worker_is_idle(); |
| 244 | |
| 245 | if(work_request->opcode == RRDENG_OPCODE_EXTENT_READ || work_request->opcode == RRDENG_OPCODE_QUERY) { |
| 246 | internal_fatal(work_request->after_work_cb != NULL, "DBENGINE: opcodes with a callback should not boosted"); |
| 247 | |
| 248 | while(1) { |
| 249 | struct rrdeng_cmd cmd = rrdeng_deq_cmd(true); |
| 250 | if (cmd.opcode == RRDENG_OPCODE_NOOP) |
| 251 | break; |
| 252 | |
| 253 | worker_is_busy(UV_EVENT_WORKER_INIT); |
| 254 | switch (cmd.opcode) { |
| 255 | case RRDENG_OPCODE_EXTENT_READ: |
| 256 | worker_dispatch_extent_read(cmd, true); |
| 257 | break; |
| 258 | |
| 259 | case RRDENG_OPCODE_QUERY: |
| 260 | worker_dispatch_query_prep(cmd, true); |
| 261 | break; |
| 262 | |
| 263 | default: |
| 264 | fatal("DBENGINE: Opcode should not be executed synchronously"); |
| 265 | break; |
| 266 | } |
| 267 | worker_is_idle(); |
| 268 | } |
| 269 | } |
| 270 | |
| 271 | __atomic_sub_fetch(&rrdeng_main.work_cmd.atomics.dispatched, 1, __ATOMIC_RELAXED); |
| 272 | __atomic_sub_fetch(&rrdeng_main.work_cmd.atomics.executing, 1, __ATOMIC_RELAXED); |
| 273 | } |
| 274 | |
| 275 | static void after_work_standard_callback(uv_work_t* req, int status) { |
| 276 | struct rrdeng_work *work_request = req->data; |
| 277 | |
| 278 | worker_is_busy(RRDENG_OPCODE_MAX + work_request->opcode); |
| 279 | |
| 280 | if(work_request->after_work_cb) |
| 281 | work_request->after_work_cb(work_request->ctx, work_request->data, work_request->completion, req, status); |
| 282 | |
| 283 | work_done(work_request); |
| 284 | |
| 285 | worker_is_idle(); |
| 286 | } |
| 287 | |
| 288 | static bool work_dispatch(struct rrdengine_instance *ctx, void *data, struct completion *completion, enum rrdeng_opcode opcode, work_cb do_work_cb, after_work_cb do_after_work_cb) { |
| 289 | struct rrdeng_work *work_request = NULL; |
| 290 | |
| 291 | internal_fatal(rrdeng_main.tid != gettid_cached(), "work_dispatch() can only be run from the event loop thread"); |
| 292 | |
| 293 | work_request = aral_mallocz(rrdeng_main.work_cmd.ar); |
| 294 | memset(work_request, 0, sizeof(struct rrdeng_work)); |
| 295 | work_request->req.data = work_request; |
| 296 | work_request->ctx = ctx; |
| 297 | work_request->data = data; |
| 298 | work_request->completion = completion; |
| 299 | work_request->work_cb = do_work_cb; |
| 300 | work_request->after_work_cb = do_after_work_cb; |
| 301 | work_request->opcode = opcode; |
| 302 | |
| 303 | if(uv_queue_work(&rrdeng_main.loop, &work_request->req, work_standard_worker, after_work_standard_callback)) { |
| 304 | internal_fatal(true, "DBENGINE: cannot queue work"); |
| 305 | work_done(work_request); |
| 306 | return false; |
| 307 | } |
| 308 | |
| 309 | __atomic_add_fetch(&rrdeng_main.work_cmd.atomics.dispatched, 1, __ATOMIC_RELAXED); |
| 310 | |
| 311 | return true; |
| 312 | } |
| 313 | |
| 314 | // ---------------------------------------------------------------------------- |
| 315 | // page descriptor cache |
| 316 | |
| 317 | void page_descriptors_init(void) { |
| 318 | rrdeng_main.descriptors.ar = aral_create( |
| 319 | "dbengine-descriptors", |
| 320 | sizeof(struct page_descr_with_data), |
| 321 | 0, |
| 322 | 0, |
| 323 | NULL, |
| 324 | NULL, NULL, false, false, true); |
| 325 | |
| 326 | pulse_aral_register(rrdeng_main.descriptors.ar, "descriptors"); |
| 327 | } |
| 328 | |
| 329 | struct page_descr_with_data *page_descriptor_get(void) { |
| 330 | struct page_descr_with_data *descr = aral_mallocz(rrdeng_main.descriptors.ar); |
| 331 | memset(descr, 0, sizeof(struct page_descr_with_data)); |
| 332 | return descr; |
| 333 | } |
| 334 | |
| 335 | static inline void page_descriptor_release(struct page_descr_with_data *descr) { |
| 336 | uuidmap_free(descr->uuid_id); |
| 337 | aral_freez(rrdeng_main.descriptors.ar, descr); |
| 338 | } |
| 339 | |
| 340 | // ---------------------------------------------------------------------------- |
| 341 | // extent io descriptor cache |
| 342 | |
| 343 | static void extent_io_descriptor_init(void) { |
| 344 | rrdeng_main.xt_io_descr.ar = aral_create( |
| 345 | "dbengine-extent-io", |
| 346 | sizeof(struct extent_io_descriptor), |
| 347 | 0, |
| 348 | 0, |
| 349 | NULL, |
| 350 | NULL, NULL, false, false, true |
| 351 | ); |
| 352 | |
| 353 | pulse_aral_register(rrdeng_main.xt_io_descr.ar, "extent io"); |
| 354 | } |
| 355 | |
| 356 | static struct extent_io_descriptor *extent_io_descriptor_get(void) { |
| 357 | struct extent_io_descriptor *xt_io_descr = aral_mallocz(rrdeng_main.xt_io_descr.ar); |
| 358 | memset(xt_io_descr, 0, sizeof(struct extent_io_descriptor)); |
| 359 | return xt_io_descr; |
| 360 | } |
| 361 | |
| 362 | static inline void extent_io_descriptor_release(struct extent_io_descriptor *xt_io_descr) { |
| 363 | aral_freez(rrdeng_main.xt_io_descr.ar, xt_io_descr); |
| 364 | } |
| 365 | |
| 366 | // ---------------------------------------------------------------------------- |
| 367 | // query handle cache |
| 368 | |
| 369 | void rrdeng_query_handle_init(void) { |
| 370 | rrdeng_main.handles.ar = aral_create( |
| 371 | "dbengine-query-handles", |
| 372 | sizeof(struct rrdeng_query_handle), |
| 373 | 0, |
| 374 | 0, |
| 375 | NULL, |
| 376 | NULL, NULL, false, false, true); |
| 377 | |
| 378 | pulse_aral_register(rrdeng_main.handles.ar, "query handles"); |
| 379 | } |
| 380 | |
| 381 | ALWAYS_INLINE struct rrdeng_query_handle *rrdeng_query_handle_get(void) { |
| 382 | struct rrdeng_query_handle *handle = aral_mallocz(rrdeng_main.handles.ar); |
| 383 | memset(handle, 0, sizeof(struct rrdeng_query_handle)); |
| 384 | return handle; |
| 385 | } |
| 386 | |
| 387 | ALWAYS_INLINE void rrdeng_query_handle_release(struct rrdeng_query_handle *handle) { |
| 388 | aral_freez(rrdeng_main.handles.ar, handle); |
| 389 | } |
| 390 | |
| 391 | // ---------------------------------------------------------------------------- |
| 392 | // WAL cache |
| 393 | |
| 394 | static struct { |
| 395 | struct { |
| 396 | SPINLOCK spinlock; |
| 397 | WAL *available_items; |
| 398 | size_t available; |
| 399 | } protected; |
| 400 | |
| 401 | struct { |
| 402 | size_t allocated; |
| 403 | } atomics; |
| 404 | } wal_globals = { |
| 405 | .protected = { |
| 406 | .spinlock = SPINLOCK_INITIALIZER, |
| 407 | .available_items = NULL, |
| 408 | .available = 0, |
| 409 | }, |
| 410 | .atomics = { |
| 411 | .allocated = 0, |
| 412 | }, |
| 413 | }; |
| 414 | |
| 415 | static void wal_cleanup1(void) { |
| 416 | WAL *wal = NULL; |
| 417 | |
| 418 | if(!spinlock_trylock(&wal_globals.protected.spinlock)) |
| 419 | return; |
| 420 | |
| 421 | if(wal_globals.protected.available_items && wal_globals.protected.available > nd_profile.storage_tiers) { |
| 422 | wal = wal_globals.protected.available_items; |
| 423 | DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(wal_globals.protected.available_items, wal, cache.prev, cache.next); |
| 424 | wal_globals.protected.available--; |
| 425 | } |
| 426 | |
| 427 | spinlock_unlock(&wal_globals.protected.spinlock); |
| 428 | |
| 429 | if(wal) { |
| 430 | posix_memalign_freez(wal->buf); |
| 431 | freez(wal); |
| 432 | __atomic_sub_fetch(&wal_globals.atomics.allocated, 1, __ATOMIC_RELAXED); |
| 433 | } |
| 434 | } |
| 435 | |
| 436 | WAL *wal_get(struct rrdengine_instance *ctx, unsigned size) { |
| 437 | if(!size || size > RRDENG_BLOCK_SIZE) |
| 438 | fatal("DBENGINE: invalid WAL size requested"); |
| 439 | |
| 440 | WAL *wal = NULL; |
| 441 | |
| 442 | spinlock_lock(&wal_globals.protected.spinlock); |
| 443 | |
| 444 | if(likely(wal_globals.protected.available_items)) { |
| 445 | wal = wal_globals.protected.available_items; |
| 446 | DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(wal_globals.protected.available_items, wal, cache.prev, cache.next); |
| 447 | wal_globals.protected.available--; |
| 448 | } |
| 449 | |
| 450 | uint64_t transaction_id = __atomic_fetch_add(&ctx->atomic.transaction_id, 1, __ATOMIC_RELAXED); |
| 451 | spinlock_unlock(&wal_globals.protected.spinlock); |
| 452 | |
| 453 | if(unlikely(!wal)) { |
| 454 | wal = mallocz(sizeof(WAL)); |
| 455 | wal->buf_size = RRDENG_BLOCK_SIZE; |
| 456 | (void)posix_memalignz((void *)&wal->buf, RRDFILE_ALIGNMENT, wal->buf_size); |
| 457 | __atomic_add_fetch(&wal_globals.atomics.allocated, 1, __ATOMIC_RELAXED); |
| 458 | } |
| 459 | |
| 460 | // these need to survive |
| 461 | unsigned buf_size = wal->buf_size; |
| 462 | void *buf = wal->buf; |
| 463 | |
| 464 | memset(wal, 0, sizeof(WAL)); |
| 465 | |
| 466 | // put them back |
| 467 | wal->buf_size = buf_size; |
| 468 | wal->buf = buf; |
| 469 | |
| 470 | memset(wal->buf, 0, wal->buf_size); |
| 471 | |
| 472 | wal->transaction_id = transaction_id; |
| 473 | wal->size = size; |
| 474 | |
| 475 | return wal; |
| 476 | } |
| 477 | |
| 478 | void wal_release(WAL *wal) { |
| 479 | if(unlikely(!wal)) return; |
| 480 | |
| 481 | spinlock_lock(&wal_globals.protected.spinlock); |
| 482 | DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(wal_globals.protected.available_items, wal, cache.prev, cache.next); |
| 483 | wal_globals.protected.available++; |
| 484 | spinlock_unlock(&wal_globals.protected.spinlock); |
| 485 | } |
| 486 | |
| 487 | // ---------------------------------------------------------------------------- |
| 488 | // command queue cache |
| 489 | |
| 490 | static void rrdeng_cmd_queue_init(void) { |
| 491 | rrdeng_main.cmd_queue.ar = aral_create("dbengine-opcodes", |
| 492 | sizeof(struct rrdeng_cmd), |
| 493 | 0, |
| 494 | 0, |
| 495 | NULL, |
| 496 | NULL, NULL, false, false, true); |
| 497 | |
| 498 | pulse_aral_register(rrdeng_main.cmd_queue.ar, "opcodes"); |
| 499 | } |
| 500 | |
| 501 | static inline STORAGE_PRIORITY rrdeng_enq_cmd_map_opcode_to_priority(enum rrdeng_opcode opcode, STORAGE_PRIORITY priority) { |
| 502 | if(unlikely(priority >= STORAGE_PRIORITY_INTERNAL_MAX_DONT_USE)) |
| 503 | priority = STORAGE_PRIORITY_BEST_EFFORT; |
| 504 | |
| 505 | switch(opcode) { |
| 506 | case RRDENG_OPCODE_QUERY: |
| 507 | priority = STORAGE_PRIORITY_INTERNAL_QUERY_PREP; |
| 508 | break; |
| 509 | |
| 510 | default: |
| 511 | break; |
| 512 | } |
| 513 | |
| 514 | return priority; |
| 515 | } |
| 516 | |
| 517 | ALWAYS_INLINE void rrdeng_enqueue_epdl_cmd(struct rrdeng_cmd *cmd) { |
| 518 | epdl_cmd_queued(cmd->data, cmd); |
| 519 | } |
| 520 | |
| 521 | ALWAYS_INLINE void rrdeng_dequeue_epdl_cmd(struct rrdeng_cmd *cmd) { |
| 522 | epdl_cmd_dequeued(cmd->data); |
| 523 | } |
| 524 | |
| 525 | ALWAYS_INLINE void rrdeng_req_cmd(requeue_callback_t get_cmd_cb, void *data, STORAGE_PRIORITY priority) { |
| 526 | spinlock_lock(&rrdeng_main.cmd_queue.unsafe.spinlock); |
| 527 | |
| 528 | struct rrdeng_cmd *cmd = get_cmd_cb(data); |
| 529 | if(cmd) { |
| 530 | priority = rrdeng_enq_cmd_map_opcode_to_priority(cmd->opcode, priority); |
| 531 | |
| 532 | if (cmd->priority > priority) { |
| 533 | DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(rrdeng_main.cmd_queue.unsafe.waiting_items_by_priority[cmd->priority], cmd, queue.prev, queue.next); |
| 534 | DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(rrdeng_main.cmd_queue.unsafe.waiting_items_by_priority[priority], cmd, queue.prev, queue.next); |
| 535 | cmd->priority = priority; |
| 536 | } |
| 537 | } |
| 538 | |
| 539 | spinlock_unlock(&rrdeng_main.cmd_queue.unsafe.spinlock); |
| 540 | } |
| 541 | |
| 542 | ALWAYS_INLINE void rrdeng_enq_cmd(struct rrdengine_instance *ctx, enum rrdeng_opcode opcode, void *data, struct completion *completion, |
| 543 | enum storage_priority priority, enqueue_callback_t enqueue_cb, dequeue_callback_t dequeue_cb) { |
| 544 | |
| 545 | priority = rrdeng_enq_cmd_map_opcode_to_priority(opcode, priority); |
| 546 | |
| 547 | struct rrdeng_cmd *cmd = aral_mallocz(rrdeng_main.cmd_queue.ar); |
| 548 | memset(cmd, 0, sizeof(struct rrdeng_cmd)); |
| 549 | cmd->ctx = ctx; |
| 550 | cmd->opcode = opcode; |
| 551 | cmd->data = data; |
| 552 | cmd->completion = completion; |
| 553 | cmd->priority = priority; |
| 554 | cmd->dequeue_cb = dequeue_cb; |
| 555 | |
| 556 | spinlock_lock(&rrdeng_main.cmd_queue.unsafe.spinlock); |
| 557 | DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(rrdeng_main.cmd_queue.unsafe.waiting_items_by_priority[priority], cmd, queue.prev, queue.next); |
| 558 | rrdeng_main.cmd_queue.unsafe.waiting++; |
| 559 | if(enqueue_cb) |
| 560 | enqueue_cb(cmd); |
| 561 | spinlock_unlock(&rrdeng_main.cmd_queue.unsafe.spinlock); |
| 562 | |
| 563 | rrdeng_async_wakeup(); |
| 564 | } |
| 565 | |
| 566 | static inline bool rrdeng_cmd_has_waiting_opcodes_in_lower_priorities(STORAGE_PRIORITY priority, STORAGE_PRIORITY max_priority) { |
| 567 | for(; priority <= max_priority ; priority++) |
| 568 | if(rrdeng_main.cmd_queue.unsafe.waiting_items_by_priority[priority]) |
| 569 | return true; |
| 570 | |
| 571 | return false; |
| 572 | } |
| 573 | |
| 574 | #define opcode_empty (struct rrdeng_cmd) { \ |
| 575 | .ctx = NULL, \ |
| 576 | .opcode = RRDENG_OPCODE_NOOP, \ |
| 577 | .priority = STORAGE_PRIORITY_BEST_EFFORT, \ |
| 578 | .completion = NULL, \ |
| 579 | .data = NULL, \ |
| 580 | } |
| 581 | |
| 582 | static inline struct rrdeng_cmd rrdeng_deq_cmd(bool from_worker) { |
| 583 | struct rrdeng_cmd *cmd = NULL; |
| 584 | enum LIBUV_WORKERS_STATUS status = work_request_full(); |
| 585 | STORAGE_PRIORITY min_priority, max_priority; |
| 586 | |
| 587 | if(unlikely(from_worker)) { |
| 588 | if(status == LIBUV_WORKERS_CRITICAL) |
| 589 | return opcode_empty; |
| 590 | |
| 591 | min_priority = STORAGE_PRIORITY_INTERNAL_QUERY_PREP; |
| 592 | max_priority = STORAGE_PRIORITY_BEST_EFFORT; |
| 593 | } |
| 594 | else { |
| 595 | min_priority = STORAGE_PRIORITY_INTERNAL_DBENGINE; |
| 596 | max_priority = (status != LIBUV_WORKERS_RELAXED) ? STORAGE_PRIORITY_INTERNAL_DBENGINE : STORAGE_PRIORITY_INTERNAL_MAX_DONT_USE - 1; |
| 597 | } |
| 598 | |
| 599 | // find an opcode to execute from the queue |
| 600 | spinlock_lock(&rrdeng_main.cmd_queue.unsafe.spinlock); |
| 601 | for(STORAGE_PRIORITY priority = min_priority; priority <= max_priority ; priority++) { |
| 602 | cmd = rrdeng_main.cmd_queue.unsafe.waiting_items_by_priority[priority]; |
| 603 | if(cmd) { |
| 604 | |
| 605 | // avoid starvation of lower priorities |
| 606 | if(unlikely(priority >= STORAGE_PRIORITY_HIGH && |
| 607 | priority < STORAGE_PRIORITY_BEST_EFFORT && |
| 608 | ++rrdeng_main.cmd_queue.unsafe.executed_by_priority[priority] % 50 == 0 && |
| 609 | rrdeng_cmd_has_waiting_opcodes_in_lower_priorities(priority + 1, max_priority))) { |
| 610 | // let the others run 2% of the requests |
| 611 | cmd = NULL; |
| 612 | continue; |
| 613 | } |
| 614 | |
| 615 | // remove it from the queue |
| 616 | DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(rrdeng_main.cmd_queue.unsafe.waiting_items_by_priority[priority], cmd, queue.prev, queue.next); |
| 617 | rrdeng_main.cmd_queue.unsafe.waiting--; |
| 618 | break; |
| 619 | } |
| 620 | } |
| 621 | |
| 622 | if(cmd && cmd->dequeue_cb) { |
| 623 | cmd->dequeue_cb(cmd); |
| 624 | cmd->dequeue_cb = NULL; |
| 625 | } |
| 626 | |
| 627 | spinlock_unlock(&rrdeng_main.cmd_queue.unsafe.spinlock); |
| 628 | |
| 629 | struct rrdeng_cmd ret; |
| 630 | if(cmd) { |
| 631 | // copy it, to return it |
| 632 | ret = *cmd; |
| 633 | |
| 634 | aral_freez(rrdeng_main.cmd_queue.ar, cmd); |
| 635 | } |
| 636 | else |
| 637 | ret = opcode_empty; |
| 638 | |
| 639 | return ret; |
| 640 | } |
| 641 | |
| 642 | |
| 643 | // ---------------------------------------------------------------------------- |
| 644 | |
| 645 | static void journalfile_extent_build(struct rrdengine_instance *ctx, struct extent_io_descriptor *xt_io_descr) { |
| 646 | unsigned count, payload_length, descr_size, size_bytes; |
| 647 | void *buf; |
| 648 | /* persistent structures */ |
| 649 | struct rrdeng_df_extent_header *df_header; |
| 650 | struct rrdeng_jf_transaction_header *jf_header; |
| 651 | struct rrdeng_jf_store_data *jf_metric_data; |
| 652 | struct rrdeng_jf_transaction_trailer *jf_trailer; |
| 653 | uLong crc; |
| 654 | |
| 655 | df_header = xt_io_descr->buf; |
| 656 | count = df_header->number_of_pages; |
| 657 | fatal_assert(count <= MAX_PAGES_PER_EXTENT); |
| 658 | descr_size = sizeof(*jf_metric_data->descr) * count; |
| 659 | payload_length = sizeof(*jf_metric_data) + descr_size; |
| 660 | size_bytes = sizeof(*jf_header) + payload_length + sizeof(*jf_trailer); |
| 661 | |
| 662 | xt_io_descr->wal = wal_get(ctx, size_bytes); |
| 663 | buf = xt_io_descr->wal->buf; |
| 664 | |
| 665 | jf_header = buf; |
| 666 | jf_header->type = STORE_DATA; |
| 667 | jf_header->reserved = 0; |
| 668 | jf_header->id = xt_io_descr->wal->transaction_id; |
| 669 | jf_header->payload_length = payload_length; |
| 670 | |
| 671 | jf_metric_data = buf + sizeof(*jf_header); |
| 672 | jf_metric_data->extent_offset = xt_io_descr->pos; |
| 673 | jf_metric_data->extent_size = xt_io_descr->bytes; |
| 674 | jf_metric_data->number_of_pages = count; |
| 675 | memcpy(jf_metric_data->descr, df_header->descr, descr_size); |
| 676 | |
| 677 | jf_trailer = buf + sizeof(*jf_header) + payload_length; |
| 678 | crc = crc32(0L, Z_NULL, 0); |
| 679 | crc = crc32(crc, buf, sizeof(*jf_header) + payload_length); |
| 680 | crc32set(jf_trailer->checksum, crc); |
| 681 | } |
| 682 | |
| 683 | static void |
| 684 | extent_flush_to_open(struct rrdengine_instance *ctx, struct extent_io_descriptor *xt_io_descr, bool have_error) |
| 685 | { |
| 686 | worker_is_busy(UV_EVENT_DBENGINE_FLUSHED_TO_OPEN); |
| 687 | |
| 688 | struct page_descr_with_data *descr; |
| 689 | struct rrdengine_datafile *datafile; |
| 690 | unsigned i; |
| 691 | |
| 692 | datafile = xt_io_descr->datafile; |
| 693 | |
| 694 | bool still_running = ctx_is_available_for_queries(ctx); |
| 695 | if (likely(still_running && !have_error)) |
| 696 | internal_fatal(!rrdeng_valid_extent_disk_size(xt_io_descr->bytes), |
| 697 | "DBENGINE: flushed extent has invalid size %u", |
| 698 | xt_io_descr->bytes); |
| 699 | |
| 700 | usec_t max_end_time_ut = 0; |
| 701 | for (i = 0 ; i < xt_io_descr->descr_count ; ++i) { |
| 702 | descr = xt_io_descr->descr_array[i]; |
| 703 | |
| 704 | if (descr->end_time_ut > max_end_time_ut) |
| 705 | max_end_time_ut = descr->end_time_ut; |
| 706 | |
| 707 | if (likely(still_running && !have_error)) { |
| 708 | pgc_open_add_hot_page( |
| 709 | (Word_t)ctx, |
| 710 | descr->metric_id, |
| 711 | (time_t)(descr->start_time_ut / USEC_PER_SEC), |
| 712 | (time_t)(descr->end_time_ut / USEC_PER_SEC), |
| 713 | descr->update_every_s, |
| 714 | datafile, |
| 715 | xt_io_descr->pos, |
| 716 | xt_io_descr->bytes); |
| 717 | } |
| 718 | |
| 719 | page_descriptor_release(descr); |
| 720 | } |
| 721 | |
| 722 | if (!have_error) { |
| 723 | if (max_end_time_ut > 0) { |
| 724 | time_t new_last_time_s = (time_t)(max_end_time_ut / USEC_PER_SEC); |
| 725 | |
| 726 | // Atomically update to keep the maximum |
| 727 | spinlock_lock(&datafile->journalfile->data_spinlock); |
| 728 | if (new_last_time_s > datafile->journalfile->v2.last_time_s) |
| 729 | datafile->journalfile->v2.last_time_s = new_last_time_s; |
| 730 | spinlock_unlock(&datafile->journalfile->data_spinlock); |
| 731 | } |
| 732 | } |
| 733 | |
| 734 | posix_memalign_freez(xt_io_descr->buf); |
| 735 | extent_io_descriptor_release(xt_io_descr); |
| 736 | |
| 737 | spinlock_lock(&datafile->writers.spinlock); |
| 738 | datafile->writers.flushed_to_open_running--; |
| 739 | spinlock_unlock(&datafile->writers.spinlock); |
| 740 | |
| 741 | if(datafile->fileno != ctx_last_fileno_get(ctx) && still_running) |
| 742 | __atomic_store_n(&ctx->atomic.needs_indexing, true, __ATOMIC_RELAXED); |
| 743 | |
| 744 | worker_is_idle(); |
| 745 | } |
| 746 | |
| 747 | // Main event loop callback |
| 748 | |
| 749 | static bool datafile_is_full(struct rrdengine_instance *ctx, struct rrdengine_datafile *datafile, uint64_t extent_size) { |
| 750 | bool ret = false; |
| 751 | |
| 752 | spinlock_lock(&datafile->writers.spinlock); |
| 753 | |
| 754 | #ifdef OS_WINDOWS |
| 755 | time_t now = now_realtime_sec(); |
| 756 | if (now - datafile->writers.last_sync_time > 60) { |
| 757 | sync_uv_file_data(datafile->file); |
| 758 | sync_uv_file_data(datafile->journalfile->file); |
| 759 | datafile->writers.last_sync_time = now_realtime_sec(); |
| 760 | } |
| 761 | #endif |
| 762 | |
| 763 | // Check if adding this extent would exceed the target size |
| 764 | if(datafile->pos + extent_size > rrdeng_target_data_file_size(ctx)) |
| 765 | ret = true; |
| 766 | |
| 767 | spinlock_unlock(&datafile->writers.spinlock); |
| 768 | |
| 769 | return ret; |
| 770 | } |
| 771 | |
| 772 | size_t datafile_count(struct rrdengine_instance *ctx, bool with_lock) |
| 773 | { |
| 774 | size_t count = 0; |
| 775 | |
| 776 | if (!with_lock) |
| 777 | netdata_rwlock_rdlock(&ctx->datafiles.rwlock); |
| 778 | |
| 779 | count = JudyLCount(ctx->datafiles.JudyL, 0, -1, PJE0); |
| 780 | |
| 781 | if (!with_lock) |
| 782 | netdata_rwlock_rdunlock(&ctx->datafiles.rwlock); |
| 783 | |
| 784 | return count; |
| 785 | } |
| 786 | |
| 787 | struct rrdengine_datafile * |
| 788 | get_next_datafile(struct rrdengine_datafile *this_datafile, struct rrdengine_instance *ctx, bool with_lock) |
| 789 | { |
| 790 | struct rrdengine_datafile *datafile = NULL; |
| 791 | |
| 792 | ctx = this_datafile ? this_datafile->ctx : ctx; |
| 793 | if (!ctx) |
| 794 | return NULL; |
| 795 | |
| 796 | if (!with_lock) |
| 797 | netdata_rwlock_rdlock(&ctx->datafiles.rwlock); |
| 798 | |
| 799 | Word_t Index = this_datafile ? this_datafile->fileno : 0; |
| 800 | Pvoid_t *Pvalue; |
| 801 | |
| 802 | Pvalue = JudyLNext(ctx->datafiles.JudyL, &Index, PJE0); |
| 803 | |
| 804 | if (Pvalue) |
| 805 | datafile = *Pvalue; |
| 806 | |
| 807 | if (!with_lock) |
| 808 | netdata_rwlock_rdunlock(&ctx->datafiles.rwlock); |
| 809 | |
| 810 | return datafile; |
| 811 | } |
| 812 | |
| 813 | static struct rrdengine_datafile *get_ctx_datafile_first_or_last(struct rrdengine_instance *ctx, bool first, bool with_lock) |
| 814 | { |
| 815 | struct rrdengine_datafile *datafile = NULL; |
| 816 | |
| 817 | if (!with_lock) |
| 818 | netdata_rwlock_rdlock(&ctx->datafiles.rwlock); |
| 819 | |
| 820 | Word_t Index = 0; |
| 821 | Pvoid_t *Pvalue; |
| 822 | |
| 823 | if (first) |
| 824 | Pvalue = JudyLFirst(ctx->datafiles.JudyL, &Index, PJE0); |
| 825 | else { |
| 826 | Index = -1; |
| 827 | Pvalue = JudyLLast(ctx->datafiles.JudyL, &Index, PJE0); |
| 828 | } |
| 829 | |
| 830 | if (Pvalue) |
| 831 | datafile = *Pvalue; |
| 832 | |
| 833 | if (!with_lock) |
| 834 | netdata_rwlock_rdunlock(&ctx->datafiles.rwlock); |
| 835 | |
| 836 | return datafile; |
| 837 | } |
| 838 | |
| 839 | struct rrdengine_datafile *get_first_ctx_datafile(struct rrdengine_instance *ctx, bool with_lock) { |
| 840 | return get_ctx_datafile_first_or_last(ctx, true, with_lock); |
| 841 | } |
| 842 | |
| 843 | struct rrdengine_datafile *get_last_ctx_datafile(struct rrdengine_instance *ctx, bool with_lock) { |
| 844 | return get_ctx_datafile_first_or_last(ctx, false, with_lock); |
| 845 | } |
| 846 | |
| 847 | static netdata_mutex_t mutex; |
| 848 | |
| 849 | static void __attribute__((constructor)) init_mutex(void) { |
| 850 | netdata_mutex_init(&mutex); |
| 851 | } |
| 852 | |
| 853 | static void __attribute__((destructor)) destroy_mutex(void) { |
| 854 | netdata_mutex_destroy(&mutex); |
| 855 | } |
| 856 | |
| 857 | static struct rrdengine_datafile *get_datafile_to_write_extent(struct rrdengine_instance *ctx, uint64_t extent_size) { |
| 858 | struct rrdengine_datafile *datafile; |
| 859 | |
| 860 | // Acquire the mutex at the beginning to make the entire check-and-act atomic |
| 861 | // This prevents the race condition where multiple threads pass the "is full" check |
| 862 | // before any of them increments the position, causing files to grow beyond limits |
| 863 | netdata_mutex_lock(&mutex); |
| 864 | |
| 865 | // get the latest datafile |
| 866 | netdata_rwlock_rdlock(&ctx->datafiles.rwlock); |
| 867 | |
| 868 | datafile = get_last_ctx_datafile(ctx, true); |
| 869 | // become a writer on this datafile, to prevent it from vanishing |
| 870 | spinlock_lock(&datafile->writers.spinlock); |
| 871 | datafile->writers.running++; |
| 872 | spinlock_unlock(&datafile->writers.spinlock); |
| 873 | netdata_rwlock_rdunlock(&ctx->datafiles.rwlock); |
| 874 | |
| 875 | if(datafile_is_full(ctx, datafile, extent_size)) { |
| 876 | // remember the datafile we have become writers to |
| 877 | struct rrdengine_datafile *old_datafile = datafile; |
| 878 | |
| 879 | // Create a new datafile - since we hold the mutex, no other thread can interfere |
| 880 | if(create_new_datafile_pair(ctx) == 0) |
| 881 | __atomic_store_n(&ctx->atomic.needs_indexing, true, __ATOMIC_RELAXED); |
| 882 | |
| 883 | // get the new datafile |
| 884 | netdata_rwlock_rdlock(&ctx->datafiles.rwlock); |
| 885 | datafile = get_last_ctx_datafile(ctx, true); |
| 886 | // become a writer on this datafile, to prevent it from vanishing |
| 887 | spinlock_lock(&datafile->writers.spinlock); |
| 888 | datafile->writers.running++; |
| 889 | spinlock_unlock(&datafile->writers.spinlock); |
| 890 | netdata_rwlock_rdunlock(&ctx->datafiles.rwlock); |
| 891 | |
| 892 | // release the writers on the old datafile |
| 893 | spinlock_lock(&old_datafile->writers.spinlock); |
| 894 | old_datafile->writers.running--; |
| 895 | #ifdef OS_WINDOWS |
| 896 | sync_uv_file_data(old_datafile->file); |
| 897 | sync_uv_file_data(old_datafile->journalfile->file); |
| 898 | datafile->writers.last_sync_time = now_realtime_sec(); |
| 899 | old_datafile->writers.last_sync_time = now_realtime_sec(); |
| 900 | #endif |
| 901 | spinlock_unlock(&old_datafile->writers.spinlock); |
| 902 | } |
| 903 | |
| 904 | netdata_mutex_unlock(&mutex); |
| 905 | |
| 906 | return datafile; |
| 907 | } |
| 908 | |
| 909 | /* |
| 910 | * Take a page list in a judy array and write them |
| 911 | */ |
| 912 | static struct extent_io_descriptor * |
| 913 | datafile_extent_build(struct rrdengine_instance *ctx, struct page_descr_with_data *base, uv_buf_t *iov) |
| 914 | { |
| 915 | unsigned i; |
| 916 | uint32_t real_io_size, size_bytes, count, pos; |
| 917 | uint32_t uncompressed_payload_length, max_compressed_size, payload_offset; |
| 918 | struct page_descr_with_data *descr, *eligible_pages[MAX_PAGES_PER_EXTENT]; |
| 919 | struct extent_io_descriptor *xt_io_descr; |
| 920 | Word_t Index; |
| 921 | uint8_t compression_algorithm = ctx->config.global_compress_alg; |
| 922 | struct rrdengine_datafile *datafile; |
| 923 | /* persistent structures */ |
| 924 | struct rrdeng_df_extent_header *header; |
| 925 | struct rrdeng_df_extent_trailer *trailer; |
| 926 | uLong crc; |
| 927 | |
| 928 | for(descr = base, Index = 0, count = 0, uncompressed_payload_length = 0; |
| 929 | descr && count != rrdeng_pages_per_extent; |
| 930 | descr = descr->link.next, Index++) { |
| 931 | |
| 932 | uncompressed_payload_length += descr->page_length; |
| 933 | eligible_pages[count++] = descr; |
| 934 | |
| 935 | } |
| 936 | |
| 937 | if (!count) { |
| 938 | __atomic_sub_fetch(&ctx->atomic.extents_currently_being_flushed, 1, __ATOMIC_RELAXED); |
| 939 | return NULL; |
| 940 | } |
| 941 | |
| 942 | xt_io_descr = extent_io_descriptor_get(); |
| 943 | payload_offset = sizeof(*header) + count * sizeof(header->descr[0]); |
| 944 | max_compressed_size = dbengine_max_compressed_size(uncompressed_payload_length, compression_algorithm); |
| 945 | size_bytes = payload_offset + MAX(uncompressed_payload_length, max_compressed_size) + sizeof(*trailer); |
| 946 | (void)posix_memalignz((void *)&xt_io_descr->buf, RRDFILE_ALIGNMENT, ALIGN_BYTES_CEILING(size_bytes)); |
| 947 | memset(xt_io_descr->buf, 0, ALIGN_BYTES_CEILING(size_bytes)); |
| 948 | (void) memcpy(xt_io_descr->descr_array, eligible_pages, sizeof(struct page_descr_with_data *) * count); |
| 949 | xt_io_descr->descr_count = count; |
| 950 | |
| 951 | pos = 0; |
| 952 | header = xt_io_descr->buf; |
| 953 | header->number_of_pages = count; |
| 954 | pos += sizeof(*header); |
| 955 | |
| 956 | for (i = 0 ; i < count ; ++i) { |
| 957 | descr = xt_io_descr->descr_array[i]; |
| 958 | header->descr[i].type = descr->type; |
| 959 | uuid_copy(*(nd_uuid_t *)header->descr[i].uuid, *uuidmap_uuid_ptr(descr->uuid_id)); |
| 960 | header->descr[i].page_length = descr->page_length; |
| 961 | header->descr[i].start_time_ut = descr->start_time_ut; |
| 962 | |
| 963 | switch (descr->type) { |
| 964 | case RRDENG_PAGE_TYPE_ARRAY_32BIT: |
| 965 | case RRDENG_PAGE_TYPE_ARRAY_TIER1: |
| 966 | header->descr[i].end_time_ut = descr->end_time_ut; |
| 967 | break; |
| 968 | case RRDENG_PAGE_TYPE_GORILLA_32BIT: |
| 969 | header->descr[i].gorilla.delta_time_s = (uint32_t) ((descr->end_time_ut - descr->start_time_ut) / USEC_PER_SEC); |
| 970 | header->descr[i].gorilla.entries = pgd_slots_used(descr->pgd); |
| 971 | break; |
| 972 | default: |
| 973 | fatal("Unknown page type: %uc", descr->type); |
| 974 | } |
| 975 | |
| 976 | pos += sizeof(header->descr[i]); |
| 977 | } |
| 978 | |
| 979 | // build the extent payload |
| 980 | for (i = 0 ; i < count ; ++i) { |
| 981 | descr = xt_io_descr->descr_array[i]; |
| 982 | pgd_copy_to_extent(descr->pgd, xt_io_descr->buf + pos, descr->page_length); |
| 983 | pos += descr->page_length; |
| 984 | } |
| 985 | |
| 986 | // compress the payload |
| 987 | size_t compressed_size = |
| 988 | (int)dbengine_compress(xt_io_descr->buf + payload_offset, |
| 989 | uncompressed_payload_length, |
| 990 | compression_algorithm); |
| 991 | |
| 992 | internal_fatal(compressed_size > max_compressed_size, "DBENGINE: compression returned more data than the max allowed"); |
| 993 | internal_fatal(compressed_size > uncompressed_payload_length, "DBENGINE: compression returned more data than the uncompressed extent"); |
| 994 | |
| 995 | if(compressed_size) { |
| 996 | header->compression_algorithm = compression_algorithm; |
| 997 | header->payload_length = compressed_size; |
| 998 | } |
| 999 | else { |
| 1000 | // compression failed, or generated bigger pages |
| 1001 | // so it didn't touch our uncompressed buffer |
| 1002 | header->compression_algorithm = RRDENG_COMPRESSION_NONE; |
| 1003 | header->payload_length = compressed_size = uncompressed_payload_length; |
| 1004 | } |
| 1005 | |
| 1006 | // set the correct size |
| 1007 | size_bytes = payload_offset + compressed_size + sizeof(*trailer); |
| 1008 | |
| 1009 | if(compression_algorithm != RRDENG_COMPRESSION_NONE) { |
| 1010 | __atomic_add_fetch(&ctx->stats.before_compress_bytes, uncompressed_payload_length, __ATOMIC_RELAXED); |
| 1011 | __atomic_add_fetch(&ctx->stats.after_compress_bytes, compressed_size, __ATOMIC_RELAXED); |
| 1012 | } |
| 1013 | |
| 1014 | real_io_size = ALIGN_BYTES_CEILING(size_bytes); |
| 1015 | |
| 1016 | // Pass the extent size so the check can determine if this extent will fit |
| 1017 | datafile = get_datafile_to_write_extent(ctx, real_io_size); |
| 1018 | spinlock_lock(&datafile->writers.spinlock); |
| 1019 | xt_io_descr->datafile = datafile; |
| 1020 | xt_io_descr->pos = datafile->pos; |
| 1021 | datafile->pos += real_io_size; |
| 1022 | spinlock_unlock(&datafile->writers.spinlock); |
| 1023 | |
| 1024 | xt_io_descr->bytes = size_bytes; |
| 1025 | |
| 1026 | trailer = xt_io_descr->buf + size_bytes - sizeof(*trailer); |
| 1027 | crc = crc32(0L, Z_NULL, 0); |
| 1028 | crc = crc32(crc, xt_io_descr->buf, size_bytes - sizeof(*trailer)); |
| 1029 | crc32set(trailer->checksum, crc); |
| 1030 | |
| 1031 | *iov = uv_buf_init((void *)xt_io_descr->buf, real_io_size); |
| 1032 | journalfile_extent_build(ctx, xt_io_descr); |
| 1033 | |
| 1034 | ctx_last_flush_fileno_set(ctx, datafile->fileno); |
| 1035 | xt_io_descr->real_io_size = real_io_size; |
| 1036 | |
| 1037 | return xt_io_descr; |
| 1038 | } |
| 1039 | |
| 1040 | |
| 1041 | static void after_weights_worker(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* uv_work_req __maybe_unused, int status __maybe_unused) |
| 1042 | { |
| 1043 | ; |
| 1044 | } |
| 1045 | |
| 1046 | static void *weights_worker( |
| 1047 | struct rrdengine_instance *ctx __maybe_unused, |
| 1048 | void *data, |
| 1049 | struct completion *completion, |
| 1050 | uv_work_t *req __maybe_unused) |
| 1051 | { |
| 1052 | worker_is_busy(UV_EVENT_WEIGHTS_CALCULATION); |
| 1053 | query_weights_worker_thread(data); |
| 1054 | completion_mark_complete(completion); |
| 1055 | worker_is_idle(); |
| 1056 | return NULL; |
| 1057 | } |
| 1058 | |
| 1059 | static void after_extent_write(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* uv_work_req __maybe_unused, int status __maybe_unused) |
| 1060 | { |
| 1061 | check_and_schedule_db_rotation(ctx); |
| 1062 | } |
| 1063 | |
| 1064 | static void *extent_write_tp_worker( |
| 1065 | struct rrdengine_instance *ctx, |
| 1066 | void *data, |
| 1067 | struct completion *completion __maybe_unused, |
| 1068 | uv_work_t *req __maybe_unused) |
| 1069 | { |
| 1070 | worker_is_busy(UV_EVENT_DBENGINE_EXTENT_WRITE); |
| 1071 | uv_buf_t iov; |
| 1072 | struct page_descr_with_data *base = data; |
| 1073 | struct extent_io_descriptor *xt_io_descr = datafile_extent_build(ctx, base, &iov); |
| 1074 | |
| 1075 | if (!xt_io_descr) |
| 1076 | goto done; |
| 1077 | |
| 1078 | struct rrdengine_datafile *datafile = xt_io_descr->datafile; |
| 1079 | uv_fs_t request; |
| 1080 | |
| 1081 | int retries = 10; |
| 1082 | int ret = -1; |
| 1083 | while (ret < 0 && --retries) { |
| 1084 | ret = uv_fs_write(NULL, &request, datafile->file, &iov, 1, (int64_t)xt_io_descr->pos, NULL); |
| 1085 | uv_fs_req_cleanup(&request); |
| 1086 | if (ret < 0) { |
| 1087 | if (ret == -ENOSPC || ret == -EBADF || ret == -EACCES || ret == -EROFS || ret == -EINVAL) |
| 1088 | break; |
| 1089 | sleep_usec(300 * USEC_PER_MS); |
| 1090 | } |
| 1091 | } |
| 1092 | |
| 1093 | if (unlikely(ret < 0)) |
| 1094 | ctx_io_error(ctx); |
| 1095 | else { |
| 1096 | ctx_current_disk_space_increase(ctx, xt_io_descr->real_io_size); |
| 1097 | ctx_io_write_op_bytes(ctx, xt_io_descr->real_io_size); |
| 1098 | ret = journalfile_v1_extent_write(ctx, datafile, xt_io_descr->wal); |
| 1099 | } |
| 1100 | |
| 1101 | if (ret < 0) { |
| 1102 | nd_log_limit_static_global_var(dbengine_erl, 10, 0); |
| 1103 | nd_log_limit(&dbengine_erl, NDLS_DAEMON, NDLP_ERR, "DBENGINE: Tier %d, %s", ctx->config.tier, uv_strerror(ret)); |
| 1104 | } |
| 1105 | |
| 1106 | spinlock_lock(&datafile->writers.spinlock); |
| 1107 | datafile->writers.running--; |
| 1108 | datafile->writers.flushed_to_open_running++; |
| 1109 | spinlock_unlock(&datafile->writers.spinlock); |
| 1110 | |
| 1111 | extent_flush_to_open(ctx, xt_io_descr, ret < 0); |
| 1112 | |
| 1113 | done: |
| 1114 | __atomic_sub_fetch(&ctx->atomic.extents_currently_being_flushed, 1, __ATOMIC_RELAXED); |
| 1115 | completion_mark_complete(completion); |
| 1116 | worker_is_idle(); |
| 1117 | return NULL; |
| 1118 | } |
| 1119 | |
| 1120 | static void after_database_rotate(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* req __maybe_unused, int status __maybe_unused) { |
| 1121 | __atomic_store_n(&ctx->atomic.now_deleting_files, false, __ATOMIC_RELAXED); |
| 1122 | |
| 1123 | check_and_schedule_db_rotation(ctx); |
| 1124 | } |
| 1125 | |
| 1126 | struct uuid_first_time_s { |
| 1127 | nd_uuid_t *uuid; |
| 1128 | time_t first_time_s; |
| 1129 | METRIC *metric; |
| 1130 | size_t pages_found; |
| 1131 | size_t df_matched; |
| 1132 | size_t df_index_oldest; |
| 1133 | }; |
| 1134 | |
| 1135 | struct rrdengine_datafile *datafile_release_and_acquire_next_for_retention(struct rrdengine_instance *ctx, struct rrdengine_datafile *datafile) { |
| 1136 | |
| 1137 | netdata_rwlock_rdlock(&ctx->datafiles.rwlock); |
| 1138 | |
| 1139 | struct rrdengine_datafile *next_datafile = get_next_datafile(datafile, NULL, true); |
| 1140 | |
| 1141 | while(next_datafile && !datafile_acquire(next_datafile, DATAFILE_ACQUIRE_RETENTION)) |
| 1142 | next_datafile = get_next_datafile(next_datafile, NULL, true); |
| 1143 | |
| 1144 | netdata_rwlock_rdunlock(&ctx->datafiles.rwlock); |
| 1145 | |
| 1146 | datafile_release(datafile, DATAFILE_ACQUIRE_RETENTION); |
| 1147 | |
| 1148 | return next_datafile; |
| 1149 | } |
| 1150 | |
| 1151 | static time_t find_uuid_first_time( |
| 1152 | struct rrdengine_instance *ctx, |
| 1153 | struct rrdengine_datafile *datafile, |
| 1154 | struct uuid_first_time_s *uuid_first_entry_list, |
| 1155 | size_t count) |
| 1156 | { |
| 1157 | time_t global_first_time_s = LONG_MAX; |
| 1158 | |
| 1159 | // acquire the datafile to work with it |
| 1160 | netdata_rwlock_rdlock(&ctx->datafiles.rwlock); |
| 1161 | while(datafile && !datafile_acquire(datafile, DATAFILE_ACQUIRE_RETENTION)) |
| 1162 | datafile = get_next_datafile(datafile, NULL, true); |
| 1163 | |
| 1164 | netdata_rwlock_rdunlock(&ctx->datafiles.rwlock); |
| 1165 | |
| 1166 | if (unlikely(!datafile)) |
| 1167 | return global_first_time_s; |
| 1168 | |
| 1169 | unsigned journalfile_count = 0; |
| 1170 | size_t binary_match = 0; |
| 1171 | size_t not_matching_bsearches = 0; |
| 1172 | |
| 1173 | bool agent_shutdown = false; |
| 1174 | while (datafile) { |
| 1175 | size_t journal_v2_file_size = 0; |
| 1176 | struct journal_v2_header *j2_header = journalfile_v2_data_acquire_with_hint( |
| 1177 | datafile->journalfile, &journal_v2_file_size, 0, 0, JOURNALFILE_V2_ACCESS_RANDOM); |
| 1178 | if (!j2_header) { |
| 1179 | datafile = datafile_release_and_acquire_next_for_retention(ctx, datafile); |
| 1180 | continue; |
| 1181 | } |
| 1182 | |
| 1183 | bool any_matching = false; |
| 1184 | bool journal_access_failed = false; |
| 1185 | |
| 1186 | char file_path[RRDENG_PATH_MAX]; |
| 1187 | journalfile_v2_generate_path(datafile, file_path, sizeof(file_path)); |
| 1188 | PROTECTED_ACCESS_SETUP(datafile->journalfile->mmap.data, datafile->journalfile->mmap.size, file_path, "read"); |
| 1189 | if (no_signal_received) { |
| 1190 | time_t journal_start_time_s = (time_t)(j2_header->start_time_ut / USEC_PER_SEC); |
| 1191 | |
| 1192 | if (journal_start_time_s < global_first_time_s) |
| 1193 | global_first_time_s = journal_start_time_s; |
| 1194 | |
| 1195 | size_t metric_offset = j2_header->metric_offset; |
| 1196 | size_t journal_metric_count = j2_header->metric_count; |
| 1197 | size_t metric_list_size; |
| 1198 | if (__builtin_mul_overflow(journal_metric_count, sizeof(struct journal_metric_list), &metric_list_size)) { |
| 1199 | nd_log_daemon(NDLP_ERR, |
| 1200 | "DBENGINE: metric list size overflow in journalfile \"%s\" " |
| 1201 | "(metric_count=%zu, entry_size=%zu), skipping it", |
| 1202 | file_path, journal_metric_count, sizeof(struct journal_metric_list)); |
| 1203 | journal_access_failed = true; |
| 1204 | goto release_journal; |
| 1205 | } |
| 1206 | if (metric_offset > journal_v2_file_size || |
| 1207 | metric_list_size > journal_v2_file_size - metric_offset) { |
| 1208 | nd_log_daemon(NDLP_ERR, |
| 1209 | "DBENGINE: metric list exceeds journal file size in journalfile \"%s\" " |
| 1210 | "(metric_offset=%zu, list_size=%zu, file_size=%zu), skipping it", |
| 1211 | file_path, metric_offset, metric_list_size, journal_v2_file_size); |
| 1212 | journal_access_failed = true; |
| 1213 | goto release_journal; |
| 1214 | } |
| 1215 | |
| 1216 | struct journal_metric_list *uuid_list = |
| 1217 | (struct journal_metric_list *)((uint8_t *)j2_header + metric_offset); |
| 1218 | struct uuid_first_time_s *uuid_original_entry; |
| 1219 | |
| 1220 | size_t journal_search_start = 0; // Start of remaining search space |
| 1221 | any_matching = false; |
| 1222 | for (size_t index = 0; index < count; ++index) { |
| 1223 | uuid_original_entry = &uuid_first_entry_list[index]; |
| 1224 | |
| 1225 | if (uuid_original_entry->df_matched > 3 || uuid_original_entry->pages_found > 5) |
| 1226 | continue; |
| 1227 | |
| 1228 | any_matching = true; |
| 1229 | if (journal_search_start >= journal_metric_count) |
| 1230 | break; |
| 1231 | |
| 1232 | struct journal_metric_list *live_entry = &uuid_list[journal_search_start]; |
| 1233 | // Check if we avoid bsearch |
| 1234 | if (journal_metric_uuid_compare(uuid_original_entry->uuid, live_entry->uuid) != 0) { |
| 1235 | live_entry = bsearch( |
| 1236 | uuid_original_entry->uuid, |
| 1237 | uuid_list + journal_search_start, |
| 1238 | journal_metric_count - journal_search_start, |
| 1239 | sizeof(*uuid_list), |
| 1240 | journal_metric_uuid_compare); |
| 1241 | |
| 1242 | if (!live_entry) { |
| 1243 | not_matching_bsearches++; |
| 1244 | continue; |
| 1245 | } |
| 1246 | } |
| 1247 | |
| 1248 | size_t found_index = live_entry - uuid_list; |
| 1249 | journal_search_start = found_index + 1; // Next search starts after this match |
| 1250 | |
| 1251 | if (journal_search_start >= journal_metric_count) |
| 1252 | break; |
| 1253 | |
| 1254 | uuid_original_entry->pages_found += live_entry->entries; |
| 1255 | uuid_original_entry->df_matched++; |
| 1256 | |
| 1257 | time_t old_first_time_s = uuid_original_entry->first_time_s; |
| 1258 | time_t first_time_s = live_entry->delta_start_s + journal_start_time_s; |
| 1259 | uuid_original_entry->first_time_s = MIN(uuid_original_entry->first_time_s, first_time_s); |
| 1260 | |
| 1261 | if (uuid_original_entry->first_time_s != old_first_time_s) |
| 1262 | uuid_original_entry->df_index_oldest = uuid_original_entry->df_matched; |
| 1263 | |
| 1264 | binary_match++; |
| 1265 | |
| 1266 | if (unlikely(!ctx_is_available_for_queries(ctx))) { |
| 1267 | agent_shutdown = true; |
| 1268 | break; |
| 1269 | } |
| 1270 | } |
| 1271 | } else { |
| 1272 | journal_access_failed = true; |
| 1273 | } |
| 1274 | |
| 1275 | release_journal: |
| 1276 | journalfile_v2_data_release(datafile->journalfile); |
| 1277 | |
| 1278 | if (agent_shutdown) { |
| 1279 | datafile_release(datafile, DATAFILE_ACQUIRE_RETENTION); |
| 1280 | break; |
| 1281 | } |
| 1282 | |
| 1283 | journalfile_count++; |
| 1284 | datafile = datafile_release_and_acquire_next_for_retention(ctx, datafile); |
| 1285 | if (journal_access_failed) |
| 1286 | continue; |
| 1287 | |
| 1288 | if (!any_matching) { |
| 1289 | if (datafile) |
| 1290 | datafile_release(datafile, DATAFILE_ACQUIRE_RETENTION); |
| 1291 | break; |
| 1292 | } |
| 1293 | } |
| 1294 | |
| 1295 | if (agent_shutdown) |
| 1296 | return global_first_time_s; |
| 1297 | |
| 1298 | // Let's scan the open cache for almost exact match |
| 1299 | size_t open_cache_count = 0; |
| 1300 | |
| 1301 | size_t df_index[10] = { 0 }; |
| 1302 | size_t without_metric = 0; |
| 1303 | size_t open_cache_gave_first_time_s = 0; |
| 1304 | size_t metric_count = 0; |
| 1305 | size_t without_retention = 0; |
| 1306 | size_t not_needed_bsearches = 0; |
| 1307 | |
| 1308 | for (size_t index = 0; index < count; ++index) { |
| 1309 | struct uuid_first_time_s *uuid_first_t_entry = &uuid_first_entry_list[index]; |
| 1310 | |
| 1311 | metric_count++; |
| 1312 | |
| 1313 | size_t idx = uuid_first_t_entry->df_index_oldest; |
| 1314 | if(idx >= 10) |
| 1315 | idx = 9; |
| 1316 | |
| 1317 | df_index[idx]++; |
| 1318 | |
| 1319 | not_needed_bsearches += uuid_first_t_entry->df_matched - uuid_first_t_entry->df_index_oldest; |
| 1320 | |
| 1321 | if (unlikely(!uuid_first_t_entry->metric)) { |
| 1322 | without_metric++; |
| 1323 | continue; |
| 1324 | } |
| 1325 | |
| 1326 | PGC_PAGE *page = pgc_page_get_and_acquire( |
| 1327 | open_cache, (Word_t)ctx, |
| 1328 | (Word_t)uuid_first_t_entry->metric, 0, |
| 1329 | PGC_SEARCH_FIRST); |
| 1330 | |
| 1331 | if (page) { |
| 1332 | time_t old_first_time_s = uuid_first_t_entry->first_time_s; |
| 1333 | |
| 1334 | time_t first_time_s = pgc_page_start_time_s(page); |
| 1335 | uuid_first_t_entry->first_time_s = MIN(uuid_first_t_entry->first_time_s, first_time_s); |
| 1336 | pgc_page_release(open_cache, page); |
| 1337 | open_cache_count++; |
| 1338 | |
| 1339 | if(uuid_first_t_entry->first_time_s != old_first_time_s) { |
| 1340 | open_cache_gave_first_time_s++; |
| 1341 | } |
| 1342 | } |
| 1343 | else { |
| 1344 | if(!uuid_first_t_entry->df_index_oldest) |
| 1345 | without_retention++; |
| 1346 | } |
| 1347 | } |
| 1348 | internal_error(true, |
| 1349 | "DBENGINE: analyzed the retention of %zu rotated metrics of tier %d, " |
| 1350 | "did %zu jv2 matching binary searches (%zu not matching, %zu overflown) in %u journal files, " |
| 1351 | "%zu metrics with entries in open cache, " |
| 1352 | "metrics first time found per datafile index ([not in jv2]:%zu, [1]:%zu, [2]:%zu, [3]:%zu, [4]:%zu, [5]:%zu, [6]:%zu, [7]:%zu, [8]:%zu, [bigger]: %zu), " |
| 1353 | "open cache found first time %zu, " |
| 1354 | "metrics without any remaining retention %zu, " |
| 1355 | "metrics not in MRG %zu", |
| 1356 | metric_count, |
| 1357 | ctx->config.tier, |
| 1358 | binary_match, |
| 1359 | not_matching_bsearches, |
| 1360 | not_needed_bsearches, |
| 1361 | journalfile_count, |
| 1362 | open_cache_count, |
| 1363 | df_index[0], df_index[1], df_index[2], df_index[3], df_index[4], df_index[5], df_index[6], df_index[7], df_index[8], df_index[9], |
| 1364 | open_cache_gave_first_time_s, |
| 1365 | without_retention, |
| 1366 | without_metric |
| 1367 | ); |
| 1368 | |
| 1369 | return global_first_time_s; |
| 1370 | } |
| 1371 | |
| 1372 | static void update_metrics_first_time_s(struct rrdengine_instance *ctx, struct rrdengine_datafile *datafile_to_delete, struct rrdengine_datafile *first_datafile_remaining, bool worker) { |
| 1373 | time_t global_first_time_s = LONG_MAX; |
| 1374 | |
| 1375 | if(worker) |
| 1376 | worker_is_busy(UV_EVENT_DBENGINE_FIND_ROTATED_METRICS); |
| 1377 | |
| 1378 | struct rrdengine_journalfile *journalfile = datafile_to_delete->journalfile; |
| 1379 | struct journal_v2_header *j2_header = journalfile_v2_data_acquire_with_hint( |
| 1380 | journalfile, NULL, 0, 0, JOURNALFILE_V2_ACCESS_SEQUENTIAL_DIRECTORY); |
| 1381 | |
| 1382 | if (unlikely(!j2_header)) { |
| 1383 | if (worker) |
| 1384 | worker_is_idle(); |
| 1385 | return; |
| 1386 | } |
| 1387 | |
| 1388 | __atomic_add_fetch(&rrdeng_cache_efficiency_stats.metrics_retention_started, 1, __ATOMIC_RELAXED); |
| 1389 | |
| 1390 | char file_path[RRDENG_PATH_MAX]; |
| 1391 | journalfile_v2_generate_path(datafile_to_delete, file_path, sizeof(file_path)); |
| 1392 | |
| 1393 | struct uuid_first_time_s *uuid_first_t_entry; |
| 1394 | // PROTECTED_ACCESS_SETUP below uses sigsetjmp/siglongjmp (see |
| 1395 | // src/daemon/protected-access.h). Per C11 7.13.2.1, non-volatile locals |
| 1396 | // that are modified between setjmp and longjmp have indeterminate values |
| 1397 | // on the recovery path. uuid_first_entry_list / count / added are all |
| 1398 | // mutated inside the protected region and then read afterwards (the |
| 1399 | // unconditional log line and the journal_access_failed cleanup loop), so |
| 1400 | // they must be volatile to keep the recovery path well-defined. |
| 1401 | // journal_access_failed is also marked volatile defensively: although it |
| 1402 | // is only assigned on a path that does not subsequently SIGBUS, future |
| 1403 | // edits could break that invariant, and the bool is read once on cleanup. |
| 1404 | struct uuid_first_time_s * volatile uuid_first_entry_list = NULL; |
| 1405 | volatile size_t count = 0; |
| 1406 | volatile size_t added = 0; |
| 1407 | volatile bool journal_access_failed = false; |
| 1408 | |
| 1409 | // Protect the mmap walk: reading j2_header->metric_offset, metric_count, |
| 1410 | // and the per-metric uuid_list[] entries can SIGBUS if the underlying v2 |
| 1411 | // file has any unreadable page (truncated, sparse hole, transient I/O |
| 1412 | // error). Without a protected region active the process aborts. Same |
| 1413 | // pattern as find_uuid_first_time() added by commit 26b26ac25a (#22310); |
| 1414 | // this caller was missed at the time. |
| 1415 | // Scope the protected frame tightly to the mmap walk only. The |
| 1416 | // PROTECTED_ACCESS_AUTO_CLEANUP() guard inside PROTECTED_ACCESS_SETUP |
| 1417 | // declares a __attribute__((cleanup)) local; when this inner block exits |
| 1418 | // (normally or via the SIGBUS recovery else-branch falling through), the |
| 1419 | // cleanup runs and the protected-access depth drops back to its prior |
| 1420 | // value. Without this scoping, the frame would stay live through the |
| 1421 | // post-walk log, the data_release, the cleanup loop, the nested |
| 1422 | // find_uuid_first_time() (which registers its own frame), and the final |
| 1423 | // cleanup -- masking unrelated faults that might land in the mmap range |
| 1424 | // and inflating nesting depth unnecessarily. |
| 1425 | { |
| 1426 | PROTECTED_ACCESS_SETUP(journalfile->mmap.data, journalfile->mmap.size, file_path, "mrg-retention"); |
| 1427 | if(no_signal_received) { |
| 1428 | size_t journal_v2_file_size = journalfile->mmap.size; |
| 1429 | size_t metric_offset = j2_header->metric_offset; |
| 1430 | count = j2_header->metric_count; |
| 1431 | size_t metric_list_size; |
| 1432 | size_t entry_list_size; |
| 1433 | // Also check count * sizeof(uuid_first_time_s) -- the allocation below |
| 1434 | // sizes the working array by count, and on 32-bit builds count can |
| 1435 | // pass the metric_list_size bound while still overflowing the entry |
| 1436 | // list multiplication (struct uuid_first_time_s is larger than |
| 1437 | // struct journal_metric_list). |
| 1438 | if (__builtin_mul_overflow(count, sizeof(struct journal_metric_list), &metric_list_size) || |
| 1439 | __builtin_mul_overflow(count, sizeof(struct uuid_first_time_s), &entry_list_size) || |
| 1440 | metric_offset > journal_v2_file_size || |
| 1441 | metric_list_size > journal_v2_file_size - metric_offset) { |
| 1442 | nd_log_daemon(NDLP_ERR, |
| 1443 | "DBENGINE: metric list exceeds journal file size in journalfile \"%s\" " |
| 1444 | "(metric_offset=%zu, list_size=%zu, file_size=%zu), skipping retention update", |
| 1445 | file_path, metric_offset, metric_list_size, journal_v2_file_size); |
| 1446 | journal_access_failed = true; |
| 1447 | } |
| 1448 | else { |
| 1449 | struct journal_metric_list *uuid_list = (struct journal_metric_list *)((uint8_t *) j2_header + metric_offset); |
| 1450 | uuid_first_entry_list = callocz(count, sizeof(struct uuid_first_time_s)); |
| 1451 | |
| 1452 | for (size_t index = 0; index < count; ++index) { |
| 1453 | // Copy uuid out of the mmap onto the stack BEFORE calling mrg. |
| 1454 | // If a backing page is unreadable, uuid_copy SIGBUSes here and |
| 1455 | // the protected region recovers cleanly; the mrg call then |
| 1456 | // never executes. If we passed &uuid_list[index].uuid into |
| 1457 | // mrg, a SIGBUS could fire INSIDE mrg while it holds internal |
| 1458 | // locks, and siglongjmp would skip mrg's unlock paths. |
| 1459 | nd_uuid_t local_uuid; |
| 1460 | uuid_copy(local_uuid, uuid_list[index].uuid); |
| 1461 | |
| 1462 | METRIC *metric = mrg_metric_get_and_acquire_by_uuid(main_mrg, &local_uuid, (Word_t)ctx); |
| 1463 | if (!metric) |
| 1464 | continue; |
| 1465 | |
| 1466 | uuid_first_entry_list[added].metric = metric; |
| 1467 | uuid_first_entry_list[added].first_time_s = LONG_MAX; |
| 1468 | uuid_first_entry_list[added].df_matched = 0; |
| 1469 | uuid_first_entry_list[added].df_index_oldest = 0; |
| 1470 | uuid_first_entry_list[added].uuid = mrg_metric_uuid(main_mrg, metric); |
| 1471 | added++; |
| 1472 | } |
| 1473 | } |
| 1474 | } |
| 1475 | else { |
| 1476 | // SIGBUS/SIGSEGV inside the mmap walk -- bail cleanly. The |
| 1477 | // PROTECTED_ACCESS_SETUP macro already rate-limits the error log. |
| 1478 | journal_access_failed = true; |
| 1479 | } |
| 1480 | } |
| 1481 | |
| 1482 | netdata_log_info( |
| 1483 | "DBENGINE: tier %d: recalculating retention for %zu metrics starting with datafile %u", |
| 1484 | ctx->config.tier, |
| 1485 | count, |
| 1486 | first_datafile_remaining ? first_datafile_remaining->fileno : 0); |
| 1487 | |
| 1488 | journalfile_v2_data_release(journalfile); |
| 1489 | |
| 1490 | if (unlikely(journal_access_failed)) { |
| 1491 | // Release any partially-acquired metrics; uuid_first_entry_list may |
| 1492 | // be NULL (signal received before callocz). |
| 1493 | for (size_t index = 0; index < added; ++index) |
| 1494 | mrg_metric_release(main_mrg, uuid_first_entry_list[index].metric); |
| 1495 | goto done; |
| 1496 | } |
| 1497 | |
| 1498 | // Update the first time / last time for all metrics we plan to delete |
| 1499 | |
| 1500 | if(worker) |
| 1501 | worker_is_busy(UV_EVENT_DBENGINE_FIND_REMAINING_RETENTION); |
| 1502 | |
| 1503 | global_first_time_s = find_uuid_first_time(ctx, first_datafile_remaining, uuid_first_entry_list, added); |
| 1504 | |
| 1505 | if (!ctx_is_available_for_queries(ctx)) { |
| 1506 | for (size_t index = 0; index < added; ++index) { |
| 1507 | uuid_first_t_entry = &uuid_first_entry_list[index]; |
| 1508 | mrg_metric_release(main_mrg, uuid_first_t_entry->metric); |
| 1509 | } |
| 1510 | goto done; |
| 1511 | } |
| 1512 | |
| 1513 | if(worker) |
| 1514 | worker_is_busy(UV_EVENT_DBENGINE_POPULATE_MRG); |
| 1515 | |
| 1516 | netdata_log_info("DBENGINE: tier %d: updating metrics registry retention for %zu metrics", ctx->config.tier, added); |
| 1517 | |
| 1518 | size_t deleted_metrics = 0, zero_retention_referenced = 0, zero_disk_retention = 0, zero_disk_but_live = 0; |
| 1519 | for (size_t index = 0; index < added; ++index) { |
| 1520 | uuid_first_t_entry = &uuid_first_entry_list[index]; |
| 1521 | |
| 1522 | if (!ctx_is_available_for_queries(ctx)) { |
| 1523 | mrg_metric_release(main_mrg, uuid_first_t_entry->metric); |
| 1524 | continue; |
| 1525 | } |
| 1526 | |
| 1527 | if (likely(uuid_first_t_entry->first_time_s != LONG_MAX)) { |
| 1528 | |
| 1529 | time_t old_first_time_s = mrg_metric_get_first_time_s(main_mrg, uuid_first_t_entry->metric); |
| 1530 | |
| 1531 | bool changed = mrg_metric_set_first_time_s_if_bigger(main_mrg, uuid_first_t_entry->metric, uuid_first_t_entry->first_time_s); |
| 1532 | if (changed) { |
| 1533 | uint32_t update_every_s = mrg_metric_get_update_every_s(main_mrg, uuid_first_t_entry->metric); |
| 1534 | if (update_every_s && old_first_time_s && uuid_first_t_entry->first_time_s > old_first_time_s) { |
| 1535 | uint64_t remove_samples = (uuid_first_t_entry->first_time_s - old_first_time_s) / update_every_s; |
| 1536 | __atomic_sub_fetch(&ctx->atomic.samples, remove_samples, __ATOMIC_RELAXED); |
| 1537 | } |
| 1538 | } |
| 1539 | mrg_metric_release(main_mrg, uuid_first_t_entry->metric); |
| 1540 | } |
| 1541 | else { |
| 1542 | zero_disk_retention++; |
| 1543 | |
| 1544 | // there is no retention for this metric |
| 1545 | bool has_retention = mrg_metric_has_zero_disk_retention(main_mrg, uuid_first_t_entry->metric); |
| 1546 | if (!has_retention) { |
| 1547 | time_t first_time_s = mrg_metric_get_first_time_s(main_mrg, uuid_first_t_entry->metric); |
| 1548 | time_t last_time_s = mrg_metric_get_latest_time_s(main_mrg, uuid_first_t_entry->metric); |
| 1549 | time_t update_every_s = mrg_metric_get_update_every_s(main_mrg, uuid_first_t_entry->metric); |
| 1550 | if (update_every_s && first_time_s && last_time_s) { |
| 1551 | uint64_t remove_samples = (first_time_s - last_time_s) / update_every_s; |
| 1552 | __atomic_sub_fetch(&ctx->atomic.samples, remove_samples, __ATOMIC_RELAXED); |
| 1553 | } |
| 1554 | |
| 1555 | bool deleted = mrg_metric_release_and_delete(main_mrg, uuid_first_t_entry->metric); |
| 1556 | if(deleted) |
| 1557 | deleted_metrics++; |
| 1558 | else |
| 1559 | zero_retention_referenced++; |
| 1560 | } |
| 1561 | else { |
| 1562 | zero_disk_but_live++; |
| 1563 | mrg_metric_release(main_mrg, uuid_first_t_entry->metric); |
| 1564 | } |
| 1565 | } |
| 1566 | } |
| 1567 | |
| 1568 | if (!ctx_is_available_for_queries(ctx)) |
| 1569 | goto done; |
| 1570 | |
| 1571 | internal_error(zero_disk_retention, |
| 1572 | "DBENGINE: tier %d: deleted %zu metrics from metrics registry; %zu still had zero retention but were referenced " |
| 1573 | "(out of %zu total zero on-disk retention metrics, of which %zu have main cache retention)", |
| 1574 | ctx->config.tier, deleted_metrics, zero_retention_referenced, zero_disk_retention, zero_disk_but_live); |
| 1575 | |
| 1576 | if(global_first_time_s != LONG_MAX) |
| 1577 | __atomic_store_n(&ctx->atomic.first_time_s, global_first_time_s, __ATOMIC_RELAXED); |
| 1578 | |
| 1579 | done: |
| 1580 | freez(uuid_first_entry_list); |
| 1581 | |
| 1582 | if(worker) |
| 1583 | worker_is_idle(); |
| 1584 | } |
| 1585 | |
| 1586 | void datafile_delete( |
| 1587 | struct rrdengine_instance *ctx, |
| 1588 | struct rrdengine_datafile *datafile, |
| 1589 | bool update_retention, |
| 1590 | bool disk_time, |
| 1591 | bool worker) |
| 1592 | { |
| 1593 | unsigned tier = ctx->config.tier; |
| 1594 | unsigned fileno = datafile->fileno; |
| 1595 | |
| 1596 | if(worker) |
| 1597 | worker_is_busy(UV_EVENT_DBENGINE_DATAFILE_DELETE_WAIT); |
| 1598 | |
| 1599 | bool datafile_got_for_deletion = datafile_acquire_for_deletion(datafile, false); |
| 1600 | size_t attempts = 0; |
| 1601 | |
| 1602 | while (!datafile_got_for_deletion) { |
| 1603 | if(worker) |
| 1604 | worker_is_busy(UV_EVENT_DBENGINE_DATAFILE_DELETE_WAIT); |
| 1605 | |
| 1606 | datafile_got_for_deletion = datafile_acquire_for_deletion(datafile, false); |
| 1607 | |
| 1608 | if (!datafile_got_for_deletion) { |
| 1609 | if(++attempts >= 30) { |
| 1610 | // pending_deletion is already set, blocking new acquires. |
| 1611 | // Bail out and let the next rotation cycle retry - lockers |
| 1612 | // will drain over time since no new ones can be added. |
| 1613 | netdata_log_error("DBENGINE: tier %u: " DATAFILE_PREFIX RRDENG_FILE_NUMBER_PRINT_TMPL |
| 1614 | " could not be acquired for deletion after %zu attempts (%u lockers remain)" |
| 1615 | " - will retry on next rotation", |
| 1616 | tier, datafile->tier, fileno, attempts, datafile->users.lockers); |
| 1617 | |
| 1618 | if(worker) |
| 1619 | worker_is_idle(); |
| 1620 | |
| 1621 | return; |
| 1622 | } |
| 1623 | |
| 1624 | netdata_log_info("DBENGINE: tier %u: waiting for " DATAFILE_PREFIX RRDENG_FILE_NUMBER_PRINT_TMPL |
| 1625 | " to be available for deletion, in use by %u users.", |
| 1626 | tier, datafile->tier, fileno, datafile->users.lockers); |
| 1627 | |
| 1628 | __atomic_add_fetch(&rrdeng_cache_efficiency_stats.datafile_deletion_spin, 1, __ATOMIC_RELAXED); |
| 1629 | sleep_usec(1 * USEC_PER_SEC); |
| 1630 | } |
| 1631 | } |
| 1632 | |
| 1633 | if (update_retention) |
| 1634 | update_metrics_first_time_s(ctx, datafile, get_next_datafile(datafile, NULL, false), worker); |
| 1635 | |
| 1636 | // if (!ctx_is_available_for_queries(ctx)) { |
| 1637 | // // agent is shutting down, we cannot continue |
| 1638 | // if(worker) |
| 1639 | // worker_is_idle(); |
| 1640 | // return; |
| 1641 | // } |
| 1642 | |
| 1643 | __atomic_add_fetch(&rrdeng_cache_efficiency_stats.datafile_deletion_started, 1, __ATOMIC_RELAXED); |
| 1644 | netdata_log_info("DBENGINE: tier %u: deleting " DATAFILE_PREFIX RRDENG_FILE_NUMBER_PRINT_TMPL " to maintain %s.", |
| 1645 | tier, datafile->tier, fileno, disk_time ? "disk quota" : "time retention"); |
| 1646 | |
| 1647 | if(worker) |
| 1648 | worker_is_busy(UV_EVENT_DBENGINE_DATAFILE_DELETE); |
| 1649 | |
| 1650 | struct rrdengine_journalfile *journal_file; |
| 1651 | size_t deleted_bytes, journal_file_bytes, datafile_bytes; |
| 1652 | uint8_t deleted_journal_files = 0; |
| 1653 | uint8_t expected_journal_files = JOURNALFILE_DELETED_V1; |
| 1654 | bool deleted_datafile = false; |
| 1655 | unsigned datafile_tier = datafile->tier; |
| 1656 | int ret; |
| 1657 | |
| 1658 | netdata_rwlock_wrlock(&ctx->datafiles.rwlock); |
| 1659 | datafile_list_delete_unsafe(ctx, datafile); |
| 1660 | netdata_rwlock_wrunlock(&ctx->datafiles.rwlock); |
| 1661 | |
| 1662 | journal_file = datafile->journalfile; |
| 1663 | datafile_bytes = datafile->pos; |
| 1664 | journal_file_bytes = journalfile_current_size(journal_file); |
| 1665 | size_t journal_v2_bytes = journalfile_v2_data_size_get(journal_file); |
| 1666 | if (journalfile_v2_data_available(journal_file)) |
| 1667 | expected_journal_files |= JOURNALFILE_DELETED_V2; |
| 1668 | deleted_bytes = 0; |
| 1669 | |
| 1670 | // This will delete journalfile_v2 and journalfile_v1 (returns bitmask of JOURNALFILE_DELETED_V1/V2) |
| 1671 | deleted_journal_files = journalfile_destroy_unsafe(journal_file, datafile); |
| 1672 | if (deleted_journal_files & JOURNALFILE_DELETED_V1) |
| 1673 | deleted_bytes += journal_file_bytes; |
| 1674 | if (deleted_journal_files & JOURNALFILE_DELETED_V2) |
| 1675 | deleted_bytes += journal_v2_bytes; |
| 1676 | // This will delete the datafile |
| 1677 | ret = destroy_data_file_unsafe(datafile); |
| 1678 | if (!ret) { |
| 1679 | deleted_datafile = true; |
| 1680 | deleted_bytes += datafile_bytes; |
| 1681 | } |
| 1682 | |
| 1683 | cleanup_datafile_epdl_structures(datafile); |
| 1684 | |
| 1685 | memset(journal_file, 0, sizeof(*journal_file)); |
| 1686 | memset(datafile, 0, sizeof(*datafile)); |
| 1687 | |
| 1688 | freez(journal_file); |
| 1689 | freez(datafile); |
| 1690 | |
| 1691 | ctx_current_disk_space_decrease(ctx, deleted_bytes); |
| 1692 | char size_for_humans[128]; |
| 1693 | size_snprintf(size_for_humans, sizeof(size_for_humans), deleted_bytes, "B", false); |
| 1694 | |
| 1695 | bool del_ndf = deleted_datafile; |
| 1696 | bool del_njf = deleted_journal_files & JOURNALFILE_DELETED_V1; |
| 1697 | bool del_njfv2 = deleted_journal_files & JOURNALFILE_DELETED_V2; |
| 1698 | bool exp_njf = expected_journal_files & JOURNALFILE_DELETED_V1; |
| 1699 | bool exp_njfv2 = expected_journal_files & JOURNALFILE_DELETED_V2; |
| 1700 | |
| 1701 | if (del_ndf && del_njf && del_njfv2) |
| 1702 | netdata_log_info("DBENGINE: tier %u: deleted " DATAFILE_PREFIX RRDENG_FILE_NUMBER_PRINT_TMPL " (.ndf, .njf, .njfv2), reclaimed %s.", |
| 1703 | tier, datafile_tier, fileno, size_for_humans); |
| 1704 | else if (del_ndf && del_njf && !exp_njfv2) |
| 1705 | netdata_log_info("DBENGINE: tier %u: deleted " DATAFILE_PREFIX RRDENG_FILE_NUMBER_PRINT_TMPL " (.ndf, .njf), reclaimed %s.", |
| 1706 | tier, datafile_tier, fileno, size_for_humans); |
| 1707 | else if (del_ndf || del_njf || del_njfv2) { |
| 1708 | BUFFER *removed = buffer_create(0, NULL); |
| 1709 | BUFFER *failed = buffer_create(0, NULL); |
| 1710 | const char *sep; |
| 1711 | |
| 1712 | sep = ""; |
| 1713 | if (del_ndf) { buffer_strcat(removed, sep); buffer_strcat(removed, ".ndf"); sep = ", "; } |
| 1714 | if (del_njf) { buffer_strcat(removed, sep); buffer_strcat(removed, ".njf"); sep = ", "; } |
| 1715 | if (del_njfv2) { buffer_strcat(removed, sep); buffer_strcat(removed, ".njfv2"); } |
| 1716 | |
| 1717 | sep = ""; |
| 1718 | if (!del_ndf) { buffer_strcat(failed, sep); buffer_strcat(failed, ".ndf"); sep = ", "; } |
| 1719 | if (exp_njf && !del_njf) { buffer_strcat(failed, sep); buffer_strcat(failed, ".njf"); sep = ", "; } |
| 1720 | if (exp_njfv2 && !del_njfv2) { buffer_strcat(failed, sep); buffer_strcat(failed, ".njfv2"); } |
| 1721 | |
| 1722 | if(buffer_strlen(failed)) |
| 1723 | netdata_log_error("DBENGINE: tier %u: partial delete of " DATAFILE_PREFIX RRDENG_FILE_NUMBER_PRINT_TMPL |
| 1724 | " - removed: %s, failed: %s, reclaimed %s.", |
| 1725 | tier, datafile_tier, fileno, |
| 1726 | buffer_tostring(removed), buffer_tostring(failed), size_for_humans); |
| 1727 | else |
| 1728 | netdata_log_info("DBENGINE: tier %u: deleted " DATAFILE_PREFIX RRDENG_FILE_NUMBER_PRINT_TMPL " (%s), reclaimed %s.", |
| 1729 | tier, datafile_tier, fileno, buffer_tostring(removed), size_for_humans); |
| 1730 | buffer_free(removed); |
| 1731 | buffer_free(failed); |
| 1732 | } |
| 1733 | else |
| 1734 | netdata_log_error("DBENGINE: tier %u: failed to delete " DATAFILE_PREFIX RRDENG_FILE_NUMBER_PRINT_TMPL " to maintain %s.", |
| 1735 | tier, datafile_tier, fileno, disk_time ? "disk quota" : "time retention"); |
| 1736 | } |
| 1737 | |
| 1738 | static void *database_rotate_tp_worker(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t *uv_work_req __maybe_unused) { |
| 1739 | |
| 1740 | struct rrdengine_datafile *datafile = get_first_ctx_datafile(ctx, false); |
| 1741 | datafile_delete(ctx, datafile, ctx_is_available_for_queries(ctx), true, true); |
| 1742 | |
| 1743 | rrdcontext_db_rotation(); |
| 1744 | |
| 1745 | return data; |
| 1746 | } |
| 1747 | |
| 1748 | static void after_flush_all_hot_and_dirty_pages_of_section(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* req __maybe_unused, int status __maybe_unused) { |
| 1749 | ; |
| 1750 | } |
| 1751 | |
| 1752 | static void *flush_all_hot_and_dirty_pages_of_section_tp_worker(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t *uv_work_req __maybe_unused) { |
| 1753 | worker_is_busy(UV_EVENT_DBENGINE_QUIESCE); |
| 1754 | pgc_flush_all_hot_and_dirty_pages(main_cache, (Word_t)ctx); |
| 1755 | |
| 1756 | for(size_t i = 0; i < pgc_max_flushers() ; i++) |
| 1757 | rrdeng_enq_cmd(NULL, RRDENG_OPCODE_FLUSH_MAIN, NULL, NULL, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL); |
| 1758 | |
| 1759 | return data; |
| 1760 | } |
| 1761 | |
| 1762 | static void after_flush_dirty_pages_of_section(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* req __maybe_unused, int status __maybe_unused) { |
| 1763 | ; |
| 1764 | } |
| 1765 | |
| 1766 | static void *flush_dirty_pages_of_section_tp_worker(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t *uv_work_req __maybe_unused) { |
| 1767 | worker_is_busy(UV_EVENT_DBENGINE_FLUSH_DIRTY); |
| 1768 | pgc_flush_dirty_pages(main_cache, (Word_t)ctx); |
| 1769 | |
| 1770 | for(size_t i = 0; i < pgc_max_flushers() ; i++) |
| 1771 | rrdeng_enq_cmd(NULL, RRDENG_OPCODE_FLUSH_MAIN, NULL, NULL, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL); |
| 1772 | |
| 1773 | return data; |
| 1774 | } |
| 1775 | |
| 1776 | struct mrg_load_thread { |
| 1777 | uv_sem_t *sem; |
| 1778 | struct rrdengine_datafile *datafile; |
| 1779 | size_t *total; |
| 1780 | size_t *populated_datafiles; |
| 1781 | }; |
| 1782 | |
| 1783 | void journalfile_v2_populate_retention_to_mrg_worker(void *arg) |
| 1784 | { |
| 1785 | struct mrg_load_thread *mlt = arg; |
| 1786 | struct rrdengine_instance *ctx = mlt->datafile->ctx; |
| 1787 | |
| 1788 | journalfile_v2_populate_retention_to_mrg(ctx, mlt->datafile->journalfile); |
| 1789 | |
| 1790 | uv_sem_post(mlt->sem); |
| 1791 | } |
| 1792 | |
| 1793 | static void *tier_mrg_load( |
| 1794 | struct rrdengine_instance *ctx __maybe_unused, |
| 1795 | void *data, |
| 1796 | struct completion *completion __maybe_unused, |
| 1797 | uv_work_t *req __maybe_unused) |
| 1798 | { |
| 1799 | worker_is_busy(UV_EVENT_DBENGINE_MRG_LOAD); |
| 1800 | struct mrg_load_thread *mlt = data; |
| 1801 | journalfile_v2_populate_retention_to_mrg_worker(mlt); |
| 1802 | mlt->datafile->populate_mrg.populated = true; |
| 1803 | spinlock_unlock(&mlt->datafile->populate_mrg.spinlock); |
| 1804 | |
| 1805 | __atomic_add_fetch(mlt->populated_datafiles, 1, __ATOMIC_RELAXED); |
| 1806 | __atomic_sub_fetch(mlt->total, 1, __ATOMIC_RELEASE); |
| 1807 | freez(mlt); |
| 1808 | worker_is_idle(); |
| 1809 | return NULL; |
| 1810 | } |
| 1811 | |
| 1812 | |
| 1813 | static void after_populate_mrg(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* req __maybe_unused, int status __maybe_unused) { |
| 1814 | if (completion) |
| 1815 | completion_mark_complete(completion); |
| 1816 | } |
| 1817 | |
| 1818 | static void *populate_mrg_tp_worker( |
| 1819 | struct rrdengine_instance *ctx, |
| 1820 | void *data, |
| 1821 | struct completion *completion __maybe_unused, |
| 1822 | uv_work_t *uv_work_req __maybe_unused) |
| 1823 | { |
| 1824 | worker_is_busy(UV_EVENT_DBENGINE_POPULATE_MRG); |
| 1825 | |
| 1826 | struct mrg_load_thread *mlt = data; |
| 1827 | int tier = ctx->config.tier; |
| 1828 | |
| 1829 | netdata_rwlock_rdlock(&ctx->datafiles.rwlock); |
| 1830 | |
| 1831 | size_t total_datafiles = 0; |
| 1832 | size_t populated_datafiles = 0; |
| 1833 | struct rrdengine_datafile *df = NULL; |
| 1834 | while ((df = get_next_datafile(df, ctx, true))) { |
| 1835 | total_datafiles++; |
| 1836 | if (df->populate_mrg.populated) |
| 1837 | populated_datafiles++; |
| 1838 | } |
| 1839 | netdata_rwlock_rdunlock(&ctx->datafiles.rwlock); |
| 1840 | |
| 1841 | if (total_datafiles == 0) { |
| 1842 | nd_log_daemon(NDLP_WARNING, "DBENGINE: tier %d: no datafiles to populate MRG", tier); |
| 1843 | worker_is_idle(); |
| 1844 | return data; |
| 1845 | } |
| 1846 | |
| 1847 | size_t total = 0; |
| 1848 | Word_t last_index = 0; |
| 1849 | bool resume_scan = false; |
| 1850 | do { |
| 1851 | struct rrdengine_datafile *datafile = NULL; |
| 1852 | |
| 1853 | // find a datafile to work on |
| 1854 | netdata_rwlock_rdlock(&ctx->datafiles.rwlock); |
| 1855 | Pvoid_t *Pvalue = NULL; |
| 1856 | Word_t Index = resume_scan ? last_index : 0; |
| 1857 | bool first_then_next = !resume_scan; |
| 1858 | while((Pvalue = JudyLFirstThenNext(ctx->datafiles.JudyL, &Index, &first_then_next))) { |
| 1859 | datafile = *Pvalue; |
| 1860 | if(!spinlock_trylock(&datafile->populate_mrg.spinlock)) { |
| 1861 | datafile = NULL; |
| 1862 | continue; |
| 1863 | } |
| 1864 | |
| 1865 | if(datafile->populate_mrg.populated) { |
| 1866 | spinlock_unlock(&datafile->populate_mrg.spinlock); |
| 1867 | datafile = NULL; |
| 1868 | continue; |
| 1869 | } |
| 1870 | break; |
| 1871 | } |
| 1872 | netdata_rwlock_rdunlock(&ctx->datafiles.rwlock); |
| 1873 | |
| 1874 | if(!datafile) |
| 1875 | break; |
| 1876 | |
| 1877 | // resume next scan from current position |
| 1878 | last_index = Index; |
| 1879 | resume_scan = true; |
| 1880 | |
| 1881 | uv_sem_wait(mlt->sem); |
| 1882 | struct mrg_load_thread *local_mlt = callocz(1, sizeof(struct mrg_load_thread)); |
| 1883 | local_mlt->datafile = datafile; |
| 1884 | local_mlt->sem = mlt->sem; |
| 1885 | local_mlt->total = &total; |
| 1886 | local_mlt->populated_datafiles = &populated_datafiles; |
| 1887 | __atomic_add_fetch(local_mlt->total, 1, __ATOMIC_RELAXED); |
| 1888 | rrdeng_enq_cmd(ctx, RRDENG_OPCODE_MRG_LOAD, local_mlt, NULL, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL); |
| 1889 | { |
| 1890 | nd_log_limit_static_thread_var(erl, 10, 0); |
| 1891 | size_t completed = __atomic_load_n(&populated_datafiles, __ATOMIC_RELAXED); |
| 1892 | nd_log_limit(&erl, NDLS_DAEMON, NDLP_INFO, |
| 1893 | "DBENGINE: tier %d: MRG population completed: %.2f%% (%zu/%zu)", |
| 1894 | tier, (completed * 100.0) / total_datafiles, completed, total_datafiles); |
| 1895 | } |
| 1896 | } while(1); |
| 1897 | |
| 1898 | // We've queued all datafiles. Now wait for all worker threads to complete. |
| 1899 | size_t pending; |
| 1900 | do { |
| 1901 | pending = __atomic_load_n(&total, __ATOMIC_ACQUIRE); |
| 1902 | if (pending) { |
| 1903 | nd_log_limit_static_thread_var(erl, 10, 0); |
| 1904 | size_t completed = __atomic_load_n(&populated_datafiles, __ATOMIC_RELAXED); |
| 1905 | nd_log_limit(&erl, NDLS_DAEMON, NDLP_INFO, |
| 1906 | "DBENGINE: tier %d: MRG population completed: %.2f%% (%zu/%zu), waiting for %zu workers", |
| 1907 | tier, (completed * 100.0) / total_datafiles, completed, total_datafiles, pending); |
| 1908 | sleep_usec(10 * USEC_PER_MS); |
| 1909 | } |
| 1910 | } while (pending > 0); |
| 1911 | |
| 1912 | worker_is_idle(); |
| 1913 | return data; |
| 1914 | } |
| 1915 | |
| 1916 | static void after_ctx_shutdown(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* req __maybe_unused, int status __maybe_unused) { |
| 1917 | ; |
| 1918 | } |
| 1919 | |
| 1920 | static void *ctx_shutdown_tp_worker(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t *uv_work_req __maybe_unused) { |
| 1921 | worker_is_busy(UV_EVENT_DBENGINE_SHUTDOWN); |
| 1922 | |
| 1923 | bool logged = false; |
| 1924 | while(__atomic_load_n(&ctx->atomic.extents_currently_being_flushed, __ATOMIC_RELAXED) || |
| 1925 | __atomic_load_n(&ctx->atomic.inflight_queries, __ATOMIC_RELAXED)) { |
| 1926 | if(!logged) { |
| 1927 | logged = true; |
| 1928 | netdata_log_info("DBENGINE: waiting for %zu inflight queries to finish to shutdown tier %d...", |
| 1929 | __atomic_load_n(&ctx->atomic.inflight_queries, __ATOMIC_RELAXED), ctx->config.tier); |
| 1930 | } |
| 1931 | sleep_usec(1 * USEC_PER_MS); |
| 1932 | } |
| 1933 | |
| 1934 | completion_mark_complete(completion); |
| 1935 | |
| 1936 | return data; |
| 1937 | } |
| 1938 | |
| 1939 | static void *cache_flush_tp_worker(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t *uv_work_req __maybe_unused) { |
| 1940 | if (!main_cache) |
| 1941 | return data; |
| 1942 | |
| 1943 | worker_is_busy(UV_EVENT_DBENGINE_FLUSH_MAIN_CACHE); |
| 1944 | while (pgc_flush_pages(main_cache)) |
| 1945 | yield_the_processor(); |
| 1946 | |
| 1947 | return data; |
| 1948 | } |
| 1949 | |
| 1950 | static void *cache_evict_main_tp_worker(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t *req __maybe_unused) { |
| 1951 | if (!main_cache) |
| 1952 | return data; |
| 1953 | |
| 1954 | worker_is_busy(UV_EVENT_DBENGINE_EVICT_MAIN_CACHE); |
| 1955 | while (pgc_evict_pages(main_cache, 0, 0)) |
| 1956 | yield_the_processor(); |
| 1957 | |
| 1958 | return data; |
| 1959 | } |
| 1960 | |
| 1961 | static void *cache_evict_open_tp_worker(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t *req __maybe_unused) { |
| 1962 | if (!open_cache) |
| 1963 | return data; |
| 1964 | |
| 1965 | worker_is_busy(UV_EVENT_DBENGINE_EVICT_OPEN_CACHE); |
| 1966 | while (pgc_evict_pages(open_cache, 0, 0)) |
| 1967 | yield_the_processor(); |
| 1968 | |
| 1969 | return data; |
| 1970 | } |
| 1971 | |
| 1972 | static void *cache_evict_extent_tp_worker(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t *req __maybe_unused) { |
| 1973 | if (!extent_cache) |
| 1974 | return data; |
| 1975 | |
| 1976 | worker_is_busy(UV_EVENT_DBENGINE_EVICT_EXTENT_CACHE); |
| 1977 | while (pgc_evict_pages(extent_cache, 0, 0)) |
| 1978 | yield_the_processor(); |
| 1979 | |
| 1980 | return data; |
| 1981 | } |
| 1982 | |
| 1983 | static void *query_prep_tp_worker(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t *req __maybe_unused) { |
| 1984 | PDC *pdc = data; |
| 1985 | rrdeng_prep_query(pdc, true); |
| 1986 | return data; |
| 1987 | } |
| 1988 | |
| 1989 | uint64_t rrdeng_target_data_file_size(struct rrdengine_instance *ctx) { |
| 1990 | uint64_t target_size = ctx->config.max_disk_space ? ctx->config.max_disk_space / TARGET_DATAFILES : MAX_DATAFILE_SIZE; |
| 1991 | target_size = MIN(target_size, MAX_DATAFILE_SIZE); |
| 1992 | target_size = MAX(target_size, MIN_DATAFILE_SIZE); |
| 1993 | return target_size; |
| 1994 | } |
| 1995 | |
| 1996 | /* return 0 on success */ |
| 1997 | int init_rrd_files(struct rrdengine_instance *ctx) |
| 1998 | { |
| 1999 | return init_data_files(ctx); |
| 2000 | } |
| 2001 | |
| 2002 | void finalize_rrd_files(struct rrdengine_instance *ctx) |
| 2003 | { |
| 2004 | return finalize_data_files(ctx); |
| 2005 | } |
| 2006 | |
| 2007 | #if defined(OS_WINDOWS) |
| 2008 | uint64_t last_async_callback; |
| 2009 | |
| 2010 | void async_cb(uv_async_t *handle) |
| 2011 | { |
| 2012 | last_async_callback = uv_hrtime(); |
| 2013 | |
| 2014 | netdata_log_debug(D_RRDENGINE, "%s called, active=%d.", __func__, uv_is_active((uv_handle_t *)handle)); |
| 2015 | } |
| 2016 | |
| 2017 | static void async_closed_cb(uv_handle_t *handle) |
| 2018 | { |
| 2019 | struct rrdeng_main *main = handle->data; |
| 2020 | |
| 2021 | int ret = uv_async_init(handle->loop, &main->async, async_cb); |
| 2022 | if (ret) |
| 2023 | netdata_log_error("DBENGINE: reinitializing uv_async_init(): %s", uv_strerror(ret)); |
| 2024 | __atomic_store_n(&main->async_ready, true, __ATOMIC_RELEASE); |
| 2025 | } |
| 2026 | #else |
| 2027 | void async_cb(uv_async_t *handle __maybe_unused) |
| 2028 | { |
| 2029 | netdata_log_debug(D_RRDENGINE, "%s called, active=%d.", __func__, uv_is_active((uv_handle_t *)handle)); |
| 2030 | } |
| 2031 | #endif |
| 2032 | |
| 2033 | #define TIMER_PERIOD_MS (1000) |
| 2034 | |
| 2035 | static void *extent_read_tp_worker(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t *uv_work_req __maybe_unused) { |
| 2036 | EPDL *epdl = data; |
| 2037 | epdl_find_extent_and_populate_pages(ctx, epdl, true); |
| 2038 | return data; |
| 2039 | } |
| 2040 | |
| 2041 | static NOT_INLINE_HOT void epdl_populate_pages_asynchronously(struct rrdengine_instance *ctx, EPDL *epdl, STORAGE_PRIORITY priority) { |
| 2042 | rrdeng_enq_cmd(ctx, RRDENG_OPCODE_EXTENT_READ, epdl, NULL, priority, |
| 2043 | rrdeng_enqueue_epdl_cmd, rrdeng_dequeue_epdl_cmd); |
| 2044 | } |
| 2045 | |
| 2046 | NOT_INLINE_HOT void pdc_route_asynchronously(struct rrdengine_instance *ctx, struct page_details_control *pdc) { |
| 2047 | pdc_to_epdl_router(ctx, pdc, epdl_populate_pages_asynchronously, epdl_populate_pages_asynchronously); |
| 2048 | } |
| 2049 | |
| 2050 | NOT_INLINE_HOT void epdl_populate_pages_synchronously(struct rrdengine_instance *ctx, EPDL *epdl, enum storage_priority priority __maybe_unused) { |
| 2051 | epdl_find_extent_and_populate_pages(ctx, epdl, false); |
| 2052 | } |
| 2053 | |
| 2054 | NOT_INLINE_HOT void pdc_route_synchronously(struct rrdengine_instance *ctx, struct page_details_control *pdc) { |
| 2055 | pdc_to_epdl_router(ctx, pdc, epdl_populate_pages_synchronously, epdl_populate_pages_synchronously); |
| 2056 | } |
| 2057 | |
| 2058 | NOT_INLINE_HOT void pdc_route_synchronously_first(struct rrdengine_instance *ctx, struct page_details_control *pdc) { |
| 2059 | pdc_to_epdl_router(ctx, pdc, epdl_populate_pages_synchronously, epdl_populate_pages_asynchronously); |
| 2060 | } |
| 2061 | |
| 2062 | static struct rrdengine_datafile *release_and_aquire_next_datafile_for_indexing(struct rrdengine_instance *ctx, struct rrdengine_datafile *release_datafile) |
| 2063 | { |
| 2064 | struct rrdengine_datafile *datafile = NULL; |
| 2065 | |
| 2066 | netdata_rwlock_rdlock(&ctx->datafiles.rwlock); |
| 2067 | if (release_datafile) { |
| 2068 | datafile = get_next_datafile(release_datafile, NULL, true); |
| 2069 | datafile_release(release_datafile, DATAFILE_ACQUIRE_INDEXING); |
| 2070 | } |
| 2071 | else |
| 2072 | datafile = get_first_ctx_datafile(ctx, true); |
| 2073 | |
| 2074 | while (datafile && datafile->fileno != ctx_last_fileno_get(ctx) && datafile->fileno != ctx_last_flush_fileno_get(ctx)) { |
| 2075 | if(journalfile_v2_data_available(datafile->journalfile)) { |
| 2076 | datafile = get_next_datafile(datafile, NULL, true); |
| 2077 | continue; |
| 2078 | } |
| 2079 | |
| 2080 | int retries = 5; |
| 2081 | bool locked = false; |
| 2082 | while (retries-- > 0) { |
| 2083 | locked = datafile_acquire(datafile, DATAFILE_ACQUIRE_INDEXING); |
| 2084 | if (locked) |
| 2085 | break; |
| 2086 | sleep_usec(200 * USEC_PER_MS); |
| 2087 | } |
| 2088 | if (locked) { |
| 2089 | netdata_rwlock_rdunlock(&ctx->datafiles.rwlock); |
| 2090 | return datafile; |
| 2091 | } |
| 2092 | nd_log_daemon(NDLP_INFO, "DBENGINE: tier %d: " DATAFILE_PREFIX RRDENG_FILE_NUMBER_PRINT_TMPL " cannot be locked for indexing after retries; skipping", |
| 2093 | ctx->config.tier, datafile->tier, datafile->fileno); |
| 2094 | datafile = get_next_datafile(datafile, NULL, true); |
| 2095 | } |
| 2096 | netdata_rwlock_rdunlock(&ctx->datafiles.rwlock); |
| 2097 | return NULL; |
| 2098 | } |
| 2099 | |
| 2100 | |
| 2101 | static void *journal_v2_indexing_tp_worker(struct rrdengine_instance *ctx, void *data, struct completion *completion __maybe_unused, uv_work_t *uv_work_req __maybe_unused) { |
| 2102 | unsigned count = 0; |
| 2103 | |
| 2104 | if (unlikely(!ctx_is_available_for_queries(ctx))) |
| 2105 | return data; |
| 2106 | |
| 2107 | worker_is_busy(UV_EVENT_DBENGINE_JOURNAL_INDEX); |
| 2108 | struct rrdengine_datafile *datafile = NULL; |
| 2109 | |
| 2110 | bool index_once = false; |
| 2111 | while ((datafile = release_and_aquire_next_datafile_for_indexing(ctx, datafile))) { |
| 2112 | |
| 2113 | spinlock_lock(&datafile->writers.spinlock); |
| 2114 | bool available = (datafile->writers.running || datafile->writers.flushed_to_open_running) ? false : true; |
| 2115 | spinlock_unlock(&datafile->writers.spinlock); |
| 2116 | |
| 2117 | if(!available) { |
| 2118 | nd_log_daemon(NDLP_NOTICE, |
| 2119 | "DBENGINE: tier %d: " DATAFILE_PREFIX RRDENG_FILE_NUMBER_PRINT_TMPL |
| 2120 | " needs to be indexed, but it has writers working on it - skipping it for now", |
| 2121 | ctx->config.tier, datafile->tier, datafile->fileno); |
| 2122 | continue; |
| 2123 | } |
| 2124 | |
| 2125 | if (index_once && unlikely(rrdeng_ctx_tier_cap_exceeded(ctx))) { |
| 2126 | nd_log_daemon( |
| 2127 | NDLP_INFO, "DBENGINE: tier %d: reached quota limit, stopping journal indexing", ctx->config.tier); |
| 2128 | __atomic_store_n(&ctx->atomic.needs_indexing, true, __ATOMIC_RELAXED); |
| 2129 | datafile_release(datafile, DATAFILE_ACQUIRE_INDEXING); |
| 2130 | break; |
| 2131 | } |
| 2132 | nd_log_daemon(NDLP_INFO, "DBENGINE: tier %d: " DATAFILE_PREFIX RRDENG_FILE_NUMBER_PRINT_TMPL " is ready to be indexed", |
| 2133 | ctx->config.tier, datafile->tier, datafile->fileno); |
| 2134 | |
| 2135 | pgc_open_cache_to_journal_v2( |
| 2136 | open_cache, |
| 2137 | (Word_t)ctx, |
| 2138 | (int)datafile->fileno, |
| 2139 | ctx->config.page_type, |
| 2140 | journalfile_migrate_to_v2_callback, |
| 2141 | (void *)datafile->journalfile, |
| 2142 | false); |
| 2143 | |
| 2144 | index_once = true; |
| 2145 | |
| 2146 | count++; |
| 2147 | |
| 2148 | // check if we are shutting down |
| 2149 | if (unlikely(!ctx_is_available_for_queries(ctx))) { |
| 2150 | datafile_release(datafile, DATAFILE_ACQUIRE_INDEXING); |
| 2151 | break; |
| 2152 | } |
| 2153 | } |
| 2154 | |
| 2155 | errno_clear(); |
| 2156 | if(count) |
| 2157 | nd_log(NDLS_DAEMON, NDLP_DEBUG, |
| 2158 | "DBENGINE: tier %d: journal indexing done; %u files processed", |
| 2159 | ctx->config.tier, count); |
| 2160 | |
| 2161 | worker_is_idle(); |
| 2162 | |
| 2163 | return data; |
| 2164 | } |
| 2165 | |
| 2166 | static void after_do_cache_flush(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* req __maybe_unused, int status __maybe_unused) { |
| 2167 | rrdeng_main.flushes_running--; |
| 2168 | } |
| 2169 | |
| 2170 | static void after_do_main_cache_evict(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* req __maybe_unused, int status __maybe_unused) { |
| 2171 | rrdeng_main.evict_main_running--; |
| 2172 | } |
| 2173 | |
| 2174 | static void after_do_open_cache_evict(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* req __maybe_unused, int status __maybe_unused) { |
| 2175 | rrdeng_main.evict_open_running--; |
| 2176 | } |
| 2177 | |
| 2178 | static void after_do_extent_cache_evict(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* req __maybe_unused, int status __maybe_unused) { |
| 2179 | rrdeng_main.evict_extent_running--; |
| 2180 | } |
| 2181 | |
| 2182 | static void after_journal_v2_indexing(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* req __maybe_unused, int status __maybe_unused) { |
| 2183 | __atomic_store_n(&ctx->atomic.migration_to_v2_running, false, __ATOMIC_RELAXED); |
| 2184 | |
| 2185 | check_and_schedule_db_rotation(ctx); |
| 2186 | } |
| 2187 | |
| 2188 | struct rrdeng_buffer_sizes rrdeng_pulse_memory_sizes(void) { |
| 2189 | return (struct rrdeng_buffer_sizes) { |
| 2190 | .as = { |
| 2191 | [RRDENG_MEM_PGC] = pgc_aral_stats(), |
| 2192 | [RRDENG_MEM_PGD] = pgd_aral_stats(), |
| 2193 | [RRDENG_MEM_MRG] = mrg_aral_stats(), |
| 2194 | [RRDENG_MEM_PDC] = pdc_aral_stats(), |
| 2195 | [RRDENG_MEM_EPDL] = epdl_aral_stats(), |
| 2196 | [RRDENG_MEM_DEOL] = deol_aral_stats(), |
| 2197 | [RRDENG_MEM_PD] = pd_aral_stats(), |
| 2198 | [RRDENG_MEM_EPDL_EXTENT] = epdl_extent_aral_stats(), |
| 2199 | [RRDENG_MEM_OPCODES] = aral_get_statistics(rrdeng_main.cmd_queue.ar), |
| 2200 | [RRDENG_MEM_HANDLES] = aral_get_statistics(rrdeng_main.handles.ar), |
| 2201 | [RRDENG_MEM_DESCRIPTORS] = aral_get_statistics(rrdeng_main.descriptors.ar), |
| 2202 | [RRDENG_MEM_WORKERS] = aral_get_statistics(rrdeng_main.work_cmd.ar), |
| 2203 | [RRDENG_MEM_XT_IO] = aral_get_statistics(rrdeng_main.xt_io_descr.ar), |
| 2204 | }, |
| 2205 | .wal = __atomic_load_n(&wal_globals.atomics.allocated, __ATOMIC_RELAXED) * (sizeof(WAL) + RRDENG_BLOCK_SIZE), |
| 2206 | .xt_buf = extent_buffer_cache_size(), |
| 2207 | }; |
| 2208 | } |
| 2209 | |
| 2210 | static void after_cleanup(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t* req __maybe_unused, int status __maybe_unused) { |
| 2211 | rrdeng_main.cleanup_running--; |
| 2212 | } |
| 2213 | |
| 2214 | static void *cleanup_tp_worker(struct rrdengine_instance *ctx __maybe_unused, void *data __maybe_unused, struct completion *completion __maybe_unused, uv_work_t *uv_work_req __maybe_unused) { |
| 2215 | worker_is_busy(UV_EVENT_DBENGINE_BUFFERS_CLEANUP); |
| 2216 | |
| 2217 | wal_cleanup1(); |
| 2218 | extent_buffer_cleanup1(); |
| 2219 | |
| 2220 | { |
| 2221 | static time_t last_run_s = 0; |
| 2222 | time_t now_s = now_monotonic_sec(); |
| 2223 | if(now_s - last_run_s >= 10) { |
| 2224 | last_run_s = now_s; |
| 2225 | journalfile_v2_data_unmount_cleanup(now_s); |
| 2226 | } |
| 2227 | } |
| 2228 | |
| 2229 | return data; |
| 2230 | } |
| 2231 | |
| 2232 | uint64_t rrdeng_get_used_disk_space(struct rrdengine_instance *ctx, bool having_lock) |
| 2233 | { |
| 2234 | uint64_t active_space = 0; |
| 2235 | |
| 2236 | if (!having_lock) |
| 2237 | netdata_rwlock_rdlock(&ctx->datafiles.rwlock); |
| 2238 | |
| 2239 | struct rrdengine_datafile *first_datafile = get_first_ctx_datafile(ctx, true); |
| 2240 | struct rrdengine_datafile *last_datafile = get_last_ctx_datafile(ctx, true); |
| 2241 | |
| 2242 | if (first_datafile && last_datafile) |
| 2243 | active_space = last_datafile->pos; |
| 2244 | |
| 2245 | if (!having_lock) |
| 2246 | netdata_rwlock_rdunlock(&ctx->datafiles.rwlock); |
| 2247 | |
| 2248 | // calculate the estimated disk space based on the expected final size of the datafile |
| 2249 | // We cant know the final v1/v2 journal size -- we let the current v1 size be part of the calculation by not |
| 2250 | // including it in the active_space |
| 2251 | uint64_t estimated_disk_space = ctx_current_disk_space_get(ctx) + rrdeng_target_data_file_size(ctx) - active_space; |
| 2252 | |
| 2253 | uint64_t database_space = get_total_database_space(); |
| 2254 | uint64_t adjusted_database_space = database_space * ctx->config.disk_percentage / 100 ; |
| 2255 | estimated_disk_space += adjusted_database_space; |
| 2256 | |
| 2257 | return estimated_disk_space; |
| 2258 | } |
| 2259 | |
| 2260 | // Check if disk or retention time cap reached |
| 2261 | bool rrdeng_ctx_tier_cap_exceeded(struct rrdengine_instance *ctx) |
| 2262 | { |
| 2263 | bool trigger_time_retention = false; |
| 2264 | uint64_t estimated_disk_space = 0; |
| 2265 | |
| 2266 | netdata_rwlock_rdlock(&ctx->datafiles.rwlock); |
| 2267 | struct rrdengine_datafile *first_datafile = get_first_ctx_datafile(ctx, true); |
| 2268 | |
| 2269 | if (!first_datafile || datafile_count(ctx, true) < 2) { |
| 2270 | netdata_rwlock_rdunlock(&ctx->datafiles.rwlock); |
| 2271 | return false; |
| 2272 | } |
| 2273 | |
| 2274 | if (ctx->config.max_retention_s) { |
| 2275 | time_t last_time_s = first_datafile->journalfile->v2.last_time_s; |
| 2276 | if (!last_time_s) |
| 2277 | last_time_s = first_datafile->journalfile->v2.first_time_s; |
| 2278 | |
| 2279 | time_t cutoff_before_time_s = now_realtime_sec() - ctx->config.max_retention_s; |
| 2280 | trigger_time_retention = (last_time_s && last_time_s <= cutoff_before_time_s); |
| 2281 | } |
| 2282 | |
| 2283 | // avoid disk calculation if we will trigger time retention |
| 2284 | // calculate estimated disk space only if we have a disk cap |
| 2285 | if (false == trigger_time_retention && ctx->config.max_disk_space) |
| 2286 | estimated_disk_space = rrdeng_get_used_disk_space(ctx, true); |
| 2287 | |
| 2288 | netdata_rwlock_rdunlock(&ctx->datafiles.rwlock); |
| 2289 | |
| 2290 | if (trigger_time_retention) { |
| 2291 | __atomic_store_n(&ctx->datafiles.disk_time, false, __ATOMIC_RELAXED); |
| 2292 | return true; |
| 2293 | } |
| 2294 | |
| 2295 | if (ctx->config.max_disk_space && estimated_disk_space > ctx->config.max_disk_space) { |
| 2296 | __atomic_store_n(&ctx->datafiles.disk_time, true, __ATOMIC_RELAXED); |
| 2297 | return true; |
| 2298 | } |
| 2299 | |
| 2300 | return false; |
| 2301 | } |
| 2302 | |
| 2303 | static void retention_timer_cb(uv_timer_t *handle __maybe_unused) |
| 2304 | { |
| 2305 | if (!localhost) |
| 2306 | return; |
| 2307 | |
| 2308 | worker_is_busy(RRDENG_RETENTION_TIMER_CB); |
| 2309 | |
| 2310 | for (size_t tier = 0; tier < nd_profile.storage_tiers; tier++) { |
| 2311 | STORAGE_ENGINE *eng = localhost->db[tier].eng; |
| 2312 | if (!eng || eng->seb != STORAGE_ENGINE_BACKEND_DBENGINE) |
| 2313 | continue; |
| 2314 | check_and_schedule_db_rotation(multidb_ctx[tier]); |
| 2315 | } |
| 2316 | |
| 2317 | worker_is_idle(); |
| 2318 | } |
| 2319 | |
| 2320 | static void timer_per_sec_cb(uv_timer_t *handle __maybe_unused) |
| 2321 | { |
| 2322 | worker_is_busy(RRDENG_TIMER_CB); |
| 2323 | |
| 2324 | worker_set_metric(RRDENG_OPCODES_WAITING, (NETDATA_DOUBLE)rrdeng_main.cmd_queue.unsafe.waiting); |
| 2325 | worker_set_metric(RRDENG_WORKS_DISPATCHED, (NETDATA_DOUBLE)__atomic_load_n(&rrdeng_main.work_cmd.atomics.dispatched, __ATOMIC_RELAXED)); |
| 2326 | worker_set_metric(RRDENG_WORKS_EXECUTING, (NETDATA_DOUBLE)__atomic_load_n(&rrdeng_main.work_cmd.atomics.executing, __ATOMIC_RELAXED)); |
| 2327 | |
| 2328 | rrdeng_enq_cmd(NULL, RRDENG_OPCODE_FLUSH_MAIN, NULL, NULL, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL); |
| 2329 | rrdeng_enq_cmd(NULL, RRDENG_OPCODE_CLEANUP, NULL, NULL, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL); |
| 2330 | |
| 2331 | worker_is_idle(); |
| 2332 | } |
| 2333 | |
| 2334 | static void dbengine_initialize_structures(void) { |
| 2335 | pgd_init_arals(); |
| 2336 | pgc_and_mrg_initialize(); |
| 2337 | |
| 2338 | pdc_init(); |
| 2339 | page_details_init(); |
| 2340 | epdl_init(); |
| 2341 | deol_init(); |
| 2342 | epdl_extent_init(); |
| 2343 | rrdeng_cmd_queue_init(); |
| 2344 | work_request_init(); |
| 2345 | rrdeng_query_handle_init(); |
| 2346 | page_descriptors_init(); |
| 2347 | extent_buffer_init(); |
| 2348 | extent_io_descriptor_init(); |
| 2349 | } |
| 2350 | |
| 2351 | bool rrdeng_dbengine_spawn(struct rrdengine_instance *ctx __maybe_unused) { |
| 2352 | static bool spawned = false; |
| 2353 | static SPINLOCK spinlock = SPINLOCK_INITIALIZER; |
| 2354 | |
| 2355 | spinlock_lock(&spinlock); |
| 2356 | |
| 2357 | if(!spawned) { |
| 2358 | int ret; |
| 2359 | |
| 2360 | ret = uv_loop_init(&rrdeng_main.loop); |
| 2361 | if (ret) { |
| 2362 | netdata_log_error("DBENGINE: uv_loop_init(): %s", uv_strerror(ret)); |
| 2363 | return false; |
| 2364 | } |
| 2365 | rrdeng_main.loop.data = &rrdeng_main; |
| 2366 | |
| 2367 | ret = uv_async_init(&rrdeng_main.loop, &rrdeng_main.async, async_cb); |
| 2368 | if (ret) { |
| 2369 | netdata_log_error("DBENGINE: uv_async_init(): %s", uv_strerror(ret)); |
| 2370 | fatal_assert(0 == uv_loop_close(&rrdeng_main.loop)); |
| 2371 | return false; |
| 2372 | } |
| 2373 | rrdeng_main.async.data = &rrdeng_main; |
| 2374 | #if defined(OS_WINDOWS) |
| 2375 | rrdeng_main.async_ready = true; |
| 2376 | #endif |
| 2377 | |
| 2378 | ret = uv_timer_init(&rrdeng_main.loop, &rrdeng_main.timer); |
| 2379 | if (ret) { |
| 2380 | netdata_log_error("DBENGINE: uv_timer_init(): %s", uv_strerror(ret)); |
| 2381 | uv_close((uv_handle_t *)&rrdeng_main.async, NULL); |
| 2382 | fatal_assert(0 == uv_loop_close(&rrdeng_main.loop)); |
| 2383 | return false; |
| 2384 | } |
| 2385 | |
| 2386 | ret = uv_timer_init(&rrdeng_main.loop, &rrdeng_main.retention_timer); |
| 2387 | if (ret) { |
| 2388 | netdata_log_error("DBENGINE: uv_timer_init(): %s", uv_strerror(ret)); |
| 2389 | uv_close((uv_handle_t *)&rrdeng_main.async, NULL); |
| 2390 | fatal_assert(0 == uv_loop_close(&rrdeng_main.loop)); |
| 2391 | return false; |
| 2392 | } |
| 2393 | |
| 2394 | rrdeng_main.timer.data = &rrdeng_main; |
| 2395 | rrdeng_main.retention_timer.data = &rrdeng_main; |
| 2396 | |
| 2397 | dbengine_initialize_structures(); |
| 2398 | |
| 2399 | int retries = 0; |
| 2400 | rrdeng_main.thread = nd_thread_create("DBEV", NETDATA_THREAD_OPTION_DEFAULT, dbengine_event_loop, &rrdeng_main); |
| 2401 | |
| 2402 | fatal_assert(0 != rrdeng_main.thread); |
| 2403 | |
| 2404 | if (retries) |
| 2405 | nd_log_daemon(NDLP_WARNING, "DBENGINE thread was created after %d attempts", retries); |
| 2406 | |
| 2407 | spawned = true; |
| 2408 | } |
| 2409 | |
| 2410 | spinlock_unlock(&spinlock); |
| 2411 | return true; |
| 2412 | } |
| 2413 | |
| 2414 | static inline void worker_dispatch_extent_read(struct rrdeng_cmd cmd, bool from_worker) { |
| 2415 | struct rrdengine_instance *ctx = cmd.ctx; |
| 2416 | EPDL *epdl = cmd.data; |
| 2417 | |
| 2418 | if(from_worker) |
| 2419 | epdl_find_extent_and_populate_pages(ctx, epdl, true); |
| 2420 | else |
| 2421 | work_dispatch(ctx, epdl, NULL, cmd.opcode, extent_read_tp_worker, NULL); |
| 2422 | } |
| 2423 | |
| 2424 | static inline void worker_dispatch_query_prep(struct rrdeng_cmd cmd, bool from_worker) { |
| 2425 | struct rrdengine_instance *ctx = cmd.ctx; |
| 2426 | PDC *pdc = cmd.data; |
| 2427 | |
| 2428 | if(from_worker) |
| 2429 | rrdeng_prep_query(pdc, true); |
| 2430 | else |
| 2431 | work_dispatch(ctx, pdc, NULL, cmd.opcode, query_prep_tp_worker, NULL); |
| 2432 | } |
| 2433 | |
| 2434 | uint64_t rrdeng_get_directory_free_bytes_space(struct rrdengine_instance *ctx) |
| 2435 | { |
| 2436 | uint64_t free_bytes = 0; |
| 2437 | OS_SYSTEM_DISK_SPACE space = os_disk_space(ctx->config.dbfiles_path); |
| 2438 | free_bytes = OS_SYSTEM_DISK_SPACE_OK(space) ? space.free_bytes : 0; |
| 2439 | return (free_bytes - (free_bytes * 5 / 100)); |
| 2440 | } |
| 2441 | |
| 2442 | void rrdeng_calculate_tier_disk_space_percentage(void) |
| 2443 | { |
| 2444 | uint64_t tier_space[RRD_STORAGE_TIERS]; |
| 2445 | |
| 2446 | if (!localhost) |
| 2447 | return; |
| 2448 | |
| 2449 | uint64_t total_diskspace = 0; |
| 2450 | for(size_t tier = 0; tier < nd_profile.storage_tiers;tier++) { |
| 2451 | STORAGE_ENGINE *eng = localhost->db[tier].eng; |
| 2452 | if (!eng || eng->seb != STORAGE_ENGINE_BACKEND_DBENGINE) { |
| 2453 | tier_space[tier] = 0; |
| 2454 | continue; |
| 2455 | } |
| 2456 | uint64_t tier_disk_space = multidb_ctx[tier]->config.max_disk_space ? |
| 2457 | multidb_ctx[tier]->config.max_disk_space : |
| 2458 | rrdeng_get_directory_free_bytes_space(multidb_ctx[tier]); |
| 2459 | total_diskspace += tier_disk_space; |
| 2460 | tier_space[tier] = tier_disk_space; |
| 2461 | } |
| 2462 | |
| 2463 | if (total_diskspace) { |
| 2464 | for (size_t tier = 0; tier < nd_profile.storage_tiers; tier++) { |
| 2465 | multidb_ctx[tier]->config.disk_percentage = (100 * tier_space[tier] / total_diskspace); |
| 2466 | } |
| 2467 | } |
| 2468 | } |
| 2469 | |
| 2470 | #define NOT_DELETING_FILES(ctx) \ |
| 2471 | (!__atomic_load_n(&(ctx)->atomic.now_deleting_files, __ATOMIC_RELAXED)) |
| 2472 | |
| 2473 | #define NOT_INDEXING_FILES(ctx) \ |
| 2474 | (!__atomic_load_n(&(ctx)->atomic.migration_to_v2_running, __ATOMIC_RELAXED)) |
| 2475 | |
| 2476 | void dbengine_event_loop(void* arg) { |
| 2477 | sanity_check(); |
| 2478 | uv_thread_set_name_np("DBENGINE"); |
| 2479 | service_register(NULL, NULL, NULL); |
| 2480 | |
| 2481 | worker_register("DBENGINE"); |
| 2482 | |
| 2483 | // opcode jobs |
| 2484 | worker_register_job_name(RRDENG_OPCODE_NOOP, "noop"); |
| 2485 | |
| 2486 | worker_register_job_name(RRDENG_OPCODE_QUERY, "query"); |
| 2487 | worker_register_job_name(RRDENG_OPCODE_EXTENT_WRITE, "extent write"); |
| 2488 | worker_register_job_name(RRDENG_OPCODE_EXTENT_READ, "extent read"); |
| 2489 | worker_register_job_name(RRDENG_OPCODE_DATABASE_ROTATE, "db rotate"); |
| 2490 | worker_register_job_name(RRDENG_OPCODE_JOURNAL_INDEX, "journal index"); |
| 2491 | worker_register_job_name(RRDENG_OPCODE_FLUSH_MAIN, "flush init"); |
| 2492 | worker_register_job_name(RRDENG_OPCODE_EVICT_MAIN, "evict init"); |
| 2493 | worker_register_job_name(RRDENG_OPCODE_CTX_SHUTDOWN, "ctx shutdown"); |
| 2494 | worker_register_job_name(RRDENG_OPCODE_CTX_FLUSH_DIRTY, "ctx flush dirty"); |
| 2495 | worker_register_job_name(RRDENG_OPCODE_CTX_FLUSH_HOT_DIRTY, "ctx flush all"); |
| 2496 | worker_register_job_name(RRDENG_OPCODE_CTX_QUIESCE, "ctx quiesce"); |
| 2497 | worker_register_job_name(RRDENG_OPCODE_SHUTDOWN_EVLOOP, "dbengine shutdown"); |
| 2498 | worker_register_job_name(RRDENG_OPCODE_PARALLEL_WEIGHT, "parallel weight"); |
| 2499 | worker_register_job_name(RRDENG_OPCODE_MRG_LOAD, "mrg tier load"); |
| 2500 | |
| 2501 | |
| 2502 | worker_register_job_name(RRDENG_OPCODE_MAX, "get opcode"); |
| 2503 | |
| 2504 | worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_QUERY, "query cb"); |
| 2505 | worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_EXTENT_WRITE, "extent write cb"); |
| 2506 | worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_EXTENT_READ, "extent read cb"); |
| 2507 | worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_DATABASE_ROTATE, "db rotate cb"); |
| 2508 | worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_JOURNAL_INDEX, "journal index cb"); |
| 2509 | worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_FLUSH_MAIN, "flush init cb"); |
| 2510 | worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_EVICT_MAIN, "evict init cb"); |
| 2511 | worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_CTX_SHUTDOWN, "ctx shutdown cb"); |
| 2512 | worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_CTX_FLUSH_DIRTY, "ctx flush dirty cb"); |
| 2513 | worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_CTX_QUIESCE, "ctx quiesce cb"); |
| 2514 | worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_PARALLEL_WEIGHT, "parallel weight cb"); |
| 2515 | worker_register_job_name(RRDENG_OPCODE_MAX + RRDENG_OPCODE_MRG_LOAD, "mrg tier load cb"); |
| 2516 | |
| 2517 | // special jobs |
| 2518 | worker_register_job_name(RRDENG_RETENTION_TIMER_CB, "retention timer"); |
| 2519 | worker_register_job_name(RRDENG_TIMER_CB, "timer"); |
| 2520 | |
| 2521 | worker_register_job_custom_metric(RRDENG_OPCODES_WAITING, "opcodes waiting", "opcodes", WORKER_METRIC_ABSOLUTE); |
| 2522 | worker_register_job_custom_metric(RRDENG_WORKS_DISPATCHED, "works dispatched", "works", WORKER_METRIC_ABSOLUTE); |
| 2523 | worker_register_job_custom_metric(RRDENG_WORKS_EXECUTING, "works executing", "works", WORKER_METRIC_ABSOLUTE); |
| 2524 | |
| 2525 | struct rrdeng_main *main = arg; |
| 2526 | enum rrdeng_opcode opcode; |
| 2527 | struct rrdeng_cmd cmd; |
| 2528 | main->tid = gettid_cached(); |
| 2529 | |
| 2530 | fatal_assert(0 == uv_timer_start(&main->timer, timer_per_sec_cb, TIMER_PERIOD_MS, TIMER_PERIOD_MS)); |
| 2531 | fatal_assert(0 == uv_timer_start(&main->retention_timer, retention_timer_cb, TIMER_PERIOD_MS * 60, TIMER_PERIOD_MS * 60)); |
| 2532 | |
| 2533 | bool shutdown = false; |
| 2534 | size_t cpus = netdata_conf_cpus(); |
| 2535 | uv_sem_t sem; |
| 2536 | uv_sem_init(&sem, (unsigned int) cpus); |
| 2537 | struct mrg_load_thread *mlt = callocz(cpus, sizeof(*mlt)); |
| 2538 | for (size_t i = 0; i < cpus; i++) { |
| 2539 | mlt[i].sem = &sem; |
| 2540 | } |
| 2541 | |
| 2542 | #if defined(OS_WINDOWS) |
| 2543 | last_async_callback = uv_hrtime(); |
| 2544 | #endif |
| 2545 | |
| 2546 | while (likely(!shutdown)) { |
| 2547 | worker_is_idle(); |
| 2548 | uv_run(&main->loop, UV_RUN_ONCE); |
| 2549 | |
| 2550 | do { |
| 2551 | worker_is_busy(RRDENG_OPCODE_MAX); |
| 2552 | cmd = rrdeng_deq_cmd(RRDENG_OPCODE_NOOP); |
| 2553 | opcode = cmd.opcode; |
| 2554 | |
| 2555 | worker_is_busy(opcode); |
| 2556 | |
| 2557 | switch (opcode) { |
| 2558 | case RRDENG_OPCODE_MRG_LOAD: |
| 2559 | work_dispatch(NULL, cmd.data, cmd.completion, cmd.opcode, tier_mrg_load, NULL); |
| 2560 | break; |
| 2561 | |
| 2562 | case RRDENG_OPCODE_PARALLEL_WEIGHT:; |
| 2563 | |
| 2564 | work_dispatch(NULL, cmd.data, cmd.completion, cmd.opcode, weights_worker, after_weights_worker); |
| 2565 | break; |
| 2566 | |
| 2567 | case RRDENG_OPCODE_EXTENT_READ: |
| 2568 | worker_dispatch_extent_read(cmd, false); |
| 2569 | break; |
| 2570 | |
| 2571 | case RRDENG_OPCODE_QUERY:; |
| 2572 | #if defined(OS_WINDOWS) |
| 2573 | static int max_timeout_count = 0; |
| 2574 | if (uv_hrtime() - last_async_callback > 1000UL * NSEC_PER_MSEC) { |
| 2575 | if (++max_timeout_count > 30) { |
| 2576 | netdata_log_error("DBENGINE: async callback timeout detected, re-initializing the async handle"); |
| 2577 | __atomic_store_n(&main->async_ready, false, __ATOMIC_RELEASE); |
| 2578 | uv_close((uv_handle_t *)&main->async, async_closed_cb); |
| 2579 | max_timeout_count = 0; |
| 2580 | } else |
| 2581 | netdata_log_error("DBENGINE: async callback timeout detected count = %d", max_timeout_count); |
| 2582 | } |
| 2583 | #endif |
| 2584 | worker_dispatch_query_prep(cmd, false); |
| 2585 | break; |
| 2586 | |
| 2587 | case RRDENG_OPCODE_EXTENT_WRITE: { |
| 2588 | struct rrdengine_instance *ctx = cmd.ctx; |
| 2589 | struct page_descr_with_data *base = cmd.data; |
| 2590 | struct completion *completion = cmd.completion; // optional |
| 2591 | work_dispatch(ctx, base, completion, opcode, extent_write_tp_worker, after_extent_write); |
| 2592 | break; |
| 2593 | } |
| 2594 | |
| 2595 | case RRDENG_OPCODE_FLUSH_MAIN: { |
| 2596 | if(rrdeng_main.flushes_running < pgc_max_flushers()) { |
| 2597 | rrdeng_main.flushes_running++; |
| 2598 | work_dispatch(NULL, NULL, NULL, opcode, cache_flush_tp_worker, after_do_cache_flush); |
| 2599 | } |
| 2600 | break; |
| 2601 | } |
| 2602 | |
| 2603 | case RRDENG_OPCODE_EVICT_MAIN: { |
| 2604 | if(rrdeng_main.evict_main_running < pgc_max_evictors()) { |
| 2605 | rrdeng_main.evict_main_running++; |
| 2606 | work_dispatch(NULL, NULL, NULL, opcode, cache_evict_main_tp_worker, after_do_main_cache_evict); |
| 2607 | } |
| 2608 | break; |
| 2609 | } |
| 2610 | |
| 2611 | case RRDENG_OPCODE_EVICT_OPEN: { |
| 2612 | if(rrdeng_main.evict_open_running < pgc_max_evictors()) { |
| 2613 | rrdeng_main.evict_open_running++; |
| 2614 | work_dispatch(NULL, NULL, NULL, opcode, cache_evict_open_tp_worker, after_do_open_cache_evict); |
| 2615 | } |
| 2616 | break; |
| 2617 | } |
| 2618 | |
| 2619 | case RRDENG_OPCODE_EVICT_EXTENT: { |
| 2620 | if(rrdeng_main.evict_extent_running < pgc_max_evictors()) { |
| 2621 | rrdeng_main.evict_extent_running++; |
| 2622 | work_dispatch(NULL, NULL, NULL, opcode, cache_evict_extent_tp_worker, after_do_extent_cache_evict); |
| 2623 | } |
| 2624 | break; |
| 2625 | } |
| 2626 | |
| 2627 | case RRDENG_OPCODE_CLEANUP: { |
| 2628 | if(!rrdeng_main.cleanup_running) { |
| 2629 | rrdeng_main.cleanup_running++; |
| 2630 | work_dispatch(NULL, NULL, NULL, opcode, cleanup_tp_worker, after_cleanup); |
| 2631 | } |
| 2632 | break; |
| 2633 | } |
| 2634 | |
| 2635 | case RRDENG_OPCODE_JOURNAL_INDEX: { |
| 2636 | struct rrdengine_instance *ctx = cmd.ctx; |
| 2637 | struct rrdengine_datafile *datafile = cmd.data; |
| 2638 | // We no longer have an indexing command pending |
| 2639 | ctx->datafiles.pending_index = false; |
| 2640 | if (NOT_INDEXING_FILES(ctx) && ctx_is_available_for_queries(ctx)) { |
| 2641 | __atomic_store_n(&ctx->atomic.migration_to_v2_running, true, __ATOMIC_RELAXED); |
| 2642 | __atomic_store_n(&ctx->atomic.needs_indexing, false, __ATOMIC_RELAXED); |
| 2643 | work_dispatch(ctx, datafile, NULL, opcode, journal_v2_indexing_tp_worker, after_journal_v2_indexing); |
| 2644 | } |
| 2645 | break; |
| 2646 | } |
| 2647 | |
| 2648 | case RRDENG_OPCODE_DATABASE_ROTATE: { |
| 2649 | struct rrdengine_instance *ctx = cmd.ctx; |
| 2650 | ctx->datafiles.pending_rotate = false; |
| 2651 | if (NOT_DELETING_FILES(ctx) && datafile_count(ctx, false) > 2 && |
| 2652 | rrdeng_ctx_tier_cap_exceeded(ctx)) { |
| 2653 | __atomic_store_n(&ctx->atomic.now_deleting_files, true, __ATOMIC_RELAXED); |
| 2654 | work_dispatch(ctx, NULL, NULL, opcode, database_rotate_tp_worker, after_database_rotate); |
| 2655 | } |
| 2656 | break; |
| 2657 | } |
| 2658 | |
| 2659 | case RRDENG_OPCODE_CTX_POPULATE_MRG: { |
| 2660 | struct rrdengine_instance *ctx = cmd.ctx; |
| 2661 | struct completion *completion = cmd.completion; |
| 2662 | work_dispatch(ctx, mlt, completion, opcode, populate_mrg_tp_worker, after_populate_mrg); |
| 2663 | break; |
| 2664 | } |
| 2665 | |
| 2666 | case RRDENG_OPCODE_CTX_FLUSH_DIRTY: { |
| 2667 | struct rrdengine_instance *ctx = cmd.ctx; |
| 2668 | work_dispatch(ctx, NULL, NULL, opcode, |
| 2669 | flush_dirty_pages_of_section_tp_worker, |
| 2670 | after_flush_dirty_pages_of_section); |
| 2671 | break; |
| 2672 | } |
| 2673 | |
| 2674 | case RRDENG_OPCODE_CTX_FLUSH_HOT_DIRTY: { |
| 2675 | struct rrdengine_instance *ctx = cmd.ctx; |
| 2676 | work_dispatch(ctx, NULL, NULL, opcode, |
| 2677 | flush_all_hot_and_dirty_pages_of_section_tp_worker, |
| 2678 | after_flush_all_hot_and_dirty_pages_of_section); |
| 2679 | break; |
| 2680 | } |
| 2681 | |
| 2682 | case RRDENG_OPCODE_CTX_QUIESCE: { |
| 2683 | // a ctx will shutdown shortly |
| 2684 | struct rrdengine_instance *ctx = cmd.ctx; |
| 2685 | nd_log_daemon(NDLP_INFO, "DBENGINE: Tier %d is shutting down — query processing disabled", ctx->config.tier); |
| 2686 | __atomic_store_n(&ctx->quiesce.enabled, true, __ATOMIC_RELEASE); |
| 2687 | break; |
| 2688 | } |
| 2689 | |
| 2690 | case RRDENG_OPCODE_CTX_SHUTDOWN: { |
| 2691 | // a ctx is shutting down |
| 2692 | struct rrdengine_instance *ctx = cmd.ctx; |
| 2693 | struct completion *completion = cmd.completion; |
| 2694 | work_dispatch(ctx, NULL, completion, opcode, ctx_shutdown_tp_worker, after_ctx_shutdown); |
| 2695 | break; |
| 2696 | } |
| 2697 | |
| 2698 | case RRDENG_OPCODE_SHUTDOWN_EVLOOP: { |
| 2699 | uv_close((uv_handle_t *)&main->async, NULL); |
| 2700 | |
| 2701 | (void) uv_timer_stop(&main->timer); |
| 2702 | uv_close((uv_handle_t *)&main->timer, NULL); |
| 2703 | |
| 2704 | (void) uv_timer_stop(&main->retention_timer); |
| 2705 | uv_close((uv_handle_t *)&main->retention_timer, NULL); |
| 2706 | shutdown = true; |
| 2707 | break; |
| 2708 | } |
| 2709 | |
| 2710 | case RRDENG_OPCODE_NOOP: { |
| 2711 | /* the command queue was empty, do nothing */ |
| 2712 | break; |
| 2713 | } |
| 2714 | |
| 2715 | // not opcodes |
| 2716 | case RRDENG_OPCODE_MAX: |
| 2717 | default: { |
| 2718 | internal_fatal(true, "DBENGINE: unknown opcode"); |
| 2719 | break; |
| 2720 | } |
| 2721 | } |
| 2722 | if (opcode != RRDENG_OPCODE_NOOP) |
| 2723 | uv_run(&main->loop, UV_RUN_NOWAIT); |
| 2724 | |
| 2725 | } while (opcode != RRDENG_OPCODE_NOOP); |
| 2726 | } |
| 2727 | freez(mlt); |
| 2728 | uv_sem_destroy(&sem); |
| 2729 | |
| 2730 | nd_log(NDLS_DAEMON, NDLP_DEBUG, "Shutting down dbengine thread"); |
| 2731 | (void) uv_loop_close(&main->loop); |
| 2732 | worker_unregister(); |
| 2733 | } |
| 2734 | |
| 2735 | void dbengine_shutdown() |
| 2736 | { |
| 2737 | rrdeng_enq_cmd(NULL, RRDENG_OPCODE_SHUTDOWN_EVLOOP, NULL, NULL, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL); |
| 2738 | |
| 2739 | int rc = nd_thread_join(rrdeng_main.thread); |
| 2740 | if (rc) |
| 2741 | nd_log_daemon(NDLP_ERR, "DBENGINE: Failed to join thread, error %s", uv_err_name(rc)); |
| 2742 | else |
| 2743 | nd_log_daemon(NDLP_INFO, "DBENGINE: thread shutdown completed"); |
| 2744 | } |