ML-related changes to address issue/discussion comments. (#12494)
* Increase training thread's max sleep time. With this change we will only cap the allotted time when it is more than ten seconds. The previous limit was one second, which had the effect of scheduling dimensions near the beggining of each training window. This was not desirable because it would cause high CPU usage on parents with many children. * Only exclude netdata.* charts from training. * Use heartbeat in detection thread. * Track rusage of prediction thread. * Track rusage of training thread. * Add support for random sampling of extracted features. * Rebase * Skip RNG when ML is disabled and fix undef behaviour
vkalintiris committed
Mar 30, 2022 at 13:38 UTC
41a40dc3a406c3c8dc70f41038e7d75ef2601f8b
9 files changed
+89
-54
ml/Config.cc
+4
-4
@@ -38,6 +38,7 @@ void Config::readMLConfig(void) {
38
unsigned SmoothN = config_get_number(ConfigSectionML, "num samples to smooth", 3);
39
unsigned LagN = config_get_number(ConfigSectionML, "num samples to lag", 5);
40
41
+ double RandomSamplingRatio = config_get_float(ConfigSectionML, "random sampling ratio", 1.0 / LagN);
42
unsigned MaxKMeansIters = config_get_number(ConfigSectionML, "maximum number of k-means iterations", 1000);
43
44
double DimensionAnomalyScoreThreshold = config_get_float(ConfigSectionML, "dimension anomaly score threshold", 0.99);
@@ -67,6 +68,7 @@ void Config::readMLConfig(void) {
68
SmoothN = clamp(SmoothN, 0u, 5u);
69
LagN = clamp(LagN, 0u, 5u);
70
71
+ RandomSamplingRatio = clamp(RandomSamplingRatio, 0.2, 1.0);
72
MaxKMeansIters = clamp(MaxKMeansIters, 500u, 1000u);
73
74
DimensionAnomalyScoreThreshold = clamp(DimensionAnomalyScoreThreshold, 0.01, 5.00);
@@ -112,6 +114,7 @@ void Config::readMLConfig(void) {
114
Cfg.SmoothN = SmoothN;
115
Cfg.LagN = LagN;
116
117
+ Cfg.RandomSamplingRatio = RandomSamplingRatio;
118
Cfg.MaxKMeansIters = MaxKMeansIters;
119
120
Cfg.DimensionAnomalyScoreThreshold = DimensionAnomalyScoreThreshold;
@@ -128,9 +131,6 @@ void Config::readMLConfig(void) {
131
132
// Always exclude anomaly_detection charts from training.
133
Cfg.ChartsToSkip = "anomaly_detection.* ";
131
- Cfg.ChartsToSkip += config_get(ConfigSectionML, "charts to skip from training",
132
- "!system.* !cpu.* !mem.* !disk.* !disk_* "
133
- "!ip.* !ipv4.* !ipv6.* !net.* !net_* !netfilter.* "
134
- "!services.* !apps.* !groups.* !user.* !ebpf.* !netdata.* *");
134
+ Cfg.ChartsToSkip += config_get(ConfigSectionML, "charts to skip from training", "netdata.*");
135
Cfg.SP_ChartsToSkip = simple_pattern_create(ChartsToSkip.c_str(), NULL, SIMPLE_PATTERN_EXACT);
136
}
ml/Config.h
+2
@@ -21,6 +21,7 @@ public:
21
unsigned SmoothN;
22
unsigned LagN;
23
24
+ double RandomSamplingRatio;
25
unsigned MaxKMeansIters;
26
27
double DimensionAnomalyScoreThreshold;
@@ -39,6 +40,7 @@ public:
40
SIMPLE_PATTERN *SP_ChartsToSkip;
41
42
std::string AnomalyDBPath;
43
+ std::vector<uint32_t> RandomNums;
44
45
void readMLConfig();
46
};
ml/Dimension.cc
+8
-2
@@ -125,8 +125,13 @@ MLResult TrainableDimension::trainModel() {
125
if (!CNs)
126
return MLResult::MissingData;
127
128
- SamplesBuffer SB = SamplesBuffer(CNs, N, 1, Cfg.DiffN, Cfg.SmoothN, Cfg.LagN);
128
+ unsigned TargetNumSamples = Cfg.MaxTrainSamples * Cfg.RandomSamplingRatio;
129
+ double SamplingRatio = std::min(static_cast<double>(TargetNumSamples) / N, 1.0);
130
+
131
+ SamplesBuffer SB = SamplesBuffer(CNs, N, 1, Cfg.DiffN, Cfg.SmoothN, Cfg.LagN,
132
+ SamplingRatio, Cfg.RandomNums);
133
KM.train(SB, Cfg.MaxKMeansIters);
134
+
135
Trained = true;
136
ConstantModel = true;
137
@@ -162,7 +167,8 @@ std::pair<MLResult, bool> PredictableDimension::predict() {
167
CalculatedNumber *TmpCNs = new CalculatedNumber[N * (Cfg.LagN + 1)]();
168
std::memcpy(TmpCNs, CNs.data(), N * sizeof(CalculatedNumber));
169
165
- SamplesBuffer SB = SamplesBuffer(TmpCNs, N, 1, Cfg.DiffN, Cfg.SmoothN, Cfg.LagN);
170
+ SamplesBuffer SB = SamplesBuffer(TmpCNs, N, 1, Cfg.DiffN, Cfg.SmoothN, Cfg.LagN,
171
+ 1.0, Cfg.RandomNums);
172
AnomalyScore = computeAnomalyScore(SB);
173
delete[] TmpCNs;
174
ml/Dimension.h
-5
@@ -76,10 +76,6 @@ public:
76
77
bool isTrained() const { return Trained; }
78
79
- double updateTrainingDuration(double Duration) {
80
- return TrainingDuration.exchange(Duration);
81
- }
82
-
79
private:
80
std::pair<CalculatedNumber *, size_t> getCalculatedNumbers();
81
@@ -94,7 +90,6 @@ private:
90
KMeans KM;
91
92
std::atomic<bool> Trained{false};
97
- std::atomic<double> TrainingDuration{0.0};
93
};
94
95
class PredictableDimension : public TrainableDimension {
ml/Host.cc
+35
-42
@@ -184,13 +184,13 @@ static void updateEventsChart(RRDHOST *RH,
184
rrdset_done(RS);
185
}
186
187
-static void updateDetectionChart(RRDHOST *RH, collected_number PredictionDuration) {
187
+static void updateDetectionChart(RRDHOST *RH) {
188
static thread_local RRDSET *RS = nullptr;
189
- static thread_local RRDDIM *PredictiobDurationRD = nullptr;
189
+ static thread_local RRDDIM *UserRD, *SystemRD = nullptr;
190
191
if (!RS) {
192
std::string IdPrefix = "prediction_stats";
193
- std::string TitlePrefix = "Time it took to run prediction for host";
193
+ std::string TitlePrefix = "Prediction thread CPU usage for host";
194
auto IdTitlePair = getHostSpecificIdAndTitle(RH, IdPrefix, TitlePrefix);
195
196
RS = rrdset_create_localhost(
@@ -200,35 +200,36 @@ static void updateDetectionChart(RRDHOST *RH, collected_number PredictionDuratio
200
"prediction_stats", // family
201
"anomaly_detection.prediction_stats", // ctx
202
IdTitlePair.second.c_str(), // title
203
- "milliseconds", // units
203
+ "milliseconds/s", // units
204
"netdata", // plugin
205
"ml", // module
206
39187, // priority
207
RH->rrd_update_every, // update_every
208
- RRDSET_TYPE_LINE // chart_type
208
+ RRDSET_TYPE_STACKED // chart_type
209
);
210
211
- PredictiobDurationRD = rrddim_add(RS, "duration", NULL,
212
- 1, 1, RRD_ALGORITHM_ABSOLUTE);
211
+ UserRD = rrddim_add(RS, "user", NULL, 1, 1000, RRD_ALGORITHM_INCREMENTAL);
212
+ SystemRD = rrddim_add(RS, "system", NULL, 1, 1000, RRD_ALGORITHM_INCREMENTAL);
213
} else
214
rrdset_next(RS);
215
216
- rrddim_set_by_pointer(RS, PredictiobDurationRD, PredictionDuration);
216
+ struct rusage TRU;
217
+ getrusage(RUSAGE_THREAD, &TRU);
218
219
+ rrddim_set_by_pointer(RS, UserRD, TRU.ru_utime.tv_sec * 1000000ULL + TRU.ru_utime.tv_usec);
220
+ rrddim_set_by_pointer(RS, SystemRD, TRU.ru_stime.tv_sec * 1000000ULL + TRU.ru_stime.tv_usec);
221
rrdset_done(RS);
222
}
223
221
-static void updateTrainingChart(RRDHOST *RH,
222
- collected_number TotalTrainingDuration,
223
- collected_number MaxTrainingDuration)
224
+static void updateTrainingChart(RRDHOST *RH, struct rusage *TRU)
225
{
226
static thread_local RRDSET *RS = nullptr;
226
- static thread_local RRDDIM *TotalTrainingDurationRD = nullptr;
227
- static thread_local RRDDIM *MaxTrainingDurationRD = nullptr;
227
+ static thread_local RRDDIM *UserRD = nullptr;
228
+ static thread_local RRDDIM *SystemRD = nullptr;
229
230
if (!RS) {
231
std::string IdPrefix = "training_stats";
231
- std::string TitlePrefix = "Training step statistics for host";
232
+ std::string TitlePrefix = "Training thread CPU usage for host";
233
auto IdTitlePair = getHostSpecificIdAndTitle(RH, IdPrefix, TitlePrefix);
234
235
RS = rrdset_create_localhost(
@@ -238,24 +239,21 @@ static void updateTrainingChart(RRDHOST *RH,
239
"training_stats", // family
240
"anomaly_detection.training_stats", // ctx
241
IdTitlePair.second.c_str(), // title
241
- "milliseconds", // units
242
+ "milliseconds/s", // units
243
"netdata", // plugin
244
"ml", // module
245
39188, // priority
246
RH->rrd_update_every, // update_every
246
- RRDSET_TYPE_LINE // chart_type
247
+ RRDSET_TYPE_STACKED // chart_type
248
);
249
249
- TotalTrainingDurationRD = rrddim_add(RS, "total_training_duration", NULL,
250
- 1, 1, RRD_ALGORITHM_ABSOLUTE);
251
- MaxTrainingDurationRD = rrddim_add(RS, "max_training_duration", NULL,
252
- 1, 1, RRD_ALGORITHM_ABSOLUTE);
250
+ UserRD = rrddim_add(RS, "user", NULL, 1, 1000, RRD_ALGORITHM_INCREMENTAL);
251
+ SystemRD = rrddim_add(RS, "system", NULL, 1, 1000, RRD_ALGORITHM_INCREMENTAL);
252
} else
253
rrdset_next(RS);
254
256
- rrddim_set_by_pointer(RS, TotalTrainingDurationRD, TotalTrainingDuration);
257
- rrddim_set_by_pointer(RS, MaxTrainingDurationRD, MaxTrainingDuration);
258
-
255
+ rrddim_set_by_pointer(RS, UserRD, TRU->ru_utime.tv_sec * 1000000ULL + TRU->ru_utime.tv_usec);
256
+ rrddim_set_by_pointer(RS, SystemRD, TRU->ru_stime.tv_sec * 1000000ULL + TRU->ru_stime.tv_usec);
257
rrdset_done(RS);
258
}
259
@@ -307,6 +305,7 @@ void RrdHost::getConfigAsJson(nlohmann::json &Json) const {
305
Json["smooth-n"] = Cfg.SmoothN;
306
Json["lag-n"] = Cfg.LagN;
307
308
+ Json["random-sampling-ratio"] = Cfg.RandomSamplingRatio;
309
Json["max-kmeans-iters"] = Cfg.MaxKMeansIters;
310
311
Json["dimension-anomaly-score-threshold"] = Cfg.DimensionAnomalyScoreThreshold;
@@ -345,11 +344,7 @@ void TrainableHost::trainDimension(Dimension *D, const TimePoint &NowTP) {
344
return;
345
346
D->LastTrainedAt = NowTP + Seconds{D->updateEvery()};
348
-
349
- TimePoint StartTP = SteadyClock::now();
347
D->trainModel();
351
- Duration<double> Duration = SteadyClock::now() - StartTP;
352
- D->updateTrainingDuration(Duration.count());
348
349
{
350
std::lock_guard<std::mutex> Lock(Mutex);
@@ -358,9 +353,11 @@ void TrainableHost::trainDimension(Dimension *D, const TimePoint &NowTP) {
353
}
354
355
void TrainableHost::train() {
361
- Duration<double> MaxSleepFor = Seconds{updateEvery()};
356
+ Duration<double> MaxSleepFor = Seconds{10 * updateEvery()};
357
358
while (!netdata_exit) {
359
+ updateResourceUsage();
360
+
361
TimePoint NowTP = SteadyClock::now();
362
363
auto P = findDimensionToTrain(NowTP);
@@ -393,9 +390,6 @@ void DetectableHost::detectOnce() {
390
size_t NumNormalDimensions = 0;
391
size_t NumTrainedDimensions = 0;
392
396
- double TotalTrainingDuration = 0.0;
397
- double MaxTrainingDuration = 0.0;
398
-
393
bool CollectAnomalyRates = (++AnomalyRateTimer == Cfg.DBEngineAnomalyRateEvery);
394
if (CollectAnomalyRates)
395
rrdset_next(AnomalyRateRS);
@@ -414,10 +408,6 @@ void DetectableHost::detectOnce() {
408
409
NumTrainedDimensions += D->isTrained();
410
417
- double DimTrainingDuration = D->updateTrainingDuration(0.0);
418
- MaxTrainingDuration = std::max(MaxTrainingDuration, DimTrainingDuration);
419
- TotalTrainingDuration += DimTrainingDuration;
420
-
411
if (IsAnomalous)
412
NumAnomalousDimensions += 1;
413
@@ -448,7 +438,10 @@ void DetectableHost::detectOnce() {
438
updateRateChart(getRH(), WindowAnomalyRate * 10000.0);
439
updateWindowLengthChart(getRH(), WindowLength);
440
updateEventsChart(getRH(), P, ResetBitCounter, NewAnomalyEvent);
451
- updateTrainingChart(getRH(), TotalTrainingDuration * 1000.0, MaxTrainingDuration * 1000.0);
441
+
442
+ struct rusage TRU;
443
+ getResourceUsage(&TRU);
444
+ updateTrainingChart(getRH(), &TRU);
445
446
if (!NewAnomalyEvent || (DimsOverThreshold.size() == 0))
447
return;
@@ -477,15 +470,15 @@ void DetectableHost::detectOnce() {
470
void DetectableHost::detect() {
471
std::this_thread::sleep_for(Seconds{10});
472
473
+ heartbeat_t HB;
474
+ heartbeat_init(&HB);
475
+
476
while (!netdata_exit) {
481
- TimePoint StartTP = SteadyClock::now();
482
- detectOnce();
483
- TimePoint EndTP = SteadyClock::now();
477
+ heartbeat_next(&HB, updateEvery() * USEC_PER_SEC);
478
485
- Duration<double> Dur = EndTP - StartTP;
486
- updateDetectionChart(getRH(), Dur.count() * 1000);
479
+ detectOnce();
480
488
- std::this_thread::sleep_for(Seconds{updateEvery()});
481
+ updateDetectionChart(getRH());
482
}
483
}
484
ml/Host.h
+13
@@ -70,9 +70,22 @@ public:
70
71
void train();
72
73
+ void updateResourceUsage() {
74
+ std::lock_guard<std::mutex> Lock(ResourceUsageMutex);
75
+ getrusage(RUSAGE_THREAD, &ResourceUsage);
76
+ }
77
+
78
+ void getResourceUsage(struct rusage *RU) {
79
+ std::lock_guard<std::mutex> Lock(ResourceUsageMutex);
80
+ memcpy(RU, &ResourceUsage, sizeof(struct rusage));
81
+ }
82
+
83
private:
84
std::pair<Dimension *, Duration<double>> findDimensionToTrain(const TimePoint &NowTP);
85
void trainDimension(Dimension *D, const TimePoint &NowTP);
86
+
87
+ std::mutex ResourceUsageMutex;
88
+ struct rusage ResourceUsage;
89
};
90
91
class DetectableHost : public TrainableHost {
ml/kmeans/SamplesBuffer.cc
+6
@@ -130,7 +130,13 @@ std::vector<DSample> SamplesBuffer::preprocess() {
130
DSamples.reserve(OutN);
131
Preprocessed = true;
132
133
+ uint32_t MaxMT = std::numeric_limits<uint32_t>::max();
134
+ uint32_t CutOff = static_cast<double>(MaxMT) * SamplingRatio;
135
+
136
for (size_t Idx = NumSamples - OutN; Idx != NumSamples; Idx++) {
137
+ if (RandNums[Idx] > CutOff)
138
+ continue;
139
+
140
DSample DS;
141
DS.set_size(NumDimsPerSample * (LagN + 1));
142
ml/kmeans/SamplesBuffer.h
+6
-1
@@ -80,9 +80,11 @@ class SamplesBuffer {
80
public:
81
SamplesBuffer(CalculatedNumber *CNs,
82
size_t NumSamples, size_t NumDimsPerSample,
83
- size_t DiffN = 1, size_t SmoothN = 3, size_t LagN = 3) :
83
+ size_t DiffN, size_t SmoothN, size_t LagN,
84
+ double SamplingRatio, std::vector<uint32_t> &RandNums) :
85
CNs(CNs), NumSamples(NumSamples), NumDimsPerSample(NumDimsPerSample),
86
DiffN(DiffN), SmoothN(SmoothN), LagN(LagN),
87
+ SamplingRatio(SamplingRatio), RandNums(RandNums),
88
BytesPerSample(NumDimsPerSample * sizeof(CalculatedNumber)),
89
Preprocessed(false) {};
90
@@ -129,6 +131,9 @@ private:
131
size_t DiffN;
132
size_t SmoothN;
133
size_t LagN;
134
+ double SamplingRatio;
135
+ std::vector<uint32_t> &RandNums;
136
+
137
size_t BytesPerSample;
138
bool Preprocessed;
139
};
ml/ml.cc
+15
@@ -4,6 +4,8 @@
4
#include "Dimension.h"
5
#include "Host.h"
6
7
+#include <random>
8
+
9
using namespace ml;
10
11
bool ml_capable() {
@@ -27,7 +29,20 @@ bool ml_enabled(RRDHOST *RH) {
29
*/
30
31
void ml_init(void) {
32
+ // Read config values
33
Cfg.readMLConfig();
34
+
35
+ if (!Cfg.EnableAnomalyDetection)
36
+ return;
37
+
38
+ // Generate random numbers to efficiently sample the features we need
39
+ // for KMeans clustering.
40
+ std::random_device RD;
41
+ std::mt19937 Gen(RD());
42
+
43
+ Cfg.RandomNums.reserve(Cfg.MaxTrainSamples);
44
+ for (size_t Idx = 0; Idx != Cfg.MaxTrainSamples; Idx++)
45
+ Cfg.RandomNums.push_back(Gen());
46
}
47
48
void ml_new_host(RRDHOST *RH) {