Browse Source

QUERY_TARGET: new query engine for Netdata Agent (#13697)

* initial implementation of QUERY_TARGET

* rrd2rrdr() interface

* rrddim_find_best_tier_for_timeframe() ported

* added dimension filtering

* added db object in query target

* rrd2rrdr() ported

* working on formatters

* working on jsonwrapper

* finally, it compiles...

* 1st run without crashes

* query planer working

* cleanup old code

* review changes

* fix also changing data collection frequency

* fix signess

* fix rrdlabels and dimension ordering

* fixes

* remove unused variable

* ml should accept NULL response from rrd2rrdr()

* number formatting fixes

* more number formatting fixes

* more number formatting fixes

* support mc parallel queries

* formatting and cleanup

* added rrd2rrdr_legacy() as a simplified interface to run a query

* make sure rrdset_find_natural_update_every_for_timeframe() returns a value

* make signed comparisons

* weights endpoint using rrdcontexts

* fix for legacy db modes and cleanup

* fix for chart_ids and remove AR chart from weights endpoint

* Ignore command if not initialized yet

* remove unused members

* properly initialize window

* code cleanup - rrddim linked list is gone; rrdset rwlock is gone too

* reviewed RRDR.internal members

* eliminate unnecessary members of QUERY_TARGET

* more complete query ids; more detailed information on aborted queries

* properly terminate option strings

* query id contains group_options which is controlled by users, so escaping is necessary

* tense in query id

* tense in query id - again

* added the remaining query options to the query id

* Expose hidden option to the dimension

* use the hidden flag when loading context dimensions

* Specify table alias for option

* dont update chart last access time, unless at least a dimension of the chart will be queried

Co-authored-by: Stelios Fragkakis <52996999+stelfrag@users.noreply.github.com>
Costa Tsaousis 2 years ago
parent
commit
00712b351b

+ 6 - 5
daemon/global_statistics.c

@@ -695,13 +695,14 @@ static void dbengine_statistics_charts(void) {
         unsigned dbengine_contexts = 0, counted_multihost_db[RRD_STORAGE_TIERS] = { 0 }, i;
 
         rrdhost_foreach_read(host) {
-            if (host->rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE && !rrdhost_flag_check(host, RRDHOST_FLAG_ARCHIVED)) {
+            if (!rrdhost_flag_check(host, RRDHOST_FLAG_ARCHIVED)) {
 
                 /* get localhost's DB engine's statistics for each tier */
-                for(int tier = 0; tier < storage_tiers ;tier++) {
-                    if(!host->storage_instance[tier]) continue;
+                for(size_t tier = 0; tier < storage_tiers ;tier++) {
+                    if(host->db[tier].mode != RRD_MEMORY_MODE_DBENGINE) continue;
+                    if(!host->db[tier].instance) continue;
 
-                    if(is_storage_engine_shared(host->storage_instance[tier])) {
+                    if(is_storage_engine_shared(host->db[tier].instance)) {
                         if(counted_multihost_db[tier])
                             continue;
                         else
@@ -709,7 +710,7 @@ static void dbengine_statistics_charts(void) {
                     }
 
                     ++dbengine_contexts;
-                    rrdeng_get_37_statistics((struct rrdengine_instance *)host->storage_instance[tier], local_stats_array);
+                    rrdeng_get_37_statistics((struct rrdengine_instance *)host->db[tier].instance, local_stats_array);
                     for (i = 0; i < RRDENG_NR_STATS; ++i) {
                         /* aggregate statistics across hosts */
                         stats_array[i] += local_stats_array[i];

+ 2 - 2
daemon/main.c

@@ -56,7 +56,7 @@ void netdata_cleanup_and_exit(int ret) {
         info("EXIT: freeing database memory...");
 #ifdef ENABLE_DBENGINE
         if(dbengine_enabled) {
-            for (int tier = 0; tier < storage_tiers; tier++)
+            for (size_t tier = 0; tier < storage_tiers; tier++)
                 rrdeng_prepare_exit(multidb_ctx[tier]);
         }
 #endif
@@ -65,7 +65,7 @@ void netdata_cleanup_and_exit(int ret) {
         metadata_sync_shutdown();
 #ifdef ENABLE_DBENGINE
         if(dbengine_enabled) {
-            for (int tier = 0; tier < storage_tiers; tier++)
+            for (size_t tier = 0; tier < storage_tiers; tier++)
                 rrdeng_exit(multidb_ctx[tier]);
         }
 #endif

+ 3 - 3
daemon/service.c

@@ -47,11 +47,11 @@ static void svc_rrddim_obsolete_to_archive(RRDDIM *rd) {
         /* only a collector can mark a chart as obsolete, so we must remove the reference */
 
         size_t tiers_available = 0, tiers_said_yes = 0;
-        for(int tier = 0; tier < storage_tiers ;tier++) {
+        for(size_t tier = 0; tier < storage_tiers ;tier++) {
             if(rd->tiers[tier]) {
                 tiers_available++;
 
-                if(rd->tiers[tier]->collect_ops.finalize(rd->tiers[tier]->db_collection_handle))
+                if(rd->tiers[tier]->collect_ops->finalize(rd->tiers[tier]->db_collection_handle))
                     tiers_said_yes++;
 
                 rd->tiers[tier]->db_collection_handle = NULL;
@@ -217,7 +217,7 @@ restart_after_removal:
 
             if (rrdhost_option_check(host, RRDHOST_OPTION_DELETE_ORPHAN_HOST)
                 /* don't delete multi-host DB host files */
-                && !(host->rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE && is_storage_engine_shared(host->storage_instance[0]))
+                && !(host->rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE && is_storage_engine_shared(host->db[0].instance))
             ) {
                 worker_is_busy(WORKER_JOB_DELETE_HOST_CHARTS);
                 rrdhost_delete_charts(host);

+ 29 - 29
daemon/unit_test.c

@@ -1818,7 +1818,7 @@ static time_t test_dbengine_create_metrics(RRDSET *st[CHARTS], RRDDIM *rd[CHARTS
     // feed it with the test data
     for (i = 0 ; i < CHARTS ; ++i) {
         for (j = 0 ; j < DIMS ; ++j) {
-            rd[i][j]->tiers[0]->collect_ops.change_collection_frequency(rd[i][j]->tiers[0]->db_collection_handle, update_every);
+            rd[i][j]->tiers[0]->collect_ops->change_collection_frequency(rd[i][j]->tiers[0]->db_collection_handle, update_every);
 
             rd[i][j]->last_collected_time.tv_sec =
             st[i]->last_collected_time.tv_sec = st[i]->last_updated.tv_sec = time_now;
@@ -1852,7 +1852,7 @@ static int test_dbengine_check_metrics(RRDSET *st[CHARTS], RRDDIM *rd[CHARTS][DI
     int i, j, k, c, errors, update_every;
     collected_number last;
     NETDATA_DOUBLE value, expected;
-    struct rrddim_query_handle handle;
+    struct storage_engine_query_handle handle;
     size_t value_errors = 0, time_errors = 0;
 
     update_every = REGION_UPDATE_EVERY[current_region];
@@ -1863,13 +1863,13 @@ static int test_dbengine_check_metrics(RRDSET *st[CHARTS], RRDDIM *rd[CHARTS][DI
         time_now = time_start + (c + 1) * update_every;
         for (i = 0 ; i < CHARTS ; ++i) {
             for (j = 0; j < DIMS; ++j) {
-                rd[i][j]->tiers[0]->query_ops.init(rd[i][j]->tiers[0]->db_metric_handle, &handle, time_now, time_now + QUERY_BATCH * update_every);
+                rd[i][j]->tiers[0]->query_ops->init(rd[i][j]->tiers[0]->db_metric_handle, &handle, time_now, time_now + QUERY_BATCH * update_every);
                 for (k = 0; k < QUERY_BATCH; ++k) {
                     last = ((collected_number)i * DIMS) * REGION_POINTS[current_region] +
                            j * REGION_POINTS[current_region] + c + k;
                     expected = unpack_storage_number(pack_storage_number((NETDATA_DOUBLE)last, SN_DEFAULT_FLAGS));
 
-                    STORAGE_POINT sp = rd[i][j]->tiers[0]->query_ops.next_metric(&handle);
+                    STORAGE_POINT sp = rd[i][j]->tiers[0]->query_ops->next_metric(&handle);
                     value = sp.sum;
                     time_retrieved = sp.start_time;
                     end_time = sp.end_time;
@@ -1891,7 +1891,7 @@ static int test_dbengine_check_metrics(RRDSET *st[CHARTS], RRDDIM *rd[CHARTS][DI
                         errors++;
                     }
                 }
-                rd[i][j]->tiers[0]->query_ops.finalize(&handle);
+                rd[i][j]->tiers[0]->query_ops->finalize(&handle);
             }
         }
     }
@@ -1922,23 +1922,22 @@ static int test_dbengine_check_rrdr(RRDSET *st[CHARTS], RRDDIM *rd[CHARTS][DIMS]
     long points = (time_end - time_start) / update_every;
     for (i = 0 ; i < CHARTS ; ++i) {
         ONEWAYALLOC *owa = onewayalloc_create(0);
-        RRDR *r = rrd2rrdr(owa, st[i], points, time_start, time_end,
-                           RRDR_GROUPING_AVERAGE, 0, RRDR_OPTION_NATURAL_POINTS,
-                           NULL, NULL, NULL, 0, 0);
-
+        RRDR *r = rrd2rrdr_legacy(owa, st[i], points, time_start, time_end,
+                                  RRDR_GROUPING_AVERAGE, 0, RRDR_OPTION_NATURAL_POINTS,
+                                  NULL, NULL, 0, 0);
         if (!r) {
             fprintf(stderr, "    DB-engine unittest %s: empty RRDR on region %d ### E R R O R ###\n", rrdset_name(st[i]), current_region);
             return ++errors;
         } else {
-            assert(r->st == st[i]);
-            for (c = 0; c != rrdr_rows(r) ; ++c) {
+            assert(r->internal.qt->request.st == st[i]);
+            for (c = 0; c != (long)rrdr_rows(r) ; ++c) {
                 RRDDIM *d;
                 time_now = time_start + (c + 1) * update_every;
                 time_retrieved = r->t[c];
 
                 // for each dimension
-                rrddim_foreach_read(d, r->st) {
-                    if(unlikely((int)d_dfe.counter >= r->d)) break; // d_counter is provided by the dictionary dfe
+                rrddim_foreach_read(d, r->internal.qt->request.st) {
+                    if(unlikely(d_dfe.counter >= r->d)) break; // d_counter is provided by the dictionary dfe
 
                     j = (int)d_dfe.counter;
 
@@ -2061,25 +2060,26 @@ int test_dbengine(void)
     long point_offset = (time_start[current_region] - time_start[0]) / update_every;
     for (i = 0 ; i < CHARTS ; ++i) {
         ONEWAYALLOC *owa = onewayalloc_create(0);
-        RRDR *r = rrd2rrdr(owa, st[i], points, time_start[0] + update_every,
-                           time_end[REGIONS - 1], RRDR_GROUPING_AVERAGE, 0,
-                           RRDR_OPTION_NATURAL_POINTS, NULL, NULL, NULL, 0, 0);
+        RRDR *r = rrd2rrdr_legacy(owa, st[i], points, time_start[0] + update_every,
+                                  time_end[REGIONS - 1], RRDR_GROUPING_AVERAGE, 0,
+                                  RRDR_OPTION_NATURAL_POINTS, NULL, NULL, 0, 0);
+
         if (!r) {
             fprintf(stderr, "    DB-engine unittest %s: empty RRDR ### E R R O R ###\n", rrdset_name(st[i]));
             ++errors;
         } else {
             long c;
 
-            assert(r->st == st[i]);
+            assert(r->internal.qt->request.st == st[i]);
             // test current region values only, since they must be left unchanged
-            for (c = point_offset ; c < point_offset + rrdr_rows(r) / REGIONS / 2 ; ++c) {
+            for (c = point_offset ; c < (long)(point_offset + rrdr_rows(r) / REGIONS / 2) ; ++c) {
                 RRDDIM *d;
                 time_t time_now = time_start[current_region] + (c - point_offset + 2) * update_every;
                 time_t time_retrieved = r->t[c];
 
                 // for each dimension
-                rrddim_foreach_read(d, r->st) {
-                    if(unlikely((int)d_dfe.counter >= r->d)) break; // d_counter is provided by the dictionary dfe
+                rrddim_foreach_read(d, r->internal.qt->request.st) {
+                    if(unlikely(d_dfe.counter >= r->d)) break; // d_counter is provided by the dictionary dfe
 
                     j = (int)d_dfe.counter;
 
@@ -2113,9 +2113,9 @@ int test_dbengine(void)
     }
 error_out:
     rrd_wrlock();
-    rrdeng_prepare_exit((struct rrdengine_instance *)host->storage_instance[0]);
+    rrdeng_prepare_exit((struct rrdengine_instance *)host->db[0].instance);
     rrdhost_delete_charts(host);
-    rrdeng_exit((struct rrdengine_instance *)host->storage_instance[0]);
+    rrdeng_exit((struct rrdengine_instance *)host->db[0].instance);
     rrd_unlock();
 
     return errors + value_errors + time_errors;
@@ -2293,7 +2293,7 @@ static void query_dbengine_chart(void *arg)
     time_t time_now, time_retrieved, end_time;
     collected_number generatedv;
     NETDATA_DOUBLE value, expected;
-    struct rrddim_query_handle handle;
+    struct storage_engine_query_handle handle;
     size_t value_errors = 0, time_errors = 0;
 
     do {
@@ -2320,13 +2320,13 @@ static void query_dbengine_chart(void *arg)
             time_before = MIN(time_after + duration, time_max); /* up to 1 hour queries */
         }
 
-        rd->tiers[0]->query_ops.init(rd->tiers[0]->db_metric_handle, &handle, time_after, time_before);
+        rd->tiers[0]->query_ops->init(rd->tiers[0]->db_metric_handle, &handle, time_after, time_before);
         ++thread_info->queries_nr;
         for (time_now = time_after ; time_now <= time_before ; time_now += update_every) {
             generatedv = generate_dbengine_chart_value(i, j, time_now);
             expected = unpack_storage_number(pack_storage_number((NETDATA_DOUBLE) generatedv, SN_DEFAULT_FLAGS));
 
-            if (unlikely(rd->tiers[0]->query_ops.is_finished(&handle))) {
+            if (unlikely(rd->tiers[0]->query_ops->is_finished(&handle))) {
                 if (!thread_info->delete_old_data) { /* data validation only when we don't delete */
                     fprintf(stderr, "    DB-engine stresstest %s/%s: at %lu secs, expecting value " NETDATA_DOUBLE_FORMAT
                         ", found data gap, ### E R R O R ###\n",
@@ -2336,7 +2336,7 @@ static void query_dbengine_chart(void *arg)
                 break;
             }
 
-            STORAGE_POINT sp = rd->tiers[0]->query_ops.next_metric(&handle);
+            STORAGE_POINT sp = rd->tiers[0]->query_ops->next_metric(&handle);
             value = sp.sum;
             time_retrieved = sp.start_time;
             end_time = sp.end_time;
@@ -2374,7 +2374,7 @@ static void query_dbengine_chart(void *arg)
                 }
             }
         }
-        rd->tiers[0]->query_ops.finalize(&handle);
+        rd->tiers[0]->query_ops->finalize(&handle);
     } while(!thread_info->done);
 
     if(value_errors)
@@ -2522,9 +2522,9 @@ void dbengine_stress_test(unsigned TEST_DURATION_SEC, unsigned DSET_CHARTS, unsi
     }
     freez(query_threads);
     rrd_wrlock();
-    rrdeng_prepare_exit((struct rrdengine_instance *)host->storage_instance[0]);
+    rrdeng_prepare_exit((struct rrdengine_instance *)host->db[0].instance);
     rrdhost_delete_charts(host);
-    rrdeng_exit((struct rrdengine_instance *)host->storage_instance[0]);
+    rrdeng_exit((struct rrdengine_instance *)host->db[0].instance);
     rrd_unlock();
 }
 

+ 21 - 27
database/engine/rrdengineapi.c

@@ -36,16 +36,6 @@ int default_multidb_disk_quota_mb = 256;
 /* Default behaviour is to unblock data collection if the page cache is full of dirty pages by dropping metrics */
 uint8_t rrdeng_drop_metrics_under_page_cache_pressure = 1;
 
-
-// ----------------------------------------------------------------------------
-// helpers
-
-static inline struct rrdengine_instance *get_rrdeng_ctx_from_host(RRDHOST *host, int tier) {
-    if(tier < 0 || tier >= RRD_STORAGE_TIERS) tier = 0;
-    if(!host->storage_instance[tier]) tier = 0;
-    return (struct rrdengine_instance *)host->storage_instance[tier];
-}
-
 // ----------------------------------------------------------------------------
 // metrics groups
 
@@ -114,16 +104,18 @@ STORAGE_METRIC_HANDLE *rrdeng_metric_get_legacy(STORAGE_INSTANCE *db_instance, c
 
 void rrdeng_metric_release(STORAGE_METRIC_HANDLE *db_metric_handle) {
     struct pg_cache_page_index *page_index = (struct pg_cache_page_index *)db_metric_handle;
-    struct rrdengine_instance *ctx = page_index->ctx;
-    struct page_cache *pg_cache = &ctx->pg_cache;
 
-    uv_rwlock_rdlock(&pg_cache->metrics_index.lock);
-    page_index->refcount--;
-    if(page_index->alignment && page_index->refcount == 0) {
-        page_index->alignment->refcount--;
+    unsigned short refcount = __atomic_sub_fetch(&page_index->refcount, 1, __ATOMIC_SEQ_CST);
+    if(refcount == 0 && page_index->alignment) {
+        __atomic_sub_fetch(&page_index->alignment->refcount, 1, __ATOMIC_SEQ_CST);
         page_index->alignment = NULL;
     }
-    uv_rwlock_rdunlock(&pg_cache->metrics_index.lock);
+}
+
+STORAGE_METRIC_HANDLE *rrdeng_metric_dup(STORAGE_METRIC_HANDLE *db_metric_handle) {
+    struct pg_cache_page_index *page_index = (struct pg_cache_page_index *)db_metric_handle;
+    __atomic_add_fetch(&page_index->refcount, 1, __ATOMIC_SEQ_CST);
+    return db_metric_handle;
 }
 
 STORAGE_METRIC_HANDLE *rrdeng_metric_get(STORAGE_INSTANCE *db_instance, uuid_t *uuid, STORAGE_METRICS_GROUP *smg) {
@@ -134,9 +126,12 @@ STORAGE_METRIC_HANDLE *rrdeng_metric_get(STORAGE_INSTANCE *db_instance, uuid_t *
 
     uv_rwlock_rdlock(&pg_cache->metrics_index.lock);
     Pvoid_t *PValue = JudyHSGet(pg_cache->metrics_index.JudyHS_array, uuid, sizeof(uuid_t));
-    if (likely(NULL != PValue)) {
+    if (likely(NULL != PValue))
         page_index = *PValue;
-        page_index->refcount++;
+    uv_rwlock_rdunlock(&pg_cache->metrics_index.lock);
+
+    if (likely(page_index)) {
+        __atomic_add_fetch(&page_index->refcount, 1, __ATOMIC_SEQ_CST);
 
         if(pa) {
             if(page_index->alignment && page_index->alignment != pa)
@@ -144,11 +139,10 @@ STORAGE_METRIC_HANDLE *rrdeng_metric_get(STORAGE_INSTANCE *db_instance, uuid_t *
 
             if(!page_index->alignment) {
                 page_index->alignment = pa;
-                pa->refcount++;
+                __atomic_add_fetch(&pa->refcount, 1, __ATOMIC_SEQ_CST);
             }
         }
     }
-    uv_rwlock_rdunlock(&pg_cache->metrics_index.lock);
 
     return (STORAGE_METRIC_HANDLE *)page_index;
 }
@@ -572,7 +566,7 @@ void rrdeng_store_metric_change_collection_frequency(STORAGE_COLLECT_HANDLE *col
  * Gets a handle for loading metrics from the database.
  * The handle must be released with rrdeng_load_metric_final().
  */
-void rrdeng_load_metric_init(STORAGE_METRIC_HANDLE *db_metric_handle, struct rrddim_query_handle *rrdimm_handle, time_t start_time_s, time_t end_time_s)
+void rrdeng_load_metric_init(STORAGE_METRIC_HANDLE *db_metric_handle, struct storage_engine_query_handle *rrdimm_handle, time_t start_time_s, time_t end_time_s)
 {
     struct pg_cache_page_index *page_index = (struct pg_cache_page_index *)db_metric_handle;
     struct rrdengine_instance *ctx = page_index->ctx;
@@ -603,7 +597,7 @@ void rrdeng_load_metric_init(STORAGE_METRIC_HANDLE *db_metric_handle, struct rrd
         handle->wanted_start_time_s = INVALID_TIME;
 }
 
-static int rrdeng_load_page_next(struct rrddim_query_handle *rrdimm_handle, bool debug_this __maybe_unused) {
+static int rrdeng_load_page_next(struct storage_engine_query_handle *rrdimm_handle, bool debug_this __maybe_unused) {
     struct rrdeng_query_handle *handle = (struct rrdeng_query_handle *)rrdimm_handle->handle;
 
     struct rrdengine_instance *ctx = handle->ctx;
@@ -682,7 +676,7 @@ static int rrdeng_load_page_next(struct rrddim_query_handle *rrdimm_handle, bool
 // Returns the metric and sets its timestamp into current_time
 // IT IS REQUIRED TO **ALWAYS** SET ALL RETURN VALUES (current_time, end_time, flags)
 // IT IS REQUIRED TO **ALWAYS** KEEP TRACK OF TIME, EVEN OUTSIDE THE DATABASE BOUNDARIES
-STORAGE_POINT rrdeng_load_metric_next(struct rrddim_query_handle *rrddim_handle) {
+STORAGE_POINT rrdeng_load_metric_next(struct storage_engine_query_handle *rrddim_handle) {
     struct rrdeng_query_handle *handle = (struct rrdeng_query_handle *)rrddim_handle->handle;
     // struct rrdeng_metric_handle *metric_handle = handle->metric_handle;
 
@@ -799,7 +793,7 @@ STORAGE_POINT rrdeng_load_metric_next(struct rrddim_query_handle *rrddim_handle)
     return sp;
 }
 
-int rrdeng_load_metric_is_finished(struct rrddim_query_handle *rrdimm_handle)
+int rrdeng_load_metric_is_finished(struct storage_engine_query_handle *rrdimm_handle)
 {
     struct rrdeng_query_handle *handle = (struct rrdeng_query_handle *)rrdimm_handle->handle;
     return (INVALID_TIME == handle->wanted_start_time_s);
@@ -808,7 +802,7 @@ int rrdeng_load_metric_is_finished(struct rrddim_query_handle *rrdimm_handle)
 /*
  * Releases the database reference from the handle for loading metrics.
  */
-void rrdeng_load_metric_finalize(struct rrddim_query_handle *rrdimm_handle)
+void rrdeng_load_metric_finalize(struct storage_engine_query_handle *rrdimm_handle)
 {
     struct rrdeng_query_handle *handle = (struct rrdeng_query_handle *)rrdimm_handle->handle;
     struct rrdengine_instance *ctx = handle->ctx;
@@ -1039,7 +1033,7 @@ void rrdeng_put_page(struct rrdengine_instance *ctx, void *handle)
  * Returns 0 on success, negative on error
  */
 int rrdeng_init(RRDHOST *host, struct rrdengine_instance **ctxp, char *dbfiles_path, unsigned page_cache_mb,
-                unsigned disk_space_mb, int tier) {
+                unsigned disk_space_mb, size_t tier) {
     struct rrdengine_instance *ctx;
     int error;
     uint32_t max_open_files;

+ 6 - 8
database/engine/rrdengineapi.h

@@ -47,6 +47,7 @@ STORAGE_METRIC_HANDLE *rrdeng_metric_get(STORAGE_INSTANCE *db_instance, uuid_t *
 STORAGE_METRIC_HANDLE *rrdeng_metric_create(STORAGE_INSTANCE *db_instance, uuid_t *uuid, STORAGE_METRICS_GROUP *smg);
 STORAGE_METRIC_HANDLE *rrdeng_metric_get_legacy(STORAGE_INSTANCE *db_instance, const char *rd_id, const char *st_id, STORAGE_METRICS_GROUP *smg);
 void rrdeng_metric_release(STORAGE_METRIC_HANDLE *db_metric_handle);
+STORAGE_METRIC_HANDLE *rrdeng_metric_dup(STORAGE_METRIC_HANDLE *db_metric_handle);
 
 STORAGE_COLLECT_HANDLE *rrdeng_store_metric_init(STORAGE_METRIC_HANDLE *db_metric_handle, uint32_t update_every);
 void rrdeng_store_metric_flush_current_page(STORAGE_COLLECT_HANDLE *collection_handle);
@@ -59,16 +60,13 @@ void rrdeng_store_metric_next(STORAGE_COLLECT_HANDLE *collection_handle, usec_t
                                      SN_FLAGS flags);
 int rrdeng_store_metric_finalize(STORAGE_COLLECT_HANDLE *collection_handle);
 
-unsigned rrdeng_variable_step_boundaries(RRDSET *st, time_t start_time_s, time_t end_time_s,
-                                    struct rrdeng_region_info **region_info_arrayp, unsigned *max_intervalp, struct context_param *context_param_list);
-
-void rrdeng_load_metric_init(STORAGE_METRIC_HANDLE *db_metric_handle, struct rrddim_query_handle *rrdimm_handle,
+void rrdeng_load_metric_init(STORAGE_METRIC_HANDLE *db_metric_handle, struct storage_engine_query_handle *rrdimm_handle,
                                     time_t start_time_s, time_t end_time_s);
-STORAGE_POINT rrdeng_load_metric_next(struct rrddim_query_handle *rrddim_handle);
+STORAGE_POINT rrdeng_load_metric_next(struct storage_engine_query_handle *rrddim_handle);
 
 
-int rrdeng_load_metric_is_finished(struct rrddim_query_handle *rrdimm_handle);
-void rrdeng_load_metric_finalize(struct rrddim_query_handle *rrdimm_handle);
+int rrdeng_load_metric_is_finished(struct storage_engine_query_handle *rrdimm_handle);
+void rrdeng_load_metric_finalize(struct storage_engine_query_handle *rrdimm_handle);
 time_t rrdeng_metric_latest_time(STORAGE_METRIC_HANDLE *db_metric_handle);
 time_t rrdeng_metric_oldest_time(STORAGE_METRIC_HANDLE *db_metric_handle);
 
@@ -76,7 +74,7 @@ void rrdeng_get_37_statistics(struct rrdengine_instance *ctx, unsigned long long
 
 /* must call once before using anything */
 int rrdeng_init(RRDHOST *host, struct rrdengine_instance **ctxp, char *dbfiles_path, unsigned page_cache_mb,
-                       unsigned disk_space_mb, int tier);
+                       unsigned disk_space_mb, size_t tier);
 
 int rrdeng_exit(struct rrdengine_instance *ctx);
 void rrdeng_prepare_exit(struct rrdengine_instance *ctx);

+ 10 - 6
database/ram/rrddim_mem.c

@@ -5,7 +5,6 @@
 static Pvoid_t rrddim_JudyHS_array = NULL;
 static netdata_rwlock_t rrddim_JudyHS_rwlock = NETDATA_RWLOCK_INITIALIZER;
 
-
 // ----------------------------------------------------------------------------
 // metrics groups
 
@@ -39,7 +38,8 @@ rrddim_metric_get_or_create(RRDDIM *rd, STORAGE_INSTANCE *db_instance __maybe_un
     return (STORAGE_METRIC_HANDLE *)rd;
 }
 
-STORAGE_METRIC_HANDLE *rrddim_metric_get(STORAGE_INSTANCE *db_instance __maybe_unused, uuid_t *uuid, STORAGE_METRICS_GROUP *smg __maybe_unused) {
+STORAGE_METRIC_HANDLE *
+rrddim_metric_get(STORAGE_INSTANCE *db_instance __maybe_unused, uuid_t *uuid, STORAGE_METRICS_GROUP *smg __maybe_unused) {
     RRDDIM *rd = NULL;
     netdata_rwlock_rdlock(&rrddim_JudyHS_rwlock);
     Pvoid_t *PValue = JudyHSGet(rrddim_JudyHS_array, uuid, sizeof(uuid_t));
@@ -50,6 +50,10 @@ STORAGE_METRIC_HANDLE *rrddim_metric_get(STORAGE_INSTANCE *db_instance __maybe_u
     return (STORAGE_METRIC_HANDLE *)rd;
 }
 
+STORAGE_METRIC_HANDLE *rrddim_metric_dup(STORAGE_METRIC_HANDLE *db_metric_handle) {
+    return db_metric_handle;
+}
+
 void rrddim_metric_release(STORAGE_METRIC_HANDLE *db_metric_handle __maybe_unused) {
     RRDDIM *rd = (RRDDIM *)db_metric_handle;
 
@@ -186,7 +190,7 @@ static inline time_t rrddim_slot2time(RRDDIM *rd, size_t slot) {
 // ----------------------------------------------------------------------------
 // RRDDIM legacy database query functions
 
-void rrddim_query_init(STORAGE_METRIC_HANDLE *db_metric_handle, struct rrddim_query_handle *handle, time_t start_time, time_t end_time) {
+void rrddim_query_init(STORAGE_METRIC_HANDLE *db_metric_handle, struct storage_engine_query_handle *handle, time_t start_time, time_t end_time) {
     RRDDIM *rd = (RRDDIM *)db_metric_handle;
 
     handle->rd = rd;
@@ -209,7 +213,7 @@ void rrddim_query_init(STORAGE_METRIC_HANDLE *db_metric_handle, struct rrddim_qu
 // Returns the metric and sets its timestamp into current_time
 // IT IS REQUIRED TO **ALWAYS** SET ALL RETURN VALUES (current_time, end_time, flags)
 // IT IS REQUIRED TO **ALWAYS** KEEP TRACK OF TIME, EVEN OUTSIDE THE DATABASE BOUNDARIES
-STORAGE_POINT rrddim_query_next_metric(struct rrddim_query_handle *handle) {
+STORAGE_POINT rrddim_query_next_metric(struct storage_engine_query_handle *handle) {
     RRDDIM *rd = handle->rd;
     struct mem_query_handle* h = (struct mem_query_handle*)handle->handle;
     size_t entries = rd->rrdset->entries;
@@ -248,12 +252,12 @@ STORAGE_POINT rrddim_query_next_metric(struct rrddim_query_handle *handle) {
     return sp;
 }
 
-int rrddim_query_is_finished(struct rrddim_query_handle *handle) {
+int rrddim_query_is_finished(struct storage_engine_query_handle *handle) {
     struct mem_query_handle* h = (struct mem_query_handle*)handle->handle;
     return (h->next_timestamp > handle->end_time_s);
 }
 
-void rrddim_query_finalize(struct rrddim_query_handle *handle) {
+void rrddim_query_finalize(struct storage_engine_query_handle *handle) {
 #ifdef NETDATA_INTERNAL_CHECKS
     if(!rrddim_query_is_finished(handle))
         error("QUERY: query for chart '%s' dimension '%s' has been stopped unfinished", rrdset_id(handle->rd->rrdset), rrddim_name(handle->rd));

+ 5 - 4
database/ram/rrddim_mem.h

@@ -22,6 +22,7 @@ struct mem_query_handle {
 
 STORAGE_METRIC_HANDLE *rrddim_metric_get_or_create(RRDDIM *rd, STORAGE_INSTANCE *db_instance, STORAGE_METRICS_GROUP *smg);
 STORAGE_METRIC_HANDLE *rrddim_metric_get(STORAGE_INSTANCE *db_instance, uuid_t *uuid, STORAGE_METRICS_GROUP *smg);
+STORAGE_METRIC_HANDLE *rrddim_metric_dup(STORAGE_METRIC_HANDLE *db_metric_handle);
 void rrddim_metric_release(STORAGE_METRIC_HANDLE *db_metric_handle);
 
 STORAGE_METRICS_GROUP *rrddim_metrics_group_get(STORAGE_INSTANCE *db_instance, uuid_t *uuid);
@@ -38,10 +39,10 @@ void rrddim_collect_store_metric(STORAGE_COLLECT_HANDLE *collection_handle, usec
 void rrddim_store_metric_flush(STORAGE_COLLECT_HANDLE *collection_handle);
 int rrddim_collect_finalize(STORAGE_COLLECT_HANDLE *collection_handle);
 
-void rrddim_query_init(STORAGE_METRIC_HANDLE *db_metric_handle, struct rrddim_query_handle *handle, time_t start_time, time_t end_time);
-STORAGE_POINT rrddim_query_next_metric(struct rrddim_query_handle *handle);
-int rrddim_query_is_finished(struct rrddim_query_handle *handle);
-void rrddim_query_finalize(struct rrddim_query_handle *handle);
+void rrddim_query_init(STORAGE_METRIC_HANDLE *db_metric_handle, struct storage_engine_query_handle *handle, time_t start_time, time_t end_time);
+STORAGE_POINT rrddim_query_next_metric(struct storage_engine_query_handle *handle);
+int rrddim_query_is_finished(struct storage_engine_query_handle *handle);
+void rrddim_query_finalize(struct storage_engine_query_handle *handle);
 time_t rrddim_query_latest_time(STORAGE_METRIC_HANDLE *db_metric_handle);
 time_t rrddim_query_oldest_time(STORAGE_METRIC_HANDLE *db_metric_handle);
 

+ 74 - 67
database/rrd.h

@@ -20,19 +20,21 @@ typedef struct rrdset RRDSET;
 typedef struct rrdcalc RRDCALC;
 typedef struct rrdcalctemplate RRDCALCTEMPLATE;
 typedef struct alarm_entry ALARM_ENTRY;
-typedef struct context_param CONTEXT_PARAM;
 
 typedef struct rrdfamily_acquired RRDFAMILY_ACQUIRED;
 typedef struct rrdvar_acquired RRDVAR_ACQUIRED;
 typedef struct rrdsetvar_acquired RRDSETVAR_ACQUIRED;
 typedef struct rrdcalc_acquired RRDCALC_ACQUIRED;
 
+typedef struct rrdhost_acquired RRDHOST_ACQUIRED;
+typedef struct rrdset_acquired RRDSET_ACQUIRED;
+typedef struct rrddim_acquired RRDDIM_ACQUIRED;
+
 typedef void *ml_host_t;
 typedef void *ml_dimension_t;
 
 // forward declarations
 struct rrddim_tier;
-struct context_param;
 
 #ifdef ENABLE_DBENGINE
 struct rrdeng_page_descr;
@@ -54,8 +56,8 @@ struct pg_cache_page_index;
 #include "rrdcontext.h"
 
 extern bool dbengine_enabled;
-extern int storage_tiers;
-extern int storage_tiers_grouping_iterations[RRD_STORAGE_TIERS];
+extern size_t storage_tiers;
+extern size_t storage_tiers_grouping_iterations[RRD_STORAGE_TIERS];
 
 typedef enum {
     RRD_BACKFILL_NONE,
@@ -174,8 +176,9 @@ DICTIONARY *rrdfamily_rrdvars_dict(const RRDFAMILY_ACQUIRED *rf);
 // options are permanent configuration options (no atomics to alter/access them)
 typedef enum rrddim_options {
     RRDDIM_OPTION_NONE                              = 0,
-    RRDDIM_OPTION_HIDDEN                            = (1 << 0),  // this dimension will not be offered to callers
-    RRDDIM_OPTION_DONT_DETECT_RESETS_OR_OVERFLOWS   = (1 << 1),  // do not offer RESET or OVERFLOW info to callers
+    RRDDIM_OPTION_HIDDEN                            = (1 << 0), // this dimension will not be offered to callers
+    RRDDIM_OPTION_DONT_DETECT_RESETS_OR_OVERFLOWS   = (1 << 1), // do not offer RESET or OVERFLOW info to callers
+    RRDDIM_OPTION_BACKFILLED_HIGH_TIERS             = (1 << 2), // when set, we have backfilled higher tiers
 
     // this is 8-bit
 } RRDDIM_OPTIONS;
@@ -227,6 +230,7 @@ void rrdlabels_add(DICTIONARY *dict, const char *name, const char *value, RRDLAB
 void rrdlabels_add_pair(DICTIONARY *dict, const char *string, RRDLABEL_SRC ls);
 void rrdlabels_get_value_to_buffer_or_null(DICTIONARY *labels, BUFFER *wb, const char *key, const char *quote, const char *null);
 void rrdlabels_get_value_to_char_or_null(DICTIONARY *labels, char **value, const char *key);
+void rrdlabels_flush(DICTIONARY *labels_dict);
 
 void rrdlabels_unmark_all(DICTIONARY *labels);
 void rrdlabels_remove_all_unmarked(DICTIONARY *labels);
@@ -281,18 +285,11 @@ struct rrddim {
     // ------------------------------------------------------------------------
     // operational state members
 
-#ifdef ENABLE_ACLK
-    int aclk_live_status;
-#endif
-
     ml_dimension_t ml_dimension;                    // machine learning data about this dimension
 
     // ------------------------------------------------------------------------
     // linking to siblings and parents
 
-    struct rrddim *next;                            // linking of dimensions within the same data set
-    struct rrddim *prev;                            // linking of dimensions within the same data set
-
     struct rrdset *rrdset;
 
     RRDMETRIC_ACQUIRED *rrdmetric;                  // the rrdmetric of this dimension
@@ -346,21 +343,6 @@ size_t rrddim_memory_file_header_size(void);
 void rrddim_memory_file_save(RRDDIM *rd);
 
 // ----------------------------------------------------------------------------
-// engine-specific iterator state for dimension data collection
-typedef struct storage_collect_handle STORAGE_COLLECT_HANDLE;
-
-// ----------------------------------------------------------------------------
-// engine-specific iterator state for dimension data queries
-typedef struct storage_query_handle STORAGE_QUERY_HANDLE;
-
-// ----------------------------------------------------------------------------
-// iterator state for RRD dimension data queries
-struct rrddim_query_handle {
-    RRDDIM *rd;
-    time_t start_time_s;
-    time_t end_time_s;
-    STORAGE_QUERY_HANDLE* handle;
-};
 
 typedef struct storage_point {
     NETDATA_DOUBLE min;     // when count > 1, this is the minimum among them
@@ -398,9 +380,17 @@ typedef struct storage_point {
 #define storage_point_is_unset(x) (!(x).count)
 #define storage_point_is_empty(x) (!netdata_double_isnumber((x).sum))
 
+// ----------------------------------------------------------------------------
+// engine-specific iterator state for dimension data collection
+typedef struct storage_collect_handle STORAGE_COLLECT_HANDLE;
+
+// ----------------------------------------------------------------------------
+// engine-specific iterator state for dimension data queries
+typedef struct storage_query_handle STORAGE_QUERY_HANDLE;
+
 // ------------------------------------------------------------------------
 // function pointers that handle data collection
-struct rrddim_collect_ops {
+struct storage_engine_collect_ops {
     // an initialization function to run before starting collection
     STORAGE_COLLECT_HANDLE *(*init)(STORAGE_METRIC_HANDLE *db_metric_handle, uint32_t update_every);
 
@@ -421,19 +411,28 @@ struct rrddim_collect_ops {
     void (*metrics_group_release)(STORAGE_INSTANCE *db_instance, STORAGE_METRICS_GROUP *sa);
 };
 
+// ----------------------------------------------------------------------------
+// iterator state for RRD dimension data queries
+struct storage_engine_query_handle {
+    RRDDIM *rd;
+    time_t start_time_s;
+    time_t end_time_s;
+    STORAGE_QUERY_HANDLE* handle;
+};
+
 // function pointers that handle database queries
-struct rrddim_query_ops {
+struct storage_engine_query_ops {
     // run this before starting a series of next_metric() database queries
-    void (*init)(STORAGE_METRIC_HANDLE *db_metric_handle, struct rrddim_query_handle *handle, time_t start_time, time_t end_time);
+    void (*init)(STORAGE_METRIC_HANDLE *db_metric_handle, struct storage_engine_query_handle *handle, time_t start_time, time_t end_time);
 
     // run this to load each metric number from the database
-    STORAGE_POINT (*next_metric)(struct rrddim_query_handle *handle);
+    STORAGE_POINT (*next_metric)(struct storage_engine_query_handle *handle);
 
     // run this to test if the series of next_metric() database queries is finished
-    int (*is_finished)(struct rrddim_query_handle *handle);
+    int (*is_finished)(struct storage_engine_query_handle *handle);
 
     // run this after finishing a series of load_metric() database queries
-    void (*finalize)(struct rrddim_query_handle *handle);
+    void (*finalize)(struct storage_engine_query_handle *handle);
 
     // get the timestamp of the last entry of this metric
     time_t (*latest_time)(STORAGE_METRIC_HANDLE *db_metric_handle);
@@ -442,23 +441,45 @@ struct rrddim_query_ops {
     time_t (*oldest_time)(STORAGE_METRIC_HANDLE *db_metric_handle);
 };
 
+typedef struct storage_engine STORAGE_ENGINE;
+
+// ------------------------------------------------------------------------
+// function pointers for all APIs provided by a storage engine
+typedef struct storage_engine_api {
+    // metric management
+    STORAGE_METRIC_HANDLE *(*metric_get)(STORAGE_INSTANCE *instance, uuid_t *uuid, STORAGE_METRICS_GROUP *smg);
+    STORAGE_METRIC_HANDLE *(*metric_get_or_create)(RRDDIM *rd, STORAGE_INSTANCE *instance, STORAGE_METRICS_GROUP *smg);
+    void (*metric_release)(STORAGE_METRIC_HANDLE *);
+    STORAGE_METRIC_HANDLE *(*metric_dup)(STORAGE_METRIC_HANDLE *);
+
+    // operations
+    struct storage_engine_collect_ops collect_ops;
+    struct storage_engine_query_ops query_ops;
+} STORAGE_ENGINE_API;
+
+struct storage_engine {
+    RRD_MEMORY_MODE id;
+    const char* name;
+    STORAGE_ENGINE_API api;
+};
+
+STORAGE_ENGINE* storage_engine_get(RRD_MEMORY_MODE mmode);
+STORAGE_ENGINE* storage_engine_find(const char* name);
 
 // ----------------------------------------------------------------------------
 // Storage tier data for every dimension
 
 struct rrddim_tier {
-    int tier_grouping;
-    RRD_MEMORY_MODE mode;                           // the memory mode of this tier
+    size_t tier_grouping;
     STORAGE_METRIC_HANDLE *db_metric_handle;        // the metric handle inside the database
     STORAGE_COLLECT_HANDLE *db_collection_handle;   // the data collection handle
     STORAGE_POINT virtual_point;
     time_t next_point_time;
-    usec_t last_collected_ut;
-    struct rrddim_collect_ops collect_ops;
-    struct rrddim_query_ops query_ops;
+    struct storage_engine_collect_ops *collect_ops;
+    struct storage_engine_query_ops *query_ops;
 };
 
-void rrdr_fill_tier_gap_from_smaller_tiers(RRDDIM *rd, int tier, time_t now);
+void rrdr_fill_tier_gap_from_smaller_tiers(RRDDIM *rd, size_t tier, time_t now);
 
 // ----------------------------------------------------------------------------
 // these loop macros make sure the linked list is accessed with the right lock
@@ -549,10 +570,6 @@ struct rrdset {
     DICTIONARY *rrddimvar_root_index;               // dimension variables
                                                     // we use this dictionary to manage their allocation
 
-    // TODO - dimensions linked list and lock to be removed
-    netdata_rwlock_t rrdset_rwlock;                 // protects the  dimensions linked list
-    RRDDIM *dimensions;                             // chart metrics
-
     // ------------------------------------------------------------------------
     // operational state members
 
@@ -600,19 +617,6 @@ struct rrdset {
 
     time_t upstream_resync_time;                    // the timestamp up to which we should resync clock upstream
 
-    // ------------------------------------------------------------------------
-    // context queries temp variables
-    // TODO - eliminate these
-
-    time_t last_entry_t;                            // the last_entry_t computed for transient RRDSET
-
-    // ------------------------------------------------------------------------
-    // dbengine specifics
-    // TODO - they should be managed by storage engine
-    //        (RRDSET_DB_STATE ptr to an undefined structure, and a call to clean this up during destruction)
-
-    size_t rrddim_page_alignment;                   // keeps metric pages in alignment when using dbengine
-
     // ------------------------------------------------------------------------
     // db mode SAVE, MAP specifics
     // TODO - they should be managed by storage engine
@@ -666,10 +670,6 @@ struct rrdset {
 #define rrdset_name(st) string2str((st)->name)
 #define rrdset_id(st) string2str((st)->id)
 
-#define rrdset_rdlock(st) netdata_rwlock_rdlock(&((st)->rrdset_rwlock))
-#define rrdset_wrlock(st) netdata_rwlock_wrlock(&((st)->rrdset_rwlock))
-#define rrdset_unlock(st) netdata_rwlock_unlock(&((st)->rrdset_rwlock))
-
 STRING *rrd_string_strdupz(const char *s);
 
 // ----------------------------------------------------------------------------
@@ -910,11 +910,20 @@ struct rrdhost {
 
     int rrd_update_every;                           // the update frequency of the host
     long rrd_history_entries;                       // the number of history entries for the host's charts
-    RRD_MEMORY_MODE rrd_memory_mode;                // the memory more for the charts of this host
+
+    RRD_MEMORY_MODE rrd_memory_mode;                // the configured memory more for the charts of this host
+                                                    // the actual per tier is at .db[tier].mode
 
     char *cache_dir;                                // the directory to save RRD cache files
     char *varlib_dir;                               // the directory to save health log
 
+    struct {
+        RRD_MEMORY_MODE mode;                       // the db mode for this tier
+        STORAGE_ENGINE *eng;                        // the storage engine API for this tier
+        STORAGE_INSTANCE *instance;                 // the db instance for this tier
+        size_t tier_grouping;                       // tier 0 iterations aggregated on this tier
+    } db[RRD_STORAGE_TIERS];
+
     struct rrdhost_system_info *system_info;        // information collected from the host environment
 
     // ------------------------------------------------------------------------
@@ -999,8 +1008,6 @@ struct rrdhost {
     DICTIONARY *rrdvars;                            // the host's chart variables index
                                                     // this includes custom host variables
 
-    STORAGE_INSTANCE *storage_instance[RRD_STORAGE_TIERS];  // the database instances of the storage tiers
-
     RRDCONTEXTS *rrdctx_hub_queue;
     RRDCONTEXTS *rrdctx_post_processing_queue;
     RRDCONTEXTS *rrdctx;
@@ -1229,8 +1236,8 @@ void rrdset_isnot_obsolete(RRDSET *st);
 
 // checks if the RRDSET should be offered to viewers
 #define rrdset_is_available_for_viewers(st) (!rrdset_flag_check(st, RRDSET_FLAG_HIDDEN) && !rrdset_flag_check(st, RRDSET_FLAG_OBSOLETE) && !rrdset_flag_check(st, RRDSET_FLAG_ARCHIVED) && rrdset_number_of_dimensions(st) && (st)->rrd_memory_mode != RRD_MEMORY_MODE_NONE)
-#define rrdset_is_available_for_exporting_and_alarms(st) (!rrdset_flag_check(st, RRDSET_FLAG_OBSOLETE) && !rrdset_flag_check(st, RRDSET_FLAG_ARCHIVED) && (st)->dimensions)
-#define rrdset_is_archived(st) (rrdset_flag_check(st, RRDSET_FLAG_ARCHIVED) && (st)->dimensions)
+#define rrdset_is_available_for_exporting_and_alarms(st) (!rrdset_flag_check(st, RRDSET_FLAG_OBSOLETE) && !rrdset_flag_check(st, RRDSET_FLAG_ARCHIVED) && rrdset_number_of_dimensions(st))
+#define rrdset_is_archived(st) (rrdset_flag_check(st, RRDSET_FLAG_ARCHIVED) && rrdset_number_of_dimensions(st))
 
 time_t rrddim_first_entry_t(RRDDIM *rd);
 time_t rrddim_last_entry_t(RRDDIM *rd);
@@ -1313,7 +1320,7 @@ void set_host_properties(
     const char *os, const char *tags, const char *tzone, const char *abbrev_tzone, int32_t utc_offset,
     const char *program_name, const char *program_version);
 
-int get_tier_grouping(int tier);
+size_t get_tier_grouping(size_t tier);
 
 // ----------------------------------------------------------------------------
 // RRD DB engine declarations

+ 756 - 32
database/rrdcontext.c

@@ -5,6 +5,7 @@
 #include "aclk/schema-wrappers/context.h"
 #include "aclk/aclk_contexts_api.h"
 #include "aclk/aclk.h"
+#include "storage_engine.h"
 
 #define MESSAGES_PER_BUNDLE_TO_SEND_TO_HUB_PER_HOST         5000
 #define FULL_RETENTION_SCAN_DELAY_AFTER_DB_ROTATION_SECS    120
@@ -115,7 +116,7 @@ typedef enum {
 // check if ANY of the given flags (bits) is set
 #define rrd_flag_check(obj, flag) (rrd_flags_get(obj) & (flag))
 
-// check if ALL of the given flags (bits) are set
+// check if ALL the given flags (bits) are set
 #define rrd_flag_check_all(obj, flag) (rrd_flag_check(obj, flag) == (flag))
 
 // set one or more flags (bits)
@@ -320,15 +321,41 @@ typedef struct rrdcontext {
 // ----------------------------------------------------------------------------
 // helper one-liners for RRDMETRIC
 
+static void rrdmetric_update_retention(RRDMETRIC *rm);
+
 static inline RRDMETRIC *rrdmetric_acquired_value(RRDMETRIC_ACQUIRED *rma) {
     return dictionary_acquired_item_value((DICTIONARY_ITEM *)rma);
 }
 
+static inline RRDMETRIC_ACQUIRED *rrdmetric_acquired_dup(RRDMETRIC_ACQUIRED *rma) {
+    RRDMETRIC *rm = rrdmetric_acquired_value(rma);
+    return (RRDMETRIC_ACQUIRED *)dictionary_acquired_item_dup(rm->ri->rrdmetrics, (DICTIONARY_ITEM *)rma);
+}
+
 static inline void rrdmetric_release(RRDMETRIC_ACQUIRED *rma) {
     RRDMETRIC *rm = rrdmetric_acquired_value(rma);
     dictionary_acquired_item_release(rm->ri->rrdmetrics, (DICTIONARY_ITEM *)rma);
 }
 
+const char *rrdmetric_acquired_id(RRDMETRIC_ACQUIRED *rma) {
+    RRDMETRIC *rm = rrdmetric_acquired_value(rma);
+    return string2str(rm->id);
+}
+
+const char *rrdmetric_acquired_name(RRDMETRIC_ACQUIRED *rma) {
+    RRDMETRIC *rm = rrdmetric_acquired_value(rma);
+    return string2str(rm->name);
+}
+
+NETDATA_DOUBLE rrdmetric_acquired_last_stored_value(RRDMETRIC_ACQUIRED *rma) {
+    RRDMETRIC *rm = rrdmetric_acquired_value(rma);
+
+    if(rm->rrddim)
+        return rm->rrddim->last_stored_value;
+
+    return NAN;
+}
+
 // ----------------------------------------------------------------------------
 // helper one-liners for RRDINSTANCE
 
@@ -336,11 +363,37 @@ static inline RRDINSTANCE *rrdinstance_acquired_value(RRDINSTANCE_ACQUIRED *ria)
     return dictionary_acquired_item_value((DICTIONARY_ITEM *)ria);
 }
 
+static inline RRDINSTANCE_ACQUIRED *rrdinstance_acquired_dup(RRDINSTANCE_ACQUIRED *ria) {
+    RRDINSTANCE *ri = rrdinstance_acquired_value(ria);
+    return (RRDINSTANCE_ACQUIRED *)dictionary_acquired_item_dup(ri->rc->rrdinstances, (DICTIONARY_ITEM *)ria);
+}
+
 static inline void rrdinstance_release(RRDINSTANCE_ACQUIRED *ria) {
     RRDINSTANCE *ri = rrdinstance_acquired_value(ria);
     dictionary_acquired_item_release(ri->rc->rrdinstances, (DICTIONARY_ITEM *)ria);
 }
 
+const char *rrdinstance_acquired_id(RRDINSTANCE_ACQUIRED *ria) {
+    RRDINSTANCE *ri = rrdinstance_acquired_value(ria);
+    return string2str(ri->id);
+}
+
+const char *rrdinstance_acquired_name(RRDINSTANCE_ACQUIRED *ria) {
+    RRDINSTANCE *ri = rrdinstance_acquired_value(ria);
+    return string2str(ri->name);
+}
+
+DICTIONARY *rrdinstance_acquired_labels(RRDINSTANCE_ACQUIRED *ria) {
+    RRDINSTANCE *ri = rrdinstance_acquired_value(ria);
+    return ri->rrdlabels;
+}
+
+DICTIONARY *rrdinstance_acquired_functions(RRDINSTANCE_ACQUIRED *ria) {
+    RRDINSTANCE *ri = rrdinstance_acquired_value(ria);
+    if(!ri->rrdset) return NULL;
+    return ri->rrdset->functions_view;
+}
+
 // ----------------------------------------------------------------------------
 // helper one-liners for RRDCONTEXT
 
@@ -348,6 +401,16 @@ static inline RRDCONTEXT *rrdcontext_acquired_value(RRDCONTEXT_ACQUIRED *rca) {
     return dictionary_acquired_item_value((DICTIONARY_ITEM *)rca);
 }
 
+const char *rrdcontext_acquired_id(RRDCONTEXT_ACQUIRED *rca) {
+    RRDCONTEXT *rc = rrdcontext_acquired_value(rca);
+    return string2str(rc->id);
+}
+
+static inline RRDCONTEXT_ACQUIRED *rrdcontext_acquired_dup(RRDCONTEXT_ACQUIRED *rca) {
+    RRDCONTEXT *rc = rrdcontext_acquired_value(rca);
+    return (RRDCONTEXT_ACQUIRED *)dictionary_acquired_item_dup((DICTIONARY *)rc->rrdhost->rrdctx, (DICTIONARY_ITEM *)rca);
+}
+
 static inline void rrdcontext_release(RRDCONTEXT_ACQUIRED *rca) {
     RRDCONTEXT *rc = rrdcontext_acquired_value(rca);
     dictionary_acquired_item_release((DICTIONARY *)rc->rrdhost->rrdctx, (DICTIONARY_ITEM *)rca);
@@ -467,9 +530,9 @@ static void rrdmetric_delete_callback(const DICTIONARY_ITEM *item __maybe_unused
 
 // called when the same rrdmetric is inserted again to the rrdmetrics dictionary of a rrdinstance
 // while this is called, the dictionary is write locked, but there may be other users of the object
-static bool rrdmetric_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *oldv, void *newv, void *rrdinstance __maybe_unused) {
-    RRDMETRIC *rm     = oldv;
-    RRDMETRIC *rm_new = newv;
+static bool rrdmetric_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value, void *rrdinstance __maybe_unused) {
+    RRDMETRIC *rm     = old_value;
+    RRDMETRIC *rm_new = new_value;
 
     internal_error(rm->id != rm_new->id,
                    "RRDMETRIC: '%s' cannot change id to '%s'",
@@ -729,9 +792,9 @@ static void rrdinstance_delete_callback(const DICTIONARY_ITEM *item __maybe_unus
     rrdinstance_free(ri);
 }
 
-static bool rrdinstance_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *oldv, void *newv, void *rrdcontext __maybe_unused) {
-    RRDINSTANCE *ri     = (RRDINSTANCE *)oldv;
-    RRDINSTANCE *ri_new = (RRDINSTANCE *)newv;
+static bool rrdinstance_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value, void *rrdcontext __maybe_unused) {
+    RRDINSTANCE *ri     = (RRDINSTANCE *)old_value;
+    RRDINSTANCE *ri_new = (RRDINSTANCE *)new_value;
 
     internal_error(ri->id != ri_new->id,
                    "RRDINSTANCE: '%s' cannot change id to '%s'",
@@ -934,7 +997,7 @@ static inline void rrdinstance_from_rrdset(RRDSET *st) {
     }
 
     if(rca_old && ria_old) {
-        // Ooops! The chart changed context!
+        // Oops! The chart changed context!
 
         // RRDCONTEXT *rc_old = rrdcontext_acquired_value(rca_old);
         RRDINSTANCE *ri_old = rrdinstance_acquired_value(ria_old);
@@ -1145,8 +1208,8 @@ static void rrdcontext_insert_callback(const DICTIONARY_ITEM *item __maybe_unuse
 
         rc->version      = rc->hub.version;
         rc->priority     = rc->hub.priority;
-        rc->first_time_t = rc->hub.first_time_t;
-        rc->last_time_t  = rc->hub.last_time_t;
+        rc->first_time_t = (time_t)rc->hub.first_time_t;
+        rc->last_time_t  = (time_t)rc->hub.last_time_t;
 
         if(rc->hub.deleted || !rc->hub.first_time_t)
             rrd_flag_set_deleted(rc, RRD_FLAG_NONE);
@@ -1180,11 +1243,11 @@ static void rrdcontext_delete_callback(const DICTIONARY_ITEM *item __maybe_unuse
     rrdcontext_freez(rc);
 }
 
-static bool rrdcontext_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *oldv, void *newv, void *rrdhost __maybe_unused) {
-    RRDCONTEXT *rc = (RRDCONTEXT *)oldv;
-    RRDCONTEXT *rc_new = (RRDCONTEXT *)newv;
+static bool rrdcontext_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value, void *rrdhost __maybe_unused) {
+    RRDCONTEXT *rc = (RRDCONTEXT *)old_value;
+    RRDCONTEXT *rc_new = (RRDCONTEXT *)new_value;
 
-    //current rc is not archived, new_rc is archived, dont merge
+    //current rc is not archived, new_rc is archived, don't merge
     if (!rrd_flag_is_archived(rc) && rrd_flag_is_archived(rc_new)) {
         rrdcontext_freez(rc_new);
         return false;
@@ -1972,13 +2035,8 @@ int rrdcontext_to_json(RRDHOST *host, BUFFER *wb, time_t after, time_t before, R
 
     RRDCONTEXT *rc = rrdcontext_acquired_value(rca);
 
-    if(after != 0 && before != 0) {
-        long long after_wanted = after;
-        long long before_wanted = before;
-        rrdr_relative_window_to_absolute(&after_wanted, &before_wanted);
-        after = after_wanted;
-        before = before_wanted;
-    }
+    if(after != 0 && before != 0)
+        rrdr_relative_window_to_absolute(&after, &before);
 
     struct rrdcontext_to_json t_contexts = {
         .wb = wb,
@@ -2012,13 +2070,8 @@ int rrdcontexts_to_json(RRDHOST *host, BUFFER *wb, time_t after, time_t before,
     if(host->node_id)
         uuid_unparse(*host->node_id, node_uuid);
 
-    if(after != 0 && before != 0) {
-        long long after_wanted = after;
-        long long before_wanted = before;
-        rrdr_relative_window_to_absolute(&after_wanted, &before_wanted);
-        after = after_wanted;
-        before = before_wanted;
-    }
+    if(after != 0 && before != 0)
+        rrdr_relative_window_to_absolute(&after, &before);
 
     buffer_sprintf(wb, "{\n"
                           "\t\"hostname\": \"%s\""
@@ -2057,6 +2110,675 @@ int rrdcontexts_to_json(RRDHOST *host, BUFFER *wb, time_t after, time_t before,
     return HTTP_RESP_OK;
 }
 
+// ----------------------------------------------------------------------------
+// weights API
+
+static void metric_entry_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) {
+    struct metric_entry *t = value;
+    t->rca = rrdcontext_acquired_dup(t->rca);
+    t->ria = rrdinstance_acquired_dup(t->ria);
+    t->rma = rrdmetric_acquired_dup(t->rma);
+}
+static void metric_entry_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) {
+    struct metric_entry *t = value;
+    rrdcontext_release(t->rca);
+    rrdinstance_release(t->ria);
+    rrdmetric_release(t->rma);
+}
+static bool metric_entry_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *old_value __maybe_unused, void *new_value __maybe_unused, void *data __maybe_unused) {
+    fatal("RRDCONTEXT: %s() detected a conflict on a metric pointer!", __FUNCTION__);
+    return false;
+}
+
+DICTIONARY *rrdcontext_all_metrics_to_dict(RRDHOST *host, SIMPLE_PATTERN *contexts) {
+    if(!host || !host->rrdctx)
+        return NULL;
+
+    DICTIONARY *dict = dictionary_create(DICT_OPTION_SINGLE_THREADED|DICT_OPTION_DONT_OVERWRITE_VALUE);
+    dictionary_register_insert_callback(dict, metric_entry_insert_callback, NULL);
+    dictionary_register_delete_callback(dict, metric_entry_delete_callback, NULL);
+    dictionary_register_conflict_callback(dict, metric_entry_conflict_callback, NULL);
+
+    RRDCONTEXT *rc;
+    dfe_start_reentrant((DICTIONARY *)host->rrdctx, rc) {
+        if(rrd_flag_is_deleted(rc))
+            continue;
+
+        if(contexts && !simple_pattern_matches(contexts, string2str(rc->id)))
+            continue;
+
+        RRDINSTANCE *ri;
+        dfe_start_read(rc->rrdinstances, ri) {
+            if(rrd_flag_is_deleted(ri))
+                continue;
+
+            if(ri->rrdset && rrdset_is_ar_chart(ri->rrdset))
+                continue;
+
+            RRDMETRIC *rm;
+            dfe_start_read(ri->rrdmetrics, rm) {
+                if(rrd_flag_is_deleted(rm))
+                    continue;
+
+                struct metric_entry tmp = {
+                    .rca = (RRDCONTEXT_ACQUIRED *)rc_dfe.item,
+                    .ria = (RRDINSTANCE_ACQUIRED *)ri_dfe.item,
+                    .rma = (RRDMETRIC_ACQUIRED *)rm_dfe.item,
+                };
+
+                char buffer[20 + 1];
+                ssize_t len = snprintfz(buffer, 20, "%p", rm);
+                dictionary_set_advanced(dict, buffer, len + 1, &tmp, sizeof(struct metric_entry), NULL);
+            }
+            dfe_done(rm);
+        }
+        dfe_done(ri);
+    }
+    dfe_done(rc);
+
+    return dict;
+}
+
+// ----------------------------------------------------------------------------
+// query API
+
+typedef struct query_target_locals {
+    time_t start_s;
+
+    QUERY_TARGET *qt;
+
+    RRDSET *st;
+
+    const char *hosts;
+    const char *contexts;
+    const char *charts;
+    const char *dimensions;
+    const char *chart_label_key;
+    const char *charts_labels_filter;
+
+    long long after;
+    long long before;
+    bool match_ids;
+    bool match_names;
+
+    RRDHOST *host;
+    RRDCONTEXT_ACQUIRED *rca;
+    RRDINSTANCE_ACQUIRED *ria;
+
+    size_t metrics_skipped_due_to_not_matching_timeframe;
+} QUERY_TARGET_LOCALS;
+
+static __thread QUERY_TARGET thread_query_target = {};
+void query_target_release(QUERY_TARGET *qt) {
+    if(unlikely(!qt)) return;
+
+    simple_pattern_free(qt->hosts.pattern);
+    qt->hosts.pattern = NULL;
+
+    simple_pattern_free(qt->contexts.pattern);
+    qt->contexts.pattern = NULL;
+
+    simple_pattern_free(qt->instances.pattern);
+    qt->instances.pattern = NULL;
+
+    simple_pattern_free(qt->instances.chart_label_key_pattern);
+    qt->instances.chart_label_key_pattern = NULL;
+
+    simple_pattern_free(qt->instances.charts_labels_filter_pattern);
+    qt->instances.charts_labels_filter_pattern = NULL;
+
+    simple_pattern_free(qt->query.pattern);
+    qt->query.pattern = NULL;
+
+    // release the query
+    for(size_t i = 0, used = qt->query.used; i < used ;i++) {
+        string_freez(qt->query.array[i].dimension.id);
+        qt->query.array[i].dimension.id = NULL;
+
+        string_freez(qt->query.array[i].dimension.name);
+        qt->query.array[i].dimension.name = NULL;
+
+        string_freez(qt->query.array[i].chart.id);
+        qt->query.array[i].chart.id = NULL;
+
+        string_freez(qt->query.array[i].chart.name);
+        qt->query.array[i].chart.name = NULL;
+
+        for(size_t tier = 0; tier < storage_tiers ;tier++) {
+            if(qt->query.array[i].tiers[tier].db_metric_handle) {
+                STORAGE_ENGINE *eng = qt->query.array[i].tiers[tier].eng;
+                eng->api.metric_release(qt->query.array[i].tiers[tier].db_metric_handle);
+                qt->query.array[i].tiers[tier].db_metric_handle = NULL;
+            }
+        }
+    }
+
+    // release the metrics
+    for(size_t i = 0, used = qt->metrics.used; i < used ;i++) {
+        rrdmetric_release(qt->metrics.array[i]);
+        qt->metrics.array[i] = NULL;
+    }
+
+    // release the instances
+    for(size_t i = 0, used = qt->instances.used; i < used ;i++) {
+        rrdinstance_release(qt->instances.array[i]);
+        qt->instances.array[i] = NULL;
+    }
+
+    // release the contexts
+    for(size_t i = 0, used = qt->contexts.used; i < used ;i++) {
+        rrdcontext_release(qt->contexts.array[i]);
+        qt->contexts.array[i] = NULL;
+    }
+
+    // release the hosts
+    for(size_t i = 0, used = qt->hosts.used; i < used ;i++) {
+        qt->hosts.array[i] = NULL;
+    }
+
+    qt->query.used = 0;
+    qt->metrics.used = 0;
+    qt->instances.used = 0;
+    qt->contexts.used = 0;
+    qt->hosts.used = 0;
+
+    qt->db.minimum_latest_update_every = 0;
+    qt->db.first_time_t = 0;
+    qt->db.last_time_t = 0;
+
+    qt->id[0] = '\0';
+
+    qt->used = false;
+}
+void query_target_free(void) {
+    if(thread_query_target.used)
+        query_target_release(&thread_query_target);
+
+    freez(thread_query_target.query.array);
+    thread_query_target.query.array = NULL;
+    thread_query_target.query.size = 0;
+
+    freez(thread_query_target.metrics.array);
+    thread_query_target.metrics.array = NULL;
+    thread_query_target.metrics.size = 0;
+
+    freez(thread_query_target.instances.array);
+    thread_query_target.instances.array = NULL;
+    thread_query_target.instances.size = 0;
+
+    freez(thread_query_target.contexts.array);
+    thread_query_target.contexts.array = NULL;
+    thread_query_target.contexts.size = 0;
+
+    freez(thread_query_target.hosts.array);
+    thread_query_target.hosts.array = NULL;
+    thread_query_target.hosts.size = 0;
+}
+
+static void query_target_add_metric(QUERY_TARGET_LOCALS *qtl, RRDMETRIC_ACQUIRED *rma, RRDINSTANCE *ri, bool instance_matches_label_filters) {
+    QUERY_TARGET *qt = qtl->qt;
+
+    RRDMETRIC *rm = rrdmetric_acquired_value(rma);
+    if(rrd_flag_is_deleted(rm))
+        return;
+
+    if(qt->metrics.used == qt->metrics.size) {
+        qt->metrics.size = (qt->metrics.size) ? qt->metrics.size * 2 : 1;
+        qt->metrics.array = reallocz(qt->metrics.array, qt->metrics.size * sizeof(RRDMETRIC_ACQUIRED *));
+    }
+    qt->metrics.array[qt->metrics.used++] = rrdmetric_acquired_dup(rma);
+
+    if(!instance_matches_label_filters)
+        return;
+
+    time_t common_first_time_t = 0;
+    time_t common_last_time_t = 0;
+    time_t common_update_every = 0;
+    size_t tiers_added = 0;
+    struct {
+        STORAGE_ENGINE *eng;
+        STORAGE_METRIC_HANDLE *db_metric_handle;
+        time_t db_first_time_t;
+        time_t db_last_time_t;
+        time_t db_update_every;
+    } tier_retention[storage_tiers];
+
+    for (size_t tier = 0; tier < storage_tiers; tier++) {
+        STORAGE_ENGINE *eng = qtl->host->db[tier].eng;
+        tier_retention[tier].eng = eng;
+        tier_retention[tier].db_update_every = (time_t) (qtl->host->db[tier].tier_grouping * ri->update_every);
+
+        if(rm->rrddim && rm->rrddim->tiers[tier]->db_metric_handle)
+            tier_retention[tier].db_metric_handle = eng->api.metric_dup(rm->rrddim->tiers[tier]->db_metric_handle);
+        else
+            tier_retention[tier].db_metric_handle = eng->api.metric_get(qtl->host->db[tier].instance, &rm->uuid, NULL);
+
+        if(tier_retention[tier].db_metric_handle) {
+            tier_retention[tier].db_first_time_t = tier_retention[tier].eng->api.query_ops.oldest_time(tier_retention[tier].db_metric_handle);
+            tier_retention[tier].db_last_time_t = tier_retention[tier].eng->api.query_ops.latest_time(tier_retention[tier].db_metric_handle);
+
+            if(!common_first_time_t)
+                common_first_time_t = tier_retention[tier].db_first_time_t;
+            else
+                common_first_time_t = MIN(common_first_time_t, tier_retention[tier].db_first_time_t);
+
+            if(!common_last_time_t)
+                common_last_time_t = tier_retention[tier].db_last_time_t;
+            else
+                common_last_time_t = MAX(common_last_time_t, tier_retention[tier].db_last_time_t);
+
+            if(!common_update_every)
+                common_update_every = tier_retention[tier].db_update_every;
+            else
+                common_update_every = MIN(common_update_every, tier_retention[tier].db_update_every);
+
+            tiers_added++;
+        }
+        else {
+            tier_retention[tier].db_first_time_t = 0;
+            tier_retention[tier].db_last_time_t = 0;
+            tier_retention[tier].db_update_every = 0;
+        }
+    }
+
+    bool timeframe_matches = (tiers_added && common_first_time_t <= qt->window.before && common_last_time_t >= qt->window.after) ? true : false;
+
+    if(timeframe_matches) {
+        RRDR_DIMENSION_FLAGS options = RRDR_DIMENSION_DEFAULT;
+
+        if (rrd_flag_check(rm, RRD_FLAG_HIDDEN)
+            || (rm->rrddim && rrddim_option_check(rm->rrddim, RRDDIM_OPTION_HIDDEN))) {
+            options |= RRDR_DIMENSION_HIDDEN;
+            options &= ~RRDR_DIMENSION_SELECTED;
+        }
+
+        if (qt->query.pattern) {
+            // we have a dimensions pattern
+            // lets see if this dimension is selected
+
+            if ((qtl->match_ids   && simple_pattern_matches(qt->query.pattern, string2str(rm->id)))
+             || (qtl->match_names && simple_pattern_matches(qt->query.pattern, string2str(rm->name)))
+                    ) {
+                // it matches the pattern
+                options |= (RRDR_DIMENSION_SELECTED | RRDR_DIMENSION_NONZERO);
+                options &= ~RRDR_DIMENSION_HIDDEN;
+            }
+            else {
+                // it does not match the pattern
+                options |= RRDR_DIMENSION_HIDDEN;
+                options &= ~RRDR_DIMENSION_SELECTED;
+            }
+        }
+        else {
+            // we don't have a dimensions pattern
+            // so this is a selected dimension
+            // if it is not hidden
+            if(!(options & RRDR_DIMENSION_HIDDEN))
+                options |= RRDR_DIMENSION_SELECTED;
+        }
+
+        if((options & RRDR_DIMENSION_HIDDEN) && (options & RRDR_DIMENSION_SELECTED))
+            options &= ~RRDR_DIMENSION_HIDDEN;
+
+        if(!(options & RRDR_DIMENSION_HIDDEN) || (qt->request.options & RRDR_OPTION_PERCENTAGE)) {
+            // we have a non-hidden dimension
+            // let's add it to the query metrics
+
+            if(ri->rrdset)
+                ri->rrdset->last_accessed_time = qtl->start_s;
+
+            if (qt->query.used == qt->query.size) {
+                qt->query.size = (qt->query.size) ? qt->query.size * 2 : 1;
+                qt->query.array = reallocz(qt->query.array, qt->query.size * sizeof(QUERY_METRIC));
+            }
+            QUERY_METRIC *qm = &qt->query.array[qt->query.used++];
+
+            qm->dimension.options = options;
+
+            qm->link.host = qtl->host;
+            qm->link.rca = qtl->rca;
+            qm->link.ria = qtl->ria;
+            qm->link.rma = rma;
+
+            qm->chart.id = string_dup(ri->id);
+            qm->chart.name = string_dup(ri->name);
+
+            qm->dimension.id = string_dup(rm->id);
+            qm->dimension.name = string_dup(rm->name);
+
+            if (!qt->db.first_time_t || common_first_time_t < qt->db.first_time_t)
+                qt->db.first_time_t = common_first_time_t;
+
+            if (!qt->db.last_time_t || common_last_time_t > qt->db.last_time_t)
+                qt->db.last_time_t = common_last_time_t;
+
+            for (size_t tier = 0; tier < storage_tiers; tier++) {
+                qm->tiers[tier].eng = tier_retention[tier].eng;
+                qm->tiers[tier].db_metric_handle = tier_retention[tier].db_metric_handle;
+                qm->tiers[tier].db_first_time_t = tier_retention[tier].db_first_time_t;
+                qm->tiers[tier].db_last_time_t = tier_retention[tier].db_last_time_t;
+                qm->tiers[tier].db_update_every = tier_retention[tier].db_update_every;
+            }
+        }
+    }
+    else {
+        qtl->metrics_skipped_due_to_not_matching_timeframe++;
+
+        // cleanup anything we allocated to the retention we will not use
+        for(size_t tier = 0; tier < storage_tiers ;tier++) {
+            if (tier_retention[tier].db_metric_handle)
+                tier_retention[tier].eng->api.metric_release(tier_retention[tier].db_metric_handle);
+        }
+    }
+}
+
+static void query_target_add_instance(QUERY_TARGET_LOCALS *qtl, RRDINSTANCE_ACQUIRED *ria) {
+    QUERY_TARGET *qt = qtl->qt;
+
+    RRDINSTANCE *ri = rrdinstance_acquired_value(ria);
+    if(rrd_flag_is_deleted(ri))
+        return;
+
+    if(qt->instances.used == qt->instances.size) {
+        qt->instances.size = (qt->instances.size) ? qt->instances.size * 2 : 1;
+        qt->instances.array = reallocz(qt->instances.array, qt->instances.size * sizeof(RRDINSTANCE_ACQUIRED *));
+    }
+
+    qtl->ria = qt->instances.array[qt->instances.used++] = rrdinstance_acquired_dup(ria);
+
+    if(qt->db.minimum_latest_update_every == 0 || ri->update_every < qt->db.minimum_latest_update_every)
+        qt->db.minimum_latest_update_every = ri->update_every;
+
+    bool instance_matches_label_filters = true;
+    if ((qt->instances.chart_label_key_pattern && !rrdlabels_match_simple_pattern_parsed(ri->rrdlabels, qt->instances.chart_label_key_pattern, ':')) ||
+        (qt->instances.charts_labels_filter_pattern && !rrdlabels_match_simple_pattern_parsed(ri->rrdlabels, qt->instances.charts_labels_filter_pattern, ':')))
+        instance_matches_label_filters = false;
+
+    size_t added = 0;
+
+    if(unlikely(qt->request.rma)) {
+        query_target_add_metric(qtl, qt->request.rma, ri, instance_matches_label_filters);
+        added++;
+    }
+    else {
+        RRDMETRIC *rm;
+        dfe_start_read(ri->rrdmetrics, rm){
+                    query_target_add_metric(qtl, (RRDMETRIC_ACQUIRED *) rm_dfe.item, ri,
+                                            instance_matches_label_filters);
+                    added++;
+                }
+        dfe_done(rm);
+    }
+
+    if(!added) {
+        qt->instances.used--;
+        rrdinstance_release(ria);
+    }
+}
+
+static void query_target_add_context(QUERY_TARGET_LOCALS *qtl, RRDCONTEXT_ACQUIRED *rca) {
+    QUERY_TARGET *qt = qtl->qt;
+
+    RRDCONTEXT *rc = rrdcontext_acquired_value(rca);
+    if(rrd_flag_is_deleted(rc))
+        return;
+
+    if(qt->contexts.used == qt->contexts.size) {
+        qt->contexts.size = (qt->contexts.size) ? qt->contexts.size * 2 : 1;
+        qt->contexts.array = reallocz(qt->contexts.array, qt->contexts.size * sizeof(RRDCONTEXT_ACQUIRED *));
+    }
+    qtl->rca = qt->contexts.array[qt->contexts.used++] = rrdcontext_acquired_dup(rca);
+
+    size_t added = 0;
+    if(unlikely(qt->request.ria)) {
+        query_target_add_instance(qtl, qt->request.ria);
+        added++;
+    }
+    else if(unlikely(qtl->st && qtl->st->rrdcontext == rca && qtl->st->rrdinstance)) {
+        query_target_add_instance(qtl, qtl->st->rrdinstance);
+        added++;
+    }
+    else {
+        RRDINSTANCE *ri;
+        dfe_start_read(rc->rrdinstances, ri) {
+            if(!qt->instances.pattern
+                || (qtl->match_ids   && simple_pattern_matches(qt->instances.pattern, string2str(ri->id)))
+                || (qtl->match_names && simple_pattern_matches(qt->instances.pattern, string2str(ri->name)))
+                ) {
+                query_target_add_instance(qtl, (RRDINSTANCE_ACQUIRED *)ri_dfe.item);
+                added++;
+            }
+        }
+        dfe_done(ri);
+    }
+
+    if(!added) {
+        qt->contexts.used--;
+        rrdcontext_release(rca);
+    }
+}
+
+static void query_target_add_host(QUERY_TARGET_LOCALS *qtl, RRDHOST *host) {
+    QUERY_TARGET *qt = qtl->qt;
+
+    if(qt->hosts.used == qt->hosts.size) {
+        qt->hosts.size = (qt->hosts.size) ? qt->hosts.size * 2 : 1;
+        qt->hosts.array = reallocz(qt->hosts.array, qt->hosts.size * sizeof(RRDHOST *));
+    }
+    qtl->host = qt->hosts.array[qt->hosts.used++] = host;
+
+    // is the chart given valid?
+    if(unlikely(qtl->st && (!qtl->st->rrdinstance || !qtl->st->rrdcontext))) {
+        error("QUERY TARGET: RRDSET '%s' given, because it is not linked to rrdcontext structures. Switching to context query.", rrdset_name(qtl->st));
+
+        if(!is_valid_sp(qtl->charts))
+            qtl->charts = rrdset_name(qtl->st);
+
+        qtl->st = NULL;
+    }
+
+    size_t added = 0;
+    if(unlikely(qt->request.rca)) {
+        query_target_add_context(qtl, qt->request.rca);
+        added++;
+    }
+    else if(unlikely(qtl->st)) {
+        // single chart data queries
+        query_target_add_context(qtl, qtl->st->rrdcontext);
+        added++;
+    }
+    else {
+        // context pattern queries
+        RRDCONTEXT_ACQUIRED *rca = (RRDCONTEXT_ACQUIRED *)dictionary_get_and_acquire_item((DICTIONARY *)qtl->host->rrdctx, qtl->contexts);
+        if(likely(rca)) {
+            // we found it!
+            query_target_add_context(qtl, rca);
+            rrdcontext_release(rca);
+            added++;
+        }
+        else {
+            // Probably it is a pattern, we need to search for it...
+            RRDCONTEXT *rc;
+            dfe_start_read((DICTIONARY *)qtl->host->rrdctx, rc) {
+                if(qt->contexts.pattern && !simple_pattern_matches(qt->contexts.pattern, string2str(rc->id)))
+                    continue;
+
+                query_target_add_context(qtl, (RRDCONTEXT_ACQUIRED *)rc_dfe.item);
+                added++;
+            }
+            dfe_done(rc);
+        }
+    }
+
+    if(!added) {
+        qt->hosts.used--;
+    }
+}
+
+void query_target_generate_name(QUERY_TARGET *qt) {
+    char options_buffer[100 + 1];
+    web_client_api_request_v1_data_options_to_string(options_buffer, 100, qt->request.options);
+
+    char resampling_buffer[20 + 1] = "";
+    if(qt->request.resampling_time > 1)
+        snprintfz(resampling_buffer, 20, "/resampling:%ld", qt->request.resampling_time);
+
+    char tier_buffer[20 + 1] = "";
+    if(qt->request.options & RRDR_OPTION_SELECTED_TIER)
+        snprintfz(tier_buffer, 20, "/tier:%zu", qt->request.tier);
+
+    if(qt->request.st)
+        snprintfz(qt->id, MAX_QUERY_TARGET_ID_LENGTH, "chart://host:%s/instance:%s/dimensions:%s/after:%ld/before:%ld/points:%zu/group:%s%s/options:%s%s%s"
+                  , rrdhost_hostname(qt->request.st->rrdhost)
+                  , rrdset_name(qt->request.st)
+                  , (qt->request.dimensions) ? qt->request.dimensions : "*"
+                  , qt->request.after
+                  , qt->request.before
+                  , qt->request.points
+                  , web_client_api_request_v1_data_group_to_string(qt->request.group_method)
+                  , qt->request.group_options?qt->request.group_options:""
+                  , options_buffer
+                  , resampling_buffer
+                  , tier_buffer
+                  );
+    else if(qt->request.host && qt->request.rca && qt->request.ria && qt->request.rma)
+        snprintfz(qt->id, MAX_QUERY_TARGET_ID_LENGTH, "metric://host:%s/context:%s/instance:%s/dimension:%s/after:%ld/before:%ld/points:%zu/group:%s%s/options:%s%s%s"
+                , rrdhost_hostname(qt->request.host)
+                , rrdcontext_acquired_id(qt->request.rca)
+                , rrdinstance_acquired_id(qt->request.ria)
+                , rrdmetric_acquired_id(qt->request.rma)
+                , qt->request.after
+                , qt->request.before
+                , qt->request.points
+                , web_client_api_request_v1_data_group_to_string(qt->request.group_method)
+                , qt->request.group_options?qt->request.group_options:""
+                , options_buffer
+                , resampling_buffer
+                , tier_buffer
+                );
+    else
+        snprintfz(qt->id, MAX_QUERY_TARGET_ID_LENGTH, "context://host:%s/contexts:%s/instances:%s/dimensions:%s/after:%ld/before:%ld/points:%zu/group:%s%s/options:%s%s%s"
+                , (qt->request.host) ? rrdhost_hostname(qt->request.host) : ((qt->request.hosts) ? qt->request.hosts : "*")
+                , (qt->request.contexts) ? qt->request.contexts : "*"
+                , (qt->request.charts) ? qt->request.charts : "*"
+                , (qt->request.dimensions) ? qt->request.dimensions : "*"
+                , qt->request.after
+                , qt->request.before
+                , qt->request.points
+                , web_client_api_request_v1_data_group_to_string(qt->request.group_method)
+                , qt->request.group_options?qt->request.group_options:""
+                , options_buffer
+                , resampling_buffer
+                , tier_buffer
+                );
+
+    json_fix_string(qt->id);
+}
+
+QUERY_TARGET *query_target_create(QUERY_TARGET_REQUEST *qtr) {
+    QUERY_TARGET *qt = &thread_query_target;
+
+    if(qt->used)
+        fatal("QUERY TARGET: this query target is already used.");
+
+    qt->used = true;
+
+    // copy the request into query_thread_target
+    qt->request = *qtr;
+
+    query_target_generate_name(qt);
+    qt->window.after = qt->request.after;
+    qt->window.before = qt->request.before;
+    rrdr_relative_window_to_absolute(&qt->window.after, &qt->window.before);
+
+    // prepare our local variables - we need these across all these functions
+    QUERY_TARGET_LOCALS qtl = {
+        .qt = qt,
+        .start_s = now_realtime_sec(),
+        .host = qt->request.host,
+        .st = qt->request.st,
+        .hosts = qt->request.hosts,
+        .contexts = qt->request.contexts,
+        .charts = qt->request.charts,
+        .dimensions = qt->request.dimensions,
+        .chart_label_key = qt->request.chart_label_key,
+        .charts_labels_filter = qt->request.charts_labels_filter,
+    };
+
+    qt->db.minimum_latest_update_every = 0; // it will be updated by query_target_add_query()
+
+    // prepare all the patterns
+    qt->hosts.pattern = is_valid_sp(qtl.hosts) ? simple_pattern_create(qtl.hosts, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT) : NULL;
+    qt->contexts.pattern = is_valid_sp(qtl.contexts) ? simple_pattern_create(qtl.contexts, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT) : NULL;
+    qt->instances.pattern = is_valid_sp(qtl.charts) ? simple_pattern_create(qtl.charts, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT) : NULL;
+    qt->query.pattern = is_valid_sp(qtl.dimensions) ? simple_pattern_create(qtl.dimensions, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT) : NULL;
+    qt->instances.chart_label_key_pattern = is_valid_sp(qtl.chart_label_key) ? simple_pattern_create(qtl.chart_label_key, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT) : NULL;
+    qt->instances.charts_labels_filter_pattern = is_valid_sp(qtl.charts_labels_filter) ? simple_pattern_create(qtl.charts_labels_filter, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT) : NULL;
+
+    qtl.match_ids = qt->request.options & RRDR_OPTION_MATCH_IDS;
+    qtl.match_names = qt->request.options & RRDR_OPTION_MATCH_NAMES;
+    if(likely(!qtl.match_ids && !qtl.match_names))
+        qtl.match_ids = qtl.match_names = true;
+
+    // verify that the chart belongs to the host we are interested
+    if(qtl.st) {
+        if (!qtl.host) {
+            // It is NULL, set it ourselves.
+            qtl.host = qtl.st->rrdhost;
+        }
+        else if (unlikely(qtl.host != qtl.st->rrdhost)) {
+            // Oops! A different host!
+            error("QUERY TARGET: RRDSET '%s' given does not belong to host '%s'. Switching query host to '%s'",
+                  rrdset_name(qtl.st), rrdhost_hostname(qtl.host), rrdhost_hostname(qtl.st->rrdhost));
+            qtl.host = qtl.st->rrdhost;
+        }
+    }
+
+    if(qtl.host) {
+        // single host query
+        query_target_add_host(&qtl, qtl.host);
+        qtl.hosts = rrdhost_hostname(qtl.host);
+    }
+    else {
+        // multi host query
+        rrd_rdlock();
+        rrdhost_foreach_read(qtl.host) {
+            if(!qt->hosts.pattern || simple_pattern_matches(qt->hosts.pattern, rrdhost_hostname(qtl.host)))
+                query_target_add_host(&qtl, qtl.host);
+        }
+        rrd_unlock();
+    }
+
+    // make sure everything is good
+    if(!qt->query.used || !qt->metrics.used || !qt->instances.used || !qt->contexts.used || !qt->hosts.used) {
+        internal_error(
+                true
+                , "QUERY TARGET: query '%s' does not have all the data required. "
+                  "Matched %u hosts, %u contexts, %u instances, %u dimensions, %u metrics to query, "
+                  "%zu metrics skipped because they don't have data in the desired time-frame. "
+                  "Aborting it."
+                , qt->id
+                , qt->hosts.used
+                , qt->contexts.used
+                , qt->instances.used
+                , qt->metrics.used
+                , qt->query.used
+                , qtl.metrics_skipped_due_to_not_matching_timeframe
+                );
+
+        query_target_release(qt);
+        return NULL;
+    }
+
+    if(!query_target_calculate_window(qt)) {
+        query_target_release(qt);
+        return NULL;
+    }
+
+    return qt;
+}
+
+
 // ----------------------------------------------------------------------------
 // load from SQL
 
@@ -2073,6 +2795,8 @@ static void rrdinstance_load_dimension(SQL_DIMENSION_DATA *sd, void *data) {
         .name = string_strdupz(sd->name),
         .flags = RRD_FLAG_ARCHIVED | RRD_FLAG_UPDATE_REASON_LOAD_SQL, // no need for atomic
     };
+    if(sd->hidden) trm.flags |= RRD_FLAG_HIDDEN;
+
     uuid_copy(trm.uuid, sd->dim_id);
 
     dictionary_set(ri->rrdmetrics, string2str(trm.id), &trm, sizeof(trm));
@@ -2244,11 +2968,11 @@ static void rrdmetric_update_retention(RRDMETRIC *rm) {
 #ifdef ENABLE_DBENGINE
     else if (dbengine_enabled) {
         RRDHOST *rrdhost = rm->ri->rc->rrdhost;
-        for (int tier = 0; tier < storage_tiers; tier++) {
-            if(!rrdhost->storage_instance[tier]) continue;
+        for (size_t tier = 0; tier < storage_tiers; tier++) {
+            if(!rrdhost->db[tier].instance) continue;
 
             time_t first_time_t, last_time_t;
-            if (rrdeng_metric_retention_by_uuid(rrdhost->storage_instance[tier], &rm->uuid, &first_time_t, &last_time_t) == 0) {
+            if (rrdeng_metric_retention_by_uuid(rrdhost->db[tier].instance, &rm->uuid, &first_time_t, &last_time_t) == 0) {
                 if (first_time_t < min_first_time_t)
                     min_first_time_t = first_time_t;
 
@@ -3045,7 +3769,7 @@ void *rrdcontext_main(void *ptr) {
     worker_register_job_name(WORKER_JOB_HOSTS, "hosts");
     worker_register_job_name(WORKER_JOB_CHECK, "dedup checks");
     worker_register_job_name(WORKER_JOB_SEND, "sent contexts");
-    worker_register_job_name(WORKER_JOB_DEQUEUE, "deduped contexts");
+    worker_register_job_name(WORKER_JOB_DEQUEUE, "deduplicated contexts");
     worker_register_job_name(WORKER_JOB_RETENTION, "metrics retention");
     worker_register_job_name(WORKER_JOB_QUEUED, "queued contexts");
     worker_register_job_name(WORKER_JOB_CLEANUP, "cleanups");

Some files were not shown because too many files changed in this diff