master
c 1,479 lines 57.4 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "database/engine/rrddiskprotocol.h"
4 #include "rrdengine.h"
5 #include "dbengine-compression.h"
6
7 /* Default global database instance */
8 struct rrdengine_instance multidb_ctx_storage_tier0 = { 0 };
9 struct rrdengine_instance multidb_ctx_storage_tier1 = { 0 };
10 struct rrdengine_instance multidb_ctx_storage_tier2 = { 0 };
11 struct rrdengine_instance multidb_ctx_storage_tier3 = { 0 };
12 struct rrdengine_instance multidb_ctx_storage_tier4 = { 0 };
13
14 #define mrg_metric_ctx(metric) (struct rrdengine_instance *)mrg_metric_section(main_mrg, metric)
15
16 #if RRD_STORAGE_TIERS != 5
17 #error RRD_STORAGE_TIERS is not 5 - you need to add allocations here
18 #endif
19 struct rrdengine_instance *multidb_ctx[RRD_STORAGE_TIERS] = { 0 };
20 uint8_t tier_page_type[RRD_STORAGE_TIERS] = {
21 RRDENG_PAGE_TYPE_GORILLA_32BIT,
22 RRDENG_PAGE_TYPE_ARRAY_TIER1,
23 RRDENG_PAGE_TYPE_ARRAY_TIER1,
24 RRDENG_PAGE_TYPE_ARRAY_TIER1,
25 RRDENG_PAGE_TYPE_ARRAY_TIER1};
26
27 #if defined(ENV32BIT)
28 size_t tier_page_size[RRD_STORAGE_TIERS] = {2048, 1024, 192, 192, 192};
29 size_t tier_quota_mb[RRD_STORAGE_TIERS] = {512, 512, 512, 0, 0};
30 #else
31 size_t tier_page_size[RRD_STORAGE_TIERS] = {4096, 2048, 384, 384, 384};
32 size_t tier_quota_mb[RRD_STORAGE_TIERS] = {1024, 1024, 1024, 128, 64};
33 #endif
34
35 #if RRDENG_PAGE_TYPE_MAX != 2
36 #error PAGE_TYPE_MAX is not 2 - you need to add allocations here
37 #endif
38
39 size_t page_type_size[256] = {
40 [RRDENG_PAGE_TYPE_ARRAY_32BIT] = sizeof(storage_number),
41 [RRDENG_PAGE_TYPE_ARRAY_TIER1] = sizeof(storage_number_tier1_t),
42 [RRDENG_PAGE_TYPE_GORILLA_32BIT] = sizeof(storage_number)
43 };
44
45 static inline void initialize_single_ctx(struct rrdengine_instance *ctx) {
46 memset(ctx, 0, sizeof(*ctx));
47 netdata_rwlock_init(&ctx->datafiles.rwlock);
48 rw_spinlock_init(&ctx->njfv2idx.spinlock);
49 }
50
51 __attribute__((constructor)) void initialize_multidb_ctx(void) {
52 multidb_ctx[0] = &multidb_ctx_storage_tier0;
53 multidb_ctx[1] = &multidb_ctx_storage_tier1;
54 multidb_ctx[2] = &multidb_ctx_storage_tier2;
55 multidb_ctx[3] = &multidb_ctx_storage_tier3;
56 multidb_ctx[4] = &multidb_ctx_storage_tier4;
57
58 for(int i = 0; i < RRD_STORAGE_TIERS ; i++)
59 initialize_single_ctx(multidb_ctx[i]);
60 }
61
62 uint64_t dbengine_out_of_memory_protection = 0;
63 bool dbengine_use_all_ram_for_caches = false;
64 int db_engine_journal_check = 0;
65 bool new_dbengine_defaults = false;
66 bool legacy_multihost_db_space = false;
67 int default_rrdeng_disk_quota_mb = RRDENG_DEFAULT_TIER_DISK_SPACE_MB;
68 int default_multidb_disk_quota_mb = RRDENG_DEFAULT_TIER_DISK_SPACE_MB;
69 RRD_BACKFILL default_backfill = RRD_BACKFILL_NEW;
70
71 #if defined(ENV32BIT)
72 int default_rrdeng_page_cache_mb = 16;
73 int default_rrdeng_extent_cache_mb = 0;
74 #else
75 int default_rrdeng_page_cache_mb = 32;
76 int default_rrdeng_extent_cache_mb = 0;
77 #endif
78
79 // ----------------------------------------------------------------------------
80 // metrics groups
81
82 static inline void rrdeng_page_alignment_acquire(struct pg_alignment *pa) {
83 if(unlikely(!pa)) return;
84 __atomic_add_fetch(&pa->refcount, 1, __ATOMIC_SEQ_CST);
85 }
86
87 static inline bool rrdeng_page_alignment_release(struct pg_alignment *pa) {
88 if(unlikely(!pa)) return true;
89
90 if(__atomic_sub_fetch(&pa->refcount, 1, __ATOMIC_SEQ_CST) == 0) {
91 freez(pa);
92 return true;
93 }
94
95 return false;
96 }
97
98 // charts call this
99 STORAGE_METRICS_GROUP *rrdeng_metrics_group_get(STORAGE_INSTANCE *si __maybe_unused, nd_uuid_t *uuid __maybe_unused) {
100 struct pg_alignment *pa = callocz(1, sizeof(struct pg_alignment));
101 rrdeng_page_alignment_acquire(pa);
102 return (STORAGE_METRICS_GROUP *)pa;
103 }
104
105 // charts call this
106 void rrdeng_metrics_group_release(STORAGE_INSTANCE *si __maybe_unused, STORAGE_METRICS_GROUP *smg) {
107 if(unlikely(!smg)) return;
108
109 struct pg_alignment *pa = (struct pg_alignment *)smg;
110 rrdeng_page_alignment_release(pa);
111 }
112
113 // ----------------------------------------------------------------------------
114 // metric handle for legacy dbs
115
116 /* This UUID is not unique across hosts */
117 void rrdeng_generate_unittest_uuid(const char *dim_id, const char *chart_id, nd_uuid_t *ret_uuid)
118 {
119 CLEAN_BUFFER *wb = buffer_create(100, NULL);
120 buffer_sprintf(wb,"%s.%s", dim_id, chart_id);
121 ND_UUID uuid = UUID_generate_from_hash(buffer_tostring(wb), buffer_strlen(wb));
122 uuid_copy(*ret_uuid, uuid.uuid);
123 }
124
125 static METRIC *rrdeng_metric_unittest(STORAGE_INSTANCE *si, const char *rd_id, const char *st_id) {
126 struct rrdengine_instance *ctx = (struct rrdengine_instance *)si;
127 nd_uuid_t legacy_uuid;
128 rrdeng_generate_unittest_uuid(rd_id, st_id, &legacy_uuid);
129 return mrg_metric_get_and_acquire_by_uuid(main_mrg, &legacy_uuid, (Word_t)ctx);
130 }
131
132 // ----------------------------------------------------------------------------
133 // metric handle
134
135 void rrdeng_metric_release(STORAGE_METRIC_HANDLE *smh) {
136 METRIC *metric = (METRIC *)smh;
137 mrg_metric_release(main_mrg, metric);
138 }
139
140 STORAGE_METRIC_HANDLE *rrdeng_metric_dup(STORAGE_METRIC_HANDLE *smh) {
141 METRIC *metric = (METRIC *)smh;
142 return (STORAGE_METRIC_HANDLE *) mrg_metric_dup(main_mrg, metric);
143 }
144
145 STORAGE_METRIC_HANDLE *rrdeng_metric_get_by_uuid(STORAGE_INSTANCE *si, nd_uuid_t *uuid) {
146 struct rrdengine_instance *ctx = (struct rrdengine_instance *)si;
147 return (STORAGE_METRIC_HANDLE *)mrg_metric_get_and_acquire_by_uuid(main_mrg, uuid, (Word_t)ctx);
148 }
149
150 STORAGE_METRIC_HANDLE *rrdeng_metric_get_by_id(STORAGE_INSTANCE *si, UUIDMAP_ID id) {
151 struct rrdengine_instance *ctx = (struct rrdengine_instance *)si;
152 return (STORAGE_METRIC_HANDLE *)mrg_metric_get_and_acquire_by_id(main_mrg, id, (Word_t)ctx);
153 }
154
155 static METRIC *rrdeng_metric_create(STORAGE_INSTANCE *si, nd_uuid_t *uuid) {
156 internal_fatal(!si, "DBENGINE: STORAGE_INSTANCE is NULL");
157
158 struct rrdengine_instance *ctx = (struct rrdengine_instance *)si;
159 MRG_ENTRY entry = {
160 .uuid = uuid,
161 .section = (Word_t)ctx,
162 .first_time_s = 0,
163 .last_time_s = 0,
164 .latest_update_every_s = 0,
165 };
166
167 bool added;
168 METRIC *metric = mrg_metric_add_and_acquire(main_mrg, entry, &added);
169 return metric;
170 }
171
172 STORAGE_METRIC_HANDLE *rrdeng_metric_get_or_create(RRDDIM *rd, STORAGE_INSTANCE *si) {
173 struct rrdengine_instance *ctx = (struct rrdengine_instance *)si;
174 METRIC *metric;
175
176 metric = mrg_metric_get_and_acquire_by_id(main_mrg, rd->uuid, (Word_t) ctx);
177
178 if(unlikely(!metric)) {
179 if(unlikely(unittest_running)) {
180 metric = rrdeng_metric_unittest(si, rrddim_id(rd), rrdset_id(rd->rrdset));
181 if (metric)
182 rd->uuid = mrg_metric_uuidmap_id_dup(main_mrg, metric);
183 }
184
185 if(likely(!metric))
186 metric = rrdeng_metric_create(si, uuidmap_uuid_ptr(rd->uuid));
187 }
188
189 #ifdef NETDATA_INTERNAL_CHECKS
190 if(!uuid_eq(*uuidmap_uuid_ptr(rd->uuid), *mrg_metric_uuid(main_mrg, metric))) {
191 char uuid1[UUID_STR_LEN + 1];
192 char uuid2[UUID_STR_LEN + 1];
193
194 uuid_unparse(*uuidmap_uuid_ptr(rd->uuid), uuid1);
195 uuid_unparse(*mrg_metric_uuid(main_mrg, metric), uuid2);
196 fatal("DBENGINE: uuids do not match, asked for metric '%s', but got metric '%s'", uuid1, uuid2);
197 }
198
199 if(mrg_metric_ctx(metric) != ctx)
200 fatal("DBENGINE: mixed up db instances, asked for metric from %p, got from %p",
201 ctx, mrg_metric_ctx(metric));
202 #endif
203
204 return (STORAGE_METRIC_HANDLE *)metric;
205 }
206
207
208 // ----------------------------------------------------------------------------
209 // collect ops
210
211 static inline void check_and_fix_mrg_update_every(struct rrdeng_collect_handle *handle) {
212 if(unlikely((uint32_t)(handle->update_every_ut / USEC_PER_SEC) != mrg_metric_get_update_every_s(main_mrg, handle->metric))) {
213 internal_error(true, "DBENGINE: collection handle has update every %u, but the metric registry has %u. Fixing it.",
214 (uint32_t)(handle->update_every_ut / USEC_PER_SEC), mrg_metric_get_update_every_s(main_mrg, handle->metric));
215
216 if(unlikely(!handle->update_every_ut))
217 handle->update_every_ut = (usec_t)mrg_metric_get_update_every_s(main_mrg, handle->metric) * USEC_PER_SEC;
218 else
219 mrg_metric_set_update_every(main_mrg, handle->metric, (uint32_t)(handle->update_every_ut / USEC_PER_SEC));
220 }
221 }
222
223 static inline bool check_completed_page_consistency(struct rrdeng_collect_handle *handle __maybe_unused) {
224 #ifdef NETDATA_INTERNAL_CHECKS
225 if (unlikely(!handle->pgc_page || !handle->page_entries_max || !handle->page_position || !handle->page_end_time_ut))
226 return false;
227
228 struct rrdengine_instance *ctx = mrg_metric_ctx(handle->metric);
229
230 nd_uuid_t *uuid = mrg_metric_uuid(main_mrg, handle->metric);
231 time_t start_time_s = pgc_page_start_time_s(handle->pgc_page);
232 time_t end_time_s = pgc_page_end_time_s(handle->pgc_page);
233 uint32_t update_every_s = pgc_page_update_every_s(handle->pgc_page);
234 size_t page_length = handle->page_position * CTX_POINT_SIZE_BYTES(ctx);
235 size_t entries = handle->page_position;
236 time_t overwrite_zero_update_every_s = (time_t)(handle->update_every_ut / USEC_PER_SEC);
237
238 if(end_time_s > max_acceptable_collected_time())
239 handle->page_flags |= RRDENG_PAGE_COMPLETED_IN_FUTURE;
240
241 VALIDATED_PAGE_DESCRIPTOR vd = validate_page(
242 uuid,
243 start_time_s,
244 end_time_s,
245 update_every_s,
246 page_length,
247 ctx->config.page_type,
248 entries,
249 0, // do not check for future timestamps - we inherit the timestamps of the children
250 overwrite_zero_update_every_s,
251 false,
252 "collected",
253 handle->page_flags);
254
255 return vd.is_valid;
256 #else
257 return true;
258 #endif
259 }
260
261 /*
262 * Gets a handle for storing metrics to the database.
263 * The handle must be released with rrdeng_store_metric_final().
264 */
265 STORAGE_COLLECT_HANDLE *rrdeng_store_metric_init(STORAGE_METRIC_HANDLE *smh, uint32_t update_every, STORAGE_METRICS_GROUP *smg) {
266 METRIC *metric = (METRIC *)smh;
267 struct rrdengine_instance *ctx = mrg_metric_ctx(metric);
268
269 RRDENG_COLLECT_HANDLE_OPTIONS options = 0;
270 #ifdef NETDATA_INTERNAL_CHECKS
271 bool is_1st_metric_writer = true;
272 if(!mrg_metric_set_writer(main_mrg, metric)) {
273 is_1st_metric_writer = false;
274 char uuid[UUID_STR_LEN + 1];
275 uuid_unparse(*mrg_metric_uuid(main_mrg, metric), uuid);
276 netdata_log_error("DBENGINE: metric '%s' is already collected and should not be collected twice - expect gaps on the charts", uuid);
277 }
278 if(is_1st_metric_writer)
279 options = RRDENG_1ST_METRIC_WRITER;
280 else
281 __atomic_add_fetch(&ctx->atomic.collectors_running_duplicate, 1, __ATOMIC_RELAXED);
282
283 #endif
284
285 metric = mrg_metric_dup(main_mrg, metric);
286 if(!metric) {
287 #ifdef NETDATA_INTERNAL_CHECKS
288 if(is_1st_metric_writer)
289 mrg_metric_clear_writer(main_mrg, (METRIC *)smh);
290 else
291 __atomic_sub_fetch(&ctx->atomic.collectors_running_duplicate, 1, __ATOMIC_RELAXED);
292 #endif
293 return NULL;
294 }
295
296 struct rrdeng_collect_handle *handle;
297
298 handle = callocz(1, sizeof(struct rrdeng_collect_handle));
299 handle->common.seb = STORAGE_ENGINE_BACKEND_DBENGINE;
300 handle->metric = metric;
301
302 handle->pgc_page = NULL;
303 handle->page_data = NULL;
304
305 handle->page_position = 0;
306 handle->page_entries_max = 0;
307 handle->update_every_ut = (usec_t)update_every * USEC_PER_SEC;
308 handle->options = options;
309
310 __atomic_add_fetch(&ctx->atomic.collectors_running, 1, __ATOMIC_RELAXED);
311
312 mrg_metric_set_update_every(main_mrg, metric, update_every);
313
314 handle->alignment = (struct pg_alignment *)smg;
315 rrdeng_page_alignment_acquire(handle->alignment);
316
317 // this is important!
318 // if we don't set the page_end_time_ut during the first collection
319 // data collection may be able to go back in time and during the addition of new pages
320 // clean pages may be found matching ours!
321
322 time_t db_first_time_s, db_last_time_s;
323 mrg_metric_get_retention(main_mrg, metric, &db_first_time_s, &db_last_time_s, NULL);
324 handle->page_end_time_ut = (usec_t)db_last_time_s * USEC_PER_SEC;
325
326 return (STORAGE_COLLECT_HANDLE *)handle;
327 }
328
329 void rrdeng_store_metric_flush_current_page(STORAGE_COLLECT_HANDLE *sch) {
330 struct rrdeng_collect_handle *handle = (struct rrdeng_collect_handle *)sch;
331
332 if (unlikely(!handle->pgc_page))
333 return;
334
335 if(pgd_is_empty(handle->page_data))
336 pgc_page_to_clean_evict_or_release(main_cache, handle->pgc_page);
337
338 else {
339 check_completed_page_consistency(handle);
340 mrg_metric_set_clean_latest_time_s(main_mrg, handle->metric, pgc_page_end_time_s(handle->pgc_page));
341
342 struct rrdengine_instance *ctx = mrg_metric_ctx(handle->metric);
343 time_t start_time_s = pgc_page_start_time_s(handle->pgc_page);
344 time_t end_time_s = pgc_page_end_time_s(handle->pgc_page);
345 uint32_t update_every_s = mrg_metric_get_update_every_s(main_mrg, handle->metric);
346 if (end_time_s && start_time_s && end_time_s > start_time_s && update_every_s) {
347 uint64_t add_samples = (end_time_s - start_time_s) / update_every_s;
348 __atomic_add_fetch(&ctx->atomic.samples, add_samples, __ATOMIC_RELAXED);
349 }
350
351 pgc_page_hot_to_dirty_and_release(main_cache, handle->pgc_page, false);
352 }
353
354 mrg_metric_set_hot_latest_time_s(main_mrg, handle->metric, 0);
355
356 handle->pgc_page = NULL;
357 handle->page_flags = 0;
358 handle->page_position = 0;
359 handle->page_entries_max = 0;
360 handle->page_data = NULL;
361
362 // important!
363 // we should never zero page end time ut, because this will allow
364 // collection to go back in time
365 // handle->page_end_time_ut = 0;
366 // handle->page_start_time_ut;
367
368 check_and_fix_mrg_update_every(handle);
369
370 timing_step(TIMING_STEP_DBENGINE_FLUSH_PAGE);
371 }
372
373 static void rrdeng_store_metric_create_new_page(struct rrdeng_collect_handle *handle,
374 struct rrdengine_instance *ctx,
375 usec_t point_in_time_ut,
376 PGD *data) {
377 time_t point_in_time_s = (time_t)(point_in_time_ut / USEC_PER_SEC);
378 const uint32_t update_every_s = (uint32_t)(handle->update_every_ut / USEC_PER_SEC);
379
380 PGC_ENTRY page_entry = {
381 .section = (Word_t) ctx,
382 .metric_id = mrg_metric_id(main_mrg, handle->metric),
383 .start_time_s = point_in_time_s,
384 .end_time_s = point_in_time_s,
385 .size = pgd_memory_footprint(data),
386 .data = data,
387 .update_every_s = update_every_s,
388 .hot = true
389 };
390
391 size_t conflicts = 0;
392 bool added = true;
393 PGC_PAGE *pgc_page = pgc_page_add_and_acquire(main_cache, page_entry, &added);
394 while (unlikely(!added)) {
395 conflicts++;
396
397 char uuid[UUID_STR_LEN + 1];
398 uuid_unparse(*mrg_metric_uuid(main_mrg, handle->metric), uuid);
399
400 #ifdef NETDATA_INTERNAL_CHECKS
401 internal_error(true,
402 #else
403 nd_log_limit_static_global_var(erl, 1, 0);
404 nd_log_limit(&erl, NDLS_DAEMON, NDLP_WARNING,
405 #endif
406 "DBENGINE: metric '%s' new page from %ld to %ld, update every %u, has a conflict in main cache "
407 "with existing %s%s page from %ld to %ld, update every %u - "
408 "is it collected more than once?",
409 uuid,
410 page_entry.start_time_s, page_entry.end_time_s, page_entry.update_every_s,
411 pgc_is_page_hot(pgc_page) ? "hot" : "not-hot",
412 pgc_page_data(pgc_page) == PGD_EMPTY ? " gap" : "",
413 pgc_page_start_time_s(pgc_page), pgc_page_end_time_s(pgc_page), pgc_page_update_every_s(pgc_page)
414 );
415
416 pgc_page_release(main_cache, pgc_page);
417
418 point_in_time_ut -= handle->update_every_ut;
419 point_in_time_s = (time_t)(point_in_time_ut / USEC_PER_SEC);
420 page_entry.start_time_s = point_in_time_s;
421 page_entry.end_time_s = point_in_time_s;
422 pgc_page = pgc_page_add_and_acquire(main_cache, page_entry, &added);
423 }
424
425 handle->page_entries_max = pgd_capacity(data);
426 handle->page_start_time_ut = point_in_time_ut;
427 handle->page_end_time_ut = point_in_time_ut;
428 handle->page_position = 1; // zero is already in our data
429 handle->pgc_page = pgc_page;
430 handle->page_flags = conflicts? RRDENG_PAGE_CONFLICT : 0;
431
432 if(point_in_time_s > max_acceptable_collected_time())
433 handle->page_flags |= RRDENG_PAGE_CREATED_IN_FUTURE;
434
435 check_and_fix_mrg_update_every(handle);
436
437 timing_step(TIMING_STEP_DBENGINE_CREATE_NEW_PAGE);
438 }
439
440 static size_t aligned_allocation_entries(size_t max_slots, size_t target_slot, time_t now_s) {
441 size_t slots = target_slot;
442 size_t pos = (now_s % max_slots);
443
444 if(pos > slots)
445 slots += max_slots - pos;
446
447 else if(pos < slots)
448 slots -= pos;
449
450 else
451 slots = max_slots;
452
453 return slots;
454 }
455
456 static PGD *rrdeng_alloc_new_page_data(struct rrdeng_collect_handle *handle, usec_t point_in_time_ut) {
457 struct rrdengine_instance *ctx = mrg_metric_ctx(handle->metric);
458
459 PGD *d = NULL;
460
461 size_t max_size = tier_page_size[ctx->config.tier];
462 size_t max_slots = max_size / CTX_POINT_SIZE_BYTES(ctx);
463
464 size_t slots = aligned_allocation_entries(
465 max_slots,
466 indexing_partition((Word_t) handle->alignment, max_slots),
467 (time_t) (point_in_time_ut / USEC_PER_SEC)
468 );
469
470 if(slots < max_slots / 3)
471 slots = max_slots / 3;
472
473 if(slots < 3)
474 slots = 3;
475
476 size_t size = slots * CTX_POINT_SIZE_BYTES(ctx); (void)size;
477
478 // internal_error(true, "PAGE ALLOC %zu bytes (%zu max)", size, max_size);
479
480 internal_fatal(slots < 3 || slots > max_slots, "ooops! wrong distribution of metrics across time");
481 internal_fatal(size > tier_page_size[ctx->config.tier] || size < CTX_POINT_SIZE_BYTES(ctx) * 2, "ooops! wrong page size");
482
483 switch (ctx->config.page_type) {
484 case RRDENG_PAGE_TYPE_ARRAY_32BIT:
485 case RRDENG_PAGE_TYPE_ARRAY_TIER1:
486 case RRDENG_PAGE_TYPE_GORILLA_32BIT:
487 d = pgd_create(ctx->config.page_type, slots);
488 break;
489 default:
490 fatal("Unknown page type: %uc\n", ctx->config.page_type);
491 }
492
493 timing_step(TIMING_STEP_DBENGINE_PAGE_ALLOC);
494 return d;
495 }
496
497 static ALWAYS_INLINE_HOT void rrdeng_store_metric_append_point(STORAGE_COLLECT_HANDLE *sch,
498 const usec_t point_in_time_ut,
499 const NETDATA_DOUBLE n,
500 const NETDATA_DOUBLE min_value,
501 const NETDATA_DOUBLE max_value,
502 const uint16_t count,
503 const uint16_t anomaly_count,
504 const SN_FLAGS flags)
505 {
506 struct rrdeng_collect_handle *handle = (struct rrdeng_collect_handle *)sch;
507 struct rrdengine_instance *ctx = mrg_metric_ctx(handle->metric);
508
509 if(unlikely(!handle->page_data))
510 handle->page_data = rrdeng_alloc_new_page_data(handle, point_in_time_ut);
511
512 timing_step(TIMING_STEP_DBENGINE_CHECK_DATA);
513
514 size_t additional_bytes = pgd_append_point(handle->page_data,
515 point_in_time_ut,
516 n, min_value, max_value, count, anomaly_count, flags,
517 handle->page_position);
518
519 timing_step(TIMING_STEP_DBENGINE_PACK);
520
521 if(unlikely(!handle->pgc_page)) {
522 rrdeng_store_metric_create_new_page(handle, ctx, point_in_time_ut, handle->page_data);
523 // handle->position is set to 1 already
524 }
525 else {
526 // update an existing page
527 pgc_page_hot_set_end_time_s(main_cache, handle->pgc_page,
528 (time_t) (point_in_time_ut / USEC_PER_SEC), additional_bytes);
529 handle->page_end_time_ut = point_in_time_ut;
530
531 if(unlikely(++handle->page_position >= handle->page_entries_max)) {
532 internal_fatal(handle->page_position > handle->page_entries_max, "DBENGINE: exceeded page max number of points");
533 handle->page_flags |= RRDENG_PAGE_FULL;
534 rrdeng_store_metric_flush_current_page(sch);
535 }
536 }
537
538 timing_step(TIMING_STEP_DBENGINE_PAGE_FIN);
539
540 // update the metric information
541 mrg_metric_set_hot_latest_time_s(main_mrg, handle->metric, (time_t) (point_in_time_ut / USEC_PER_SEC));
542
543 timing_step(TIMING_STEP_DBENGINE_MRG_UPDATE);
544 }
545
546 static void store_metric_next_error_log(struct rrdeng_collect_handle *handle __maybe_unused, usec_t point_in_time_ut __maybe_unused, const char *msg __maybe_unused) {
547 #ifdef NETDATA_INTERNAL_CHECKS
548 time_t point_in_time_s = (time_t)(point_in_time_ut / USEC_PER_SEC);
549 char uuid[UUID_STR_LEN + 1];
550 uuid_unparse(*mrg_metric_uuid(main_mrg, handle->metric), uuid);
551
552 BUFFER *wb = NULL;
553 if(handle->pgc_page && handle->page_flags) {
554 wb = buffer_create(0, NULL);
555 collect_page_flags_to_buffer(wb, handle->page_flags);
556 }
557
558 nd_log_limit_static_global_var(erl, 1, 0);
559 nd_log_limit(&erl, NDLS_DAEMON, NDLP_NOTICE,
560 "DBENGINE: metric '%s' collected point at %ld, %s last collection at %ld, "
561 "update every %ld, %s page from %ld to %ld, position %u (of %u), flags: %s",
562 uuid,
563 point_in_time_s,
564 msg,
565 (time_t)(handle->page_end_time_ut / USEC_PER_SEC),
566 (time_t)(handle->update_every_ut / USEC_PER_SEC),
567 handle->pgc_page ? "current" : "*LAST*",
568 (time_t)(handle->page_start_time_ut / USEC_PER_SEC),
569 (time_t)(handle->page_end_time_ut / USEC_PER_SEC),
570 handle->page_position, handle->page_entries_max,
571 wb ? buffer_tostring(wb) : ""
572 );
573
574 buffer_free(wb);
575 #else
576 ;
577 #endif
578 }
579
580 ALWAYS_INLINE_HOT void rrdeng_store_metric_next(
581 STORAGE_COLLECT_HANDLE *sch,
582 const usec_t point_in_time_ut,
583 const NETDATA_DOUBLE n,
584 const NETDATA_DOUBLE min_value,
585 const NETDATA_DOUBLE max_value,
586 const uint16_t count,
587 const uint16_t anomaly_count,
588 const SN_FLAGS flags)
589 {
590 timing_step(TIMING_STEP_RRDSET_STORE_METRIC);
591
592 struct rrdeng_collect_handle *handle = (struct rrdeng_collect_handle *)sch;
593
594 #ifdef NETDATA_INTERNAL_CHECKS
595 if(unlikely(point_in_time_ut > (usec_t)max_acceptable_collected_time() * USEC_PER_SEC))
596 handle->page_flags |= RRDENG_PAGE_FUTURE_POINT;
597 #endif
598
599 usec_t delta_ut = point_in_time_ut - handle->page_end_time_ut;
600
601 if(likely(delta_ut == handle->update_every_ut)) {
602 // happy path
603 ;
604 }
605 else if(unlikely(point_in_time_ut > handle->page_end_time_ut)) {
606 if(handle->pgc_page) {
607 if (unlikely(delta_ut < handle->update_every_ut)) {
608 handle->page_flags |= RRDENG_PAGE_STEP_TOO_SMALL;
609 rrdeng_store_metric_flush_current_page(sch);
610 }
611 else if (unlikely(delta_ut % handle->update_every_ut)) {
612 handle->page_flags |= RRDENG_PAGE_STEP_UNALIGNED;
613 rrdeng_store_metric_flush_current_page(sch);
614 }
615 else {
616 size_t points_gap = delta_ut / handle->update_every_ut;
617 size_t page_remaining_points = handle->page_entries_max - handle->page_position;
618
619 if (points_gap >= page_remaining_points) {
620 handle->page_flags |= RRDENG_PAGE_BIG_GAP;
621 rrdeng_store_metric_flush_current_page(sch);
622 }
623 else {
624 // loop to fill the gap
625 handle->page_flags |= RRDENG_PAGE_GAP;
626
627 usec_t stop_ut = point_in_time_ut - handle->update_every_ut;
628 for (usec_t this_ut = handle->page_end_time_ut + handle->update_every_ut;
629 this_ut <= stop_ut;
630 this_ut = handle->page_end_time_ut + handle->update_every_ut) {
631 rrdeng_store_metric_append_point(
632 sch,
633 this_ut,
634 NAN, NAN, NAN,
635 1, 0,
636 SN_EMPTY_SLOT);
637 }
638 }
639 }
640 }
641 }
642 else if(unlikely(point_in_time_ut < handle->page_end_time_ut)) {
643 handle->page_flags |= RRDENG_PAGE_PAST_COLLECTION;
644 store_metric_next_error_log(handle, point_in_time_ut, "is older than the");
645 return;
646 }
647
648 else /* if(unlikely(point_in_time_ut == handle->page_end_time_ut)) */ {
649 handle->page_flags |= RRDENG_PAGE_REPEATED_COLLECTION;
650 store_metric_next_error_log(handle, point_in_time_ut, "is at the same time as the");
651 return;
652 }
653
654 timing_step(TIMING_STEP_DBENGINE_FIRST_CHECK);
655
656 rrdeng_store_metric_append_point(sch,
657 point_in_time_ut,
658 n, min_value, max_value,
659 count, anomaly_count,
660 flags);
661 }
662
663 /*
664 * Releases the database reference from the handle for storing metrics.
665 * Returns 1 if it's safe to delete the dimension.
666 */
667 int rrdeng_store_metric_finalize(STORAGE_COLLECT_HANDLE *sch) {
668 struct rrdeng_collect_handle *handle = (struct rrdeng_collect_handle *)sch;
669 struct rrdengine_instance *ctx = mrg_metric_ctx(handle->metric);
670
671 handle->page_flags |= RRDENG_PAGE_COLLECT_FINALIZE;
672 rrdeng_store_metric_flush_current_page(sch);
673 rrdeng_page_alignment_release(handle->alignment);
674
675 __atomic_sub_fetch(&ctx->atomic.collectors_running, 1, __ATOMIC_RELAXED);
676
677 #ifdef NETDATA_INTERNAL_CHECKS
678 if(!(handle->options & RRDENG_1ST_METRIC_WRITER))
679 __atomic_sub_fetch(&ctx->atomic.collectors_running_duplicate, 1, __ATOMIC_RELAXED);
680
681 if((handle->options & RRDENG_1ST_METRIC_WRITER) && !mrg_metric_clear_writer(main_mrg, handle->metric))
682 internal_fatal(true, "DBENGINE: metric is already released");
683 #endif
684
685 time_t first_time_s, last_time_s;
686 mrg_metric_get_retention(main_mrg, handle->metric, &first_time_s, &last_time_s, NULL);
687
688 mrg_metric_release(main_mrg, handle->metric);
689 freez(handle);
690
691 if(!first_time_s && !last_time_s)
692 return 1;
693
694 return 0;
695 }
696
697 void rrdeng_store_metric_change_collection_frequency(STORAGE_COLLECT_HANDLE *sch, int update_every) {
698 struct rrdeng_collect_handle *handle = (struct rrdeng_collect_handle *)sch;
699 check_and_fix_mrg_update_every(handle);
700
701 METRIC *metric = handle->metric;
702 usec_t update_every_ut = (usec_t)update_every * USEC_PER_SEC;
703
704 if(update_every_ut == handle->update_every_ut)
705 return;
706
707 handle->page_flags |= RRDENG_PAGE_UPDATE_EVERY_CHANGE;
708 rrdeng_store_metric_flush_current_page(sch);
709 mrg_metric_set_update_every(main_mrg, metric, update_every);
710 handle->update_every_ut = update_every_ut;
711 }
712
713 // ----------------------------------------------------------------------------
714 // query ops
715
716 #ifdef NETDATA_INTERNAL_CHECKS
717 SPINLOCK global_query_handle_spinlock = SPINLOCK_INITIALIZER;
718 static struct rrdeng_query_handle *global_query_handle_ll = NULL;
719 static ALWAYS_INLINE void register_query_handle(struct rrdeng_query_handle *handle) {
720 handle->query_pid = gettid_cached();
721 handle->started_time_s = now_realtime_sec();
722
723 spinlock_lock(&global_query_handle_spinlock);
724 DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(global_query_handle_ll, handle, prev, next);
725 spinlock_unlock(&global_query_handle_spinlock);
726 }
727 static ALWAYS_INLINE void unregister_query_handle(struct rrdeng_query_handle *handle) {
728 spinlock_lock(&global_query_handle_spinlock);
729 DOUBLE_LINKED_LIST_REMOVE_ITEM_UNSAFE(global_query_handle_ll, handle, prev, next);
730 spinlock_unlock(&global_query_handle_spinlock);
731 }
732 #else
733 static void register_query_handle(struct rrdeng_query_handle *handle __maybe_unused) {
734 ;
735 }
736 static void unregister_query_handle(struct rrdeng_query_handle *handle __maybe_unused) {
737 ;
738 }
739 #endif
740
741 /*
742 * Gets a handle for loading metrics from the database.
743 * The handle must be released with rrdeng_load_metric_final().
744 */
745 ALWAYS_INLINE_HOT void rrdeng_load_metric_init(
746 STORAGE_METRIC_HANDLE *smh,
747 struct storage_engine_query_handle *seqh,
748 time_t start_time_s,
749 time_t end_time_s,
750 STORAGE_PRIORITY priority)
751 {
752 usec_t started_ut = now_monotonic_usec();
753
754 METRIC *metric = (METRIC *)smh;
755 struct rrdengine_instance *ctx = mrg_metric_ctx(metric);
756 struct rrdeng_query_handle *handle;
757
758 handle = rrdeng_query_handle_get();
759 register_query_handle(handle);
760
761 if (unlikely(priority < STORAGE_PRIORITY_HIGH))
762 priority = STORAGE_PRIORITY_HIGH;
763 else if (unlikely(priority >= STORAGE_PRIORITY_INTERNAL_MAX_DONT_USE))
764 priority = STORAGE_PRIORITY_INTERNAL_MAX_DONT_USE - 1;
765
766 handle->ctx = ctx;
767 handle->metric = metric;
768 handle->priority = priority;
769
770 // IMPORTANT!
771 // It is crucial not to exceed the db boundaries, because dbengine
772 // now has gap caching, so when a gap is detected a negative page
773 // is inserted into the main cache, to avoid scanning the journals
774 // again for pages matching the gap.
775
776 time_t db_first_time_s, db_last_time_s;
777 uint32_t db_update_every_s;
778 mrg_metric_get_retention(main_mrg, metric, &db_first_time_s, &db_last_time_s, &db_update_every_s);
779
780 if(is_page_in_time_range(start_time_s, end_time_s, db_first_time_s, db_last_time_s) == PAGE_IS_IN_RANGE) {
781 handle->start_time_s = MAX(start_time_s, db_first_time_s);
782 handle->end_time_s = MIN(end_time_s, db_last_time_s);
783 handle->now_s = handle->start_time_s;
784
785 handle->dt_s = db_update_every_s;
786 if (!handle->dt_s) {
787 handle->dt_s = nd_profile.update_every;
788 mrg_metric_set_update_every_s_if_zero(main_mrg, metric, nd_profile.update_every);
789 }
790
791 seqh->handle = (STORAGE_QUERY_HANDLE *) handle;
792 seqh->start_time_s = handle->start_time_s;
793 seqh->end_time_s = handle->end_time_s;
794 seqh->priority = priority;
795 seqh->seb = STORAGE_ENGINE_BACKEND_DBENGINE;
796
797 pg_cache_preload(handle);
798
799 time_and_count_add(&rrdeng_cache_efficiency_stats.query_time_init, now_monotonic_usec() - started_ut);
800 }
801 else {
802 handle->start_time_s = start_time_s;
803 handle->end_time_s = end_time_s;
804 handle->now_s = start_time_s;
805 handle->dt_s = db_update_every_s;
806
807 seqh->handle = (STORAGE_QUERY_HANDLE *) handle;
808 seqh->start_time_s = handle->start_time_s;
809 seqh->end_time_s = 0;
810 seqh->priority = priority;
811 seqh->seb = STORAGE_ENGINE_BACKEND_DBENGINE;
812 }
813 }
814
815 static ALWAYS_INLINE_HOT bool rrdeng_load_page_next(struct storage_engine_query_handle *seqh, bool debug_this __maybe_unused) {
816 struct rrdeng_query_handle *handle = (struct rrdeng_query_handle *)seqh->handle;
817 struct rrdengine_instance *ctx = mrg_metric_ctx(handle->metric);
818
819 if (likely(handle->page)) {
820 // we have a page to release
821 pgc_page_release(main_cache, handle->page);
822 handle->page = NULL;
823 pgdc_reset(&handle->pgdc, NULL, UINT32_MAX);
824 }
825
826 if (unlikely(handle->now_s > seqh->end_time_s))
827 return false;
828
829 size_t entries = 0;
830 handle->page = pg_cache_lookup_next(ctx, handle->pdc, handle->now_s, handle->dt_s, &entries);
831
832 internal_fatal(handle->page && (pgc_page_data(handle->page) == PGD_EMPTY || !entries),
833 "A page was returned, but it is empty - pg_cache_lookup_next() should be handling this case");
834
835 if (unlikely(!handle->page || pgc_page_data(handle->page) == PGD_EMPTY || !entries))
836 return false;
837
838 time_t page_start_time_s = pgc_page_start_time_s(handle->page);
839 time_t page_end_time_s = pgc_page_end_time_s(handle->page);
840 uint32_t page_update_every_s = pgc_page_update_every_s(handle->page);
841
842 unsigned position;
843 if(likely(handle->now_s >= page_start_time_s && handle->now_s <= page_end_time_s)) {
844
845 if(unlikely(entries == 1 || page_start_time_s == page_end_time_s || !page_update_every_s)) {
846 position = 0;
847 handle->now_s = page_start_time_s;
848 }
849 else {
850 position = (handle->now_s - page_start_time_s) * (entries - 1) / (page_end_time_s - page_start_time_s);
851 time_t point_end_time_s = page_start_time_s + position * (time_t) page_update_every_s;
852 while(point_end_time_s < handle->now_s && position + 1 < entries) {
853 // https://github.com/netdata/netdata/issues/14411
854 // we really need a while() here, because the delta may be
855 // 2 points at higher tiers
856 position++;
857 point_end_time_s = page_start_time_s + position * (time_t) page_update_every_s;
858 }
859 handle->now_s = point_end_time_s;
860 }
861
862 internal_fatal(position >= entries, "DBENGINE: wrong page position calculation");
863 }
864 else if(handle->now_s < page_start_time_s) {
865 handle->now_s = page_start_time_s;
866 position = 0;
867 }
868 else {
869 internal_fatal(true, "DBENGINE: this page is entirely in our past and should not be accepted for this query in the first place");
870 handle->now_s = page_end_time_s;
871 position = entries - 1;
872 }
873
874 handle->entries = entries;
875 handle->position = position;
876 handle->dt_s = page_update_every_s;
877
878 pgdc_reset(&handle->pgdc, pgc_page_data(handle->page), handle->position);
879
880 return true;
881 }
882
883 // Returns the metric and sets its timestamp into current_time
884 // IT IS REQUIRED TO **ALWAYS** SET ALL RETURN VALUES (current_time, end_time, flags)
885 // IT IS REQUIRED TO **ALWAYS** KEEP TRACK OF TIME, EVEN OUTSIDE THE DATABASE BOUNDARIES
886 ALWAYS_INLINE_HOT STORAGE_POINT rrdeng_load_metric_next(struct storage_engine_query_handle *seqh) {
887 struct rrdeng_query_handle *handle = (struct rrdeng_query_handle *)seqh->handle;
888 STORAGE_POINT sp;
889
890 if (unlikely(handle->now_s > seqh->end_time_s)) {
891 storage_point_empty(sp, handle->now_s - handle->dt_s, handle->now_s);
892 goto prepare_for_next_iteration;
893 }
894
895 if (unlikely(!handle->page || handle->position >= handle->entries)) {
896 // We need to get a new page
897
898 if (!rrdeng_load_page_next(seqh, false)) {
899 handle->now_s = seqh->end_time_s;
900 storage_point_empty(sp, handle->now_s - handle->dt_s, handle->now_s);
901 goto prepare_for_next_iteration;
902 }
903 }
904
905 sp.start_time_s = handle->now_s - handle->dt_s;
906 sp.end_time_s = handle->now_s;
907
908 pgdc_get_next_point(&handle->pgdc, handle->position, &sp);
909
910 prepare_for_next_iteration:
911 // internal_fatal(sp.end_time_s < seqh->start_time_s, "DBENGINE: this point is too old for this query");
912 internal_fatal(sp.end_time_s < handle->now_s, "DBENGINE: this point is too old for this point in time");
913
914 handle->now_s += handle->dt_s;
915 handle->position++;
916
917 return sp;
918 }
919
920 ALWAYS_INLINE int rrdeng_load_metric_is_finished(struct storage_engine_query_handle *seqh) {
921 struct rrdeng_query_handle *handle = (struct rrdeng_query_handle *)seqh->handle;
922 return (handle->now_s > seqh->end_time_s);
923 }
924
925 /*
926 * Releases the database reference from the handle for loading metrics.
927 */
928 ALWAYS_INLINE void rrdeng_load_metric_finalize(struct storage_engine_query_handle *seqh)
929 {
930 struct rrdeng_query_handle *handle = (struct rrdeng_query_handle *)seqh->handle;
931
932 if (handle->page) {
933 pgc_page_release(main_cache, handle->page);
934 pgdc_reset(&handle->pgdc, NULL, UINT32_MAX);
935 }
936
937 if(handle->pdc) {
938 __atomic_store_n(&handle->pdc->workers_should_stop, true, __ATOMIC_RELAXED);
939 pdc_release_and_destroy_if_unreferenced(handle->pdc, false, false);
940 }
941
942 unregister_query_handle(handle);
943 rrdeng_query_handle_release(handle);
944 seqh->handle = NULL;
945 }
946
947 ALWAYS_INLINE time_t rrdeng_load_align_to_optimal_before(struct storage_engine_query_handle *seqh) {
948 struct rrdeng_query_handle *handle = (struct rrdeng_query_handle *)seqh->handle;
949
950 if(handle->pdc) {
951 rrdeng_prep_wait(handle->pdc);
952 if (handle->pdc->optimal_end_time_s > seqh->end_time_s)
953 seqh->end_time_s = handle->pdc->optimal_end_time_s;
954 }
955
956 return seqh->end_time_s;
957 }
958
959 ALWAYS_INLINE time_t rrdeng_metric_latest_time(STORAGE_METRIC_HANDLE *smh) {
960 METRIC *metric = (METRIC *)smh;
961 time_t latest_time_s = 0;
962
963 if (metric)
964 latest_time_s = mrg_metric_get_latest_time_s(main_mrg, metric);
965
966 return latest_time_s;
967 }
968
969 ALWAYS_INLINE time_t rrdeng_metric_oldest_time(STORAGE_METRIC_HANDLE *smh) {
970 METRIC *metric = (METRIC *)smh;
971
972 time_t oldest_time_s = 0;
973 if (metric)
974 oldest_time_s = mrg_metric_get_first_time_s(main_mrg, metric);
975
976 return oldest_time_s;
977 }
978
979 bool rrdeng_metric_retention_by_uuid(STORAGE_INSTANCE *si, nd_uuid_t *dim_uuid, time_t *first_entry_s, time_t *last_entry_s) {
980 struct rrdengine_instance *ctx = (struct rrdengine_instance *)si;
981 if (unlikely(!ctx)) {
982 netdata_log_error("DBENGINE: invalid STORAGE INSTANCE to %s()", __FUNCTION__);
983 return false;
984 }
985
986 METRIC *metric = mrg_metric_get_and_acquire_by_uuid(main_mrg, dim_uuid, (Word_t)ctx);
987 if (unlikely(!metric))
988 return false;
989
990 mrg_metric_get_retention(main_mrg, metric, first_entry_s, last_entry_s, NULL);
991
992 mrg_metric_release(main_mrg, metric);
993
994 return true;
995 }
996
997 bool rrdeng_metric_retention_by_id(STORAGE_INSTANCE *si, UUIDMAP_ID id, time_t *first_entry_s, time_t *last_entry_s) {
998 struct rrdengine_instance *ctx = (struct rrdengine_instance *)si;
999 if (unlikely(!ctx)) {
1000 netdata_log_error("DBENGINE: invalid STORAGE INSTANCE to %s()", __FUNCTION__);
1001 return false;
1002 }
1003
1004 METRIC *metric = mrg_metric_get_and_acquire_by_id(main_mrg, id, (Word_t)ctx);
1005 if (unlikely(!metric))
1006 return false;
1007
1008 mrg_metric_get_retention(main_mrg, metric, first_entry_s, last_entry_s, NULL);
1009
1010 mrg_metric_release(main_mrg, metric);
1011
1012 return true;
1013 }
1014
1015 void rrdeng_metric_retention_delete_by_id(STORAGE_INSTANCE *si, UUIDMAP_ID id) {
1016 struct rrdengine_instance *ctx = (struct rrdengine_instance *)si;
1017 if (unlikely(!ctx)) {
1018 netdata_log_error("DBENGINE: invalid STORAGE INSTANCE to %s()", __FUNCTION__);
1019 return;
1020 }
1021
1022 METRIC *metric = mrg_metric_get_and_acquire_by_id(main_mrg, id, (Word_t)ctx);
1023 if (unlikely(!metric))
1024 return;
1025
1026 mrg_metric_clear_retention(main_mrg, metric);
1027 mrg_metric_release(main_mrg, metric);
1028 }
1029
1030 uint64_t rrdeng_disk_space_max(STORAGE_INSTANCE *si) {
1031 struct rrdengine_instance *ctx = (struct rrdengine_instance *)si;
1032 return ctx->config.max_disk_space;
1033 }
1034
1035 uint64_t rrdeng_disk_space_used(STORAGE_INSTANCE *si) {
1036 struct rrdengine_instance *ctx = (struct rrdengine_instance *)si;
1037 return __atomic_load_n(&ctx->atomic.current_disk_space, __ATOMIC_RELAXED);
1038 }
1039
1040 uint64_t rrdeng_metrics(STORAGE_INSTANCE *si) {
1041 struct rrdengine_instance *ctx = (struct rrdengine_instance *)si;
1042 return __atomic_load_n(&ctx->atomic.metrics, __ATOMIC_RELAXED);
1043 }
1044
1045 uint64_t rrdeng_samples(STORAGE_INSTANCE *si) {
1046 struct rrdengine_instance *ctx = (struct rrdengine_instance *)si;
1047 return __atomic_load_n(&ctx->atomic.samples, __ATOMIC_RELAXED);
1048 }
1049
1050 time_t rrdeng_global_first_time_s(STORAGE_INSTANCE *si) {
1051 struct rrdengine_instance *ctx = (struct rrdengine_instance *)si;
1052
1053 time_t t = __atomic_load_n(&ctx->atomic.first_time_s, __ATOMIC_RELAXED);
1054 if(t == LONG_MAX || t < 0)
1055 t = 0;
1056
1057 return t;
1058 }
1059
1060 size_t rrdeng_currently_collected_metrics(STORAGE_INSTANCE *si) {
1061 struct rrdengine_instance *ctx = (struct rrdengine_instance *)si;
1062 return __atomic_load_n(&ctx->atomic.collectors_running, __ATOMIC_RELAXED);
1063 }
1064
1065 /*
1066 * Gathers Database Engine statistics.
1067 * Careful when modifying this function.
1068 * You must not change the indices of the statistics or user code will break.
1069 * You must not exceed RRDENG_NR_STATS or it will crash.
1070 */
1071 void rrdeng_get_37_statistics(struct rrdengine_instance *ctx, unsigned long long *array)
1072 {
1073 if (ctx == NULL)
1074 return;
1075
1076 array[0] = (uint64_t)__atomic_load_n(&ctx->atomic.collectors_running, __ATOMIC_RELAXED); // API producers
1077 array[1] = (uint64_t)__atomic_load_n(&ctx->atomic.inflight_queries, __ATOMIC_RELAXED); // API consumers
1078 array[2] = 0;
1079 array[3] = 0;
1080 array[4] = 0;
1081 array[5] = 0; // (uint64_t)ctx->stats.pg_cache_insertions;
1082 array[6] = 0; // (uint64_t)ctx->stats.pg_cache_deletions;
1083 array[7] = 0; // (uint64_t)ctx->stats.pg_cache_hits;
1084 array[8] = 0; // (uint64_t)ctx->stats.pg_cache_misses;
1085 array[9] = 0; // (uint64_t)ctx->stats.pg_cache_backfills;
1086 array[10] = 0; // (uint64_t)ctx->stats.pg_cache_evictions;
1087 array[11] = (uint64_t)__atomic_load_n(&ctx->stats.before_compress_bytes, __ATOMIC_RELAXED); // used
1088 array[12] = (uint64_t)__atomic_load_n(&ctx->stats.after_compress_bytes, __ATOMIC_RELAXED); // used
1089 array[13] = (uint64_t)__atomic_load_n(&ctx->stats.before_decompress_bytes, __ATOMIC_RELAXED);
1090 array[14] = (uint64_t)__atomic_load_n(&ctx->stats.after_decompress_bytes, __ATOMIC_RELAXED);
1091 array[15] = (uint64_t)__atomic_load_n(&ctx->stats.io_write_bytes, __ATOMIC_RELAXED); // used
1092 array[16] = (uint64_t)__atomic_load_n(&ctx->stats.io_write_requests, __ATOMIC_RELAXED); // used
1093 array[17] = (uint64_t)__atomic_load_n(&ctx->stats.io_read_bytes, __ATOMIC_RELAXED);
1094 array[18] = (uint64_t)__atomic_load_n(&ctx->stats.io_read_requests, __ATOMIC_RELAXED); // used
1095 array[19] = 0; // (uint64_t)__atomic_load_n(&ctx->stats.io_write_extent_bytes, __ATOMIC_RELAXED);
1096 array[20] = 0; // (uint64_t)__atomic_load_n(&ctx->stats.io_write_extents, __ATOMIC_RELAXED);
1097 array[21] = 0; // (uint64_t)__atomic_load_n(&ctx->stats.io_read_extent_bytes, __ATOMIC_RELAXED);
1098 array[22] = 0; // (uint64_t)__atomic_load_n(&ctx->stats.io_read_extents, __ATOMIC_RELAXED);
1099 array[23] = (uint64_t)__atomic_load_n(&ctx->stats.datafile_creations, __ATOMIC_RELAXED);
1100 array[24] = (uint64_t)__atomic_load_n(&ctx->stats.datafile_deletions, __ATOMIC_RELAXED);
1101 array[25] = (uint64_t)__atomic_load_n(&ctx->stats.journalfile_creations, __ATOMIC_RELAXED);
1102 array[26] = (uint64_t)__atomic_load_n(&ctx->stats.journalfile_deletions, __ATOMIC_RELAXED);
1103 array[27] = 0; // (uint64_t)__atomic_load_n(&ctx->stats.page_cache_descriptors, __ATOMIC_RELAXED);
1104 array[28] = (uint64_t)__atomic_load_n(&ctx->stats.io_errors, __ATOMIC_RELAXED);
1105 array[29] = (uint64_t)__atomic_load_n(&ctx->stats.fs_errors, __ATOMIC_RELAXED);
1106 array[30] = (uint64_t)__atomic_load_n(&global_stats.global_io_errors, __ATOMIC_RELAXED); // used
1107 array[31] = (uint64_t)__atomic_load_n(&global_stats.global_fs_errors, __ATOMIC_RELAXED); // used
1108 array[32] = (uint64_t)__atomic_load_n(&global_stats.rrdeng_reserved_file_descriptors, __ATOMIC_RELAXED); // used
1109 array[33] = 0; // (uint64_t)__atomic_load_n(&ctx->stats.pg_cache_over_half_dirty_events, __ATOMIC_RELAXED);
1110 array[34] = (uint64_t)__atomic_load_n(&global_stats.global_pg_cache_over_half_dirty_events, __ATOMIC_RELAXED); // used
1111 array[35] = 0; // (uint64_t)__atomic_load_n(&ctx->stats.flushing_pressure_page_deletions, __ATOMIC_RELAXED);
1112 array[36] = (uint64_t)__atomic_load_n(&global_stats.global_flushing_pressure_page_deletions, __ATOMIC_RELAXED); // used
1113 array[37] = 0; //(uint64_t)pg_cache->active_descriptors;
1114
1115 fatal_assert(RRDENG_NR_STATS == 38);
1116 }
1117
1118 static void rrdeng_populate_mrg(struct rrdengine_instance *ctx)
1119 {
1120 size_t datafiles = datafile_count(ctx, false);
1121
1122 ssize_t cpus = (ssize_t)netdata_conf_cpus();
1123 if(cpus < 1)
1124 cpus = 1;
1125
1126 netdata_log_info("DBENGINE: tier %d: populating retention to MRG from %zu journal files, using a shared pool of %zd threads...", ctx->config.tier, datafiles, cpus);
1127
1128 completion_init(&ctx->loading.load_mrg);
1129 rrdeng_enq_cmd(
1130 ctx,
1131 RRDENG_OPCODE_CTX_POPULATE_MRG,
1132 NULL,
1133 &ctx->loading.load_mrg,
1134 STORAGE_PRIORITY_INTERNAL_DBENGINE,
1135 NULL,
1136 NULL);
1137 }
1138
1139 void rrdeng_readiness_wait(struct rrdengine_instance *ctx) {
1140 completion_wait_for(&ctx->loading.load_mrg);
1141 completion_destroy(&ctx->loading.load_mrg);
1142
1143 if(__atomic_load_n(&ctx->atomic.first_time_s, __ATOMIC_RELAXED) == LONG_MAX)
1144 __atomic_store_n(&ctx->atomic.first_time_s, now_realtime_sec(), __ATOMIC_RELAXED);
1145
1146 // Preserve the caller's errno while avoiding stale errno values in this informational readiness log.
1147 int saved_errno = errno;
1148 errno_clear();
1149 netdata_log_info("DBENGINE: tier %d: ready for data collection and queries", ctx->config.tier);
1150 errno = saved_errno;
1151 }
1152
1153 /*
1154 * Returns 0 on success, negative on error
1155 */
1156 int rrdeng_init(
1157 struct rrdengine_instance **ctxp,
1158 const char *dbfiles_path,
1159 unsigned disk_space_mb,
1160 size_t tier,
1161 time_t max_retention_s)
1162 {
1163 struct rrdengine_instance *ctx;
1164 uint32_t max_open_files;
1165
1166 max_open_files = rlimit_nofile.rlim_cur / 4;
1167
1168 /* reserve RRDENG_FD_BUDGET_PER_INSTANCE file descriptors for this instance */
1169 rrd_stat_atomic_add(&global_stats.rrdeng_reserved_file_descriptors, RRDENG_FD_BUDGET_PER_INSTANCE);
1170 if (global_stats.rrdeng_reserved_file_descriptors > max_open_files) {
1171 netdata_log_error(
1172 "Exceeded the budget of available file descriptors (%u/%u), cannot create new dbengine instance.",
1173 (unsigned)global_stats.rrdeng_reserved_file_descriptors,
1174 (unsigned)max_open_files);
1175
1176 rrd_stat_atomic_add(&global_stats.global_fs_errors, 1);
1177 rrd_stat_atomic_add(&global_stats.rrdeng_reserved_file_descriptors, -RRDENG_FD_BUDGET_PER_INSTANCE);
1178 return UV_EMFILE;
1179 }
1180
1181 if(ctxp) {
1182 *ctxp = ctx = mallocz(sizeof(*ctx));
1183 initialize_single_ctx(ctx);
1184 }
1185 else
1186 ctx = multidb_ctx[tier];
1187
1188 ctx->config.tier = (int)tier;
1189 ctx->config.page_type = tier_page_type[tier];
1190 ctx->config.global_compress_alg = dbengine_default_compression();
1191
1192 strncpyz(ctx->config.dbfiles_path, dbfiles_path, sizeof(ctx->config.dbfiles_path) - 1);
1193 ctx->config.dbfiles_path[sizeof(ctx->config.dbfiles_path) - 1] = '\0';
1194
1195 if (disk_space_mb && disk_space_mb < RRDENG_MIN_DISK_SPACE_MB)
1196 disk_space_mb = RRDENG_MIN_DISK_SPACE_MB;
1197
1198 ctx->config.max_disk_space = disk_space_mb * 1048576LLU;
1199
1200 ctx->config.max_retention_s = max_retention_s;
1201
1202 ctx->atomic.transaction_id = 1;
1203 ctx->quiesce.enabled = false;
1204
1205 ctx->atomic.first_time_s = LONG_MAX;
1206 ctx->atomic.metrics = 0;
1207 ctx->atomic.samples = 0;
1208
1209 if (rrdeng_dbengine_spawn(ctx) && !init_rrd_files(ctx)) {
1210 // success - we run this ctx too
1211 rrdeng_populate_mrg(ctx);
1212 return 0;
1213 }
1214
1215 if (unittest_running) {
1216 freez(ctx);
1217 if (ctxp)
1218 *ctxp = NULL;
1219 }
1220
1221 rrd_stat_atomic_add(&global_stats.rrdeng_reserved_file_descriptors, -RRDENG_FD_BUDGET_PER_INSTANCE);
1222 return UV_EIO;
1223 }
1224
1225 size_t rrdeng_collectors_running(struct rrdengine_instance *ctx) {
1226 return __atomic_load_n(&ctx->atomic.collectors_running, __ATOMIC_RELAXED);
1227 }
1228
1229 /*
1230 * Returns 0 on success, 1 on error
1231 */
1232 int rrdeng_exit(struct rrdengine_instance *ctx) {
1233 if (NULL == ctx)
1234 return 1;
1235
1236 // FIXME - ktsaou - properly cleanup ctx
1237 // 1. make sure all collectors are stopped
1238 // 2. make new queries will not be accepted (this is quiesce that has already run)
1239 // 3. flush this section of the main cache
1240 // 4. then wait for completion
1241
1242 bool logged = false;
1243 size_t count = 10;
1244 while(__atomic_load_n(&ctx->atomic.collectors_running, __ATOMIC_RELAXED) && count && !unittest_running) {
1245 if(!logged) {
1246 netdata_log_info("DBENGINE: waiting for collectors to finish on tier %d...", ctx->config.tier);
1247 logged = true;
1248 }
1249 sleep_usec(100 * USEC_PER_MS);
1250 count--;
1251 }
1252
1253 pgc_flush_all_hot_and_dirty_pages(main_cache, (Word_t)ctx);
1254
1255 struct completion completion = {};
1256 completion_init(&completion);
1257 rrdeng_enq_cmd(ctx, RRDENG_OPCODE_CTX_SHUTDOWN, NULL, &completion, STORAGE_PRIORITY_BEST_EFFORT, NULL, NULL);
1258
1259 completion_wait_for(&completion);
1260 completion_destroy(&completion);
1261
1262 if(unittest_running)
1263 freez(ctx);
1264
1265 rrd_stat_atomic_add(&global_stats.rrdeng_reserved_file_descriptors, -RRDENG_FD_BUDGET_PER_INSTANCE);
1266 return 0;
1267 }
1268
1269 void rrdeng_flush_dirty(struct rrdengine_instance *ctx)
1270 {
1271 if (NULL == ctx)
1272 return;
1273
1274 rrdeng_enq_cmd(ctx, RRDENG_OPCODE_CTX_FLUSH_DIRTY, NULL, NULL, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL);
1275 }
1276
1277 void rrdeng_flush_all(struct rrdengine_instance *ctx)
1278 {
1279 if (NULL == ctx)
1280 return;
1281
1282 rrdeng_enq_cmd(ctx, RRDENG_OPCODE_CTX_FLUSH_HOT_DIRTY, NULL, NULL, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL);
1283 }
1284
1285 void rrdeng_quiesce(struct rrdengine_instance *ctx)
1286 {
1287 if (NULL == ctx)
1288 return;
1289
1290 rrdeng_enq_cmd(ctx, RRDENG_OPCODE_CTX_QUIESCE, NULL, NULL, STORAGE_PRIORITY_INTERNAL_DBENGINE, NULL, NULL);
1291 }
1292
1293 static void populate_v2_statistics(struct rrdengine_datafile *datafile, RRDENG_SIZE_STATS *stats)
1294 {
1295 struct journal_v2_header *j2_header = journalfile_v2_data_acquire(datafile->journalfile, NULL, 0, 0);
1296 uint8_t *data_start = (uint8_t *)j2_header;
1297
1298 if(unlikely(!j2_header))
1299 return;
1300
1301 char file_path[RRDENG_PATH_MAX];
1302 journalfile_v2_generate_path(datafile, file_path, sizeof(file_path));
1303
1304 // Protect the mmap walk: every j2_header->*, extent_list, metric, and
1305 // descr access reads the mmap'd v2 journal. If the underlying file has
1306 // any unreadable page (truncated, sparse hole, transient I/O error), the
1307 // walk SIGBUSes and the process aborts. Same pattern as the sister sites
1308 // (populate_retention_to_mrg, find_uuid_first_time, update_metrics_first_time_s).
1309 // Partial accumulator increments on signal recovery are acceptable:
1310 // size statistics are best-effort across datafiles, the caller continues
1311 // to the next datafile in rrdeng_size_statistics().
1312 PROTECTED_ACCESS_SETUP(datafile->journalfile->mmap.data, datafile->journalfile->mmap.size, file_path, "size-stats");
1313 if(no_signal_received) {
1314 size_t mmap_size = datafile->journalfile->mmap.size;
1315
1316 // Bounds-check the extent list array against the mapping size before
1317 // walking through header-controlled offsets. PROTECTED_ACCESS_SETUP
1318 // only catches faults in [data_start, data_start+mmap_size); a
1319 // corrupted offset that points past mmap_size would over-read into
1320 // unrelated memory and abort. Same idiom as the bounds check in
1321 // journalfile_v2_populate_retention_to_mrg.
1322 bool extents_in_bounds = (size_t)j2_header->extent_offset <= mmap_size &&
1323 (size_t)j2_header->extent_count <= (mmap_size - (size_t)j2_header->extent_offset) / sizeof(struct journal_extent_list);
1324 if (extents_in_bounds) {
1325 stats->extents += j2_header->extent_count;
1326
1327 struct journal_extent_list *extent_list = (void *) (data_start + j2_header->extent_offset);
1328 for (unsigned entries = 0; entries < j2_header->extent_count; entries++) {
1329 stats->extents_compressed_bytes += extent_list->datafile_size;
1330 stats->extents_pages += extent_list->pages;
1331 extent_list++;
1332 }
1333 }
1334
1335 bool metrics_in_bounds = (size_t)j2_header->metric_offset <= mmap_size &&
1336 (size_t)j2_header->metric_count <= (mmap_size - (size_t)j2_header->metric_offset) / sizeof(struct journal_metric_list);
1337 if (!metrics_in_bounds)
1338 goto release;
1339
1340 struct journal_metric_list *metric = (void *) (data_start + j2_header->metric_offset);
1341 time_t journal_start_time_s = (time_t) (j2_header->start_time_ut / USEC_PER_SEC);
1342
1343 stats->metrics += j2_header->metric_count;
1344 for (unsigned entries = 0; entries < j2_header->metric_count; entries++) {
1345
1346 // Per-metric: page_offset is header-controlled. Validate it points
1347 // into the mapping and that there is room for the page_header AND
1348 // its trailing page_list[] before dereferencing through it.
1349 if ((size_t)metric->page_offset > mmap_size ||
1350 mmap_size - (size_t)metric->page_offset < sizeof(struct journal_page_header)) {
1351 metric++;
1352 continue;
1353 }
1354
1355 struct journal_page_header *metric_list_header = (void *) (data_start + metric->page_offset);
1356
1357 size_t page_list_room = mmap_size - (size_t)metric->page_offset - sizeof(struct journal_page_header);
1358 if ((size_t)metric_list_header->entries > page_list_room / sizeof(struct journal_page_list)) {
1359 metric++;
1360 continue;
1361 }
1362
1363 stats->metrics_pages += metric_list_header->entries;
1364 struct journal_page_list *descr = (void *) (data_start + metric->page_offset + sizeof(struct journal_page_header));
1365 for (uint32_t idx=0; idx < metric_list_header->entries; idx++) {
1366
1367 time_t update_every_s;
1368
1369 size_t points = descr->page_length / CTX_POINT_SIZE_BYTES(datafile_ctx(datafile));
1370
1371 time_t start_time_s = journal_start_time_s + descr->delta_start_s;
1372 time_t end_time_s = journal_start_time_s + descr->delta_end_s;
1373
1374 if(likely(points > 1))
1375 update_every_s = (time_t) ((end_time_s - start_time_s) / (points - 1));
1376 else {
1377 update_every_s = (time_t) (nd_profile.update_every * get_tier_grouping(datafile_ctx(datafile)->config.tier));
1378 stats->single_point_pages++;
1379 }
1380
1381 time_t duration_s = (time_t)((end_time_s - start_time_s + update_every_s));
1382
1383 stats->pages_uncompressed_bytes += descr->page_length;
1384 stats->pages_duration_secs += duration_s;
1385 stats->points += points;
1386
1387 // descr->type is uint8_t (range [0, 255]); page_types is sized
1388 // [256]. The index is bounded by the type width, so no runtime
1389 // check is needed -- a `descr->type < 256` guard would be a
1390 // tautology. Note for static analyzers flagging this site.
1391 stats->page_types[descr->type].pages++;
1392 stats->page_types[descr->type].pages_uncompressed_bytes += descr->page_length;
1393 stats->page_types[descr->type].pages_duration_secs += duration_s;
1394 stats->page_types[descr->type].points += points;
1395
1396 if(!stats->first_time_s || (start_time_s - update_every_s) < stats->first_time_s)
1397 stats->first_time_s = (start_time_s - update_every_s);
1398
1399 if(!stats->last_time_s || end_time_s > stats->last_time_s)
1400 stats->last_time_s = end_time_s;
1401
1402 descr++;
1403 }
1404 metric++;
1405 }
1406 }
1407 // On SIGBUS/SIGSEGV the PROTECTED_ACCESS_SETUP macro already
1408 // rate-limits the error log; fall through to release the journal.
1409
1410 release:
1411 journalfile_v2_data_release(datafile->journalfile);
1412 }
1413
1414 RRDENG_SIZE_STATS rrdeng_size_statistics(struct rrdengine_instance *ctx) {
1415 RRDENG_SIZE_STATS stats = { 0 };
1416
1417 netdata_rwlock_rdlock(&ctx->datafiles.rwlock);
1418 struct rrdengine_datafile *df = NULL;
1419
1420 while ((df = get_next_datafile(df, ctx, true))) {
1421 stats.datafiles++;
1422 populate_v2_statistics(df, &stats);
1423 }
1424 netdata_rwlock_rdunlock(&ctx->datafiles.rwlock);
1425
1426 stats.currently_collected_metrics = __atomic_load_n(&ctx->atomic.collectors_running, __ATOMIC_RELAXED);
1427
1428 internal_error(stats.metrics_pages != stats.extents_pages + stats.currently_collected_metrics,
1429 "DBENGINE: metrics pages is %zu, but extents pages is %zu and API consumers is %zu",
1430 stats.metrics_pages, stats.extents_pages, stats.currently_collected_metrics);
1431
1432 stats.disk_space = ctx_current_disk_space_get(ctx);
1433 stats.max_disk_space = ctx->config.max_disk_space;
1434
1435 stats.database_retention_secs = (time_t)(stats.last_time_s - stats.first_time_s);
1436
1437 if(stats.extents_pages)
1438 stats.average_page_size_bytes = (double)stats.pages_uncompressed_bytes / (double)stats.extents_pages;
1439
1440 if(stats.pages_uncompressed_bytes > 0)
1441 stats.average_compression_savings = 100.0 - ((double)stats.extents_compressed_bytes * 100.0 / (double)stats.pages_uncompressed_bytes);
1442
1443 if(stats.points)
1444 stats.average_point_duration_secs = (double)stats.pages_duration_secs / (double)stats.points;
1445
1446 if(stats.metrics) {
1447 stats.average_metric_retention_secs = (double)stats.pages_duration_secs / (double)stats.metrics;
1448
1449 if(stats.database_retention_secs) {
1450 double metric_coverage = stats.average_metric_retention_secs / (double)stats.database_retention_secs;
1451 double db_retention_days = (double)stats.database_retention_secs / 86400.0;
1452
1453 stats.estimated_concurrently_collected_metrics = stats.metrics * metric_coverage;
1454
1455 stats.ephemeral_metrics_per_day_percent = ((double)stats.metrics * 100.0 / (double)stats.estimated_concurrently_collected_metrics - 100.0) / (double)db_retention_days;
1456 }
1457 }
1458
1459 // stats.sizeof_metric = 0;
1460 stats.sizeof_datafile =
1461 natural_alignment(sizeof(struct rrdengine_datafile)) +
1462 natural_alignment(sizeof(struct rrdengine_journalfile));
1463 stats.sizeof_page_in_cache = 0; // struct_natural_alignment(sizeof(struct page_cache_descr));
1464 stats.sizeof_point_data = page_type_size[ctx->config.page_type];
1465 stats.sizeof_page_data = tier_page_size[ctx->config.tier];
1466 stats.pages_per_extent = rrdeng_pages_per_extent;
1467
1468 // stats.sizeof_metric_in_index = 40;
1469 // stats.sizeof_page_in_index = 24;
1470
1471 stats.default_granularity_secs = (size_t)nd_profile.update_every * get_tier_grouping(ctx->config.tier);
1472
1473 return stats;
1474 }
1475
1476 struct rrdeng_cache_efficiency_stats rrdeng_get_cache_efficiency_stats(void) {
1477 // FIXME - make cache efficiency stats atomic
1478 return rrdeng_cache_efficiency_stats;
1479 }