master
c 517 lines 20.4 KB
Raw
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