Load/Store ML models (#14981)
* Pass DB connection in db_execute() * Add support for loading/saving models. * Fix ML stats when no training takes place. * Make model flushing batch size configurable. * Delete unused function * Update ML config. * Restore threshold for logs/period. * Rm whitespace. * Add missing dummy function. * Update function call arguments * Guard transactions with a lock when flushing ML models. * Mark dimensions with loaded models as trained.
vkalintiris committed
May 2, 2023 at 19:09 UTC
0a1ef218f00742758585eda5321e6630680eb1bb
12 files changed
+134
-83
database/sqlite/sqlite_aclk.c
+4
-4
@@ -255,7 +255,7 @@ static void sql_delete_aclk_table_list(char *host_guid)
255
if (unlikely(rc != SQLITE_OK))
256
error_report("Failed to finalize statement to clean up aclk tables, rc = %d", rc);
257
258
- rc = db_execute(buffer_tostring(sql));
258
+ rc = db_execute(db_meta, buffer_tostring(sql));
259
if (unlikely(rc))
260
error("Failed to drop unused ACLK tables");
261
@@ -294,7 +294,7 @@ static int sql_maint_aclk_sync_database(void *data __maybe_unused, int argc __ma
294
{
295
char sql[512];
296
snprintfz(sql,511, SQL_ALERT_CLEANUP, (char *) argv[0], ACLK_DELETE_ACK_ALERTS_INTERNAL);
297
- if (unlikely(db_execute(sql)))
297
+ if (unlikely(db_execute(db_meta, sql)))
298
error_report("Failed to clean stale ACLK alert entries");
299
return 0;
300
}
@@ -491,12 +491,12 @@ void sql_create_aclk_table(RRDHOST *host __maybe_unused, uuid_t *host_uuid __may
491
char sql[ACLK_SYNC_QUERY_SIZE];
492
493
snprintfz(sql, ACLK_SYNC_QUERY_SIZE-1, TABLE_ACLK_ALERT, uuid_str);
494
- rc = db_execute(sql);
494
+ rc = db_execute(db_meta, sql);
495
if (unlikely(rc))
496
error_report("Failed to create ACLK alert table for host %s", host ? rrdhost_hostname(host) : host_guid);
497
else {
498
snprintfz(sql, ACLK_SYNC_QUERY_SIZE -1, INDEX_ACLK_ALERT, uuid_str, uuid_str);
499
- rc = db_execute(sql);
499
+ rc = db_execute(db_meta, sql);
500
if (unlikely(rc))
501
error_report("Failed to create ACLK alert table index for host %s", host ? string2str(host->hostname) : host_guid);
502
}
database/sqlite/sqlite_aclk_alert.c
+5
-5
@@ -299,13 +299,13 @@ void aclk_push_alert_event(struct aclk_sync_host_config *wc)
299
300
BUFFER *sql_fix = buffer_create(1024, &netdata_buffers_statistics.buffers_sqlite);
301
buffer_sprintf(sql_fix, TABLE_ACLK_ALERT, wc->uuid_str);
302
- rc = db_execute(buffer_tostring(sql_fix));
302
+ rc = db_execute(db_meta, buffer_tostring(sql_fix));
303
if (unlikely(rc))
304
error_report("Failed to create ACLK alert table for host %s", rrdhost_hostname(wc->host));
305
else {
306
buffer_flush(sql_fix);
307
buffer_sprintf(sql_fix, INDEX_ACLK_ALERT, wc->uuid_str, wc->uuid_str);
308
- if (unlikely(db_execute(buffer_tostring(sql_fix))))
308
+ if (unlikely(db_execute(db_meta, buffer_tostring(sql_fix))))
309
error_report("Failed to create ACLK alert table for host %s", rrdhost_hostname(wc->host));
310
}
311
buffer_free(sql_fix);
@@ -416,7 +416,7 @@ void aclk_push_alert_event(struct aclk_sync_host_config *wc)
416
"WHERE date_submitted IS NULL AND sequence_id BETWEEN %" PRIu64 " AND %" PRIu64 ";",
417
wc->uuid_str, first_sequence_id, last_sequence_id);
418
419
- if (unlikely(db_execute(buffer_tostring(sql))))
419
+ if (unlikely(db_execute(db_meta, buffer_tostring(sql))))
420
error_report("Failed to mark ACLK alert entries as submitted for host %s", rrdhost_hostname(wc->host));
421
422
// Mark to do one more check
@@ -475,7 +475,7 @@ void sql_queue_existing_alerts_to_aclk(RRDHOST *host)
475
476
netdata_rwlock_rdlock(&host->health_log.alarm_log_rwlock);
477
478
- if (unlikely(db_execute(buffer_tostring(sql))))
478
+ if (unlikely(db_execute(db_meta, buffer_tostring(sql))))
479
error_report("Failed to queue existing ACLK alert events for host %s", rrdhost_hostname(host));
480
481
netdata_rwlock_unlock(&host->health_log.alarm_log_rwlock);
@@ -685,7 +685,7 @@ void sql_process_queue_removed_alerts_to_aclk(char *node_id)
685
686
snprintfz(sql,ACLK_SYNC_QUERY_SIZE * 2 - 1, SQL_QUEUE_REMOVE_ALERTS, wc->uuid_str, wc->uuid_str, wc->uuid_str);
687
688
- if (unlikely(db_execute(sql))) {
688
+ if (unlikely(db_execute(db_meta, sql))) {
689
log_access("ACLK STA [%s (%s)]: QUEUED REMOVED ALERTS FAILED", wc->node_id, rrdhost_hostname(wc->host));
690
error_report("Failed to queue ACLK alert removed entries for host %s", rrdhost_hostname(wc->host));
691
}
database/sqlite/sqlite_functions.c
+2
-2
@@ -510,13 +510,13 @@ skip:
510
}
511
// Return 0 OK
512
// Return 1 Failed
513
-int db_execute(const char *cmd)
513
+int db_execute(sqlite3 *db, const char *cmd)
514
{
515
int rc;
516
int cnt = 0;
517
while (cnt < SQL_MAX_RETRY) {
518
char *err_msg;
519
- rc = sqlite3_exec_monitored(db_meta, cmd, 0, 0, &err_msg);
519
+ rc = sqlite3_exec_monitored(db, cmd, 0, 0, &err_msg);
520
if (rc != SQLITE_OK) {
521
error_report("Failed to execute '%s', rc = %d (%s) -- attempt %d", cmd, rc, err_msg, cnt);
522
sqlite3_free(err_msg);
database/sqlite/sqlite_functions.h
+1
-1
@@ -55,7 +55,7 @@ int bind_text_null(sqlite3_stmt *res, int position, const char *text, bool can_b
55
int prepare_statement(sqlite3 *database, const char *query, sqlite3_stmt **statement);
56
int execute_insert(sqlite3_stmt *res);
57
int exec_statement_with_uuid(const char *sql, uuid_t *uuid);
58
-int db_execute(const char *cmd);
58
+int db_execute(sqlite3 *database, const char *cmd);
59
void initialize_thread_key_pool(void);
60
61
// Look up functions
database/sqlite/sqlite_health.c
+2
-2
@@ -25,12 +25,12 @@ int sql_create_health_log_table(RRDHOST *host) {
25
26
snprintfz(command, MAX_HEALTH_SQL_SIZE, SQL_CREATE_HEALTH_LOG_TABLE(uuid_str));
27
28
- rc = db_execute(command);
28
+ rc = db_execute(db_meta, command);
29
if (unlikely(rc))
30
error_report("HEALTH [%s]: SQLite error during creation of health log table", rrdhost_hostname(host));
31
else {
32
snprintfz(command, MAX_HEALTH_SQL_SIZE, "CREATE INDEX IF NOT EXISTS health_log_index_%s ON health_log_%s (unique_id); ", uuid_str, uuid_str);
33
- rc = db_execute(command);
33
+ rc = db_execute(db_meta, command);
34
if (unlikely(unlikely(rc)))
35
error_report("HEALTH [%s]: SQLite error during creation of health log table index", rrdhost_hostname(host));
36
}
database/sqlite/sqlite_metadata.c
+22
-8
@@ -68,6 +68,7 @@ enum metadata_opcode {
68
METADATA_MAINTENANCE,
69
METADATA_SYNC_SHUTDOWN,
70
METADATA_UNITTEST,
71
+ METADATA_ML_LOAD_MODELS,
72
// leave this last
73
// we need it to check for worker utilization
74
METADATA_MAX_ENUMERATIONS_DEFINED
@@ -184,7 +185,7 @@ static int check_and_update_chart_labels(RRDSET *st, BUFFER *work_buffer, size_t
185
struct query_build tmp = {.sql = work_buffer, .count = 0};
186
uuid_unparse_lower(st->chart_uuid, tmp.uuid_str);
187
rrdlabels_walkthrough_read(st->rrdlabels, chart_label_store_to_sql_callback, &tmp);
187
- int rc = db_execute(buffer_tostring(work_buffer));
188
+ int rc = db_execute(db_meta, buffer_tostring(work_buffer));
189
if (likely(!rc)) {
190
st->rrdlabels_last_saved_version = new_version;
191
(*query_counter)++;
@@ -203,7 +204,7 @@ void migrate_localhost(uuid_t *host_uuid)
204
if (!rc)
205
rc = exec_statement_with_uuid(DELETE_NON_EXISTING_LOCALHOST, host_uuid);
206
if (!rc) {
206
- if (unlikely(db_execute(DELETE_MISSING_NODE_INSTANCES)))
207
+ if (unlikely(db_execute(db_meta, DELETE_MISSING_NODE_INSTANCES)))
208
error_report("Failed to remove deleted hosts from node instances");
209
}
210
}
@@ -1012,7 +1013,7 @@ static bool metadata_scan_host(RRDHOST *host, uint32_t max_count, bool use_trans
1013
uint32_t scan_count = 1;
1014
1015
if (use_transaction)
1015
- (void)db_execute("BEGIN TRANSACTION;");
1016
+ (void)db_execute(db_meta, "BEGIN TRANSACTION;");
1017
1018
rrdset_foreach_reentrant(st, host) {
1019
if (scan_count == max_count) {
@@ -1061,7 +1062,7 @@ static bool metadata_scan_host(RRDHOST *host, uint32_t max_count, bool use_trans
1062
rrdset_foreach_done(st);
1063
1064
if (use_transaction)
1064
- (void)db_execute("COMMIT TRANSACTION;");
1065
+ (void)db_execute(db_meta, "COMMIT TRANSACTION;");
1066
1067
return more_to_do;
1068
}
@@ -1074,7 +1075,7 @@ static void store_host_and_system_info(RRDHOST *host, BUFFER *work_buffer, size_
1075
work_buffer = buffer_create(1024, &netdata_buffers_statistics.buffers_sqlite);
1076
1077
if (build_host_system_info_statements(host, work_buffer)) {
1077
- int rc = db_execute(buffer_tostring(work_buffer));
1078
+ int rc = db_execute(db_meta, buffer_tostring(work_buffer));
1079
if (unlikely(rc)) {
1080
error_report("METADATA: 'host:%s': Failed to store host updated information in the database", rrdhost_hostname(host));
1081
rrdhost_flag_set(host, RRDHOST_FLAG_METADATA_INFO | RRDHOST_FLAG_METADATA_UPDATE);
@@ -1118,7 +1119,7 @@ static void start_metadata_hosts(uv_work_t *req __maybe_unused)
1119
worker_is_busy(UV_EVENT_METADATA_STORE);
1120
1121
if (!data->max_count)
1121
- transaction_started = !db_execute("BEGIN TRANSACTION;");
1122
+ transaction_started = !db_execute(db_meta, "BEGIN TRANSACTION;");
1123
1124
dfe_start_reentrant(rrdhost_root_index, host) {
1125
if (rrdhost_flag_check(host, RRDHOST_FLAG_ARCHIVED) || !rrdhost_flag_check(host, RRDHOST_FLAG_METADATA_UPDATE))
@@ -1139,7 +1140,7 @@ static void start_metadata_hosts(uv_work_t *req __maybe_unused)
1140
struct query_build tmp = {.sql = work_buffer, .count = 0};
1141
uuid_unparse_lower(host->host_uuid, tmp.uuid_str);
1142
rrdlabels_walkthrough_read(host->rrdlabels, host_label_store_to_sql_callback, &tmp);
1142
- rc = db_execute(buffer_tostring(work_buffer));
1143
+ rc = db_execute(db_meta, buffer_tostring(work_buffer));
1144
1145
if (unlikely(rc)) {
1146
error_report("METADATA: 'host:%s': failed to update metadata host labels", rrdhost_hostname(host));
@@ -1187,7 +1188,7 @@ static void start_metadata_hosts(uv_work_t *req __maybe_unused)
1188
dfe_done(host);
1189
1190
if (!data->max_count && transaction_started)
1190
- transaction_started = db_execute("COMMIT TRANSACTION;");
1191
+ transaction_started = db_execute(db_meta, "COMMIT TRANSACTION;");
1192
1193
usec_t all_ended_ut = now_monotonic_usec(); (void)all_ended_ut;
1194
internal_error(true, "METADATA: checking all hosts completed in %0.2f ms",
@@ -1209,6 +1210,7 @@ static void metadata_event_loop(void *arg)
1210
worker_register_job_name(METADATA_STORE_CLAIM_ID, "add claim id");
1211
worker_register_job_name(METADATA_ADD_HOST_INFO, "add host info");
1212
worker_register_job_name(METADATA_MAINTENANCE, "maintenance");
1213
+ worker_register_job_name(METADATA_ML_LOAD_MODELS, "ml load models");
1214
1215
int ret;
1216
uv_loop_t *loop;
@@ -1289,6 +1291,11 @@ static void metadata_event_loop(void *arg)
1291
case METADATA_DATABASE_TIMER:
1292
break;
1293
1294
+ case METADATA_ML_LOAD_MODELS: {
1295
+ RRDDIM *rd = (RRDDIM *) cmd.param[0];
1296
+ ml_dimension_load_models(rd);
1297
+ break;
1298
+ }
1299
case METADATA_DEL_DIMENSION:
1300
uuid = (uuid_t *) cmd.param[0];
1301
if (likely(dimension_can_be_deleted(uuid)))
@@ -1514,6 +1521,13 @@ void metaqueue_host_update_info(RRDHOST *host)
1521
queue_metadata_cmd(METADATA_ADD_HOST_INFO, host, NULL);
1522
}
1523
1524
+void metaqueue_ml_load_models(RRDDIM *rd)
1525
+{
1526
+ if (unlikely(!metasync_worker.loop))
1527
+ return;
1528
+ queue_metadata_cmd(METADATA_ML_LOAD_MODELS, rd, NULL);
1529
+}
1530
+
1531
void metadata_queue_load_host_context(RRDHOST *host)
1532
{
1533
if (unlikely(!metasync_worker.loop))
database/sqlite/sqlite_metadata.h
+1
@@ -14,6 +14,7 @@ void metadata_sync_shutdown_prepare(void);
14
void metaqueue_delete_dimension_uuid(uuid_t *uuid);
15
void metaqueue_store_claim_id(uuid_t *host_uuid, uuid_t *claim_uuid);
16
void metaqueue_host_update_info(RRDHOST *host);
17
+void metaqueue_ml_load_models(RRDDIM *rd);
18
void migrate_localhost(uuid_t *host_uuid);
19
void metadata_queue_load_host_context(RRDHOST *host);
20
ml/Config.cc
+4
-1
@@ -44,8 +44,9 @@ void ml_config_load(ml_config_t *cfg) {
44
time_t anomaly_detection_query_duration = config_get_number(config_section_ml, "anomaly detection grouping duration", 5 * 60);
45
46
size_t num_training_threads = config_get_number(config_section_ml, "num training threads", 4);
47
+ size_t flush_models_batch_size = config_get_number(config_section_ml, "flush models batch size", 128);
48
48
- bool enable_statistics_charts = config_get_boolean(config_section_ml, "enable statistics charts", false);
49
+ bool enable_statistics_charts = config_get_boolean(config_section_ml, "enable statistics charts", true);
50
51
/*
52
* Clamp
@@ -69,6 +70,7 @@ void ml_config_load(ml_config_t *cfg) {
70
anomaly_detection_query_duration = clamp<time_t>(anomaly_detection_query_duration, 60, 15 * 60);
71
72
num_training_threads = clamp<size_t>(num_training_threads, 1, 128);
73
+ flush_models_batch_size = clamp<size_t>(flush_models_batch_size, 8, 512);
74
75
/*
76
* Validate
@@ -117,6 +119,7 @@ void ml_config_load(ml_config_t *cfg) {
119
cfg->stream_anomaly_detection_charts = config_get_boolean(config_section_ml, "stream anomaly detection charts", true);
120
121
cfg->num_training_threads = num_training_threads;
122
+ cfg->flush_models_batch_size = flush_models_batch_size;
123
124
cfg->enable_statistics_charts = enable_statistics_charts;
125
}
ml/ml-dummy.c
+5
@@ -92,6 +92,11 @@ bool ml_dimension_is_anomalous(RRDDIM *rd, time_t curr_time, double value, bool
92
return false;
93
}
94
95
+int ml_dimension_load_models(RRDDIM *rd) {
96
+ UNUSED(rd);
97
+ return 0;
98
+}
99
+
100
void ml_update_global_statistics_charts(uint64_t models_consulted) {
101
UNUSED(models_consulted);
102
}
ml/ml-private.h
+8
@@ -246,6 +246,11 @@ typedef struct {
246
RRDDIM *detector_events_new_anomaly_event_rd;
247
} ml_host_t;
248
249
+typedef struct {
250
+ uuid_t metric_uuid;
251
+ ml_kmeans_t kmeans;
252
+} ml_model_info_t;
253
+
254
typedef struct {
255
size_t id;
256
netdata_thread_t nd_thread;
@@ -258,6 +263,8 @@ typedef struct {
263
calculated_number_t *scratch_training_cns;
264
std::vector<DSample> training_samples;
265
266
+ std::vector<ml_model_info_t> pending_model_info;
267
+
268
RRDSET *queue_stats_rs;
269
RRDDIM *queue_stats_queue_size_rd;
270
RRDDIM *queue_stats_popped_items_rd;
@@ -313,6 +320,7 @@ typedef struct {
320
std::atomic<bool> detection_stop;
321
322
size_t num_training_threads;
323
+ size_t flush_models_batch_size;
324
325
std::vector<ml_training_thread_t> training_threads;
326
std::atomic<bool> training_stop;
ml/ml.cc
+78
-60
@@ -16,9 +16,10 @@
16
#define WORKER_TRAIN_UPDATE_MODELS 4
17
#define WORKER_TRAIN_RELEASE_DIMENSION 5
18
#define WORKER_TRAIN_UPDATE_HOST 6
19
-#define WORKER_TRAIN_LOAD_MODELS 7
19
+#define WORKER_TRAIN_FLUSH_MODELS 7
20
21
static sqlite3 *db = NULL;
22
+static netdata_mutex_t db_mutex = NETDATA_MUTEX_INITIALIZER;
23
24
/*
25
* Functions to convert enums to strings
@@ -406,7 +407,7 @@ ml_dimension_calculated_numbers(ml_training_thread_t *training_thread, ml_dimens
407
408
const char *db_models_create_table =
409
"CREATE TABLE IF NOT EXISTS models("
409
- " dim_id BLOB, dim_str TEXT, after INT, before INT,"
410
+ " dim_id BLOB, after INT, before INT,"
411
" min_dist REAL, max_dist REAL,"
412
" c00 REAL, c01 REAL, c02 REAL, c03 REAL, c04 REAL, c05 REAL,"
413
" c10 REAL, c11 REAL, c12 REAL, c13 REAL, c14 REAL, c15 REAL,"
@@ -415,26 +416,26 @@ const char *db_models_create_table =
416
417
const char *db_models_add_model =
418
"INSERT OR REPLACE INTO models("
418
- " dim_id, dim_str, after, before,"
419
+ " dim_id, after, before,"
420
" min_dist, max_dist,"
421
" c00, c01, c02, c03, c04, c05,"
422
" c10, c11, c12, c13, c14, c15)"
423
"VALUES("
423
- " @dim_id, @dim_str, @after, @before,"
424
+ " @dim_id, @after, @before,"
425
" @min_dist, @max_dist,"
426
" @c00, @c01, @c02, @c03, @c04, @c05,"
427
" @c10, @c11, @c12, @c13, @c14, @c15);";
428
429
const char *db_models_load =
430
"SELECT * FROM models "
430
- "WHERE dim_id == @dim_id AND after >= @after ORDER BY before ASC;";
431
+ "WHERE dim_id = @dim_id AND after >= @after ORDER BY before ASC;";
432
433
const char *db_models_delete =
434
"DELETE FROM models "
435
"WHERE dim_id = @dim_id AND before < @before;";
436
437
static int
437
-ml_dimension_add_model(ml_dimension_t *dim)
438
+ml_dimension_add_model(const uuid_t *metric_uuid, const ml_kmeans_t *km)
439
{
440
static __thread sqlite3_stmt *res = NULL;
441
int param = 0;
@@ -453,36 +454,30 @@ ml_dimension_add_model(ml_dimension_t *dim)
454
}
455
}
456
456
- rc = sqlite3_bind_blob(res, ++param, &dim->rd->metric_uuid, sizeof(dim->rd->metric_uuid), SQLITE_STATIC);
457
- if (unlikely(rc != SQLITE_OK))
458
- goto bind_fail;
459
-
460
- char id[1024];
461
- snprintfz(id, 1024 - 1, "%s.%s", rrdset_id(dim->rd->rrdset), rrddim_id(dim->rd));
462
- rc = sqlite3_bind_text(res, ++param, id, -1, SQLITE_STATIC);
457
+ rc = sqlite3_bind_blob(res, ++param, metric_uuid, sizeof(*metric_uuid), SQLITE_STATIC);
458
if (unlikely(rc != SQLITE_OK))
459
goto bind_fail;
460
466
- rc = sqlite3_bind_int(res, ++param, (int) dim->kmeans.after);
461
+ rc = sqlite3_bind_int(res, ++param, (int) km->after);
462
if (unlikely(rc != SQLITE_OK))
463
goto bind_fail;
464
470
- rc = sqlite3_bind_int(res, ++param, (int) dim->kmeans.before);
465
+ rc = sqlite3_bind_int(res, ++param, (int) km->before);
466
if (unlikely(rc != SQLITE_OK))
467
goto bind_fail;
468
474
- rc = sqlite3_bind_double(res, ++param, dim->kmeans.min_dist);
469
+ rc = sqlite3_bind_double(res, ++param, km->min_dist);
470
if (unlikely(rc != SQLITE_OK))
471
goto bind_fail;
472
478
- rc = sqlite3_bind_double(res, ++param, dim->kmeans.max_dist);
473
+ rc = sqlite3_bind_double(res, ++param, km->max_dist);
474
if (unlikely(rc != SQLITE_OK))
475
goto bind_fail;
476
482
- if (dim->kmeans.cluster_centers.size() != 2)
483
- fatal("Expected 2 cluster centers, got %zu", dim->kmeans.cluster_centers.size());
477
+ if (km->cluster_centers.size() != 2)
478
+ fatal("Expected 2 cluster centers, got %zu", km->cluster_centers.size());
479
485
- for (const DSample &ds : dim->kmeans.cluster_centers) {
480
+ for (const DSample &ds : km->cluster_centers) {
481
if (ds.size() != 6)
482
fatal("Expected dsample with 6 dimensions, got %ld", ds.size());
483
@@ -513,7 +508,7 @@ bind_fail:
508
}
509
510
static int
516
-ml_dimension_delete_models(ml_dimension_t *dim)
511
+ml_dimension_delete_models(const uuid_t *metric_uuid, time_t before)
512
{
513
static __thread sqlite3_stmt *res = NULL;
514
int rc = 0;
@@ -532,11 +527,11 @@ ml_dimension_delete_models(ml_dimension_t *dim)
527
}
528
}
529
535
- rc = sqlite3_bind_blob(res, ++param, &dim->rd->metric_uuid, sizeof(dim->rd->metric_uuid), SQLITE_STATIC);
530
+ rc = sqlite3_bind_blob(res, ++param, metric_uuid, sizeof(*metric_uuid), SQLITE_STATIC);
531
if (unlikely(rc != SQLITE_OK))
532
goto bind_fail;
533
539
- rc = sqlite3_bind_int(res, ++param, (int) dim->kmeans.before - (Cfg.num_models_to_use * Cfg.train_every));
534
+ rc = sqlite3_bind_int(res, ++param, (int) before);
535
if (unlikely(rc != SQLITE_OK))
536
goto bind_fail;
537
@@ -558,8 +553,18 @@ bind_fail:
553
return 1;
554
}
555
561
-static int
562
-ml_dimension_load_models(ml_dimension_t *dim) {
556
+int ml_dimension_load_models(RRDDIM *rd) {
557
+ ml_dimension_t *dim = (ml_dimension_t *) rd->ml_dimension;
558
+ if (!dim)
559
+ return 0;
560
+
561
+ netdata_mutex_lock(&dim->mutex);
562
+ bool is_empty = dim->km_contexts.empty();
563
+ netdata_mutex_unlock(&dim->mutex);
564
+
565
+ if (!is_empty)
566
+ return 0;
567
+
568
std::vector<ml_kmeans_t> V;
569
570
static __thread sqlite3_stmt *res = NULL;
@@ -587,6 +592,8 @@ ml_dimension_load_models(ml_dimension_t *dim) {
592
if (unlikely(rc != SQLITE_OK))
593
goto bind_fail;
594
595
+ netdata_mutex_lock(&dim->mutex);
596
+
597
dim->km_contexts.reserve(Cfg.num_models_to_use);
598
while ((rc = sqlite3_step_monitored(res)) == SQLITE_ROW) {
599
ml_kmeans_t km;
@@ -618,6 +625,12 @@ ml_dimension_load_models(ml_dimension_t *dim) {
625
dim->km_contexts.push_back(km);
626
}
627
628
+ if (!dim->km_contexts.empty()) {
629
+ dim->ts = TRAINING_STATUS_TRAINED;
630
+ }
631
+
632
+ netdata_mutex_unlock(&dim->mutex);
633
+
634
if (unlikely(rc != SQLITE_DONE))
635
error_report("Failed to load models, rc = %d", rc);
636
@@ -635,24 +648,6 @@ bind_fail:
648
return 1;
649
}
650
638
-static int
639
-ml_dimension_update_models(ml_dimension_t *dim)
640
-{
641
- int rc;
642
-
643
- if (dim->km_contexts.empty()) {
644
- rc = ml_dimension_load_models(dim);
645
- if (rc)
646
- return rc;
647
- }
648
-
649
- rc = ml_dimension_add_model(dim);
650
- if (rc)
651
- return rc;
652
-
653
- return ml_dimension_delete_models(dim);
654
-}
655
-
651
static enum ml_training_result
652
ml_dimension_train_model(ml_training_thread_t *training_thread, ml_dimension_t *dim, const ml_training_request_t &training_request)
653
{
@@ -704,22 +699,10 @@ ml_dimension_train_model(ml_training_thread_t *training_thread, ml_dimension_t *
699
}
700
701
// update models
702
+ worker_is_busy(WORKER_TRAIN_UPDATE_MODELS);
703
{
704
netdata_mutex_lock(&dim->mutex);
705
710
- // temporarily disable sqlite operations because they interfere with
711
- // training scheduling on busy parents
712
- #if 0
713
- worker_is_busy(WORKER_TRAIN_LOAD_MODELS);
714
-
715
- int rc = ml_dimension_update_models(dim);
716
- if (rc) {
717
- error("Failed to update models for %s [%u, %u]", rrddim_id(dim->rd), dim->kmeans.after, dim->kmeans.before);
718
- }
719
- #endif
720
-
721
- worker_is_busy(WORKER_TRAIN_UPDATE_MODELS);
722
-
706
if (dim->km_contexts.size() < Cfg.num_models_to_use) {
707
dim->km_contexts.push_back(std::move(dim->kmeans));
708
} else {
@@ -747,6 +730,12 @@ ml_dimension_train_model(ml_training_thread_t *training_thread, ml_dimension_t *
730
dim->tr = training_response;
731
dim->last_training_time = rrddim_last_entry_s(dim->rd);
732
733
+ // Add the newly generated model to the list of pending models to flush
734
+ ml_model_info_t model_info;
735
+ uuid_copy(model_info.metric_uuid, dim->rd->metric_uuid);
736
+ model_info.kmeans = dim->km_contexts.back();
737
+ training_thread->pending_model_info.push_back(model_info);
738
+
739
netdata_mutex_unlock(&dim->mutex);
740
}
741
@@ -1133,10 +1122,9 @@ ml_detect_main(void *arg)
1122
training_stats.consumed_ut /= training_stats.num_popped_items;
1123
training_stats.remaining_ut /= training_stats.num_popped_items;
1124
} else {
1136
- training_stats.queue_size = 0;
1137
- training_stats.allotted_ut = 0;
1125
+ training_stats.queue_size = ml_queue_size(training_thread->training_queue);
1126
training_stats.consumed_ut = 0;
1139
- training_stats.remaining_ut = 0;
1127
+ training_stats.remaining_ut = training_stats.allotted_ut;
1128
1129
training_stats.training_result_ok = 0;
1130
training_stats.training_result_invalid_query_time_range = 0;
@@ -1352,6 +1340,8 @@ void ml_dimension_new(RRDDIM *rd)
1340
dim->km_contexts.reserve(Cfg.num_models_to_use);
1341
1342
rd->ml_dimension = (rrd_ml_dimension_t *) dim;
1343
+
1344
+ metaqueue_ml_load_models(rd);
1345
}
1346
1347
void ml_dimension_delete(RRDDIM *rd)
@@ -1380,6 +1370,25 @@ bool ml_dimension_is_anomalous(RRDDIM *rd, time_t curr_time, double value, bool
1370
return is_anomalous;
1371
}
1372
1373
+static int ml_flush_pending_models(ml_training_thread_t *training_thread) {
1374
+ (void) db_execute(db, "BEGIN TRANSACTION;");
1375
+
1376
+ for (const auto &pending_model: training_thread->pending_model_info) {
1377
+ int rc = ml_dimension_add_model(&pending_model.metric_uuid, &pending_model.kmeans);
1378
+ if (rc)
1379
+ return rc;
1380
+
1381
+ rc = ml_dimension_delete_models(&pending_model.metric_uuid, pending_model.kmeans.before - (Cfg.num_models_to_use * Cfg.train_every));
1382
+ if (rc)
1383
+ return rc;
1384
+ }
1385
+
1386
+ (void) db_execute(db, "COMMIT TRANSACTION;");
1387
+
1388
+ training_thread->pending_model_info.clear();
1389
+ return 0;
1390
+}
1391
+
1392
static void *ml_train_main(void *arg) {
1393
ml_training_thread_t *training_thread = (ml_training_thread_t *) arg;
1394
@@ -1392,9 +1401,9 @@ static void *ml_train_main(void *arg) {
1401
worker_register_job_name(WORKER_TRAIN_QUERY, "query");
1402
worker_register_job_name(WORKER_TRAIN_KMEANS, "kmeans");
1403
worker_register_job_name(WORKER_TRAIN_UPDATE_MODELS, "update models");
1395
- worker_register_job_name(WORKER_TRAIN_LOAD_MODELS, "load models");
1404
worker_register_job_name(WORKER_TRAIN_RELEASE_DIMENSION, "release");
1405
worker_register_job_name(WORKER_TRAIN_UPDATE_HOST, "update host");
1406
+ worker_register_job_name(WORKER_TRAIN_FLUSH_MODELS, "flush models");
1407
1408
while (!Cfg.training_stop) {
1409
worker_is_busy(WORKER_TRAIN_QUEUE_POP);
@@ -1470,6 +1479,14 @@ static void *ml_train_main(void *arg) {
1479
netdata_mutex_unlock(&training_thread->nd_mutex);
1480
}
1481
1482
+ if (training_thread->pending_model_info.size() >= Cfg.flush_models_batch_size) {
1483
+ worker_is_busy(WORKER_TRAIN_FLUSH_MODELS);
1484
+ netdata_mutex_lock(&db_mutex);
1485
+ ml_flush_pending_models(training_thread);
1486
+ netdata_mutex_unlock(&db_mutex);
1487
+ continue;
1488
+ }
1489
+
1490
worker_is_idle();
1491
std::this_thread::sleep_for(std::chrono::microseconds{remaining_ut});
1492
}
@@ -1505,6 +1522,7 @@ void ml_init()
1522
1523
training_thread->id = idx;
1524
training_thread->training_queue = ml_queue_init();
1525
+ training_thread->pending_model_info.reserve(Cfg.flush_models_batch_size);
1526
netdata_mutex_init(&training_thread->nd_mutex);
1527
}
1528
ml/ml.h
+2
@@ -36,6 +36,8 @@ void ml_dimension_new(RRDDIM *rd);
36
void ml_dimension_delete(RRDDIM *rd);
37
bool ml_dimension_is_anomalous(RRDDIM *rd, time_t curr_time, double value, bool exists);
38
39
+int ml_dimension_load_models(RRDDIM *rd);
40
+
41
void ml_update_global_statistics_charts(uint64_t models_consulted);
42
43
#ifdef __cplusplus