| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "query-internal.h" |
| 4 | |
| 5 | static void query_group_by_make_dimension_key(BUFFER *key, RRDR_GROUP_BY group_by, size_t group_by_id, QUERY_TARGET *qt, QUERY_NODE *qn, QUERY_CONTEXT *qc, QUERY_INSTANCE *qi, QUERY_DIMENSION *qd __maybe_unused, QUERY_METRIC *qm, bool query_has_percentage_of_group) { |
| 6 | buffer_flush(key); |
| 7 | if(unlikely(!query_has_percentage_of_group && qm->status & RRDR_DIMENSION_HIDDEN)) { |
| 8 | buffer_strcat(key, "__hidden_dimensions__"); |
| 9 | } |
| 10 | else if(unlikely(group_by & RRDR_GROUP_BY_SELECTED)) { |
| 11 | buffer_strcat(key, "selected"); |
| 12 | } |
| 13 | else { |
| 14 | if (group_by & RRDR_GROUP_BY_DIMENSION) { |
| 15 | buffer_fast_strcat(key, "|", 1); |
| 16 | buffer_strcat(key, query_metric_name(qt, qm)); |
| 17 | } |
| 18 | |
| 19 | if (group_by & (RRDR_GROUP_BY_INSTANCE|RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE)) { |
| 20 | buffer_fast_strcat(key, "|", 1); |
| 21 | buffer_strcat(key, string2str(query_instance_id_fqdn(qi, qt->request.version))); |
| 22 | } |
| 23 | |
| 24 | if (group_by & RRDR_GROUP_BY_LABEL) { |
| 25 | RRDLABELS *labels = rrdinstance_acquired_labels(qi->ria); |
| 26 | for (size_t l = 0; l < qt->group_by[group_by_id].used; l++) { |
| 27 | buffer_fast_strcat(key, "|", 1); |
| 28 | rrdlabels_get_value_to_buffer_or_unset(labels, key, qt->group_by[group_by_id].label_keys[l], "[unset]"); |
| 29 | } |
| 30 | } |
| 31 | |
| 32 | if (group_by & RRDR_GROUP_BY_NODE) { |
| 33 | buffer_fast_strcat(key, "|", 1); |
| 34 | buffer_strcat(key, qn->rrdhost->machine_guid); |
| 35 | } |
| 36 | |
| 37 | if (group_by & RRDR_GROUP_BY_CONTEXT) { |
| 38 | buffer_fast_strcat(key, "|", 1); |
| 39 | buffer_strcat(key, rrdcontext_acquired_id(qc->rca)); |
| 40 | } |
| 41 | |
| 42 | if (group_by & RRDR_GROUP_BY_UNITS) { |
| 43 | buffer_fast_strcat(key, "|", 1); |
| 44 | buffer_strcat(key, query_target_has_percentage_units(qt) ? "%" : rrdinstance_acquired_units(qi->ria)); |
| 45 | } |
| 46 | } |
| 47 | } |
| 48 | |
| 49 | static void query_group_by_make_dimension_id(BUFFER *key, RRDR_GROUP_BY group_by, size_t group_by_id, QUERY_TARGET *qt, QUERY_NODE *qn, QUERY_CONTEXT *qc, QUERY_INSTANCE *qi, QUERY_DIMENSION *qd __maybe_unused, QUERY_METRIC *qm, bool query_has_percentage_of_group) { |
| 50 | buffer_flush(key); |
| 51 | if(unlikely(!query_has_percentage_of_group && qm->status & RRDR_DIMENSION_HIDDEN)) { |
| 52 | buffer_strcat(key, "__hidden_dimensions__"); |
| 53 | } |
| 54 | else if(unlikely(group_by & RRDR_GROUP_BY_SELECTED)) { |
| 55 | buffer_strcat(key, "selected"); |
| 56 | } |
| 57 | else { |
| 58 | if (group_by & RRDR_GROUP_BY_DIMENSION) { |
| 59 | buffer_strcat(key, query_metric_name(qt, qm)); |
| 60 | } |
| 61 | |
| 62 | if (group_by & (RRDR_GROUP_BY_INSTANCE|RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE)) { |
| 63 | if (buffer_strlen(key) != 0) |
| 64 | buffer_fast_strcat(key, ",", 1); |
| 65 | |
| 66 | if (group_by & RRDR_GROUP_BY_NODE) |
| 67 | buffer_strcat(key, rrdinstance_acquired_id(qi->ria)); |
| 68 | else |
| 69 | buffer_strcat(key, string2str(query_instance_id_fqdn(qi, qt->request.version))); |
| 70 | } |
| 71 | |
| 72 | if (group_by & RRDR_GROUP_BY_LABEL) { |
| 73 | RRDLABELS *labels = rrdinstance_acquired_labels(qi->ria); |
| 74 | for (size_t l = 0; l < qt->group_by[group_by_id].used; l++) { |
| 75 | if (buffer_strlen(key) != 0) |
| 76 | buffer_fast_strcat(key, ",", 1); |
| 77 | rrdlabels_get_value_to_buffer_or_unset(labels, key, qt->group_by[group_by_id].label_keys[l], "[unset]"); |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | if (group_by & RRDR_GROUP_BY_NODE) { |
| 82 | if (buffer_strlen(key) != 0) |
| 83 | buffer_fast_strcat(key, ",", 1); |
| 84 | |
| 85 | buffer_strcat(key, qn->rrdhost->machine_guid); |
| 86 | } |
| 87 | |
| 88 | if (group_by & RRDR_GROUP_BY_CONTEXT) { |
| 89 | if (buffer_strlen(key) != 0) |
| 90 | buffer_fast_strcat(key, ",", 1); |
| 91 | |
| 92 | buffer_strcat(key, rrdcontext_acquired_id(qc->rca)); |
| 93 | } |
| 94 | |
| 95 | if (group_by & RRDR_GROUP_BY_UNITS) { |
| 96 | if (buffer_strlen(key) != 0) |
| 97 | buffer_fast_strcat(key, ",", 1); |
| 98 | |
| 99 | buffer_strcat(key, query_target_has_percentage_units(qt) ? "%" : rrdinstance_acquired_units(qi->ria)); |
| 100 | } |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | static void query_group_by_make_dimension_name(BUFFER *key, RRDR_GROUP_BY group_by, size_t group_by_id, QUERY_TARGET *qt, QUERY_NODE *qn, QUERY_CONTEXT *qc, QUERY_INSTANCE *qi, QUERY_DIMENSION *qd __maybe_unused, QUERY_METRIC *qm, bool query_has_percentage_of_group) { |
| 105 | buffer_flush(key); |
| 106 | if(unlikely(!query_has_percentage_of_group && qm->status & RRDR_DIMENSION_HIDDEN)) { |
| 107 | buffer_strcat(key, "__hidden_dimensions__"); |
| 108 | } |
| 109 | else if(unlikely(group_by & RRDR_GROUP_BY_SELECTED)) { |
| 110 | buffer_strcat(key, "selected"); |
| 111 | } |
| 112 | else { |
| 113 | if (group_by & RRDR_GROUP_BY_DIMENSION) { |
| 114 | buffer_strcat(key, query_metric_name(qt, qm)); |
| 115 | } |
| 116 | |
| 117 | if (group_by & (RRDR_GROUP_BY_INSTANCE|RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE)) { |
| 118 | if (buffer_strlen(key) != 0) |
| 119 | buffer_fast_strcat(key, ",", 1); |
| 120 | |
| 121 | if (group_by & RRDR_GROUP_BY_NODE) |
| 122 | buffer_strcat(key, rrdinstance_acquired_name(qi->ria)); |
| 123 | else |
| 124 | buffer_strcat(key, string2str(query_instance_name_fqdn(qi, qt->request.version))); |
| 125 | } |
| 126 | |
| 127 | if (group_by & RRDR_GROUP_BY_LABEL) { |
| 128 | RRDLABELS *labels = rrdinstance_acquired_labels(qi->ria); |
| 129 | for (size_t l = 0; l < qt->group_by[group_by_id].used; l++) { |
| 130 | if (buffer_strlen(key) != 0) |
| 131 | buffer_fast_strcat(key, ",", 1); |
| 132 | rrdlabels_get_value_to_buffer_or_unset(labels, key, qt->group_by[group_by_id].label_keys[l], "[unset]"); |
| 133 | } |
| 134 | } |
| 135 | |
| 136 | if (group_by & RRDR_GROUP_BY_NODE) { |
| 137 | if (buffer_strlen(key) != 0) |
| 138 | buffer_fast_strcat(key, ",", 1); |
| 139 | |
| 140 | buffer_strcat(key, rrdhost_hostname(qn->rrdhost)); |
| 141 | } |
| 142 | |
| 143 | if (group_by & RRDR_GROUP_BY_CONTEXT) { |
| 144 | if (buffer_strlen(key) != 0) |
| 145 | buffer_fast_strcat(key, ",", 1); |
| 146 | |
| 147 | buffer_strcat(key, rrdcontext_acquired_id(qc->rca)); |
| 148 | } |
| 149 | |
| 150 | if (group_by & RRDR_GROUP_BY_UNITS) { |
| 151 | if (buffer_strlen(key) != 0) |
| 152 | buffer_fast_strcat(key, ",", 1); |
| 153 | |
| 154 | buffer_strcat(key, query_target_has_percentage_units(qt) ? "%" : rrdinstance_acquired_units(qi->ria)); |
| 155 | } |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | struct rrdr_group_by_entry { |
| 160 | size_t priority; |
| 161 | size_t count; |
| 162 | STRING *id; |
| 163 | STRING *name; |
| 164 | STRING *units; |
| 165 | RRDR_DIMENSION_FLAGS od; |
| 166 | DICTIONARY *dl; |
| 167 | }; |
| 168 | |
| 169 | RRDR *rrd2rrdr_group_by_initialize(ONEWAYALLOC *owa, QUERY_TARGET *qt) { |
| 170 | RRDR *r_tmp = NULL; |
| 171 | RRDR_OPTIONS options = qt->window.options; |
| 172 | |
| 173 | if(qt->request.version < 2) { |
| 174 | // v1 query |
| 175 | RRDR *r = rrdr_create(owa, qt, qt->query.used, qt->window.points); |
| 176 | if(unlikely(!r)) { |
| 177 | internal_error(true, "QUERY: cannot create RRDR for %s, after=%ld, before=%ld, dimensions=%u, points=%zu", |
| 178 | qt->id, qt->window.after, qt->window.before, qt->query.used, qt->window.points); |
| 179 | return NULL; |
| 180 | } |
| 181 | r->group_by.r = NULL; |
| 182 | |
| 183 | for(size_t d = 0; d < qt->query.used ; d++) { |
| 184 | QUERY_METRIC *qm = query_metric(qt, d); |
| 185 | QUERY_DIMENSION *qd = query_dimension(qt, qm->link.query_dimension_id); |
| 186 | r->di[d] = rrdmetric_acquired_id_dup(qd->rma); |
| 187 | r->dn[d] = rrdmetric_acquired_name_dup(qd->rma); |
| 188 | } |
| 189 | |
| 190 | rrd2rrdr_set_timestamps(r); |
| 191 | return r; |
| 192 | } |
| 193 | // v2 query |
| 194 | |
| 195 | // parse all the group-by label keys |
| 196 | for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) { |
| 197 | if (qt->request.group_by[g].group_by & RRDR_GROUP_BY_LABEL && |
| 198 | qt->request.group_by[g].group_by_label && *qt->request.group_by[g].group_by_label) |
| 199 | qt->group_by[g].used = quoted_strings_splitter_query_group_by_label( |
| 200 | qt->request.group_by[g].group_by_label, qt->group_by[g].label_keys, |
| 201 | GROUP_BY_MAX_LABEL_KEYS); |
| 202 | |
| 203 | if (!qt->group_by[g].used) |
| 204 | qt->request.group_by[g].group_by &= ~RRDR_GROUP_BY_LABEL; |
| 205 | } |
| 206 | |
| 207 | // make sure there are valid group-by methods |
| 208 | for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) { |
| 209 | if(!(qt->request.group_by[g].group_by & SUPPORTED_GROUP_BY_METHODS)) |
| 210 | qt->request.group_by[g].group_by = (g == 0) ? RRDR_GROUP_BY_DIMENSION : RRDR_GROUP_BY_NONE; |
| 211 | } |
| 212 | |
| 213 | bool query_has_percentage_of_group = query_target_has_percentage_of_group(qt); |
| 214 | |
| 215 | // merge all group-by options to upper levels, |
| 216 | // so that the top level has all the groupings of the inner levels, |
| 217 | // and each subsequent level has all the groupings of its inner levels. |
| 218 | for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES - 1 ;g++) { |
| 219 | if(qt->request.group_by[g].group_by == RRDR_GROUP_BY_NONE) |
| 220 | continue; |
| 221 | |
| 222 | if(qt->request.group_by[g].group_by == RRDR_GROUP_BY_SELECTED) { |
| 223 | for (size_t r = g + 1; r < MAX_QUERY_GROUP_BY_PASSES; r++) |
| 224 | qt->request.group_by[r].group_by = RRDR_GROUP_BY_NONE; |
| 225 | } |
| 226 | else { |
| 227 | for (size_t r = g + 1; r < MAX_QUERY_GROUP_BY_PASSES; r++) { |
| 228 | if (qt->request.group_by[r].group_by == RRDR_GROUP_BY_NONE) |
| 229 | continue; |
| 230 | |
| 231 | if (qt->request.group_by[r].group_by != RRDR_GROUP_BY_SELECTED) { |
| 232 | if(qt->request.group_by[r].group_by & RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE) |
| 233 | qt->request.group_by[g].group_by |= RRDR_GROUP_BY_INSTANCE; |
| 234 | else |
| 235 | qt->request.group_by[g].group_by |= qt->request.group_by[r].group_by; |
| 236 | |
| 237 | if(qt->request.group_by[r].group_by & RRDR_GROUP_BY_LABEL) { |
| 238 | for (size_t lr = 0; lr < qt->group_by[r].used; lr++) { |
| 239 | bool found = false; |
| 240 | for (size_t lg = 0; lg < qt->group_by[g].used; lg++) { |
| 241 | if (strcmp(qt->group_by[g].label_keys[lg], qt->group_by[r].label_keys[lr]) == 0) { |
| 242 | found = true; |
| 243 | break; |
| 244 | } |
| 245 | } |
| 246 | |
| 247 | if (!found && qt->group_by[g].used < GROUP_BY_MAX_LABEL_KEYS * MAX_QUERY_GROUP_BY_PASSES) |
| 248 | qt->group_by[g].label_keys[qt->group_by[g].used++] = qt->group_by[r].label_keys[lr]; |
| 249 | } |
| 250 | } |
| 251 | } |
| 252 | } |
| 253 | } |
| 254 | } |
| 255 | |
| 256 | int added = 0; |
| 257 | RRDR *first_r = NULL, *last_r = NULL; |
| 258 | BUFFER *key = buffer_create(0, NULL); |
| 259 | struct rrdr_group_by_entry *entries = onewayalloc_mallocz(owa, qt->query.used * sizeof(struct rrdr_group_by_entry)); |
| 260 | DICTIONARY *groups = dictionary_create(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE); |
| 261 | DICTIONARY *label_keys = NULL; |
| 262 | |
| 263 | for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) { |
| 264 | RRDR_GROUP_BY group_by = qt->request.group_by[g].group_by; |
| 265 | RRDR_GROUP_BY_FUNCTION aggregation_method = qt->request.group_by[g].aggregation; |
| 266 | |
| 267 | if(group_by == RRDR_GROUP_BY_NONE) |
| 268 | break; |
| 269 | |
| 270 | memset(entries, 0, qt->query.used * sizeof(struct rrdr_group_by_entry)); |
| 271 | dictionary_flush(groups); |
| 272 | added = 0; |
| 273 | |
| 274 | size_t hidden_dimensions = 0; |
| 275 | bool final_grouping = (g == MAX_QUERY_GROUP_BY_PASSES - 1 || qt->request.group_by[g + 1].group_by == RRDR_GROUP_BY_NONE) ? true : false; |
| 276 | |
| 277 | if (final_grouping && (options & RRDR_OPTION_GROUP_BY_LABELS)) |
| 278 | label_keys = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE, NULL, 0); |
| 279 | |
| 280 | QUERY_INSTANCE *last_qi = NULL; |
| 281 | size_t priority = 0; |
| 282 | time_t update_every_max = 0; |
| 283 | for (size_t d = 0; d < qt->query.used; d++) { |
| 284 | QUERY_METRIC *qm = query_metric(qt, d); |
| 285 | QUERY_DIMENSION *qd = query_dimension(qt, qm->link.query_dimension_id); |
| 286 | QUERY_INSTANCE *qi = query_instance(qt, qm->link.query_instance_id); |
| 287 | QUERY_CONTEXT *qc = query_context(qt, qm->link.query_context_id); |
| 288 | QUERY_NODE *qn = query_node(qt, qm->link.query_node_id); |
| 289 | |
| 290 | if (qi != last_qi) { |
| 291 | last_qi = qi; |
| 292 | |
| 293 | time_t update_every = rrdinstance_acquired_update_every(qi->ria); |
| 294 | if (update_every > update_every_max) |
| 295 | update_every_max = update_every; |
| 296 | } |
| 297 | |
| 298 | priority = qd->priority; |
| 299 | |
| 300 | if(qm->status & RRDR_DIMENSION_HIDDEN) |
| 301 | hidden_dimensions++; |
| 302 | |
| 303 | // -------------------------------------------------------------------- |
| 304 | // generate the group by key |
| 305 | |
| 306 | query_group_by_make_dimension_key(key, group_by, g, qt, qn, qc, qi, qd, qm, query_has_percentage_of_group); |
| 307 | |
| 308 | // lookup the key in the dictionary |
| 309 | |
| 310 | int pos = -1; |
| 311 | int *set = dictionary_set(groups, buffer_tostring(key), &pos, sizeof(pos)); |
| 312 | if (*set == -1) { |
| 313 | // the key just added to the dictionary |
| 314 | |
| 315 | *set = pos = added++; |
| 316 | |
| 317 | // ---------------------------------------------------------------- |
| 318 | // generate the dimension id |
| 319 | |
| 320 | query_group_by_make_dimension_id(key, group_by, g, qt, qn, qc, qi, qd, qm, query_has_percentage_of_group); |
| 321 | entries[pos].id = string_strdupz(buffer_tostring(key)); |
| 322 | |
| 323 | // ---------------------------------------------------------------- |
| 324 | // generate the dimension name |
| 325 | |
| 326 | query_group_by_make_dimension_name(key, group_by, g, qt, qn, qc, qi, qd, qm, query_has_percentage_of_group); |
| 327 | entries[pos].name = string_strdupz(buffer_tostring(key)); |
| 328 | |
| 329 | // add the rest of the info |
| 330 | entries[pos].units = rrdinstance_acquired_units_dup(qi->ria); |
| 331 | entries[pos].priority = priority; |
| 332 | |
| 333 | if (label_keys) { |
| 334 | entries[pos].dl = dictionary_create_advanced( |
| 335 | DICT_OPTION_SINGLE_THREADED | DICT_OPTION_FIXED_SIZE | DICT_OPTION_DONT_OVERWRITE_VALUE, |
| 336 | NULL, sizeof(struct group_by_label_key)); |
| 337 | dictionary_register_insert_callback(entries[pos].dl, group_by_label_key_insert_cb, label_keys); |
| 338 | dictionary_register_delete_callback(entries[pos].dl, group_by_label_key_delete_cb, label_keys); |
| 339 | } |
| 340 | } else { |
| 341 | // the key found in the dictionary |
| 342 | pos = *set; |
| 343 | } |
| 344 | |
| 345 | entries[pos].count++; |
| 346 | |
| 347 | if (unlikely(priority < entries[pos].priority)) |
| 348 | entries[pos].priority = priority; |
| 349 | |
| 350 | if(g > 0) |
| 351 | last_r->dgbs[qm->grouped_as.slot] = pos; |
| 352 | else |
| 353 | qm->grouped_as.first_slot = pos; |
| 354 | |
| 355 | qm->grouped_as.slot = pos; |
| 356 | qm->grouped_as.id = entries[pos].id; |
| 357 | qm->grouped_as.name = entries[pos].name; |
| 358 | qm->grouped_as.units = entries[pos].units; |
| 359 | |
| 360 | // copy the dimension flags decided by the query target |
| 361 | // we need this, because if a dimension is explicitly selected |
| 362 | // the query target adds to it the non-zero flag |
| 363 | qm->status |= RRDR_DIMENSION_GROUPED; |
| 364 | |
| 365 | if(query_has_percentage_of_group) |
| 366 | // when the query has percentage of group |
| 367 | // there will be no hidden dimensions in the final query, |
| 368 | // so we have to remove the hidden flag from all dimensions |
| 369 | entries[pos].od |= qm->status & ~RRDR_DIMENSION_HIDDEN; |
| 370 | else |
| 371 | entries[pos].od |= qm->status; |
| 372 | |
| 373 | if (entries[pos].dl) |
| 374 | rrdlabels_walkthrough_read(rrdinstance_acquired_labels(qi->ria), |
| 375 | rrdlabels_traversal_cb_to_group_by_label_key, entries[pos].dl); |
| 376 | } |
| 377 | |
| 378 | RRDR *r = rrdr_create(owa, qt, added, qt->window.points); |
| 379 | if (!r) { |
| 380 | internal_error(true, |
| 381 | "QUERY: cannot create group by RRDR for %s, after=%ld, before=%ld, dimensions=%d, points=%zu", |
| 382 | qt->id, qt->window.after, qt->window.before, added, qt->window.points); |
| 383 | goto cleanup; |
| 384 | } |
| 385 | // prevent double free at cleanup in case of error |
| 386 | added = 0; |
| 387 | |
| 388 | // link this RRDR |
| 389 | if(!last_r) |
| 390 | first_r = last_r = r; |
| 391 | else |
| 392 | last_r->group_by.r = r; |
| 393 | |
| 394 | last_r = r; |
| 395 | |
| 396 | rrd2rrdr_set_timestamps(r); |
| 397 | |
| 398 | if(r->d) { |
| 399 | r->dp = onewayalloc_callocz(owa, r->d, sizeof(*r->dp)); |
| 400 | r->dview = onewayalloc_callocz(owa, r->d, sizeof(*r->dview)); |
| 401 | r->dgbc = onewayalloc_callocz(owa, r->d, sizeof(*r->dgbc)); |
| 402 | r->dqp = onewayalloc_callocz(owa, r->d, sizeof(STORAGE_POINT)); |
| 403 | |
| 404 | if(!final_grouping) |
| 405 | // this is where we are going to store the slot in the next RRDR |
| 406 | // that we are going to group by the dimension of this RRDR |
| 407 | r->dgbs = onewayalloc_callocz(owa, r->d, sizeof(*r->dgbs)); |
| 408 | |
| 409 | if (label_keys) { |
| 410 | r->dl = onewayalloc_callocz(owa, r->d, sizeof(DICTIONARY *)); |
| 411 | r->label_keys = label_keys; |
| 412 | label_keys = NULL; |
| 413 | } |
| 414 | |
| 415 | if(r->n) { |
| 416 | r->gbc = onewayalloc_callocz(owa, r->n * r->d, sizeof(*r->gbc)); |
| 417 | |
| 418 | if(hidden_dimensions && ((group_by & RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE) || (aggregation_method == RRDR_GROUP_BY_FUNCTION_PERCENTAGE))) |
| 419 | // this is where we are going to group the hidden dimensions |
| 420 | r->vh = onewayalloc_mallocz(owa, r->n * r->d * sizeof(*r->vh)); |
| 421 | } |
| 422 | } |
| 423 | |
| 424 | // zero r (dimension options, names, and ids) |
| 425 | // this is required, because group-by may lead to empty dimensions |
| 426 | for (size_t d = 0; d < r->d; d++) { |
| 427 | r->di[d] = entries[d].id; |
| 428 | r->dn[d] = entries[d].name; |
| 429 | |
| 430 | r->od[d] = entries[d].od; |
| 431 | r->du[d] = entries[d].units; |
| 432 | r->dp[d] = entries[d].priority; |
| 433 | r->dgbc[d] = entries[d].count; |
| 434 | |
| 435 | if (r->dl) |
| 436 | r->dl[d] = entries[d].dl; |
| 437 | } |
| 438 | |
| 439 | // initialize partial trimming |
| 440 | r->partial_data_trimming.max_update_every = update_every_max * 2; |
| 441 | r->partial_data_trimming.expected_after = |
| 442 | (!query_target_aggregatable(qt) && |
| 443 | qt->window.before >= qt->window.now - r->partial_data_trimming.max_update_every) ? |
| 444 | qt->window.before - r->partial_data_trimming.max_update_every : |
| 445 | qt->window.before; |
| 446 | r->partial_data_trimming.trimmed_after = qt->window.before; |
| 447 | |
| 448 | // make all values empty |
| 449 | if(r->n && r->d) { |
| 450 | for (size_t i = 0; i != r->n; i++) { |
| 451 | NETDATA_DOUBLE *cn = &r->v[i * r->d]; |
| 452 | RRDR_VALUE_FLAGS *co = &r->o[i * r->d]; |
| 453 | NETDATA_DOUBLE *ar = &r->ar[i * r->d]; |
| 454 | NETDATA_DOUBLE *vh = r->vh ? &r->vh[i * r->d] : NULL; |
| 455 | |
| 456 | for (size_t d = 0; d < r->d; d++) { |
| 457 | cn[d] = NAN; |
| 458 | ar[d] = 0.0; |
| 459 | co[d] = RRDR_VALUE_EMPTY; |
| 460 | |
| 461 | if (vh) |
| 462 | vh[d] = NAN; |
| 463 | } |
| 464 | } |
| 465 | } |
| 466 | } |
| 467 | |
| 468 | if(!first_r || !last_r) |
| 469 | goto cleanup; |
| 470 | |
| 471 | r_tmp = rrdr_create(owa, qt, 1, qt->window.points); |
| 472 | if (!r_tmp) { |
| 473 | internal_error(true, |
| 474 | "QUERY: cannot create group by temporary RRDR for %s, after=%ld, before=%ld, dimensions=%d, points=%zu", |
| 475 | qt->id, qt->window.after, qt->window.before, 1, qt->window.points); |
| 476 | goto cleanup; |
| 477 | } |
| 478 | rrd2rrdr_set_timestamps(r_tmp); |
| 479 | r_tmp->group_by.r = first_r; |
| 480 | |
| 481 | cleanup: |
| 482 | if(!first_r || !last_r || !r_tmp) { |
| 483 | if(r_tmp) { |
| 484 | r_tmp->group_by.r = NULL; |
| 485 | rrdr_free(owa, r_tmp); |
| 486 | } |
| 487 | |
| 488 | if(first_r) { |
| 489 | RRDR *r = first_r; |
| 490 | while (r) { |
| 491 | r_tmp = r->group_by.r; |
| 492 | r->group_by.r = NULL; |
| 493 | rrdr_free(owa, r); |
| 494 | r = r_tmp; |
| 495 | } |
| 496 | } |
| 497 | |
| 498 | if(entries && added) { |
| 499 | for (int d = 0; d < added; d++) { |
| 500 | string_freez(entries[d].id); |
| 501 | string_freez(entries[d].name); |
| 502 | string_freez(entries[d].units); |
| 503 | dictionary_destroy(entries[d].dl); |
| 504 | } |
| 505 | } |
| 506 | dictionary_destroy(label_keys); |
| 507 | |
| 508 | first_r = last_r = r_tmp = NULL; |
| 509 | } |
| 510 | |
| 511 | buffer_free(key); |
| 512 | onewayalloc_freez(owa, entries); |
| 513 | dictionary_destroy(groups); |
| 514 | |
| 515 | return r_tmp; |
| 516 | } |
| 517 |