/api/v1/weights endpoint (#13449)
* /api/v1/weights endpoints * high resolution anomaly rate in parallel with queries; points and options in /api/v1/weights reflect the truth * context printing * merged metric_correlations with weights API; added parameter tier to select the tier to run the query; weight api now returns points per tier; added swagger info about weights api * moved metric_correlations files to web/api/queries as weights * added contexts filtering; renamed correlated_dimensions; weights API is always enabled; code cleanup * allow returning zero results
Costa Tsaousis committed
Aug 1, 2022 at 21:47 UTC
ccf0f6b6f48f12a30c45dd31c2d4c279e660e304
24 files changed
+730
-181
CMakeLists.txt
+2
-2
@@ -774,8 +774,6 @@ set(RRD_PLUGIN_FILES
774
database/engine/metadata_log/compaction.h
775
database/KolmogorovSmirnovDist.c
776
database/KolmogorovSmirnovDist.h
777
- database/metric_correlations.c
778
- database/metric_correlations.h
777
)
778
779
set(WEB_PLUGIN_FILES
@@ -822,6 +820,8 @@ set(API_PLUGIN_FILES
820
web/api/queries/ses/ses.h
821
web/api/queries/des/des.c
822
web/api/queries/des/des.h
823
+ web/api/queries/weights.c
824
+ web/api/queries/weights.h
825
web/api/formatters/rrd2json.c
826
web/api/formatters/rrd2json.h
827
web/api/formatters/csv/csv.c
Makefile.am
+2
-2
@@ -473,8 +473,6 @@ RRD_PLUGIN_FILES = \
473
database/sqlite/sqlite3.h \
474
database/KolmogorovSmirnovDist.c \
475
database/KolmogorovSmirnovDist.h \
476
- database/metric_correlations.c \
477
- database/metric_correlations.h \
476
$(NULL)
477
478
database/sqlite/sqlite3.$(OBJEXT) : CFLAGS += -Wno-cast-function-type
@@ -599,6 +597,8 @@ API_PLUGIN_FILES = \
597
web/api/queries/stddev/stddev.h \
598
web/api/queries/sum/sum.c \
599
web/api/queries/sum/sum.h \
600
+ web/api/queries/weights.c \
601
+ web/api/queries/weights.h \
602
web/api/formatters/rrd2json.c \
603
web/api/formatters/rrd2json.h \
604
web/api/formatters/csv/csv.c \
configure.ac
+2
@@ -1681,6 +1681,8 @@ AC_CONFIG_FILES([
1681
database/Makefile
1682
database/engine/Makefile
1683
database/engine/metadata_log/Makefile
1684
+ database/ram/Makefile
1685
+ database/sqlite/Makefile
1686
diagrams/Makefile
1687
exporting/Makefile
1688
exporting/graphite/Makefile
daemon/common.h
-3
@@ -84,9 +84,6 @@
84
#include "commands.h"
85
#include "analytics.h"
86
87
-// metric correlations
88
-#include "database/metric_correlations.h"
89
-
87
// global netdata daemon variables
88
extern char *netdata_configured_hostname;
89
extern char *netdata_configured_user_config_dir;
daemon/main.c
+3
-1
@@ -713,7 +713,9 @@ static void get_netdata_configured_variables() {
713
// metric correlations
714
715
enable_metric_correlations = config_get_boolean(CONFIG_SECTION_GLOBAL, "enable metric correlations", enable_metric_correlations);
716
- default_metric_correlations_method = mc_string_to_method(config_get(CONFIG_SECTION_GLOBAL, "metric correlations method", mc_method_to_string(default_metric_correlations_method)));
716
+ default_metric_correlations_method = weights_string_to_method(config_get(
717
+ CONFIG_SECTION_GLOBAL, "metric correlations method",
718
+ weights_method_to_string(default_metric_correlations_method)));
719
720
// --------------------------------------------------------------------
721
database/Makefile.am
+2
@@ -5,6 +5,8 @@ MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
5
6
SUBDIRS = \
7
engine \
8
+ ram \
9
+ sqlite \
10
$(NULL)
11
12
dist_noinst_DATA = \
database/metric_correlations.h
deleted
-27
@@ -1,27 +0,0 @@
1
-// SPDX-License-Identifier: GPL-3.0-or-later
2
-
3
-#ifndef NETDATA_METRIC_CORRELATIONS_H
4
-#define NETDATA_METRIC_CORRELATIONS_H 1
5
-
6
-#include "web/api/queries/query.h"
7
-
8
-typedef enum {
9
- METRIC_CORRELATIONS_KS2 = 1,
10
- METRIC_CORRELATIONS_VOLUME = 2,
11
-} METRIC_CORRELATIONS_METHOD;
12
-
13
-extern int enable_metric_correlations;
14
-extern int metric_correlations_version;
15
-extern METRIC_CORRELATIONS_METHOD default_metric_correlations_method;
16
-
17
-extern int metric_correlations (RRDHOST *host, BUFFER *wb, METRIC_CORRELATIONS_METHOD method,
18
- RRDR_GROUPING group, const char *group_options,
19
- long long baseline_after, long long baseline_before,
20
- long long after, long long before,
21
- long long points, RRDR_OPTIONS options, int timeout);
22
-
23
-extern METRIC_CORRELATIONS_METHOD mc_string_to_method(const char *method);
24
-extern const char *mc_method_to_string(METRIC_CORRELATIONS_METHOD method);
25
-extern int mc_unittest(void);
26
-
27
-#endif //NETDATA_METRIC_CORRELATIONS_H
database/ram/Makefile.am
new
+11
@@ -0,0 +1,11 @@
1
+# SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+AUTOMAKE_OPTIONS = subdir-objects
4
+MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
5
+
6
+SUBDIRS = \
7
+ $(NULL)
8
+
9
+dist_noinst_DATA = \
10
+ README.md \
11
+ $(NULL)
database/ram/README.md
new
+7
@@ -0,0 +1,7 @@
1
+<!--
2
+title: "RAM database modes"
3
+description: "Netdata's RAM database modes."
4
+custom_edit_url: https://github.com/netdata/netdata/edit/master/database/ram/README.md
5
+-->
6
+
7
+# RAM modes
\ No newline at end of file
health/health.c
+1
-1
@@ -894,7 +894,7 @@ void *health_main(void *ptr) {
894
rc->after, rc->before, rc->group, NULL,
895
0, rc->options,
896
&rc->db_after,&rc->db_before,
897
- NULL, NULL,
897
+ NULL, NULL, NULL,
898
&value_is_null, NULL, 0, 0);
899
900
if (unlikely(ret != 200)) {
web/api/badges/web_buffer_svg.c
+1
-1
@@ -1110,7 +1110,7 @@ int web_client_api_request_v1_badge(RRDHOST *host, struct web_client *w, char *u
1110
(dimensions) ? buffer_tostring(dimensions) : NULL,
1111
points, after, before, group, group_options, 0, options,
1112
NULL, &latest_timestamp,
1113
- NULL, NULL,
1113
+ NULL, NULL, NULL,
1114
&value_is_null, NULL, 0, 0);
1115
1116
// if the value cannot be calculated, show empty badge
web/api/formatters/json/json.c
+3
-3
@@ -162,7 +162,7 @@ void rrdr2json(RRDR *r, BUFFER *wb, RRDR_OPTIONS options, int datatable, struct
162
for(i = start; i != end ;i += step) {
163
NETDATA_DOUBLE *cn = &r->v[ i * r->d ];
164
RRDR_VALUE_FLAGS *co = &r->o[ i * r->d ];
165
- uint8_t *ar = &r->ar[ i * r->d ];
165
+ NETDATA_DOUBLE *ar = &r->ar[ i * r->d ];
166
167
time_t now = r->t[i];
168
@@ -225,7 +225,7 @@ void rrdr2json(RRDR *r, BUFFER *wb, RRDR_OPTIONS options, int datatable, struct
225
for(c = 0, rd = temp_rd?temp_rd:r->st->dimensions; rd && c < r->d ;c++, rd = rd->next) {
226
NETDATA_DOUBLE n;
227
if(unlikely(options & RRDR_OPTION_INTERNAL_AR))
228
- n = (NETDATA_DOUBLE)ar[c] / 2.0; // rrdr stores anomaly rates 0 - 200
228
+ n = ar[c];
229
else
230
n = cn[c];
231
@@ -246,7 +246,7 @@ void rrdr2json(RRDR *r, BUFFER *wb, RRDR_OPTIONS options, int datatable, struct
246
247
NETDATA_DOUBLE n;
248
if(unlikely(options & RRDR_OPTION_INTERNAL_AR))
249
- n = (NETDATA_DOUBLE)ar[c] / 2.0; // rrdr stores anomaly rates 0 - 200
249
+ n = ar[c];
250
else
251
n = cn[c];
252
web/api/formatters/rrd2json.c
+7
-1
@@ -193,9 +193,10 @@ int rrdset2value_api_v1(
193
, time_t *db_after
194
, time_t *db_before
195
, size_t *db_points_read
196
+ , size_t *db_points_per_tier
197
, size_t *result_points_generated
198
, int *value_is_null
198
- , uint8_t *anomaly_rate
199
+ , NETDATA_DOUBLE *anomaly_rate
200
, int timeout
201
, int tier
202
) {
@@ -216,6 +217,11 @@ int rrdset2value_api_v1(
217
if(db_points_read)
218
*db_points_read += r->internal.db_points_read;
219
220
+ if(db_points_per_tier) {
221
+ for(int t = 0; t < storage_tiers ;t++)
222
+ db_points_per_tier[t] += r->internal.tier_points_read[t];
223
+ }
224
+
225
if(result_points_generated)
226
*result_points_generated += r->internal.result_points_generated;
227
web/api/formatters/rrd2json.h
+2
-1
@@ -96,9 +96,10 @@ extern int rrdset2value_api_v1(
96
, time_t *db_after
97
, time_t *db_before
98
, size_t *db_points_read
99
+ , size_t *db_points_per_tier
100
, size_t *result_points_generated
101
, int *value_is_null
101
- , uint8_t *anomaly_rate
102
+ , NETDATA_DOUBLE *anomaly_rate
103
, int timeout
104
, int tier
105
);
web/api/formatters/value/value.c
+3
-3
@@ -3,19 +3,19 @@
3
#include "value.h"
4
5
6
-inline NETDATA_DOUBLE rrdr2value(RRDR *r, long i, RRDR_OPTIONS options, int *all_values_are_null, uint8_t *anomaly_rate, RRDDIM *temp_rd) {
6
+inline NETDATA_DOUBLE rrdr2value(RRDR *r, long i, RRDR_OPTIONS options, int *all_values_are_null, NETDATA_DOUBLE *anomaly_rate, RRDDIM *temp_rd) {
7
long c;
8
RRDDIM *d;
9
10
NETDATA_DOUBLE *cn = &r->v[ i * r->d ];
11
RRDR_VALUE_FLAGS *co = &r->o[ i * r->d ];
12
- uint8_t *ar = &r->ar[ i * r->d ];
12
+ NETDATA_DOUBLE *ar = &r->ar[ i * r->d ];
13
14
NETDATA_DOUBLE sum = 0, min = 0, max = 0, v;
15
int all_null = 1, init = 1;
16
17
NETDATA_DOUBLE total = 1;
18
- size_t total_anomaly_rate = 0;
18
+ NETDATA_DOUBLE total_anomaly_rate = 0;
19
20
int set_min_max = 0;
21
if(unlikely(options & RRDR_OPTION_PERCENTAGE)) {
web/api/formatters/value/value.h
+1
-2
@@ -5,7 +5,6 @@
5
6
#include "../rrd2json.h"
7
8
-extern NETDATA_DOUBLE
9
-rrdr2value(RRDR *r, long i, RRDR_OPTIONS options, int *all_values_are_null, uint8_t *anomaly_rate, RRDDIM *temp_rd);
8
+extern NETDATA_DOUBLE rrdr2value(RRDR *r, long i, RRDR_OPTIONS options, int *all_values_are_null, NETDATA_DOUBLE *anomaly_rate, RRDDIM *temp_rd);
9
10
#endif //NETDATA_API_FORMATTER_VALUE_H
web/api/netdata-swagger.yaml
+255
-1
@@ -1100,7 +1100,8 @@ paths:
1100
/metric_correlations:
1101
get:
1102
summary: "Analyze all the metrics to find their correlations"
1103
- description: "Given two time-windows (baseline, highlight), it goes
1103
+ description: "THIS ENDPOINT IS OBSOLETE. Use the /weights endpoint.
1104
+ Given two time-windows (baseline, highlight), it goes
1105
through all the available metrics, querying both windows and tries to find
1106
how these two windows relate to each other. It supports
1107
multiple algorithms to do so. The result is a list of all
@@ -1262,6 +1263,181 @@ paths:
1263
that correlated the metrics did not produce any result.
1264
"504":
1265
description: Timeout - the query took too long and has been cancelled.
1266
+ /weights:
1267
+ get:
1268
+ summary: "Analyze all the metrics using an algorithm and score them accordingly"
1269
+ description: "This endpoint goes through all metrics and scores them according to an algorithm."
1270
+ parameters:
1271
+ - name: baseline_after
1272
+ in: query
1273
+ description: This parameter can either be an absolute timestamp specifying the
1274
+ starting point of baseline window, or a relative number of
1275
+ seconds (negative, relative to parameter baseline_before). Netdata will
1276
+ assume it is a relative number if it is less that 3 years (in seconds).
1277
+ This parameter is used in KS2 and VOLUME algorithms.
1278
+ required: false
1279
+ allowEmptyValue: false
1280
+ schema:
1281
+ type: number
1282
+ format: integer
1283
+ default: -300
1284
+ - name: baseline_before
1285
+ in: query
1286
+ description: This parameter can either be an absolute timestamp specifying the
1287
+ ending point of the baseline window, or a relative number of
1288
+ seconds (negative), relative to the last collected timestamp.
1289
+ Netdata will assume it is a relative number if it is less than 3
1290
+ years (in seconds).
1291
+ This parameter is used in KS2 and VOLUME algorithms.
1292
+ required: false
1293
+ schema:
1294
+ type: number
1295
+ format: integer
1296
+ default: -60
1297
+ - name: after
1298
+ in: query
1299
+ description: This parameter can either be an absolute timestamp specifying the
1300
+ starting point of highlighted window, or a relative number of
1301
+ seconds (negative, relative to parameter highlight_before). Netdata will
1302
+ assume it is a relative number if it is less that 3 years (in seconds).
1303
+ required: false
1304
+ allowEmptyValue: false
1305
+ schema:
1306
+ type: number
1307
+ format: integer
1308
+ default: -60
1309
+ - name: before
1310
+ in: query
1311
+ description: This parameter can either be an absolute timestamp specifying the
1312
+ ending point of the highlighted window, or a relative number of
1313
+ seconds (negative), relative to the last collected timestamp.
1314
+ Netdata will assume it is a relative number if it is less than 3
1315
+ years (in seconds).
1316
+ required: false
1317
+ schema:
1318
+ type: number
1319
+ format: integer
1320
+ default: 0
1321
+ - name: context
1322
+ in: query
1323
+ description: A simple pattern matching the contexts to evaluate.
1324
+ required: false
1325
+ allowEmptyValue: false
1326
+ schema:
1327
+ type: string
1328
+ - name: points
1329
+ in: query
1330
+ description: The number of points to be evaluated for the highlighted window.
1331
+ The baseline window will be adjusted automatically to receive a proportional
1332
+ amount of points.
1333
+ This parameter is only used by the KS2 algorithm.
1334
+ required: false
1335
+ allowEmptyValue: false
1336
+ schema:
1337
+ type: number
1338
+ format: integer
1339
+ default: 500
1340
+ - name: method
1341
+ in: query
1342
+ description: the algorithm to run
1343
+ required: false
1344
+ schema:
1345
+ type: string
1346
+ enum:
1347
+ - ks2
1348
+ - volume
1349
+ - anomaly-rate
1350
+ default: anomaly-rate
1351
+ - name: tier
1352
+ in: query
1353
+ description: Use the specified database tier
1354
+ required: false
1355
+ allowEmptyValue: false
1356
+ schema:
1357
+ type: number
1358
+ format: integer
1359
+ - name: timeout
1360
+ in: query
1361
+ description: Cancel the query if to takes more that this amount of milliseconds.
1362
+ required: false
1363
+ allowEmptyValue: false
1364
+ schema:
1365
+ type: number
1366
+ format: integer
1367
+ default: 60000
1368
+ - name: options
1369
+ in: query
1370
+ description: Options that affect data generation.
1371
+ required: false
1372
+ allowEmptyValue: false
1373
+ schema:
1374
+ type: array
1375
+ items:
1376
+ type: string
1377
+ enum:
1378
+ - min2max
1379
+ - abs
1380
+ - absolute
1381
+ - absolute-sum
1382
+ - null2zero
1383
+ - percentage
1384
+ - unaligned
1385
+ - nonzero
1386
+ - anomaly-bit
1387
+ - raw
1388
+ default:
1389
+ - null2zero
1390
+ - nonzero
1391
+ - unaligned
1392
+ - name: group
1393
+ in: query
1394
+ description: The grouping method. If multiple collected values are to be grouped
1395
+ in order to return fewer points, this parameters defines the method
1396
+ of grouping. methods supported "min", "max", "average", "sum",
1397
+ "incremental-sum". "max" is actually calculated on the absolute
1398
+ value collected (so it works for both positive and negative
1399
+ dimensions to return the most extreme value in either direction).
1400
+ required: true
1401
+ allowEmptyValue: false
1402
+ schema:
1403
+ type: string
1404
+ enum:
1405
+ - min
1406
+ - max
1407
+ - average
1408
+ - median
1409
+ - stddev
1410
+ - sum
1411
+ - incremental-sum
1412
+ - ses
1413
+ - des
1414
+ - cv
1415
+ - countif
1416
+ default: average
1417
+ - name: group_options
1418
+ in: query
1419
+ description: When the group function supports additional parameters, this field
1420
+ can be used to pass them to it. Currently only "countif" supports this.
1421
+ required: false
1422
+ allowEmptyValue: false
1423
+ schema:
1424
+ type: string
1425
+ responses:
1426
+ "200":
1427
+ description: JSON object with weights for each context, chart and dimension.
1428
+ content:
1429
+ application/json:
1430
+ schema:
1431
+ $ref: "#/components/schemas/weight"
1432
+ "400":
1433
+ description: The given parameters are invalid.
1434
+ "403":
1435
+ description: metrics correlations are not enabled on this Netdata Agent.
1436
+ "404":
1437
+ description: No charts could be found, or the method
1438
+ that correlated the metrics did not produce any result.
1439
+ "504":
1440
+ description: Timeout - the query took too long and has been cancelled.
1441
servers:
1442
- url: https://registry.my-netdata.io/api/v1
1443
- url: http://registry.my-netdata.io/api/v1
@@ -2197,3 +2373,81 @@ components:
2373
type: number
2374
dimension2-name:
2375
type: number
2376
+ weight:
2377
+ type: object
2378
+ properties:
2379
+ after:
2380
+ description: the start time of the highlighted window
2381
+ type: integer
2382
+ before:
2383
+ description: the end time of the highlighted window
2384
+ type: integer
2385
+ duration:
2386
+ description: the duration of the highlighted window
2387
+ type: integer
2388
+ points:
2389
+ description: the points of the highlighted window
2390
+ type: integer
2391
+ baseline_after:
2392
+ description: the start time of the baseline window
2393
+ type: integer
2394
+ baseline_before:
2395
+ description: the end time of the baseline window
2396
+ type: integer
2397
+ baseline_duration:
2398
+ description: the duration of the baseline window
2399
+ type: integer
2400
+ baseline_points:
2401
+ description: the points of the baseline window
2402
+ type: integer
2403
+ group:
2404
+ description: the grouping method across time
2405
+ type: string
2406
+ method:
2407
+ description: the correlation method used
2408
+ type: string
2409
+ options:
2410
+ description: a comma separated list of the query options set
2411
+ type: string
2412
+ correlated_dimensions:
2413
+ description: the number of dimensions returned in the result
2414
+ total_dimensions_count:
2415
+ description: the total number of dimensions evaluated
2416
+ type: integer
2417
+ statistics:
2418
+ type: object
2419
+ properties:
2420
+ query_time_ms:
2421
+ type: number
2422
+ db_queries:
2423
+ type: integer
2424
+ db_points_read:
2425
+ type: integer
2426
+ query_result_points:
2427
+ type: integer
2428
+ binary_searches:
2429
+ type: integer
2430
+ contexts:
2431
+ type: object
2432
+ description: An object containing context objects.
2433
+ properties:
2434
+ contextX:
2435
+ type: object
2436
+ properties:
2437
+ charts:
2438
+ type: object
2439
+ properties:
2440
+ chartX:
2441
+ type: object
2442
+ properties:
2443
+ dimensions:
2444
+ type: object
2445
+ properties:
2446
+ dimensionX:
2447
+ type: number
2448
+ weight:
2449
+ description: The average chart weight
2450
+ type: number
2451
+ weight:
2452
+ description: The average context weight
2453
+ type: number
web/api/queries/query.c
+5
-6
@@ -567,7 +567,7 @@ typedef struct query_point {
567
time_t end_time;
568
time_t start_time;
569
NETDATA_DOUBLE value;
570
- size_t anomaly;
570
+ NETDATA_DOUBLE anomaly;
571
SN_FLAGS flags;
572
#ifdef NETDATA_INTERNAL_CHECKS
573
size_t id;
@@ -628,7 +628,7 @@ typedef struct query_engine_ops {
628
NETDATA_DOUBLE (*grouping_flush)(struct rrdresult *r, RRDR_VALUE_FLAGS *rrdr_value_options_ptr);
629
size_t group_points_non_zero;
630
size_t group_points_added;
631
- size_t group_anomaly_rate;
631
+ NETDATA_DOUBLE group_anomaly_rate;
632
RRDR_VALUE_FLAGS group_value_flags;
633
634
// statistics
@@ -910,14 +910,14 @@ static inline void rrd2rrdr_do_dimension(
910
911
new_point.start_time = sp.start_time;
912
new_point.end_time = sp.end_time;
913
- new_point.anomaly = sp.count ? sp.anomaly_count * 100 / sp.count : 0;
913
+ new_point.anomaly = sp.count ? (NETDATA_DOUBLE)sp.anomaly_count * 100.0 / (NETDATA_DOUBLE)sp.count : 0.0;
914
query_point_set_id(new_point, ops.db_total_points_read);
915
916
// set the right value to the point we got
917
if(likely(!storage_point_is_unset(sp) && !storage_point_is_empty(sp))) {
918
919
if(unlikely(use_anomaly_bit_as_value))
920
- new_point.value = (NETDATA_DOUBLE)new_point.anomaly;
920
+ new_point.value = new_point.anomaly;
921
922
else {
923
switch (ops.tier_query_fetch) {
@@ -1073,8 +1073,7 @@ static inline void rrd2rrdr_do_dimension(
1073
// we only store uint8_t anomaly rates,
1074
// so let's get double precision by storing
1075
// anomaly rates in the range 0 - 200
1076
- ops.group_anomaly_rate = (ops.group_anomaly_rate << 1) / ops.group_points_added;
1077
- r->ar[rrdr_o_v_index] = (uint8_t)ops.group_anomaly_rate;
1076
+ r->ar[rrdr_o_v_index] = ops.group_anomaly_rate / (NETDATA_DOUBLE)ops.group_points_added;
1077
1078
if(likely(points_added || dim_id_in_rrdr)) {
1079
// find the min/max across all dimensions
web/api/queries/rrdr.c
+1
-1
@@ -82,7 +82,7 @@ RRDR *rrdr_create_for_x_dimensions(ONEWAYALLOC *owa, int dimensions, long points
82
r->t = onewayalloc_callocz(owa, points, sizeof(time_t));
83
r->v = onewayalloc_mallocz(owa, points * dimensions * sizeof(NETDATA_DOUBLE));
84
r->o = onewayalloc_mallocz(owa, points * dimensions * sizeof(RRDR_VALUE_FLAGS));
85
- r->ar = onewayalloc_mallocz(owa, points * dimensions * sizeof(uint8_t));
85
+ r->ar = onewayalloc_mallocz(owa, points * dimensions * sizeof(NETDATA_DOUBLE));
86
r->od = onewayalloc_mallocz(owa, dimensions * sizeof(RRDR_DIMENSION_FLAGS));
87
88
r->group = 1;
web/api/queries/rrdr.h
+1
-1
@@ -83,7 +83,7 @@ typedef struct rrdresult {
83
time_t *t; // array of n timestamps
84
NETDATA_DOUBLE *v; // array n x d values
85
RRDR_VALUE_FLAGS *o; // array n x d options for each value returned
86
- uint8_t *ar; // array n x d of anomaly rates (0 - 200)
86
+ NETDATA_DOUBLE *ar; // array n x d of anomaly rates (0 - 100)
87
88
long group; // how many collected values were grouped for each row
89
int update_every; // what is the suggested update frequency in seconds
web/api/queries/weights.c
renamed
+355
-118
@@ -1,45 +1,47 @@
1
// SPDX-License-Identifier: GPL-3.0-or-later
2
3
#include "daemon/common.h"
4
-#include "KolmogorovSmirnovDist.h"
4
+#include "database/KolmogorovSmirnovDist.h"
5
6
#define MAX_POINTS 10000
7
int enable_metric_correlations = CONFIG_BOOLEAN_YES;
8
int metric_correlations_version = 1;
9
-METRIC_CORRELATIONS_METHOD default_metric_correlations_method = METRIC_CORRELATIONS_KS2;
9
+WEIGHTS_METHOD default_metric_correlations_method = WEIGHTS_METHOD_MC_KS2;
10
11
-typedef struct mc_stats {
11
+typedef struct weights_stats {
12
NETDATA_DOUBLE max_base_high_ratio;
13
size_t db_points;
14
size_t result_points;
15
size_t db_queries;
16
+ size_t db_points_per_tier[RRD_STORAGE_TIERS];
17
size_t binary_searches;
17
-} MC_STATS;
18
+} WEIGHTS_STATS;
19
20
// ----------------------------------------------------------------------------
21
// parse and render metric correlations methods
22
23
static struct {
24
const char *name;
24
- METRIC_CORRELATIONS_METHOD value;
25
-} metric_correlations_methods[] = {
26
- { "ks2" , METRIC_CORRELATIONS_KS2 }
27
- , { "volume" , METRIC_CORRELATIONS_VOLUME }
28
- , { NULL , 0 }
25
+ WEIGHTS_METHOD value;
26
+} weights_methods[] = {
27
+ { "ks2" , WEIGHTS_METHOD_MC_KS2}
28
+ , { "volume" , WEIGHTS_METHOD_MC_VOLUME}
29
+ , { "anomaly-rate" , WEIGHTS_METHOD_ANOMALY_RATE}
30
+ , { NULL , 0 }
31
};
32
31
-METRIC_CORRELATIONS_METHOD mc_string_to_method(const char *method) {
32
- for(int i = 0; metric_correlations_methods[i].name ;i++)
33
- if(strcmp(method, metric_correlations_methods[i].name) == 0)
34
- return metric_correlations_methods[i].value;
33
+WEIGHTS_METHOD weights_string_to_method(const char *method) {
34
+ for(int i = 0; weights_methods[i].name ;i++)
35
+ if(strcmp(method, weights_methods[i].name) == 0)
36
+ return weights_methods[i].value;
37
38
return default_metric_correlations_method;
39
}
40
39
-const char *mc_method_to_string(METRIC_CORRELATIONS_METHOD method) {
40
- for(int i = 0; metric_correlations_methods[i].name ;i++)
41
- if(metric_correlations_methods[i].value == method)
42
- return metric_correlations_methods[i].name;
41
+const char *weights_method_to_string(WEIGHTS_METHOD method) {
42
+ for(int i = 0; weights_methods[i].name ;i++)
43
+ if(weights_methods[i].value == method)
44
+ return weights_methods[i].name;
45
46
return "unknown";
47
}
@@ -59,6 +61,8 @@ struct register_result {
61
const char *context;
62
const char *dim_name;
63
NETDATA_DOUBLE value;
64
+
65
+ struct register_result *next; // used to link contexts together
66
};
67
68
static void register_result_insert_callback(const char *name, void *value, void *data) {
@@ -93,14 +97,22 @@ static void register_result_destroy(DICTIONARY *results) {
97
dictionary_destroy(results);
98
}
99
96
-static void register_result(DICTIONARY *results, RRDSET *st, RRDDIM *d, NETDATA_DOUBLE value, RESULT_FLAGS flags, MC_STATS *stats) {
100
+static void register_result(DICTIONARY *results,
101
+ RRDSET *st,
102
+ RRDDIM *d,
103
+ NETDATA_DOUBLE value,
104
+ RESULT_FLAGS flags,
105
+ WEIGHTS_STATS *stats,
106
+ bool register_zero) {
107
+
108
if(!netdata_double_isnumber(value)) return;
109
110
// make it positive
111
NETDATA_DOUBLE v = fabsndd(value);
112
113
// no need to store zero scored values
103
- if(v == 0.0) return;
114
+ if(unlikely(fpclassify(v) == FP_ZERO && !register_zero))
115
+ return;
116
117
// keep track of the max of the baseline / highlight ratio
118
if(flags & RESULT_IS_BASE_HIGH_RATIO && v > stats->max_base_high_ratio)
@@ -123,49 +135,78 @@ static void register_result(DICTIONARY *results, RRDSET *st, RRDDIM *d, NETDATA_
135
// ----------------------------------------------------------------------------
136
// Generation of JSON output for the results
137
126
-static size_t registered_results_to_json(DICTIONARY *results, BUFFER *wb,
127
- long long after, long long before,
128
- long long baseline_after, long long baseline_before,
129
- long points, METRIC_CORRELATIONS_METHOD method,
130
- RRDR_GROUPING group, RRDR_OPTIONS options, uint32_t shifts,
131
- size_t correlated_dimensions, usec_t duration, MC_STATS *stats) {
138
+static void results_header_to_json(DICTIONARY *results __maybe_unused, BUFFER *wb,
139
+ long long after, long long before,
140
+ long long baseline_after, long long baseline_before,
141
+ long points, WEIGHTS_METHOD method,
142
+ RRDR_GROUPING group, RRDR_OPTIONS options, uint32_t shifts,
143
+ size_t examined_dimensions __maybe_unused, usec_t duration,
144
+ WEIGHTS_STATS *stats) {
145
146
buffer_sprintf(wb, "{\n"
147
"\t\"after\": %lld,\n"
148
"\t\"before\": %lld,\n"
149
"\t\"duration\": %lld,\n"
137
- "\t\"points\": %ld,\n"
138
- "\t\"baseline_after\": %lld,\n"
139
- "\t\"baseline_before\": %lld,\n"
140
- "\t\"baseline_duration\": %lld,\n"
141
- "\t\"baseline_points\": %ld,\n"
150
+ "\t\"points\": %ld,\n",
151
+ after,
152
+ before,
153
+ before - after,
154
+ points
155
+ );
156
+
157
+ if(method == WEIGHTS_METHOD_MC_KS2 || method == WEIGHTS_METHOD_MC_VOLUME)
158
+ buffer_sprintf(wb, ""
159
+ "\t\"baseline_after\": %lld,\n"
160
+ "\t\"baseline_before\": %lld,\n"
161
+ "\t\"baseline_duration\": %lld,\n"
162
+ "\t\"baseline_points\": %ld,\n",
163
+ baseline_after,
164
+ baseline_before,
165
+ baseline_before - baseline_after,
166
+ points << shifts
167
+ );
168
+
169
+ buffer_sprintf(wb, ""
170
"\t\"statistics\": {\n"
171
"\t\t\"query_time_ms\": %f,\n"
172
"\t\t\"db_queries\": %zu,\n"
145
- "\t\t\"db_points_read\": %zu,\n"
173
"\t\t\"query_result_points\": %zu,\n"
147
- "\t\t\"binary_searches\": %zu\n"
174
+ "\t\t\"binary_searches\": %zu,\n"
175
+ "\t\t\"db_points_read\": %zu,\n"
176
+ "\t\t\"db_points_per_tier\": [ ",
177
+ (double)duration / (double)USEC_PER_MS,
178
+ stats->db_queries,
179
+ stats->result_points,
180
+ stats->binary_searches,
181
+ stats->db_points
182
+ );
183
+
184
+ for(int tier = 0; tier < storage_tiers ;tier++)
185
+ buffer_sprintf(wb, "%s%zu", tier?", ":"", stats->db_points_per_tier[tier]);
186
+
187
+ buffer_sprintf(wb, " ]\n"
188
"\t},\n"
189
"\t\"group\": \"%s\",\n"
190
"\t\"method\": \"%s\",\n"
191
"\t\"options\": \"",
152
- after,
153
- before,
154
- before - after,
155
- points,
156
- baseline_after,
157
- baseline_before,
158
- baseline_before - baseline_after,
159
- points << shifts,
160
- (double)duration / (double)USEC_PER_MS,
161
- stats->db_queries,
162
- stats->db_points,
163
- stats->result_points,
164
- stats->binary_searches,
165
- web_client_api_request_v1_data_group_to_string(group),
166
- mc_method_to_string(method));
192
+ web_client_api_request_v1_data_group_to_string(group),
193
+ weights_method_to_string(method)
194
+ );
195
196
web_client_api_request_v1_data_options_to_string(wb, options);
197
+}
198
+
199
+static size_t registered_results_to_json_charts(DICTIONARY *results, BUFFER *wb,
200
+ long long after, long long before,
201
+ long long baseline_after, long long baseline_before,
202
+ long points, WEIGHTS_METHOD method,
203
+ RRDR_GROUPING group, RRDR_OPTIONS options, uint32_t shifts,
204
+ size_t examined_dimensions, usec_t duration,
205
+ WEIGHTS_STATS *stats) {
206
+
207
+ results_header_to_json(results, wb, after, before, baseline_after, baseline_before,
208
+ points, method, group, options, shifts, examined_dimensions, duration, stats);
209
+
210
buffer_strcat(wb, "\",\n\t\"correlated_charts\": {\n");
211
212
size_t charts = 0, chart_dims = 0, total_dimensions = 0;
@@ -202,12 +243,108 @@ static size_t registered_results_to_json(DICTIONARY *results, BUFFER *wb,
243
"\t\"total_dimensions_count\": %zu\n"
244
"}\n",
245
total_dimensions,
205
- correlated_dimensions // yes, we flip them
246
+ examined_dimensions
247
);
248
249
return total_dimensions;
250
}
251
252
+static size_t registered_results_to_json_contexts(DICTIONARY *results, BUFFER *wb,
253
+ long long after, long long before,
254
+ long long baseline_after, long long baseline_before,
255
+ long points, WEIGHTS_METHOD method,
256
+ RRDR_GROUPING group, RRDR_OPTIONS options, uint32_t shifts,
257
+ size_t examined_dimensions, usec_t duration,
258
+ WEIGHTS_STATS *stats) {
259
+
260
+ results_header_to_json(results, wb, after, before, baseline_after, baseline_before,
261
+ points, method, group, options, shifts, examined_dimensions, duration, stats);
262
+
263
+ DICTIONARY *context_results = dictionary_create(
264
+ DICTIONARY_FLAG_SINGLE_THREADED
265
+ |DICTIONARY_FLAG_VALUE_LINK_DONT_CLONE
266
+ |DICTIONARY_FLAG_NAME_LINK_DONT_CLONE
267
+ |DICTIONARY_FLAG_DONT_OVERWRITE_VALUE
268
+ );
269
+
270
+ struct register_result *t;
271
+ dfe_start_read(results, t) {
272
+ struct register_result *tc = dictionary_set(context_results, t->context, t, sizeof(*t));
273
+ if(tc == t)
274
+ t->next = NULL;
275
+ else {
276
+ t->next = tc->next;
277
+ tc->next = t;
278
+ }
279
+ }
280
+ dfe_done(t);
281
+
282
+ buffer_strcat(wb, "\",\n\t\"contexts\": {\n");
283
+
284
+ size_t contexts = 0, total_dimensions = 0, charts = 0, context_dims = 0, chart_dims = 0;
285
+ NETDATA_DOUBLE contexts_total_weight = 0.0, charts_total_weight = 0.0;
286
+ RRDSET *last_st = NULL; // never access this - we use it only for comparison
287
+ dfe_start_read(context_results, t) {
288
+
289
+ if(contexts)
290
+ buffer_sprintf(wb, "\n\t\t\t\t\t},\n\t\t\t\t\t\"weight\":" NETDATA_DOUBLE_FORMAT "\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"weight\":" NETDATA_DOUBLE_FORMAT "\n\t\t},\n", charts_total_weight / chart_dims, contexts_total_weight / context_dims);
291
+
292
+ contexts++;
293
+ context_dims = 0;
294
+ contexts_total_weight = 0.0;
295
+
296
+ buffer_strcat(wb, "\t\t\"");
297
+ buffer_strcat(wb, t->context);
298
+ buffer_strcat(wb, "\": {\n\t\t\t\"charts\":{\n");
299
+
300
+ charts = 0;
301
+ chart_dims = 0;
302
+ struct register_result *tt;
303
+ for(tt = t; tt ; tt = tt->next) {
304
+ if(!last_st || tt->st != last_st) {
305
+ last_st = tt->st;
306
+
307
+ if(charts)
308
+ buffer_sprintf(wb, "\n\t\t\t\t\t},\n\t\t\t\t\t\"weight\":" NETDATA_DOUBLE_FORMAT "\n\t\t\t\t},\n", charts_total_weight / chart_dims);
309
+
310
+ buffer_strcat(wb, "\t\t\t\t\"");
311
+ buffer_strcat(wb, tt->chart_id);
312
+ buffer_strcat(wb, "\": {\n");
313
+ buffer_strcat(wb, "\t\t\t\t\t\"dimensions\": {\n");
314
+ charts++;
315
+ chart_dims = 0;
316
+ charts_total_weight = 0.0;
317
+ }
318
+
319
+ if (chart_dims) buffer_sprintf(wb, ",\n");
320
+ buffer_sprintf(wb, "\t\t\t\t\t\t\"%s\": " NETDATA_DOUBLE_FORMAT, tt->dim_name, tt->value);
321
+ charts_total_weight += tt->value;
322
+ contexts_total_weight += tt->value;
323
+ chart_dims++;
324
+ context_dims++;
325
+ total_dimensions++;
326
+ }
327
+ }
328
+ dfe_done(t);
329
+
330
+ dictionary_destroy(context_results);
331
+
332
+ // close dimensions and chart
333
+ if (total_dimensions)
334
+ buffer_sprintf(wb, "\n\t\t\t\t\t},\n\t\t\t\t\t\"weight\":" NETDATA_DOUBLE_FORMAT "\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"weight\":" NETDATA_DOUBLE_FORMAT "\n\t\t}\n", charts_total_weight / chart_dims, contexts_total_weight / context_dims);
335
+
336
+ // close correlated_charts
337
+ buffer_sprintf(wb, "\t},\n"
338
+ "\t\"weighted_dimensions\": %zu,\n"
339
+ "\t\"total_dimensions_count\": %zu\n"
340
+ "}\n",
341
+ total_dimensions,
342
+ examined_dimensions
343
+ );
344
+
345
+ return total_dimensions;
346
+}
347
+
348
// ----------------------------------------------------------------------------
349
// KS2 algorithm functions
350
@@ -382,14 +519,15 @@ static int rrdset_metric_correlations_ks2(RRDSET *st, DICTIONARY *results,
519
long long baseline_after, long long baseline_before,
520
long long after, long long before,
521
long long points, RRDR_OPTIONS options,
385
- RRDR_GROUPING group, const char *group_options,
386
- uint32_t shifts, int timeout, MC_STATS *stats) {
522
+ RRDR_GROUPING group, const char *group_options, int tier,
523
+ uint32_t shifts, int timeout,
524
+ WEIGHTS_STATS *stats, bool register_zero) {
525
options |= RRDR_OPTION_NATURAL_POINTS;
526
527
long group_time = 0;
528
struct context_param *context_param_list = NULL;
529
392
- int correlated_dimensions = 0;
530
+ int examined_dimensions = 0;
531
532
RRDR *high_rrdr = NULL;
533
RRDR *base_rrdr = NULL;
@@ -401,11 +539,15 @@ static int rrdset_metric_correlations_ks2(RRDSET *st, DICTIONARY *results,
539
high_rrdr = rrd2rrdr(owa, st, points,
540
after, before, group,
541
group_time, options, NULL, context_param_list, group_options,
404
- timeout, 0);
542
+ timeout, tier);
543
if(!high_rrdr) {
544
info("Metric correlations: rrd2rrdr() failed for the highlighted window on chart '%s'.", st->name);
545
goto cleanup;
546
}
547
+
548
+ for(int i = 0; i < storage_tiers ;i++)
549
+ stats->db_points_per_tier[i] += high_rrdr->internal.tier_points_read[i];
550
+
551
stats->db_points += high_rrdr->internal.db_points_read;
552
stats->result_points += high_rrdr->internal.result_points_generated;
553
if(!high_rrdr->d) {
@@ -427,11 +569,15 @@ static int rrdset_metric_correlations_ks2(RRDSET *st, DICTIONARY *results,
569
base_rrdr = rrd2rrdr(owa, st,high_points << shifts,
570
baseline_after, baseline_before, group,
571
group_time, options, NULL, context_param_list, group_options,
430
- (int)(timeout - ((now_usec - started_usec) / USEC_PER_MS)), 0);
572
+ (int)(timeout - ((now_usec - started_usec) / USEC_PER_MS)), tier);
573
if(!base_rrdr) {
574
info("Metric correlations: rrd2rrdr() failed for the baseline window on chart '%s'.", st->name);
575
goto cleanup;
576
}
577
+
578
+ for(int i = 0; i < storage_tiers ;i++)
579
+ stats->db_points_per_tier[i] += base_rrdr->internal.tier_points_read[i];
580
+
581
stats->db_points += base_rrdr->internal.db_points_read;
582
stats->result_points += base_rrdr->internal.result_points_generated;
583
if(!base_rrdr->d) {
@@ -465,7 +611,7 @@ static int rrdset_metric_correlations_ks2(RRDSET *st, DICTIONARY *results,
611
if(unlikely(base_rrdr->od[i] & RRDR_DIMENSION_HIDDEN) || (high_rrdr->od[i] & RRDR_DIMENSION_HIDDEN))
612
continue;
613
468
- correlated_dimensions++;
614
+ examined_dimensions++;
615
616
// skip the dimensions that are just zero for both the baseline and the highlight
617
if(unlikely(!(base_rrdr->od[i] & RRDR_DIMENSION_NONZERO) && !(high_rrdr->od[i] & RRDR_DIMENSION_NONZERO)))
@@ -501,7 +647,7 @@ static int rrdset_metric_correlations_ks2(RRDSET *st, DICTIONARY *results,
647
648
// to spread the results evenly, 0.0 needs to be the less correlated and 1.0 the most correlated
649
// so we flip the result of kstwo()
504
- register_result(results, base_rrdr->st, d, 1.0 - prob, RESULT_IS_BASE_HIGH_RATIO, stats);
650
+ register_result(results, base_rrdr->st, d, 1.0 - prob, RESULT_IS_BASE_HIGH_RATIO, stats, register_zero);
651
}
652
}
653
@@ -509,7 +655,7 @@ cleanup:
655
rrdr_free(owa, high_rrdr);
656
rrdr_free(owa, base_rrdr);
657
onewayalloc_destroy(owa);
512
- return correlated_dimensions;
658
+ return examined_dimensions;
659
}
660
661
// ----------------------------------------------------------------------------
@@ -519,11 +665,13 @@ static int rrdset_metric_correlations_volume(RRDSET *st, DICTIONARY *results,
665
long long baseline_after, long long baseline_before,
666
long long after, long long before,
667
RRDR_OPTIONS options, RRDR_GROUPING group, const char *group_options,
522
- int timeout, MC_STATS *stats) {
668
+ int tier, int timeout,
669
+ WEIGHTS_STATS *stats, bool register_zero) {
670
+
671
options |= RRDR_OPTION_MATCH_IDS | RRDR_OPTION_ABSOLUTE | RRDR_OPTION_NATURAL_POINTS;
672
long group_time = 0;
673
526
- int correlated_dimensions = 0;
674
+ int examined_dimensions = 0;
675
int ret, value_is_null;
676
usec_t started_usec = now_realtime_usec();
677
@@ -531,10 +679,10 @@ static int rrdset_metric_correlations_volume(RRDSET *st, DICTIONARY *results,
679
for(d = st->dimensions; d ; d = d->next) {
680
usec_t now_usec = now_realtime_usec();
681
if(now_usec - started_usec > timeout * USEC_PER_MS)
534
- return correlated_dimensions;
682
+ return examined_dimensions;
683
684
// we count how many metrics we evaluated
537
- correlated_dimensions++;
685
+ examined_dimensions++;
686
687
// there is no point to pass a timeout to these queries
688
// since the query engine checks for a timeout between
@@ -542,14 +690,15 @@ static int rrdset_metric_correlations_volume(RRDSET *st, DICTIONARY *results,
690
691
stats->db_queries++;
692
NETDATA_DOUBLE baseline_average = NAN;
545
- uint8_t base_anomaly_rate = 0;
693
+ NETDATA_DOUBLE base_anomaly_rate = 0;
694
value_is_null = 1;
695
ret = rrdset2value_api_v1(st, NULL, &baseline_average, d->id, 1,
696
baseline_after, baseline_before,
697
group, group_options, group_time, options,
698
NULL, NULL,
551
- &stats->db_points, &stats->result_points,
552
- &value_is_null, &base_anomaly_rate, 0, 0);
699
+ &stats->db_points, stats->db_points_per_tier,
700
+ &stats->result_points,
701
+ &value_is_null, &base_anomaly_rate, 0, tier);
702
703
if(ret != HTTP_RESP_OK || value_is_null || !netdata_double_isnumber(baseline_average)) {
704
// this means no data for the baseline window, but we may have data for the highlighted one - assume zero
@@ -558,14 +707,15 @@ static int rrdset_metric_correlations_volume(RRDSET *st, DICTIONARY *results,
707
708
stats->db_queries++;
709
NETDATA_DOUBLE highlight_average = NAN;
561
- uint8_t high_anomaly_rate = 0;
710
+ NETDATA_DOUBLE high_anomaly_rate = 0;
711
value_is_null = 1;
712
ret = rrdset2value_api_v1(st, NULL, &highlight_average, d->id, 1,
713
after, before,
714
group, group_options, group_time, options,
715
NULL, NULL,
567
- &stats->db_points, &stats->result_points,
568
- &value_is_null, &high_anomaly_rate, 0, 0);
716
+ &stats->db_points, stats->db_points_per_tier,
717
+ &stats->result_points,
718
+ &value_is_null, &high_anomaly_rate, 0, tier);
719
720
if(ret != HTTP_RESP_OK || value_is_null || !netdata_double_isnumber(highlight_average)) {
721
// this means no data for the highlighted duration - so skip it
@@ -589,8 +739,9 @@ static int rrdset_metric_correlations_volume(RRDSET *st, DICTIONARY *results,
739
RRDR_GROUPING_COUNTIF,highlighted_countif_options,
740
group_time, options,
741
NULL, NULL,
592
- &stats->db_points, &stats->result_points,
593
- &value_is_null, NULL, 0, 0);
742
+ &stats->db_points, stats->db_points_per_tier,
743
+ &stats->result_points,
744
+ &value_is_null, NULL, 0, tier);
745
746
if(ret != HTTP_RESP_OK || value_is_null || !netdata_double_isnumber(highlight_countif)) {
747
info("MC: highlighted countif query failed, but highlighted average worked - strange...");
@@ -613,12 +764,62 @@ static int rrdset_metric_correlations_volume(RRDSET *st, DICTIONARY *results,
764
pcent = highlight_countif;
765
}
766
616
- register_result(results, st, d, pcent, flags, stats);
767
+ register_result(results, st, d, pcent, flags, stats, register_zero);
768
}
769
619
- return correlated_dimensions;
770
+ return examined_dimensions;
771
}
772
773
+// ----------------------------------------------------------------------------
774
+// ANOMALY RATE algorithm functions
775
+
776
+static int rrdset_weights_anomaly_rate(RRDSET *st, DICTIONARY *results,
777
+ long long after, long long before,
778
+ RRDR_OPTIONS options, RRDR_GROUPING group, const char *group_options,
779
+ int tier, int timeout,
780
+ WEIGHTS_STATS *stats, bool register_zero) {
781
+
782
+ options |= RRDR_OPTION_MATCH_IDS | RRDR_OPTION_ANOMALY_BIT | RRDR_OPTION_NATURAL_POINTS;
783
+ long group_time = 0;
784
+
785
+ int examined_dimensions = 0;
786
+ int ret, value_is_null;
787
+ usec_t started_usec = now_realtime_usec();
788
+
789
+ RRDDIM *d;
790
+ for(d = st->dimensions; d ; d = d->next) {
791
+ usec_t now_usec = now_realtime_usec();
792
+ if(now_usec - started_usec > timeout * USEC_PER_MS)
793
+ return examined_dimensions;
794
+
795
+ // we count how many metrics we evaluated
796
+ examined_dimensions++;
797
+
798
+ // there is no point to pass a timeout to these queries
799
+ // since the query engine checks for a timeout between
800
+ // dimensions, and we query a single dimension at a time.
801
+
802
+ stats->db_queries++;
803
+ NETDATA_DOUBLE average = NAN;
804
+ NETDATA_DOUBLE anomaly_rate = 0;
805
+ value_is_null = 1;
806
+ ret = rrdset2value_api_v1(st, NULL, &average, d->id, 1,
807
+ after, before,
808
+ group, group_options, group_time, options,
809
+ NULL, NULL,
810
+ &stats->db_points, stats->db_points_per_tier,
811
+ &stats->result_points,
812
+ &value_is_null, &anomaly_rate, 0, tier);
813
+
814
+ if(ret == HTTP_RESP_OK || !value_is_null || netdata_double_isnumber(average))
815
+ register_result(results, st, d, average, 0, stats, register_zero);
816
+ }
817
+
818
+ return examined_dimensions;
819
+}
820
+
821
+// ----------------------------------------------------------------------------
822
+
823
int compare_netdata_doubles(const void *left, const void *right) {
824
NETDATA_DOUBLE lt = *(NETDATA_DOUBLE *)left;
825
NETDATA_DOUBLE rt = *(NETDATA_DOUBLE *)right;
@@ -648,7 +849,7 @@ static inline int binary_search_bigger_than_netdata_double(const NETDATA_DOUBLE
849
// ----------------------------------------------------------------------------
850
// spread the results evenly according to their value
851
651
-static size_t spread_results_evenly(DICTIONARY *results, MC_STATS *stats) {
852
+static size_t spread_results_evenly(DICTIONARY *results, WEIGHTS_STATS *stats) {
853
struct register_result *t;
854
855
// count the dimensions
@@ -702,21 +903,17 @@ static size_t spread_results_evenly(DICTIONARY *results, MC_STATS *stats) {
903
// ----------------------------------------------------------------------------
904
// The main function
905
705
-int metric_correlations(RRDHOST *host, BUFFER *wb, METRIC_CORRELATIONS_METHOD method,
906
+int web_api_v1_weights(RRDHOST *host, BUFFER *wb, WEIGHTS_METHOD method, WEIGHTS_FORMAT format,
907
RRDR_GROUPING group, const char *group_options,
908
long long baseline_after, long long baseline_before,
909
long long after, long long before,
709
- long long points, RRDR_OPTIONS options, int timeout) {
910
+ long long points, RRDR_OPTIONS options, SIMPLE_PATTERN *contexts, int tier, int timeout) {
911
+ WEIGHTS_STATS stats = {};
912
711
- // method = METRIC_CORRELATIONS_VOLUME;
712
- // options |= RRDR_OPTION_ANOMALY_BIT;
713
-
714
- MC_STATS stats = {};
715
-
716
- if (enable_metric_correlations == CONFIG_BOOLEAN_NO) {
717
- buffer_strcat(wb, "{\"error\": \"Metric correlations functionality is not enabled.\" }");
718
- return HTTP_RESP_FORBIDDEN;
719
- }
913
+ DICTIONARY *results = register_result_init();
914
+ DICTIONARY *charts = dictionary_create(DICTIONARY_FLAG_SINGLE_THREADED|DICTIONARY_FLAG_VALUE_LINK_DONT_CLONE);;
915
+ char *error = NULL;
916
+ int resp = HTTP_RESP_OK;
917
918
// if the user didn't give a timeout
919
// assume 60 seconds
@@ -731,29 +928,31 @@ int metric_correlations(RRDHOST *host, BUFFER *wb, METRIC_CORRELATIONS_METHOD me
928
usec_t timeout_usec = timeout * USEC_PER_MS;
929
usec_t started_usec = now_realtime_usec();
930
734
- if(!points) points = 500;
931
+ if(!rrdr_relative_window_to_absolute(&after, &before))
932
+ buffer_no_cacheable(wb);
933
736
- rrdr_relative_window_to_absolute(&after, &before);
737
-
738
- if(baseline_before <= API_RELATIVE_TIME_MAX)
739
- baseline_before += after;
934
+ if (before <= after) {
935
+ resp = HTTP_RESP_BAD_REQUEST;
936
+ error = "Invalid selected time-range.";
937
+ goto cleanup;
938
+ }
939
741
- rrdr_relative_window_to_absolute(&baseline_after, &baseline_before);
940
+ uint32_t shifts = 0;
941
+ if(method == WEIGHTS_METHOD_MC_KS2 || method == WEIGHTS_METHOD_MC_VOLUME) {
942
+ if(!points) points = 500;
943
743
- if (before <= after || baseline_before <= baseline_after) {
744
- buffer_strcat(wb, "{\"error\": \"Invalid baseline or highlight ranges.\" }");
745
- return HTTP_RESP_BAD_REQUEST;
746
- }
944
+ if(baseline_before <= API_RELATIVE_TIME_MAX)
945
+ baseline_before += after;
946
748
- DICTIONARY *results = register_result_init();
749
- DICTIONARY *charts = dictionary_create(DICTIONARY_FLAG_SINGLE_THREADED|DICTIONARY_FLAG_VALUE_LINK_DONT_CLONE);;
947
+ rrdr_relative_window_to_absolute(&baseline_after, &baseline_before);
948
751
- char *error = NULL;
752
- int resp = HTTP_RESP_OK;
949
+ if (baseline_before <= baseline_after) {
950
+ resp = HTTP_RESP_BAD_REQUEST;
951
+ error = "Invalid baseline time-range.";
952
+ goto cleanup;
953
+ }
954
754
- // baseline should be a power of two multiple of highlight
755
- uint32_t shifts = 0;
756
- {
955
+ // baseline should be a power of two multiple of highlight
956
long long base_delta = baseline_before - baseline_after;
957
long long high_delta = before - after;
958
uint32_t multiplier = (uint32_t)round((double)base_delta / (double)high_delta);
@@ -793,6 +992,7 @@ int metric_correlations(RRDHOST *host, BUFFER *wb, METRIC_CORRELATIONS_METHOD me
992
993
if(points < 15) {
994
resp = HTTP_RESP_BAD_REQUEST;
995
+ error = "Too few points available, at least 15 are needed.";
996
goto cleanup;
997
}
998
@@ -805,14 +1005,22 @@ int metric_correlations(RRDHOST *host, BUFFER *wb, METRIC_CORRELATIONS_METHOD me
1005
RRDSET *st;
1006
rrdhost_rdlock(host);
1007
rrdset_foreach_read(st, host) {
808
- if (rrdset_is_available_for_viewers(st))
809
- dictionary_set(charts, st->name, "", 1);
1008
+ if (rrdset_is_available_for_viewers(st)) {
1009
+ if(!contexts || simple_pattern_matches(contexts, st->context))
1010
+ dictionary_set(charts, st->name, NULL, 0);
1011
+ }
1012
}
1013
rrdhost_unlock(host);
1014
813
- size_t correlated_dimensions = 0;
1015
+ size_t examined_dimensions = 0;
1016
void *ptr;
1017
1018
+ bool register_zero = true;
1019
+ if(options & RRDR_OPTION_NONZERO) {
1020
+ register_zero = false;
1021
+ options &= ~RRDR_OPTION_NONZERO;
1022
+ }
1023
+
1024
// for every chart in the dictionary
1025
dfe_start_read(charts, ptr) {
1026
usec_t now_usec = now_realtime_usec();
@@ -828,23 +1036,34 @@ int metric_correlations(RRDHOST *host, BUFFER *wb, METRIC_CORRELATIONS_METHOD me
1036
rrdset_rdlock(st);
1037
1038
switch(method) {
831
- case METRIC_CORRELATIONS_VOLUME:
832
- correlated_dimensions += rrdset_metric_correlations_volume(st, results,
1039
+ case WEIGHTS_METHOD_ANOMALY_RATE:
1040
+ options |= RRDR_OPTION_ANOMALY_BIT;
1041
+ points = 1;
1042
+ examined_dimensions += rrdset_weights_anomaly_rate(st, results,
1043
+ after, before,
1044
+ options, group, group_options, tier,
1045
+ (int)(timeout - ((now_usec - started_usec) / USEC_PER_MS)),
1046
+ &stats, register_zero);
1047
+ break;
1048
+
1049
+ case WEIGHTS_METHOD_MC_VOLUME:
1050
+ points = 1;
1051
+ examined_dimensions += rrdset_metric_correlations_volume(st, results,
1052
baseline_after, baseline_before,
1053
after, before,
835
- options, group, group_options,
1054
+ options, group, group_options, tier,
1055
(int)(timeout - ((now_usec - started_usec) / USEC_PER_MS)),
837
- &stats);
1056
+ &stats, register_zero);
1057
break;
1058
1059
default:
841
- case METRIC_CORRELATIONS_KS2:
842
- correlated_dimensions += rrdset_metric_correlations_ks2(st, results,
1060
+ case WEIGHTS_METHOD_MC_KS2:
1061
+ examined_dimensions += rrdset_metric_correlations_ks2(st, results,
1062
baseline_after, baseline_before,
1063
after, before,
845
- points, options, group, group_options, shifts,
1064
+ points, options, group, group_options, tier, shifts,
1065
(int)(timeout - ((now_usec - started_usec) / USEC_PER_MS)),
847
- &stats);
1066
+ &stats, register_zero);
1067
break;
1068
}
1069
@@ -852,6 +1071,9 @@ int metric_correlations(RRDHOST *host, BUFFER *wb, METRIC_CORRELATIONS_METHOD me
1071
}
1072
dfe_done(ptr);
1073
1074
+ if(!register_zero)
1075
+ options |= RRDR_OPTION_NONZERO;
1076
+
1077
if(!(options & RRDR_OPTION_RETURN_RAW))
1078
spread_results_evenly(results, &stats);
1079
@@ -859,14 +1081,31 @@ int metric_correlations(RRDHOST *host, BUFFER *wb, METRIC_CORRELATIONS_METHOD me
1081
1082
// generate the json output we need
1083
buffer_flush(wb);
862
- size_t added_dimensions = registered_results_to_json(results, wb,
863
- after, before,
864
- baseline_after, baseline_before,
865
- points, method, group, options, shifts, correlated_dimensions,
866
- ended_usec - started_usec, &stats);
1084
+
1085
+ size_t added_dimensions = 0;
1086
+ switch(format) {
1087
+ case WEIGHTS_FORMAT_CHARTS:
1088
+ added_dimensions = registered_results_to_json_charts(results, wb,
1089
+ after, before,
1090
+ baseline_after, baseline_before,
1091
+ points, method, group, options, shifts,
1092
+ examined_dimensions,
1093
+ ended_usec - started_usec, &stats);
1094
+ break;
1095
+
1096
+ default:
1097
+ case WEIGHTS_FORMAT_CONTEXTS:
1098
+ added_dimensions = registered_results_to_json_contexts(results, wb,
1099
+ after, before,
1100
+ baseline_after, baseline_before,
1101
+ points, method, group, options, shifts,
1102
+ examined_dimensions,
1103
+ ended_usec - started_usec, &stats);
1104
+ break;
1105
+ }
1106
1107
if(!added_dimensions) {
869
- error = "no results produced from correlations";
1108
+ error = "no results produced.";
1109
resp = HTTP_RESP_NOT_FOUND;
1110
}
1111
@@ -882,8 +1121,6 @@ cleanup:
1121
return resp;
1122
}
1123
885
-
886
-
1124
// ----------------------------------------------------------------------------
1125
// unittest
1126
web/api/queries/weights.h
new
+33
@@ -0,0 +1,33 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+#ifndef NETDATA_API_WEIGHTS_H
4
+#define NETDATA_API_WEIGHTS_H 1
5
+
6
+#include "query.h"
7
+
8
+typedef enum {
9
+ WEIGHTS_METHOD_MC_KS2 = 1,
10
+ WEIGHTS_METHOD_MC_VOLUME = 2,
11
+ WEIGHTS_METHOD_ANOMALY_RATE = 3,
12
+} WEIGHTS_METHOD;
13
+
14
+typedef enum {
15
+ WEIGHTS_FORMAT_CHARTS = 1,
16
+ WEIGHTS_FORMAT_CONTEXTS = 2,
17
+} WEIGHTS_FORMAT;
18
+
19
+extern int enable_metric_correlations;
20
+extern int metric_correlations_version;
21
+extern WEIGHTS_METHOD default_metric_correlations_method;
22
+
23
+extern int web_api_v1_weights (RRDHOST *host, BUFFER *wb, WEIGHTS_METHOD method, WEIGHTS_FORMAT format,
24
+ RRDR_GROUPING group, const char *group_options,
25
+ long long baseline_after, long long baseline_before,
26
+ long long after, long long before,
27
+ long long points, RRDR_OPTIONS options, SIMPLE_PATTERN *contexts, int tier, int timeout);
28
+
29
+extern WEIGHTS_METHOD weights_string_to_method(const char *method);
30
+extern const char *weights_method_to_string(WEIGHTS_METHOD method);
31
+extern int mc_unittest(void);
32
+
33
+#endif //NETDATA_API_WEIGHTS_H
web/api/web_api_v1.c
+32
-7
@@ -1476,16 +1476,17 @@ static int web_client_api_request_v1_aclk_state(RRDHOST *host, struct web_client
1476
return HTTP_RESP_OK;
1477
}
1478
1479
-int web_client_api_request_v1_metric_correlations(RRDHOST *host, struct web_client *w, char *url) {
1479
+static int web_client_api_request_v1_weights_internal(RRDHOST *host, struct web_client *w, char *url, WEIGHTS_METHOD method, WEIGHTS_FORMAT format) {
1480
if (!netdata_ready)
1481
return HTTP_RESP_BACKEND_FETCH_FAILED;
1482
1483
long long baseline_after = 0, baseline_before = 0, after = 0, before = 0, points = 0;
1484
RRDR_OPTIONS options = RRDR_OPTION_NOT_ALIGNED | RRDR_OPTION_NONZERO | RRDR_OPTION_NULL2ZERO;
1485
- METRIC_CORRELATIONS_METHOD method = default_metric_correlations_method;
1485
+ int options_count = 0;
1486
RRDR_GROUPING group = RRDR_GROUPING_AVERAGE;
1487
int timeout = 0;
1488
- const char *group_options = NULL;
1488
+ int tier = 0;
1489
+ const char *group_options = NULL, *contexts_str = NULL;
1490
1491
while (url) {
1492
char *value = mystrsep(&url, "&");
@@ -1519,20 +1520,43 @@ int web_client_api_request_v1_metric_correlations(RRDHOST *host, struct web_clie
1520
else if(!strcmp(name, "group"))
1521
group = web_client_api_request_v1_data_group(value, RRDR_GROUPING_AVERAGE);
1522
1522
- else if(!strcmp(name, "options"))
1523
+ else if(!strcmp(name, "options")) {
1524
+ if(!options_count) options = RRDR_OPTION_NOT_ALIGNED | RRDR_OPTION_NULL2ZERO;
1525
options |= web_client_api_request_v1_data_options(value);
1526
+ options_count++;
1527
+ }
1528
1529
else if(!strcmp(name, "method"))
1526
- method = mc_string_to_method(value);
1530
+ method = weights_string_to_method(value);
1531
+
1532
+ else if(!strcmp(name, "context") || !strcmp(name, "contexts"))
1533
+ contexts_str = value;
1534
1535
+ else if(!strcmp(name, "tier")) {
1536
+ tier = str2i(value);
1537
+ if(tier >= 0 && tier < storage_tiers)
1538
+ options |= RRDR_OPTION_SELECTED_TIER;
1539
+ }
1540
}
1541
1542
BUFFER *wb = w->response.data;
1543
buffer_flush(wb);
1544
wb->contenttype = CT_APPLICATION_JSON;
1533
- buffer_no_cacheable(wb);
1545
1535
- return metric_correlations(host, wb, method, group, group_options, baseline_after, baseline_before, after, before, points, options, timeout);
1546
+ SIMPLE_PATTERN *contexts = (contexts_str) ? simple_pattern_create(contexts_str, ",|\t\r\n\f\v", SIMPLE_PATTERN_EXACT) : NULL;
1547
+
1548
+ int ret = web_api_v1_weights(host, wb, method, format, group, group_options, baseline_after, baseline_before, after, before, points, options, contexts, tier, timeout);
1549
+
1550
+ simple_pattern_free(contexts);
1551
+ return ret;
1552
+}
1553
+
1554
+int web_client_api_request_v1_metric_correlations(RRDHOST *host, struct web_client *w, char *url) {
1555
+ return web_client_api_request_v1_weights_internal(host, w, url, default_metric_correlations_method, WEIGHTS_FORMAT_CHARTS);
1556
+}
1557
+
1558
+int web_client_api_request_v1_weights(RRDHOST *host, struct web_client *w, char *url) {
1559
+ return web_client_api_request_v1_weights_internal(host, w, url, WEIGHTS_METHOD_ANOMALY_RATE, WEIGHTS_FORMAT_CONTEXTS);
1560
}
1561
1562
#ifndef ENABLE_DBENGINE
@@ -1673,6 +1697,7 @@ static struct api_command {
1697
{ "manage/health", 0, WEB_CLIENT_ACL_MGMT, web_client_api_request_v1_mgmt_health },
1698
{ "aclk", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_aclk_state },
1699
{ "metric_correlations", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_metric_correlations },
1700
+ { "weights", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_weights },
1701
1702
{ "dbengine_stats", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_dbengine_stats },
1703
web/api/web_api_v1.h
+1
@@ -7,6 +7,7 @@
7
#include "web/api/badges/web_buffer_svg.h"
8
#include "web/api/formatters/rrd2json.h"
9
#include "web/api/health/health_cmdapi.h"
10
+#include "web/api/queries/weights.h"
11
12
#define MAX_CHART_LABELS_FILTER (32)
13
extern RRDR_OPTIONS web_client_api_request_v1_data_options(char *o);