1
// SPDX-License-Identifier: GPL-3.0-or-later
2
3
-#include "query.h"
4
-#include "web/api/formatters/rrd2json.h"
5
-#include "rrdr.h"
6
-
7
-#include "average/average.h"
8
-#include "countif/countif.h"
9
-#include "incremental_sum/incremental_sum.h"
10
-#include "max/max.h"
11
-#include "median/median.h"
12
-#include "min/min.h"
13
-#include "sum/sum.h"
14
-#include "stddev/stddev.h"
15
-#include "ses/ses.h"
16
-#include "des/des.h"
17
-#include "percentile/percentile.h"
18
-#include "trimmed_mean/trimmed_mean.h"
19
-
20
-#define QUERY_PLAN_MIN_POINTS 10
21
-#define POINTS_TO_EXPAND_QUERY 5
3
+#include "query-internal.h"
4
5
// ----------------------------------------------------------------------------
6
+// helpers to find our way in RRDR
7
25
-static struct {
26
- const char *name;
27
- uint32_t hash;
28
- RRDR_TIME_GROUPING value;
29
- RRDR_TIME_GROUPING add_flush;
30
-
31
- // One time initialization for the module.
32
- // This is called once, when netdata starts.
33
- void (*init)(void);
34
-
35
- // Allocate all required structures for a query.
36
- // This is called once for each netdata query.
37
- void (*create)(struct rrdresult *r, const char *options);
38
-
39
- // Cleanup collected values, but don't destroy the structures.
40
- // This is called when the query engine switches dimensions,
41
- // as part of the same query (so same chart, switching metric).
42
- void (*reset)(struct rrdresult *r);
43
-
44
- // Free all resources allocated for the query.
45
- void (*free)(struct rrdresult *r);
46
-
47
- // Add a single value into the calculation.
48
- // The module may decide to cache it, or use it in the fly.
49
- void (*add)(struct rrdresult *r, NETDATA_DOUBLE value);
50
-
51
- // Generate a single result for the values added so far.
52
- // More values and points may be requested later.
53
- // It is up to the module to reset its internal structures
54
- // when flushing it (so for a few modules it may be better to
55
- // continue after a flush as if nothing changed, for others a
56
- // cleanup of the internal structures may be required).
57
- NETDATA_DOUBLE (*flush)(struct rrdresult *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr);
58
-
59
- TIER_QUERY_FETCH tier_query_fetch;
60
-} api_v1_data_groups[] = {
61
- {.name = "average",
62
- .hash = 0,
63
- .value = RRDR_GROUPING_AVERAGE,
64
- .add_flush = RRDR_GROUPING_AVERAGE,
65
- .init = NULL,
66
- .create= tg_average_create,
67
- .reset = tg_average_reset,
68
- .free = tg_average_free,
69
- .add = tg_average_add,
70
- .flush = tg_average_flush,
71
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
72
- },
73
- {.name = "avg", // alias on 'average'
74
- .hash = 0,
75
- .value = RRDR_GROUPING_AVERAGE,
76
- .add_flush = RRDR_GROUPING_AVERAGE,
77
- .init = NULL,
78
- .create= tg_average_create,
79
- .reset = tg_average_reset,
80
- .free = tg_average_free,
81
- .add = tg_average_add,
82
- .flush = tg_average_flush,
83
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
84
- },
85
- {.name = "mean", // alias on 'average'
86
- .hash = 0,
87
- .value = RRDR_GROUPING_AVERAGE,
88
- .add_flush = RRDR_GROUPING_AVERAGE,
89
- .init = NULL,
90
- .create= tg_average_create,
91
- .reset = tg_average_reset,
92
- .free = tg_average_free,
93
- .add = tg_average_add,
94
- .flush = tg_average_flush,
95
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
96
- },
97
- {.name = "trimmed-mean1",
98
- .hash = 0,
99
- .value = RRDR_GROUPING_TRIMMED_MEAN1,
100
- .add_flush = RRDR_GROUPING_TRIMMED_MEAN,
101
- .init = NULL,
102
- .create= tg_trimmed_mean_create_1,
103
- .reset = tg_trimmed_mean_reset,
104
- .free = tg_trimmed_mean_free,
105
- .add = tg_trimmed_mean_add,
106
- .flush = tg_trimmed_mean_flush,
107
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
108
- },
109
- {.name = "trimmed-mean2",
110
- .hash = 0,
111
- .value = RRDR_GROUPING_TRIMMED_MEAN2,
112
- .add_flush = RRDR_GROUPING_TRIMMED_MEAN,
113
- .init = NULL,
114
- .create= tg_trimmed_mean_create_2,
115
- .reset = tg_trimmed_mean_reset,
116
- .free = tg_trimmed_mean_free,
117
- .add = tg_trimmed_mean_add,
118
- .flush = tg_trimmed_mean_flush,
119
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
120
- },
121
- {.name = "trimmed-mean3",
122
- .hash = 0,
123
- .value = RRDR_GROUPING_TRIMMED_MEAN3,
124
- .add_flush = RRDR_GROUPING_TRIMMED_MEAN,
125
- .init = NULL,
126
- .create= tg_trimmed_mean_create_3,
127
- .reset = tg_trimmed_mean_reset,
128
- .free = tg_trimmed_mean_free,
129
- .add = tg_trimmed_mean_add,
130
- .flush = tg_trimmed_mean_flush,
131
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
132
- },
133
- {.name = "trimmed-mean5",
134
- .hash = 0,
135
- .value = RRDR_GROUPING_TRIMMED_MEAN,
136
- .add_flush = RRDR_GROUPING_TRIMMED_MEAN,
137
- .init = NULL,
138
- .create= tg_trimmed_mean_create_5,
139
- .reset = tg_trimmed_mean_reset,
140
- .free = tg_trimmed_mean_free,
141
- .add = tg_trimmed_mean_add,
142
- .flush = tg_trimmed_mean_flush,
143
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
144
- },
145
- {.name = "trimmed-mean10",
146
- .hash = 0,
147
- .value = RRDR_GROUPING_TRIMMED_MEAN10,
148
- .add_flush = RRDR_GROUPING_TRIMMED_MEAN,
149
- .init = NULL,
150
- .create= tg_trimmed_mean_create_10,
151
- .reset = tg_trimmed_mean_reset,
152
- .free = tg_trimmed_mean_free,
153
- .add = tg_trimmed_mean_add,
154
- .flush = tg_trimmed_mean_flush,
155
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
156
- },
157
- {.name = "trimmed-mean15",
158
- .hash = 0,
159
- .value = RRDR_GROUPING_TRIMMED_MEAN15,
160
- .add_flush = RRDR_GROUPING_TRIMMED_MEAN,
161
- .init = NULL,
162
- .create= tg_trimmed_mean_create_15,
163
- .reset = tg_trimmed_mean_reset,
164
- .free = tg_trimmed_mean_free,
165
- .add = tg_trimmed_mean_add,
166
- .flush = tg_trimmed_mean_flush,
167
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
168
- },
169
- {.name = "trimmed-mean20",
170
- .hash = 0,
171
- .value = RRDR_GROUPING_TRIMMED_MEAN20,
172
- .add_flush = RRDR_GROUPING_TRIMMED_MEAN,
173
- .init = NULL,
174
- .create= tg_trimmed_mean_create_20,
175
- .reset = tg_trimmed_mean_reset,
176
- .free = tg_trimmed_mean_free,
177
- .add = tg_trimmed_mean_add,
178
- .flush = tg_trimmed_mean_flush,
179
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
180
- },
181
- {.name = "trimmed-mean25",
182
- .hash = 0,
183
- .value = RRDR_GROUPING_TRIMMED_MEAN25,
184
- .add_flush = RRDR_GROUPING_TRIMMED_MEAN,
185
- .init = NULL,
186
- .create= tg_trimmed_mean_create_25,
187
- .reset = tg_trimmed_mean_reset,
188
- .free = tg_trimmed_mean_free,
189
- .add = tg_trimmed_mean_add,
190
- .flush = tg_trimmed_mean_flush,
191
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
192
- },
193
- {.name = "trimmed-mean",
194
- .hash = 0,
195
- .value = RRDR_GROUPING_TRIMMED_MEAN,
196
- .add_flush = RRDR_GROUPING_TRIMMED_MEAN,
197
- .init = NULL,
198
- .create= tg_trimmed_mean_create_5,
199
- .reset = tg_trimmed_mean_reset,
200
- .free = tg_trimmed_mean_free,
201
- .add = tg_trimmed_mean_add,
202
- .flush = tg_trimmed_mean_flush,
203
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
204
- },
205
- {.name = "incremental_sum",
206
- .hash = 0,
207
- .value = RRDR_GROUPING_INCREMENTAL_SUM,
208
- .add_flush = RRDR_GROUPING_INCREMENTAL_SUM,
209
- .init = NULL,
210
- .create= tg_incremental_sum_create,
211
- .reset = tg_incremental_sum_reset,
212
- .free = tg_incremental_sum_free,
213
- .add = tg_incremental_sum_add,
214
- .flush = tg_incremental_sum_flush,
215
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
216
- },
217
- {.name = "incremental-sum",
218
- .hash = 0,
219
- .value = RRDR_GROUPING_INCREMENTAL_SUM,
220
- .add_flush = RRDR_GROUPING_INCREMENTAL_SUM,
221
- .init = NULL,
222
- .create= tg_incremental_sum_create,
223
- .reset = tg_incremental_sum_reset,
224
- .free = tg_incremental_sum_free,
225
- .add = tg_incremental_sum_add,
226
- .flush = tg_incremental_sum_flush,
227
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
228
- },
229
- {.name = "median",
230
- .hash = 0,
231
- .value = RRDR_GROUPING_MEDIAN,
232
- .add_flush = RRDR_GROUPING_MEDIAN,
233
- .init = NULL,
234
- .create= tg_median_create,
235
- .reset = tg_median_reset,
236
- .free = tg_median_free,
237
- .add = tg_median_add,
238
- .flush = tg_median_flush,
239
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
240
- },
241
- {.name = "trimmed-median1",
242
- .hash = 0,
243
- .value = RRDR_GROUPING_TRIMMED_MEDIAN1,
244
- .add_flush = RRDR_GROUPING_MEDIAN,
245
- .init = NULL,
246
- .create= tg_median_create_trimmed_1,
247
- .reset = tg_median_reset,
248
- .free = tg_median_free,
249
- .add = tg_median_add,
250
- .flush = tg_median_flush,
251
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
252
- },
253
- {.name = "trimmed-median2",
254
- .hash = 0,
255
- .value = RRDR_GROUPING_TRIMMED_MEDIAN2,
256
- .add_flush = RRDR_GROUPING_MEDIAN,
257
- .init = NULL,
258
- .create= tg_median_create_trimmed_2,
259
- .reset = tg_median_reset,
260
- .free = tg_median_free,
261
- .add = tg_median_add,
262
- .flush = tg_median_flush,
263
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
264
- },
265
- {.name = "trimmed-median3",
266
- .hash = 0,
267
- .value = RRDR_GROUPING_TRIMMED_MEDIAN3,
268
- .add_flush = RRDR_GROUPING_MEDIAN,
269
- .init = NULL,
270
- .create= tg_median_create_trimmed_3,
271
- .reset = tg_median_reset,
272
- .free = tg_median_free,
273
- .add = tg_median_add,
274
- .flush = tg_median_flush,
275
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
276
- },
277
- {.name = "trimmed-median5",
278
- .hash = 0,
279
- .value = RRDR_GROUPING_TRIMMED_MEDIAN,
280
- .add_flush = RRDR_GROUPING_MEDIAN,
281
- .init = NULL,
282
- .create= tg_median_create_trimmed_5,
283
- .reset = tg_median_reset,
284
- .free = tg_median_free,
285
- .add = tg_median_add,
286
- .flush = tg_median_flush,
287
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
288
- },
289
- {.name = "trimmed-median10",
290
- .hash = 0,
291
- .value = RRDR_GROUPING_TRIMMED_MEDIAN10,
292
- .add_flush = RRDR_GROUPING_MEDIAN,
293
- .init = NULL,
294
- .create= tg_median_create_trimmed_10,
295
- .reset = tg_median_reset,
296
- .free = tg_median_free,
297
- .add = tg_median_add,
298
- .flush = tg_median_flush,
299
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
300
- },
301
- {.name = "trimmed-median15",
302
- .hash = 0,
303
- .value = RRDR_GROUPING_TRIMMED_MEDIAN15,
304
- .add_flush = RRDR_GROUPING_MEDIAN,
305
- .init = NULL,
306
- .create= tg_median_create_trimmed_15,
307
- .reset = tg_median_reset,
308
- .free = tg_median_free,
309
- .add = tg_median_add,
310
- .flush = tg_median_flush,
311
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
312
- },
313
- {.name = "trimmed-median20",
314
- .hash = 0,
315
- .value = RRDR_GROUPING_TRIMMED_MEDIAN20,
316
- .add_flush = RRDR_GROUPING_MEDIAN,
317
- .init = NULL,
318
- .create= tg_median_create_trimmed_20,
319
- .reset = tg_median_reset,
320
- .free = tg_median_free,
321
- .add = tg_median_add,
322
- .flush = tg_median_flush,
323
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
324
- },
325
- {.name = "trimmed-median25",
326
- .hash = 0,
327
- .value = RRDR_GROUPING_TRIMMED_MEDIAN25,
328
- .add_flush = RRDR_GROUPING_MEDIAN,
329
- .init = NULL,
330
- .create= tg_median_create_trimmed_25,
331
- .reset = tg_median_reset,
332
- .free = tg_median_free,
333
- .add = tg_median_add,
334
- .flush = tg_median_flush,
335
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
336
- },
337
- {.name = "trimmed-median",
338
- .hash = 0,
339
- .value = RRDR_GROUPING_TRIMMED_MEDIAN,
340
- .add_flush = RRDR_GROUPING_MEDIAN,
341
- .init = NULL,
342
- .create= tg_median_create_trimmed_5,
343
- .reset = tg_median_reset,
344
- .free = tg_median_free,
345
- .add = tg_median_add,
346
- .flush = tg_median_flush,
347
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
348
- },
349
- {.name = "percentile25",
350
- .hash = 0,
351
- .value = RRDR_GROUPING_PERCENTILE25,
352
- .add_flush = RRDR_GROUPING_PERCENTILE,
353
- .init = NULL,
354
- .create= tg_percentile_create_25,
355
- .reset = tg_percentile_reset,
356
- .free = tg_percentile_free,
357
- .add = tg_percentile_add,
358
- .flush = tg_percentile_flush,
359
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
360
- },
361
- {.name = "percentile50",
362
- .hash = 0,
363
- .value = RRDR_GROUPING_PERCENTILE50,
364
- .add_flush = RRDR_GROUPING_PERCENTILE,
365
- .init = NULL,
366
- .create= tg_percentile_create_50,
367
- .reset = tg_percentile_reset,
368
- .free = tg_percentile_free,
369
- .add = tg_percentile_add,
370
- .flush = tg_percentile_flush,
371
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
372
- },
373
- {.name = "percentile75",
374
- .hash = 0,
375
- .value = RRDR_GROUPING_PERCENTILE75,
376
- .add_flush = RRDR_GROUPING_PERCENTILE,
377
- .init = NULL,
378
- .create= tg_percentile_create_75,
379
- .reset = tg_percentile_reset,
380
- .free = tg_percentile_free,
381
- .add = tg_percentile_add,
382
- .flush = tg_percentile_flush,
383
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
384
- },
385
- {.name = "percentile80",
386
- .hash = 0,
387
- .value = RRDR_GROUPING_PERCENTILE80,
388
- .add_flush = RRDR_GROUPING_PERCENTILE,
389
- .init = NULL,
390
- .create= tg_percentile_create_80,
391
- .reset = tg_percentile_reset,
392
- .free = tg_percentile_free,
393
- .add = tg_percentile_add,
394
- .flush = tg_percentile_flush,
395
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
396
- },
397
- {.name = "percentile90",
398
- .hash = 0,
399
- .value = RRDR_GROUPING_PERCENTILE90,
400
- .add_flush = RRDR_GROUPING_PERCENTILE,
401
- .init = NULL,
402
- .create= tg_percentile_create_90,
403
- .reset = tg_percentile_reset,
404
- .free = tg_percentile_free,
405
- .add = tg_percentile_add,
406
- .flush = tg_percentile_flush,
407
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
408
- },
409
- {.name = "percentile95",
410
- .hash = 0,
411
- .value = RRDR_GROUPING_PERCENTILE,
412
- .add_flush = RRDR_GROUPING_PERCENTILE,
413
- .init = NULL,
414
- .create= tg_percentile_create_95,
415
- .reset = tg_percentile_reset,
416
- .free = tg_percentile_free,
417
- .add = tg_percentile_add,
418
- .flush = tg_percentile_flush,
419
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
420
- },
421
- {.name = "percentile97",
422
- .hash = 0,
423
- .value = RRDR_GROUPING_PERCENTILE97,
424
- .add_flush = RRDR_GROUPING_PERCENTILE,
425
- .init = NULL,
426
- .create= tg_percentile_create_97,
427
- .reset = tg_percentile_reset,
428
- .free = tg_percentile_free,
429
- .add = tg_percentile_add,
430
- .flush = tg_percentile_flush,
431
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
432
- },
433
- {.name = "percentile98",
434
- .hash = 0,
435
- .value = RRDR_GROUPING_PERCENTILE98,
436
- .add_flush = RRDR_GROUPING_PERCENTILE,
437
- .init = NULL,
438
- .create= tg_percentile_create_98,
439
- .reset = tg_percentile_reset,
440
- .free = tg_percentile_free,
441
- .add = tg_percentile_add,
442
- .flush = tg_percentile_flush,
443
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
444
- },
445
- {.name = "percentile99",
446
- .hash = 0,
447
- .value = RRDR_GROUPING_PERCENTILE99,
448
- .add_flush = RRDR_GROUPING_PERCENTILE,
449
- .init = NULL,
450
- .create= tg_percentile_create_99,
451
- .reset = tg_percentile_reset,
452
- .free = tg_percentile_free,
453
- .add = tg_percentile_add,
454
- .flush = tg_percentile_flush,
455
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
456
- },
457
- {.name = "percentile",
458
- .hash = 0,
459
- .value = RRDR_GROUPING_PERCENTILE,
460
- .add_flush = RRDR_GROUPING_PERCENTILE,
461
- .init = NULL,
462
- .create= tg_percentile_create_95,
463
- .reset = tg_percentile_reset,
464
- .free = tg_percentile_free,
465
- .add = tg_percentile_add,
466
- .flush = tg_percentile_flush,
467
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
468
- },
469
- {.name = "min",
470
- .hash = 0,
471
- .value = RRDR_GROUPING_MIN,
472
- .add_flush = RRDR_GROUPING_MIN,
473
- .init = NULL,
474
- .create= tg_min_create,
475
- .reset = tg_min_reset,
476
- .free = tg_min_free,
477
- .add = tg_min_add,
478
- .flush = tg_min_flush,
479
- .tier_query_fetch = TIER_QUERY_FETCH_MIN
480
- },
481
- {.name = "max",
482
- .hash = 0,
483
- .value = RRDR_GROUPING_MAX,
484
- .add_flush = RRDR_GROUPING_MAX,
485
- .init = NULL,
486
- .create= tg_max_create,
487
- .reset = tg_max_reset,
488
- .free = tg_max_free,
489
- .add = tg_max_add,
490
- .flush = tg_max_flush,
491
- .tier_query_fetch = TIER_QUERY_FETCH_MAX
492
- },
493
- {.name = "sum",
494
- .hash = 0,
495
- .value = RRDR_GROUPING_SUM,
496
- .add_flush = RRDR_GROUPING_SUM,
497
- .init = NULL,
498
- .create= tg_sum_create,
499
- .reset = tg_sum_reset,
500
- .free = tg_sum_free,
501
- .add = tg_sum_add,
502
- .flush = tg_sum_flush,
503
- .tier_query_fetch = TIER_QUERY_FETCH_SUM
504
- },
505
-
506
- // standard deviation
507
- {.name = "stddev",
508
- .hash = 0,
509
- .value = RRDR_GROUPING_STDDEV,
510
- .add_flush = RRDR_GROUPING_STDDEV,
511
- .init = NULL,
512
- .create= tg_stddev_create,
513
- .reset = tg_stddev_reset,
514
- .free = tg_stddev_free,
515
- .add = tg_stddev_add,
516
- .flush = tg_stddev_flush,
517
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
518
- },
519
- {.name = "cv", // coefficient variation is calculated by stddev
520
- .hash = 0,
521
- .value = RRDR_GROUPING_CV,
522
- .add_flush = RRDR_GROUPING_CV,
523
- .init = NULL,
524
- .create= tg_stddev_create, // not an error, stddev calculates this too
525
- .reset = tg_stddev_reset, // not an error, stddev calculates this too
526
- .free = tg_stddev_free, // not an error, stddev calculates this too
527
- .add = tg_stddev_add, // not an error, stddev calculates this too
528
- .flush = tg_stddev_coefficient_of_variation_flush,
529
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
530
- },
531
- {.name = "rsd", // alias of 'cv'
532
- .hash = 0,
533
- .value = RRDR_GROUPING_CV,
534
- .add_flush = RRDR_GROUPING_CV,
535
- .init = NULL,
536
- .create= tg_stddev_create, // not an error, stddev calculates this too
537
- .reset = tg_stddev_reset, // not an error, stddev calculates this too
538
- .free = tg_stddev_free, // not an error, stddev calculates this too
539
- .add = tg_stddev_add, // not an error, stddev calculates this too
540
- .flush = tg_stddev_coefficient_of_variation_flush,
541
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
542
- },
543
-
544
- // single exponential smoothing
545
- {.name = "ses",
546
- .hash = 0,
547
- .value = RRDR_GROUPING_SES,
548
- .add_flush = RRDR_GROUPING_SES,
549
- .init = tg_ses_init,
550
- .create= tg_ses_create,
551
- .reset = tg_ses_reset,
552
- .free = tg_ses_free,
553
- .add = tg_ses_add,
554
- .flush = tg_ses_flush,
555
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
556
- },
557
- {.name = "ema", // alias for 'ses'
558
- .hash = 0,
559
- .value = RRDR_GROUPING_SES,
560
- .add_flush = RRDR_GROUPING_SES,
561
- .init = NULL,
562
- .create= tg_ses_create,
563
- .reset = tg_ses_reset,
564
- .free = tg_ses_free,
565
- .add = tg_ses_add,
566
- .flush = tg_ses_flush,
567
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
568
- },
569
- {.name = "ewma", // alias for ses
570
- .hash = 0,
571
- .value = RRDR_GROUPING_SES,
572
- .add_flush = RRDR_GROUPING_SES,
573
- .init = NULL,
574
- .create= tg_ses_create,
575
- .reset = tg_ses_reset,
576
- .free = tg_ses_free,
577
- .add = tg_ses_add,
578
- .flush = tg_ses_flush,
579
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
580
- },
581
-
582
- // double exponential smoothing
583
- {.name = "des",
584
- .hash = 0,
585
- .value = RRDR_GROUPING_DES,
586
- .add_flush = RRDR_GROUPING_DES,
587
- .init = tg_des_init,
588
- .create= tg_des_create,
589
- .reset = tg_des_reset,
590
- .free = tg_des_free,
591
- .add = tg_des_add,
592
- .flush = tg_des_flush,
593
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
594
- },
595
-
596
- {.name = "countif",
597
- .hash = 0,
598
- .value = RRDR_GROUPING_COUNTIF,
599
- .add_flush = RRDR_GROUPING_COUNTIF,
600
- .init = NULL,
601
- .create= tg_countif_create,
602
- .reset = tg_countif_reset,
603
- .free = tg_countif_free,
604
- .add = tg_countif_add,
605
- .flush = tg_countif_flush,
606
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
607
- },
608
-
609
- // terminator
610
- {.name = NULL,
611
- .hash = 0,
612
- .value = RRDR_GROUPING_UNDEFINED,
613
- .add_flush = RRDR_GROUPING_AVERAGE,
614
- .init = NULL,
615
- .create= tg_average_create,
616
- .reset = tg_average_reset,
617
- .free = tg_average_free,
618
- .add = tg_average_add,
619
- .flush = tg_average_flush,
620
- .tier_query_fetch = TIER_QUERY_FETCH_AVERAGE
621
- }
622
-};
623
-
624
-void time_grouping_init(void) {
625
- int i;
626
-
627
- for(i = 0; api_v1_data_groups[i].name ; i++) {
628
- api_v1_data_groups[i].hash = simple_hash(api_v1_data_groups[i].name);
629
-
630
- if(api_v1_data_groups[i].init)
631
- api_v1_data_groups[i].init();
632
- }
8
+ALWAYS_INLINE
9
+static RRDR_VALUE_FLAGS *UNUSED_FUNCTION(rrdr_line_options)(RRDR *r, long rrdr_line) {
10
+ return &r->o[ rrdr_line * r->d ];
11
}
12
635
-const char *time_grouping_id2txt(RRDR_TIME_GROUPING group) {
636
- int i;
637
-
638
- for(i = 0; api_v1_data_groups[i].name ; i++) {
639
- if(api_v1_data_groups[i].value == group) {
640
- return api_v1_data_groups[i].name;
641
- }
642
- }
643
-
644
- return "average";
13
+ALWAYS_INLINE
14
+static NETDATA_DOUBLE *UNUSED_FUNCTION(rrdr_line_values)(RRDR *r, long rrdr_line) {
15
+ return &r->v[ rrdr_line * r->d ];
16
}
17
647
-RRDR_TIME_GROUPING time_grouping_txt2id(const char *name) {
648
- int i;
649
-
650
- uint32_t hash = simple_hash(name);
651
- for(i = 0; api_v1_data_groups[i].name ; i++)
652
- if(unlikely(hash == api_v1_data_groups[i].hash && !strcmp(name, api_v1_data_groups[i].name)))
653
- return api_v1_data_groups[i].value;
654
-
655
- return RRDR_GROUPING_AVERAGE;
656
-}
18
+ALWAYS_INLINE
19
+static long rrdr_line_init(RRDR *r __maybe_unused, time_t t __maybe_unused, long rrdr_line) {
20
+ rrdr_line++;
21
658
-RRDR_TIME_GROUPING time_grouping_parse(const char *name, RRDR_TIME_GROUPING def) {
659
- int i;
22
+ internal_fatal(rrdr_line >= (long)r->n,
23
+ "QUERY: requested to step above RRDR size for query '%s'",
24
+ r->internal.qt->id);
25
661
- uint32_t hash = simple_hash(name);
662
- for(i = 0; api_v1_data_groups[i].name ; i++)
663
- if(unlikely(hash == api_v1_data_groups[i].hash && !strcmp(name, api_v1_data_groups[i].name)))
664
- return api_v1_data_groups[i].value;
26
+ internal_fatal(r->t[rrdr_line] != t,
27
+ "QUERY: wrong timestamp at RRDR line %ld, expected %ld, got %ld, of query '%s'",
28
+ rrdr_line, r->t[rrdr_line], t, r->internal.qt->id);
29
666
- return def;
30
+ return rrdr_line;
31
}
32
669
-const char *time_grouping_tostring(RRDR_TIME_GROUPING group) {
670
- int i;
671
-
672
- for(i = 0; api_v1_data_groups[i].name ; i++)
673
- if(unlikely(group == api_v1_data_groups[i].value))
674
- return api_v1_data_groups[i].name;
33
+// ----------------------------------------------------------------------------
34
+// dimension level query engine
35
676
- return "unknown";
677
-}
36
+#define query_interpolate_point(this_point, last_point, now) do { \
37
+ if(likely( \
38
+ /* the point to interpolate is more than 1s wide */ \
39
+ (this_point).sp.end_time_s - (this_point).sp.start_time_s > 1 \
40
+ \
41
+ /* the two points are exactly next to each other */ \
42
+ && (last_point).sp.end_time_s == (this_point).sp.start_time_s \
43
+ \
44
+ /* both points are valid numbers */ \
45
+ && netdata_double_isnumber((this_point).value) \
46
+ && netdata_double_isnumber((last_point).value) \
47
+ \
48
+ )) { \
49
+ (this_point).value = (last_point).value + ((this_point).value - (last_point).value) * (1.0 - (NETDATA_DOUBLE)((this_point).sp.end_time_s - (now)) / (NETDATA_DOUBLE)((this_point).sp.end_time_s - (this_point).sp.start_time_s)); \
50
+ (this_point).sp.end_time_s = now; \
51
+ } \
52
+} while(0)
53
679
-static void rrdr_set_grouping_function(RRDR *r, RRDR_TIME_GROUPING group_method) {
680
- int i, found = 0;
681
- for(i = 0; !found && api_v1_data_groups[i].name ;i++) {
682
- if(api_v1_data_groups[i].value == group_method) {
683
- r->time_grouping.create = api_v1_data_groups[i].create;
684
- r->time_grouping.reset = api_v1_data_groups[i].reset;
685
- r->time_grouping.free = api_v1_data_groups[i].free;
686
- r->time_grouping.add = api_v1_data_groups[i].add;
687
- r->time_grouping.flush = api_v1_data_groups[i].flush;
688
- r->time_grouping.tier_query_fetch = api_v1_data_groups[i].tier_query_fetch;
689
- r->time_grouping.add_flush = api_v1_data_groups[i].add_flush;
690
- found = 1;
691
- }
692
- }
693
- if(!found) {
694
- errno_clear();
695
- internal_error(true, "QUERY: grouping method %u not found. Using 'average'", (unsigned int)group_method);
696
- r->time_grouping.create = tg_average_create;
697
- r->time_grouping.reset = tg_average_reset;
698
- r->time_grouping.free = tg_average_free;
699
- r->time_grouping.add = tg_average_add;
700
- r->time_grouping.flush = tg_average_flush;
701
- r->time_grouping.tier_query_fetch = TIER_QUERY_FETCH_AVERAGE;
702
- r->time_grouping.add_flush = RRDR_GROUPING_AVERAGE;
703
- }
704
-}
54
+#define query_add_point_to_group(r, point, ops, add_flush) do { \
55
+ if(likely(netdata_double_isnumber((point).value))) { \
56
+ if(likely(fpclassify((point).value) != FP_ZERO)) \
57
+ (ops)->group_points_non_zero++; \
58
+ \
59
+ if(unlikely((point).sp.flags & SN_FLAG_RESET)) \
60
+ (ops)->group_value_flags |= RRDR_VALUE_RESET; \
61
+ \
62
+ time_grouping_add(r, (point).value, add_flush); \
63
+ \
64
+ storage_point_merge_to((ops)->group_point, (point).sp); \
65
+ if(!(point).added) \
66
+ storage_point_merge_to((ops)->query_point, (point).sp); \
67
+ } \
68
+ \
69
+ (ops)->group_points_added++; \
70
+} while(0)
71
706
-ALWAYS_INLINE_HOT_FLATTEN
707
-static void time_grouping_add(RRDR *r, NETDATA_DOUBLE value, const RRDR_TIME_GROUPING add_flush) {
708
- switch(add_flush) {
709
- case RRDR_GROUPING_AVERAGE:
710
- tg_average_add(r, value);
711
- break;
72
+NOT_INLINE_HOT static void rrd2rrdr_query_execute(RRDR *r, size_t dim_id_in_rrdr, QUERY_ENGINE_OPS *ops) {
73
+ QUERY_TARGET *qt = r->internal.qt;
74
+ QUERY_METRIC *qm = ops->qm;
75
713
- case RRDR_GROUPING_MAX:
714
- tg_max_add(r, value);
715
- break;
76
+ const RRDR_TIME_GROUPING add_flush = r->time_grouping.add_flush;
77
717
- case RRDR_GROUPING_MIN:
718
- tg_min_add(r, value);
719
- break;
78
+ ops->group_point = STORAGE_POINT_UNSET;
79
+ ops->query_point = STORAGE_POINT_UNSET;
80
721
- case RRDR_GROUPING_MEDIAN:
722
- tg_median_add(r, value);
723
- break;
81
+ RRDR_OPTIONS options = qt->window.options;
82
+ size_t points_wanted = qt->window.points;
83
+ time_t after_wanted = qt->window.after;
84
+ time_t before_wanted = qt->window.before; (void)before_wanted;
85
725
- case RRDR_GROUPING_STDDEV:
726
- case RRDR_GROUPING_CV:
727
- tg_stddev_add(r, value);
728
- break;
86
+// bool debug_this = false;
87
+// if(strcmp("user", string2str(rd->id)) == 0 && strcmp("system.cpu", string2str(rd->rrdset->id)) == 0)
88
+// debug_this = true;
89
730
- case RRDR_GROUPING_SUM:
731
- tg_sum_add(r, value);
732
- break;
90
+ size_t points_added = 0;
91
734
- case RRDR_GROUPING_COUNTIF:
735
- tg_countif_add(r, value);
736
- break;
92
+ long rrdr_line = -1;
93
+ bool use_anomaly_bit_as_value = (r->internal.qt->window.options & RRDR_OPTION_ANOMALY_BIT) ? true : false;
94
738
- case RRDR_GROUPING_TRIMMED_MEAN:
739
- tg_trimmed_mean_add(r, value);
740
- break;
95
+ NETDATA_DOUBLE min = r->view.min, max = r->view.max;
96
742
- case RRDR_GROUPING_PERCENTILE:
743
- tg_percentile_add(r, value);
744
- break;
97
+ QUERY_POINT last2_point = QUERY_POINT_EMPTY;
98
+ QUERY_POINT last1_point = QUERY_POINT_EMPTY;
99
+ QUERY_POINT new_point = QUERY_POINT_EMPTY;
100
746
- case RRDR_GROUPING_SES:
747
- tg_ses_add(r, value);
748
- break;
101
+ // ONE POINT READ-AHEAD
102
+ // when we switch plans, we read-ahead a point from the next plan
103
+ // to join them smoothly at the exact time the next plan begins
104
+ STORAGE_POINT next1_point = STORAGE_POINT_UNSET;
105
750
- case RRDR_GROUPING_DES:
751
- tg_des_add(r, value);
752
- break;
106
+ time_t now_start_time = after_wanted - ops->query_granularity;
107
+ time_t now_end_time = after_wanted + ops->view_update_every - ops->query_granularity;
108
754
- case RRDR_GROUPING_INCREMENTAL_SUM:
755
- tg_incremental_sum_add(r, value);
756
- break;
109
+ size_t db_points_read_since_plan_switch = 0; (void)db_points_read_since_plan_switch;
110
+ size_t query_is_finished_counter = 0;
111
758
- default:
759
- r->time_grouping.add(r, value);
760
- break;
761
- }
762
-}
112
+ // The main loop, based on the query granularity we need
113
+ for( ; points_added < points_wanted && query_is_finished_counter <= 10 ;
114
+ now_start_time = now_end_time, now_end_time += ops->view_update_every) {
115
764
-ALWAYS_INLINE_HOT_FLATTEN
765
-static NETDATA_DOUBLE time_grouping_flush(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr, const RRDR_TIME_GROUPING add_flush) {
766
- switch(add_flush) {
767
- case RRDR_GROUPING_AVERAGE:
768
- return tg_average_flush(r, rrdr_value_options_ptr);
116
+ if(unlikely(query_plan_should_switch_plan(ops, now_end_time))) {
117
+ query_planer_next_plan(ops, now_end_time, new_point.sp.end_time_s);
118
+ db_points_read_since_plan_switch = 0;
119
+ }
120
770
- case RRDR_GROUPING_MAX:
771
- return tg_max_flush(r, rrdr_value_options_ptr);
121
+ // read all the points of the db, prior to the time we need (now_end_time)
122
773
- case RRDR_GROUPING_MIN:
774
- return tg_min_flush(r, rrdr_value_options_ptr);
123
+ size_t count_same_end_time = 0;
124
+ while(count_same_end_time < 100) {
125
+ if(likely(count_same_end_time == 0)) {
126
+ last2_point = last1_point;
127
+ last1_point = new_point;
128
+ }
129
776
- case RRDR_GROUPING_MEDIAN:
777
- return tg_median_flush(r, rrdr_value_options_ptr);
130
+ if(unlikely(storage_engine_query_is_finished(ops->seqh))) {
131
+ query_is_finished_counter++;
132
779
- case RRDR_GROUPING_STDDEV:
780
- return tg_stddev_flush(r, rrdr_value_options_ptr);
133
+ if(count_same_end_time != 0) {
134
+ last2_point = last1_point;
135
+ last1_point = new_point;
136
+ }
137
+ new_point = QUERY_POINT_EMPTY;
138
+ new_point.sp.start_time_s = last1_point.sp.end_time_s;
139
+ new_point.sp.end_time_s = now_end_time;
140
+//
141
+// if(debug_this) netdata_log_info("QUERY: is finished() returned true");
142
+//
143
+ break;
144
+ }
145
+ else
146
+ query_is_finished_counter = 0;
147
782
- case RRDR_GROUPING_CV:
783
- return tg_stddev_coefficient_of_variation_flush(r, rrdr_value_options_ptr);
148
+ // fetch the new point
149
+ {
150
+ STORAGE_POINT sp;
151
+ if(likely(storage_point_is_unset(next1_point))) {
152
+ db_points_read_since_plan_switch++;
153
+ sp = storage_engine_query_next_metric(ops->seqh);
154
+ ops->db_points_read_per_tier[ops->tier]++;
155
+ ops->db_total_points_read++;
156
785
- case RRDR_GROUPING_SUM:
786
- return tg_sum_flush(r, rrdr_value_options_ptr);
157
+ if(unlikely(options & RRDR_OPTION_ABSOLUTE))
158
+ storage_point_make_positive(sp);
159
+ }
160
+ else {
161
+ // ONE POINT READ-AHEAD
162
+ sp = next1_point;
163
+ storage_point_unset(next1_point);
164
+ db_points_read_since_plan_switch = 1;
165
+ }
166
788
- case RRDR_GROUPING_COUNTIF:
789
- return tg_countif_flush(r, rrdr_value_options_ptr);
167
+ // ONE POINT READ-AHEAD
168
+ if(unlikely(query_plan_should_switch_plan(ops, sp.end_time_s) &&
169
+ query_planer_next_plan(ops, now_end_time, new_point.sp.end_time_s))) {
170
791
- case RRDR_GROUPING_TRIMMED_MEAN:
792
- return tg_trimmed_mean_flush(r, rrdr_value_options_ptr);
171
+ // The end time of the current point, crosses our plans (tiers)
172
+ // so, we switched plan (tier)
173
+ //
174
+ // There are 2 cases now:
175
+ //
176
+ // A. the entire point of the previous plan is to the future of point from the next plan
177
+ // B. part of the point of the previous plan overlaps with the point from the next plan
178
794
- case RRDR_GROUPING_PERCENTILE:
795
- return tg_percentile_flush(r, rrdr_value_options_ptr);
179
+ STORAGE_POINT sp2 = storage_engine_query_next_metric(ops->seqh);
180
+ ops->db_points_read_per_tier[ops->tier]++;
181
+ ops->db_total_points_read++;
182
797
- case RRDR_GROUPING_SES:
798
- return tg_ses_flush(r, rrdr_value_options_ptr);
183
+ if(unlikely(options & RRDR_OPTION_ABSOLUTE))
184
+ storage_point_make_positive(sp);
185
800
- case RRDR_GROUPING_DES:
801
- return tg_des_flush(r, rrdr_value_options_ptr);
186
+ if(sp.start_time_s > sp2.start_time_s)
187
+ // the point from the previous plan is useless
188
+ sp = sp2;
189
+ else
190
+ // let the query run from the previous plan
191
+ // but setting this will also cut off the interpolation
192
+ // of the point from the previous plan
193
+ next1_point = sp2;
194
+ }
195
803
- case RRDR_GROUPING_INCREMENTAL_SUM:
804
- return tg_incremental_sum_flush(r, rrdr_value_options_ptr);
196
+ new_point.sp = sp;
197
+ new_point.added = false;
198
+ query_point_set_id(new_point, ops->db_total_points_read);
199
806
- default:
807
- return r->time_grouping.flush(r, rrdr_value_options_ptr);
808
- }
809
-}
200
+// if(debug_this)
201
+// netdata_log_info("QUERY: got point %zu, from time %ld to %ld // now from %ld to %ld // query from %ld to %ld",
202
+// new_point.id, new_point.start_time, new_point.end_time, now_start_time, now_end_time, after_wanted, before_wanted);
203
+//
204
+ // get the right value from the point we got
205
+ if(likely(!storage_point_is_unset(sp) && !storage_point_is_gap(sp))) {
206
811
-RRDR_GROUP_BY group_by_parse(char *s) {
812
- RRDR_GROUP_BY group_by = RRDR_GROUP_BY_NONE;
207
+ if(unlikely(use_anomaly_bit_as_value))
208
+ new_point.value = storage_point_anomaly_rate(new_point.sp);
209
814
- while(s) {
815
- char *key = strsep_skip_consecutive_separators(&s, ",| ");
816
- if (!key || !*key) continue;
210
+ else {
211
+ switch (ops->tier_query_fetch) {
212
+ default:
213
+ case TIER_QUERY_FETCH_AVERAGE:
214
+ new_point.value = sp.sum / (NETDATA_DOUBLE)sp.count;
215
+ break;
216
818
- if (strcmp(key, "selected") == 0)
819
- group_by |= RRDR_GROUP_BY_SELECTED;
217
+ case TIER_QUERY_FETCH_MIN:
218
+ new_point.value = sp.min;
219
+ break;
220
821
- if (strcmp(key, "dimension") == 0)
822
- group_by |= RRDR_GROUP_BY_DIMENSION;
221
+ case TIER_QUERY_FETCH_MAX:
222
+ new_point.value = sp.max;
223
+ break;
224
824
- if (strcmp(key, "instance") == 0)
825
- group_by |= RRDR_GROUP_BY_INSTANCE;
225
+ case TIER_QUERY_FETCH_SUM:
226
+ new_point.value = sp.sum;
227
+ break;
228
+ }
229
+ }
230
+ }
231
+ else
232
+ new_point.value = NAN;
233
+ }
234
827
- if (strcmp(key, "percentage-of-instance") == 0)
828
- group_by |= RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE;
235
+ // check if the db is giving us zero duration points
236
+ if(unlikely(db_points_read_since_plan_switch > 1 &&
237
+ new_point.sp.start_time_s == new_point.sp.end_time_s)) {
238
830
- if (strcmp(key, "label") == 0)
831
- group_by |= RRDR_GROUP_BY_LABEL;
239
+ internal_error(true, "QUERY: '%s', dimension '%s' next_metric() returned "
240
+ "point %zu from %ld to %ld, that are both equal",
241
+ qt->id, query_metric_id(qt, qm),
242
+ new_point.id, new_point.sp.start_time_s, new_point.sp.end_time_s);
243
833
- if (strcmp(key, "node") == 0)
834
- group_by |= RRDR_GROUP_BY_NODE;
244
+ new_point.sp.start_time_s = new_point.sp.end_time_s - ops->tier_ptr->db_update_every_s;
245
+ }
246
836
- if (strcmp(key, "context") == 0)
837
- group_by |= RRDR_GROUP_BY_CONTEXT;
247
+ // check if the db is advancing the query
248
+ if(unlikely(db_points_read_since_plan_switch > 1 &&
249
+ new_point.sp.end_time_s <= last1_point.sp.end_time_s)) {
250
839
- if (strcmp(key, "units") == 0)
840
- group_by |= RRDR_GROUP_BY_UNITS;
841
- }
251
+ internal_error(true,
252
+ "QUERY: '%s', dimension '%s' next_metric() returned "
253
+ "point %zu from %ld to %ld, before the "
254
+ "last point %zu from %ld to %ld, "
255
+ "now is %ld to %ld",
256
+ qt->id, query_metric_id(qt, qm),
257
+ new_point.id, new_point.sp.start_time_s, new_point.sp.end_time_s,
258
+ last1_point.id, last1_point.sp.start_time_s, last1_point.sp.end_time_s,
259
+ now_start_time, now_end_time);
260
843
- if((group_by & RRDR_GROUP_BY_SELECTED) && (group_by & ~RRDR_GROUP_BY_SELECTED)) {
844
- internal_error(true, "group-by given by query has 'selected' together with more groupings");
845
- group_by = RRDR_GROUP_BY_SELECTED; // remove all other groupings
846
- }
261
+ count_same_end_time++;
262
+ continue;
263
+ }
264
+ count_same_end_time = 0;
265
848
- if(group_by & RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE)
849
- group_by = RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE; // remove all other groupings
266
+ // decide how to use this point
267
+ if(likely(new_point.sp.end_time_s < now_end_time)) { // likely to favor tier0
268
+ // this db point ends before our now_end_time
269
851
- return group_by;
852
-}
270
+ if(likely(new_point.sp.end_time_s >= now_start_time)) { // likely to favor tier0
271
+ // this db point ends after our now_start time
272
854
-void buffer_json_group_by_to_array(BUFFER *wb, RRDR_GROUP_BY group_by) {
855
- if(group_by == RRDR_GROUP_BY_NONE)
856
- buffer_json_add_array_item_string(wb, "none");
857
- else {
858
- if (group_by & RRDR_GROUP_BY_DIMENSION)
859
- buffer_json_add_array_item_string(wb, "dimension");
273
+ query_add_point_to_group(r, new_point, ops, add_flush);
274
+ new_point.added = true;
275
+ }
276
+ else {
277
+ // we don't need this db point
278
+ // it is totally outside our current time-frame
279
861
- if (group_by & RRDR_GROUP_BY_INSTANCE)
862
- buffer_json_add_array_item_string(wb, "instance");
280
+ // this is desirable for the first point of the query
281
+ // because it allows us to interpolate the next point
282
+ // at exactly the time we will want
283
864
- if (group_by & RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE)
865
- buffer_json_add_array_item_string(wb, "percentage-of-instance");
866
-
867
- if (group_by & RRDR_GROUP_BY_LABEL)
868
- buffer_json_add_array_item_string(wb, "label");
869
-
870
- if (group_by & RRDR_GROUP_BY_NODE)
871
- buffer_json_add_array_item_string(wb, "node");
872
-
873
- if (group_by & RRDR_GROUP_BY_CONTEXT)
874
- buffer_json_add_array_item_string(wb, "context");
875
-
876
- if (group_by & RRDR_GROUP_BY_UNITS)
877
- buffer_json_add_array_item_string(wb, "units");
878
-
879
- if (group_by & RRDR_GROUP_BY_SELECTED)
880
- buffer_json_add_array_item_string(wb, "selected");
881
- }
882
-}
883
-
884
-RRDR_GROUP_BY_FUNCTION group_by_aggregate_function_parse(const char *s) {
885
- if(strcmp(s, "average") == 0)
886
- return RRDR_GROUP_BY_FUNCTION_AVERAGE;
887
-
888
- if(strcmp(s, "avg") == 0)
889
- return RRDR_GROUP_BY_FUNCTION_AVERAGE;
890
-
891
- if(strcmp(s, "min") == 0)
892
- return RRDR_GROUP_BY_FUNCTION_MIN;
893
-
894
- if(strcmp(s, "max") == 0)
895
- return RRDR_GROUP_BY_FUNCTION_MAX;
896
-
897
- if(strcmp(s, "sum") == 0)
898
- return RRDR_GROUP_BY_FUNCTION_SUM;
899
-
900
- if(strcmp(s, "percentage") == 0)
901
- return RRDR_GROUP_BY_FUNCTION_PERCENTAGE;
902
-
903
- return RRDR_GROUP_BY_FUNCTION_AVERAGE;
904
-}
905
-
906
-const char *group_by_aggregate_function_to_string(RRDR_GROUP_BY_FUNCTION group_by_function) {
907
- switch(group_by_function) {
908
- default:
909
- case RRDR_GROUP_BY_FUNCTION_AVERAGE:
910
- return "average";
911
-
912
- case RRDR_GROUP_BY_FUNCTION_MIN:
913
- return "min";
914
-
915
- case RRDR_GROUP_BY_FUNCTION_MAX:
916
- return "max";
917
-
918
- case RRDR_GROUP_BY_FUNCTION_SUM:
919
- return "sum";
920
-
921
- case RRDR_GROUP_BY_FUNCTION_PERCENTAGE:
922
- return "percentage";
923
- }
924
-}
925
-
926
-// ----------------------------------------------------------------------------
927
-// helpers to find our way in RRDR
928
-
929
-static inline RRDR_VALUE_FLAGS *UNUSED_FUNCTION(rrdr_line_options)(RRDR *r, long rrdr_line) {
930
- return &r->o[ rrdr_line * r->d ];
931
-}
932
-
933
-static inline NETDATA_DOUBLE *UNUSED_FUNCTION(rrdr_line_values)(RRDR *r, long rrdr_line) {
934
- return &r->v[ rrdr_line * r->d ];
935
-}
936
-
937
-static inline long rrdr_line_init(RRDR *r __maybe_unused, time_t t __maybe_unused, long rrdr_line) {
938
- rrdr_line++;
939
-
940
- internal_fatal(rrdr_line >= (long)r->n,
941
- "QUERY: requested to step above RRDR size for query '%s'",
942
- r->internal.qt->id);
943
-
944
- internal_fatal(r->t[rrdr_line] != t,
945
- "QUERY: wrong timestamp at RRDR line %ld, expected %ld, got %ld, of query '%s'",
946
- rrdr_line, r->t[rrdr_line], t, r->internal.qt->id);
947
-
948
- return rrdr_line;
949
-}
950
-
951
-// ----------------------------------------------------------------------------
952
-// tier management
953
-
954
-static bool query_metric_is_valid_tier(QUERY_METRIC *qm, size_t tier) {
955
- if(!qm->tiers[tier].smh || !qm->tiers[tier].db_first_time_s || !qm->tiers[tier].db_last_time_s || !qm->tiers[tier].db_update_every_s)
956
- return false;
957
-
958
- return true;
959
-}
960
-
961
-static size_t query_metric_first_working_tier(QUERY_METRIC *qm) {
962
- for(size_t tier = 0; tier < nd_profile.storage_tiers; tier++) {
963
-
964
- // find the db time-range for this tier for all metrics
965
- STORAGE_METRIC_HANDLE *smh = qm->tiers[tier].smh;
966
- time_t first_time_s = qm->tiers[tier].db_first_time_s;
967
- time_t last_time_s = qm->tiers[tier].db_last_time_s;
968
- time_t update_every_s = qm->tiers[tier].db_update_every_s;
969
-
970
- if(!smh || !first_time_s || !last_time_s || !update_every_s)
971
- continue;
972
-
973
- return tier;
974
- }
975
-
976
- return 0;
977
-}
978
-
979
-static long query_plan_points_coverage_weight(time_t db_first_time_s, time_t db_last_time_s, time_t db_update_every_s, time_t after_wanted, time_t before_wanted, size_t points_wanted, size_t tier __maybe_unused) {
980
- if(db_first_time_s == 0 ||
981
- db_last_time_s == 0 ||
982
- db_update_every_s == 0 ||
983
- db_first_time_s > before_wanted ||
984
- db_last_time_s < after_wanted)
985
- return -LONG_MAX;
986
-
987
- long long common_first_t = MAX(db_first_time_s, after_wanted);
988
- long long common_last_t = MIN(db_last_time_s, before_wanted);
989
-
990
- long long time_coverage = (common_last_t - common_first_t) * 1000000LL / (before_wanted - after_wanted);
991
- long long points_wanted_in_coverage = (long long)points_wanted * time_coverage / 1000000LL;
992
-
993
- long long points_available = (common_last_t - common_first_t) / db_update_every_s;
994
- long long points_delta = (long)(points_available - points_wanted_in_coverage);
995
- long long points_coverage = (points_delta < 0) ? (long)(points_available * time_coverage / points_wanted_in_coverage) : time_coverage;
996
-
997
- // a way to benefit higher tiers
998
- // points_coverage += (long)tier * 10000;
999
-
1000
- if(points_available <= 0)
1001
- return -LONG_MAX;
1002
-
1003
- return (long)(points_coverage + (25000LL * tier)); // 2.5% benefit for each higher tier
1004
-}
1005
-
1006
-static size_t query_metric_best_tier_for_timeframe(QUERY_METRIC *qm, time_t after_wanted, time_t before_wanted, size_t points_wanted) {
1007
- if(unlikely(nd_profile.storage_tiers < 2))
1008
- return 0;
1009
-
1010
- if(unlikely(after_wanted == before_wanted || points_wanted <= 0))
1011
- return query_metric_first_working_tier(qm);
1012
-
1013
- if(points_wanted < QUERY_PLAN_MIN_POINTS)
1014
- // when selecting tiers, aim for a resolution of at least QUERY_PLAN_MIN_POINTS points
1015
- points_wanted = (before_wanted - after_wanted) > QUERY_PLAN_MIN_POINTS ? QUERY_PLAN_MIN_POINTS : before_wanted - after_wanted;
1016
-
1017
- time_t min_first_time_s = 0;
1018
- time_t max_last_time_s = 0;
1019
-
1020
- for(size_t tier = 0; tier < nd_profile.storage_tiers; tier++) {
1021
- time_t first_time_s = qm->tiers[tier].db_first_time_s;
1022
- time_t last_time_s = qm->tiers[tier].db_last_time_s;
1023
-
1024
- if(!min_first_time_s || (first_time_s && first_time_s < min_first_time_s))
1025
- min_first_time_s = first_time_s;
1026
-
1027
- if(!max_last_time_s || (last_time_s && last_time_s > max_last_time_s))
1028
- max_last_time_s = last_time_s;
1029
- }
1030
-
1031
- for(size_t tier = 0; tier < nd_profile.storage_tiers; tier++) {
1032
-
1033
- // find the db time-range for this tier for all metrics
1034
- STORAGE_METRIC_HANDLE *smh = qm->tiers[tier].smh;
1035
- time_t first_time_s = qm->tiers[tier].db_first_time_s;
1036
- time_t last_time_s = qm->tiers[tier].db_last_time_s;
1037
- time_t update_every_s = qm->tiers[tier].db_update_every_s;
1038
-
1039
- if( !smh ||
1040
- !first_time_s ||
1041
- !last_time_s ||
1042
- !update_every_s ||
1043
- first_time_s > before_wanted ||
1044
- last_time_s < after_wanted
1045
- ) {
1046
- qm->tiers[tier].weight = -LONG_MAX;
1047
- continue;
1048
- }
1049
-
1050
- internal_fatal(first_time_s > before_wanted || last_time_s < after_wanted, "QUERY: invalid db durations");
1051
-
1052
- qm->tiers[tier].weight = query_plan_points_coverage_weight(
1053
- min_first_time_s, max_last_time_s, update_every_s,
1054
- after_wanted, before_wanted, points_wanted, tier);
1055
- }
1056
-
1057
- size_t best_tier = 0;
1058
- for(size_t tier = 1; tier < nd_profile.storage_tiers; tier++) {
1059
- if(qm->tiers[tier].weight >= qm->tiers[best_tier].weight)
1060
- best_tier = tier;
1061
- }
1062
-
1063
- return best_tier;
1064
-}
1065
-
1066
-static size_t rrddim_find_best_tier_for_timeframe(QUERY_TARGET *qt, time_t after_wanted, time_t before_wanted, size_t points_wanted) {
1067
- if(unlikely(nd_profile.storage_tiers < 2))
1068
- return 0;
1069
-
1070
- if(unlikely(after_wanted == before_wanted || points_wanted <= 0)) {
1071
- internal_error(true, "QUERY: '%s' has invalid params to tier calculation", qt->id);
1072
- return 0;
1073
- }
1074
-
1075
- long weight[nd_profile.storage_tiers];
1076
-
1077
- for(size_t tier = 0; tier < nd_profile.storage_tiers; tier++) {
1078
-
1079
- time_t common_first_time_s = 0;
1080
- time_t common_last_time_s = 0;
1081
- time_t common_update_every_s = 0;
1082
-
1083
- // find the db time-range for this tier for all metrics
1084
- for(size_t i = 0, used = qt->query.used; i < used ; i++) {
1085
- QUERY_METRIC *qm = query_metric(qt, i);
1086
-
1087
- time_t first_time_s = qm->tiers[tier].db_first_time_s;
1088
- time_t last_time_s = qm->tiers[tier].db_last_time_s;
1089
- time_t update_every_s = qm->tiers[tier].db_update_every_s;
1090
-
1091
- if(!first_time_s || !last_time_s || !update_every_s)
1092
- continue;
1093
-
1094
- if(!common_first_time_s)
1095
- common_first_time_s = first_time_s;
1096
- else
1097
- common_first_time_s = MIN(first_time_s, common_first_time_s);
1098
-
1099
- if(!common_last_time_s)
1100
- common_last_time_s = last_time_s;
1101
- else
1102
- common_last_time_s = MAX(last_time_s, common_last_time_s);
1103
-
1104
- if(!common_update_every_s)
1105
- common_update_every_s = update_every_s;
1106
- else
1107
- common_update_every_s = MIN(update_every_s, common_update_every_s);
1108
- }
1109
-
1110
- weight[tier] = query_plan_points_coverage_weight(common_first_time_s, common_last_time_s, common_update_every_s, after_wanted, before_wanted, points_wanted, tier);
1111
- }
1112
-
1113
- size_t best_tier = 0;
1114
- for(size_t tier = 1; tier < nd_profile.storage_tiers; tier++) {
1115
- if(weight[tier] >= weight[best_tier])
1116
- best_tier = tier;
1117
- }
1118
-
1119
- if(weight[best_tier] == -LONG_MAX)
1120
- best_tier = 0;
1121
-
1122
- return best_tier;
1123
-}
1124
-
1125
-static time_t rrdset_find_natural_update_every_for_timeframe(QUERY_TARGET *qt, time_t after_wanted, time_t before_wanted, size_t points_wanted, RRDR_OPTIONS options, size_t tier) {
1126
- size_t best_tier;
1127
- if((options & RRDR_OPTION_SELECTED_TIER) && tier < nd_profile.storage_tiers)
1128
- best_tier = tier;
1129
- else
1130
- best_tier = rrddim_find_best_tier_for_timeframe(qt, after_wanted, before_wanted, points_wanted);
1131
-
1132
- // find the db minimum update every for this tier for all metrics
1133
- time_t common_update_every_s = nd_profile.update_every;
1134
- for(size_t i = 0, used = qt->query.used; i < used ; i++) {
1135
- QUERY_METRIC *qm = query_metric(qt, i);
1136
-
1137
- time_t update_every_s = qm->tiers[best_tier].db_update_every_s;
1138
-
1139
- if(!i)
1140
- common_update_every_s = update_every_s;
1141
- else
1142
- common_update_every_s = MIN(update_every_s, common_update_every_s);
1143
- }
1144
-
1145
- return common_update_every_s;
1146
-}
1147
-
1148
-// ----------------------------------------------------------------------------
1149
-// query ops
1150
-
1151
-typedef struct query_point {
1152
- STORAGE_POINT sp;
1153
- NETDATA_DOUBLE value;
1154
- bool added;
1155
-#ifdef NETDATA_INTERNAL_CHECKS
1156
- size_t id;
1157
-#endif
1158
-} QUERY_POINT;
1159
-
1160
-QUERY_POINT QUERY_POINT_EMPTY = {
1161
- .sp = STORAGE_POINT_UNSET,
1162
- .value = NAN,
1163
- .added = false,
1164
-#ifdef NETDATA_INTERNAL_CHECKS
1165
- .id = 0,
1166
-#endif
1167
-};
1168
-
1169
-#ifdef NETDATA_INTERNAL_CHECKS
1170
-#define query_point_set_id(point, point_id) (point).id = point_id
1171
-#else
1172
-#define query_point_set_id(point, point_id) debug_dummy()
1173
-#endif
1174
-
1175
-typedef struct query_engine_ops {
1176
- // configuration
1177
- RRDR *r;
1178
- QUERY_METRIC *qm;
1179
- time_t view_update_every;
1180
- time_t query_granularity;
1181
- TIER_QUERY_FETCH tier_query_fetch;
1182
-
1183
- // query planer
1184
- size_t current_plan;
1185
- time_t current_plan_expire_time;
1186
- time_t plan_expanded_after;
1187
- time_t plan_expanded_before;
1188
-
1189
- // storage queries
1190
- size_t tier;
1191
- struct query_metric_tier *tier_ptr;
1192
- struct storage_engine_query_handle *seqh;
1193
-
1194
- // aggregating points over time
1195
- size_t group_points_non_zero;
1196
- size_t group_points_added;
1197
- STORAGE_POINT group_point; // aggregates min, max, sum, count, anomaly count for each group point
1198
- STORAGE_POINT query_point; // aggregates min, max, sum, count, anomaly count across the whole query
1199
- RRDR_VALUE_FLAGS group_value_flags;
1200
-
1201
- // statistics
1202
- size_t db_total_points_read;
1203
- size_t db_points_read_per_tier[RRD_STORAGE_TIERS];
1204
-
1205
- struct {
1206
- time_t expanded_after;
1207
- time_t expanded_before;
1208
- struct storage_engine_query_handle handle;
1209
- bool initialized;
1210
- bool finalized;
1211
- } plans[QUERY_PLANS_MAX];
1212
-
1213
- struct query_engine_ops *next;
1214
-} QUERY_ENGINE_OPS;
1215
-
1216
-
1217
-// ----------------------------------------------------------------------------
1218
-// query planer
1219
-
1220
-#define query_plan_should_switch_plan(ops, now) ((now) >= (ops)->current_plan_expire_time)
1221
-
1222
-static size_t query_planer_expand_duration_in_points(time_t this_update_every, time_t next_update_every) {
1223
-
1224
- time_t delta = this_update_every - next_update_every;
1225
- if(delta < 0) delta = -delta;
1226
-
1227
- size_t points;
1228
- if(delta < this_update_every * POINTS_TO_EXPAND_QUERY)
1229
- points = POINTS_TO_EXPAND_QUERY;
1230
- else
1231
- points = (delta + this_update_every - 1) / this_update_every;
1232
-
1233
- return points;
1234
-}
1235
-
1236
-static void query_planer_initialize_plans(QUERY_ENGINE_OPS *ops) {
1237
- QUERY_METRIC *qm = ops->qm;
1238
-
1239
- for(size_t p = 0; p < qm->plan.used ; p++) {
1240
- size_t tier = qm->plan.array[p].tier;
1241
- time_t update_every = qm->tiers[tier].db_update_every_s;
1242
-
1243
- size_t points_to_add_to_after;
1244
- if(p > 0) {
1245
- // there is another plan before to this
1246
-
1247
- size_t tier0 = qm->plan.array[p - 1].tier;
1248
- time_t update_every0 = qm->tiers[tier0].db_update_every_s;
1249
-
1250
- points_to_add_to_after = query_planer_expand_duration_in_points(update_every, update_every0);
1251
- }
1252
- else
1253
- points_to_add_to_after = (tier == 0) ? 0 : POINTS_TO_EXPAND_QUERY;
1254
-
1255
- size_t points_to_add_to_before;
1256
- if(p + 1 < qm->plan.used) {
1257
- // there is another plan after to this
1258
-
1259
- size_t tier1 = qm->plan.array[p+1].tier;
1260
- time_t update_every1 = qm->tiers[tier1].db_update_every_s;
1261
-
1262
- points_to_add_to_before = query_planer_expand_duration_in_points(update_every, update_every1);
1263
- }
1264
- else
1265
- points_to_add_to_before = POINTS_TO_EXPAND_QUERY;
1266
-
1267
- time_t after = qm->plan.array[p].after - (time_t)(update_every * points_to_add_to_after);
1268
- time_t before = qm->plan.array[p].before + (time_t)(update_every * points_to_add_to_before);
1269
-
1270
- ops->plans[p].expanded_after = after;
1271
- ops->plans[p].expanded_before = before;
1272
-
1273
- ops->r->internal.qt->db.tiers[tier].queries++;
1274
-
1275
- struct query_metric_tier *tier_ptr = &qm->tiers[tier];
1276
- STORAGE_ENGINE *eng = query_metric_storage_engine(ops->r->internal.qt, qm, tier);
1277
- storage_engine_query_init(eng->seb, tier_ptr->smh, &ops->plans[p].handle,
1278
- after, before, ops->r->internal.qt->request.priority);
1279
-
1280
- ops->plans[p].initialized = true;
1281
- ops->plans[p].finalized = false;
1282
- }
1283
-}
1284
-
1285
-static void query_planer_finalize_plan(QUERY_ENGINE_OPS *ops, size_t plan_id) {
1286
- // QUERY_METRIC *qm = ops->qm;
1287
-
1288
- if(ops->plans[plan_id].initialized && !ops->plans[plan_id].finalized) {
1289
- storage_engine_query_finalize(&ops->plans[plan_id].handle);
1290
- ops->plans[plan_id].initialized = false;
1291
- ops->plans[plan_id].finalized = true;
1292
- }
1293
-}
1294
-
1295
-static void query_planer_finalize_remaining_plans(QUERY_ENGINE_OPS *ops) {
1296
- QUERY_METRIC *qm = ops->qm;
1297
-
1298
- for(size_t p = 0; p < qm->plan.used ; p++)
1299
- query_planer_finalize_plan(ops, p);
1300
-}
1301
-
1302
-static void query_planer_activate_plan(QUERY_ENGINE_OPS *ops, size_t plan_id, time_t overwrite_after __maybe_unused) {
1303
- QUERY_METRIC *qm = ops->qm;
1304
-
1305
- internal_fatal(plan_id >= qm->plan.used, "QUERY: invalid plan_id given");
1306
- internal_fatal(!ops->plans[plan_id].initialized, "QUERY: plan has not been initialized");
1307
- internal_fatal(ops->plans[plan_id].finalized, "QUERY: plan has been finalized");
1308
-
1309
- internal_fatal(qm->plan.array[plan_id].after > qm->plan.array[plan_id].before, "QUERY: flipped after/before");
1310
-
1311
- ops->tier = qm->plan.array[plan_id].tier;
1312
- ops->tier_ptr = &qm->tiers[ops->tier];
1313
- ops->seqh = &ops->plans[plan_id].handle;
1314
- ops->current_plan = plan_id;
1315
-
1316
- if(plan_id + 1 < qm->plan.used && qm->plan.array[plan_id + 1].after < qm->plan.array[plan_id].before)
1317
- ops->current_plan_expire_time = qm->plan.array[plan_id + 1].after;
1318
- else
1319
- ops->current_plan_expire_time = qm->plan.array[plan_id].before;
1320
-
1321
- ops->plan_expanded_after = ops->plans[plan_id].expanded_after;
1322
- ops->plan_expanded_before = ops->plans[plan_id].expanded_before;
1323
-}
1324
-
1325
-static bool query_planer_next_plan(QUERY_ENGINE_OPS *ops, time_t now, time_t last_point_end_time) {
1326
- QUERY_METRIC *qm = ops->qm;
1327
-
1328
- size_t old_plan = ops->current_plan;
1329
-
1330
- time_t next_plan_before_time;
1331
- do {
1332
- ops->current_plan++;
1333
-
1334
- if (ops->current_plan >= qm->plan.used) {
1335
- ops->current_plan = old_plan;
1336
- ops->current_plan_expire_time = ops->r->internal.qt->window.before;
1337
- // let the query run with current plan
1338
- // we will not switch it
1339
- return false;
1340
- }
1341
-
1342
- next_plan_before_time = qm->plan.array[ops->current_plan].before;
1343
- } while(now >= next_plan_before_time || last_point_end_time >= next_plan_before_time);
1344
-
1345
- if(!query_metric_is_valid_tier(qm, qm->plan.array[ops->current_plan].tier)) {
1346
- ops->current_plan = old_plan;
1347
- ops->current_plan_expire_time = ops->r->internal.qt->window.before;
1348
- return false;
1349
- }
1350
-
1351
- query_planer_finalize_plan(ops, old_plan);
1352
- query_planer_activate_plan(ops, ops->current_plan, MIN(now, last_point_end_time));
1353
- return true;
1354
-}
1355
-
1356
-static int compare_query_plan_entries_on_start_time(const void *a, const void *b) {
1357
- QUERY_PLAN_ENTRY *p1 = (QUERY_PLAN_ENTRY *)a;
1358
- QUERY_PLAN_ENTRY *p2 = (QUERY_PLAN_ENTRY *)b;
1359
- return (p1->after < p2->after)?-1:1;
1360
-}
1361
-
1362
-static bool query_plan(QUERY_ENGINE_OPS *ops, time_t after_wanted, time_t before_wanted, size_t points_wanted) {
1363
- QUERY_METRIC *qm = ops->qm;
1364
-
1365
- // put our selected tier as the first plan
1366
- size_t selected_tier;
1367
- bool switch_tiers = true;
1368
-
1369
- if((ops->r->internal.qt->window.options & RRDR_OPTION_SELECTED_TIER)
1370
- && ops->r->internal.qt->window.tier < nd_profile.storage_tiers && query_metric_is_valid_tier(qm, ops->r->internal.qt->window.tier)) {
1371
- selected_tier = ops->r->internal.qt->window.tier;
1372
- switch_tiers = false;
1373
- }
1374
- else {
1375
- selected_tier = query_metric_best_tier_for_timeframe(qm, after_wanted, before_wanted, points_wanted);
1376
-
1377
- if(!query_metric_is_valid_tier(qm, selected_tier))
1378
- return false;
1379
- }
1380
-
1381
- if(qm->tiers[selected_tier].db_first_time_s > before_wanted ||
1382
- qm->tiers[selected_tier].db_last_time_s < after_wanted) {
1383
- // we don't have any data to satisfy this query
1384
- return false;
1385
- }
1386
-
1387
- qm->plan.used = 1;
1388
- qm->plan.array[0].tier = selected_tier;
1389
- qm->plan.array[0].after = (qm->tiers[selected_tier].db_first_time_s < after_wanted) ? after_wanted : qm->tiers[selected_tier].db_first_time_s;
1390
- qm->plan.array[0].before = (qm->tiers[selected_tier].db_last_time_s > before_wanted) ? before_wanted : qm->tiers[selected_tier].db_last_time_s;
1391
-
1392
- if(switch_tiers) {
1393
- // the selected tier
1394
- time_t selected_tier_first_time_s = qm->plan.array[0].after;
1395
- time_t selected_tier_last_time_s = qm->plan.array[0].before;
1396
-
1397
- // check if our selected tier can start the query
1398
- if (selected_tier_first_time_s > after_wanted) {
1399
- // we need some help from other tiers
1400
- for (size_t tr = (int)selected_tier + 1; tr < nd_profile.storage_tiers && qm->plan.used < QUERY_PLANS_MAX ; tr++) {
1401
- if(!query_metric_is_valid_tier(qm, tr))
1402
- continue;
1403
-
1404
- // find the first time of this tier
1405
- time_t tier_first_time_s = qm->tiers[tr].db_first_time_s;
1406
- time_t tier_last_time_s = qm->tiers[tr].db_last_time_s;
1407
-
1408
- // can it help?
1409
- if (tier_first_time_s < selected_tier_first_time_s && tier_first_time_s <= before_wanted && tier_last_time_s >= after_wanted) {
1410
- // it can help us add detail at the beginning of the query
1411
- QUERY_PLAN_ENTRY t = {
1412
- .tier = tr,
1413
- .after = (tier_first_time_s < after_wanted) ? after_wanted : tier_first_time_s,
1414
- .before = selected_tier_first_time_s,
1415
- };
1416
- ops->plans[qm->plan.used].initialized = false;
1417
- ops->plans[qm->plan.used].finalized = false;
1418
- qm->plan.array[qm->plan.used++] = t;
1419
-
1420
- internal_fatal(!t.after || !t.before, "QUERY: invalid plan selected");
1421
-
1422
- // prepare for the tier
1423
- selected_tier_first_time_s = t.after;
1424
-
1425
- if (t.after <= after_wanted)
1426
- break;
1427
- }
1428
- }
1429
- }
1430
-
1431
- // check if our selected tier can finish the query
1432
- if (selected_tier_last_time_s < before_wanted) {
1433
- // we need some help from other tiers
1434
- for (int tr = (int)selected_tier - 1; tr >= 0 && qm->plan.used < QUERY_PLANS_MAX ; tr--) {
1435
- if(!query_metric_is_valid_tier(qm, tr))
1436
- continue;
1437
-
1438
- // find the last time of this tier
1439
- time_t tier_first_time_s = qm->tiers[tr].db_first_time_s;
1440
- time_t tier_last_time_s = qm->tiers[tr].db_last_time_s;
1441
-
1442
- //buffer_sprintf(wb, ": EVAL BEFORE tier %d, %ld", tier, last_time_s);
1443
-
1444
- // can it help?
1445
- if (tier_last_time_s > selected_tier_last_time_s && tier_first_time_s <= before_wanted && tier_last_time_s >= after_wanted) {
1446
- // it can help us add detail at the end of the query
1447
- QUERY_PLAN_ENTRY t = {
1448
- .tier = tr,
1449
- .after = selected_tier_last_time_s,
1450
- .before = (tier_last_time_s > before_wanted) ? before_wanted : tier_last_time_s,
1451
- };
1452
- ops->plans[qm->plan.used].initialized = false;
1453
- ops->plans[qm->plan.used].finalized = false;
1454
- qm->plan.array[qm->plan.used++] = t;
1455
-
1456
- // prepare for the tier
1457
- selected_tier_last_time_s = t.before;
1458
-
1459
- internal_fatal(!t.after || !t.before, "QUERY: invalid plan selected");
1460
-
1461
- if (t.before >= before_wanted)
1462
- break;
1463
- }
1464
- }
1465
- }
1466
- }
1467
-
1468
- // sort the query plan
1469
- if(qm->plan.used > 1)
1470
- qsort(&qm->plan.array, qm->plan.used, sizeof(QUERY_PLAN_ENTRY), compare_query_plan_entries_on_start_time);
1471
-
1472
- if(!query_metric_is_valid_tier(qm, qm->plan.array[0].tier))
1473
- return false;
1474
-
1475
-#ifdef NETDATA_INTERNAL_CHECKS
1476
- for(size_t p = 0; p < qm->plan.used ;p++) {
1477
- internal_fatal(qm->plan.array[p].after > qm->plan.array[p].before, "QUERY: flipped after/before");
1478
- internal_fatal(qm->plan.array[p].after < after_wanted, "QUERY: too small plan first time");
1479
- internal_fatal(qm->plan.array[p].before > before_wanted, "QUERY: too big plan last time");
1480
- }
1481
-#endif
1482
-
1483
- query_planer_initialize_plans(ops);
1484
- query_planer_activate_plan(ops, 0, 0);
1485
-
1486
- return true;
1487
-}
1488
-
1489
-
1490
-// ----------------------------------------------------------------------------
1491
-// dimension level query engine
1492
-
1493
-#define query_interpolate_point(this_point, last_point, now) do { \
1494
- if(likely( \
1495
- /* the point to interpolate is more than 1s wide */ \
1496
- (this_point).sp.end_time_s - (this_point).sp.start_time_s > 1 \
1497
- \
1498
- /* the two points are exactly next to each other */ \
1499
- && (last_point).sp.end_time_s == (this_point).sp.start_time_s \
1500
- \
1501
- /* both points are valid numbers */ \
1502
- && netdata_double_isnumber((this_point).value) \
1503
- && netdata_double_isnumber((last_point).value) \
1504
- \
1505
- )) { \
1506
- (this_point).value = (last_point).value + ((this_point).value - (last_point).value) * (1.0 - (NETDATA_DOUBLE)((this_point).sp.end_time_s - (now)) / (NETDATA_DOUBLE)((this_point).sp.end_time_s - (this_point).sp.start_time_s)); \
1507
- (this_point).sp.end_time_s = now; \
1508
- } \
1509
-} while(0)
1510
-
1511
-#define query_add_point_to_group(r, point, ops, add_flush) do { \
1512
- if(likely(netdata_double_isnumber((point).value))) { \
1513
- if(likely(fpclassify((point).value) != FP_ZERO)) \
1514
- (ops)->group_points_non_zero++; \
1515
- \
1516
- if(unlikely((point).sp.flags & SN_FLAG_RESET)) \
1517
- (ops)->group_value_flags |= RRDR_VALUE_RESET; \
1518
- \
1519
- time_grouping_add(r, (point).value, add_flush); \
1520
- \
1521
- storage_point_merge_to((ops)->group_point, (point).sp); \
1522
- if(!(point).added) \
1523
- storage_point_merge_to((ops)->query_point, (point).sp); \
1524
- } \
1525
- \
1526
- (ops)->group_points_added++; \
1527
-} while(0)
1528
-
1529
-static __thread QUERY_ENGINE_OPS *released_ops = NULL;
1530
-
1531
-static void rrd2rrdr_query_ops_freeall(RRDR *r __maybe_unused) {
1532
- while(released_ops) {
1533
- QUERY_ENGINE_OPS *ops = released_ops;
1534
- released_ops = ops->next;
1535
-
1536
- onewayalloc_freez(r->internal.owa, ops);
1537
- }
1538
-}
1539
-
1540
-static void rrd2rrdr_query_ops_release(QUERY_ENGINE_OPS *ops) {
1541
- if(!ops) return;
1542
-
1543
- ops->next = released_ops;
1544
- released_ops = ops;
1545
-}
1546
-
1547
-static QUERY_ENGINE_OPS *rrd2rrdr_query_ops_get(RRDR *r) {
1548
- QUERY_ENGINE_OPS *ops;
1549
- if(released_ops) {
1550
- ops = released_ops;
1551
- released_ops = ops->next;
1552
- }
1553
- else {
1554
- ops = onewayalloc_mallocz(r->internal.owa, sizeof(QUERY_ENGINE_OPS));
1555
- }
1556
-
1557
- memset(ops, 0, sizeof(*ops));
1558
- return ops;
1559
-}
1560
-
1561
-static QUERY_ENGINE_OPS *rrd2rrdr_query_ops_prep(RRDR *r, size_t query_metric_id) {
1562
- QUERY_TARGET *qt = r->internal.qt;
1563
-
1564
- QUERY_ENGINE_OPS *ops = rrd2rrdr_query_ops_get(r);
1565
- *ops = (QUERY_ENGINE_OPS) {
1566
- .r = r,
1567
- .qm = query_metric(qt, query_metric_id),
1568
- .tier_query_fetch = r->time_grouping.tier_query_fetch,
1569
- .view_update_every = r->view.update_every,
1570
- .query_granularity = (time_t)(r->view.update_every / r->view.group),
1571
- .group_value_flags = RRDR_VALUE_NOTHING,
1572
- };
1573
-
1574
- if(!query_plan(ops, qt->window.after, qt->window.before, qt->window.points)) {
1575
- rrd2rrdr_query_ops_release(ops);
1576
- return NULL;
1577
- }
1578
-
1579
- return ops;
1580
-}
1581
-
1582
-NOT_INLINE_HOT static void rrd2rrdr_query_execute(RRDR *r, size_t dim_id_in_rrdr, QUERY_ENGINE_OPS *ops) {
1583
- QUERY_TARGET *qt = r->internal.qt;
1584
- QUERY_METRIC *qm = ops->qm;
1585
-
1586
- const RRDR_TIME_GROUPING add_flush = r->time_grouping.add_flush;
1587
-
1588
- ops->group_point = STORAGE_POINT_UNSET;
1589
- ops->query_point = STORAGE_POINT_UNSET;
1590
-
1591
- RRDR_OPTIONS options = qt->window.options;
1592
- size_t points_wanted = qt->window.points;
1593
- time_t after_wanted = qt->window.after;
1594
- time_t before_wanted = qt->window.before; (void)before_wanted;
1595
-
1596
-// bool debug_this = false;
1597
-// if(strcmp("user", string2str(rd->id)) == 0 && strcmp("system.cpu", string2str(rd->rrdset->id)) == 0)
1598
-// debug_this = true;
1599
-
1600
- size_t points_added = 0;
1601
-
1602
- long rrdr_line = -1;
1603
- bool use_anomaly_bit_as_value = (r->internal.qt->window.options & RRDR_OPTION_ANOMALY_BIT) ? true : false;
1604
-
1605
- NETDATA_DOUBLE min = r->view.min, max = r->view.max;
1606
-
1607
- QUERY_POINT last2_point = QUERY_POINT_EMPTY;
1608
- QUERY_POINT last1_point = QUERY_POINT_EMPTY;
1609
- QUERY_POINT new_point = QUERY_POINT_EMPTY;
1610
-
1611
- // ONE POINT READ-AHEAD
1612
- // when we switch plans, we read-ahead a point from the next plan
1613
- // to join them smoothly at the exact time the next plan begins
1614
- STORAGE_POINT next1_point = STORAGE_POINT_UNSET;
1615
-
1616
- time_t now_start_time = after_wanted - ops->query_granularity;
1617
- time_t now_end_time = after_wanted + ops->view_update_every - ops->query_granularity;
1618
-
1619
- size_t db_points_read_since_plan_switch = 0; (void)db_points_read_since_plan_switch;
1620
- size_t query_is_finished_counter = 0;
1621
-
1622
- // The main loop, based on the query granularity we need
1623
- for( ; points_added < points_wanted && query_is_finished_counter <= 10 ;
1624
- now_start_time = now_end_time, now_end_time += ops->view_update_every) {
1625
-
1626
- if(unlikely(query_plan_should_switch_plan(ops, now_end_time))) {
1627
- query_planer_next_plan(ops, now_end_time, new_point.sp.end_time_s);
1628
- db_points_read_since_plan_switch = 0;
1629
- }
1630
-
1631
- // read all the points of the db, prior to the time we need (now_end_time)
1632
-
1633
- size_t count_same_end_time = 0;
1634
- while(count_same_end_time < 100) {
1635
- if(likely(count_same_end_time == 0)) {
1636
- last2_point = last1_point;
1637
- last1_point = new_point;
1638
- }
1639
-
1640
- if(unlikely(storage_engine_query_is_finished(ops->seqh))) {
1641
- query_is_finished_counter++;
1642
-
1643
- if(count_same_end_time != 0) {
1644
- last2_point = last1_point;
1645
- last1_point = new_point;
1646
- }
1647
- new_point = QUERY_POINT_EMPTY;
1648
- new_point.sp.start_time_s = last1_point.sp.end_time_s;
1649
- new_point.sp.end_time_s = now_end_time;
1650
-//
1651
-// if(debug_this) netdata_log_info("QUERY: is finished() returned true");
1652
-//
1653
- break;
1654
- }
1655
- else
1656
- query_is_finished_counter = 0;
1657
-
1658
- // fetch the new point
1659
- {
1660
- STORAGE_POINT sp;
1661
- if(likely(storage_point_is_unset(next1_point))) {
1662
- db_points_read_since_plan_switch++;
1663
- sp = storage_engine_query_next_metric(ops->seqh);
1664
- ops->db_points_read_per_tier[ops->tier]++;
1665
- ops->db_total_points_read++;
1666
-
1667
- if(unlikely(options & RRDR_OPTION_ABSOLUTE))
1668
- storage_point_make_positive(sp);
1669
- }
1670
- else {
1671
- // ONE POINT READ-AHEAD
1672
- sp = next1_point;
1673
- storage_point_unset(next1_point);
1674
- db_points_read_since_plan_switch = 1;
1675
- }
1676
-
1677
- // ONE POINT READ-AHEAD
1678
- if(unlikely(query_plan_should_switch_plan(ops, sp.end_time_s) &&
1679
- query_planer_next_plan(ops, now_end_time, new_point.sp.end_time_s))) {
1680
-
1681
- // The end time of the current point, crosses our plans (tiers)
1682
- // so, we switched plan (tier)
1683
- //
1684
- // There are 2 cases now:
1685
- //
1686
- // A. the entire point of the previous plan is to the future of point from the next plan
1687
- // B. part of the point of the previous plan overlaps with the point from the next plan
1688
-
1689
- STORAGE_POINT sp2 = storage_engine_query_next_metric(ops->seqh);
1690
- ops->db_points_read_per_tier[ops->tier]++;
1691
- ops->db_total_points_read++;
1692
-
1693
- if(unlikely(options & RRDR_OPTION_ABSOLUTE))
1694
- storage_point_make_positive(sp);
1695
-
1696
- if(sp.start_time_s > sp2.start_time_s)
1697
- // the point from the previous plan is useless
1698
- sp = sp2;
1699
- else
1700
- // let the query run from the previous plan
1701
- // but setting this will also cut off the interpolation
1702
- // of the point from the previous plan
1703
- next1_point = sp2;
1704
- }
1705
-
1706
- new_point.sp = sp;
1707
- new_point.added = false;
1708
- query_point_set_id(new_point, ops->db_total_points_read);
1709
-
1710
-// if(debug_this)
1711
-// netdata_log_info("QUERY: got point %zu, from time %ld to %ld // now from %ld to %ld // query from %ld to %ld",
1712
-// new_point.id, new_point.start_time, new_point.end_time, now_start_time, now_end_time, after_wanted, before_wanted);
1713
-//
1714
- // get the right value from the point we got
1715
- if(likely(!storage_point_is_unset(sp) && !storage_point_is_gap(sp))) {
1716
-
1717
- if(unlikely(use_anomaly_bit_as_value))
1718
- new_point.value = storage_point_anomaly_rate(new_point.sp);
1719
-
1720
- else {
1721
- switch (ops->tier_query_fetch) {
1722
- default:
1723
- case TIER_QUERY_FETCH_AVERAGE:
1724
- new_point.value = sp.sum / (NETDATA_DOUBLE)sp.count;
1725
- break;
1726
-
1727
- case TIER_QUERY_FETCH_MIN:
1728
- new_point.value = sp.min;
1729
- break;
1730
-
1731
- case TIER_QUERY_FETCH_MAX:
1732
- new_point.value = sp.max;
1733
- break;
1734
-
1735
- case TIER_QUERY_FETCH_SUM:
1736
- new_point.value = sp.sum;
1737
- break;
1738
- }
1739
- }
1740
- }
1741
- else
1742
- new_point.value = NAN;
1743
- }
1744
-
1745
- // check if the db is giving us zero duration points
1746
- if(unlikely(db_points_read_since_plan_switch > 1 &&
1747
- new_point.sp.start_time_s == new_point.sp.end_time_s)) {
1748
-
1749
- internal_error(true, "QUERY: '%s', dimension '%s' next_metric() returned "
1750
- "point %zu from %ld to %ld, that are both equal",
1751
- qt->id, query_metric_id(qt, qm),
1752
- new_point.id, new_point.sp.start_time_s, new_point.sp.end_time_s);
1753
-
1754
- new_point.sp.start_time_s = new_point.sp.end_time_s - ops->tier_ptr->db_update_every_s;
1755
- }
1756
-
1757
- // check if the db is advancing the query
1758
- if(unlikely(db_points_read_since_plan_switch > 1 &&
1759
- new_point.sp.end_time_s <= last1_point.sp.end_time_s)) {
1760
-
1761
- internal_error(true,
1762
- "QUERY: '%s', dimension '%s' next_metric() returned "
1763
- "point %zu from %ld to %ld, before the "
1764
- "last point %zu from %ld to %ld, "
1765
- "now is %ld to %ld",
1766
- qt->id, query_metric_id(qt, qm),
1767
- new_point.id, new_point.sp.start_time_s, new_point.sp.end_time_s,
1768
- last1_point.id, last1_point.sp.start_time_s, last1_point.sp.end_time_s,
1769
- now_start_time, now_end_time);
1770
-
1771
- count_same_end_time++;
1772
- continue;
1773
- }
1774
- count_same_end_time = 0;
1775
-
1776
- // decide how to use this point
1777
- if(likely(new_point.sp.end_time_s < now_end_time)) { // likely to favor tier0
1778
- // this db point ends before our now_end_time
1779
-
1780
- if(likely(new_point.sp.end_time_s >= now_start_time)) { // likely to favor tier0
1781
- // this db point ends after our now_start time
1782
-
1783
- query_add_point_to_group(r, new_point, ops, add_flush);
1784
- new_point.added = true;
1785
- }
1786
- else {
1787
- // we don't need this db point
1788
- // it is totally outside our current time-frame
1789
-
1790
- // this is desirable for the first point of the query
1791
- // because it allows us to interpolate the next point
1792
- // at exactly the time we will want
1793
-
1794
- // we only log if this is not point 1
1795
- internal_error(new_point.sp.end_time_s < ops->plan_expanded_after &&
1796
- db_points_read_since_plan_switch > 1,
1797
- "QUERY: '%s', dimension '%s' next_metric() "
1798
- "returned point %zu from %ld time %ld, "
1799
- "which is entirely before our current timeframe %ld to %ld "
1800
- "(and before the entire query, after %ld, before %ld)",
1801
- qt->id, query_metric_id(qt, qm),
1802
- new_point.id, new_point.sp.start_time_s, new_point.sp.end_time_s,
1803
- now_start_time, now_end_time,
1804
- ops->plan_expanded_after, ops->plan_expanded_before);
1805
- }
1806
-
1807
- }
1808
- else {
1809
- // the point ends in the future
1810
- // so, we will interpolate it below, at the inner loop
1811
- break;
1812
- }
1813
- }
1814
-
1815
- if(unlikely(count_same_end_time)) {
1816
- internal_error(true,
1817
- "QUERY: '%s', dimension '%s', the database does not advance the query,"
1818
- " it returned an end time less or equal to the end time of the last "
1819
- "point we got %ld, %zu times",
1820
- qt->id, query_metric_id(qt, qm),
1821
- last1_point.sp.end_time_s, count_same_end_time);
1822
-
1823
- if(unlikely(new_point.sp.end_time_s <= last1_point.sp.end_time_s))
1824
- new_point.sp.end_time_s = now_end_time;
1825
- }
1826
-
1827
- time_t stop_time = new_point.sp.end_time_s;
1828
- if(unlikely(!storage_point_is_unset(next1_point) && next1_point.start_time_s >= now_end_time)) {
1829
- // ONE POINT READ-AHEAD
1830
- // the point crosses the start time of the
1831
- // read ahead storage point we have read
1832
- stop_time = next1_point.start_time_s;
1833
- }
1834
-
1835
- // the inner loop
1836
- // we have 3 points in memory: last2, last1, new
1837
- // we select the one to use based on their timestamps
1838
-
1839
- internal_fatal(now_end_time > stop_time || points_added >= points_wanted,
1840
- "QUERY: first part of query provides invalid point to interpolate (now_end_time %ld, stop_time %ld",
1841
- now_end_time, stop_time);
1842
-
1843
- do {
1844
- // now_start_time is wrong in this loop
1845
- // but, we don't need it
1846
-
1847
- QUERY_POINT current_point;
1848
-
1849
- if(likely(now_end_time > new_point.sp.start_time_s)) {
1850
- // it is time for our NEW point to be used
1851
- current_point = new_point;
1852
- new_point.added = true; // first copy, then set it, so that new_point will not be added again
1853
- query_interpolate_point(current_point, last1_point, now_end_time);
1854
-
1855
-// internal_error(current_point.id > 0
1856
-// && last1_point.id == 0
1857
-// && current_point.end_time > after_wanted
1858
-// && current_point.end_time > now_end_time,
1859
-// "QUERY: '%s', dimension '%s', after %ld, before %ld, view update every %ld,"
1860
-// " query granularity %ld, interpolating point %zu (from %ld to %ld) at %ld,"
1861
-// " but we could really favor by having last_point1 in this query.",
1862
-// qt->id, string2str(qm->dimension.id),
1863
-// after_wanted, before_wanted,
1864
-// ops.view_update_every, ops.query_granularity,
1865
-// current_point.id, current_point.start_time, current_point.end_time,
1866
-// now_end_time);
1867
- }
1868
- else if(likely(now_end_time <= last1_point.sp.end_time_s)) {
1869
- // our LAST point is still valid
1870
- current_point = last1_point;
1871
- last1_point.added = true; // first copy, then set it, so that last1_point will not be added again
1872
- query_interpolate_point(current_point, last2_point, now_end_time);
1873
-
1874
-// internal_error(current_point.id > 0
1875
-// && last2_point.id == 0
1876
-// && current_point.end_time > after_wanted
1877
-// && current_point.end_time > now_end_time,
1878
-// "QUERY: '%s', dimension '%s', after %ld, before %ld, view update every %ld,"
1879
-// " query granularity %ld, interpolating point %zu (from %ld to %ld) at %ld,"
1880
-// " but we could really favor by having last_point2 in this query.",
1881
-// qt->id, string2str(qm->dimension.id),
1882
-// after_wanted, before_wanted, ops.view_update_every, ops.query_granularity,
1883
-// current_point.id, current_point.start_time, current_point.end_time,
1884
-// now_end_time);
1885
- }
1886
- else {
1887
- // a GAP, we don't have a value this time
1888
- current_point = QUERY_POINT_EMPTY;
1889
- }
1890
-
1891
- query_add_point_to_group(r, current_point, ops, add_flush);
1892
-
1893
- rrdr_line = rrdr_line_init(r, now_end_time, rrdr_line);
1894
- size_t rrdr_o_v_index = rrdr_line * r->d + dim_id_in_rrdr;
1895
-
1896
- // find the place to store our values
1897
- RRDR_VALUE_FLAGS *rrdr_value_options_ptr = &r->o[rrdr_o_v_index];
1898
-
1899
- // update the dimension options
1900
- if(likely(ops->group_points_non_zero))
1901
- r->od[dim_id_in_rrdr] |= RRDR_DIMENSION_NONZERO;
1902
-
1903
- // store the specific point options
1904
- *rrdr_value_options_ptr = ops->group_value_flags;
1905
-
1906
- // store the group value
1907
- NETDATA_DOUBLE group_value = time_grouping_flush(r, rrdr_value_options_ptr, add_flush);
1908
- r->v[rrdr_o_v_index] = group_value;
1909
-
1910
- r->ar[rrdr_o_v_index] = storage_point_anomaly_rate(ops->group_point);
1911
-
1912
- if(likely(points_added || r->internal.queries_count)) {
1913
- // find the min/max across all dimensions
1914
-
1915
- if(unlikely(group_value < min)) min = group_value;
1916
- if(unlikely(group_value > max)) max = group_value;
1917
-
1918
- }
1919
- else {
1920
- // runs only when r->internal.queries_count == 0 && points_added == 0
1921
- // so, on the first point added for the query.
1922
- min = max = group_value;
1923
- }
1924
-
1925
- points_added++;
1926
- ops->group_points_added = 0;
1927
- ops->group_value_flags = RRDR_VALUE_NOTHING;
1928
- ops->group_points_non_zero = 0;
1929
- ops->group_point = STORAGE_POINT_UNSET;
1930
-
1931
- now_end_time += ops->view_update_every;
1932
- } while(now_end_time <= stop_time && points_added < points_wanted);
1933
-
1934
- // the loop above increased "now" by ops->view_update_every,
1935
- // but the main loop will increase it too,
1936
- // so, let's undo the last iteration of this loop
1937
- now_end_time -= ops->view_update_every;
1938
- }
1939
- query_planer_finalize_remaining_plans(ops);
1940
-
1941
- qm->query_points = ops->query_point;
1942
-
1943
- // fill the rest of the points with empty values
1944
- while (points_added < points_wanted) {
1945
- rrdr_line++;
1946
- size_t rrdr_o_v_index = rrdr_line * r->d + dim_id_in_rrdr;
1947
- r->o[rrdr_o_v_index] = RRDR_VALUE_EMPTY;
1948
- r->v[rrdr_o_v_index] = 0.0;
1949
- r->ar[rrdr_o_v_index] = 0.0;
1950
- points_added++;
1951
- }
1952
-
1953
- r->internal.queries_count++;
1954
- r->view.min = min;
1955
- r->view.max = max;
1956
-
1957
- r->stats.result_points_generated += points_added;
1958
- r->stats.db_points_read += ops->db_total_points_read;
1959
- for(size_t tr = 0; tr < nd_profile.storage_tiers; tr++)
1960
- qt->db.tiers[tr].points += ops->db_points_read_per_tier[tr];
1961
-}
1962
-
1963
-// ----------------------------------------------------------------------------
1964
-// fill RRDR for the whole chart
1965
-
1966
-#ifdef NETDATA_INTERNAL_CHECKS
1967
-static void rrd2rrdr_log_request_response_metadata(RRDR *r
1968
- , RRDR_OPTIONS options __maybe_unused
1969
- , RRDR_TIME_GROUPING group_method
1970
- , bool aligned
1971
- , size_t group
1972
- , time_t resampling_time
1973
- , size_t resampling_group
1974
- , time_t after_wanted
1975
- , time_t after_requested
1976
- , time_t before_wanted
1977
- , time_t before_requested
1978
- , size_t points_requested
1979
- , size_t points_wanted
1980
- //, size_t after_slot
1981
- //, size_t before_slot
1982
- , const char *msg
1983
- ) {
1984
-
1985
- QUERY_TARGET *qt = r->internal.qt;
1986
- time_t first_entry_s = qt->db.first_time_s;
1987
- time_t last_entry_s = qt->db.last_time_s;
1988
-
1989
- internal_error(
1990
- true,
1991
- "rrd2rrdr() on %s update every %ld with %s grouping %s (group: %zu, resampling_time: %ld, resampling_group: %zu), "
1992
- "after (got: %ld, want: %ld, req: %ld, db: %ld), "
1993
- "before (got: %ld, want: %ld, req: %ld, db: %ld), "
1994
- "duration (got: %ld, want: %ld, req: %ld, db: %ld), "
1995
- "points (got: %zu, want: %zu, req: %zu), "
1996
- "%s"
1997
- , qt->id
1998
- , qt->window.query_granularity
1999
-
2000
- // grouping
2001
- , (aligned) ? "aligned" : "unaligned"
2002
- , time_grouping_id2txt(group_method)
2003
- , group
2004
- , resampling_time
2005
- , resampling_group
2006
-
2007
- // after
2008
- , r->view.after
2009
- , after_wanted
2010
- , after_requested
2011
- , first_entry_s
2012
-
2013
- // before
2014
- , r->view.before
2015
- , before_wanted
2016
- , before_requested
2017
- , last_entry_s
2018
-
2019
- // duration
2020
- , (long)(r->view.before - r->view.after + qt->window.query_granularity)
2021
- , (long)(before_wanted - after_wanted + qt->window.query_granularity)
2022
- , (long)before_requested - after_requested
2023
- , (long)((last_entry_s - first_entry_s) + qt->window.query_granularity)
2024
-
2025
- // points
2026
- , r->rows
2027
- , points_wanted
2028
- , points_requested
2029
-
2030
- // message
2031
- , msg
2032
- );
2033
-}
2034
-#endif // NETDATA_INTERNAL_CHECKS
2035
-
2036
-// #define DEBUG_QUERY_LOGIC 1
2037
-
2038
-#ifdef DEBUG_QUERY_LOGIC
2039
-#define query_debug_log_init() BUFFER *debug_log = buffer_create(1000)
2040
-#define query_debug_log(args...) buffer_sprintf(debug_log, ##args)
2041
-#define query_debug_log_fin() { \
2042
- netdata_log_info("QUERY: '%s', after:%ld, before:%ld, duration:%ld, points:%zu, res:%ld - wanted => after:%ld, before:%ld, points:%zu, group:%zu, granularity:%ld, resgroup:%ld, resdiv:" NETDATA_DOUBLE_FORMAT_AUTO " %s", qt->id, after_requested, before_requested, before_requested - after_requested, points_requested, resampling_time_requested, after_wanted, before_wanted, points_wanted, group, query_granularity, resampling_group, resampling_divisor, buffer_tostring(debug_log)); \
2043
- buffer_free(debug_log); \
2044
- debug_log = NULL; \
2045
- }
2046
-#define query_debug_log_free() do { buffer_free(debug_log); } while(0)
2047
-#else
2048
-#define query_debug_log_init() debug_dummy()
2049
-#define query_debug_log(args...) debug_dummy()
2050
-#define query_debug_log_fin() debug_dummy()
2051
-#define query_debug_log_free() debug_dummy()
2052
-#endif
2053
-
2054
-bool query_target_calculate_window(QUERY_TARGET *qt) {
2055
- if (unlikely(!qt)) return false;
2056
-
2057
- size_t points_requested = (long)qt->request.points;
2058
- time_t after_requested = qt->request.after;
2059
- time_t before_requested = qt->request.before;
2060
- RRDR_TIME_GROUPING group_method = qt->request.time_group_method;
2061
- time_t resampling_time_requested = qt->request.resampling_time;
2062
- RRDR_OPTIONS options = qt->window.options;
2063
- size_t tier = qt->request.tier;
2064
- time_t update_every = qt->db.minimum_latest_update_every_s ? qt->db.minimum_latest_update_every_s : 1;
2065
-
2066
- // RULES
2067
- // points_requested = 0
2068
- // the user wants all the natural points the database has
2069
- //
2070
- // after_requested = 0
2071
- // the user wants to start the query from the oldest point in our database
2072
- //
2073
- // before_requested = 0
2074
- // the user wants the query to end to the latest point in our database
2075
- //
2076
- // when natural points are wanted, the query has to be aligned to the update_every
2077
- // of the database
2078
-
2079
- size_t points_wanted = points_requested;
2080
- time_t after_wanted = after_requested;
2081
- time_t before_wanted = before_requested;
2082
-
2083
- bool aligned = !(options & RRDR_OPTION_NOT_ALIGNED);
2084
- bool automatic_natural_points = (points_wanted == 0);
2085
- bool relative_period_requested = false;
2086
- bool natural_points = (options & RRDR_OPTION_NATURAL_POINTS) || automatic_natural_points;
2087
- bool before_is_aligned_to_db_end = false;
2088
-
2089
- query_debug_log_init();
2090
-
2091
- if (ABS(before_requested) <= API_RELATIVE_TIME_MAX || ABS(after_requested) <= API_RELATIVE_TIME_MAX) {
2092
- relative_period_requested = true;
2093
- natural_points = true;
2094
- options |= RRDR_OPTION_NATURAL_POINTS;
2095
- query_debug_log(":relative+natural");
2096
- }
2097
-
2098
- // if the user wants virtual points, make sure we do it
2099
- if (options & RRDR_OPTION_VIRTUAL_POINTS)
2100
- natural_points = false;
2101
-
2102
- // set the right flag about natural and virtual points
2103
- if (natural_points) {
2104
- options |= RRDR_OPTION_NATURAL_POINTS;
2105
-
2106
- if (options & RRDR_OPTION_VIRTUAL_POINTS)
2107
- options &= ~RRDR_OPTION_VIRTUAL_POINTS;
2108
- }
2109
- else {
2110
- options |= RRDR_OPTION_VIRTUAL_POINTS;
2111
-
2112
- if (options & RRDR_OPTION_NATURAL_POINTS)
2113
- options &= ~RRDR_OPTION_NATURAL_POINTS;
2114
- }
2115
-
2116
- if (after_wanted == 0 || before_wanted == 0) {
2117
- relative_period_requested = true;
2118
-
2119
- time_t first_entry_s = qt->db.first_time_s;
2120
- time_t last_entry_s = qt->db.last_time_s;
2121
-
2122
- if (first_entry_s == 0 || last_entry_s == 0) {
2123
- internal_error(true, "QUERY: no data detected on query '%s' (db first_entry_t = %ld, last_entry_t = %ld)", qt->id, first_entry_s, last_entry_s);
2124
- after_wanted = qt->window.after;
2125
- before_wanted = qt->window.before;
2126
-
2127
- if(after_wanted == before_wanted)
2128
- after_wanted = before_wanted - update_every;
2129
-
2130
- if (points_wanted == 0) {
2131
- points_wanted = (before_wanted - after_wanted) / update_every;
2132
- query_debug_log(":zero points_wanted %zu", points_wanted);
2133
- }
2134
- }
2135
- else {
2136
- query_debug_log(":first_entry_t %ld, last_entry_t %ld", first_entry_s, last_entry_s);
2137
-
2138
- if (after_wanted == 0) {
2139
- after_wanted = first_entry_s;
2140
- query_debug_log(":zero after_wanted %ld", after_wanted);
2141
- }
2142
-
2143
- if (before_wanted == 0) {
2144
- before_wanted = last_entry_s;
2145
- before_is_aligned_to_db_end = true;
2146
- query_debug_log(":zero before_wanted %ld", before_wanted);
2147
- }
2148
-
2149
- if (points_wanted == 0) {
2150
- points_wanted = (last_entry_s - first_entry_s) / update_every;
2151
- query_debug_log(":zero points_wanted %zu", points_wanted);
2152
- }
2153
- }
2154
- }
2155
-
2156
- if (points_wanted == 0) {
2157
- points_wanted = 600;
2158
- query_debug_log(":zero600 points_wanted %zu", points_wanted);
2159
- }
2160
-
2161
- // convert our before_wanted and after_wanted to absolute
2162
- rrdr_relative_window_to_absolute_query(&after_wanted, &before_wanted, NULL, unittest_running);
2163
- query_debug_log(":relative2absolute after %ld, before %ld", after_wanted, before_wanted);
2164
-
2165
- if (natural_points && (options & RRDR_OPTION_SELECTED_TIER) && tier > 0 && nd_profile.storage_tiers > 1) {
2166
- update_every = rrdset_find_natural_update_every_for_timeframe(
2167
- qt, after_wanted, before_wanted, points_wanted, options, tier);
2168
-
2169
- if (update_every <= 0) update_every = qt->db.minimum_latest_update_every_s;
2170
- query_debug_log(":natural update every %ld", update_every);
2171
- }
2172
-
2173
- // this is the update_every of the query
2174
- // it may be different to the update_every of the database
2175
- time_t query_granularity = (natural_points) ? update_every : 1;
2176
- if (query_granularity <= 0) query_granularity = 1;
2177
- query_debug_log(":query_granularity %ld", query_granularity);
2178
-
2179
- // align before_wanted and after_wanted to query_granularity
2180
- if (before_wanted % query_granularity) {
2181
- before_wanted -= before_wanted % query_granularity;
2182
- query_debug_log(":granularity align before_wanted %ld", before_wanted);
2183
- }
2184
-
2185
- if (after_wanted % query_granularity) {
2186
- after_wanted -= after_wanted % query_granularity;
2187
- query_debug_log(":granularity align after_wanted %ld", after_wanted);
2188
- }
2189
-
2190
- // automatic_natural_points is set when the user wants all the points available in the database
2191
- if (automatic_natural_points) {
2192
- points_wanted = (before_wanted - after_wanted + 1) / query_granularity;
2193
- if (unlikely(points_wanted <= 0)) points_wanted = 1;
2194
- query_debug_log(":auto natural points_wanted %zu", points_wanted);
2195
- }
2196
-
2197
- time_t duration = before_wanted - after_wanted;
2198
-
2199
- // if the resampling time is too big, extend the duration to the past
2200
- if (unlikely(resampling_time_requested > duration)) {
2201
- after_wanted = before_wanted - resampling_time_requested;
2202
- duration = before_wanted - after_wanted;
2203
- query_debug_log(":resampling after_wanted %ld", after_wanted);
2204
- }
2205
-
2206
- // if the duration is not aligned to resampling time
2207
- // extend the duration to the past, to avoid a gap at the chart
2208
- // only when the missing duration is above 1/10th of a point
2209
- if (resampling_time_requested > query_granularity && duration % resampling_time_requested) {
2210
- time_t delta = duration % resampling_time_requested;
2211
- if (delta > resampling_time_requested / 10) {
2212
- after_wanted -= resampling_time_requested - delta;
2213
- duration = before_wanted - after_wanted;
2214
- query_debug_log(":resampling2 after_wanted %ld", after_wanted);
2215
- }
2216
- }
2217
-
2218
- // the available points of the query
2219
- size_t points_available = (duration + 1) / query_granularity;
2220
- if (unlikely(points_available <= 0)) points_available = 1;
2221
- query_debug_log(":points_available %zu", points_available);
2222
-
2223
- if (points_wanted > points_available) {
2224
- points_wanted = points_available;
2225
- query_debug_log(":max points_wanted %zu", points_wanted);
2226
- }
2227
-
2228
- if(points_wanted > 86400 && !unittest_running) {
2229
- points_wanted = 86400;
2230
- query_debug_log(":absolute max points_wanted %zu", points_wanted);
2231
- }
2232
-
2233
- // calculate the desired grouping of source data points
2234
- size_t group = points_available / points_wanted;
2235
- if (group == 0) group = 1;
2236
-
2237
- // round "group" to the closest integer
2238
- if (points_available % points_wanted > points_wanted / 2)
2239
- group++;
2240
-
2241
- query_debug_log(":group %zu", group);
2242
-
2243
- if (points_wanted * group * query_granularity < (size_t)duration) {
2244
- // the grouping we are going to do, is not enough
2245
- // to cover the entire duration requested, so
2246
- // we have to change the number of points, to make sure we will
2247
- // respect the timeframe as closely as possibly
2248
-
2249
- // let's see how many points are the optimal
2250
- points_wanted = points_available / group;
2251
-
2252
- if (points_wanted * group < points_available)
2253
- points_wanted++;
2254
-
2255
- if (unlikely(points_wanted == 0))
2256
- points_wanted = 1;
2257
-
2258
- query_debug_log(":optimal points %zu", points_wanted);
2259
- }
2260
-
2261
- // resampling_time_requested enforces a certain grouping multiple
2262
- NETDATA_DOUBLE resampling_divisor = 1.0;
2263
- size_t resampling_group = 1;
2264
- if (unlikely(resampling_time_requested > query_granularity)) {
2265
- // the points we should group to satisfy gtime
2266
- resampling_group = resampling_time_requested / query_granularity;
2267
- if (unlikely(resampling_time_requested % query_granularity))
2268
- resampling_group++;
2269
-
2270
- query_debug_log(":resampling group %zu", resampling_group);
2271
-
2272
- // adapt group according to resampling_group
2273
- if (unlikely(group < resampling_group)) {
2274
- group = resampling_group; // do not allow grouping below the desired one
2275
- query_debug_log(":group less res %zu", group);
2276
- }
2277
- if (unlikely(group % resampling_group)) {
2278
- group += resampling_group - (group % resampling_group); // make sure group is multiple of resampling_group
2279
- query_debug_log(":group mod res %zu", group);
2280
- }
2281
-
2282
- // resampling_divisor = group / resampling_group;
2283
- resampling_divisor = (NETDATA_DOUBLE) (group * query_granularity) / (NETDATA_DOUBLE) resampling_time_requested;
2284
- query_debug_log(":resampling divisor " NETDATA_DOUBLE_FORMAT, resampling_divisor);
2285
- }
2286
-
2287
- // now that we have group, align the requested timeframe to fit it.
2288
- if (aligned && before_wanted % (group * query_granularity)) {
2289
- if (before_is_aligned_to_db_end)
2290
- before_wanted -= before_wanted % (time_t)(group * query_granularity);
2291
- else
2292
- before_wanted += (time_t)(group * query_granularity) - before_wanted % (time_t)(group * query_granularity);
2293
- query_debug_log(":align before_wanted %ld", before_wanted);
2294
- }
2295
-
2296
- after_wanted = before_wanted - (time_t)(points_wanted * group * query_granularity) + query_granularity;
2297
- query_debug_log(":final after_wanted %ld", after_wanted);
2298
-
2299
- duration = before_wanted - after_wanted;
2300
- query_debug_log(":final duration %ld", duration + 1);
2301
-
2302
- query_debug_log_fin();
2303
-
2304
- internal_error(points_wanted != duration / (query_granularity * group) + 1,
2305
- "QUERY: points_wanted %zu is not points %zu",
2306
- points_wanted, (size_t)(duration / (query_granularity * group) + 1));
2307
-
2308
- internal_error(group < resampling_group,
2309
- "QUERY: group %zu is less than the desired group points %zu",
2310
- group, resampling_group);
2311
-
2312
- internal_error(group > resampling_group && group % resampling_group,
2313
- "QUERY: group %zu is not a multiple of the desired group points %zu",
2314
- group, resampling_group);
2315
-
2316
- // -------------------------------------------------------------------------
2317
- // update QUERY_TARGET with our calculations
2318
-
2319
- qt->window.after = after_wanted;
2320
- qt->window.before = before_wanted;
2321
- qt->window.relative = relative_period_requested;
2322
- qt->window.points = points_wanted;
2323
- qt->window.group = group;
2324
- qt->window.time_group_method = group_method;
2325
- qt->window.time_group_options = qt->request.time_group_options;
2326
- qt->window.query_granularity = query_granularity;
2327
- qt->window.resampling_group = resampling_group;
2328
- qt->window.resampling_divisor = resampling_divisor;
2329
- qt->window.options = options;
2330
- qt->window.tier = tier;
2331
- qt->window.aligned = aligned;
2332
-
2333
- return true;
2334
-}
2335
-
2336
-// ----------------------------------------------------------------------------
2337
-// group by
2338
-
2339
-struct group_by_label_key {
2340
- DICTIONARY *values;
2341
-};
2342
-
2343
-static void group_by_label_key_insert_cb(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data) {
2344
- // add the key to our r->label_keys global keys dictionary
2345
- DICTIONARY *label_keys = data;
2346
- dictionary_set(label_keys, dictionary_acquired_item_name(item), NULL, 0);
2347
-
2348
- // create a dictionary for the values of this key
2349
- struct group_by_label_key *k = value;
2350
- k->values = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE, NULL, 0);
2351
-}
2352
-
2353
-static void group_by_label_key_delete_cb(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) {
2354
- struct group_by_label_key *k = value;
2355
- dictionary_destroy(k->values);
2356
-}
2357
-
2358
-static int rrdlabels_traversal_cb_to_group_by_label_key(const char *name, const char *value, RRDLABEL_SRC ls __maybe_unused, void *data) {
2359
- DICTIONARY *dl = data;
2360
- struct group_by_label_key *k = dictionary_set(dl, name, NULL, sizeof(struct group_by_label_key));
2361
- dictionary_set(k->values, value, NULL, 0);
2362
- return 1;
2363
-}
2364
-
2365
-void rrdr_json_group_by_labels(BUFFER *wb, const char *key, RRDR *r, RRDR_OPTIONS options) {
2366
- if(!r->label_keys || !r->dl)
2367
- return;
2368
-
2369
- buffer_json_member_add_object(wb, key);
2370
-
2371
- void *t;
2372
- dfe_start_read(r->label_keys, t) {
2373
- buffer_json_member_add_array(wb, t_dfe.name);
2374
-
2375
- for(size_t d = 0; d < r->d ;d++) {
2376
- if(!rrdr_dimension_should_be_exposed(r->od[d], options))
2377
- continue;
2378
-
2379
- struct group_by_label_key *k = dictionary_get(r->dl[d], t_dfe.name);
2380
- if(k) {
2381
- buffer_json_add_array_item_array(wb);
2382
- void *tt;
2383
- dfe_start_read(k->values, tt) {
2384
- buffer_json_add_array_item_string(wb, tt_dfe.name);
2385
- }
2386
- dfe_done(tt);
2387
- buffer_json_array_close(wb);
2388
- }
2389
- else
2390
- buffer_json_add_array_item_string(wb, NULL);
284
+ // we only log if this is not point 1
285
+ internal_error(new_point.sp.end_time_s < ops->plan_expanded_after &&
286
+ db_points_read_since_plan_switch > 1,
287
+ "QUERY: '%s', dimension '%s' next_metric() "
288
+ "returned point %zu from %ld time %ld, "
289
+ "which is entirely before our current timeframe %ld to %ld "
290
+ "(and before the entire query, after %ld, before %ld)",
291
+ qt->id, query_metric_id(qt, qm),
292
+ new_point.id, new_point.sp.start_time_s, new_point.sp.end_time_s,
293
+ now_start_time, now_end_time,
294
+ ops->plan_expanded_after, ops->plan_expanded_before);
295
}
296
2393
- buffer_json_array_close(wb);
2394
- }
2395
- dfe_done(t);
2396
-
2397
- buffer_json_object_close(wb); // key
2398
-}
2399
-
2400
-static void rrd2rrdr_set_timestamps(RRDR *r) {
2401
- QUERY_TARGET *qt = r->internal.qt;
2402
-
2403
- internal_fatal(qt->window.points != r->n, "QUERY: mismatch to the number of points in qt and r");
2404
-
2405
- r->view.group = qt->window.group;
2406
- r->view.update_every = (int) query_view_update_every(qt);
2407
- r->view.before = qt->window.before;
2408
- r->view.after = qt->window.after;
2409
-
2410
- r->time_grouping.points_wanted = qt->window.points;
2411
- r->time_grouping.resampling_group = qt->window.resampling_group;
2412
- r->time_grouping.resampling_divisor = qt->window.resampling_divisor;
2413
-
2414
- r->rows = qt->window.points;
2415
-
2416
- size_t points_wanted = qt->window.points;
2417
- time_t after_wanted = qt->window.after;
2418
- time_t before_wanted = qt->window.before; (void)before_wanted;
2419
-
2420
- time_t view_update_every = r->view.update_every;
2421
- time_t query_granularity = (time_t)(r->view.update_every / r->view.group);
2422
-
2423
- size_t rrdr_line = 0;
2424
- time_t first_point_end_time = after_wanted + view_update_every - query_granularity;
2425
- time_t now_end_time = first_point_end_time;
2426
-
2427
- while (rrdr_line < points_wanted) {
2428
- r->t[rrdr_line++] = now_end_time;
2429
- now_end_time += view_update_every;
2430
- }
2431
-
2432
- internal_fatal(r->t[0] != first_point_end_time, "QUERY: wrong first timestamp in the query");
2433
- internal_error(r->t[points_wanted - 1] != before_wanted,
2434
- "QUERY: wrong last timestamp in the query, expected %ld, found %ld",
2435
- before_wanted, r->t[points_wanted - 1]);
2436
-}
2437
-
2438
-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) {
2439
- buffer_flush(key);
2440
- if(unlikely(!query_has_percentage_of_group && qm->status & RRDR_DIMENSION_HIDDEN)) {
2441
- buffer_strcat(key, "__hidden_dimensions__");
2442
- }
2443
- else if(unlikely(group_by & RRDR_GROUP_BY_SELECTED)) {
2444
- buffer_strcat(key, "selected");
2445
- }
2446
- else {
2447
- if (group_by & RRDR_GROUP_BY_DIMENSION) {
2448
- buffer_fast_strcat(key, "|", 1);
2449
- buffer_strcat(key, query_metric_name(qt, qm));
2450
- }
2451
-
2452
- if (group_by & (RRDR_GROUP_BY_INSTANCE|RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE)) {
2453
- buffer_fast_strcat(key, "|", 1);
2454
- buffer_strcat(key, string2str(query_instance_id_fqdn(qi, qt->request.version)));
2455
- }
2456
-
2457
- if (group_by & RRDR_GROUP_BY_LABEL) {
2458
- RRDLABELS *labels = rrdinstance_acquired_labels(qi->ria);
2459
- for (size_t l = 0; l < qt->group_by[group_by_id].used; l++) {
2460
- buffer_fast_strcat(key, "|", 1);
2461
- rrdlabels_get_value_to_buffer_or_unset(labels, key, qt->group_by[group_by_id].label_keys[l], "[unset]");
2462
- }
2463
- }
2464
-
2465
- if (group_by & RRDR_GROUP_BY_NODE) {
2466
- buffer_fast_strcat(key, "|", 1);
2467
- buffer_strcat(key, qn->rrdhost->machine_guid);
2468
- }
2469
-
2470
- if (group_by & RRDR_GROUP_BY_CONTEXT) {
2471
- buffer_fast_strcat(key, "|", 1);
2472
- buffer_strcat(key, rrdcontext_acquired_id(qc->rca));
2473
- }
2474
-
2475
- if (group_by & RRDR_GROUP_BY_UNITS) {
2476
- buffer_fast_strcat(key, "|", 1);
2477
- buffer_strcat(key, query_target_has_percentage_units(qt) ? "%" : rrdinstance_acquired_units(qi->ria));
2478
- }
2479
- }
2480
-}
2481
-
2482
-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) {
2483
- buffer_flush(key);
2484
- if(unlikely(!query_has_percentage_of_group && qm->status & RRDR_DIMENSION_HIDDEN)) {
2485
- buffer_strcat(key, "__hidden_dimensions__");
2486
- }
2487
- else if(unlikely(group_by & RRDR_GROUP_BY_SELECTED)) {
2488
- buffer_strcat(key, "selected");
2489
- }
2490
- else {
2491
- if (group_by & RRDR_GROUP_BY_DIMENSION) {
2492
- buffer_strcat(key, query_metric_name(qt, qm));
2493
- }
2494
-
2495
- if (group_by & (RRDR_GROUP_BY_INSTANCE|RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE)) {
2496
- if (buffer_strlen(key) != 0)
2497
- buffer_fast_strcat(key, ",", 1);
2498
-
2499
- if (group_by & RRDR_GROUP_BY_NODE)
2500
- buffer_strcat(key, rrdinstance_acquired_id(qi->ria));
2501
- else
2502
- buffer_strcat(key, string2str(query_instance_id_fqdn(qi, qt->request.version)));
2503
- }
2504
-
2505
- if (group_by & RRDR_GROUP_BY_LABEL) {
2506
- RRDLABELS *labels = rrdinstance_acquired_labels(qi->ria);
2507
- for (size_t l = 0; l < qt->group_by[group_by_id].used; l++) {
2508
- if (buffer_strlen(key) != 0)
2509
- buffer_fast_strcat(key, ",", 1);
2510
- rrdlabels_get_value_to_buffer_or_unset(labels, key, qt->group_by[group_by_id].label_keys[l], "[unset]");
2511
- }
2512
- }
2513
-
2514
- if (group_by & RRDR_GROUP_BY_NODE) {
2515
- if (buffer_strlen(key) != 0)
2516
- buffer_fast_strcat(key, ",", 1);
2517
-
2518
- buffer_strcat(key, qn->rrdhost->machine_guid);
2519
- }
2520
-
2521
- if (group_by & RRDR_GROUP_BY_CONTEXT) {
2522
- if (buffer_strlen(key) != 0)
2523
- buffer_fast_strcat(key, ",", 1);
2524
-
2525
- buffer_strcat(key, rrdcontext_acquired_id(qc->rca));
2526
- }
2527
-
2528
- if (group_by & RRDR_GROUP_BY_UNITS) {
2529
- if (buffer_strlen(key) != 0)
2530
- buffer_fast_strcat(key, ",", 1);
2531
-
2532
- buffer_strcat(key, query_target_has_percentage_units(qt) ? "%" : rrdinstance_acquired_units(qi->ria));
2533
- }
2534
- }
2535
-}
2536
-
2537
-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) {
2538
- buffer_flush(key);
2539
- if(unlikely(!query_has_percentage_of_group && qm->status & RRDR_DIMENSION_HIDDEN)) {
2540
- buffer_strcat(key, "__hidden_dimensions__");
2541
- }
2542
- else if(unlikely(group_by & RRDR_GROUP_BY_SELECTED)) {
2543
- buffer_strcat(key, "selected");
2544
- }
2545
- else {
2546
- if (group_by & RRDR_GROUP_BY_DIMENSION) {
2547
- buffer_strcat(key, query_metric_name(qt, qm));
2548
- }
2549
-
2550
- if (group_by & (RRDR_GROUP_BY_INSTANCE|RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE)) {
2551
- if (buffer_strlen(key) != 0)
2552
- buffer_fast_strcat(key, ",", 1);
2553
-
2554
- if (group_by & RRDR_GROUP_BY_NODE)
2555
- buffer_strcat(key, rrdinstance_acquired_name(qi->ria));
2556
- else
2557
- buffer_strcat(key, string2str(query_instance_name_fqdn(qi, qt->request.version)));
2558
- }
2559
-
2560
- if (group_by & RRDR_GROUP_BY_LABEL) {
2561
- RRDLABELS *labels = rrdinstance_acquired_labels(qi->ria);
2562
- for (size_t l = 0; l < qt->group_by[group_by_id].used; l++) {
2563
- if (buffer_strlen(key) != 0)
2564
- buffer_fast_strcat(key, ",", 1);
2565
- rrdlabels_get_value_to_buffer_or_unset(labels, key, qt->group_by[group_by_id].label_keys[l], "[unset]");
2566
- }
2567
- }
2568
-
2569
- if (group_by & RRDR_GROUP_BY_NODE) {
2570
- if (buffer_strlen(key) != 0)
2571
- buffer_fast_strcat(key, ",", 1);
2572
-
2573
- buffer_strcat(key, rrdhost_hostname(qn->rrdhost));
2574
- }
2575
-
2576
- if (group_by & RRDR_GROUP_BY_CONTEXT) {
2577
- if (buffer_strlen(key) != 0)
2578
- buffer_fast_strcat(key, ",", 1);
2579
-
2580
- buffer_strcat(key, rrdcontext_acquired_id(qc->rca));
2581
- }
2582
-
2583
- if (group_by & RRDR_GROUP_BY_UNITS) {
2584
- if (buffer_strlen(key) != 0)
2585
- buffer_fast_strcat(key, ",", 1);
2586
-
2587
- buffer_strcat(key, query_target_has_percentage_units(qt) ? "%" : rrdinstance_acquired_units(qi->ria));
2588
- }
2589
- }
2590
-}
2591
-
2592
-struct rrdr_group_by_entry {
2593
- size_t priority;
2594
- size_t count;
2595
- STRING *id;
2596
- STRING *name;
2597
- STRING *units;
2598
- RRDR_DIMENSION_FLAGS od;
2599
- DICTIONARY *dl;
2600
-};
2601
-
2602
-static RRDR *rrd2rrdr_group_by_initialize(ONEWAYALLOC *owa, QUERY_TARGET *qt) {
2603
- RRDR *r_tmp = NULL;
2604
- RRDR_OPTIONS options = qt->window.options;
2605
-
2606
- if(qt->request.version < 2) {
2607
- // v1 query
2608
- RRDR *r = rrdr_create(owa, qt, qt->query.used, qt->window.points);
2609
- if(unlikely(!r)) {
2610
- internal_error(true, "QUERY: cannot create RRDR for %s, after=%ld, before=%ld, dimensions=%u, points=%zu",
2611
- qt->id, qt->window.after, qt->window.before, qt->query.used, qt->window.points);
2612
- return NULL;
2613
- }
2614
- r->group_by.r = NULL;
2615
-
2616
- for(size_t d = 0; d < qt->query.used ; d++) {
2617
- QUERY_METRIC *qm = query_metric(qt, d);
2618
- QUERY_DIMENSION *qd = query_dimension(qt, qm->link.query_dimension_id);
2619
- r->di[d] = rrdmetric_acquired_id_dup(qd->rma);
2620
- r->dn[d] = rrdmetric_acquired_name_dup(qd->rma);
2621
- }
2622
-
2623
- rrd2rrdr_set_timestamps(r);
2624
- return r;
2625
- }
2626
- // v2 query
2627
-
2628
- // parse all the group-by label keys
2629
- for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) {
2630
- if (qt->request.group_by[g].group_by & RRDR_GROUP_BY_LABEL &&
2631
- qt->request.group_by[g].group_by_label && *qt->request.group_by[g].group_by_label)
2632
- qt->group_by[g].used = quoted_strings_splitter_query_group_by_label(
2633
- qt->request.group_by[g].group_by_label, qt->group_by[g].label_keys,
2634
- GROUP_BY_MAX_LABEL_KEYS);
2635
-
2636
- if (!qt->group_by[g].used)
2637
- qt->request.group_by[g].group_by &= ~RRDR_GROUP_BY_LABEL;
2638
- }
2639
-
2640
- // make sure there are valid group-by methods
2641
- for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) {
2642
- if(!(qt->request.group_by[g].group_by & SUPPORTED_GROUP_BY_METHODS))
2643
- qt->request.group_by[g].group_by = (g == 0) ? RRDR_GROUP_BY_DIMENSION : RRDR_GROUP_BY_NONE;
2644
- }
2645
-
2646
- bool query_has_percentage_of_group = query_target_has_percentage_of_group(qt);
2647
-
2648
- // merge all group-by options to upper levels,
2649
- // so that the top level has all the groupings of the inner levels,
2650
- // and each subsequent level has all the groupings of its inner levels.
2651
- for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES - 1 ;g++) {
2652
- if(qt->request.group_by[g].group_by == RRDR_GROUP_BY_NONE)
2653
- continue;
2654
-
2655
- if(qt->request.group_by[g].group_by == RRDR_GROUP_BY_SELECTED) {
2656
- for (size_t r = g + 1; r < MAX_QUERY_GROUP_BY_PASSES; r++)
2657
- qt->request.group_by[r].group_by = RRDR_GROUP_BY_NONE;
2658
- }
2659
- else {
2660
- for (size_t r = g + 1; r < MAX_QUERY_GROUP_BY_PASSES; r++) {
2661
- if (qt->request.group_by[r].group_by == RRDR_GROUP_BY_NONE)
2662
- continue;
2663
-
2664
- if (qt->request.group_by[r].group_by != RRDR_GROUP_BY_SELECTED) {
2665
- if(qt->request.group_by[r].group_by & RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE)
2666
- qt->request.group_by[g].group_by |= RRDR_GROUP_BY_INSTANCE;
2667
- else
2668
- qt->request.group_by[g].group_by |= qt->request.group_by[r].group_by;
2669
-
2670
- if(qt->request.group_by[r].group_by & RRDR_GROUP_BY_LABEL) {
2671
- for (size_t lr = 0; lr < qt->group_by[r].used; lr++) {
2672
- bool found = false;
2673
- for (size_t lg = 0; lg < qt->group_by[g].used; lg++) {
2674
- if (strcmp(qt->group_by[g].label_keys[lg], qt->group_by[r].label_keys[lr]) == 0) {
2675
- found = true;
2676
- break;
2677
- }
2678
- }
2679
-
2680
- if (!found && qt->group_by[g].used < GROUP_BY_MAX_LABEL_KEYS * MAX_QUERY_GROUP_BY_PASSES)
2681
- qt->group_by[g].label_keys[qt->group_by[g].used++] = qt->group_by[r].label_keys[lr];
2682
- }
2683
- }
2684
- }
297
+ }
298
+ else {
299
+ // the point ends in the future
300
+ // so, we will interpolate it below, at the inner loop
301
+ break;
302
}
303
}
2687
- }
304
2689
- int added = 0;
2690
- RRDR *first_r = NULL, *last_r = NULL;
2691
- BUFFER *key = buffer_create(0, NULL);
2692
- struct rrdr_group_by_entry *entries = onewayalloc_mallocz(owa, qt->query.used * sizeof(struct rrdr_group_by_entry));
2693
- DICTIONARY *groups = dictionary_create(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE);
2694
- DICTIONARY *label_keys = NULL;
305
+ if(unlikely(count_same_end_time)) {
306
+ internal_error(true,
307
+ "QUERY: '%s', dimension '%s', the database does not advance the query,"
308
+ " it returned an end time less or equal to the end time of the last "
309
+ "point we got %ld, %zu times",
310
+ qt->id, query_metric_id(qt, qm),
311
+ last1_point.sp.end_time_s, count_same_end_time);
312
2696
- for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++) {
2697
- RRDR_GROUP_BY group_by = qt->request.group_by[g].group_by;
2698
- RRDR_GROUP_BY_FUNCTION aggregation_method = qt->request.group_by[g].aggregation;
313
+ if(unlikely(new_point.sp.end_time_s <= last1_point.sp.end_time_s))
314
+ new_point.sp.end_time_s = now_end_time;
315
+ }
316
2700
- if(group_by == RRDR_GROUP_BY_NONE)
2701
- break;
317
+ time_t stop_time = new_point.sp.end_time_s;
318
+ if(unlikely(!storage_point_is_unset(next1_point) && next1_point.start_time_s >= now_end_time)) {
319
+ // ONE POINT READ-AHEAD
320
+ // the point crosses the start time of the
321
+ // read ahead storage point we have read
322
+ stop_time = next1_point.start_time_s;
323
+ }
324
2703
- memset(entries, 0, qt->query.used * sizeof(struct rrdr_group_by_entry));
2704
- dictionary_flush(groups);
2705
- added = 0;
325
+ // the inner loop
326
+ // we have 3 points in memory: last2, last1, new
327
+ // we select the one to use based on their timestamps
328
2707
- size_t hidden_dimensions = 0;
2708
- bool final_grouping = (g == MAX_QUERY_GROUP_BY_PASSES - 1 || qt->request.group_by[g + 1].group_by == RRDR_GROUP_BY_NONE) ? true : false;
329
+ internal_fatal(now_end_time > stop_time || points_added >= points_wanted,
330
+ "QUERY: first part of query provides invalid point to interpolate (now_end_time %ld, stop_time %ld",
331
+ now_end_time, stop_time);
332
2710
- if (final_grouping && (options & RRDR_OPTION_GROUP_BY_LABELS))
2711
- label_keys = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE, NULL, 0);
333
+ do {
334
+ // now_start_time is wrong in this loop
335
+ // but, we don't need it
336
2713
- QUERY_INSTANCE *last_qi = NULL;
2714
- size_t priority = 0;
2715
- time_t update_every_max = 0;
2716
- for (size_t d = 0; d < qt->query.used; d++) {
2717
- QUERY_METRIC *qm = query_metric(qt, d);
2718
- QUERY_DIMENSION *qd = query_dimension(qt, qm->link.query_dimension_id);
2719
- QUERY_INSTANCE *qi = query_instance(qt, qm->link.query_instance_id);
2720
- QUERY_CONTEXT *qc = query_context(qt, qm->link.query_context_id);
2721
- QUERY_NODE *qn = query_node(qt, qm->link.query_node_id);
337
+ QUERY_POINT current_point;
338
2723
- if (qi != last_qi) {
2724
- last_qi = qi;
339
+ if(likely(now_end_time > new_point.sp.start_time_s)) {
340
+ // it is time for our NEW point to be used
341
+ current_point = new_point;
342
+ new_point.added = true; // first copy, then set it, so that new_point will not be added again
343
+ query_interpolate_point(current_point, last1_point, now_end_time);
344
2726
- time_t update_every = rrdinstance_acquired_update_every(qi->ria);
2727
- if (update_every > update_every_max)
2728
- update_every_max = update_every;
345
+// internal_error(current_point.id > 0
346
+// && last1_point.id == 0
347
+// && current_point.end_time > after_wanted
348
+// && current_point.end_time > now_end_time,
349
+// "QUERY: '%s', dimension '%s', after %ld, before %ld, view update every %ld,"
350
+// " query granularity %ld, interpolating point %zu (from %ld to %ld) at %ld,"
351
+// " but we could really favor by having last_point1 in this query.",
352
+// qt->id, string2str(qm->dimension.id),
353
+// after_wanted, before_wanted,
354
+// ops.view_update_every, ops.query_granularity,
355
+// current_point.id, current_point.start_time, current_point.end_time,
356
+// now_end_time);
357
}
358
+ else if(likely(now_end_time <= last1_point.sp.end_time_s)) {
359
+ // our LAST point is still valid
360
+ current_point = last1_point;
361
+ last1_point.added = true; // first copy, then set it, so that last1_point will not be added again
362
+ query_interpolate_point(current_point, last2_point, now_end_time);
363
2731
- priority = qd->priority;
2732
-
2733
- if(qm->status & RRDR_DIMENSION_HIDDEN)
2734
- hidden_dimensions++;
2735
-
2736
- // --------------------------------------------------------------------
2737
- // generate the group by key
2738
-
2739
- query_group_by_make_dimension_key(key, group_by, g, qt, qn, qc, qi, qd, qm, query_has_percentage_of_group);
2740
-
2741
- // lookup the key in the dictionary
2742
-
2743
- int pos = -1;
2744
- int *set = dictionary_set(groups, buffer_tostring(key), &pos, sizeof(pos));
2745
- if (*set == -1) {
2746
- // the key just added to the dictionary
2747
-
2748
- *set = pos = added++;
2749
-
2750
- // ----------------------------------------------------------------
2751
- // generate the dimension id
2752
-
2753
- query_group_by_make_dimension_id(key, group_by, g, qt, qn, qc, qi, qd, qm, query_has_percentage_of_group);
2754
- entries[pos].id = string_strdupz(buffer_tostring(key));
2755
-
2756
- // ----------------------------------------------------------------
2757
- // generate the dimension name
2758
-
2759
- query_group_by_make_dimension_name(key, group_by, g, qt, qn, qc, qi, qd, qm, query_has_percentage_of_group);
2760
- entries[pos].name = string_strdupz(buffer_tostring(key));
2761
-
2762
- // add the rest of the info
2763
- entries[pos].units = rrdinstance_acquired_units_dup(qi->ria);
2764
- entries[pos].priority = priority;
2765
-
2766
- if (label_keys) {
2767
- entries[pos].dl = dictionary_create_advanced(
2768
- DICT_OPTION_SINGLE_THREADED | DICT_OPTION_FIXED_SIZE | DICT_OPTION_DONT_OVERWRITE_VALUE,
2769
- NULL, sizeof(struct group_by_label_key));
2770
- dictionary_register_insert_callback(entries[pos].dl, group_by_label_key_insert_cb, label_keys);
2771
- dictionary_register_delete_callback(entries[pos].dl, group_by_label_key_delete_cb, label_keys);
2772
- }
2773
- } else {
2774
- // the key found in the dictionary
2775
- pos = *set;
364
+// internal_error(current_point.id > 0
365
+// && last2_point.id == 0
366
+// && current_point.end_time > after_wanted
367
+// && current_point.end_time > now_end_time,
368
+// "QUERY: '%s', dimension '%s', after %ld, before %ld, view update every %ld,"
369
+// " query granularity %ld, interpolating point %zu (from %ld to %ld) at %ld,"
370
+// " but we could really favor by having last_point2 in this query.",
371
+// qt->id, string2str(qm->dimension.id),
372
+// after_wanted, before_wanted, ops.view_update_every, ops.query_granularity,
373
+// current_point.id, current_point.start_time, current_point.end_time,
374
+// now_end_time);
375
+ }
376
+ else {
377
+ // a GAP, we don't have a value this time
378
+ current_point = QUERY_POINT_EMPTY;
379
}
380
2778
- entries[pos].count++;
2779
-
2780
- if (unlikely(priority < entries[pos].priority))
2781
- entries[pos].priority = priority;
381
+ query_add_point_to_group(r, current_point, ops, add_flush);
382
2783
- if(g > 0)
2784
- last_r->dgbs[qm->grouped_as.slot] = pos;
2785
- else
2786
- qm->grouped_as.first_slot = pos;
2787
-
2788
- qm->grouped_as.slot = pos;
2789
- qm->grouped_as.id = entries[pos].id;
2790
- qm->grouped_as.name = entries[pos].name;
2791
- qm->grouped_as.units = entries[pos].units;
2792
-
2793
- // copy the dimension flags decided by the query target
2794
- // we need this, because if a dimension is explicitly selected
2795
- // the query target adds to it the non-zero flag
2796
- qm->status |= RRDR_DIMENSION_GROUPED;
2797
-
2798
- if(query_has_percentage_of_group)
2799
- // when the query has percentage of group
2800
- // there will be no hidden dimensions in the final query,
2801
- // so we have to remove the hidden flag from all dimensions
2802
- entries[pos].od |= qm->status & ~RRDR_DIMENSION_HIDDEN;
2803
- else
2804
- entries[pos].od |= qm->status;
383
+ rrdr_line = rrdr_line_init(r, now_end_time, rrdr_line);
384
+ size_t rrdr_o_v_index = rrdr_line * r->d + dim_id_in_rrdr;
385
2806
- if (entries[pos].dl)
2807
- rrdlabels_walkthrough_read(rrdinstance_acquired_labels(qi->ria),
2808
- rrdlabels_traversal_cb_to_group_by_label_key, entries[pos].dl);
2809
- }
386
+ // find the place to store our values
387
+ RRDR_VALUE_FLAGS *rrdr_value_options_ptr = &r->o[rrdr_o_v_index];
388
2811
- RRDR *r = rrdr_create(owa, qt, added, qt->window.points);
2812
- if (!r) {
2813
- internal_error(true,
2814
- "QUERY: cannot create group by RRDR for %s, after=%ld, before=%ld, dimensions=%d, points=%zu",
2815
- qt->id, qt->window.after, qt->window.before, added, qt->window.points);
2816
- goto cleanup;
2817
- }
2818
- // prevent double free at cleanup in case of error
2819
- added = 0;
389
+ // update the dimension options
390
+ if(likely(ops->group_points_non_zero))
391
+ r->od[dim_id_in_rrdr] |= RRDR_DIMENSION_NONZERO;
392
2821
- // link this RRDR
2822
- if(!last_r)
2823
- first_r = last_r = r;
2824
- else
2825
- last_r->group_by.r = r;
393
+ // store the specific point options
394
+ *rrdr_value_options_ptr = ops->group_value_flags;
395
2827
- last_r = r;
396
+ // store the group value
397
+ NETDATA_DOUBLE group_value = time_grouping_flush(r, rrdr_value_options_ptr, add_flush);
398
+ r->v[rrdr_o_v_index] = group_value;
399
2829
- rrd2rrdr_set_timestamps(r);
400
+ r->ar[rrdr_o_v_index] = storage_point_anomaly_rate(ops->group_point);
401
2831
- if(r->d) {
2832
- r->dp = onewayalloc_callocz(owa, r->d, sizeof(*r->dp));
2833
- r->dview = onewayalloc_callocz(owa, r->d, sizeof(*r->dview));
2834
- r->dgbc = onewayalloc_callocz(owa, r->d, sizeof(*r->dgbc));
2835
- r->dqp = onewayalloc_callocz(owa, r->d, sizeof(STORAGE_POINT));
402
+ if(likely(points_added || r->internal.queries_count)) {
403
+ // find the min/max across all dimensions
404
2837
- if(!final_grouping)
2838
- // this is where we are going to store the slot in the next RRDR
2839
- // that we are going to group by the dimension of this RRDR
2840
- r->dgbs = onewayalloc_callocz(owa, r->d, sizeof(*r->dgbs));
405
+ if(unlikely(group_value < min)) min = group_value;
406
+ if(unlikely(group_value > max)) max = group_value;
407
2842
- if (label_keys) {
2843
- r->dl = onewayalloc_callocz(owa, r->d, sizeof(DICTIONARY *));
2844
- r->label_keys = label_keys;
2845
- label_keys = NULL;
408
}
2847
-
2848
- if(r->n) {
2849
- r->gbc = onewayalloc_callocz(owa, r->n * r->d, sizeof(*r->gbc));
2850
-
2851
- if(hidden_dimensions && ((group_by & RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE) || (aggregation_method == RRDR_GROUP_BY_FUNCTION_PERCENTAGE)))
2852
- // this is where we are going to group the hidden dimensions
2853
- r->vh = onewayalloc_mallocz(owa, r->n * r->d * sizeof(*r->vh));
409
+ else {
410
+ // runs only when r->internal.queries_count == 0 && points_added == 0
411
+ // so, on the first point added for the query.
412
+ min = max = group_value;
413
}
2855
- }
2856
-
2857
- // zero r (dimension options, names, and ids)
2858
- // this is required, because group-by may lead to empty dimensions
2859
- for (size_t d = 0; d < r->d; d++) {
2860
- r->di[d] = entries[d].id;
2861
- r->dn[d] = entries[d].name;
2862
-
2863
- r->od[d] = entries[d].od;
2864
- r->du[d] = entries[d].units;
2865
- r->dp[d] = entries[d].priority;
2866
- r->dgbc[d] = entries[d].count;
2867
-
2868
- if (r->dl)
2869
- r->dl[d] = entries[d].dl;
2870
- }
414
2872
- // initialize partial trimming
2873
- r->partial_data_trimming.max_update_every = update_every_max * 2;
2874
- r->partial_data_trimming.expected_after =
2875
- (!query_target_aggregatable(qt) &&
2876
- qt->window.before >= qt->window.now - r->partial_data_trimming.max_update_every) ?
2877
- qt->window.before - r->partial_data_trimming.max_update_every :
2878
- qt->window.before;
2879
- r->partial_data_trimming.trimmed_after = qt->window.before;
2880
-
2881
- // make all values empty
2882
- if(r->n && r->d) {
2883
- for (size_t i = 0; i != r->n; i++) {
2884
- NETDATA_DOUBLE *cn = &r->v[i * r->d];
2885
- RRDR_VALUE_FLAGS *co = &r->o[i * r->d];
2886
- NETDATA_DOUBLE *ar = &r->ar[i * r->d];
2887
- NETDATA_DOUBLE *vh = r->vh ? &r->vh[i * r->d] : NULL;
2888
-
2889
- for (size_t d = 0; d < r->d; d++) {
2890
- cn[d] = NAN;
2891
- ar[d] = 0.0;
2892
- co[d] = RRDR_VALUE_EMPTY;
2893
-
2894
- if (vh)
2895
- vh[d] = NAN;
2896
- }
2897
- }
2898
- }
2899
- }
415
+ points_added++;
416
+ ops->group_points_added = 0;
417
+ ops->group_value_flags = RRDR_VALUE_NOTHING;
418
+ ops->group_points_non_zero = 0;
419
+ ops->group_point = STORAGE_POINT_UNSET;
420
2901
- if(!first_r || !last_r)
2902
- goto cleanup;
421
+ now_end_time += ops->view_update_every;
422
+ } while(now_end_time <= stop_time && points_added < points_wanted);
423
2904
- r_tmp = rrdr_create(owa, qt, 1, qt->window.points);
2905
- if (!r_tmp) {
2906
- internal_error(true,
2907
- "QUERY: cannot create group by temporary RRDR for %s, after=%ld, before=%ld, dimensions=%d, points=%zu",
2908
- qt->id, qt->window.after, qt->window.before, 1, qt->window.points);
2909
- goto cleanup;
424
+ // the loop above increased "now" by ops->view_update_every,
425
+ // but the main loop will increase it too,
426
+ // so, let's undo the last iteration of this loop
427
+ now_end_time -= ops->view_update_every;
428
}
2911
- rrd2rrdr_set_timestamps(r_tmp);
2912
- r_tmp->group_by.r = first_r;
2913
-
2914
-cleanup:
2915
- if(!first_r || !last_r || !r_tmp) {
2916
- if(r_tmp) {
2917
- r_tmp->group_by.r = NULL;
2918
- rrdr_free(owa, r_tmp);
2919
- }
2920
-
2921
- if(first_r) {
2922
- RRDR *r = first_r;
2923
- while (r) {
2924
- r_tmp = r->group_by.r;
2925
- r->group_by.r = NULL;
2926
- rrdr_free(owa, r);
2927
- r = r_tmp;
2928
- }
2929
- }
429
+ query_planer_finalize_remaining_plans(ops);
430
2931
- if(entries && added) {
2932
- for (int d = 0; d < added; d++) {
2933
- string_freez(entries[d].id);
2934
- string_freez(entries[d].name);
2935
- string_freez(entries[d].units);
2936
- dictionary_destroy(entries[d].dl);
2937
- }
2938
- }
2939
- dictionary_destroy(label_keys);
431
+ qm->query_points = ops->query_point;
432
2941
- first_r = last_r = r_tmp = NULL;
433
+ // fill the rest of the points with empty values
434
+ while (points_added < points_wanted) {
435
+ rrdr_line++;
436
+ size_t rrdr_o_v_index = rrdr_line * r->d + dim_id_in_rrdr;
437
+ r->o[rrdr_o_v_index] = RRDR_VALUE_EMPTY;
438
+ r->v[rrdr_o_v_index] = 0.0;
439
+ r->ar[rrdr_o_v_index] = 0.0;
440
+ points_added++;
441
}
442
2944
- buffer_free(key);
2945
- onewayalloc_freez(owa, entries);
2946
- dictionary_destroy(groups);
443
+ r->internal.queries_count++;
444
+ r->view.min = min;
445
+ r->view.max = max;
446
2948
- return r_tmp;
447
+ r->stats.result_points_generated += points_added;
448
+ r->stats.db_points_read += ops->db_total_points_read;
449
+ for(size_t tr = 0; tr < nd_profile.storage_tiers; tr++)
450
+ qt->db.tiers[tr].points += ops->db_points_read_per_tier[tr];
451
}
452
2951
-static void rrd2rrdr_group_by_add_metric(RRDR *r_dst, size_t d_dst, RRDR *r_tmp, size_t d_tmp,
2952
- RRDR_GROUP_BY_FUNCTION group_by_aggregate_function,
2953
- STORAGE_POINT *query_points, size_t pass __maybe_unused) {
2954
- if(!r_tmp || r_dst == r_tmp || !(r_tmp->od[d_tmp] & RRDR_DIMENSION_QUERIED))
2955
- return;
2956
-
2957
- internal_fatal(r_dst->n != r_tmp->n, "QUERY: group-by source and destination do not have the same number of rows");
2958
- internal_fatal(d_dst >= r_dst->d, "QUERY: group-by destination dimension number exceeds destination RRDR size");
2959
- internal_fatal(d_tmp >= r_tmp->d, "QUERY: group-by source dimension number exceeds source RRDR size");
2960
- internal_fatal(!r_dst->dqp, "QUERY: group-by destination is not properly prepared (missing dqp array)");
2961
- internal_fatal(!r_dst->gbc, "QUERY: group-by destination is not properly prepared (missing gbc array)");
453
+// ----------------------------------------------------------------------------
454
+// fill RRDR for the whole chart
455
2963
- bool hidden_dimension_on_percentage_of_group = (r_tmp->od[d_tmp] & RRDR_DIMENSION_HIDDEN) && r_dst->vh;
456
+#ifdef NETDATA_INTERNAL_CHECKS
457
+static void rrd2rrdr_log_request_response_metadata(RRDR *r
458
+ , RRDR_OPTIONS options __maybe_unused
459
+ , RRDR_TIME_GROUPING group_method
460
+ , bool aligned
461
+ , size_t group
462
+ , time_t resampling_time
463
+ , size_t resampling_group
464
+ , time_t after_wanted
465
+ , time_t after_requested
466
+ , time_t before_wanted
467
+ , time_t before_requested
468
+ , size_t points_requested
469
+ , size_t points_wanted
470
+ //, size_t after_slot
471
+ //, size_t before_slot
472
+ , const char *msg
473
+ ) {
474
2965
- if(!hidden_dimension_on_percentage_of_group) {
2966
- r_dst->od[d_dst] |= r_tmp->od[d_tmp];
2967
- storage_point_merge_to(r_dst->dqp[d_dst], *query_points);
2968
- }
475
+ QUERY_TARGET *qt = r->internal.qt;
476
+ time_t first_entry_s = qt->db.first_time_s;
477
+ time_t last_entry_s = qt->db.last_time_s;
478
2970
- // do the group_by
2971
- for(size_t i = 0; i != rrdr_rows(r_tmp) ; i++) {
479
+ internal_error(
480
+ true,
481
+ "rrd2rrdr() on %s update every %ld with %s grouping %s (group: %zu, resampling_time: %ld, resampling_group: %zu), "
482
+ "after (got: %ld, want: %ld, req: %ld, db: %ld), "
483
+ "before (got: %ld, want: %ld, req: %ld, db: %ld), "
484
+ "duration (got: %ld, want: %ld, req: %ld, db: %ld), "
485
+ "points (got: %zu, want: %zu, req: %zu), "
486
+ "%s"
487
+ , qt->id
488
+ , qt->window.query_granularity
489
2973
- size_t idx_tmp = i * r_tmp->d + d_tmp;
2974
- NETDATA_DOUBLE n_tmp = r_tmp->v[ idx_tmp ];
2975
- RRDR_VALUE_FLAGS o_tmp = r_tmp->o[ idx_tmp ];
2976
- NETDATA_DOUBLE ar_tmp = r_tmp->ar[ idx_tmp ];
490
+ // grouping
491
+ , (aligned) ? "aligned" : "unaligned"
492
+ , time_grouping_id2txt(group_method)
493
+ , group
494
+ , resampling_time
495
+ , resampling_group
496
2978
- if(o_tmp & RRDR_VALUE_EMPTY)
2979
- continue;
497
+ // after
498
+ , r->view.after
499
+ , after_wanted
500
+ , after_requested
501
+ , first_entry_s
502
2981
- size_t idx_dst = i * r_dst->d + d_dst;
2982
- NETDATA_DOUBLE *cn = (hidden_dimension_on_percentage_of_group) ? &r_dst->vh[ idx_dst ] : &r_dst->v[ idx_dst ];
2983
- RRDR_VALUE_FLAGS *co = &r_dst->o[ idx_dst ];
2984
- NETDATA_DOUBLE *ar = &r_dst->ar[ idx_dst ];
2985
- uint32_t *gbc = &r_dst->gbc[ idx_dst ];
2986
-
2987
- switch(group_by_aggregate_function) {
2988
- default:
2989
- case RRDR_GROUP_BY_FUNCTION_AVERAGE:
2990
- case RRDR_GROUP_BY_FUNCTION_SUM:
2991
- case RRDR_GROUP_BY_FUNCTION_PERCENTAGE:
2992
- if(isnan(*cn))
2993
- *cn = n_tmp;
2994
- else
2995
- *cn += n_tmp;
2996
- break;
503
+ // before
504
+ , r->view.before
505
+ , before_wanted
506
+ , before_requested
507
+ , last_entry_s
508
2998
- case RRDR_GROUP_BY_FUNCTION_MIN:
2999
- if(isnan(*cn) || n_tmp < *cn)
3000
- *cn = n_tmp;
3001
- break;
509
+ // duration
510
+ , (long)(r->view.before - r->view.after + qt->window.query_granularity)
511
+ , (long)(before_wanted - after_wanted + qt->window.query_granularity)
512
+ , (long)before_requested - after_requested
513
+ , (long)((last_entry_s - first_entry_s) + qt->window.query_granularity)
514
3003
- case RRDR_GROUP_BY_FUNCTION_MAX:
3004
- if(isnan(*cn) || n_tmp > *cn)
3005
- *cn = n_tmp;
3006
- break;
3007
- }
515
+ // points
516
+ , r->rows
517
+ , points_wanted
518
+ , points_requested
519
3009
- if(!hidden_dimension_on_percentage_of_group) {
3010
- *co &= ~RRDR_VALUE_EMPTY;
3011
- *co |= (o_tmp & (RRDR_VALUE_RESET | RRDR_VALUE_PARTIAL));
3012
- *ar += ar_tmp;
3013
- (*gbc)++;
3014
- }
3015
- }
520
+ // message
521
+ , msg
522
+ );
523
}
524
+#endif // NETDATA_INTERNAL_CHECKS
525
3018
-static void rrdr2rrdr_group_by_partial_trimming(RRDR *r) {
3019
- time_t trimmable_after = r->partial_data_trimming.expected_after;
526
+// #define DEBUG_QUERY_LOGIC 1
527
3021
- // find the point just before the trimmable ones
3022
- ssize_t i = (ssize_t)r->n - 1;
3023
- for( ; i >= 0 ;i--) {
3024
- if (r->t[i] < trimmable_after)
3025
- break;
528
+#ifdef DEBUG_QUERY_LOGIC
529
+#define query_debug_log_init() BUFFER *debug_log = buffer_create(1000)
530
+#define query_debug_log(args...) buffer_sprintf(debug_log, ##args)
531
+#define query_debug_log_fin() { \
532
+ netdata_log_info("QUERY: '%s', after:%ld, before:%ld, duration:%ld, points:%zu, res:%ld - wanted => after:%ld, before:%ld, points:%zu, group:%zu, granularity:%ld, resgroup:%ld, resdiv:" NETDATA_DOUBLE_FORMAT_AUTO " %s", qt->id, after_requested, before_requested, before_requested - after_requested, points_requested, resampling_time_requested, after_wanted, before_wanted, points_wanted, group, query_granularity, resampling_group, resampling_divisor, buffer_tostring(debug_log)); \
533
+ buffer_free(debug_log); \
534
+ debug_log = NULL; \
535
}
536
+#define query_debug_log_free() do { buffer_free(debug_log); } while(0)
537
+#else
538
+#define query_debug_log_init() debug_dummy()
539
+#define query_debug_log(args...) debug_dummy()
540
+#define query_debug_log_fin() debug_dummy()
541
+#define query_debug_log_free() debug_dummy()
542
+#endif
543
+
544
+bool query_target_calculate_window(QUERY_TARGET *qt) {
545
+ if (unlikely(!qt)) return false;
546
3028
- if(unlikely(i < 0))
3029
- return;
547
+ size_t points_requested = (long)qt->request.points;
548
+ time_t after_requested = qt->request.after;
549
+ time_t before_requested = qt->request.before;
550
+ RRDR_TIME_GROUPING group_method = qt->request.time_group_method;
551
+ time_t resampling_time_requested = qt->request.resampling_time;
552
+ RRDR_OPTIONS options = qt->window.options;
553
+ size_t tier = qt->request.tier;
554
+ time_t update_every = qt->db.minimum_latest_update_every_s ? qt->db.minimum_latest_update_every_s : 1;
555
3031
- // internal_error(true, "Found trimmable index %zd (from 0 to %zu)", i, r->n - 1);
556
+ // RULES
557
+ // points_requested = 0
558
+ // the user wants all the natural points the database has
559
+ //
560
+ // after_requested = 0
561
+ // the user wants to start the query from the oldest point in our database
562
+ //
563
+ // before_requested = 0
564
+ // the user wants the query to end to the latest point in our database
565
+ //
566
+ // when natural points are wanted, the query has to be aligned to the update_every
567
+ // of the database
568
3033
- size_t last_row_gbc = 0;
3034
- for (; i < (ssize_t)r->n; i++) {
3035
- size_t row_gbc = 0;
3036
- for (size_t d = 0; d < r->d; d++) {
3037
- if (unlikely(!(r->od[d] & RRDR_DIMENSION_QUERIED)))
3038
- continue;
569
+ size_t points_wanted = points_requested;
570
+ time_t after_wanted = after_requested;
571
+ time_t before_wanted = before_requested;
572
3040
- row_gbc += r->gbc[ i * r->d + d ];
3041
- }
573
+ bool aligned = !(options & RRDR_OPTION_NOT_ALIGNED);
574
+ bool automatic_natural_points = (points_wanted == 0);
575
+ bool relative_period_requested = false;
576
+ bool natural_points = (options & RRDR_OPTION_NATURAL_POINTS) || automatic_natural_points;
577
+ bool before_is_aligned_to_db_end = false;
578
3043
- // internal_error(true, "GBC of index %zd is %zu", i, row_gbc);
579
+ query_debug_log_init();
580
3045
- if (unlikely(r->t[i] >= trimmable_after && (row_gbc < last_row_gbc || !row_gbc))) {
3046
- // discard the rest of the points
3047
- // internal_error(true, "Discarding points %zd to %zu", i, r->n - 1);
3048
- r->partial_data_trimming.trimmed_after = r->t[i];
3049
- r->rows = i;
3050
- break;
3051
- }
3052
- else
3053
- last_row_gbc = row_gbc;
581
+ if (ABS(before_requested) <= API_RELATIVE_TIME_MAX || ABS(after_requested) <= API_RELATIVE_TIME_MAX) {
582
+ relative_period_requested = true;
583
+ natural_points = true;
584
+ options |= RRDR_OPTION_NATURAL_POINTS;
585
+ query_debug_log(":relative+natural");
586
}
3055
-}
3056
-
3057
-static void rrdr2rrdr_group_by_calculate_percentage_of_group(RRDR *r) {
3058
- if(!r->vh)
3059
- return;
3060
-
3061
- if(query_target_aggregatable(r->internal.qt) && query_has_group_by_aggregation_percentage(r->internal.qt))
3062
- return;
3063
-
3064
- for(size_t i = 0; i < r->n ;i++) {
3065
- NETDATA_DOUBLE *cn = &r->v[ i * r->d ];
3066
- NETDATA_DOUBLE *ch = &r->vh[ i * r->d ];
587
3068
- for(size_t d = 0; d < r->d ;d++) {
3069
- NETDATA_DOUBLE n = cn[d];
3070
- NETDATA_DOUBLE h = ch[d];
3071
-
3072
- if(isnan(n))
3073
- cn[d] = 0.0;
588
+ // if the user wants virtual points, make sure we do it
589
+ if (options & RRDR_OPTION_VIRTUAL_POINTS)
590
+ natural_points = false;
591
3075
- else if(isnan(h))
3076
- cn[d] = 100.0;
592
+ // set the right flag about natural and virtual points
593
+ if (natural_points) {
594
+ options |= RRDR_OPTION_NATURAL_POINTS;
595
3078
- else
3079
- cn[d] = n * 100.0 / (n + h);
3080
- }
596
+ if (options & RRDR_OPTION_VIRTUAL_POINTS)
597
+ options &= ~RRDR_OPTION_VIRTUAL_POINTS;
598
}
3082
-}
599
+ else {
600
+ options |= RRDR_OPTION_VIRTUAL_POINTS;
601
3084
-static void rrd2rrdr_convert_values_to_percentage_of_total(RRDR *r) {
3085
- if(!(r->internal.qt->window.options & RRDR_OPTION_PERCENTAGE) || query_target_aggregatable(r->internal.qt))
3086
- return;
602
+ if (options & RRDR_OPTION_NATURAL_POINTS)
603
+ options &= ~RRDR_OPTION_NATURAL_POINTS;
604
+ }
605
3088
- size_t global_min_max_values = 0;
3089
- NETDATA_DOUBLE global_min = NAN, global_max = NAN;
606
+ if (after_wanted == 0 || before_wanted == 0) {
607
+ relative_period_requested = true;
608
3091
- for(size_t i = 0; i != r->n ;i++) {
3092
- NETDATA_DOUBLE *cn = &r->v[ i * r->d ];
3093
- RRDR_VALUE_FLAGS *co = &r->o[ i * r->d ];
609
+ time_t first_entry_s = qt->db.first_time_s;
610
+ time_t last_entry_s = qt->db.last_time_s;
611
3095
- NETDATA_DOUBLE total = 0;
3096
- for (size_t d = 0; d < r->d; d++) {
3097
- if (unlikely(!(r->od[d] & RRDR_DIMENSION_QUERIED)))
3098
- continue;
612
+ if (first_entry_s == 0 || last_entry_s == 0) {
613
+ internal_error(true, "QUERY: no data detected on query '%s' (db first_entry_t = %ld, last_entry_t = %ld)", qt->id, first_entry_s, last_entry_s);
614
+ after_wanted = qt->window.after;
615
+ before_wanted = qt->window.before;
616
3100
- if(co[d] & RRDR_VALUE_EMPTY)
3101
- continue;
617
+ if(after_wanted == before_wanted)
618
+ after_wanted = before_wanted - update_every;
619
3103
- total += cn[d];
620
+ if (points_wanted == 0) {
621
+ points_wanted = (before_wanted - after_wanted) / update_every;
622
+ query_debug_log(":zero points_wanted %zu", points_wanted);
623
+ }
624
}
625
+ else {
626
+ query_debug_log(":first_entry_t %ld, last_entry_t %ld", first_entry_s, last_entry_s);
627
3106
- if(total == 0.0)
3107
- total = 1.0;
3108
-
3109
- for (size_t d = 0; d < r->d; d++) {
3110
- if (unlikely(!(r->od[d] & RRDR_DIMENSION_QUERIED)))
3111
- continue;
3112
-
3113
- if(co[d] & RRDR_VALUE_EMPTY)
3114
- continue;
628
+ if (after_wanted == 0) {
629
+ after_wanted = first_entry_s;
630
+ query_debug_log(":zero after_wanted %ld", after_wanted);
631
+ }
632
3116
- NETDATA_DOUBLE n = cn[d];
3117
- n = cn[d] = n * 100.0 / total;
633
+ if (before_wanted == 0) {
634
+ before_wanted = last_entry_s;
635
+ before_is_aligned_to_db_end = true;
636
+ query_debug_log(":zero before_wanted %ld", before_wanted);
637
+ }
638
3119
- if(unlikely(!global_min_max_values++))
3120
- global_min = global_max = n;
3121
- else {
3122
- if(n < global_min)
3123
- global_min = n;
3124
- if(n > global_max)
3125
- global_max = n;
639
+ if (points_wanted == 0) {
640
+ points_wanted = (last_entry_s - first_entry_s) / update_every;
641
+ query_debug_log(":zero points_wanted %zu", points_wanted);
642
}
643
}
644
}
645
3130
- r->view.min = global_min;
3131
- r->view.max = global_max;
3132
-
3133
- if(!r->dview)
3134
- // v1 query
3135
- return;
3136
-
3137
- // v2 query
3138
-
3139
- for (size_t d = 0; d < r->d; d++) {
3140
- if (unlikely(!(r->od[d] & RRDR_DIMENSION_QUERIED)))
3141
- continue;
3142
-
3143
- size_t count = 0;
3144
- NETDATA_DOUBLE min = 0.0, max = 0.0, sum = 0.0, ars = 0.0;
3145
- for(size_t i = 0; i != r->rows ;i++) { // we use r->rows to respect trimming
3146
- size_t idx = i * r->d + d;
3147
-
3148
- RRDR_VALUE_FLAGS o = r->o[ idx ];
646
+ if (points_wanted == 0) {
647
+ points_wanted = 600;
648
+ query_debug_log(":zero600 points_wanted %zu", points_wanted);
649
+ }
650
3150
- if (o & RRDR_VALUE_EMPTY)
3151
- continue;
651
+ // convert our before_wanted and after_wanted to absolute
652
+ rrdr_relative_window_to_absolute_query(&after_wanted, &before_wanted, NULL, unittest_running);
653
+ query_debug_log(":relative2absolute after %ld, before %ld", after_wanted, before_wanted);
654
3153
- NETDATA_DOUBLE ar = r->ar[ idx ];
3154
- ars += ar;
655
+ if (natural_points && (options & RRDR_OPTION_SELECTED_TIER) && tier > 0 && nd_profile.storage_tiers > 1) {
656
+ update_every = rrdset_find_natural_update_every_for_timeframe(
657
+ qt, after_wanted, before_wanted, points_wanted, options, tier);
658
3156
- NETDATA_DOUBLE n = r->v[ idx ];
3157
- sum += n;
659
+ if (update_every <= 0) update_every = qt->db.minimum_latest_update_every_s;
660
+ query_debug_log(":natural update every %ld", update_every);
661
+ }
662
3159
- if(!count++)
3160
- min = max = n;
3161
- else {
3162
- if(n < min)
3163
- min = n;
3164
- if(n > max)
3165
- max = n;
3166
- }
3167
- }
663
+ // this is the update_every of the query
664
+ // it may be different to the update_every of the database
665
+ time_t query_granularity = (natural_points) ? update_every : 1;
666
+ if (query_granularity <= 0) query_granularity = 1;
667
+ query_debug_log(":query_granularity %ld", query_granularity);
668
3169
- r->dview[d] = (STORAGE_POINT) {
3170
- .sum = sum,
3171
- .count = count,
3172
- .min = min,
3173
- .max = max,
3174
- .anomaly_count = (size_t)(ars * (NETDATA_DOUBLE)count),
3175
- };
669
+ // align before_wanted and after_wanted to query_granularity
670
+ if (before_wanted % query_granularity) {
671
+ before_wanted -= before_wanted % query_granularity;
672
+ query_debug_log(":granularity align before_wanted %ld", before_wanted);
673
}
3177
-}
3178
-
3179
-static RRDR *rrd2rrdr_group_by_finalize(RRDR *r_tmp) {
3180
- QUERY_TARGET *qt = r_tmp->internal.qt;
674
3182
- if(!r_tmp->group_by.r) {
3183
- // v1 query
3184
- rrd2rrdr_convert_values_to_percentage_of_total(r_tmp);
3185
- return r_tmp;
675
+ if (after_wanted % query_granularity) {
676
+ after_wanted -= after_wanted % query_granularity;
677
+ query_debug_log(":granularity align after_wanted %ld", after_wanted);
678
}
3187
- // v2 query
3188
-
3189
- // do the additional passes on RRDRs
3190
- RRDR *last_r = r_tmp->group_by.r;
3191
- rrdr2rrdr_group_by_calculate_percentage_of_group(last_r);
3192
-
3193
- RRDR *r = last_r->group_by.r;
3194
- size_t pass = 0;
3195
- while(r) {
3196
- pass++;
3197
- for(size_t d = 0; d < last_r->d ;d++) {
3198
- rrd2rrdr_group_by_add_metric(r, last_r->dgbs[d], last_r, d,
3199
- qt->request.group_by[pass].aggregation,
3200
- &last_r->dqp[d], pass);
3201
- }
3202
- rrdr2rrdr_group_by_calculate_percentage_of_group(r);
679
3204
- last_r = r;
3205
- r = last_r->group_by.r;
680
+ // automatic_natural_points is set when the user wants all the points available in the database
681
+ if (automatic_natural_points) {
682
+ points_wanted = (before_wanted - after_wanted + 1) / query_granularity;
683
+ if (unlikely(points_wanted <= 0)) points_wanted = 1;
684
+ query_debug_log(":auto natural points_wanted %zu", points_wanted);
685
}
686
3208
- // free all RRDRs except the last one
3209
- r = r_tmp;
3210
- while(r != last_r) {
3211
- r_tmp = r->group_by.r;
3212
- r->group_by.r = NULL;
3213
- rrdr_free(r->internal.owa, r);
3214
- r = r_tmp;
687
+ time_t duration = before_wanted - after_wanted;
688
+
689
+ // if the resampling time is too big, extend the duration to the past
690
+ if (unlikely(resampling_time_requested > duration)) {
691
+ after_wanted = before_wanted - resampling_time_requested;
692
+ duration = before_wanted - after_wanted;
693
+ query_debug_log(":resampling after_wanted %ld", after_wanted);
694
}
3216
- r = last_r;
3217
-
3218
- // find the final aggregation
3219
- RRDR_GROUP_BY_FUNCTION aggregation = qt->request.group_by[0].aggregation;
3220
- for(size_t g = 0; g < MAX_QUERY_GROUP_BY_PASSES ;g++)
3221
- if(qt->request.group_by[g].group_by != RRDR_GROUP_BY_NONE)
3222
- aggregation = qt->request.group_by[g].aggregation;
3223
-
3224
- if(!query_target_aggregatable(qt) && r->partial_data_trimming.expected_after < qt->window.before)
3225
- rrdr2rrdr_group_by_partial_trimming(r);
3226
-
3227
- // apply averaging, remove RRDR_VALUE_EMPTY, find the non-zero dimensions, min and max
3228
- size_t global_min_max_values = 0;
3229
- size_t dimensions_nonzero = 0;
3230
- NETDATA_DOUBLE global_min = NAN, global_max = NAN;
3231
- for (size_t d = 0; d < r->d; d++) {
3232
- if (unlikely(!(r->od[d] & RRDR_DIMENSION_QUERIED)))
3233
- continue;
695
3235
- size_t points_nonzero = 0;
3236
- NETDATA_DOUBLE min = 0, max = 0, sum = 0, ars = 0;
3237
- size_t count = 0;
696
+ // if the duration is not aligned to resampling time
697
+ // extend the duration to the past, to avoid a gap at the chart
698
+ // only when the missing duration is above 1/10th of a point
699
+ if (resampling_time_requested > query_granularity && duration % resampling_time_requested) {
700
+ time_t delta = duration % resampling_time_requested;
701
+ if (delta > resampling_time_requested / 10) {
702
+ after_wanted -= resampling_time_requested - delta;
703
+ duration = before_wanted - after_wanted;
704
+ query_debug_log(":resampling2 after_wanted %ld", after_wanted);
705
+ }
706
+ }
707
3239
- for(size_t i = 0; i != r->n ;i++) {
3240
- size_t idx = i * r->d + d;
708
+ // the available points of the query
709
+ size_t points_available = (duration + 1) / query_granularity;
710
+ if (unlikely(points_available <= 0)) points_available = 1;
711
+ query_debug_log(":points_available %zu", points_available);
712
3242
- NETDATA_DOUBLE *cn = &r->v[ idx ];
3243
- RRDR_VALUE_FLAGS *co = &r->o[ idx ];
3244
- NETDATA_DOUBLE *ar = &r->ar[ idx ];
3245
- uint32_t gbc = r->gbc[ idx ];
713
+ if (points_wanted > points_available) {
714
+ points_wanted = points_available;
715
+ query_debug_log(":max points_wanted %zu", points_wanted);
716
+ }
717
3247
- if(likely(gbc)) {
3248
- *co &= ~RRDR_VALUE_EMPTY;
718
+ if(points_wanted > 86400 && !unittest_running) {
719
+ points_wanted = 86400;
720
+ query_debug_log(":absolute max points_wanted %zu", points_wanted);
721
+ }
722
3250
- if(gbc != r->dgbc[d])
3251
- *co |= RRDR_VALUE_PARTIAL;
723
+ // calculate the desired grouping of source data points
724
+ size_t group = points_available / points_wanted;
725
+ if (group == 0) group = 1;
726
3253
- NETDATA_DOUBLE n;
727
+ // round "group" to the closest integer
728
+ if (points_available % points_wanted > points_wanted / 2)
729
+ group++;
730
3255
- sum += *cn;
3256
- ars += *ar;
731
+ query_debug_log(":group %zu", group);
732
3258
- if(aggregation == RRDR_GROUP_BY_FUNCTION_AVERAGE && !query_target_aggregatable(qt))
3259
- n = (*cn /= gbc);
3260
- else
3261
- n = *cn;
733
+ if (points_wanted * group * query_granularity < (size_t)duration) {
734
+ // the grouping we are going to do, is not enough
735
+ // to cover the entire duration requested, so
736
+ // we have to change the number of points, to make sure we will
737
+ // respect the timeframe as closely as possibly
738
3263
- if(!query_target_aggregatable(qt))
3264
- *ar /= gbc;
739
+ // let's see how many points are the optimal
740
+ points_wanted = points_available / group;
741
3266
- if(islessgreater(n, 0.0))
3267
- points_nonzero++;
742
+ if (points_wanted * group < points_available)
743
+ points_wanted++;
744
3269
- if(unlikely(!count))
3270
- min = max = n;
3271
- else {
3272
- if(n < min)
3273
- min = n;
745
+ if (unlikely(points_wanted == 0))
746
+ points_wanted = 1;
747
3275
- if(n > max)
3276
- max = n;
3277
- }
748
+ query_debug_log(":optimal points %zu", points_wanted);
749
+ }
750
3279
- if(unlikely(!global_min_max_values++))
3280
- global_min = global_max = n;
3281
- else {
3282
- if(n < global_min)
3283
- global_min = n;
751
+ // resampling_time_requested enforces a certain grouping multiple
752
+ NETDATA_DOUBLE resampling_divisor = 1.0;
753
+ size_t resampling_group = 1;
754
+ if (unlikely(resampling_time_requested > query_granularity)) {
755
+ // the points we should group to satisfy gtime
756
+ resampling_group = resampling_time_requested / query_granularity;
757
+ if (unlikely(resampling_time_requested % query_granularity))
758
+ resampling_group++;
759
3285
- if(n > global_max)
3286
- global_max = n;
3287
- }
760
+ query_debug_log(":resampling group %zu", resampling_group);
761
3289
- count += gbc;
3290
- }
762
+ // adapt group according to resampling_group
763
+ if (unlikely(group < resampling_group)) {
764
+ group = resampling_group; // do not allow grouping below the desired one
765
+ query_debug_log(":group less res %zu", group);
766
}
3292
-
3293
- if(points_nonzero) {
3294
- r->od[d] |= RRDR_DIMENSION_NONZERO;
3295
- dimensions_nonzero++;
767
+ if (unlikely(group % resampling_group)) {
768
+ group += resampling_group - (group % resampling_group); // make sure group is multiple of resampling_group
769
+ query_debug_log(":group mod res %zu", group);
770
}
771
3298
- r->dview[d] = (STORAGE_POINT) {
3299
- .sum = sum,
3300
- .count = count,
3301
- .min = min,
3302
- .max = max,
3303
- .anomaly_count = (size_t)(ars * RRDR_DVIEW_ANOMALY_COUNT_MULTIPLIER / 100.0),
3304
- };
772
+ // resampling_divisor = group / resampling_group;
773
+ resampling_divisor = (NETDATA_DOUBLE) (group * query_granularity) / (NETDATA_DOUBLE) resampling_time_requested;
774
+ query_debug_log(":resampling divisor " NETDATA_DOUBLE_FORMAT, resampling_divisor);
775
}
776
3307
- r->view.min = global_min;
3308
- r->view.max = global_max;
3309
-
3310
- if(!dimensions_nonzero && (qt->window.options & RRDR_OPTION_NONZERO)) {
3311
- // all dimensions are zero
3312
- // remove the nonzero option
3313
- qt->window.options &= ~RRDR_OPTION_NONZERO;
777
+ // now that we have group, align the requested timeframe to fit it.
778
+ if (aligned && before_wanted % (group * query_granularity)) {
779
+ if (before_is_aligned_to_db_end)
780
+ before_wanted -= before_wanted % (time_t)(group * query_granularity);
781
+ else
782
+ before_wanted += (time_t)(group * query_granularity) - before_wanted % (time_t)(group * query_granularity);
783
+ query_debug_log(":align before_wanted %ld", before_wanted);
784
}
785
3316
- rrd2rrdr_convert_values_to_percentage_of_total(r);
786
+ after_wanted = before_wanted - (time_t)(points_wanted * group * query_granularity) + query_granularity;
787
+ query_debug_log(":final after_wanted %ld", after_wanted);
788
+
789
+ duration = before_wanted - after_wanted;
790
+ query_debug_log(":final duration %ld", duration + 1);
791
3318
- // update query instance counts in query host and query context
3319
- {
3320
- size_t h = 0, c = 0, i = 0;
3321
- for(; h < qt->nodes.used ; h++) {
3322
- QUERY_NODE *qn = &qt->nodes.array[h];
792
+ query_debug_log_fin();
793
3324
- for(; c < qt->contexts.used ;c++) {
3325
- QUERY_CONTEXT *qc = &qt->contexts.array[c];
794
+ internal_error(points_wanted != duration / (query_granularity * group) + 1,
795
+ "QUERY: points_wanted %zu is not points %zu",
796
+ points_wanted, (size_t)(duration / (query_granularity * group) + 1));
797
3327
- if(!rrdcontext_acquired_belongs_to_host(qc->rca, qn->rrdhost))
3328
- break;
798
+ internal_error(group < resampling_group,
799
+ "QUERY: group %zu is less than the desired group points %zu",
800
+ group, resampling_group);
801
3330
- for(; i < qt->instances.used ;i++) {
3331
- QUERY_INSTANCE *qi = &qt->instances.array[i];
802
+ internal_error(group > resampling_group && group % resampling_group,
803
+ "QUERY: group %zu is not a multiple of the desired group points %zu",
804
+ group, resampling_group);
805
3333
- if(!rrdinstance_acquired_belongs_to_context(qi->ria, qc->rca))
3334
- break;
806
+ // -------------------------------------------------------------------------
807
+ // update QUERY_TARGET with our calculations
808
3336
- if(qi->metrics.queried) {
3337
- qc->instances.queried++;
3338
- qn->instances.queried++;
3339
- }
3340
- else if(qi->metrics.failed) {
3341
- qc->instances.failed++;
3342
- qn->instances.failed++;
3343
- }
3344
- }
3345
- }
3346
- }
3347
- }
809
+ qt->window.after = after_wanted;
810
+ qt->window.before = before_wanted;
811
+ qt->window.relative = relative_period_requested;
812
+ qt->window.points = points_wanted;
813
+ qt->window.group = group;
814
+ qt->window.time_group_method = group_method;
815
+ qt->window.time_group_options = qt->request.time_group_options;
816
+ qt->window.query_granularity = query_granularity;
817
+ qt->window.resampling_group = resampling_group;
818
+ qt->window.resampling_divisor = resampling_divisor;
819
+ qt->window.options = options;
820
+ qt->window.tier = tier;
821
+ qt->window.aligned = aligned;
822
3349
- return r;
823
+ return true;
824
}
825
826
// ----------------------------------------------------------------------------
902
size_t last_db_points_read = 0;
903
size_t last_result_points_generated = 0;
904
3431
- internal_fatal(released_ops, "QUERY: released_ops should be NULL when the query starts");
905
+ // internal_fatal(released_ops, "QUERY: released_ops should be NULL when the query starts");
906
907
query_progress_set_finish_line(qt->request.transaction, qt->query.used);
908
1086
1087
// get the final RRDR to send to the caller
1088
r = rrd2rrdr_group_by_finalize(r_tmp);
1089
+
1090
+ // apply cardinality limit if requested
1091
+ r = rrd2rrdr_cardinality_limit(r);
1092
1093
#ifdef NETDATA_INTERNAL_CHECKS
1094
if (dimensions_used && !(r->view.flags & RRDR_RESULT_FLAG_CANCEL)) {
1142
ops[d] = NULL;
1143
}
1144
rrd2rrdr_query_ops_freeall(r);
3668
- internal_fatal(released_ops, "QUERY: released_ops should be NULL when the query ends");
1145
+ // internal_fatal(released_ops, "QUERY: released_ops should be NULL when the query ends");
1146
1147
onewayalloc_freez(owa, ops);
1148