1
// SPDX-License-Identifier: GPL-3.0-or-later
2
3
-#include <dlib/statistics.h>
4
-
3
#include "Config.h"
4
#include "Host.h"
5
+#include "ADCharts.h"
6
7
#include "json/single_include/nlohmann/json.hpp"
8
9
using namespace ml;
10
12
-static void updateDimensionsChart(RRDHOST *RH,
13
- collected_number NumTrainedDimensions,
14
- collected_number NumNormalDimensions,
15
- collected_number NumAnomalousDimensions) {
16
- static thread_local RRDSET *RS = nullptr;
17
- static thread_local RRDDIM *NumTotalDimensionsRD = nullptr;
18
- static thread_local RRDDIM *NumTrainedDimensionsRD = nullptr;
19
- static thread_local RRDDIM *NumNormalDimensionsRD = nullptr;
20
- static thread_local RRDDIM *NumAnomalousDimensionsRD = nullptr;
21
-
22
- if (!RS) {
23
- std::stringstream IdSS, NameSS;
24
-
25
- IdSS << "dimensions_on_" << localhost->machine_guid;
26
- NameSS << "dimensions_on_" << rrdhost_hostname(localhost);
27
-
28
- RS = rrdset_create(
29
- RH,
30
- "anomaly_detection", // type
31
- IdSS.str().c_str(), // id
32
- NameSS.str().c_str(), // name
33
- "dimensions", // family
34
- "anomaly_detection.dimensions", // ctx
35
- "Anomaly detection dimensions", // title
36
- "dimensions", // units
37
- "netdata", // plugin
38
- "ml", // module
39
- 39183, // priority
40
- RH->rrd_update_every, // update_every
41
- RRDSET_TYPE_LINE // chart_type
42
- );
43
- rrdset_flag_set(RS, RRDSET_FLAG_ANOMALY_DETECTION);
44
-
45
- NumTotalDimensionsRD = rrddim_add(RS, "total", NULL,
46
- 1, 1, RRD_ALGORITHM_ABSOLUTE);
47
- NumTrainedDimensionsRD = rrddim_add(RS, "trained", NULL,
48
- 1, 1, RRD_ALGORITHM_ABSOLUTE);
49
- NumNormalDimensionsRD = rrddim_add(RS, "normal", NULL,
50
- 1, 1, RRD_ALGORITHM_ABSOLUTE);
51
- NumAnomalousDimensionsRD = rrddim_add(RS, "anomalous", NULL,
52
- 1, 1, RRD_ALGORITHM_ABSOLUTE);
53
- } else
54
- rrdset_next(RS);
55
-
56
- rrddim_set_by_pointer(RS, NumTotalDimensionsRD, NumNormalDimensions + NumAnomalousDimensions);
57
- rrddim_set_by_pointer(RS, NumTrainedDimensionsRD, NumTrainedDimensions);
58
- rrddim_set_by_pointer(RS, NumNormalDimensionsRD, NumNormalDimensions);
59
- rrddim_set_by_pointer(RS, NumAnomalousDimensionsRD, NumAnomalousDimensions);
60
-
61
- rrdset_done(RS);
62
-}
63
-
64
-static void updateRateChart(RRDHOST *RH, collected_number AnomalyRate) {
65
- static thread_local RRDSET *RS = nullptr;
66
- static thread_local RRDDIM *AnomalyRateRD = nullptr;
67
-
68
- if (!RS) {
69
- std::stringstream IdSS, NameSS;
70
-
71
- IdSS << "anomaly_rate_on_" << localhost->machine_guid;
72
- NameSS << "anomaly_rate_on_" << rrdhost_hostname(localhost);
73
-
74
- RS = rrdset_create(
75
- RH,
76
- "anomaly_detection", // type
77
- IdSS.str().c_str(), // id
78
- NameSS.str().c_str(), // name
79
- "anomaly_rate", // family
80
- "anomaly_detection.anomaly_rate", // ctx
81
- "Percentage of anomalous dimensions", // title
82
- "percentage", // units
83
- "netdata", // plugin
84
- "ml", // module
85
- 39184, // priority
86
- RH->rrd_update_every, // update_every
87
- RRDSET_TYPE_LINE // chart_type
88
- );
89
- rrdset_flag_set(RS, RRDSET_FLAG_ANOMALY_DETECTION);
90
-
91
- AnomalyRateRD = rrddim_add(RS, "anomaly_rate", NULL,
92
- 1, 100, RRD_ALGORITHM_ABSOLUTE);
93
- } else
94
- rrdset_next(RS);
95
-
96
- rrddim_set_by_pointer(RS, AnomalyRateRD, AnomalyRate);
97
-
98
- rrdset_done(RS);
99
-}
100
-
101
-static void updateWindowLengthChart(RRDHOST *RH, collected_number WindowLength) {
102
- static thread_local RRDSET *RS = nullptr;
103
- static thread_local RRDDIM *WindowLengthRD = nullptr;
104
-
105
- if (!RS) {
106
- std::stringstream IdSS, NameSS;
107
-
108
- IdSS << "detector_window_on_" << localhost->machine_guid;
109
- NameSS << "detector_window_on_" << rrdhost_hostname(localhost);
110
-
111
- RS = rrdset_create(
112
- RH,
113
- "anomaly_detection", // type
114
- IdSS.str().c_str(), // id
115
- NameSS.str().c_str(), // name
116
- "detector_window", // family
117
- "anomaly_detection.detector_window", // ctx
118
- "Anomaly detector window length", // title
119
- "seconds", // units
120
- "netdata", // plugin
121
- "ml", // module
122
- 39185, // priority
123
- RH->rrd_update_every, // update_every
124
- RRDSET_TYPE_LINE // chart_type
125
- );
126
- rrdset_flag_set(RS, RRDSET_FLAG_ANOMALY_DETECTION);
127
-
128
- WindowLengthRD = rrddim_add(RS, "duration", NULL,
129
- 1, 1, RRD_ALGORITHM_ABSOLUTE);
130
- } else
131
- rrdset_next(RS);
132
-
133
- rrddim_set_by_pointer(RS, WindowLengthRD, WindowLength * RH->rrd_update_every);
134
- rrdset_done(RS);
135
-}
136
-
137
-static void updateEventsChart(RRDHOST *RH,
138
- std::pair<BitRateWindow::Edge, size_t> P,
139
- bool ResetBitCounter,
140
- bool NewAnomalyEvent) {
141
- static thread_local RRDSET *RS = nullptr;
142
- static thread_local RRDDIM *AboveThresholdRD = nullptr;
143
- static thread_local RRDDIM *ResetBitCounterRD = nullptr;
144
- static thread_local RRDDIM *NewAnomalyEventRD = nullptr;
145
-
146
- if (!RS) {
147
- std::stringstream IdSS, NameSS;
148
-
149
- IdSS << "detector_events_on_" << localhost->machine_guid;
150
- NameSS << "detector_events_on_" << rrdhost_hostname(localhost);
151
-
152
- RS = rrdset_create(
153
- RH,
154
- "anomaly_detection", // type
155
- IdSS.str().c_str(), // id
156
- NameSS.str().c_str(), // name
157
- "detector_events", // family
158
- "anomaly_detection.detector_events", // ctx
159
- "Anomaly events triggered", // title
160
- "boolean", // units
161
- "netdata", // plugin
162
- "ml", // module
163
- 39186, // priority
164
- RH->rrd_update_every, // update_every
165
- RRDSET_TYPE_LINE // chart_type
166
- );
167
- rrdset_flag_set(RS, RRDSET_FLAG_ANOMALY_DETECTION);
168
-
169
- AboveThresholdRD = rrddim_add(RS, "above_threshold", NULL,
170
- 1, 1, RRD_ALGORITHM_ABSOLUTE);
171
- ResetBitCounterRD = rrddim_add(RS, "reset_bit_counter", NULL,
172
- 1, 1, RRD_ALGORITHM_ABSOLUTE);
173
- NewAnomalyEventRD = rrddim_add(RS, "new_anomaly_event", NULL,
174
- 1, 1, RRD_ALGORITHM_ABSOLUTE);
175
- } else
176
- rrdset_next(RS);
177
-
178
- BitRateWindow::Edge E = P.first;
179
- bool AboveThreshold = E.second == BitRateWindow::State::AboveThreshold;
180
-
181
- rrddim_set_by_pointer(RS, AboveThresholdRD, AboveThreshold);
182
- rrddim_set_by_pointer(RS, ResetBitCounterRD, ResetBitCounter);
183
- rrddim_set_by_pointer(RS, NewAnomalyEventRD, NewAnomalyEvent);
184
-
185
- rrdset_done(RS);
186
-}
187
-
188
-static void updateDetectionChart(RRDHOST *RH) {
189
- static thread_local RRDSET *RS = nullptr;
190
- static thread_local RRDDIM *UserRD, *SystemRD = nullptr;
191
-
192
- if (!RS) {
193
- std::stringstream IdSS, NameSS;
194
-
195
- IdSS << "prediction_stats_" << RH->machine_guid;
196
- NameSS << "prediction_stats_for_" << rrdhost_hostname(RH);
197
-
198
- RS = rrdset_create_localhost(
199
- "netdata", // type
200
- IdSS.str().c_str(), // id
201
- NameSS.str().c_str(), // name
202
- "ml", // family
203
- "netdata.prediction_stats", // ctx
204
- "Prediction thread CPU usage", // title
205
- "milliseconds/s", // units
206
- "netdata", // plugin
207
- "ml", // module
208
- 136000, // priority
209
- RH->rrd_update_every, // update_every
210
- RRDSET_TYPE_STACKED // chart_type
211
- );
212
-
213
- UserRD = rrddim_add(RS, "user", NULL, 1, 1000, RRD_ALGORITHM_INCREMENTAL);
214
- SystemRD = rrddim_add(RS, "system", NULL, 1, 1000, RRD_ALGORITHM_INCREMENTAL);
215
- } else
216
- rrdset_next(RS);
217
-
218
- struct rusage TRU;
219
- getrusage(RUSAGE_THREAD, &TRU);
220
-
221
- rrddim_set_by_pointer(RS, UserRD, TRU.ru_utime.tv_sec * 1000000ULL + TRU.ru_utime.tv_usec);
222
- rrddim_set_by_pointer(RS, SystemRD, TRU.ru_stime.tv_sec * 1000000ULL + TRU.ru_stime.tv_usec);
223
- rrdset_done(RS);
224
-}
225
-
226
-static void updateTrainingChart(RRDHOST *RH, struct rusage *TRU)
227
-{
228
- static thread_local RRDSET *RS = nullptr;
229
- static thread_local RRDDIM *UserRD = nullptr;
230
- static thread_local RRDDIM *SystemRD = nullptr;
231
-
232
- if (!RS) {
233
- std::stringstream IdSS, NameSS;
234
-
235
- IdSS << "training_stats_" << RH->machine_guid;
236
- NameSS << "training_stats_for_" << rrdhost_hostname(RH);
237
-
238
- RS = rrdset_create_localhost(
239
- "netdata", // type
240
- IdSS.str().c_str(), // id
241
- NameSS.str().c_str(), // name
242
- "ml", // family
243
- "netdata.training_stats", // ctx
244
- "Training thread CPU usage", // title
245
- "milliseconds/s", // units
246
- "netdata", // plugin
247
- "ml", // module
248
- 136001, // priority
249
- RH->rrd_update_every, // update_every
250
- RRDSET_TYPE_STACKED // chart_type
251
- );
252
-
253
- UserRD = rrddim_add(RS, "user", NULL, 1, 1000, RRD_ALGORITHM_INCREMENTAL);
254
- SystemRD = rrddim_add(RS, "system", NULL, 1, 1000, RRD_ALGORITHM_INCREMENTAL);
255
- } else
256
- rrdset_next(RS);
257
-
258
- rrddim_set_by_pointer(RS, UserRD, TRU->ru_utime.tv_sec * 1000000ULL + TRU->ru_utime.tv_usec);
259
- rrddim_set_by_pointer(RS, SystemRD, TRU->ru_stime.tv_sec * 1000000ULL + TRU->ru_stime.tv_usec);
260
- rrdset_done(RS);
261
-}
262
-
11
void RrdHost::addDimension(Dimension *D) {
264
- RRDDIM *AnomalyRateRD = rrddim_add(AnomalyRateRS, D->getID().c_str(), NULL,
265
- 1, 1000, RRD_ALGORITHM_ABSOLUTE);
266
- D->setAnomalyRateRD(AnomalyRateRD);
267
-
268
- {
269
- std::lock_guard<std::mutex> Lock(Mutex);
12
+ std::lock_guard<std::mutex> Lock(Mutex);
13
271
- DimensionsMap[D->getRD()] = D;
14
+ DimensionsMap[D->getRD()] = D;
15
273
- // Default construct mutex for dimension
274
- LocksMap[D];
275
- }
16
+ // Default construct mutex for dimension
17
+ LocksMap[D];
18
}
19
20
void RrdHost::removeDimension(Dimension *D) {
54
Json["max-kmeans-iters"] = Cfg.MaxKMeansIters;
55
56
Json["dimension-anomaly-score-threshold"] = Cfg.DimensionAnomalyScoreThreshold;
315
- Json["host-anomaly-rate-threshold"] = Cfg.HostAnomalyRateThreshold;
57
317
- Json["min-window-size"] = Cfg.ADMinWindowSize;
318
- Json["max-window-size"] = Cfg.ADMaxWindowSize;
319
- Json["idle-window-size"] = Cfg.ADIdleWindowSize;
320
- Json["window-rate-threshold"] = Cfg.ADWindowRateThreshold;
321
- Json["dimension-rate-threshold"] = Cfg.ADDimensionRateThreshold;
58
+ Json["host-anomaly-rate-threshold"] = Cfg.HostAnomalyRateThreshold;
59
+ Json["anomaly-detection-grouping-method"] = group_method2string(Cfg.AnomalyDetectionGroupingMethod);
60
+ Json["anomaly-detection-query-duration"] = Cfg.AnomalyDetectionQueryDuration;
61
62
Json["hosts-to-skip"] = Cfg.HostsToSkip;
63
Json["charts-to-skip"] = Cfg.ChartsToSkip;
64
}
65
66
+void TrainableHost::getModelsAsJson(nlohmann::json &Json) {
67
+ std::lock_guard<std::mutex> Lock(Mutex);
68
+
69
+ for (auto &DP : DimensionsMap) {
70
+ Dimension *D = DP.second;
71
+
72
+ nlohmann::json JsonArray = nlohmann::json::array();
73
+ for (const KMeans &KM : D->getModels()) {
74
+ nlohmann::json J;
75
+ KM.toJson(J);
76
+ JsonArray.push_back(J);
77
+ }
78
+ Json[getMLDimensionID(D->getRD())] = JsonArray;
79
+ }
80
+
81
+ return;
82
+}
83
+
84
std::pair<Dimension *, Duration<double>>
85
TrainableHost::findDimensionToTrain(const TimePoint &NowTP) {
86
std::lock_guard<std::mutex> Lock(Mutex);
150
#define WORKER_JOB_UPDATE_DETECTION_CHART 1
151
#define WORKER_JOB_UPDATE_ANOMALY_RATES 2
152
#define WORKER_JOB_UPDATE_CHARTS 3
396
-#define WORKER_JOB_SAVE_ANOMALY_EVENT 4
153
154
#if WORKER_UTILIZATION_MAX_JOB_TYPES < 5
155
#error WORKER_UTILIZATION_MAX_JOB_TYPES has to be at least 5
156
#endif
157
158
void DetectableHost::detectOnce() {
403
- auto P = BRW.insert(WindowAnomalyRate >= Cfg.HostAnomalyRateThreshold);
404
- BitRateWindow::Edge Edge = P.first;
405
- size_t WindowLength = P.second;
406
-
407
- bool ResetBitCounter = (Edge.first != BitRateWindow::State::AboveThreshold);
408
- bool NewAnomalyEvent = (Edge.first == BitRateWindow::State::AboveThreshold) &&
409
- (Edge.second == BitRateWindow::State::Idle);
410
-
411
- std::vector<std::pair<double, std::string>> DimsOverThreshold;
412
-
159
size_t NumAnomalousDimensions = 0;
160
size_t NumNormalDimensions = 0;
161
size_t NumTrainedDimensions = 0;
168
{
169
std::lock_guard<std::mutex> Lock(Mutex);
170
425
- DimsOverThreshold.reserve(DimensionsMap.size());
426
-
171
for (auto &DP : DimensionsMap) {
172
worker_is_busy(WORKER_JOB_DETECT_DIMENSION);
173
179
}
180
181
NumActiveDimensions++;
438
-
439
- auto P = D->detect(WindowLength, ResetBitCounter);
440
- bool IsAnomalous = P.first;
441
- double AnomalyScore = P.second;
442
-
182
NumTrainedDimensions += D->isTrained();
183
184
+ bool IsAnomalous = D->isAnomalous();
185
if (IsAnomalous)
186
NumAnomalousDimensions += 1;
447
-
448
- if (NewAnomalyEvent && (AnomalyScore >= Cfg.ADDimensionRateThreshold))
449
- DimsOverThreshold.push_back({ AnomalyScore, D->getID() });
450
-
187
D->updateAnomalyBitCounter(AnomalyRateRS, AnomalyRateTimer, IsAnomalous);
188
}
189
190
if (NumAnomalousDimensions)
455
- WindowAnomalyRate = static_cast<double>(NumAnomalousDimensions) / NumActiveDimensions;
191
+ HostAnomalyRate = static_cast<double>(NumAnomalousDimensions) / NumActiveDimensions;
192
else
457
- WindowAnomalyRate = 0.0;
193
+ HostAnomalyRate = 0.0;
194
195
NumNormalDimensions = NumActiveDimensions - NumAnomalousDimensions;
196
}
208
209
worker_is_busy(WORKER_JOB_UPDATE_CHARTS);
210
updateDimensionsChart(getRH(), NumTrainedDimensions, NumNormalDimensions, NumAnomalousDimensions);
475
- updateRateChart(getRH(), WindowAnomalyRate * 10000.0);
476
- updateWindowLengthChart(getRH(), WindowLength);
477
- updateEventsChart(getRH(), P, ResetBitCounter, NewAnomalyEvent);
211
+ updateHostAndDetectionRateCharts(getRH(), HostAnomalyRate * 10000.0);
212
213
struct rusage TRU;
214
getResourceUsage(&TRU);
215
updateTrainingChart(getRH(), &TRU);
482
-
483
- if (!NewAnomalyEvent || (DimsOverThreshold.size() == 0))
484
- return;
485
-
486
- worker_is_busy(WORKER_JOB_SAVE_ANOMALY_EVENT);
487
-
488
- std::sort(DimsOverThreshold.begin(), DimsOverThreshold.end());
489
- std::reverse(DimsOverThreshold.begin(), DimsOverThreshold.end());
490
-
491
- // Make sure the JSON response won't grow beyond a specific number
492
- // of dimensions. Log an error message if this happens, because it
493
- // most likely means that the user specified a very-low anomaly rate
494
- // threshold.
495
- size_t NumMaxDimsOverThreshold = 2000;
496
- if (DimsOverThreshold.size() > NumMaxDimsOverThreshold) {
497
- error("Found %zu dimensions over threshold. Reducing JSON result to %zu dimensions.",
498
- DimsOverThreshold.size(), NumMaxDimsOverThreshold);
499
- DimsOverThreshold.resize(NumMaxDimsOverThreshold);
500
- }
501
-
502
- nlohmann::json JsonResult = DimsOverThreshold;
503
-
504
- time_t Before = now_realtime_sec();
505
- time_t After = Before - (WindowLength * updateEvery());
506
- DB.insertAnomaly("AD1", 1, getUUID(), After, Before, JsonResult.dump(4));
216
}
217
218
void DetectableHost::detect() {
221
worker_register_job_name(WORKER_JOB_UPDATE_DETECTION_CHART, "detection chart");
222
worker_register_job_name(WORKER_JOB_UPDATE_ANOMALY_RATES, "anomaly rates");
223
worker_register_job_name(WORKER_JOB_UPDATE_CHARTS, "charts");
515
- worker_register_job_name(WORKER_JOB_SAVE_ANOMALY_EVENT, "anomaly event");
224
225
std::this_thread::sleep_for(Seconds{10});
226