master
c 1,776 lines 67.3 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2 #include "rrdengine.h"
3
4 // the default value is set in ND_PROFILE, not here
5 time_t dbengine_journal_v2_unmount_time = 120;
6
7 /* Careful to always call this before creating a new journal file */
8 int journalfile_v1_extent_write(struct rrdengine_instance *ctx, struct rrdengine_datafile *datafile, WAL *wal)
9 {
10 uv_fs_t request;
11 struct rrdengine_journalfile *journalfile = datafile->journalfile;
12 uv_buf_t iov;
13
14 if (wal->size < wal->buf_size) {
15 /* simulate an empty transaction to skip the rest of the block */
16 *(uint8_t *) (wal->buf + wal->size) = STORE_PADDING;
17 }
18
19 uint64_t journalfile_position;
20 spinlock_lock(&journalfile->unsafe.spinlock);
21 journalfile_position = journalfile->unsafe.pos;
22 journalfile->unsafe.pos += wal->buf_size;
23 spinlock_unlock(&journalfile->unsafe.spinlock);
24
25 iov = uv_buf_init(wal->buf, wal->buf_size);
26
27 int retries = 10;
28 int ret = -1;
29 while (ret < 0 && --retries) {
30 ret = uv_fs_write(NULL, &request, journalfile->file, &iov, 1, (int64_t)journalfile_position, NULL);
31 uv_fs_req_cleanup(&request);
32 if (ret < 0) {
33 if (ret == -ENOSPC || ret == -EBADF || ret == -EACCES || ret == -EROFS || ret == -EINVAL)
34 break;
35 sleep_usec(300 * USEC_PER_MS);
36 }
37 }
38
39 if (unlikely(ret < 0)) {
40 ctx_io_error(ctx);
41 goto done;
42 }
43
44 ctx_current_disk_space_increase(ctx, wal->buf_size);
45 ctx_io_write_op_bytes(ctx, wal->buf_size);
46
47 done:
48 wal_release(wal);
49 worker_is_idle();
50 return ret;
51 }
52
53 void journalfile_v2_generate_path(struct rrdengine_datafile *datafile, char *str, size_t maxlen)
54 {
55 (void) snprintfz(str, maxlen, "%s/" WALFILE_PREFIX RRDENG_FILE_NUMBER_PRINT_TMPL WALFILE_EXTENSION_V2,
56 datafile_ctx(datafile)->config.dbfiles_path, datafile->tier, datafile->fileno);
57 }
58
59 void journalfile_v1_generate_path(struct rrdengine_datafile *datafile, char *str, size_t maxlen)
60 {
61 (void) snprintfz(str, maxlen - 1, "%s/" WALFILE_PREFIX RRDENG_FILE_NUMBER_PRINT_TMPL WALFILE_EXTENSION,
62 datafile_ctx(datafile)->config.dbfiles_path, datafile->tier, datafile->fileno);
63 }
64
65 // ----------------------------------------------------------------------------
66
67 ALWAYS_INLINE struct rrdengine_datafile *njfv2idx_find_and_acquire_j2_header(NJFV2IDX_FIND_STATE *s) {
68 if (unlikely(!s)) return NULL;
69
70 struct rrdengine_datafile *datafile = NULL;
71
72 rw_spinlock_read_lock(&s->ctx->njfv2idx.spinlock);
73
74 Pvoid_t *PValue = NULL;
75
76 if(unlikely(!s->init)) {
77 s->init = true;
78 s->last = s->wanted_start_time_s;
79
80 PValue = JudyLPrev(s->ctx->njfv2idx.JudyL, &s->last, PJE0);
81 if (unlikely(PValue == PJERR))
82 fatal("DBENGINE: NJFV2IDX corrupted judy array");
83
84 if(!PValue) {
85 s->last = 0;
86 PValue = JudyLFirst(s->ctx->njfv2idx.JudyL, &s->last, PJE0);
87 if (unlikely(PValue == PJERR))
88 fatal("DBENGINE: NJFV2IDX corrupted judy array");
89
90 if(!PValue)
91 s->last = s->wanted_start_time_s;
92 }
93 }
94
95 while(1) {
96 if (likely(!PValue)) {
97 PValue = JudyLNext(s->ctx->njfv2idx.JudyL, &s->last, PJE0);
98 if (unlikely(PValue == PJERR))
99 fatal("DBENGINE: NJFV2IDX corrupted judy array");
100
101 if(!PValue) {
102 // cannot find anything after that point
103 datafile = NULL;
104 break;
105 }
106 }
107
108 datafile = *PValue;
109
110 if (!datafile || !datafile_acquire(datafile, DATAFILE_ACQUIRE_PAGE_DETAILS)) {
111 datafile = NULL;
112 PValue = NULL;
113 continue;
114 }
115
116 struct rrdengine_journalfile *journalfile = datafile->journalfile;
117
118 if (!journalfile) {
119 datafile_release(datafile, DATAFILE_ACQUIRE_PAGE_DETAILS);
120 datafile = NULL;
121 PValue = NULL;
122 continue;
123 }
124
125 TIME_RANGE_COMPARE rc = is_page_in_time_range(journalfile->v2.first_time_s,
126 journalfile->v2.last_time_s,
127 s->wanted_start_time_s,
128 s->wanted_end_time_s);
129
130 if(rc == PAGE_IS_IN_RANGE) {
131 s->j2_header_acquired = journalfile_v2_data_acquire(journalfile, NULL,
132 s->wanted_start_time_s,
133 s->wanted_end_time_s);
134 if(s->j2_header_acquired) {
135 // this is good to return
136 break;
137 }
138
139 datafile_release(datafile, DATAFILE_ACQUIRE_PAGE_DETAILS);
140 datafile = NULL;
141 PValue = NULL;
142 continue;
143 }
144 else if(rc == PAGE_IS_IN_THE_PAST) {
145 // continue to get the next
146 datafile_release(datafile, DATAFILE_ACQUIRE_PAGE_DETAILS);
147 datafile = NULL;
148 PValue = NULL;
149 continue;
150 }
151 else /* PAGE_IS_IN_THE_FUTURE */ {
152 // we finished - no more datafiles
153 datafile_release(datafile, DATAFILE_ACQUIRE_PAGE_DETAILS);
154 datafile = NULL;
155 PValue = NULL;
156 break;
157 }
158 }
159
160 if(!datafile)
161 s->j2_header_acquired = NULL;
162
163 rw_spinlock_read_unlock(&s->ctx->njfv2idx.spinlock);
164
165 return datafile;
166 }
167
168 static void njfv2idx_add(struct rrdengine_datafile *datafile) {
169 if(unlikely(!datafile))
170 fatal("DBENGINE: NJFV2IDX trying to index a journal file with no datafile");
171
172 struct rrdengine_instance *ctx = datafile_ctx(datafile);
173
174 internal_fatal(datafile->journalfile->v2.last_time_s <= 0, "DBENGINE: NJFV2IDX trying to index a journal file with invalid first_time_s");
175
176 rw_spinlock_write_lock(&ctx->njfv2idx.spinlock);
177 datafile->journalfile->njfv2idx.indexed_as = datafile->journalfile->v2.last_time_s;
178
179 do {
180 internal_fatal(datafile->journalfile->njfv2idx.indexed_as <= 0, "DBENGINE: NJFV2IDX journalfile is already indexed");
181
182 Pvoid_t *PValue = JudyLIns(&ctx->njfv2idx.JudyL, datafile->journalfile->njfv2idx.indexed_as, PJE0);
183 if (!PValue || PValue == PJERR)
184 fatal("DBENGINE: NJFV2IDX corrupted judy array");
185
186 if (unlikely(*PValue)) {
187 // already there
188 datafile->journalfile->njfv2idx.indexed_as++;
189 }
190 else {
191 *PValue = datafile;
192 break;
193 }
194 } while(1);
195
196 rw_spinlock_write_unlock(&ctx->njfv2idx.spinlock);
197 }
198
199 static void njfv2idx_remove(struct rrdengine_datafile *datafile) {
200 internal_fatal(!datafile->journalfile->njfv2idx.indexed_as, "DBENGINE: NJFV2IDX journalfile to remove is not indexed");
201
202 struct rrdengine_instance *ctx = datafile_ctx(datafile);
203 rw_spinlock_write_lock(&ctx->njfv2idx.spinlock);
204
205 int rc = JudyLDel(&ctx->njfv2idx.JudyL, datafile->journalfile->njfv2idx.indexed_as, PJE0);
206 (void)rc;
207 internal_fatal(!rc, "DBENGINE: NJFV2IDX cannot remove entry");
208
209 datafile->journalfile->njfv2idx.indexed_as = 0;
210
211 rw_spinlock_write_unlock(&ctx->njfv2idx.spinlock);
212 }
213
214 // ----------------------------------------------------------------------------
215
216 static struct journal_v2_header *journalfile_v2_mounted_data_get(struct rrdengine_journalfile *journalfile, size_t *data_size) {
217 struct journal_v2_header *j2_header = NULL;
218
219 spinlock_lock(&journalfile->data_spinlock);
220
221 if(!journalfile->mmap.data) {
222 journalfile->mmap.data = nd_mmap(NULL, journalfile->mmap.size, PROT_READ, MAP_SHARED, journalfile->mmap.fd, 0);
223 if (journalfile->mmap.data == MAP_FAILED) {
224 internal_fatal(true, "DBENGINE: failed to re-mmap() journal file v2");
225 close(journalfile->mmap.fd);
226 journalfile->mmap.fd = -1;
227 journalfile->mmap.data = NULL;
228 journalfile->mmap.size = 0;
229
230 journalfile->v2.flags &= ~(JOURNALFILE_FLAG_IS_AVAILABLE | JOURNALFILE_FLAG_IS_MOUNTED);
231
232 ctx_fs_error(datafile_ctx(journalfile->datafile));
233 }
234 else {
235 __atomic_add_fetch(&rrdeng_cache_efficiency_stats.journal_v2_mapped, 1, __ATOMIC_RELAXED);
236
237 madvise_dontfork(journalfile->mmap.data, journalfile->mmap.size);
238 madvise_dontdump(journalfile->mmap.data, journalfile->mmap.size);
239 // madvise_dontneed(journalfile->mmap.data, journalfile->mmap.size);
240
241 journalfile->v2.flags |= JOURNALFILE_FLAG_IS_AVAILABLE | JOURNALFILE_FLAG_IS_MOUNTED;
242 JOURNALFILE_FLAGS flags = journalfile->v2.flags;
243
244 if(flags & JOURNALFILE_FLAG_MOUNTED_FOR_RETENTION) {
245 // sequential access pattern during MRG population
246 madvise_sequential(journalfile->mmap.data, journalfile->v2.size_of_directory);
247 madvise_willneed(journalfile->mmap.data, journalfile->v2.size_of_directory);
248 }
249 else {
250 madvise_random(journalfile->mmap.data, journalfile->mmap.size);
251 }
252 }
253 }
254
255 if(journalfile->mmap.data) {
256 j2_header = journalfile->mmap.data;
257
258 if (data_size)
259 *data_size = journalfile->mmap.size;
260 }
261
262 spinlock_unlock(&journalfile->data_spinlock);
263
264 return j2_header;
265 }
266
267 static bool journalfile_v2_mounted_data_unmount(struct rrdengine_journalfile *journalfile, bool have_locks, bool wait) {
268 bool unmounted = false;
269
270 if(!have_locks) {
271 if(!wait) {
272 if (!spinlock_trylock(&journalfile->data_spinlock))
273 return false;
274 }
275 else
276 spinlock_lock(&journalfile->data_spinlock);
277 }
278
279 if(!journalfile->v2.refcount) {
280 if(journalfile->mmap.data) {
281 if (nd_munmap(journalfile->mmap.data, journalfile->mmap.size)) {
282 char path[RRDENG_PATH_MAX];
283 journalfile_v2_generate_path(journalfile->datafile, path, sizeof(path));
284 netdata_log_error("DBENGINE: failed to unmap index file \"%s\"", path);
285 internal_fatal(true, "DBENGINE: failed to unmap file \"%s\"", path);
286 ctx_fs_error(datafile_ctx(journalfile->datafile));
287 }
288 else {
289 __atomic_add_fetch(&rrdeng_cache_efficiency_stats.journal_v2_unmapped, 1, __ATOMIC_RELAXED);
290 journalfile->mmap.data = NULL;
291 journalfile->v2.flags &= ~JOURNALFILE_FLAG_IS_MOUNTED;
292 }
293 }
294
295 unmounted = true;
296 }
297
298 if(!have_locks) {
299 spinlock_unlock(&journalfile->data_spinlock);
300 }
301
302 return unmounted;
303 }
304
305 void journalfile_v2_data_unmount_cleanup(time_t now_s) {
306 // DO NOT WAIT ON ANY LOCK!!!
307
308 for(size_t tier = 0; tier < (size_t)nd_profile.storage_tiers;tier++) {
309 struct rrdengine_instance *ctx = multidb_ctx[tier];
310 if(!ctx) continue;
311
312 struct rrdengine_datafile *datafile;
313 if(netdata_rwlock_tryrdlock(&ctx->datafiles.rwlock) != 0)
314 continue;
315
316 bool first_then_next = true;
317 Pvoid_t *Pvalue = NULL;
318 Word_t Index = 0;
319
320 while((Pvalue = JudyLFirstThenNext(ctx->datafiles.JudyL, &Index, &first_then_next))) {
321
322 datafile = *Pvalue;
323 if (!datafile)
324 continue;
325
326 struct rrdengine_journalfile *journalfile = datafile->journalfile;
327
328 if(!spinlock_trylock(&journalfile->data_spinlock))
329 continue;
330
331 bool unmount = false;
332 if (!journalfile->v2.refcount && (journalfile->v2.flags & JOURNALFILE_FLAG_IS_MOUNTED)) {
333 // this journal has no references and it is mounted
334
335 if (!journalfile->v2.not_needed_since_s)
336 journalfile->v2.not_needed_since_s = now_s;
337
338 else if (
339 dbengine_journal_v2_unmount_time && now_s - journalfile->v2.not_needed_since_s >= dbengine_journal_v2_unmount_time)
340 // enough time has passed since we last needed this journal
341 unmount = true;
342 }
343 spinlock_unlock(&journalfile->data_spinlock);
344
345 if (unmount)
346 journalfile_v2_mounted_data_unmount(journalfile, false, false);
347 }
348 netdata_rwlock_rdunlock(&ctx->datafiles.rwlock);
349 }
350 }
351
352 ALWAYS_INLINE struct journal_v2_header *journalfile_v2_data_acquire_with_hint(struct rrdengine_journalfile *journalfile,
353 size_t *data_size, time_t wanted_first_time_s, time_t wanted_last_time_s, JOURNALFILE_V2_ACCESS_HINT hint)
354 {
355 spinlock_lock(&journalfile->data_spinlock);
356
357 bool has_data = (journalfile->v2.flags & JOURNALFILE_FLAG_IS_AVAILABLE);
358 bool is_mounted = (journalfile->v2.flags & JOURNALFILE_FLAG_IS_MOUNTED);
359 bool do_we_need_it = false;
360
361 if(has_data) {
362 if (!wanted_first_time_s || !wanted_last_time_s ||
363 is_page_in_time_range(journalfile->v2.first_time_s, journalfile->v2.last_time_s,
364 wanted_first_time_s, wanted_last_time_s) == PAGE_IS_IN_RANGE) {
365 bool was_sequential = (journalfile->v2.flags & JOURNALFILE_FLAG_MOUNTED_FOR_RETENTION);
366
367 journalfile->v2.refcount++;
368
369 do_we_need_it = true;
370
371 if(hint == JOURNALFILE_V2_ACCESS_AUTO)
372 hint = (!wanted_first_time_s && !wanted_last_time_s) ?
373 JOURNALFILE_V2_ACCESS_SEQUENTIAL_DIRECTORY :
374 JOURNALFILE_V2_ACCESS_RANDOM;
375
376 bool want_sequential = (hint == JOURNALFILE_V2_ACCESS_SEQUENTIAL_DIRECTORY);
377 if (want_sequential)
378 journalfile->v2.flags |= JOURNALFILE_FLAG_MOUNTED_FOR_RETENTION;
379 else
380 journalfile->v2.flags &= ~JOURNALFILE_FLAG_MOUNTED_FOR_RETENTION;
381
382 if (is_mounted && journalfile->mmap.data) {
383 if (!was_sequential && want_sequential) {
384 madvise_sequential(journalfile->mmap.data, journalfile->v2.size_of_directory);
385 madvise_willneed(journalfile->mmap.data, journalfile->v2.size_of_directory);
386 }
387 else if (was_sequential && !want_sequential)
388 madvise_random(journalfile->mmap.data, journalfile->mmap.size);
389 }
390
391 }
392 }
393 spinlock_unlock(&journalfile->data_spinlock);
394
395 if(do_we_need_it)
396 return journalfile_v2_mounted_data_get(journalfile, data_size);
397
398 return NULL;
399 }
400
401 ALWAYS_INLINE struct journal_v2_header *journalfile_v2_data_acquire(struct rrdengine_journalfile *journalfile, size_t *data_size, time_t wanted_first_time_s, time_t wanted_last_time_s) {
402 return journalfile_v2_data_acquire_with_hint(journalfile, data_size, wanted_first_time_s, wanted_last_time_s, JOURNALFILE_V2_ACCESS_AUTO);
403 }
404
405 ALWAYS_INLINE void journalfile_v2_data_release(struct rrdengine_journalfile *journalfile) {
406 spinlock_lock(&journalfile->data_spinlock);
407
408 internal_fatal(!journalfile->mmap.data, "trying to release a journalfile without data");
409 internal_fatal(journalfile->v2.refcount < 1, "trying to release a non-acquired journalfile");
410
411 bool unmount = false;
412
413 journalfile->v2.refcount--;
414
415 if(journalfile->v2.refcount == 0) {
416 journalfile->v2.not_needed_since_s = 0;
417
418 if(journalfile->v2.flags & JOURNALFILE_FLAG_MOUNTED_FOR_RETENTION)
419 unmount = true;
420 }
421 spinlock_unlock(&journalfile->data_spinlock);
422
423 if(unmount)
424 journalfile_v2_mounted_data_unmount(journalfile, false, true);
425 }
426
427 bool journalfile_v2_data_available(struct rrdengine_journalfile *journalfile) {
428
429 spinlock_lock(&journalfile->data_spinlock);
430 bool has_data = (journalfile->v2.flags & JOURNALFILE_FLAG_IS_AVAILABLE);
431 spinlock_unlock(&journalfile->data_spinlock);
432
433 return has_data;
434 }
435
436 size_t journalfile_v2_data_size_get(struct rrdengine_journalfile *journalfile) {
437
438 spinlock_lock(&journalfile->data_spinlock);
439 size_t data_size = journalfile->mmap.size;
440 spinlock_unlock(&journalfile->data_spinlock);
441
442 return data_size;
443 }
444
445 void journalfile_v2_data_set(struct rrdengine_journalfile *journalfile, int fd, void *journal_data, uint32_t journal_data_size) {
446 if(unlikely(!journalfile))
447 fatal("DBENGINE: JOURNALFILE: trying to set journal data without a journalfile");
448
449 if(unlikely(!journalfile->datafile))
450 fatal("DBENGINE: JOURNALFILE: trying to set journal data without a datafile");
451
452 spinlock_lock(&journalfile->data_spinlock);
453
454 internal_fatal(journalfile->mmap.fd != -1, "DBENGINE JOURNALFILE: trying to re-set journal fd");
455 internal_fatal(journalfile->mmap.data, "DBENGINE JOURNALFILE: trying to re-set journal_data");
456 internal_fatal(journalfile->v2.refcount, "DBENGINE JOURNALFILE: trying to re-set journal_data of referenced journalfile");
457
458 journalfile->mmap.fd = fd;
459 journalfile->mmap.data = journal_data;
460 journalfile->mmap.size = journal_data_size;
461 journalfile->v2.not_needed_since_s = now_monotonic_sec();
462 journalfile->v2.flags |= JOURNALFILE_FLAG_IS_AVAILABLE | JOURNALFILE_FLAG_IS_MOUNTED;
463
464 struct journal_v2_header *j2_header = journalfile->mmap.data;
465 journalfile->v2.first_time_s = (time_t)(j2_header->start_time_ut / USEC_PER_SEC);
466 journalfile->v2.last_time_s = (time_t)(j2_header->end_time_ut / USEC_PER_SEC);
467 journalfile->v2.size_of_directory = j2_header->metric_offset + j2_header->metric_count * sizeof(struct journal_metric_list);
468
469 journalfile_v2_mounted_data_unmount(journalfile, true, true);
470
471 spinlock_unlock(&journalfile->data_spinlock);
472
473 njfv2idx_add(journalfile->datafile);
474 }
475
476 static void journalfile_v2_data_unmap_permanently(struct rrdengine_journalfile *journalfile) {
477 njfv2idx_remove(journalfile->datafile);
478
479 bool has_references = false;
480 char path_v2[RRDENG_PATH_MAX];
481
482 journalfile_v2_generate_path(journalfile->datafile, path_v2, sizeof(path_v2));
483
484 do {
485 if (has_references)
486 sleep_usec(10 * USEC_PER_MS);
487
488 spinlock_lock(&journalfile->data_spinlock);
489
490 if(journalfile_v2_mounted_data_unmount(journalfile, true, true)) {
491 if(journalfile->mmap.fd != -1)
492 close(journalfile->mmap.fd);
493
494 journalfile->mmap.fd = -1;
495 journalfile->mmap.data = NULL;
496 journalfile->mmap.size = 0;
497 journalfile->v2.first_time_s = 0;
498 journalfile->v2.last_time_s = 0;
499 journalfile->v2.flags = 0;
500 has_references = false;
501 }
502 else {
503 has_references = true;
504 nd_log_limit_static_global_var(journalfile_erl, 10, 0);
505 nd_log_limit(&journalfile_erl, NDLS_DAEMON, NDLP_WARNING, "DBENGINE: journalfile \"%s\" is not available for unmap", path_v2);
506 }
507
508 spinlock_unlock(&journalfile->data_spinlock);
509
510 } while(has_references);
511 }
512
513 struct rrdengine_journalfile *journalfile_alloc_and_init(struct rrdengine_datafile *datafile)
514 {
515 struct rrdengine_journalfile *journalfile = callocz(1, sizeof(struct rrdengine_journalfile));
516 journalfile->datafile = datafile;
517 spinlock_init(&journalfile->data_spinlock);
518 spinlock_init(&journalfile->unsafe.spinlock);
519 journalfile->mmap.fd = -1;
520 datafile->journalfile = journalfile;
521 return journalfile;
522 }
523
524 static int close_uv_file(struct rrdengine_datafile *datafile, uv_file file)
525 {
526 int ret;
527 char path[RRDENG_PATH_MAX];
528
529 struct rrdengine_instance *ctx = datafile_ctx(datafile);
530 journalfile_v1_generate_path(datafile, path, sizeof(path));
531
532 CLOSE_FILE(ctx, path, file, ret);
533 return ret;
534 }
535
536 int journalfile_close(struct rrdengine_journalfile *journalfile, struct rrdengine_datafile *datafile)
537 {
538 if(journalfile_v2_data_available(journalfile)) {
539 journalfile_v2_data_unmap_permanently(journalfile);
540 return 0;
541 }
542
543 return close_uv_file(datafile, journalfile->file);
544 }
545
546 int journalfile_unlink(struct rrdengine_journalfile *journalfile)
547 {
548 struct rrdengine_datafile *datafile = journalfile->datafile;
549 struct rrdengine_instance *ctx = datafile_ctx(datafile);
550 int ret;
551
552 char path[RRDENG_PATH_MAX];
553 journalfile_v1_generate_path(datafile, path, sizeof(path));
554
555 UNLINK_FILE(ctx, path, ret);
556 if (ret == 0)
557 __atomic_add_fetch(&ctx->stats.journalfile_deletions, 1, __ATOMIC_RELAXED);
558
559 return ret;
560 }
561
562 uint8_t journalfile_destroy_unsafe(struct rrdengine_journalfile *journalfile, struct rrdengine_datafile *datafile)
563 {
564 struct rrdengine_instance *ctx = datafile_ctx(datafile);
565 int ret;
566 uv_fs_t req_v2 = { 0 };
567 uv_fs_t req_v1 = { 0 };
568 char path[RRDENG_PATH_MAX];
569 char path_v2[RRDENG_PATH_MAX];
570
571 journalfile_v1_generate_path(datafile, path, sizeof(path));
572 journalfile_v2_generate_path(datafile, path_v2, sizeof(path));
573
574 if (journalfile->file)
575 (void)close_uv_file(datafile, journalfile->file);
576
577 // Wait for all references to be released and unmap before deleting files.
578 // This prevents SIGBUS when threads are still accessing the mmap'd data.
579 if(journalfile_v2_data_available(journalfile))
580 journalfile_v2_data_unmap_permanently(journalfile);
581
582 // Now safe to delete the files - no threads are accessing them
583 uint8_t deleted = 0;
584
585 ret = uv_fs_unlink(NULL, &req_v2, path_v2, NULL);
586 if (ret == 0)
587 deleted |= JOURNALFILE_DELETED_V2;
588 else if (ret != UV_ENOENT) {
589 netdata_log_error("DBENGINE: uv_fs_unlink(\"%s\"): %s", path_v2, uv_strerror(ret));
590 ctx_fs_error(ctx);
591 }
592 uv_fs_req_cleanup(&req_v2);
593
594 ret = uv_fs_unlink(NULL, &req_v1, path, NULL);
595 if (ret == 0)
596 deleted |= JOURNALFILE_DELETED_V1;
597 else if (ret != UV_ENOENT) {
598 netdata_log_error("DBENGINE: uv_fs_unlink(\"%s\"): %s", path, uv_strerror(ret));
599 ctx_fs_error(ctx);
600 }
601 uv_fs_req_cleanup(&req_v1);
602
603 __atomic_add_fetch(&ctx->stats.journalfile_deletions, __builtin_popcount(deleted), __ATOMIC_RELAXED);
604
605 return deleted;
606 }
607
608 int journalfile_create(struct rrdengine_journalfile *journalfile, struct rrdengine_datafile *datafile)
609 {
610 struct rrdengine_instance *ctx = datafile_ctx(datafile);
611 uv_fs_t req;
612 uv_file file;
613 int ret, fd;
614 struct rrdeng_jf_sb *superblock = NULL;
615 uv_buf_t iov;
616 char path[RRDENG_PATH_MAX];
617
618 journalfile_v1_generate_path(datafile, path, sizeof(path));
619 fd = open_file_for_io(path, O_CREAT | O_RDWR | O_TRUNC, &file, dbengine_use_direct_io);
620 if (fd < 0) {
621 ctx_fs_error(ctx);
622 return fd;
623 }
624 journalfile->file = file;
625
626 (void)posix_memalignz((void *)&superblock, RRDFILE_ALIGNMENT, sizeof(*superblock));
627 memset(superblock, 0, sizeof(*superblock));
628 (void) strncpy(superblock->magic_number, RRDENG_JF_MAGIC, RRDENG_MAGIC_SZ);
629 (void) strncpy(superblock->version, RRDENG_JF_VER, RRDENG_VER_SZ);
630
631 iov = uv_buf_init((void *)superblock, sizeof(*superblock));
632
633 int retries = 10;
634 ret = -1;
635 while (ret < 0 && --retries) {
636 ret = uv_fs_write(NULL, &req, file, &iov, 1, 0, NULL);
637 uv_fs_req_cleanup(&req);
638 if (ret < 0) {
639 if (ret == -ENOSPC || ret == -EBADF || ret == -EACCES || ret == -EROFS || ret == -EINVAL)
640 break;
641 sleep_usec(300 * USEC_PER_MS);
642 }
643 }
644
645 posix_memalign_freez(superblock);
646
647 if (ret < 0) {
648 journalfile_destroy_unsafe(journalfile, datafile);
649 ctx_io_error(ctx);
650 nd_log_limit_static_global_var(dbengine_erl, 10, 0);
651 nd_log_limit(&dbengine_erl, NDLS_DAEMON, NDLP_ERR, "DBENGINE: Failed to create journlfile \"%s\"", path);
652 return ret;
653 }
654
655 __atomic_add_fetch(&ctx->stats.journalfile_creations, 1, __ATOMIC_RELAXED);
656 journalfile->unsafe.pos = sizeof(*superblock);
657 ctx_io_write_op_bytes(ctx, sizeof(*superblock));
658
659 return 0;
660 }
661
662 static int journalfile_check_superblock(uv_file file)
663 {
664 int ret;
665 struct rrdeng_jf_sb *superblock = NULL;
666 uv_buf_t iov;
667 uv_fs_t req;
668
669 (void)posix_memalignz((void *)&superblock, RRDFILE_ALIGNMENT, sizeof(*superblock));
670 iov = uv_buf_init((void *)superblock, sizeof(*superblock));
671
672 ret = uv_fs_read(NULL, &req, file, &iov, 1, 0, NULL);
673 if (ret < 0) {
674 netdata_log_error("DBENGINE: uv_fs_read: %s", uv_strerror(ret));
675 uv_fs_req_cleanup(&req);
676 goto error;
677 }
678 fatal_assert(req.result >= 0);
679 uv_fs_req_cleanup(&req);
680
681
682 char jf_magic[RRDENG_MAGIC_SZ] = RRDENG_JF_MAGIC;
683 char jf_ver[RRDENG_VER_SZ] = RRDENG_JF_VER;
684 if (strncmp(superblock->magic_number, jf_magic, RRDENG_MAGIC_SZ) != 0 ||
685 strncmp(superblock->version, jf_ver, RRDENG_VER_SZ) != 0) {
686 nd_log(NDLS_DAEMON, NDLP_ERR, "DBENGINE: File has invalid superblock.");
687 ret = UV_EINVAL;
688 } else {
689 ret = 0;
690 }
691 error:
692 posix_memalign_freez(superblock);
693 return ret;
694 }
695
696 static void journalfile_restore_extent_metadata(struct rrdengine_instance *ctx, struct rrdengine_journalfile *journalfile, void *buf, unsigned max_size)
697 {
698 static bitmap64_t page_error_map = BITMAP64_INITIALIZER;
699 unsigned i, count, payload_length, descr_size;
700 struct rrdeng_jf_store_data *jf_metric_data;
701
702 jf_metric_data = buf;
703 count = jf_metric_data->number_of_pages;
704 descr_size = sizeof(*jf_metric_data->descr) * count;
705 payload_length = sizeof(*jf_metric_data) + descr_size;
706 if (payload_length > max_size || !rrdeng_valid_extent_disk_size(jf_metric_data->extent_size)) {
707 netdata_log_error("DBENGINE: corrupted transaction payload.");
708 return;
709 }
710
711 time_t now_s = max_acceptable_collected_time();
712 time_t extent_first_time_s = journalfile->v2.first_time_s ? journalfile->v2.first_time_s : LONG_MAX;
713 time_t extent_last_time_s = journalfile->v2.last_time_s ? journalfile->v2.last_time_s : 0;
714 for (i = 0; i < count ; ++i) {
715 nd_uuid_t *temp_id;
716 uint8_t page_type = jf_metric_data->descr[i].type;
717
718 if (page_type > RRDENG_PAGE_TYPE_MAX) {
719 if (!bitmap64_get(&page_error_map, page_type)) {
720 netdata_log_error("DBENGINE: unknown page type %d encountered.", page_type);
721 bitmap64_set(&page_error_map, page_type);
722 }
723 continue;
724 }
725
726 temp_id = (nd_uuid_t *)jf_metric_data->descr[i].uuid;
727 METRIC *metric = mrg_metric_get_and_acquire_by_uuid(main_mrg, temp_id, (Word_t)ctx);
728
729 struct rrdeng_extent_page_descr *descr = &jf_metric_data->descr[i];
730 VALIDATED_PAGE_DESCRIPTOR vd = validate_extent_page_descr(
731 descr, now_s,
732 (metric) ? mrg_metric_get_update_every_s(main_mrg, metric) : 0,
733 false);
734
735 if(!vd.is_valid) {
736 if(metric)
737 mrg_metric_release(main_mrg, metric);
738
739 continue;
740 }
741
742 bool update_metric_time = true;
743 if (!metric) {
744 MRG_ENTRY entry = {
745 .uuid = temp_id,
746 .section = (Word_t)ctx,
747 .first_time_s = vd.start_time_s,
748 .last_time_s = vd.end_time_s,
749 .latest_update_every_s = vd.update_every_s,
750 };
751
752 bool added;
753 metric = mrg_metric_add_and_acquire(main_mrg, entry, &added);
754 if(added)
755 update_metric_time = false;
756
757 if (vd.update_every_s) {
758 uint64_t samples = (vd.end_time_s - vd.start_time_s) / vd.update_every_s;
759 __atomic_add_fetch(&ctx->atomic.samples, samples, __ATOMIC_RELAXED);
760 }
761 }
762 Word_t metric_id = mrg_metric_id(main_mrg, metric);
763
764 if (update_metric_time)
765 mrg_metric_expand_retention(main_mrg, metric, vd.start_time_s, vd.end_time_s, vd.update_every_s);
766
767 pgc_open_add_hot_page(
768 (Word_t)ctx,
769 metric_id,
770 vd.start_time_s,
771 vd.end_time_s,
772 vd.update_every_s,
773 journalfile->datafile,
774 jf_metric_data->extent_offset,
775 jf_metric_data->extent_size);
776
777 extent_first_time_s = MIN(extent_first_time_s, vd.start_time_s);
778 extent_last_time_s = MAX(extent_last_time_s, vd.end_time_s);
779
780 mrg_metric_release(main_mrg, metric);
781 }
782
783 journalfile->v2.first_time_s = extent_first_time_s;
784 journalfile->v2.last_time_s = extent_last_time_s;
785
786 time_t old = __atomic_load_n(&ctx->atomic.first_time_s, __ATOMIC_RELAXED);;
787 do {
788 if(old <= extent_first_time_s)
789 break;
790 } while(!__atomic_compare_exchange_n(&ctx->atomic.first_time_s, &old, extent_first_time_s, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED));
791 }
792
793 /*
794 * Replays transaction by interpreting up to max_size bytes from buf.
795 * Sets id to the current transaction id or to 0 if unknown.
796 * Returns size of transaction record or 0 for unknown size.
797 */
798 static unsigned journalfile_replay_transaction(struct rrdengine_instance *ctx, struct rrdengine_journalfile *journalfile,
799 void *buf, uint64_t *id, unsigned max_size)
800 {
801 unsigned payload_length, size_bytes;
802 int ret;
803 /* persistent structures */
804 struct rrdeng_jf_transaction_header *jf_header;
805 struct rrdeng_jf_transaction_trailer *jf_trailer;
806 uLong crc;
807
808 *id = 0;
809 jf_header = buf;
810 if (STORE_PADDING == jf_header->type) {
811 netdata_log_debug(D_RRDENGINE, "Skipping padding.");
812 return 0;
813 }
814 if (sizeof(*jf_header) > max_size) {
815 netdata_log_error("DBENGINE: corrupted transaction record, skipping.");
816 return 0;
817 }
818 *id = jf_header->id;
819 payload_length = jf_header->payload_length;
820 size_bytes = sizeof(*jf_header) + payload_length + sizeof(*jf_trailer);
821 if (size_bytes > max_size) {
822 netdata_log_error("DBENGINE: corrupted transaction record, skipping.");
823 return 0;
824 }
825 jf_trailer = buf + sizeof(*jf_header) + payload_length;
826 crc = crc32(0L, Z_NULL, 0);
827 crc = crc32(crc, buf, sizeof(*jf_header) + payload_length);
828 ret = crc32cmp(jf_trailer->checksum, crc);
829 netdata_log_debug(D_RRDENGINE, "Transaction %"PRIu64" was read from disk. CRC32 check: %s", *id, ret ? "FAILED" : "SUCCEEDED");
830 if (unlikely(ret)) {
831 netdata_log_error("DBENGINE: transaction %"PRIu64" was read from disk. CRC32 check: FAILED", *id);
832 return size_bytes;
833 }
834 switch (jf_header->type) {
835 case STORE_DATA:
836 netdata_log_debug(D_RRDENGINE, "Replaying transaction %"PRIu64"", jf_header->id);
837 journalfile_restore_extent_metadata(ctx, journalfile, buf + sizeof(*jf_header), payload_length);
838 break;
839 default:
840 netdata_log_error("DBENGINE: unknown transaction type, skipping record.");
841 break;
842 }
843
844 return size_bytes;
845 }
846
847
848 #define READAHEAD_BYTES (RRDENG_BLOCK_SIZE * 256)
849 /*
850 * Iterates journal file transactions and populates the page cache.
851 * Page cache must already be initialized.
852 * Returns the maximum transaction id it discovered.
853 */
854 static uint64_t journalfile_iterate_transactions(struct rrdengine_instance *ctx, struct rrdengine_journalfile *journalfile)
855 {
856 uv_file file;
857 uint64_t file_size;
858 int ret;
859 uint64_t pos, pos_i, max_id, id;
860 unsigned size_bytes;
861 void *buf = NULL;
862 uv_buf_t iov;
863 uv_fs_t req;
864
865 file = journalfile->file;
866 file_size = journalfile->unsafe.pos;
867
868 max_id = 1;
869 (void)posix_memalignz((void *)&buf, RRDFILE_ALIGNMENT, READAHEAD_BYTES);
870
871 for (pos = sizeof(struct rrdeng_jf_sb); pos < file_size; pos += READAHEAD_BYTES) {
872 size_bytes = MIN(READAHEAD_BYTES, file_size - pos);
873 iov = uv_buf_init(buf, size_bytes);
874 ret = uv_fs_read(NULL, &req, file, &iov, 1, pos, NULL);
875 if (ret < 0) {
876 netdata_log_error("DBENGINE: uv_fs_read: pos=%" PRIu64 ", %s", pos, uv_strerror(ret));
877 uv_fs_req_cleanup(&req);
878 goto skip_file;
879 }
880 fatal_assert(req.result >= 0);
881 uv_fs_req_cleanup(&req);
882 ctx_io_read_op_bytes(ctx, size_bytes);
883
884 for (pos_i = 0; pos_i < size_bytes;) {
885 unsigned max_size;
886
887 max_size = size_bytes - pos_i;
888 ret = journalfile_replay_transaction(ctx, journalfile, buf + pos_i, &id, max_size);
889 if (!ret)
890 /* unknown transaction size, move on to the next block */
891 pos_i = ALIGN_BYTES_FLOOR(pos_i + RRDENG_BLOCK_SIZE);
892 else
893 pos_i += ret;
894 max_id = MAX(max_id, id);
895 }
896 }
897 skip_file:
898 posix_memalign_freez(buf);
899 return max_id;
900 }
901
902 // Checks that the extent list checksum is valid
903 static int journalfile_check_v2_extent_list (void *data_start, size_t file_size)
904 {
905 UNUSED(file_size);
906 uLong crc;
907
908 struct journal_v2_header *j2_header = (void *) data_start;
909 struct journal_v2_block_trailer *journal_v2_trailer;
910
911 journal_v2_trailer = (struct journal_v2_block_trailer *) ((uint8_t *) data_start + j2_header->extent_trailer_offset);
912 crc = crc32(0L, Z_NULL, 0);
913 crc = crc32(crc, (uint8_t *) data_start + j2_header->extent_offset, j2_header->extent_count * sizeof(struct journal_extent_list));
914 if (unlikely(crc32cmp(journal_v2_trailer->checksum, crc))) {
915 netdata_log_error("DBENGINE: extent list CRC32 check: FAILED");
916 return 1;
917 }
918
919 return 0;
920 }
921
922 // Checks that the metric list (UUIDs) checksum is valid
923 static int journalfile_check_v2_metric_list(void *data_start, size_t file_size)
924 {
925 UNUSED(file_size);
926 uLong crc;
927
928 struct journal_v2_header *j2_header = (void *) data_start;
929 struct journal_v2_block_trailer *journal_v2_trailer;
930
931 journal_v2_trailer = (struct journal_v2_block_trailer *) ((uint8_t *) data_start + j2_header->metric_trailer_offset);
932 crc = crc32(0L, Z_NULL, 0);
933 crc = crc32(crc, (uint8_t *) data_start + j2_header->metric_offset, j2_header->metric_count * sizeof(struct journal_metric_list));
934 if (unlikely(crc32cmp(journal_v2_trailer->checksum, crc))) {
935 netdata_log_error("DBENGINE: metric list CRC32 check: FAILED");
936 return 1;
937 }
938 return 0;
939 }
940
941 //
942 // Return
943 // 0 Ok
944 // 1 Invalid
945 // 2 Force rebuild
946 // 3 skip
947
948 static int journalfile_v2_validate(void *data_start, size_t journal_v2_file_size, size_t journal_v1_file_size)
949 {
950 int rc;
951 uLong crc;
952
953 struct journal_v2_header *j2_header = (void *) data_start;
954 struct journal_v2_block_trailer *journal_v2_trailer;
955
956 if (j2_header->magic == JOURVAL_V2_REBUILD_MAGIC)
957 return 2;
958
959 if (j2_header->magic == JOURVAL_V2_SKIP_MAGIC)
960 return 3;
961
962 // Magic failure
963 if (j2_header->magic != JOURVAL_V2_MAGIC)
964 return 1;
965
966 if (j2_header->journal_v2_file_size != journal_v2_file_size)
967 return 1;
968
969 if (journal_v1_file_size && j2_header->journal_v1_file_size != journal_v1_file_size)
970 return 1;
971
972 journal_v2_trailer = (struct journal_v2_block_trailer *) ((uint8_t *) data_start + journal_v2_file_size - sizeof(*journal_v2_trailer));
973
974 crc = crc32(0L, Z_NULL, 0);
975 crc = crc32(crc, (void *) j2_header, sizeof(*j2_header));
976
977 rc = crc32cmp(journal_v2_trailer->checksum, crc);
978 if (unlikely(rc)) {
979 netdata_log_error("DBENGINE: file CRC32 check: FAILED");
980 return 1;
981 }
982
983 rc = journalfile_check_v2_extent_list(data_start, journal_v2_file_size);
984 if (rc) return 1;
985
986 if (!db_engine_journal_check)
987 return 0;
988
989 rc = journalfile_check_v2_metric_list(data_start, journal_v2_file_size);
990 if (rc) return 1;
991
992 // Verify complete UUID chain
993
994 struct journal_metric_list *metric = (void *) (data_start + j2_header->metric_offset);
995
996 unsigned verified = 0;
997 unsigned entries;
998 unsigned total_pages = 0;
999
1000 netdata_log_info("DBENGINE: checking %u metrics that exist in the journal", j2_header->metric_count);
1001 for (entries = 0; entries < j2_header->metric_count; entries++) {
1002
1003 char uuid_str[UUID_STR_LEN];
1004 uuid_unparse_lower(metric->uuid, uuid_str);
1005 struct journal_page_header *metric_list_header = (void *) (data_start + metric->page_offset);
1006 struct journal_page_header local_metric_list_header = *metric_list_header;
1007
1008 local_metric_list_header.crc = JOURVAL_V2_MAGIC;
1009
1010 crc = crc32(0L, Z_NULL, 0);
1011 crc = crc32(crc, (void *) &local_metric_list_header, sizeof(local_metric_list_header));
1012 rc = crc32cmp(metric_list_header->checksum, crc);
1013
1014 if (!rc) {
1015 struct journal_v2_block_trailer *journal_trailer =
1016 (void *) data_start + metric->page_offset + sizeof(struct journal_page_header) + (metric_list_header->entries * sizeof(struct journal_page_list));
1017
1018 crc = crc32(0L, Z_NULL, 0);
1019 crc = crc32(crc, (uint8_t *) metric_list_header + sizeof(struct journal_page_header), metric_list_header->entries * sizeof(struct journal_page_list));
1020 rc = crc32cmp(journal_trailer->checksum, crc);
1021 internal_error(rc, "DBENGINE: index %u : %s entries %u at offset %u verified, DATA CRC computed %lu, stored %u", entries, uuid_str, metric->entries, metric->page_offset,
1022 crc, metric_list_header->crc);
1023 if (!rc) {
1024 total_pages += metric_list_header->entries;
1025 verified++;
1026 }
1027 }
1028
1029 metric++;
1030 if ((uint32_t)((uint8_t *) metric - (uint8_t *) data_start) > (uint32_t) journal_v2_file_size) {
1031 netdata_log_info("DBENGINE: verification failed EOF reached -- total entries %u, verified %u", entries, verified);
1032 return 1;
1033 }
1034 }
1035
1036 if (entries != verified) {
1037 netdata_log_info("DBENGINE: verification failed -- total entries %u, verified %u", entries, verified);
1038 return 1;
1039 }
1040 netdata_log_info("DBENGINE: verification succeeded -- total entries %u, verified %u (%u total pages)", entries, verified, total_pages);
1041
1042 return 0;
1043 }
1044
1045 void journalfile_v2_populate_retention_to_mrg(struct rrdengine_instance *ctx, struct rrdengine_journalfile *journalfile) {
1046 usec_t started_ut = now_monotonic_usec();
1047
1048 size_t data_size = 0;
1049 struct journal_v2_header *j2_header = journalfile_v2_data_acquire_with_hint(
1050 journalfile, &data_size, 0, 0, JOURNALFILE_V2_ACCESS_SEQUENTIAL_DIRECTORY);
1051 if(!j2_header)
1052 return;
1053
1054 uint8_t *data_start = (uint8_t *)j2_header;
1055
1056 char path_v2[RRDENG_PATH_MAX];
1057 journalfile_v2_generate_path(journalfile->datafile, path_v2, sizeof(path_v2));
1058 time_t global_first_time_s = 0;
1059 bool failed = false;
1060 uint32_t entries = 0;
1061 // Calculate number of samples here and update once the file is loaded
1062 uint64_t journal_samples = 0;
1063
1064 // Protect the whole walk -- both the optional CRC check and the mrg update
1065 // read into the mmap'd v2 journal. If a backing page cannot be paged in
1066 // (file truncated, sparse hole, transient I/O error) the kernel raises
1067 // SIGBUS; without a protected region active the process aborts. Same
1068 // pattern as journalfile_v2_validate() in journalfile_v2_load().
1069 PROTECTED_ACCESS_SETUP(journalfile->mmap.data, journalfile->mmap.size, path_v2, "mrg-load");
1070 if(no_signal_received) {
1071 // Validate header-controlled offsets against the actual mapping size
1072 // BEFORE reading through them. PROTECTED_ACCESS_SETUP only catches
1073 // faults whose address falls within [data_start, data_start+mmap.size);
1074 // an over-read driven by a corrupted offset past the registered range
1075 // would not be recovered and the process would still abort. Out-of-
1076 // range offsets are treated as a rebuild trigger (same outcome as a
1077 // CRC failure).
1078 size_t mmap_size = journalfile->mmap.size;
1079 // Order matters: short-circuit on metric_offset > mmap_size first so the
1080 // subtraction below cannot underflow, then compare metric_count against
1081 // the available slot capacity via division rather than multiplying out
1082 // metric_count * sizeof(...), which would wrap size_t on 32-bit builds
1083 // and silently pass a malformed header. Only after those two checks is
1084 // metric_count * sizeof(...) known to fit in size_t, which lets the
1085 // trailer-ordering check below compute the expected trailer offset
1086 // safely. The on-disk layout (see journalfile_migrate_to_v2_callback)
1087 // places the metric-list trailer immediately after the metric list, so
1088 // any deviation indicates a corrupted header that would otherwise let
1089 // the CRC compare against bytes inside the metric list itself.
1090 if ((size_t)j2_header->metric_offset > mmap_size ||
1091 (size_t)j2_header->metric_count > (mmap_size - (size_t)j2_header->metric_offset) / sizeof(struct journal_metric_list) ||
1092 (size_t)j2_header->metric_trailer_offset > mmap_size ||
1093 mmap_size - (size_t)j2_header->metric_trailer_offset < sizeof(struct journal_v2_block_trailer) ||
1094 (size_t)j2_header->metric_trailer_offset != (size_t)j2_header->metric_offset + (size_t)j2_header->metric_count * sizeof(struct journal_metric_list)) {
1095 // header offsets out of range -- needs rebuild
1096 nd_log_daemon(NDLP_ERR,
1097 "DBENGINE: journal v2 \"%s\" has out-of-range header offsets "
1098 "(metric_offset=%u, metric_count=%u, metric_trailer_offset=%u, mmap_size=%zu); "
1099 "marking unavailable for rebuild",
1100 path_v2,
1101 j2_header->metric_offset,
1102 j2_header->metric_count,
1103 j2_header->metric_trailer_offset,
1104 mmap_size);
1105 failed = true;
1106 }
1107 else if (journalfile->v2.flags & JOURNALFILE_FLAG_METRIC_CRC_CHECK) {
1108 journalfile->v2.flags &= ~JOURNALFILE_FLAG_METRIC_CRC_CHECK;
1109 // Pass the verified mmap_size, not the header-controlled
1110 // j2_header->journal_v2_file_size; the helper currently ignores the
1111 // size argument (UNUSED) but the value at the call site should
1112 // still reflect the trusted bound for clarity and future-proofing.
1113 if (journalfile_check_v2_metric_list(data_start, mmap_size)) {
1114 // needs rebuild
1115 failed = true;
1116 }
1117 }
1118
1119 if (!failed) {
1120 entries = j2_header->metric_count;
1121 struct journal_metric_list *metric = (struct journal_metric_list *) (data_start + j2_header->metric_offset);
1122 time_t header_start_time_s = (time_t) (j2_header->start_time_ut / USEC_PER_SEC);
1123 global_first_time_s = header_start_time_s;
1124 time_t now_s = max_acceptable_collected_time();
1125 for (size_t i=0; i < entries; i++) {
1126 // Copy uuid out of the mmap onto the stack BEFORE calling mrg.
1127 // If a backing page is unreadable, uuid_copy SIGBUSes here and
1128 // the protected region recovers cleanly; the mrg call then
1129 // never executes. If we passed &metric->uuid into mrg, a
1130 // SIGBUS could fire INSIDE mrg while it holds internal locks,
1131 // and siglongjmp would skip mrg's unlock paths.
1132 nd_uuid_t local_uuid;
1133 uuid_copy(local_uuid, metric->uuid);
1134 time_t start_time_s = header_start_time_s + metric->delta_start_s;
1135 time_t end_time_s = header_start_time_s + metric->delta_end_s;
1136 uint32_t update_every_s = metric->update_every_s;
1137
1138 mrg_update_metric_retention_and_granularity_by_uuid(
1139 main_mrg,
1140 (Word_t)ctx,
1141 &local_uuid,
1142 start_time_s,
1143 end_time_s,
1144 update_every_s,
1145 now_s,
1146 &journal_samples);
1147
1148 metric++;
1149 }
1150 }
1151 }
1152 else {
1153 // SIGBUS/SIGSEGV inside the mmap walk. The PROTECTED_ACCESS_SETUP
1154 // macro already rate-limits the error log.
1155 failed = true;
1156 }
1157
1158 if (unlikely(failed)) {
1159 // Clear IS_AVAILABLE under the data spinlock BEFORE releasing our
1160 // refcount, so no concurrent journalfile_v2_data_acquire_with_hint()
1161 // can succeed and walk the (potentially corrupted) mmap between here
1162 // and the permanent unmap. The bounds, CRC, and signal-recovery paths
1163 // all converge through this single transition; without it, teardown
1164 // (journalfile_close / journalfile_destroy_unsafe) would gate on
1165 // IS_AVAILABLE and skip journalfile_v2_data_unmap_permanently(),
1166 // leaving a dangling njfv2idx entry, an unclosed fd, and an unmapped
1167 // region.
1168 spinlock_lock(&journalfile->data_spinlock);
1169 journalfile->v2.flags &= ~JOURNALFILE_FLAG_IS_AVAILABLE;
1170 spinlock_unlock(&journalfile->data_spinlock);
1171 }
1172
1173 journalfile_v2_data_release(journalfile);
1174
1175 if (unlikely(failed)) {
1176 journalfile_v2_data_unmap_permanently(journalfile);
1177 return;
1178 }
1179
1180 __atomic_add_fetch(&ctx->atomic.samples, journal_samples, __ATOMIC_RELAXED);
1181
1182 usec_t ended_ut = now_monotonic_usec();
1183
1184 nd_log_daemon(NDLP_DEBUG, "DBENGINE: journal v2 of tier %d, datafile %u populated, size: %0.2f MiB, metrics: %0.2f k, %0.2f ms"
1185 , ctx->config.tier, journalfile->datafile->fileno
1186 , (double)data_size / 1024 / 1024
1187 , (double)entries / 1000
1188 , ((double)(ended_ut - started_ut) / USEC_PER_MS)
1189 );
1190
1191 time_t old = __atomic_load_n(&ctx->atomic.first_time_s, __ATOMIC_RELAXED);;
1192 do {
1193 if(old <= global_first_time_s)
1194 break;
1195 } while(!__atomic_compare_exchange_n(&ctx->atomic.first_time_s, &old, global_first_time_s, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED));
1196 }
1197
1198 int journalfile_v2_load(struct rrdengine_instance *ctx, struct rrdengine_journalfile *journalfile, struct rrdengine_datafile *datafile)
1199 {
1200 int ret, fd;
1201 char path_v1[RRDENG_PATH_MAX];
1202 char path_v2[RRDENG_PATH_MAX];
1203 struct stat statbuf;
1204 size_t journal_v1_file_size = 0;
1205 size_t journal_v2_file_size;
1206
1207 journalfile_v1_generate_path(datafile, path_v1, sizeof(path_v1));
1208 ret = stat(path_v1, &statbuf);
1209 if (!ret)
1210 journal_v1_file_size = (uint32_t)statbuf.st_size;
1211
1212 journalfile_v2_generate_path(datafile, path_v2, sizeof(path_v2));
1213 fd = open(path_v2, O_RDONLY | O_CLOEXEC);
1214 if (fd < 0) {
1215 if (errno == ENOENT)
1216 return 1;
1217 ctx_fs_error(ctx);
1218 netdata_log_error("DBENGINE: failed to open \"%s\"", path_v2);
1219 return 1;
1220 }
1221
1222 ret = fstat(fd, &statbuf);
1223 if (ret) {
1224 netdata_log_error("DBENGINE: failed to get file information for \"%s\"", path_v2);
1225 close(fd);
1226 return 1;
1227 }
1228
1229 journal_v2_file_size = (size_t)statbuf.st_size;
1230
1231 if (journal_v2_file_size < sizeof(struct journal_v2_header)) {
1232 error_report("Invalid file \"%s\". Not the expected size", path_v2);
1233 close(fd);
1234 return 1;
1235 }
1236
1237 usec_t mmap_start_ut = now_monotonic_usec();
1238 uint8_t *data_start = nd_mmap(NULL, journal_v2_file_size, PROT_READ, MAP_SHARED, fd, 0);
1239 if (data_start == MAP_FAILED) {
1240 close(fd);
1241 return 1;
1242 }
1243
1244 // validation reads the file sequentially — hint the kernel to prefetch
1245 madvise_sequential(data_start, journal_v2_file_size);
1246 madvise_willneed(data_start, journal_v2_file_size);
1247
1248 nd_log_daemon(NDLP_DEBUG, "DBENGINE: checking integrity of \"%s\"", path_v2);
1249
1250 usec_t validation_start_ut = now_monotonic_usec();
1251
1252 int rc = 0;
1253 PROTECTED_ACCESS_SETUP(data_start, journal_v2_file_size, path_v2, "validate");
1254 if(no_signal_received) {
1255 rc = journalfile_v2_validate(data_start, journal_v2_file_size, journal_v1_file_size);
1256 }
1257 else {
1258 rc = 2;
1259 }
1260
1261 if (unlikely(rc)) {
1262 if (rc == 2)
1263 error_report("File \"%s\" needs to be rebuilt", path_v2);
1264 else if (rc == 3)
1265 error_report("File \"%s\" will be skipped", path_v2);
1266 else
1267 error_report("File \"%s\" is invalid and it will be rebuilt", path_v2);
1268
1269 if (unlikely(nd_munmap(data_start, journal_v2_file_size)))
1270 netdata_log_error("DBENGINE: failed to unmap \"%s\"", path_v2);
1271
1272 close(fd);
1273 return rc;
1274 }
1275
1276 struct journal_v2_header *j2_header = (void *) data_start;
1277 uint32_t entries = j2_header->metric_count;
1278
1279 if (unlikely(!entries)) {
1280 if (unlikely(nd_munmap(data_start, journal_v2_file_size)))
1281 netdata_log_error("DBENGINE: failed to unmap \"%s\"", path_v2);
1282
1283 close(fd);
1284 return 1;
1285 }
1286
1287 usec_t finished_ut = now_monotonic_usec();
1288
1289 nd_log_daemon(NDLP_DEBUG, "DBENGINE: journal v2 \"%s\" loaded, size: %0.2f MiB, metrics: %0.2f k, "
1290 "mmap: %0.2f ms, validate: %0.2f ms"
1291 , path_v2
1292 , (double)journal_v2_file_size / 1024 / 1024
1293 , (double)entries / 1000
1294 , ((double)(validation_start_ut - mmap_start_ut) / USEC_PER_MS)
1295 , ((double)(finished_ut - validation_start_ut) / USEC_PER_MS)
1296 );
1297
1298 // Initialize the journal file to be able to access the data
1299
1300 if (!db_engine_journal_check)
1301 journalfile->v2.flags |= JOURNALFILE_FLAG_METRIC_CRC_CHECK;
1302
1303 journalfile_v2_data_set(journalfile, fd, data_start, journal_v2_file_size);
1304
1305 ctx_current_disk_space_increase(ctx, journal_v2_file_size);
1306
1307 // File is OK load it
1308 return 0;
1309 }
1310
1311 struct journal_metric_list_to_sort {
1312 struct jv2_metrics_info *metric_info;
1313 };
1314
1315 static int journalfile_metric_compare (const void *item1, const void *item2)
1316 {
1317 const struct jv2_metrics_info *metric1 = ((struct journal_metric_list_to_sort *) item1)->metric_info;
1318 const struct jv2_metrics_info *metric2 = ((struct journal_metric_list_to_sort *) item2)->metric_info;
1319
1320 return memcmp(metric1->uuid, metric2->uuid, sizeof(nd_uuid_t));
1321 }
1322
1323
1324 // Write list of extents for the journalfile
1325 void *journalfile_v2_write_extent_list(Pvoid_t JudyL_extents_pos, void *data)
1326 {
1327 Pvoid_t *PValue;
1328 struct journal_extent_list *j2_extent_base = (void *) data;
1329 struct jv2_extents_info *ext_info;
1330
1331 bool first = true;
1332 Word_t pos = 0;
1333 size_t count = 0;
1334 while ((PValue = JudyLFirstThenNext(JudyL_extents_pos, &pos, &first))) {
1335 ext_info = *PValue;
1336 size_t index = ext_info->index;
1337 j2_extent_base[index].file_index = 0;
1338 j2_extent_base[index].datafile_offset = BLOCK_TO_OFFSET(ext_info->block);
1339 j2_extent_base[index].datafile_size = ext_info->bytes;
1340 j2_extent_base[index].pages = ext_info->number_of_pages;
1341 count++;
1342 }
1343 return j2_extent_base + count;
1344 }
1345
1346 static int journalfile_verify_space(struct journal_v2_header *j2_header, void *data, uint32_t bytes)
1347 {
1348 if ((unsigned long)(((uint8_t *) data - (uint8_t *) j2_header->data) + bytes) > (j2_header->journal_v2_file_size - sizeof(struct journal_v2_block_trailer)))
1349 return 1;
1350
1351 return 0;
1352 }
1353
1354 void *journalfile_v2_write_metric_page(struct journal_v2_header *j2_header, void *data, struct jv2_metrics_info *metric_info, uint32_t pages_offset)
1355 {
1356 struct journal_metric_list *metric = (void *) data;
1357
1358 if (journalfile_verify_space(j2_header, data, sizeof(*metric)))
1359 return NULL;
1360
1361 uuid_copy(metric->uuid, *metric_info->uuid);
1362 metric->entries = metric_info->number_of_pages;
1363 metric->page_offset = pages_offset;
1364 metric->delta_start_s = (uint32_t)(metric_info->first_time_s - (time_t)(j2_header->start_time_ut / USEC_PER_SEC));
1365 metric->delta_end_s = (uint32_t)(metric_info->last_time_s - (time_t)(j2_header->start_time_ut / USEC_PER_SEC));
1366 metric->update_every_s = 0;
1367
1368 return ++metric;
1369 }
1370
1371 void *journalfile_v2_write_data_page_header(struct journal_v2_header *j2_header __maybe_unused, void *data, struct jv2_metrics_info *metric_info, uint32_t uuid_offset)
1372 {
1373 struct journal_page_header *data_page_header = (void *) data;
1374 uLong crc;
1375
1376 uuid_copy(data_page_header->uuid, *metric_info->uuid);
1377 data_page_header->entries = metric_info->number_of_pages;
1378 data_page_header->uuid_offset = uuid_offset; // data header OFFSET poings to METRIC in the directory
1379 data_page_header->crc = JOURVAL_V2_MAGIC;
1380 crc = crc32(0L, Z_NULL, 0);
1381 crc = crc32(crc, (void *) data_page_header, sizeof(*data_page_header));
1382 crc32set(data_page_header->checksum, crc);
1383 return ++data_page_header;
1384 }
1385
1386 void *journalfile_v2_write_data_page_trailer(struct journal_v2_header *j2_header __maybe_unused, void *data, void *page_header)
1387 {
1388 struct journal_page_header *data_page_header = (void *) page_header;
1389 struct journal_v2_block_trailer *journal_trailer = (void *) data;
1390 uLong crc;
1391
1392 crc = crc32(0L, Z_NULL, 0);
1393 crc = crc32(crc, (uint8_t *) page_header + sizeof(struct journal_page_header), data_page_header->entries * sizeof(struct journal_page_list));
1394 crc32set(journal_trailer->checksum, crc);
1395 return ++journal_trailer;
1396 }
1397
1398 void *journalfile_v2_write_data_page(struct journal_v2_header *j2_header, void *data, struct jv2_page_info *page_info)
1399 {
1400 struct journal_page_list *data_page = data;
1401
1402 if (journalfile_verify_space(j2_header, data, sizeof(*data_page)))
1403 return NULL;
1404
1405 data_page->delta_start_s = (uint32_t) (page_info->start_time_s - (time_t) (j2_header->start_time_ut) / USEC_PER_SEC);
1406 data_page->delta_end_s = (uint32_t) (page_info->end_time_s - (time_t) (j2_header->start_time_ut) / USEC_PER_SEC);
1407 data_page->extent_index = page_info->extent_index;
1408
1409 data_page->update_every_s = page_info->update_every_s;
1410 data_page->page_length = 0;
1411 data_page->type = 0;
1412
1413 return ++data_page;
1414 }
1415
1416 // Must be recorded in metric_info->entries
1417 static void *journalfile_v2_write_descriptors(struct journal_v2_header *j2_header, void *data, struct jv2_metrics_info *metric_info,
1418 struct journal_metric_list *current_metric)
1419 {
1420 Pvoid_t *PValue;
1421
1422 struct journal_page_list *data_page = (void *)data;
1423 // We need to write all descriptors with index metric_info->min_index_time_s, metric_info->max_index_time_s
1424 // that belong to this journal file
1425 Pvoid_t JudyL_array = metric_info->JudyL_pages_by_start_time;
1426
1427 Word_t index_time = 0;
1428 bool first = true;
1429 struct jv2_page_info *page_info;
1430 uint32_t update_every_s = 0;
1431 while ((PValue = JudyLFirstThenNext(JudyL_array, &index_time, &first))) {
1432 page_info = *PValue;
1433 // Write one descriptor and return the next data page location
1434 data_page = journalfile_v2_write_data_page(j2_header, (void *) data_page, page_info);
1435 update_every_s = page_info->update_every_s;
1436 if (NULL == data_page)
1437 break;
1438 }
1439 current_metric->update_every_s = update_every_s;
1440 return data_page;
1441 }
1442
1443 // Migrate the journalfile pointed by datafile
1444 // activate : make the new file active immediately
1445 // journafile data will be set and descriptors (if deleted) will be repopulated as needed
1446 // startup : if the migration is done during agent startup
1447 // this will allow us to optimize certain things
1448
1449 bool journalfile_migrate_to_v2_callback(Word_t section, unsigned datafile_fileno __maybe_unused, uint8_t type __maybe_unused,
1450 Pvoid_t JudyL_metrics, Pvoid_t JudyL_extents_pos,
1451 size_t number_of_extents, size_t number_of_metrics, size_t number_of_pages, void *user_data)
1452 {
1453 // Nothing to migrate if no metrics
1454 if (number_of_metrics == 0)
1455 return true;
1456
1457 char path[RRDENG_PATH_MAX];
1458 Pvoid_t *PValue;
1459 struct rrdengine_instance *ctx = (struct rrdengine_instance *) section;
1460 struct rrdengine_journalfile *journalfile = (struct rrdengine_journalfile *) user_data;
1461 struct rrdengine_datafile *datafile = journalfile->datafile;
1462 time_t min_time_s = LONG_MAX;
1463 time_t max_time_s = 0;
1464 struct jv2_metrics_info *metric_info;
1465
1466 journalfile_v2_generate_path(datafile, path, sizeof(path));
1467
1468 netdata_log_info("DBENGINE: tier %d: indexing " WALFILE_PREFIX RRDENG_FILE_NUMBER_PRINT_TMPL WALFILE_EXTENSION_V2 ": extents %zu, metrics %zu, pages %zu",
1469 ctx->config.tier, datafile->tier, datafile->fileno,
1470 number_of_extents,
1471 number_of_metrics,
1472 number_of_pages);
1473
1474 #ifdef NETDATA_INTERNAL_CHECKS
1475 usec_t start_loading = now_monotonic_usec();
1476 #endif
1477
1478 size_t total_file_size = 0;
1479 total_file_size += (sizeof(struct journal_v2_header) + JOURNAL_V2_HEADER_PADDING_SZ);
1480
1481 // Extents will start here
1482 uint32_t extent_offset = total_file_size;
1483 total_file_size += (number_of_extents * sizeof(struct journal_extent_list));
1484
1485 uint32_t extent_offset_trailer = total_file_size;
1486 total_file_size += sizeof(struct journal_v2_block_trailer);
1487
1488 // UUID list will start here
1489 uint32_t metrics_offset = total_file_size;
1490 total_file_size += (number_of_metrics * sizeof(struct journal_metric_list));
1491
1492 // UUID list trailer
1493 uint32_t metric_offset_trailer = total_file_size;
1494 total_file_size += sizeof(struct journal_v2_block_trailer);
1495
1496 // descr @ time will start here
1497 uint32_t pages_offset = total_file_size;
1498 total_file_size += (number_of_pages * (sizeof(struct journal_page_list) + sizeof(struct journal_page_header) + sizeof(struct journal_v2_block_trailer)));
1499
1500 // File trailer
1501 uint32_t trailer_offset = total_file_size;
1502 total_file_size += sizeof(struct journal_v2_block_trailer);
1503
1504 int fd_v2 = -1;
1505 uint8_t *data_start = nd_mmap_advanced(path, total_file_size, MAP_SHARED, 0, false, true, &fd_v2);
1506 if(!data_start) {
1507 if(fd_v2 != -1)
1508 close(fd_v2);
1509 nd_log_daemon(NDLP_WARNING, "DBENGINE: Failed to allocate %"PRIu64" bytes of memory for journal file \"%s\". Will retry later", total_file_size, path);
1510 return false;
1511 }
1512
1513 struct journal_metric_list_to_sort *uuid_list = NULL;
1514
1515 PROTECTED_ACCESS_SETUP(data_start, total_file_size, path, "migrate");
1516 if(no_signal_received) {
1517 fatal_assert(extent_offset <= total_file_size);
1518 memset(data_start, 0, extent_offset);
1519
1520 // Write header
1521 struct journal_v2_header j2_header;
1522 memset(&j2_header, 0, sizeof(j2_header));
1523
1524 j2_header.magic = JOURVAL_V2_MAGIC;
1525 j2_header.start_time_ut = 0;
1526 j2_header.end_time_ut = 0;
1527 j2_header.extent_count = number_of_extents;
1528 j2_header.extent_offset = extent_offset;
1529 j2_header.metric_count = number_of_metrics;
1530 j2_header.metric_offset = metrics_offset;
1531 j2_header.page_count = number_of_pages;
1532 j2_header.page_offset = pages_offset;
1533 j2_header.extent_trailer_offset = extent_offset_trailer;
1534 j2_header.metric_trailer_offset = metric_offset_trailer;
1535 j2_header.journal_v2_file_size = total_file_size;
1536 j2_header.journal_v1_file_size = (uint32_t)journalfile_current_size(journalfile);
1537 j2_header.data = data_start; // Used during migration
1538
1539 struct journal_v2_block_trailer *journal_v2_trailer;
1540
1541 uint8_t *data = journalfile_v2_write_extent_list(JudyL_extents_pos, data_start + extent_offset);
1542 internal_error(
1543 true, "DBENGINE: write extent list so far %llu", (now_monotonic_usec() - start_loading) / USEC_PER_MS);
1544
1545 fatal_assert(data == data_start + extent_offset_trailer);
1546
1547 // Calculate CRC for extents
1548 journal_v2_trailer = (struct journal_v2_block_trailer *)(data_start + extent_offset_trailer);
1549 uLong crc;
1550 crc = crc32(0L, Z_NULL, 0);
1551 crc = crc32(crc, (uint8_t *)data_start + extent_offset, number_of_extents * sizeof(struct journal_extent_list));
1552 crc32set(journal_v2_trailer->checksum, crc);
1553
1554 internal_error(
1555 true, "DBENGINE: CALCULATE CRC FOR EXTENT %llu", (now_monotonic_usec() - start_loading) / USEC_PER_MS);
1556 // Skip the trailer, point to the metrics off
1557 data += sizeof(struct journal_v2_block_trailer);
1558
1559 // Sanity check -- we must be at the metrics_offset
1560 fatal_assert(data == data_start + metrics_offset);
1561
1562 // Allocate array to sort UUIDs and keep them sorted in the journal because we want to do binary search when we do lookups
1563 uuid_list = mallocz(number_of_metrics * sizeof(struct journal_metric_list_to_sort));
1564
1565 Word_t Index = 0;
1566 size_t count = 0;
1567 bool first_then_next = true;
1568 while ((PValue = JudyLFirstThenNext(JudyL_metrics, &Index, &first_then_next))) {
1569 metric_info = *PValue;
1570
1571 fatal_assert(metric_info != NULL);
1572 fatal_assert(count < number_of_metrics);
1573 uuid_list[count++].metric_info = metric_info;
1574 min_time_s = MIN(min_time_s, metric_info->first_time_s);
1575 max_time_s = MAX(max_time_s, metric_info->last_time_s);
1576 }
1577
1578 fatal_assert(count == number_of_metrics);
1579
1580 // Check if not properly set in the loop above to prevent overflow
1581 if (min_time_s == LONG_MAX)
1582 min_time_s = 0;
1583
1584 // Store in the header
1585 j2_header.start_time_ut = min_time_s * USEC_PER_SEC;
1586 j2_header.end_time_ut = max_time_s * USEC_PER_SEC;
1587
1588 qsort(&uuid_list[0], number_of_metrics, sizeof(struct journal_metric_list_to_sort), journalfile_metric_compare);
1589 internal_error(
1590 true, "DBENGINE: traverse and qsort UUID %llu", (now_monotonic_usec() - start_loading) / USEC_PER_MS);
1591
1592 for (Index = 0; Index < number_of_metrics; Index++) {
1593 metric_info = uuid_list[Index].metric_info;
1594
1595 // Calculate current UUID offset from start of file. We will store this in the data page header
1596 uint32_t uuid_offset = data - data_start;
1597
1598 struct journal_metric_list *current_metric = (void *)data;
1599 // Write the UUID we are processing
1600 data = (void *)journalfile_v2_write_metric_page(&j2_header, data, metric_info, pages_offset);
1601 if (unlikely(!data))
1602 break;
1603
1604 // Next we will write
1605 // Header
1606 // Detailed entries (descr @ time)
1607 // Trailer (checksum)
1608
1609 // Keep the page_list_header, to be used for migration when where agent is running
1610 metric_info->page_list_header = pages_offset;
1611 // Write page header
1612 void *metric_page =
1613 journalfile_v2_write_data_page_header(&j2_header, data_start + pages_offset, metric_info, uuid_offset);
1614
1615 // Start writing descr @ time
1616 void *page_trailer = journalfile_v2_write_descriptors(&j2_header, metric_page, metric_info, current_metric);
1617 if (unlikely(!page_trailer))
1618 break;
1619
1620 // Trailer (checksum)
1621 uint8_t *next_page_address =
1622 journalfile_v2_write_data_page_trailer(&j2_header, page_trailer, data_start + pages_offset);
1623
1624 // Calculate start of the pages start for next descriptor
1625 pages_offset +=
1626 (metric_info->number_of_pages * (sizeof(struct journal_page_list)) +
1627 sizeof(struct journal_page_header) + sizeof(struct journal_v2_block_trailer));
1628 // Verify we are at the right location
1629 if (pages_offset != (uint32_t)(next_page_address - data_start)) {
1630 // make sure checks fail so that we abort
1631 data = data_start;
1632 break;
1633 }
1634 }
1635
1636 if (data == data_start + metric_offset_trailer) {
1637 internal_error(
1638 true, "DBENGINE: WRITE METRICS AND PAGES %llu", (now_monotonic_usec() - start_loading) / USEC_PER_MS);
1639
1640 // Calculate CRC for metrics
1641 journal_v2_trailer = (struct journal_v2_block_trailer *)(data_start + metric_offset_trailer);
1642 crc = crc32(0L, Z_NULL, 0);
1643 crc = crc32(
1644 crc, (uint8_t *)data_start + metrics_offset, number_of_metrics * sizeof(struct journal_metric_list));
1645 crc32set(journal_v2_trailer->checksum, crc);
1646 internal_error(
1647 true, "DBENGINE: CALCULATE CRC FOR UUIDs %llu", (now_monotonic_usec() - start_loading) / USEC_PER_MS);
1648
1649 // Prepare to write checksum for the file
1650 j2_header.data = NULL;
1651 journal_v2_trailer = (struct journal_v2_block_trailer *)(data_start + trailer_offset);
1652 crc = crc32(0L, Z_NULL, 0);
1653 crc = crc32(crc, (void *)&j2_header, sizeof(j2_header));
1654 crc32set(journal_v2_trailer->checksum, crc);
1655
1656 // Write header to the file
1657 memcpy(data_start, &j2_header, sizeof(j2_header));
1658
1659 internal_error(
1660 true, "DBENGINE: FILE COMPLETED --------> %llu", (now_monotonic_usec() - start_loading) / USEC_PER_MS);
1661
1662 char size_for_humans[128];
1663 size_snprintf(size_for_humans, sizeof(size_for_humans), total_file_size, "B", false);
1664 netdata_log_info("DBENGINE: tier %d: migrated " WALFILE_PREFIX RRDENG_FILE_NUMBER_PRINT_TMPL WALFILE_EXTENSION_V2 ", %s",
1665 ctx->config.tier, datafile->tier, datafile->fileno, size_for_humans);
1666
1667 // msync(data_start, total_file_size, MS_SYNC);
1668 journalfile_v2_data_set(journalfile, fd_v2, data_start, total_file_size);
1669
1670 internal_error(
1671 true, "DBENGINE: ACTIVATING NEW INDEX JNL %llu", (now_monotonic_usec() - start_loading) / USEC_PER_MS);
1672 ctx_current_disk_space_increase(ctx, total_file_size);
1673 freez(uuid_list);
1674 return true;
1675 }
1676 }
1677 else {
1678 nd_log(NDLS_DAEMON, NDLP_ERR, "DBENGINE: failed to write journal file \"%s\" (SIGBUS)", path);
1679 }
1680
1681 freez(uuid_list);
1682
1683 netdata_log_info("DBENGINE: failed to build index \"%s\", file will be skipped", path);
1684
1685 nd_munmap(data_start, total_file_size);
1686 if(fd_v2 != -1)
1687 close(fd_v2);
1688 unlink(path);
1689 return false;
1690 }
1691
1692 int journalfile_load(struct rrdengine_instance *ctx, struct rrdengine_journalfile *journalfile,
1693 struct rrdengine_datafile *datafile)
1694 {
1695 uv_file file;
1696 int ret, fd, error;
1697 uint64_t file_size, max_id;
1698 char path[RRDENG_PATH_MAX];
1699 bool loaded_v2 = false;
1700
1701 // Do not try to load jv2 of the latest file
1702 if (datafile->fileno != ctx_last_fileno_get(ctx))
1703 loaded_v2 = journalfile_v2_load(ctx, journalfile, datafile) == 0;
1704
1705 journalfile_v1_generate_path(datafile, path, sizeof(path));
1706
1707 fd = open_file_for_io(path, O_RDWR, &file, dbengine_use_direct_io);
1708 if (fd < 0) {
1709 ctx_fs_error(ctx);
1710
1711 if(loaded_v2)
1712 return 0;
1713
1714 return fd;
1715 }
1716
1717 ret = check_file_properties(file, &file_size, sizeof(struct rrdeng_df_sb));
1718 if (ret) {
1719 error = ret;
1720 goto cleanup;
1721 }
1722
1723 if(loaded_v2) {
1724 journalfile->unsafe.pos = file_size;
1725 error = 0;
1726 goto cleanup;
1727 }
1728
1729 file_size = ALIGN_BYTES_FLOOR(file_size);
1730 journalfile->unsafe.pos = file_size;
1731 journalfile->file = file;
1732
1733 ret = journalfile_check_superblock(file);
1734 if (ret) {
1735 netdata_log_info("DBENGINE: invalid journal file \"%s\" ; superblock check failed.", path);
1736 error = ret;
1737 goto cleanup;
1738 }
1739 ctx_io_read_op_bytes(ctx, sizeof(struct rrdeng_jf_sb));
1740
1741 nd_log_daemon(NDLP_DEBUG, "DBENGINE: loading journal file \"%s\"", path);
1742
1743 max_id = journalfile_iterate_transactions(ctx, journalfile);
1744
1745 __atomic_store_n(&ctx->atomic.transaction_id, MAX(__atomic_load_n(&ctx->atomic.transaction_id, __ATOMIC_RELAXED), max_id + 1), __ATOMIC_RELAXED);
1746
1747 nd_log_daemon(NDLP_DEBUG, "DBENGINE: journal file \"%s\" loaded (size:%" PRIu64 ").", path, file_size);
1748
1749 bool is_last_file = (ctx_last_fileno_get(ctx) == journalfile->datafile->fileno);
1750 bool has_old_data = false;
1751 if (ctx->config.tier == 0 && journalfile->v2.last_time_s > 0)
1752 has_old_data = (now_realtime_sec() - journalfile->v2.last_time_s) > 86400;
1753
1754 if (is_last_file && journalfile->datafile->pos <= rrdeng_target_data_file_size(ctx) / 3 && !has_old_data) {
1755 ctx->loading.create_new_datafile_pair = false;
1756 return 0;
1757 }
1758
1759 pgc_open_cache_to_journal_v2(
1760 open_cache,
1761 (Word_t)ctx,
1762 (int)datafile->fileno,
1763 ctx->config.page_type,
1764 journalfile_migrate_to_v2_callback,
1765 (void *)datafile->journalfile,
1766 true);
1767
1768 if (is_last_file)
1769 ctx->loading.create_new_datafile_pair = true;
1770
1771 return 0;
1772
1773 cleanup:
1774 CLOSE_FILE(ctx, path, file, ret);
1775 return error;
1776 }