130
// ----------------------------------------------------------------------------
131
// group by
132
133
-struct group_by_label_key {
134
- DICTIONARY *values;
135
-};
136
-
137
-static void group_by_label_key_insert_cb(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data) {
133
+void group_by_label_key_insert_cb(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data) {
134
// add the key to our r->label_keys global keys dictionary
135
DICTIONARY *label_keys = data;
136
dictionary_set(label_keys, dictionary_acquired_item_name(item), NULL, 0);
140
k->values = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE, NULL, 0);
141
}
142
147
-static void group_by_label_key_delete_cb(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) {
143
+void group_by_label_key_delete_cb(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) {
144
struct group_by_label_key *k = value;
145
dictionary_destroy(k->values);
146
}
147
152
-static int rrdlabels_traversal_cb_to_group_by_label_key(const char *name, const char *value, RRDLABEL_SRC ls __maybe_unused, void *data) {
148
+int rrdlabels_traversal_cb_to_group_by_label_key(const char *name, const char *value, RRDLABEL_SRC ls __maybe_unused, void *data) {
149
DICTIONARY *dl = data;
150
struct group_by_label_key *k = dictionary_set(dl, name, NULL, sizeof(struct group_by_label_key));
151
dictionary_set(k->values, value, NULL, 0);
187
buffer_json_object_close(wb); // key
188
}
189
194
-static void rrd2rrdr_set_timestamps(RRDR *r) {
190
+void rrd2rrdr_set_timestamps(RRDR *r) {
191
QUERY_TARGET *qt = r->internal.qt;
192
193
internal_fatal(qt->window.points != r->n, "QUERY: mismatch to the number of points in qt and r");
225
before_wanted, r->t[points_wanted - 1]);
226
}
227
232
-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) {
233
- buffer_flush(key);
234
- if(unlikely(!query_has_percentage_of_group && qm->status & RRDR_DIMENSION_HIDDEN)) {
235
- buffer_strcat(key, "__hidden_dimensions__");
236
- }
237
- else if(unlikely(group_by & RRDR_GROUP_BY_SELECTED)) {
238
- buffer_strcat(key, "selected");
239
- }
240
- else {
241
- if (group_by & RRDR_GROUP_BY_DIMENSION) {
242
- buffer_fast_strcat(key, "|", 1);
243
- buffer_strcat(key, query_metric_name(qt, qm));
244
- }
245
-
246
- if (group_by & (RRDR_GROUP_BY_INSTANCE|RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE)) {
247
- buffer_fast_strcat(key, "|", 1);
248
- buffer_strcat(key, string2str(query_instance_id_fqdn(qi, qt->request.version)));
249
- }
250
-
251
- if (group_by & RRDR_GROUP_BY_LABEL) {
252
- RRDLABELS *labels = rrdinstance_acquired_labels(qi->ria);
253
- for (size_t l = 0; l < qt->group_by[group_by_id].used; l++) {
254
- buffer_fast_strcat(key, "|", 1);
255
- rrdlabels_get_value_to_buffer_or_unset(labels, key, qt->group_by[group_by_id].label_keys[l], "[unset]");
256
- }
257
- }
258
-
259
- if (group_by & RRDR_GROUP_BY_NODE) {
260
- buffer_fast_strcat(key, "|", 1);
261
- buffer_strcat(key, qn->rrdhost->machine_guid);
262
- }
263
-
264
- if (group_by & RRDR_GROUP_BY_CONTEXT) {
265
- buffer_fast_strcat(key, "|", 1);
266
- buffer_strcat(key, rrdcontext_acquired_id(qc->rca));
267
- }
268
-
269
- if (group_by & RRDR_GROUP_BY_UNITS) {
270
- buffer_fast_strcat(key, "|", 1);
271
- buffer_strcat(key, query_target_has_percentage_units(qt) ? "%" : rrdinstance_acquired_units(qi->ria));
272
- }
273
- }
274
-}
275
-
276
-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) {
277
- buffer_flush(key);
278
- if(unlikely(!query_has_percentage_of_group && qm->status & RRDR_DIMENSION_HIDDEN)) {
279
- buffer_strcat(key, "__hidden_dimensions__");
280
- }
281
- else if(unlikely(group_by & RRDR_GROUP_BY_SELECTED)) {
282
- buffer_strcat(key, "selected");
283
- }
284
- else {
285
- if (group_by & RRDR_GROUP_BY_DIMENSION) {
286
- buffer_strcat(key, query_metric_name(qt, qm));
287
- }
288
-
289
- if (group_by & (RRDR_GROUP_BY_INSTANCE|RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE)) {
290
- if (buffer_strlen(key) != 0)
291
- buffer_fast_strcat(key, ",", 1);
292
-
293
- if (group_by & RRDR_GROUP_BY_NODE)
294
- buffer_strcat(key, rrdinstance_acquired_id(qi->ria));
295
- else
296
- buffer_strcat(key, string2str(query_instance_id_fqdn(qi, qt->request.version)));
297
- }
298
-
299
- if (group_by & RRDR_GROUP_BY_LABEL) {
300
- RRDLABELS *labels = rrdinstance_acquired_labels(qi->ria);
301
- for (size_t l = 0; l < qt->group_by[group_by_id].used; l++) {
302
- if (buffer_strlen(key) != 0)
303
- buffer_fast_strcat(key, ",", 1);
304
- rrdlabels_get_value_to_buffer_or_unset(labels, key, qt->group_by[group_by_id].label_keys[l], "[unset]");
305
- }
306
- }
307
-
308
- if (group_by & RRDR_GROUP_BY_NODE) {
309
- if (buffer_strlen(key) != 0)
310
- buffer_fast_strcat(key, ",", 1);
311
-
312
- buffer_strcat(key, qn->rrdhost->machine_guid);
313
- }
314
-
315
- if (group_by & RRDR_GROUP_BY_CONTEXT) {
316
- if (buffer_strlen(key) != 0)
317
- buffer_fast_strcat(key, ",", 1);
318
-
319
- buffer_strcat(key, rrdcontext_acquired_id(qc->rca));
320
- }
321
-
322
- if (group_by & RRDR_GROUP_BY_UNITS) {
323
- if (buffer_strlen(key) != 0)
324
- buffer_fast_strcat(key, ",", 1);
325
-
326
- buffer_strcat(key, query_target_has_percentage_units(qt) ? "%" : rrdinstance_acquired_units(qi->ria));
327
- }
328
- }
329
-}
330
-
331
-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) {
332
- buffer_flush(key);
333
- if(unlikely(!query_has_percentage_of_group && qm->status & RRDR_DIMENSION_HIDDEN)) {
334
- buffer_strcat(key, "__hidden_dimensions__");
335
- }
336
- else if(unlikely(group_by & RRDR_GROUP_BY_SELECTED)) {
337
- buffer_strcat(key, "selected");
338
- }
339
- else {
340
- if (group_by & RRDR_GROUP_BY_DIMENSION) {
341
- buffer_strcat(key, query_metric_name(qt, qm));
342
- }
343
-
344
- if (group_by & (RRDR_GROUP_BY_INSTANCE|RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE)) {
345
- if (buffer_strlen(key) != 0)
346
- buffer_fast_strcat(key, ",", 1);
347
-
348
- if (group_by & RRDR_GROUP_BY_NODE)
349
- buffer_strcat(key, rrdinstance_acquired_name(qi->ria));
350
- else
351
- buffer_strcat(key, string2str(query_instance_name_fqdn(qi, qt->request.version)));
352
- }
353
-
354
- if (group_by & RRDR_GROUP_BY_LABEL) {
355
- RRDLABELS *labels = rrdinstance_acquired_labels(qi->ria);
356
- for (size_t l = 0; l < qt->group_by[group_by_id].used; l++) {
357
- if (buffer_strlen(key) != 0)
358
- buffer_fast_strcat(key, ",", 1);
359
- rrdlabels_get_value_to_buffer_or_unset(labels, key, qt->group_by[group_by_id].label_keys[l], "[unset]");
360
- }
361
- }
362
-
363
- if (group_by & RRDR_GROUP_BY_NODE) {
364
- if (buffer_strlen(key) != 0)
365
- buffer_fast_strcat(key, ",", 1);
366
-
367
- buffer_strcat(key, rrdhost_hostname(qn->rrdhost));
368
- }
369
-
370
- if (group_by & RRDR_GROUP_BY_CONTEXT) {
371
- if (buffer_strlen(key) != 0)
372
- buffer_fast_strcat(key, ",", 1);
373
-
374
- buffer_strcat(key, rrdcontext_acquired_id(qc->rca));
375
- }
376
-
377
- if (group_by & RRDR_GROUP_BY_UNITS) {
378
- if (buffer_strlen(key) != 0)
379
- buffer_fast_strcat(key, ",", 1);
380
-
381
- buffer_strcat(key, query_target_has_percentage_units(qt) ? "%" : rrdinstance_acquired_units(qi->ria));
382
- }
383
- }
384
-}
385
-
386
-struct rrdr_group_by_entry {
387
- size_t priority;
388
- size_t count;
389
- STRING *id;
390
- STRING *name;
391
- STRING *units;
392
- RRDR_DIMENSION_FLAGS od;
393
- DICTIONARY *dl;
394
-};
395
-
396
-RRDR *rrd2rrdr_group_by_initialize(ONEWAYALLOC *owa, QUERY_TARGET *qt) {
397
- RRDR *r_tmp = NULL;
398
- RRDR_OPTIONS options = qt->window.options;
399
-
400
- if(qt->request.version < 2) {
401
- // v1 query
402
- RRDR *r = rrdr_create(owa, qt, qt->query.used, qt->window.points);
403
- if(unlikely(!r)) {
404
- internal_error(true, "QUERY: cannot create RRDR for %s, after=%ld, before=%ld, dimensions=%u, points=%zu",
405
- qt->id, qt->window.after, qt->window.before, qt->query.used, qt->window.points);
406
- return NULL;
407
- }
408
- r->group_by.r = NULL;
409
-
410
- for(size_t d = 0; d < qt->query.used ; d++) {
411
- QUERY_METRIC *qm = query_metric(qt, d);
412
- QUERY_DIMENSION *qd = query_dimension(qt, qm->link.query_dimension_id);
413
- r->di[d] = rrdmetric_acquired_id_dup(qd->rma);
414
- r->dn[d] = rrdmetric_acquired_name_dup(qd->rma);
415
- }
416
-
417
- rrd2rrdr_set_timestamps(r);
418
- return r;
419
- }
420
- // v2 query
421
-
422
- // parse all the group-by label keys
423
- for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) {
424
- if (qt->request.group_by[g].group_by & RRDR_GROUP_BY_LABEL &&
425
- qt->request.group_by[g].group_by_label && *qt->request.group_by[g].group_by_label)
426
- qt->group_by[g].used = quoted_strings_splitter_query_group_by_label(
427
- qt->request.group_by[g].group_by_label, qt->group_by[g].label_keys,
428
- GROUP_BY_MAX_LABEL_KEYS);
429
-
430
- if (!qt->group_by[g].used)
431
- qt->request.group_by[g].group_by &= ~RRDR_GROUP_BY_LABEL;
432
- }
433
-
434
- // make sure there are valid group-by methods
435
- for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) {
436
- if(!(qt->request.group_by[g].group_by & SUPPORTED_GROUP_BY_METHODS))
437
- qt->request.group_by[g].group_by = (g == 0) ? RRDR_GROUP_BY_DIMENSION : RRDR_GROUP_BY_NONE;
438
- }
439
-
440
- bool query_has_percentage_of_group = query_target_has_percentage_of_group(qt);
441
-
442
- // merge all group-by options to upper levels,
443
- // so that the top level has all the groupings of the inner levels,
444
- // and each subsequent level has all the groupings of its inner levels.
445
- for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES - 1 ;g++) {
446
- if(qt->request.group_by[g].group_by == RRDR_GROUP_BY_NONE)
447
- continue;
448
-
449
- if(qt->request.group_by[g].group_by == RRDR_GROUP_BY_SELECTED) {
450
- for (size_t r = g + 1; r < MAX_QUERY_GROUP_BY_PASSES; r++)
451
- qt->request.group_by[r].group_by = RRDR_GROUP_BY_NONE;
452
- }
453
- else {
454
- for (size_t r = g + 1; r < MAX_QUERY_GROUP_BY_PASSES; r++) {
455
- if (qt->request.group_by[r].group_by == RRDR_GROUP_BY_NONE)
456
- continue;
457
-
458
- if (qt->request.group_by[r].group_by != RRDR_GROUP_BY_SELECTED) {
459
- if(qt->request.group_by[r].group_by & RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE)
460
- qt->request.group_by[g].group_by |= RRDR_GROUP_BY_INSTANCE;
461
- else
462
- qt->request.group_by[g].group_by |= qt->request.group_by[r].group_by;
463
-
464
- if(qt->request.group_by[r].group_by & RRDR_GROUP_BY_LABEL) {
465
- for (size_t lr = 0; lr < qt->group_by[r].used; lr++) {
466
- bool found = false;
467
- for (size_t lg = 0; lg < qt->group_by[g].used; lg++) {
468
- if (strcmp(qt->group_by[g].label_keys[lg], qt->group_by[r].label_keys[lr]) == 0) {
469
- found = true;
470
- break;
471
- }
472
- }
473
-
474
- if (!found && qt->group_by[g].used < GROUP_BY_MAX_LABEL_KEYS * MAX_QUERY_GROUP_BY_PASSES)
475
- qt->group_by[g].label_keys[qt->group_by[g].used++] = qt->group_by[r].label_keys[lr];
476
- }
477
- }
478
- }
479
- }
480
- }
481
- }
482
-
483
- int added = 0;
484
- RRDR *first_r = NULL, *last_r = NULL;
485
- BUFFER *key = buffer_create(0, NULL);
486
- struct rrdr_group_by_entry *entries = onewayalloc_mallocz(owa, qt->query.used * sizeof(struct rrdr_group_by_entry));
487
- DICTIONARY *groups = dictionary_create(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE);
488
- DICTIONARY *label_keys = NULL;
489
-
490
- for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) {
491
- RRDR_GROUP_BY group_by = qt->request.group_by[g].group_by;
492
- RRDR_GROUP_BY_FUNCTION aggregation_method = qt->request.group_by[g].aggregation;
493
-
494
- if(group_by == RRDR_GROUP_BY_NONE)
495
- break;
496
-
497
- memset(entries, 0, qt->query.used * sizeof(struct rrdr_group_by_entry));
498
- dictionary_flush(groups);
499
- added = 0;
500
-
501
- size_t hidden_dimensions = 0;
502
- bool final_grouping = (g == MAX_QUERY_GROUP_BY_PASSES - 1 || qt->request.group_by[g + 1].group_by == RRDR_GROUP_BY_NONE) ? true : false;
503
-
504
- if (final_grouping && (options & RRDR_OPTION_GROUP_BY_LABELS))
505
- label_keys = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE, NULL, 0);
506
-
507
- QUERY_INSTANCE *last_qi = NULL;
508
- size_t priority = 0;
509
- time_t update_every_max = 0;
510
- for (size_t d = 0; d < qt->query.used; d++) {
511
- QUERY_METRIC *qm = query_metric(qt, d);
512
- QUERY_DIMENSION *qd = query_dimension(qt, qm->link.query_dimension_id);
513
- QUERY_INSTANCE *qi = query_instance(qt, qm->link.query_instance_id);
514
- QUERY_CONTEXT *qc = query_context(qt, qm->link.query_context_id);
515
- QUERY_NODE *qn = query_node(qt, qm->link.query_node_id);
516
-
517
- if (qi != last_qi) {
518
- last_qi = qi;
519
-
520
- time_t update_every = rrdinstance_acquired_update_every(qi->ria);
521
- if (update_every > update_every_max)
522
- update_every_max = update_every;
523
- }
524
-
525
- priority = qd->priority;
526
-
527
- if(qm->status & RRDR_DIMENSION_HIDDEN)
528
- hidden_dimensions++;
529
-
530
- // --------------------------------------------------------------------
531
- // generate the group by key
532
-
533
- query_group_by_make_dimension_key(key, group_by, g, qt, qn, qc, qi, qd, qm, query_has_percentage_of_group);
534
-
535
- // lookup the key in the dictionary
536
-
537
- int pos = -1;
538
- int *set = dictionary_set(groups, buffer_tostring(key), &pos, sizeof(pos));
539
- if (*set == -1) {
540
- // the key just added to the dictionary
541
-
542
- *set = pos = added++;
543
-
544
- // ----------------------------------------------------------------
545
- // generate the dimension id
546
-
547
- query_group_by_make_dimension_id(key, group_by, g, qt, qn, qc, qi, qd, qm, query_has_percentage_of_group);
548
- entries[pos].id = string_strdupz(buffer_tostring(key));
549
-
550
- // ----------------------------------------------------------------
551
- // generate the dimension name
552
-
553
- query_group_by_make_dimension_name(key, group_by, g, qt, qn, qc, qi, qd, qm, query_has_percentage_of_group);
554
- entries[pos].name = string_strdupz(buffer_tostring(key));
555
-
556
- // add the rest of the info
557
- entries[pos].units = rrdinstance_acquired_units_dup(qi->ria);
558
- entries[pos].priority = priority;
559
-
560
- if (label_keys) {
561
- entries[pos].dl = dictionary_create_advanced(
562
- DICT_OPTION_SINGLE_THREADED | DICT_OPTION_FIXED_SIZE | DICT_OPTION_DONT_OVERWRITE_VALUE,
563
- NULL, sizeof(struct group_by_label_key));
564
- dictionary_register_insert_callback(entries[pos].dl, group_by_label_key_insert_cb, label_keys);
565
- dictionary_register_delete_callback(entries[pos].dl, group_by_label_key_delete_cb, label_keys);
566
- }
567
- } else {
568
- // the key found in the dictionary
569
- pos = *set;
570
- }
571
-
572
- entries[pos].count++;
573
-
574
- if (unlikely(priority < entries[pos].priority))
575
- entries[pos].priority = priority;
576
-
577
- if(g > 0)
578
- last_r->dgbs[qm->grouped_as.slot] = pos;
579
- else
580
- qm->grouped_as.first_slot = pos;
581
-
582
- qm->grouped_as.slot = pos;
583
- qm->grouped_as.id = entries[pos].id;
584
- qm->grouped_as.name = entries[pos].name;
585
- qm->grouped_as.units = entries[pos].units;
586
-
587
- // copy the dimension flags decided by the query target
588
- // we need this, because if a dimension is explicitly selected
589
- // the query target adds to it the non-zero flag
590
- qm->status |= RRDR_DIMENSION_GROUPED;
591
-
592
- if(query_has_percentage_of_group)
593
- // when the query has percentage of group
594
- // there will be no hidden dimensions in the final query,
595
- // so we have to remove the hidden flag from all dimensions
596
- entries[pos].od |= qm->status & ~RRDR_DIMENSION_HIDDEN;
597
- else
598
- entries[pos].od |= qm->status;
599
-
600
- if (entries[pos].dl)
601
- rrdlabels_walkthrough_read(rrdinstance_acquired_labels(qi->ria),
602
- rrdlabels_traversal_cb_to_group_by_label_key, entries[pos].dl);
603
- }
604
-
605
- RRDR *r = rrdr_create(owa, qt, added, qt->window.points);
606
- if (!r) {
607
- internal_error(true,
608
- "QUERY: cannot create group by RRDR for %s, after=%ld, before=%ld, dimensions=%d, points=%zu",
609
- qt->id, qt->window.after, qt->window.before, added, qt->window.points);
610
- goto cleanup;
611
- }
612
- // prevent double free at cleanup in case of error
613
- added = 0;
614
-
615
- // link this RRDR
616
- if(!last_r)
617
- first_r = last_r = r;
618
- else
619
- last_r->group_by.r = r;
620
-
621
- last_r = r;
622
-
623
- rrd2rrdr_set_timestamps(r);
624
-
625
- if(r->d) {
626
- r->dp = onewayalloc_callocz(owa, r->d, sizeof(*r->dp));
627
- r->dview = onewayalloc_callocz(owa, r->d, sizeof(*r->dview));
628
- r->dgbc = onewayalloc_callocz(owa, r->d, sizeof(*r->dgbc));
629
- r->dqp = onewayalloc_callocz(owa, r->d, sizeof(STORAGE_POINT));
630
-
631
- if(!final_grouping)
632
- // this is where we are going to store the slot in the next RRDR
633
- // that we are going to group by the dimension of this RRDR
634
- r->dgbs = onewayalloc_callocz(owa, r->d, sizeof(*r->dgbs));
635
-
636
- if (label_keys) {
637
- r->dl = onewayalloc_callocz(owa, r->d, sizeof(DICTIONARY *));
638
- r->label_keys = label_keys;
639
- label_keys = NULL;
640
- }
641
-
642
- if(r->n) {
643
- r->gbc = onewayalloc_callocz(owa, r->n * r->d, sizeof(*r->gbc));
644
-
645
- if(hidden_dimensions && ((group_by & RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE) || (aggregation_method == RRDR_GROUP_BY_FUNCTION_PERCENTAGE)))
646
- // this is where we are going to group the hidden dimensions
647
- r->vh = onewayalloc_mallocz(owa, r->n * r->d * sizeof(*r->vh));
648
- }
649
- }
650
-
651
- // zero r (dimension options, names, and ids)
652
- // this is required, because group-by may lead to empty dimensions
653
- for (size_t d = 0; d < r->d; d++) {
654
- r->di[d] = entries[d].id;
655
- r->dn[d] = entries[d].name;
656
-
657
- r->od[d] = entries[d].od;
658
- r->du[d] = entries[d].units;
659
- r->dp[d] = entries[d].priority;
660
- r->dgbc[d] = entries[d].count;
661
-
662
- if (r->dl)
663
- r->dl[d] = entries[d].dl;
664
- }
665
-
666
- // initialize partial trimming
667
- r->partial_data_trimming.max_update_every = update_every_max * 2;
668
- r->partial_data_trimming.expected_after =
669
- (!query_target_aggregatable(qt) &&
670
- qt->window.before >= qt->window.now - r->partial_data_trimming.max_update_every) ?
671
- qt->window.before - r->partial_data_trimming.max_update_every :
672
- qt->window.before;
673
- r->partial_data_trimming.trimmed_after = qt->window.before;
674
-
675
- // make all values empty
676
- if(r->n && r->d) {
677
- for (size_t i = 0; i != r->n; i++) {
678
- NETDATA_DOUBLE *cn = &r->v[i * r->d];
679
- RRDR_VALUE_FLAGS *co = &r->o[i * r->d];
680
- NETDATA_DOUBLE *ar = &r->ar[i * r->d];
681
- NETDATA_DOUBLE *vh = r->vh ? &r->vh[i * r->d] : NULL;
682
-
683
- for (size_t d = 0; d < r->d; d++) {
684
- cn[d] = NAN;
685
- ar[d] = 0.0;
686
- co[d] = RRDR_VALUE_EMPTY;
687
-
688
- if (vh)
689
- vh[d] = NAN;
690
- }
691
- }
692
- }
693
- }
694
-
695
- if(!first_r || !last_r)
696
- goto cleanup;
697
-
698
- r_tmp = rrdr_create(owa, qt, 1, qt->window.points);
699
- if (!r_tmp) {
700
- internal_error(true,
701
- "QUERY: cannot create group by temporary RRDR for %s, after=%ld, before=%ld, dimensions=%d, points=%zu",
702
- qt->id, qt->window.after, qt->window.before, 1, qt->window.points);
703
- goto cleanup;
704
- }
705
- rrd2rrdr_set_timestamps(r_tmp);
706
- r_tmp->group_by.r = first_r;
707
-
708
-cleanup:
709
- if(!first_r || !last_r || !r_tmp) {
710
- if(r_tmp) {
711
- r_tmp->group_by.r = NULL;
712
- rrdr_free(owa, r_tmp);
713
- }
714
-
715
- if(first_r) {
716
- RRDR *r = first_r;
717
- while (r) {
718
- r_tmp = r->group_by.r;
719
- r->group_by.r = NULL;
720
- rrdr_free(owa, r);
721
- r = r_tmp;
722
- }
723
- }
724
-
725
- if(entries && added) {
726
- for (int d = 0; d < added; d++) {
727
- string_freez(entries[d].id);
728
- string_freez(entries[d].name);
729
- string_freez(entries[d].units);
730
- dictionary_destroy(entries[d].dl);
731
- }
732
- }
733
- dictionary_destroy(label_keys);
734
-
735
- first_r = last_r = r_tmp = NULL;
736
- }
737
-
738
- buffer_free(key);
739
- onewayalloc_freez(owa, entries);
740
- dictionary_destroy(groups);
741
-
742
- return r_tmp;
743
-}
744
-
745
-void rrd2rrdr_group_by_add_metric(RRDR *r_dst, size_t d_dst, RRDR *r_tmp, size_t d_tmp,
746
- RRDR_GROUP_BY_FUNCTION group_by_aggregate_function,
747
- STORAGE_POINT *query_points, size_t pass __maybe_unused) {
748
- if(!r_tmp || r_dst == r_tmp || !(r_tmp->od[d_tmp] & RRDR_DIMENSION_QUERIED))
749
- return;
750
-
751
- internal_fatal(r_dst->n != r_tmp->n, "QUERY: group-by source and destination do not have the same number of rows");
752
- internal_fatal(d_dst >= r_dst->d, "QUERY: group-by destination dimension number exceeds destination RRDR size");
753
- internal_fatal(d_tmp >= r_tmp->d, "QUERY: group-by source dimension number exceeds source RRDR size");
754
- internal_fatal(!r_dst->dqp, "QUERY: group-by destination is not properly prepared (missing dqp array)");
755
- internal_fatal(!r_dst->gbc, "QUERY: group-by destination is not properly prepared (missing gbc array)");
756
-
757
- bool hidden_dimension_on_percentage_of_group = (r_tmp->od[d_tmp] & RRDR_DIMENSION_HIDDEN) && r_dst->vh;
758
-
759
- if(!hidden_dimension_on_percentage_of_group) {
760
- r_dst->od[d_dst] |= r_tmp->od[d_tmp];
761
- storage_point_merge_to(r_dst->dqp[d_dst], *query_points);
762
- }
763
-
764
- // do the group_by
765
- for(size_t i = 0; i != rrdr_rows(r_tmp) ; i++) {
766
-
767
- size_t idx_tmp = i * r_tmp->d + d_tmp;
768
- NETDATA_DOUBLE n_tmp = r_tmp->v[ idx_tmp ];
769
- RRDR_VALUE_FLAGS o_tmp = r_tmp->o[ idx_tmp ];
770
- NETDATA_DOUBLE ar_tmp = r_tmp->ar[ idx_tmp ];
771
-
772
- if(o_tmp & RRDR_VALUE_EMPTY)
773
- continue;
774
-
775
- size_t idx_dst = i * r_dst->d + d_dst;
776
- NETDATA_DOUBLE *cn = (hidden_dimension_on_percentage_of_group) ? &r_dst->vh[ idx_dst ] : &r_dst->v[ idx_dst ];
777
- RRDR_VALUE_FLAGS *co = &r_dst->o[ idx_dst ];
778
- NETDATA_DOUBLE *ar = &r_dst->ar[ idx_dst ];
779
- uint32_t *gbc = &r_dst->gbc[ idx_dst ];
780
-
781
- switch(group_by_aggregate_function) {
782
- default:
783
- case RRDR_GROUP_BY_FUNCTION_AVERAGE:
784
- case RRDR_GROUP_BY_FUNCTION_SUM:
785
- case RRDR_GROUP_BY_FUNCTION_PERCENTAGE:
786
- if(isnan(*cn))
787
- *cn = n_tmp;
788
- else
789
- *cn += n_tmp;
790
- break;
791
-
792
- case RRDR_GROUP_BY_FUNCTION_MIN:
793
- if(isnan(*cn) || n_tmp < *cn)
794
- *cn = n_tmp;
795
- break;
796
-
797
- case RRDR_GROUP_BY_FUNCTION_MAX:
798
- if(isnan(*cn) || n_tmp > *cn)
799
- *cn = n_tmp;
800
- break;
801
-
802
- case RRDR_GROUP_BY_FUNCTION_EXTREMES:
803
- // For extremes, we need to keep track of the value with the maximum absolute value
804
- if(isnan(*cn) || fabsndd(n_tmp) > fabsndd(*cn))
805
- *cn = n_tmp;
806
- break;
807
- }
808
-
809
- if(!hidden_dimension_on_percentage_of_group) {
810
- *co &= ~RRDR_VALUE_EMPTY;
811
- *co |= (o_tmp & (RRDR_VALUE_RESET | RRDR_VALUE_PARTIAL));
812
- *ar += ar_tmp;
813
- (*gbc)++;
814
- }
815
- }
816
-}
817
-
818
-void rrdr2rrdr_group_by_partial_trimming(RRDR *r) {
819
- time_t trimmable_after = r->partial_data_trimming.expected_after;
820
-
821
- // find the point just before the trimmable ones
822
- ssize_t i = (ssize_t)r->n - 1;
823
- for( ; i >= 0 ;i--) {
824
- if (r->t[i] < trimmable_after)
825
- break;
826
- }
827
-
828
- if(unlikely(i < 0))
829
- return;
830
-
831
- // internal_error(true, "Found trimmable index %zd (from 0 to %zu)", i, r->n - 1);
832
-
833
- size_t last_row_gbc = 0;
834
- for (; i < (ssize_t)r->n; i++) {
835
- size_t row_gbc = 0;
836
- for (size_t d = 0; d < r->d; d++) {
837
- if (unlikely(!(r->od[d] & RRDR_DIMENSION_QUERIED)))
838
- continue;
839
-
840
- row_gbc += r->gbc[ i * r->d + d ];
841
- }
842
-
843
- // internal_error(true, "GBC of index %zd is %zu", i, row_gbc);
844
-
845
- if (unlikely(r->t[i] >= trimmable_after && (row_gbc < last_row_gbc || !row_gbc))) {
846
- // discard the rest of the points
847
- // internal_error(true, "Discarding points %zd to %zu", i, r->n - 1);
848
- r->partial_data_trimming.trimmed_after = r->t[i];
849
- r->rows = i;
850
- break;
851
- }
852
- else
853
- last_row_gbc = row_gbc;
854
- }
855
-}
856
-
857
-void rrdr2rrdr_group_by_calculate_percentage_of_group(RRDR *r) {
858
- if(!r->vh)
859
- return;
860
-
861
- if(query_target_aggregatable(r->internal.qt) && query_has_group_by_aggregation_percentage(r->internal.qt))
862
- return;
863
-
864
- for(size_t i = 0; i < r->n ;i++) {
865
- NETDATA_DOUBLE *cn = &r->v[ i * r->d ];
866
- NETDATA_DOUBLE *ch = &r->vh[ i * r->d ];
867
-
868
- for(size_t d = 0; d < r->d ;d++) {
869
- NETDATA_DOUBLE n = cn[d];
870
- NETDATA_DOUBLE h = ch[d];
871
-
872
- if(isnan(n))
873
- cn[d] = 0.0;
874
-
875
- else if(isnan(h))
876
- cn[d] = 100.0;
877
-
878
- else
879
- cn[d] = n * 100.0 / (n + h);
880
- }
881
- }
882
-}
883
-
884
-
885
-void rrd2rrdr_convert_values_to_percentage_of_total(RRDR *r) {
886
- if(!(r->internal.qt->window.options & RRDR_OPTION_PERCENTAGE) || query_target_aggregatable(r->internal.qt))
887
- return;
888
-
889
- size_t global_min_max_values = 0;
890
- NETDATA_DOUBLE global_min = NAN, global_max = NAN;
891
-
892
- for(size_t i = 0; i != r->n ;i++) {
893
- NETDATA_DOUBLE *cn = &r->v[ i * r->d ];
894
- RRDR_VALUE_FLAGS *co = &r->o[ i * r->d ];
895
-
896
- NETDATA_DOUBLE total = 0;
897
- for (size_t d = 0; d < r->d; d++) {
898
- if (unlikely(!(r->od[d] & RRDR_DIMENSION_QUERIED)))
899
- continue;
900
-
901
- if(co[d] & RRDR_VALUE_EMPTY)
902
- continue;
903
-
904
- total += cn[d];
905
- }
906
-
907
- if(total == 0.0)
908
- total = 1.0;
909
-
910
- for (size_t d = 0; d < r->d; d++) {
911
- if (unlikely(!(r->od[d] & RRDR_DIMENSION_QUERIED)))
912
- continue;
913
-
914
- if(co[d] & RRDR_VALUE_EMPTY)
915
- continue;
916
-
917
- NETDATA_DOUBLE n = cn[d];
918
- n = cn[d] = n * 100.0 / total;
919
-
920
- if(unlikely(!global_min_max_values++))
921
- global_min = global_max = n;
922
- else {
923
- if(n < global_min)
924
- global_min = n;
925
- if(n > global_max)
926
- global_max = n;
927
- }
928
- }
929
- }
930
-
931
- r->view.min = global_min;
932
- r->view.max = global_max;
933
-
934
- if(!r->dview)
935
- // v1 query
936
- return;
937
-
938
- // v2 query
939
-
940
- for (size_t d = 0; d < r->d; d++) {
941
- if (unlikely(!(r->od[d] & RRDR_DIMENSION_QUERIED)))
942
- continue;
943
-
944
- size_t count = 0;
945
- NETDATA_DOUBLE min = 0.0, max = 0.0, sum = 0.0, ars = 0.0;
946
- for(size_t i = 0; i != r->rows ;i++) { // we use r->rows to respect trimming
947
- size_t idx = i * r->d + d;
948
-
949
- RRDR_VALUE_FLAGS o = r->o[ idx ];
950
-
951
- if (o & RRDR_VALUE_EMPTY)
952
- continue;
953
-
954
- NETDATA_DOUBLE ar = r->ar[ idx ];
955
- ars += ar;
956
-
957
- NETDATA_DOUBLE n = r->v[ idx ];
958
- sum += n;
959
-
960
- if(!count++)
961
- min = max = n;
962
- else {
963
- if(n < min)
964
- min = n;
965
- if(n > max)
966
- max = n;
967
- }
968
- }
969
-
970
- r->dview[d] = (STORAGE_POINT) {
971
- .sum = sum,
972
- .count = count,
973
- .min = min,
974
- .max = max,
975
- .anomaly_count = (size_t)(ars * (NETDATA_DOUBLE)count),
976
- };
977
- }
978
-}
979
-
980
-RRDR *rrd2rrdr_group_by_finalize(RRDR *r_tmp) {
981
- QUERY_TARGET *qt = r_tmp->internal.qt;
982
-
983
- if(!r_tmp->group_by.r) {
984
- // v1 query
985
- rrd2rrdr_convert_values_to_percentage_of_total(r_tmp);
986
- return r_tmp;
987
- }
988
- // v2 query
989
-
990
- // do the additional passes on RRDRs
991
- RRDR *last_r = r_tmp->group_by.r;
992
- rrdr2rrdr_group_by_calculate_percentage_of_group(last_r);
993
-
994
- RRDR *r = last_r->group_by.r;
995
- size_t pass = 0;
996
- while(r) {
997
- pass++;
998
- for(size_t d = 0; d < last_r->d ;d++) {
999
- rrd2rrdr_group_by_add_metric(r, last_r->dgbs[d], last_r, d,
1000
- qt->request.group_by[pass].aggregation,
1001
- &last_r->dqp[d], pass);
1002
- }
1003
- rrdr2rrdr_group_by_calculate_percentage_of_group(r);
1004
-
1005
- last_r = r;
1006
- r = last_r->group_by.r;
1007
- }
1008
-
1009
- // free all RRDRs except the last one
1010
- r = r_tmp;
1011
- while(r != last_r) {
1012
- r_tmp = r->group_by.r;
1013
- r->group_by.r = NULL;
1014
- rrdr_free(r->internal.owa, r);
1015
- r = r_tmp;
1016
- }
1017
- r = last_r;
1018
-
1019
- // find the final aggregation
1020
- RRDR_GROUP_BY_FUNCTION aggregation = qt->request.group_by[0].aggregation;
1021
- for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++)
1022
- if(qt->request.group_by[g].group_by != RRDR_GROUP_BY_NONE)
1023
- aggregation = qt->request.group_by[g].aggregation;
1024
-
1025
- if(!query_target_aggregatable(qt) && r->partial_data_trimming.expected_after < qt->window.before)
1026
- rrdr2rrdr_group_by_partial_trimming(r);
1027
-
1028
- // apply averaging, remove RRDR_VALUE_EMPTY, find the non-zero dimensions, min and max
1029
- size_t global_min_max_values = 0;
1030
- size_t dimensions_nonzero = 0;
1031
- NETDATA_DOUBLE global_min = NAN, global_max = NAN;
1032
- for (size_t d = 0; d < r->d; d++) {
1033
- if (unlikely(!(r->od[d] & RRDR_DIMENSION_QUERIED)))
1034
- continue;
1035
-
1036
- size_t points_nonzero = 0;
1037
- NETDATA_DOUBLE min = 0, max = 0, sum = 0, ars = 0;
1038
- size_t count = 0;
1039
-
1040
- for(size_t i = 0; i != r->n ;i++) {
1041
- size_t idx = i * r->d + d;
1042
-
1043
- NETDATA_DOUBLE *cn = &r->v[ idx ];
1044
- RRDR_VALUE_FLAGS *co = &r->o[ idx ];
1045
- NETDATA_DOUBLE *ar = &r->ar[ idx ];
1046
- uint32_t gbc = r->gbc[ idx ];
1047
-
1048
- if(likely(gbc)) {
1049
- *co &= ~RRDR_VALUE_EMPTY;
1050
-
1051
- if(gbc != r->dgbc[d])
1052
- *co |= RRDR_VALUE_PARTIAL;
1053
-
1054
- NETDATA_DOUBLE n;
1055
-
1056
- sum += *cn;
1057
- ars += *ar;
1058
-
1059
- if(aggregation == RRDR_GROUP_BY_FUNCTION_AVERAGE && !query_target_aggregatable(qt))
1060
- n = (*cn /= gbc);
1061
- else
1062
- n = *cn;
1063
-
1064
- if(!query_target_aggregatable(qt))
1065
- *ar /= gbc;
1066
-
1067
- if(islessgreater(n, 0.0))
1068
- points_nonzero++;
1069
-
1070
- if(unlikely(!count))
1071
- min = max = n;
1072
- else {
1073
- if(n < min)
1074
- min = n;
1075
-
1076
- if(n > max)
1077
- max = n;
1078
- }
1079
-
1080
- if(unlikely(!global_min_max_values++))
1081
- global_min = global_max = n;
1082
- else {
1083
- if(n < global_min)
1084
- global_min = n;
1085
-
1086
- if(n > global_max)
1087
- global_max = n;
1088
- }
1089
-
1090
- count += gbc;
1091
- }
1092
- }
1093
-
1094
- if(points_nonzero) {
1095
- r->od[d] |= RRDR_DIMENSION_NONZERO;
1096
- dimensions_nonzero++;
1097
- }
1098
-
1099
- r->dview[d] = (STORAGE_POINT) {
1100
- .sum = sum,
1101
- .count = count,
1102
- .min = min,
1103
- .max = max,
1104
- .anomaly_count = (size_t)(ars * RRDR_DVIEW_ANOMALY_COUNT_MULTIPLIER / 100.0),
1105
- };
1106
- }
1107
-
1108
- r->view.min = global_min;
1109
- r->view.max = global_max;
1110
-
1111
- if(!dimensions_nonzero && (qt->window.options & RRDR_OPTION_NONZERO)) {
1112
- // all dimensions are zero
1113
- // remove the nonzero option
1114
- qt->window.options &= ~RRDR_OPTION_NONZERO;
1115
- }
1116
-
1117
- rrd2rrdr_convert_values_to_percentage_of_total(r);
1118
-
1119
- // update query instance counts in query host and query context
1120
- {
1121
- size_t h = 0, c = 0, i = 0;
1122
- for(; h < qt->nodes.used ; h++) {
1123
- QUERY_NODE *qn = &qt->nodes.array[h];
1124
-
1125
- for(; c < qt->contexts.used ;c++) {
1126
- QUERY_CONTEXT *qc = &qt->contexts.array[c];
1127
-
1128
- if(!rrdcontext_acquired_belongs_to_host(qc->rca, qn->rrdhost))
1129
- break;
1130
-
1131
- for(; i < qt->instances.used ;i++) {
1132
- QUERY_INSTANCE *qi = &qt->instances.array[i];
1133
-
1134
- if(!rrdinstance_acquired_belongs_to_context(qi->ria, qc->rca))
1135
- break;
1136
-
1137
- if(qi->metrics.queried) {
1138
- qc->instances.queried++;
1139
- qn->instances.queried++;
1140
- }
1141
- else if(qi->metrics.failed) {
1142
- qc->instances.failed++;
1143
- qn->instances.failed++;
1144
- }
1145
- }
1146
- }
1147
- }
1148
- }
1149
-
1150
- return r;
1151
-}
1152
-
1153
-static int compare_contributions(const void *a, const void *b) {
1154
- const struct { size_t dim_idx; NETDATA_DOUBLE contribution; } *da = a;
1155
- const struct { size_t dim_idx; NETDATA_DOUBLE contribution; } *db = b;
1156
-
1157
- if (da->contribution > db->contribution) return -1;
1158
- if (da->contribution < db->contribution) return 1;
1159
- return 0;
1160
-}
1161
-
1162
-RRDR *rrd2rrdr_cardinality_limit(RRDR *r) {
1163
- QUERY_TARGET *qt = r->internal.qt;
1164
-
1165
- if(!qt || qt->request.cardinality_limit == 0 || r->d <= qt->request.cardinality_limit)
1166
- return r;
1167
-
1168
- ONEWAYALLOC *owa = r->internal.owa;
1169
-
1170
- // Calculate contribution of each dimension using dview statistics (sum of values)
1171
- NETDATA_DOUBLE *contributions = onewayalloc_mallocz(owa, r->d * sizeof(NETDATA_DOUBLE));
1172
-
1173
- // Count queried dimensions and get their contributions from dview
1174
- size_t queried_count = 0;
1175
- for (size_t d = 0; d < r->d; d++) {
1176
- contributions[d] = 0.0;
1177
-
1178
- if (!(r->od[d] & RRDR_DIMENSION_QUERIED))
1179
- continue;
1180
-
1181
- queried_count++;
1182
-
1183
- // Use the sum from dview if available, otherwise fall back to manual calculation
1184
- if(r->dview && !isnan(r->dview[d].sum)) {
1185
- contributions[d] = fabsndd(r->dview[d].sum);
1186
- } else {
1187
- // Fallback: calculate manually from values
1188
- for(size_t i = 0; i < r->rows; i++) {
1189
- size_t idx = i * r->d + d;
1190
-
1191
- if(r->o[idx] & RRDR_VALUE_EMPTY)
1192
- continue;
1193
-
1194
- NETDATA_DOUBLE value = r->v[idx];
1195
- if(!isnan(value))
1196
- contributions[d] += fabsndd(value);
1197
- }
1198
- }
1199
- }
1200
-
1201
- // If we don't need to reduce, return original
1202
- if(queried_count <= qt->request.cardinality_limit) {
1203
- onewayalloc_freez(owa, contributions);
1204
- return r;
1205
- }
1206
-
1207
- // Create array of dimension indices sorted by contribution (descending)
1208
- struct {
1209
- size_t dim_idx;
1210
- NETDATA_DOUBLE contribution;
1211
- } *sorted_dims = onewayalloc_mallocz(owa, queried_count * sizeof(*sorted_dims));
1212
-
1213
- size_t sorted_idx = 0;
1214
- for (size_t d = 0; d < r->d; d++) {
1215
- if (r->od[d] & RRDR_DIMENSION_QUERIED) {
1216
- sorted_dims[sorted_idx].dim_idx = d;
1217
- sorted_dims[sorted_idx].contribution = contributions[d];
1218
- sorted_idx++;
1219
- }
1220
- }
1221
-
1222
- // Sort by contribution (descending)
1223
- qsort(sorted_dims, queried_count, sizeof(*sorted_dims), compare_contributions);
1224
-
1225
- // Create new RRDR with limited dimensions
1226
- size_t new_d = qt->request.cardinality_limit;
1227
- size_t remaining_count = queried_count - (qt->request.cardinality_limit - 1);
1228
- if(remaining_count > 0)
1229
- new_d = qt->request.cardinality_limit; // Keep one slot for "remaining N dimensions"
1230
- else
1231
- new_d = queried_count; // No remaining dimensions needed
1232
-
1233
- RRDR *new_r = rrdr_create(owa, qt, new_d, r->n);
1234
- if (!new_r) {
1235
- internal_error(true, "QUERY: cannot create cardinality limited RRDR");
1236
- onewayalloc_freez(owa, contributions);
1237
- onewayalloc_freez(owa, sorted_dims);
1238
- return r;
1239
- }
1240
-
1241
- // Copy basic metadata from original RRDR
1242
- new_r->view = r->view;
1243
- new_r->time_grouping = r->time_grouping;
1244
- new_r->partial_data_trimming = r->partial_data_trimming;
1245
- new_r->rows = r->rows;
1246
-
1247
- // Copy timestamps
1248
- memcpy(new_r->t, r->t, r->n * sizeof(time_t));
1249
-
1250
- // Setup arrays for new RRDR
1251
- if(new_r->d) {
1252
- new_r->dp = onewayalloc_callocz(owa, new_r->d, sizeof(*new_r->dp));
1253
- new_r->dview = onewayalloc_callocz(owa, new_r->d, sizeof(*new_r->dview));
1254
-
1255
- if(new_r->n) {
1256
- // Initialize all values as empty
1257
- for (size_t i = 0; i < new_r->n; i++) {
1258
- for (size_t d = 0; d < new_r->d; d++) {
1259
- size_t idx = i * new_r->d + d;
1260
- new_r->v[idx] = NAN;
1261
- new_r->ar[idx] = 0.0;
1262
- new_r->o[idx] = RRDR_VALUE_EMPTY;
1263
- }
1264
- }
1265
- }
1266
- }
1267
-
1268
- // Copy top dimensions
1269
- size_t kept_dimensions = (remaining_count > 0) ? qt->request.cardinality_limit - 1 : queried_count;
1270
-
1271
- for (size_t i = 0; i < kept_dimensions; i++) {
1272
- size_t src_d = sorted_dims[i].dim_idx;
1273
-
1274
- // Copy metadata
1275
- new_r->di[i] = string_dup(r->di[src_d]);
1276
- new_r->dn[i] = string_dup(r->dn[src_d]);
1277
- new_r->od[i] = r->od[src_d];
1278
- new_r->du[i] = string_dup(r->du[src_d]);
1279
- new_r->dp[i] = r->dp[src_d];
1280
-
1281
- // Copy data
1282
- for (size_t row = 0; row < r->rows; row++) {
1283
- size_t src_idx = row * r->d + src_d;
1284
- size_t dst_idx = row * new_r->d + i;
1285
-
1286
- new_r->v[dst_idx] = r->v[src_idx];
1287
- new_r->ar[dst_idx] = r->ar[src_idx];
1288
- new_r->o[dst_idx] = r->o[src_idx];
1289
- }
1290
-
1291
- // Copy dview stats
1292
- if(r->dview)
1293
- new_r->dview[i] = r->dview[src_d];
1294
- }
1295
-
1296
- // Create "remaining N dimensions" if needed
1297
- if (remaining_count > 0) {
1298
- size_t remaining_idx = kept_dimensions;
1299
-
1300
- char remaining_name[256];
1301
- snprintfz(remaining_name, sizeof(remaining_name), "remaining %zu dimension%s",
1302
- remaining_count, remaining_count == 1 ? "" : "s");
1303
-
1304
- new_r->di[remaining_idx] = string_strdupz(remaining_name);
1305
- new_r->dn[remaining_idx] = string_strdupz(remaining_name);
1306
- new_r->od[remaining_idx] = RRDR_DIMENSION_QUERIED | RRDR_DIMENSION_NONZERO;
1307
-
1308
- // Use the units from the first remaining dimension
1309
- if(kept_dimensions < queried_count) {
1310
- size_t first_remaining_d = sorted_dims[kept_dimensions].dim_idx;
1311
- new_r->du[remaining_idx] = string_dup(r->du[first_remaining_d]);
1312
- new_r->dp[remaining_idx] = r->dp[first_remaining_d];
1313
- }
1314
-
1315
- // Aggregate remaining dimensions
1316
- NETDATA_DOUBLE sum = 0.0, min = NAN, max = NAN, ars = 0.0;
1317
- size_t count = 0;
1318
-
1319
- for (size_t row = 0; row < r->rows; row++) {
1320
- size_t dst_idx = row * new_r->d + remaining_idx;
1321
- NETDATA_DOUBLE aggregated_value = 0.0;
1322
- NETDATA_DOUBLE aggregated_ar = 0.0;
1323
- RRDR_VALUE_FLAGS aggregated_flags = RRDR_VALUE_NOTHING;
1324
- bool has_values = false;
1325
-
1326
- for (size_t i = kept_dimensions; i < queried_count; i++) {
1327
- size_t src_d = sorted_dims[i].dim_idx;
1328
- size_t src_idx = row * r->d + src_d;
1329
-
1330
- if(!(r->o[src_idx] & RRDR_VALUE_EMPTY)) {
1331
- NETDATA_DOUBLE value = r->v[src_idx];
1332
- if(!isnan(value)) {
1333
- aggregated_value += value;
1334
- aggregated_ar += r->ar[src_idx];
1335
- aggregated_flags |= (r->o[src_idx] & (RRDR_VALUE_RESET | RRDR_VALUE_PARTIAL));
1336
- has_values = true;
1337
- }
1338
- }
1339
- }
1340
-
1341
- if(has_values) {
1342
- new_r->v[dst_idx] = aggregated_value;
1343
- new_r->ar[dst_idx] = aggregated_ar;
1344
- new_r->o[dst_idx] = aggregated_flags & ~RRDR_VALUE_EMPTY;
1345
-
1346
- // Update statistics for dview
1347
- sum += aggregated_value;
1348
- ars += aggregated_ar;
1349
- if(count == 0) {
1350
- min = max = aggregated_value;
1351
- } else {
1352
- if(aggregated_value < min) min = aggregated_value;
1353
- if(aggregated_value > max) max = aggregated_value;
1354
- }
1355
- count++;
1356
- } else {
1357
- new_r->v[dst_idx] = NAN;
1358
- new_r->ar[dst_idx] = 0.0;
1359
- new_r->o[dst_idx] = RRDR_VALUE_EMPTY;
1360
- }
1361
- }
1362
-
1363
- // Set dview for remaining dimension
1364
- if(new_r->dview) {
1365
- new_r->dview[remaining_idx] = (STORAGE_POINT) {
1366
- .sum = sum,
1367
- .count = count,
1368
- .min = min,
1369
- .max = max,
1370
- .anomaly_count = (size_t)(ars * RRDR_DVIEW_ANOMALY_COUNT_MULTIPLIER / 100.0),
1371
- };
1372
- }
1373
- }
1374
-
1375
- // Cleanup
1376
- onewayalloc_freez(owa, contributions);
1377
- onewayalloc_freez(owa, sorted_dims);
1378
-
1379
- // Free the original RRDR
1380
- rrdr_free(owa, r);
1381
-
1382
- return new_r;
1383
-}