1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+#include "sqlite_metadata.h"
4
+
5
+extern DICTIONARY *rrdhost_root_index;
6
+
7
+// SQL statements
8
+
9
+#define SQL_STORE_CLAIM_ID "insert into node_instance " \
10
+ "(host_id, claim_id, date_created) values (@host_id, @claim_id, unixepoch()) " \
11
+ "on conflict(host_id) do update set claim_id = excluded.claim_id;"
12
+
13
+#define SQL_DELETE_HOST_LABELS "DELETE FROM host_label WHERE host_id = @uuid;"
14
+
15
+#define STORE_HOST_LABEL \
16
+ "INSERT OR REPLACE INTO host_label (host_id, source_type, label_key, label_value, date_created) VALUES "
17
+
18
+#define STORE_CHART_LABEL \
19
+ "INSERT OR REPLACE INTO chart_label (chart_id, source_type, label_key, label_value, date_created) VALUES "
20
+
21
+#define STORE_HOST_OR_CHART_LABEL_VALUE "(u2h('%s'), %d,'%s','%s', unixepoch())"
22
+
23
+#define DELETE_DIMENSION_UUID "DELETE FROM dimension WHERE dim_id = @uuid;"
24
+
25
+#define SQL_STORE_HOST_INFO "INSERT OR REPLACE INTO host " \
26
+ "(host_id, hostname, registry_hostname, update_every, os, timezone," \
27
+ "tags, hops, memory_mode, abbrev_timezone, utc_offset, program_name, program_version," \
28
+ "entries, health_enabled) " \
29
+ "values (@host_id, @hostname, @registry_hostname, @update_every, @os, @timezone, @tags, @hops, @memory_mode, " \
30
+ "@abbrev_timezone, @utc_offset, @program_name, @program_version, " \
31
+ "@entries, @health_enabled);"
32
+
33
+#define SQL_STORE_CHART "insert or replace into chart (chart_id, host_id, type, id, " \
34
+ "name, family, context, title, unit, plugin, module, priority, update_every , chart_type , memory_mode , " \
35
+ "history_entries) values (?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14,?15,?16);"
36
+
37
+#define SQL_STORE_DIMENSION "INSERT OR REPLACE INTO dimension (dim_id, chart_id, id, name, multiplier, divisor , algorithm) " \
38
+ "VALUES (@dim_id, @chart_id, @id, @name, @multiplier, @divisor, @algorithm);"
39
+
40
+#define SELECT_DIMENSION_LIST "SELECT dim_id, rowid FROM dimension WHERE rowid > @row_id"
41
+
42
+#define STORE_HOST_INFO "INSERT OR REPLACE INTO host_info (host_id, system_key, system_value, date_created) VALUES "
43
+#define STORE_HOST_INFO_VALUES "(u2h('%s'), '%s','%s', unixepoch())"
44
+
45
+#define MIGRATE_LOCALHOST_TO_NEW_MACHINE_GUID \
46
+ "UPDATE chart SET host_id = @host_id WHERE host_id in (SELECT host_id FROM host where host_id <> @host_id and hops = 0);"
47
+#define DELETE_NON_EXISTING_LOCALHOST "DELETE FROM host WHERE hops = 0 AND host_id <> @host_id;"
48
+#define DELETE_MISSING_NODE_INSTANCES "DELETE FROM node_instance WHERE host_id NOT IN (SELECT host_id FROM host);"
49
+
50
+#define METADATA_CMD_Q_MAX_SIZE (1024) // Max queue size; callers will block until there is room
51
+#define METADATA_MAINTENANCE_FIRST_CHECK (1800) // Maintenance first run after agent startup in seconds
52
+#define METADATA_MAINTENANCE_RETRY (60) // Retry run if already running or last run did actual work
53
+#define METADATA_MAINTENANCE_INTERVAL (3600) // Repeat maintenance after latest successful
54
+
55
+#define METADATA_HOST_CHECK_FIRST_CHECK (5) // First check for pending metadata
56
+#define METADATA_HOST_CHECK_INTERVAL (30) // Repeat check for pending metadata
57
+#define METADATA_HOST_CHECK_IMMEDIATE (5) // Repeat immediate run because we have more metadata to write
58
+
59
+#define MAX_METADATA_CLEANUP (500) // Maximum metadata write operations (e.g deletes before retrying)
60
+#define METADATA_MAX_BATCH_SIZE (512) // Maximum commands to execute before running the event loop
61
+#define METADATA_MAX_TRANSACTION_BATCH (128) // Maximum commands to add in a transaction
62
+
63
+enum metadata_opcode {
64
+ METADATA_DATABASE_NOOP = 0,
65
+ METADATA_DATABASE_TIMER,
66
+ METADATA_ADD_CHART,
67
+ METADATA_ADD_CHART_LABEL,
68
+ METADATA_ADD_DIMENSION,
69
+ METADATA_DEL_DIMENSION,
70
+ METADATA_ADD_DIMENSION_OPTION,
71
+ METADATA_ADD_HOST_SYSTEM_INFO,
72
+ METADATA_ADD_HOST_INFO,
73
+ METADATA_STORE_CLAIM_ID,
74
+ METADATA_STORE_HOST_LABELS,
75
+ METADATA_STORE_BUFFER,
76
+
77
+ METADATA_SKIP_TRANSACTION, // Dummy -- OPCODES less than this one can be in a tranasction
78
+
79
+ METADATA_SCAN_HOSTS,
80
+ METADATA_MAINTENANCE,
81
+ METADATA_SYNC_SHUTDOWN,
82
+ METADATA_UNITTEST,
83
+ // leave this last
84
+ // we need it to check for worker utilization
85
+ METADATA_MAX_ENUMERATIONS_DEFINED
86
+};
87
+
88
+#define MAX_PARAM_LIST (2)
89
+struct metadata_cmd {
90
+ enum metadata_opcode opcode;
91
+ struct completion *completion;
92
+ const void *param[MAX_PARAM_LIST];
93
+};
94
+
95
+struct metadata_database_cmdqueue {
96
+ unsigned head, tail;
97
+ struct metadata_cmd cmd_array[METADATA_CMD_Q_MAX_SIZE];
98
+};
99
+
100
+typedef enum {
101
+ METADATA_FLAG_CLEANUP = (1 << 0), // Cleanup is running
102
+ METADATA_FLAG_SCANNING_HOSTS = (1 << 1), // Scanning of hosts in worker thread
103
+ METADATA_FLAG_SHUTDOWN = (1 << 2), // Shutting down
104
+} METADATA_FLAG;
105
+
106
+#define METADATA_WORKER_BUSY (METADATA_FLAG_CLEANUP | METADATA_FLAG_SCANNING_HOSTS)
107
+
108
+struct metadata_wc {
109
+ uv_thread_t thread;
110
+ time_t check_metadata_after;
111
+ time_t check_hosts_after;
112
+ volatile unsigned queue_size;
113
+ uv_loop_t *loop;
114
+ uv_async_t async;
115
+ METADATA_FLAG flags;
116
+ uint64_t row_id;
117
+ uv_timer_t timer_req;
118
+ struct completion init_complete;
119
+ /* FIFO command queue */
120
+ uv_mutex_t cmd_mutex;
121
+ uv_cond_t cmd_cond;
122
+ struct metadata_database_cmdqueue cmd_queue;
123
+};
124
+
125
+#define metadata_flag_check(target_flags, flag) (__atomic_load_n(&((target_flags)->flags), __ATOMIC_SEQ_CST) & (flag))
126
+#define metadata_flag_set(target_flags, flag) __atomic_or_fetch(&((target_flags)->flags), (flag), __ATOMIC_SEQ_CST)
127
+#define metadata_flag_clear(target_flags, flag) __atomic_and_fetch(&((target_flags)->flags), ~(flag), __ATOMIC_SEQ_CST)
128
+
129
+//
130
+// For unittest
131
+//
132
+struct thread_unittest {
133
+ int join;
134
+ unsigned added;
135
+ unsigned processed;
136
+ unsigned *done;
137
+};
138
+
139
+
140
+// Metadata functions
141
+
142
+struct query_build {
143
+ BUFFER *sql;
144
+ int count;
145
+ char uuid_str[UUID_STR_LEN];
146
+};
147
+
148
+static int host_label_store_to_sql_callback(const char *name, const char *value, RRDLABEL_SRC ls, void *data) {
149
+ struct query_build *lb = data;
150
+ if (unlikely(!lb->count))
151
+ buffer_sprintf(lb->sql, STORE_HOST_LABEL);
152
+ else
153
+ buffer_strcat(lb->sql, ", ");
154
+ buffer_sprintf(lb->sql, STORE_HOST_OR_CHART_LABEL_VALUE, lb->uuid_str, (int)ls & ~(RRDLABEL_FLAG_INTERNAL), name, value);
155
+ lb->count++;
156
+ return 1;
157
+}
158
+
159
+static int chart_label_store_to_sql_callback(const char *name, const char *value, RRDLABEL_SRC ls, void *data) {
160
+ struct query_build *lb = data;
161
+ if (unlikely(!lb->count))
162
+ buffer_sprintf(lb->sql, STORE_CHART_LABEL);
163
+ else
164
+ buffer_strcat(lb->sql, ", ");
165
+ buffer_sprintf(lb->sql, STORE_HOST_OR_CHART_LABEL_VALUE, lb->uuid_str, ls, name, value);
166
+ lb->count++;
167
+ return 1;
168
+}
169
+
170
+static void check_and_update_chart_labels(RRDSET *st, BUFFER *work_buffer)
171
+{
172
+ size_t old_version = st->rrdlabels_last_saved_version;
173
+ size_t new_version = dictionary_version(st->rrdlabels);
174
+
175
+ if(new_version != old_version) {
176
+ buffer_flush(work_buffer);
177
+ struct query_build tmp = {.sql = work_buffer, .count = 0};
178
+ uuid_unparse_lower(st->chart_uuid, tmp.uuid_str);
179
+ rrdlabels_walkthrough_read(st->rrdlabels, chart_label_store_to_sql_callback, &tmp);
180
+ st->rrdlabels_last_saved_version = new_version;
181
+ db_execute(buffer_tostring(work_buffer));
182
+ }
183
+}
184
+
185
+// Migrate all hosts with hops zero to this host_uuid
186
+void migrate_localhost(uuid_t *host_uuid)
187
+{
188
+ int rc;
189
+
190
+ rc = exec_statement_with_uuid(MIGRATE_LOCALHOST_TO_NEW_MACHINE_GUID, host_uuid);
191
+ if (!rc)
192
+ rc = exec_statement_with_uuid(DELETE_NON_EXISTING_LOCALHOST, host_uuid);
193
+ if (!rc)
194
+ db_execute(DELETE_MISSING_NODE_INSTANCES);
195
+
196
+}
197
+
198
+static void store_claim_id(uuid_t *host_id, uuid_t *claim_id)
199
+{
200
+ sqlite3_stmt *res = NULL;
201
+ int rc;
202
+
203
+ if (unlikely(!db_meta)) {
204
+ if (default_rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE)
205
+ error_report("Database has not been initialized");
206
+ return;
207
+ }
208
+
209
+ rc = sqlite3_prepare_v2(db_meta, SQL_STORE_CLAIM_ID, -1, &res, 0);
210
+ if (unlikely(rc != SQLITE_OK)) {
211
+ error_report("Failed to prepare statement store chart labels");
212
+ return;
213
+ }
214
+
215
+ rc = sqlite3_bind_blob(res, 1, host_id, sizeof(*host_id), SQLITE_STATIC);
216
+ if (unlikely(rc != SQLITE_OK)) {
217
+ error_report("Failed to bind host_id parameter to store node instance information");
218
+ goto failed;
219
+ }
220
+
221
+ if (claim_id)
222
+ rc = sqlite3_bind_blob(res, 2, claim_id, sizeof(*claim_id), SQLITE_STATIC);
223
+ else
224
+ rc = sqlite3_bind_null(res, 2);
225
+ if (unlikely(rc != SQLITE_OK)) {
226
+ error_report("Failed to bind claim_id parameter to store node instance information");
227
+ goto failed;
228
+ }
229
+
230
+ rc = execute_insert(res);
231
+ if (unlikely(rc != SQLITE_DONE))
232
+ error_report("Failed to store node instance information, rc = %d", rc);
233
+
234
+failed:
235
+ if (unlikely(sqlite3_finalize(res) != SQLITE_OK))
236
+ error_report("Failed to finalize the prepared statement when storing node instance information");
237
+}
238
+
239
+static void delete_dimension_uuid(uuid_t *dimension_uuid)
240
+{
241
+ static __thread sqlite3_stmt *res = NULL;
242
+ int rc;
243
+
244
+ if (unlikely(!res)) {
245
+ rc = prepare_statement(db_meta, DELETE_DIMENSION_UUID, &res);
246
+ if (rc != SQLITE_OK) {
247
+ error_report("Failed to prepare statement to delete a dimension uuid");
248
+ return;
249
+ }
250
+ }
251
+
252
+ rc = sqlite3_bind_blob(res, 1, dimension_uuid, sizeof(*dimension_uuid), SQLITE_STATIC);
253
+ if (unlikely(rc != SQLITE_OK))
254
+ goto skip_execution;
255
+
256
+ rc = sqlite3_step_monitored(res);
257
+ if (unlikely(rc != SQLITE_DONE))
258
+ error_report("Failed to delete dimension uuid, rc = %d", rc);
259
+
260
+skip_execution:
261
+ rc = sqlite3_reset(res);
262
+ if (unlikely(rc != SQLITE_OK))
263
+ error_report("Failed to reset statement when deleting dimension UUID, rc = %d", rc);
264
+}
265
+
266
+//
267
+// Store host and host system info information in the database
268
+static int sql_store_host_info(RRDHOST *host)
269
+{
270
+ static __thread sqlite3_stmt *res = NULL;
271
+ int rc, param = 0;
272
+
273
+ if (unlikely(!db_meta)) {
274
+ if (default_rrd_memory_mode != RRD_MEMORY_MODE_DBENGINE)
275
+ return 0;
276
+ error_report("Database has not been initialized");
277
+ return 1;
278
+ }
279
+
280
+ if (unlikely((!res))) {
281
+ rc = prepare_statement(db_meta, SQL_STORE_HOST_INFO, &res);
282
+ if (unlikely(rc != SQLITE_OK)) {
283
+ error_report("Failed to prepare statement to store host, rc = %d", rc);
284
+ return 1;
285
+ }
286
+ }
287
+
288
+ rc = sqlite3_bind_blob(res, ++param, &host->host_uuid, sizeof(host->host_uuid), SQLITE_STATIC);
289
+ if (unlikely(rc != SQLITE_OK))
290
+ goto bind_fail;
291
+
292
+ rc = bind_text_null(res, ++param, rrdhost_hostname(host), 0);
293
+ if (unlikely(rc != SQLITE_OK))
294
+ goto bind_fail;
295
+
296
+ rc = bind_text_null(res, ++param, rrdhost_registry_hostname(host), 1);
297
+ if (unlikely(rc != SQLITE_OK))
298
+ goto bind_fail;
299
+
300
+ rc = sqlite3_bind_int(res, ++param, host->rrd_update_every);
301
+ if (unlikely(rc != SQLITE_OK))
302
+ goto bind_fail;
303
+
304
+ rc = bind_text_null(res, ++param, rrdhost_os(host), 1);
305
+ if (unlikely(rc != SQLITE_OK))
306
+ goto bind_fail;
307
+
308
+ rc = bind_text_null(res, ++param, rrdhost_timezone(host), 1);
309
+ if (unlikely(rc != SQLITE_OK))
310
+ goto bind_fail;
311
+
312
+ rc = bind_text_null(res, ++param, rrdhost_tags(host), 1);
313
+ if (unlikely(rc != SQLITE_OK))
314
+ goto bind_fail;
315
+
316
+ rc = sqlite3_bind_int(res, ++param, host->system_info ? host->system_info->hops : 0);
317
+ if (unlikely(rc != SQLITE_OK))
318
+ goto bind_fail;
319
+
320
+ rc = sqlite3_bind_int(res, ++param, host->rrd_memory_mode);
321
+ if (unlikely(rc != SQLITE_OK))
322
+ goto bind_fail;
323
+
324
+ rc = bind_text_null(res, ++param, rrdhost_abbrev_timezone(host), 1);
325
+ if (unlikely(rc != SQLITE_OK))
326
+ goto bind_fail;
327
+
328
+ rc = sqlite3_bind_int(res, ++param, host->utc_offset);
329
+ if (unlikely(rc != SQLITE_OK))
330
+ goto bind_fail;
331
+
332
+ rc = bind_text_null(res, ++param, rrdhost_program_name(host), 1);
333
+ if (unlikely(rc != SQLITE_OK))
334
+ goto bind_fail;
335
+
336
+ rc = bind_text_null(res, ++param, rrdhost_program_version(host), 1);
337
+ if (unlikely(rc != SQLITE_OK))
338
+ goto bind_fail;
339
+
340
+ rc = sqlite3_bind_int64(res, ++param, host->rrd_history_entries);
341
+ if (unlikely(rc != SQLITE_OK))
342
+ goto bind_fail;
343
+
344
+ rc = sqlite3_bind_int(res, ++param, (int ) host->health_enabled);
345
+ if (unlikely(rc != SQLITE_OK))
346
+ goto bind_fail;
347
+
348
+ int store_rc = sqlite3_step_monitored(res);
349
+ if (unlikely(store_rc != SQLITE_DONE))
350
+ error_report("Failed to store host %s, rc = %d", rrdhost_hostname(host), rc);
351
+
352
+ rc = sqlite3_reset(res);
353
+ if (unlikely(rc != SQLITE_OK))
354
+ error_report("Failed to reset statement to store host %s, rc = %d", rrdhost_hostname(host), rc);
355
+
356
+ return !(store_rc == SQLITE_DONE);
357
+bind_fail:
358
+ error_report("Failed to bind %d parameter to store host %s, rc = %d", param, rrdhost_hostname(host), rc);
359
+ rc = sqlite3_reset(res);
360
+ if (unlikely(rc != SQLITE_OK))
361
+ error_report("Failed to reset statement to store host %s, rc = %d", rrdhost_hostname(host), rc);
362
+ return 1;
363
+}
364
+
365
+static void sql_store_host_system_info_key_value(const char *name, const char *value, void *data)
366
+{
367
+ struct query_build *lb = data;
368
+
369
+ if (unlikely(!value))
370
+ return;
371
+
372
+ if (unlikely(!lb->count))
373
+ buffer_sprintf(
374
+ lb->sql, STORE_HOST_INFO);
375
+ else
376
+ buffer_strcat(lb->sql, ", ");
377
+ buffer_sprintf(lb->sql, STORE_HOST_INFO_VALUES, lb->uuid_str, name, value);
378
+ lb->count++;
379
+}
380
+
381
+static BUFFER *sql_store_host_system_info(RRDHOST *host)
382
+{
383
+ struct rrdhost_system_info *system_info = host->system_info;
384
+
385
+ if (unlikely(!system_info))
386
+ return NULL;
387
+
388
+ BUFFER *work_buffer = buffer_create(1024);
389
+
390
+ struct query_build key_data = {.sql = work_buffer, .count = 0};
391
+ uuid_unparse_lower(host->host_uuid, key_data.uuid_str);
392
+
393
+ sql_store_host_system_info_key_value("NETDATA_CONTAINER_OS_NAME", system_info->container_os_name, &key_data);
394
+ sql_store_host_system_info_key_value("NETDATA_CONTAINER_OS_ID", system_info->container_os_id, &key_data);
395
+ sql_store_host_system_info_key_value("NETDATA_CONTAINER_OS_ID_LIKE", system_info->container_os_id_like, &key_data);
396
+ sql_store_host_system_info_key_value("NETDATA_CONTAINER_OS_VERSION", system_info->container_os_version, &key_data);
397
+ sql_store_host_system_info_key_value("NETDATA_CONTAINER_OS_VERSION_ID", system_info->container_os_version_id, &key_data);
398
+ sql_store_host_system_info_key_value("NETDATA_CONTAINER_OS_DETECTION", system_info->host_os_detection, &key_data);
399
+ sql_store_host_system_info_key_value("NETDATA_HOST_OS_NAME", system_info->host_os_name, &key_data);
400
+ sql_store_host_system_info_key_value("NETDATA_HOST_OS_ID", system_info->host_os_id, &key_data);
401
+ sql_store_host_system_info_key_value("NETDATA_HOST_OS_ID_LIKE", system_info->host_os_id_like, &key_data);
402
+ sql_store_host_system_info_key_value("NETDATA_HOST_OS_VERSION", system_info->host_os_version, &key_data);
403
+ sql_store_host_system_info_key_value("NETDATA_HOST_OS_VERSION_ID", system_info->host_os_version_id, &key_data);
404
+ sql_store_host_system_info_key_value("NETDATA_HOST_OS_DETECTION", system_info->host_os_detection, &key_data);
405
+ sql_store_host_system_info_key_value("NETDATA_SYSTEM_KERNEL_NAME", system_info->kernel_name, &key_data);
406
+ sql_store_host_system_info_key_value("NETDATA_SYSTEM_CPU_LOGICAL_CPU_COUNT", system_info->host_cores, &key_data);
407
+ sql_store_host_system_info_key_value("NETDATA_SYSTEM_CPU_FREQ", system_info->host_cpu_freq, &key_data);
408
+ sql_store_host_system_info_key_value("NETDATA_SYSTEM_TOTAL_RAM", system_info->host_ram_total, &key_data);
409
+ sql_store_host_system_info_key_value("NETDATA_SYSTEM_TOTAL_DISK_SIZE", system_info->host_disk_space, &key_data);
410
+ sql_store_host_system_info_key_value("NETDATA_SYSTEM_KERNEL_VERSION", system_info->kernel_version, &key_data);
411
+ sql_store_host_system_info_key_value("NETDATA_SYSTEM_ARCHITECTURE", system_info->architecture, &key_data);
412
+ sql_store_host_system_info_key_value("NETDATA_SYSTEM_VIRTUALIZATION", system_info->virtualization, &key_data);
413
+ sql_store_host_system_info_key_value("NETDATA_SYSTEM_VIRT_DETECTION", system_info->virt_detection, &key_data);
414
+ sql_store_host_system_info_key_value("NETDATA_SYSTEM_CONTAINER", system_info->container, &key_data);
415
+ sql_store_host_system_info_key_value("NETDATA_SYSTEM_CONTAINER_DETECTION", system_info->container_detection, &key_data);
416
+ sql_store_host_system_info_key_value("NETDATA_HOST_IS_K8S_NODE", system_info->is_k8s_node, &key_data);
417
+
418
+ return work_buffer;
419
+}
420
+
421
+
422
+/*
423
+ * Store set option for a dimension
424
+ */
425
+static int sql_set_dimension_option(uuid_t *dim_uuid, char *option)
426
+{
427
+ sqlite3_stmt *res = NULL;
428
+ int rc;
429
+
430
+ if (unlikely(!db_meta)) {
431
+ if (default_rrd_memory_mode != RRD_MEMORY_MODE_DBENGINE)
432
+ return 0;
433
+ error_report("Database has not been initialized");
434
+ return 1;
435
+ }
436
+
437
+ rc = sqlite3_prepare_v2(db_meta, "UPDATE dimension SET options = @options WHERE dim_id = @dim_id", -1, &res, 0);
438
+ if (unlikely(rc != SQLITE_OK)) {
439
+ error_report("Failed to prepare statement to update dimension options");
440
+ return 0;
441
+ };
442
+
443
+ rc = sqlite3_bind_blob(res, 2, dim_uuid, sizeof(*dim_uuid), SQLITE_STATIC);
444
+ if (unlikely(rc != SQLITE_OK))
445
+ goto bind_fail;
446
+
447
+ if (!option || !strcmp(option,"unhide"))
448
+ rc = sqlite3_bind_null(res, 1);
449
+ else
450
+ rc = sqlite3_bind_text(res, 1, option, -1, SQLITE_STATIC);
451
+ if (unlikely(rc != SQLITE_OK))
452
+ goto bind_fail;
453
+
454
+ rc = execute_insert(res);
455
+ if (unlikely(rc != SQLITE_DONE))
456
+ error_report("Failed to update dimension option, rc = %d", rc);
457
+
458
+bind_fail:
459
+ rc = sqlite3_finalize(res);
460
+ if (unlikely(rc != SQLITE_OK))
461
+ error_report("Failed to finalize statement in update dimension options, rc = %d", rc);
462
+ return 0;
463
+}
464
+
465
+/*
466
+ * Store a chart in the database
467
+ */
468
+
469
+static int sql_store_chart(
470
+ uuid_t *chart_uuid, uuid_t *host_uuid, const char *type, const char *id, const char *name, const char *family,
471
+ const char *context, const char *title, const char *units, const char *plugin, const char *module, long priority,
472
+ int update_every, int chart_type, int memory_mode, long history_entries)
473
+{
474
+ static __thread sqlite3_stmt *res = NULL;
475
+ int rc, param = 0;
476
+
477
+ if (unlikely(!db_meta)) {
478
+ if (default_rrd_memory_mode != RRD_MEMORY_MODE_DBENGINE)
479
+ return 0;
480
+ error_report("Database has not been initialized");
481
+ return 1;
482
+ }
483
+
484
+ if (unlikely(!res)) {
485
+ rc = prepare_statement(db_meta, SQL_STORE_CHART, &res);
486
+ if (unlikely(rc != SQLITE_OK)) {
487
+ error_report("Failed to prepare statement to store chart, rc = %d", rc);
488
+ return 1;
489
+ }
490
+ }
491
+
492
+ param++;
493
+ rc = sqlite3_bind_blob(res, 1, chart_uuid, sizeof(*chart_uuid), SQLITE_STATIC);
494
+ if (unlikely(rc != SQLITE_OK))
495
+ goto bind_fail;
496
+
497
+ param++;
498
+ rc = sqlite3_bind_blob(res, 2, host_uuid, sizeof(*host_uuid), SQLITE_STATIC);
499
+ if (unlikely(rc != SQLITE_OK))
500
+ goto bind_fail;
501
+
502
+ param++;
503
+ rc = sqlite3_bind_text(res, 3, type, -1, SQLITE_STATIC);
504
+ if (unlikely(rc != SQLITE_OK))
505
+ goto bind_fail;
506
+
507
+ param++;
508
+ rc = sqlite3_bind_text(res, 4, id, -1, SQLITE_STATIC);
509
+ if (unlikely(rc != SQLITE_OK))
510
+ goto bind_fail;
511
+
512
+ param++;
513
+ if (name && *name)
514
+ rc = sqlite3_bind_text(res, 5, name, -1, SQLITE_STATIC);
515
+ else
516
+ rc = sqlite3_bind_null(res, 5);
517
+ if (unlikely(rc != SQLITE_OK))
518
+ goto bind_fail;
519
+
520
+ param++;
521
+ rc = sqlite3_bind_text(res, 6, family, -1, SQLITE_STATIC);
522
+ if (unlikely(rc != SQLITE_OK))
523
+ goto bind_fail;
524
+
525
+ param++;
526
+ rc = sqlite3_bind_text(res, 7, context, -1, SQLITE_STATIC);
527
+ if (unlikely(rc != SQLITE_OK))
528
+ goto bind_fail;
529
+
530
+ param++;
531
+ rc = sqlite3_bind_text(res, 8, title, -1, SQLITE_STATIC);
532
+ if (unlikely(rc != SQLITE_OK))
533
+ goto bind_fail;
534
+
535
+ param++;
536
+ rc = sqlite3_bind_text(res, 9, units, -1, SQLITE_STATIC);
537
+ if (unlikely(rc != SQLITE_OK))
538
+ goto bind_fail;
539
+
540
+ param++;
541
+ rc = sqlite3_bind_text(res, 10, plugin, -1, SQLITE_STATIC);
542
+ if (unlikely(rc != SQLITE_OK))
543
+ goto bind_fail;
544
+
545
+ param++;
546
+ rc = sqlite3_bind_text(res, 11, module, -1, SQLITE_STATIC);
547
+ if (unlikely(rc != SQLITE_OK))
548
+ goto bind_fail;
549
+
550
+ param++;
551
+ rc = sqlite3_bind_int(res, 12, (int) priority);
552
+ if (unlikely(rc != SQLITE_OK))
553
+ goto bind_fail;
554
+
555
+ param++;
556
+ rc = sqlite3_bind_int(res, 13, update_every);
557
+ if (unlikely(rc != SQLITE_OK))
558
+ goto bind_fail;
559
+
560
+ param++;
561
+ rc = sqlite3_bind_int(res, 14, chart_type);
562
+ if (unlikely(rc != SQLITE_OK))
563
+ goto bind_fail;
564
+
565
+ param++;
566
+ rc = sqlite3_bind_int(res, 15, memory_mode);
567
+ if (unlikely(rc != SQLITE_OK))
568
+ goto bind_fail;
569
+
570
+ param++;
571
+ rc = sqlite3_bind_int(res, 16, (int) history_entries);
572
+ if (unlikely(rc != SQLITE_OK))
573
+ goto bind_fail;
574
+
575
+ rc = execute_insert(res);
576
+ if (unlikely(rc != SQLITE_DONE))
577
+ error_report("Failed to store chart, rc = %d", rc);
578
+
579
+ rc = sqlite3_reset(res);
580
+ if (unlikely(rc != SQLITE_OK))
581
+ error_report("Failed to reset statement in chart store function, rc = %d", rc);
582
+
583
+ return 0;
584
+
585
+bind_fail:
586
+ error_report("Failed to bind parameter %d to store chart, rc = %d", param, rc);
587
+ rc = sqlite3_reset(res);
588
+ if (unlikely(rc != SQLITE_OK))
589
+ error_report("Failed to reset statement in chart store function, rc = %d", rc);
590
+ return 1;
591
+}
592
+
593
+/*
594
+ * Store a dimension
595
+ */
596
+static int sql_store_dimension(
597
+ uuid_t *dim_uuid, uuid_t *chart_uuid, const char *id, const char *name, collected_number multiplier,
598
+ collected_number divisor, int algorithm)
599
+{
600
+ static __thread sqlite3_stmt *res = NULL;
601
+ int rc, param = 0;
602
+
603
+ if (unlikely(!db_meta)) {
604
+ if (default_rrd_memory_mode != RRD_MEMORY_MODE_DBENGINE)
605
+ return 0;
606
+ error_report("Database has not been initialized");
607
+ return 1;
608
+ }
609
+
610
+ if (unlikely(!res)) {
611
+ rc = prepare_statement(db_meta, SQL_STORE_DIMENSION, &res);
612
+ if (unlikely(rc != SQLITE_OK)) {
613
+ error_report("Failed to prepare statement to store dimension, rc = %d", rc);
614
+ return 1;
615
+ }
616
+ }
617
+
618
+ rc = sqlite3_bind_blob(res, ++param, dim_uuid, sizeof(*dim_uuid), SQLITE_STATIC);
619
+ if (unlikely(rc != SQLITE_OK))
620
+ goto bind_fail;
621
+
622
+ rc = sqlite3_bind_blob(res, ++param, chart_uuid, sizeof(*chart_uuid), SQLITE_STATIC);
623
+ if (unlikely(rc != SQLITE_OK))
624
+ goto bind_fail;
625
+
626
+ rc = sqlite3_bind_text(res, ++param, id, -1, SQLITE_STATIC);
627
+ if (unlikely(rc != SQLITE_OK))
628
+ goto bind_fail;
629
+
630
+ rc = sqlite3_bind_text(res, ++param, name, -1, SQLITE_STATIC);
631
+ if (unlikely(rc != SQLITE_OK))
632
+ goto bind_fail;
633
+
634
+ rc = sqlite3_bind_int(res, ++param, (int) multiplier);
635
+ if (unlikely(rc != SQLITE_OK))
636
+ goto bind_fail;
637
+
638
+ rc = sqlite3_bind_int(res, ++param, (int ) divisor);
639
+ if (unlikely(rc != SQLITE_OK))
640
+ goto bind_fail;
641
+
642
+ rc = sqlite3_bind_int(res, ++param, algorithm);
643
+ if (unlikely(rc != SQLITE_OK))
644
+ goto bind_fail;
645
+
646
+ rc = execute_insert(res);
647
+ if (unlikely(rc != SQLITE_DONE))
648
+ error_report("Failed to store dimension, rc = %d", rc);
649
+
650
+ rc = sqlite3_reset(res);
651
+ if (unlikely(rc != SQLITE_OK))
652
+ error_report("Failed to reset statement in store dimension, rc = %d", rc);
653
+ return 0;
654
+
655
+bind_fail:
656
+ error_report("Failed to bind parameter %d to store dimension, rc = %d", param, rc);
657
+ rc = sqlite3_reset(res);
658
+ if (unlikely(rc != SQLITE_OK))
659
+ error_report("Failed to reset statement in store dimension, rc = %d", rc);
660
+ return 1;
661
+}
662
+
663
+static bool dimension_can_be_deleted(uuid_t *dim_uuid)
664
+{
665
+#ifdef ENABLE_DBENGINE
666
+ bool no_retention = true;
667
+ for (int tier = 0; tier < storage_tiers; tier++) {
668
+ if (!multidb_ctx[tier])
669
+ continue;
670
+ time_t first_time_t = 0, last_time_t = 0;
671
+ if (rrdeng_metric_retention_by_uuid((void *) multidb_ctx[tier], dim_uuid, &first_time_t, &last_time_t) == 0) {
672
+ if (first_time_t > 0) {
673
+ no_retention = false;
674
+ break;
675
+ }
676
+ }
677
+ }
678
+ return no_retention;
679
+#else
680
+ return false;
681
+#endif
682
+}
683
+
684
+static void check_dimension_metadata(struct metadata_wc *wc)
685
+{
686
+ int rc;
687
+ sqlite3_stmt *res = NULL;
688
+
689
+ rc = sqlite3_prepare_v2(db_meta, SELECT_DIMENSION_LIST, -1, &res, 0);
690
+ if (unlikely(rc != SQLITE_OK)) {
691
+ error_report("Failed to prepare statement to fetch host dimensions");
692
+ return;
693
+ }
694
+
695
+ rc = sqlite3_bind_int64(res, 1, (sqlite3_int64) wc->row_id);
696
+ if (unlikely(rc != SQLITE_OK)) {
697
+ error_report("Failed to row parameter");
698
+ goto skip_run;
699
+ }
700
+
701
+ uint32_t total_checked = 0;
702
+ uint32_t total_deleted= 0;
703
+ uint64_t last_row_id = wc->row_id;
704
+
705
+ info("METADATA: Checking dimensions starting after row %"PRIu64, wc->row_id);
706
+
707
+ while (sqlite3_step_monitored(res) == SQLITE_ROW && total_deleted < MAX_METADATA_CLEANUP) {
708
+ if (unlikely(metadata_flag_check(wc, METADATA_FLAG_SHUTDOWN)))
709
+ break;
710
+
711
+ last_row_id = sqlite3_column_int64(res, 1);
712
+ rc = dimension_can_be_deleted((uuid_t *)sqlite3_column_blob(res, 0));
713
+ if (rc == true) {
714
+ delete_dimension_uuid((uuid_t *)sqlite3_column_blob(res, 0));
715
+ total_deleted++;
716
+ }
717
+ total_checked++;
718
+ }
719
+ wc->row_id = last_row_id;
720
+ time_t now = now_realtime_sec();
721
+ if (total_deleted > 0) {
722
+ wc->check_metadata_after = now + METADATA_MAINTENANCE_RETRY;
723
+ } else
724
+ wc->row_id = 0;
725
+ info("METADATA: Checked %u, deleted %u -- will resume after row %"PRIu64" in %ld seconds", total_checked, total_deleted, wc->row_id,
726
+ wc->check_metadata_after - now);
727
+
728
+skip_run:
729
+ rc = sqlite3_finalize(res);
730
+ if (unlikely(rc != SQLITE_OK))
731
+ error_report("Failed to finalize the prepared statement when reading dimensions");
732
+}
733
+
734
+
735
+//
736
+// EVENT LOOP STARTS HERE
737
+//
738
+static uv_mutex_t metadata_async_lock;
739
+
740
+static void metadata_init_cmd_queue(struct metadata_wc *wc)
741
+{
742
+ wc->cmd_queue.head = wc->cmd_queue.tail = 0;
743
+ wc->queue_size = 0;
744
+ fatal_assert(0 == uv_cond_init(&wc->cmd_cond));
745
+ fatal_assert(0 == uv_mutex_init(&wc->cmd_mutex));
746
+}
747
+
748
+int metadata_enq_cmd_noblock(struct metadata_wc *wc, struct metadata_cmd *cmd)
749
+{
750
+ unsigned queue_size;
751
+
752
+ /* wait for free space in queue */
753
+ uv_mutex_lock(&wc->cmd_mutex);
754
+
755
+ if (cmd->opcode == METADATA_SYNC_SHUTDOWN) {
756
+ metadata_flag_set(wc, METADATA_FLAG_SHUTDOWN);
757
+ uv_mutex_unlock(&wc->cmd_mutex);
758
+ return 0;
759
+ }
760
+
761
+ if (unlikely((queue_size = wc->queue_size) == METADATA_CMD_Q_MAX_SIZE ||
762
+ metadata_flag_check(wc, METADATA_FLAG_SHUTDOWN))) {
763
+ uv_mutex_unlock(&wc->cmd_mutex);
764
+ return 1;
765
+ }
766
+
767
+ fatal_assert(queue_size < METADATA_CMD_Q_MAX_SIZE);
768
+ /* enqueue command */
769
+ wc->cmd_queue.cmd_array[wc->cmd_queue.tail] = *cmd;
770
+ wc->cmd_queue.tail = wc->cmd_queue.tail != METADATA_CMD_Q_MAX_SIZE - 1 ?
771
+ wc->cmd_queue.tail + 1 : 0;
772
+ wc->queue_size = queue_size + 1;
773
+ uv_mutex_unlock(&wc->cmd_mutex);
774
+ return 0;
775
+}
776
+
777
+static void metadata_enq_cmd(struct metadata_wc *wc, struct metadata_cmd *cmd)
778
+{
779
+ unsigned queue_size;
780
+
781
+ /* wait for free space in queue */
782
+ uv_mutex_lock(&wc->cmd_mutex);
783
+ if (unlikely(metadata_flag_check(wc, METADATA_FLAG_SHUTDOWN))) {
784
+ uv_mutex_unlock(&wc->cmd_mutex);
785
+ (void) uv_async_send(&wc->async);
786
+ return;
787
+ }
788
+
789
+ if (cmd->opcode == METADATA_SYNC_SHUTDOWN) {
790
+ metadata_flag_set(wc, METADATA_FLAG_SHUTDOWN);
791
+ uv_mutex_unlock(&wc->cmd_mutex);
792
+ (void) uv_async_send(&wc->async);
793
+ return;
794
+ }
795
+
796
+ while ((queue_size = wc->queue_size) == METADATA_CMD_Q_MAX_SIZE) {
797
+ if (unlikely(metadata_flag_check(wc, METADATA_FLAG_SHUTDOWN))) {
798
+ uv_mutex_unlock(&wc->cmd_mutex);
799
+ return;
800
+ }
801
+ uv_cond_wait(&wc->cmd_cond, &wc->cmd_mutex);
802
+ }
803
+ fatal_assert(queue_size < METADATA_CMD_Q_MAX_SIZE);
804
+ /* enqueue command */
805
+ wc->cmd_queue.cmd_array[wc->cmd_queue.tail] = *cmd;
806
+ wc->cmd_queue.tail = wc->cmd_queue.tail != METADATA_CMD_Q_MAX_SIZE - 1 ?
807
+ wc->cmd_queue.tail + 1 : 0;
808
+ wc->queue_size = queue_size + 1;
809
+ uv_mutex_unlock(&wc->cmd_mutex);
810
+
811
+ /* wake up event loop */
812
+ (void) uv_async_send(&wc->async);
813
+}
814
+
815
+static struct metadata_cmd metadata_deq_cmd(struct metadata_wc *wc, enum metadata_opcode *next_opcode)
816
+{
817
+ struct metadata_cmd ret;
818
+ unsigned queue_size;
819
+
820
+ uv_mutex_lock(&wc->cmd_mutex);
821
+ queue_size = wc->queue_size;
822
+ if (queue_size == 0) {
823
+ memset(&ret, 0, sizeof(ret));
824
+ ret.opcode = METADATA_DATABASE_NOOP;
825
+ ret.completion = NULL;
826
+ *next_opcode = METADATA_DATABASE_NOOP;
827
+ } else {
828
+ /* dequeue command */
829
+ ret = wc->cmd_queue.cmd_array[wc->cmd_queue.head];
830
+
831
+ if (queue_size == 1) {
832
+ wc->cmd_queue.head = wc->cmd_queue.tail = 0;
833
+ } else {
834
+ wc->cmd_queue.head = wc->cmd_queue.head != METADATA_CMD_Q_MAX_SIZE - 1 ?
835
+ wc->cmd_queue.head + 1 : 0;
836
+ }
837
+ wc->queue_size = queue_size - 1;
838
+ if (wc->queue_size > 0)
839
+ *next_opcode = wc->cmd_queue.cmd_array[wc->cmd_queue.head].opcode;
840
+ else
841
+ *next_opcode = METADATA_DATABASE_NOOP;
842
+ /* wake up producers */
843
+ uv_cond_signal(&wc->cmd_cond);
844
+ }
845
+ uv_mutex_unlock(&wc->cmd_mutex);
846
+
847
+ return ret;
848
+}
849
+
850
+static void async_cb(uv_async_t *handle)
851
+{
852
+ uv_stop(handle->loop);
853
+ uv_update_time(handle->loop);
854
+}
855
+
856
+#define TIMER_INITIAL_PERIOD_MS (1000)
857
+#define TIMER_REPEAT_PERIOD_MS (1000)
858
+
859
+static void timer_cb(uv_timer_t* handle)
860
+{
861
+ uv_stop(handle->loop);
862
+ uv_update_time(handle->loop);
863
+
864
+ struct metadata_wc *wc = handle->data;
865
+ struct metadata_cmd cmd;
866
+ memset(&cmd, 0, sizeof(cmd));
867
+
868
+ time_t now = now_realtime_sec();
869
+
870
+ if (wc->check_metadata_after && wc->check_metadata_after < now) {
871
+ cmd.opcode = METADATA_MAINTENANCE;
872
+ if (!metadata_enq_cmd_noblock(wc, &cmd))
873
+ wc->check_metadata_after = now + METADATA_MAINTENANCE_INTERVAL;
874
+ }
875
+
876
+ if (wc->check_hosts_after && wc->check_hosts_after < now) {
877
+ cmd.opcode = METADATA_SCAN_HOSTS;
878
+ if (!metadata_enq_cmd_noblock(wc, &cmd))
879
+ wc->check_hosts_after = now + METADATA_HOST_CHECK_INTERVAL;
880
+ }
881
+}
882
+
883
+static void after_metadata_cleanup(uv_work_t *req, int status)
884
+{
885
+ UNUSED(status);
886
+
887
+ struct metadata_wc *wc = req->data;
888
+ metadata_flag_clear(wc, METADATA_FLAG_CLEANUP);
889
+}
890
+static void start_metadata_cleanup(uv_work_t *req)
891
+{
892
+ struct metadata_wc *wc = req->data;
893
+ check_dimension_metadata(wc);
894
+}
895
+
896
+struct scan_metadata_payload {
897
+ uv_work_t request;
898
+ struct metadata_wc *wc;
899
+ struct completion *completion;
900
+ uint32_t max_count;
901
+};
902
+
903
+// Callback after scan of hosts is done
904
+static void after_metadata_hosts(uv_work_t *req, int status __maybe_unused)
905
+{
906
+ struct scan_metadata_payload *data = req->data;
907
+ struct metadata_wc *wc = data->wc;
908
+
909
+ metadata_flag_clear(wc, METADATA_FLAG_SCANNING_HOSTS);
910
+ internal_error(true, "METADATA: scanning hosts complete");
911
+ if (unlikely(data->completion)) {
912
+ completion_mark_complete(data->completion);
913
+ internal_error(true, "METADATA: Sending completion done");
914
+ }
915
+ freez(data);
916
+}
917
+
918
+static bool metadata_scan_host(RRDHOST *host, uint32_t max_count) {
919
+ RRDSET *st;
920
+ int rc;
921
+
922
+ bool more_to_do = false;
923
+ uint32_t scan_count = 1;
924
+ BUFFER *work_buffer = buffer_create(1024);
925
+
926
+ rrdset_foreach_reentrant(st, host) {
927
+ if (scan_count == max_count) {
928
+ more_to_do = true;
929
+ break;
930
+ }
931
+ if(rrdset_flag_check(st, RRDSET_FLAG_METADATA_UPDATE)) {
932
+ rrdset_flag_clear(st, RRDSET_FLAG_METADATA_UPDATE);
933
+ scan_count++;
934
+
935
+ check_and_update_chart_labels(st, work_buffer);
936
+
937
+ rc = sql_store_chart(
938
+ &st->chart_uuid,
939
+ &st->rrdhost->host_uuid,
940
+ string2str(st->parts.type),
941
+ string2str(st->parts.id),
942
+ string2str(st->parts.name),
943
+ rrdset_family(st),
944
+ rrdset_context(st),
945
+ rrdset_title(st),
946
+ rrdset_units(st),
947
+ rrdset_plugin_name(st),
948
+ rrdset_module_name(st),
949
+ st->priority,
950
+ st->update_every,
951
+ st->chart_type,
952
+ st->rrd_memory_mode,
953
+ st->entries);
954
+ if (unlikely(rc))
955
+ internal_error(true, "METADATA: Failed to store chart metadata %s", string2str(st->id));
956
+ }
957
+
958
+ RRDDIM *rd;
959
+ rrddim_foreach_read(rd, st) {
960
+ if(rrddim_flag_check(rd, RRDDIM_FLAG_METADATA_UPDATE)) {
961
+ rrddim_flag_clear(rd, RRDDIM_FLAG_METADATA_UPDATE);
962
+
963
+ rc = sql_store_dimension(
964
+ &rd->metric_uuid,
965
+ &rd->rrdset->chart_uuid,
966
+ string2str(rd->id),
967
+ string2str(rd->name),
968
+ rd->multiplier,
969
+ rd->divisor,
970
+ rd->algorithm);
971
+
972
+ if (unlikely(rc))
973
+ error_report("METADATA: Failed to store dimension %s", string2str(rd->id));
974
+ }
975
+ }
976
+ rrddim_foreach_done(rd);
977
+ }
978
+ rrdset_foreach_done(st);
979
+
980
+ buffer_free(work_buffer);
981
+ return more_to_do;
982
+}
983
+
984
+// Worker thread to scan hosts for pending metadata to store
985
+static void start_metadata_hosts(uv_work_t *req __maybe_unused)
986
+{
987
+ RRDHOST *host;
988
+
989
+ struct scan_metadata_payload *data = req->data;
990
+ struct metadata_wc *wc = data->wc;
991
+
992
+ bool run_again = false;
993
+ dfe_start_reentrant(rrdhost_root_index, host) {
994
+ if (rrdhost_flag_check(host, RRDHOST_FLAG_ARCHIVED) || !rrdhost_flag_check(host, RRDHOST_FLAG_METADATA_UPDATE))
995
+ continue;
996
+ internal_error(true, "METADATA: Scanning host %s", rrdhost_hostname(host));
997
+ rrdhost_flag_clear(host,RRDHOST_FLAG_METADATA_UPDATE);
998
+ if (unlikely(metadata_scan_host(host, data->max_count))) {
999
+ run_again = true;
1000
+ rrdhost_flag_set(host,RRDHOST_FLAG_METADATA_UPDATE);
1001
+ internal_error(true,"METADATA: Rescheduling host %s to run; more charts to store", rrdhost_hostname(host));
1002
+ }
1003
+ }
1004
+ dfe_done(host);
1005
+ if (unlikely(run_again))
1006
+ wc->check_hosts_after = now_realtime_sec() + METADATA_HOST_CHECK_IMMEDIATE;
1007
+ else
1008
+ wc->check_hosts_after = now_realtime_sec() + METADATA_HOST_CHECK_INTERVAL;
1009
+}
1010
+
1011
+static void metadata_event_loop(void *arg)
1012
+{
1013
+ worker_register("METASYNC");
1014
+ worker_register_job_name(METADATA_DATABASE_NOOP, "noop");
1015
+ worker_register_job_name(METADATA_DATABASE_TIMER, "timer");
1016
+ worker_register_job_name(METADATA_ADD_CHART, "add chart");
1017
+ worker_register_job_name(METADATA_ADD_CHART_LABEL, "add chart label");
1018
+ worker_register_job_name(METADATA_ADD_DIMENSION, "add dimension");
1019
+ worker_register_job_name(METADATA_DEL_DIMENSION, "delete dimension");
1020
+ worker_register_job_name(METADATA_ADD_DIMENSION_OPTION, "dimension option");
1021
+ worker_register_job_name(METADATA_ADD_HOST_SYSTEM_INFO, "host system info");
1022
+ worker_register_job_name(METADATA_ADD_HOST_INFO, "host info");
1023
+ worker_register_job_name(METADATA_STORE_CLAIM_ID, "add claim id");
1024
+ worker_register_job_name(METADATA_STORE_HOST_LABELS, "host labels");
1025
+ worker_register_job_name(METADATA_MAINTENANCE, "maintenance");
1026
+
1027
+
1028
+ int ret;
1029
+ uv_loop_t *loop;
1030
+ unsigned cmd_batch_size;
1031
+ struct metadata_wc *wc = arg;
1032
+ enum metadata_opcode opcode, next_opcode;
1033
+ uv_work_t metadata_cleanup_worker;
1034
+
1035
+ uv_thread_set_name_np(wc->thread, "METASYNC");
1036
+ loop = wc->loop = mallocz(sizeof(uv_loop_t));
1037
+ ret = uv_loop_init(loop);
1038
+ if (ret) {
1039
+ error("uv_loop_init(): %s", uv_strerror(ret));
1040
+ goto error_after_loop_init;
1041
+ }
1042
+ loop->data = wc;
1043
+
1044
+ ret = uv_async_init(wc->loop, &wc->async, async_cb);
1045
+ if (ret) {
1046
+ error("uv_async_init(): %s", uv_strerror(ret));
1047
+ goto error_after_async_init;
1048
+ }
1049
+ wc->async.data = wc;
1050
+
1051
+ ret = uv_timer_init(loop, &wc->timer_req);
1052
+ if (ret) {
1053
+ error("uv_timer_init(): %s", uv_strerror(ret));
1054
+ goto error_after_timer_init;
1055
+ }
1056
+ wc->timer_req.data = wc;
1057
+ fatal_assert(0 == uv_timer_start(&wc->timer_req, timer_cb, TIMER_INITIAL_PERIOD_MS, TIMER_REPEAT_PERIOD_MS));
1058
+
1059
+ info("Starting metadata sync thread with %d entries command queue", METADATA_CMD_Q_MAX_SIZE);
1060
+
1061
+ struct metadata_cmd cmd;
1062
+ memset(&cmd, 0, sizeof(cmd));
1063
+ metadata_flag_clear(wc, METADATA_FLAG_CLEANUP);
1064
+ metadata_flag_clear(wc, METADATA_FLAG_SCANNING_HOSTS);
1065
+
1066
+ wc->check_metadata_after = now_realtime_sec() + METADATA_MAINTENANCE_FIRST_CHECK;
1067
+ wc->check_hosts_after = now_realtime_sec() + METADATA_HOST_CHECK_FIRST_CHECK;
1068
+
1069
+ int shutdown = 0;
1070
+ int in_transaction = 0;
1071
+ int commands_in_transaction = 0;
1072
+ // This can be used in the event loop for all opcodes (not workers)
1073
+ BUFFER *work_buffer = buffer_create(1024);
1074
+ wc->row_id = 0;
1075
+ completion_mark_complete(&wc->init_complete);
1076
+
1077
+ while (shutdown == 0 || (wc->flags & METADATA_WORKER_BUSY)) {
1078
+ RRDDIM *rd = NULL;
1079
+ RRDSET *st = NULL;
1080
+ RRDHOST *host = NULL;
1081
+ DICTIONARY_ITEM *dict_item = NULL;
1082
+ BUFFER *buffer = NULL;
1083
+ uuid_t *uuid;
1084
+ int rc;
1085
+
1086
+ worker_is_idle();
1087
+ uv_run(loop, UV_RUN_DEFAULT);
1088
+
1089
+ /* wait for commands */
1090
+ cmd_batch_size = 0;
1091
+ do {
1092
+ if (unlikely(cmd_batch_size >= METADATA_MAX_BATCH_SIZE))
1093
+ break;
1094
+
1095
+ cmd = metadata_deq_cmd(wc, &next_opcode);
1096
+ opcode = cmd.opcode;
1097
+
1098
+ if (unlikely(opcode == METADATA_DATABASE_NOOP && metadata_flag_check(wc, METADATA_FLAG_SHUTDOWN))) {
1099
+ shutdown = 1;
1100
+ continue;
1101
+ }
1102
+
1103
+ ++cmd_batch_size;
1104
+
1105
+ // If we are not in transaction and this command is the same with the next ; start a transaction
1106
+ if (!in_transaction && opcode < METADATA_SKIP_TRANSACTION && opcode == next_opcode) {
1107
+ if (opcode != METADATA_DATABASE_NOOP) {
1108
+ in_transaction = 1;
1109
+ db_execute("BEGIN TRANSACTION;");
1110
+ }
1111
+ }
1112
+
1113
+ if (likely(in_transaction)) {
1114
+ commands_in_transaction++;
1115
+ }
1116
+
1117
+ if (likely(opcode != METADATA_DATABASE_NOOP))
1118
+ worker_is_busy(opcode);
1119
+
1120
+ switch (opcode) {
1121
+ case METADATA_DATABASE_NOOP:
1122
+ case METADATA_DATABASE_TIMER:
1123
+ break;
1124
+ case METADATA_ADD_CHART:
1125
+ dict_item = (DICTIONARY_ITEM * ) cmd.param[0];
1126
+ st = (RRDSET *) dictionary_acquired_item_value(dict_item);
1127
+
1128
+ rc = sql_store_chart(
1129
+ &st->chart_uuid,
1130
+ &st->rrdhost->host_uuid,
1131
+ string2str(st->parts.type),
1132
+ string2str(st->parts.id),
1133
+ string2str(st->parts.name),
1134
+ rrdset_family(st),
1135
+ rrdset_context(st),
1136
+ rrdset_title(st),
1137
+ rrdset_units(st),
1138
+ rrdset_plugin_name(st),
1139
+ rrdset_module_name(st),
1140
+ st->priority,
1141
+ st->update_every,
1142
+ st->chart_type,
1143
+ st->rrd_memory_mode,
1144
+ st->entries);
1145
+
1146
+ if (unlikely(rc))
1147
+ error_report("Failed to store chart %s", rrdset_id(st));
1148
+
1149
+ dictionary_acquired_item_release(st->rrdhost->rrdset_root_index, dict_item);
1150
+ break;
1151
+ case METADATA_ADD_CHART_LABEL:
1152
+ dict_item = (DICTIONARY_ITEM * ) cmd.param[0];
1153
+ st = (RRDSET *) dictionary_acquired_item_value(dict_item);
1154
+ check_and_update_chart_labels(st, work_buffer);
1155
+ dictionary_acquired_item_release(st->rrdhost->rrdset_root_index, dict_item);
1156
+ break;
1157
+ case METADATA_ADD_DIMENSION:
1158
+ dict_item = (DICTIONARY_ITEM * ) cmd.param[0];
1159
+ rd = (RRDDIM *) dictionary_acquired_item_value(dict_item);
1160
+
1161
+ rc = sql_store_dimension(
1162
+ &rd->metric_uuid,
1163
+ &rd->rrdset->chart_uuid,
1164
+ string2str(rd->id),
1165
+ string2str(rd->name),
1166
+ rd->multiplier,
1167
+ rd->divisor,
1168
+ rd->algorithm);
1169
+
1170
+ if (unlikely(rc))
1171
+ error_report("Failed to store dimension %s", rrddim_id(rd));
1172
+
1173
+ dictionary_acquired_item_release(rd->rrdset->rrddim_root_index, dict_item);
1174
+ break;
1175
+ case METADATA_DEL_DIMENSION:
1176
+ uuid = (uuid_t *) cmd.param[0];
1177
+ if (likely(dimension_can_be_deleted(uuid)))
1178
+ delete_dimension_uuid(uuid);
1179
+ freez(uuid);
1180
+ break;
1181
+ case METADATA_ADD_DIMENSION_OPTION:
1182
+ dict_item = (DICTIONARY_ITEM * ) cmd.param[0];
1183
+ rd = (RRDDIM *) dictionary_acquired_item_value(dict_item);
1184
+ rc = sql_set_dimension_option(
1185
+ &rd->metric_uuid, rrddim_flag_check(rd, RRDDIM_FLAG_META_HIDDEN) ? "hidden" : NULL);
1186
+ if (unlikely(rc))
1187
+ error_report("Failed to store dimension option for %s", string2str(rd->id));
1188
+ dictionary_acquired_item_release(rd->rrdset->rrddim_root_index, dict_item);
1189
+ break;
1190
+ case METADATA_ADD_HOST_SYSTEM_INFO:
1191
+ buffer = (BUFFER *) cmd.param[0];
1192
+ db_execute(buffer_tostring(buffer));
1193
+ buffer_free(buffer);
1194
+ break;
1195
+ case METADATA_ADD_HOST_INFO:
1196
+ dict_item = (DICTIONARY_ITEM * ) cmd.param[0];
1197
+ host = (RRDHOST *) dictionary_acquired_item_value(dict_item);
1198
+ rc = sql_store_host_info(host);
1199
+ if (unlikely(rc))
1200
+ error_report("Failed to store host info in the database for %s", string2str(host->hostname));
1201
+ dictionary_acquired_item_release(rrdhost_root_index, dict_item);
1202
+ break;
1203
+ case METADATA_STORE_CLAIM_ID:
1204
+ store_claim_id((uuid_t *) cmd.param[0], (uuid_t *) cmd.param[1]);
1205
+ freez((void *) cmd.param[0]);
1206
+ freez((void *) cmd.param[1]);
1207
+ break;
1208
+ case METADATA_STORE_HOST_LABELS:
1209
+ dict_item = (DICTIONARY_ITEM * ) cmd.param[0];
1210
+ host = (RRDHOST *) dictionary_acquired_item_value(dict_item);
1211
+ rc = exec_statement_with_uuid(SQL_DELETE_HOST_LABELS, &host->host_uuid);
1212
+
1213
+ if (likely(rc == SQLITE_OK)) {
1214
+ buffer_flush(work_buffer);
1215
+ struct query_build tmp = {.sql = work_buffer, .count = 0};
1216
+ uuid_unparse_lower(host->host_uuid, tmp.uuid_str);
1217
+ rrdlabels_walkthrough_read(host->rrdlabels, host_label_store_to_sql_callback, &tmp);
1218
+ db_execute(buffer_tostring(work_buffer));
1219
+ }
1220
+
1221
+ dictionary_acquired_item_release(rrdhost_root_index, dict_item);
1222
+ break;
1223
+
1224
+ case METADATA_SCAN_HOSTS:
1225
+ if (unlikely(metadata_flag_check(wc, METADATA_FLAG_SCANNING_HOSTS)))
1226
+ break;
1227
+
1228
+ struct scan_metadata_payload *data = mallocz(sizeof(*data));
1229
+ data->request.data = data;
1230
+ data->wc = wc;
1231
+ data->completion = cmd.completion; // Completion by the worker
1232
+
1233
+ if (unlikely(cmd.completion)) {
1234
+ data->max_count = 0; // 0 will process all pending updates
1235
+ cmd.completion = NULL; // Do not complete after launching worker (worker will do)
1236
+ }
1237
+ else
1238
+ data->max_count = 1000;
1239
+
1240
+ metadata_flag_set(wc, METADATA_FLAG_SCANNING_HOSTS);
1241
+ if (unlikely(
1242
+ uv_queue_work(loop,&data->request,
1243
+ start_metadata_hosts,
1244
+ after_metadata_hosts))) {
1245
+ // Failed to launch worker -- let the event loop handle completion
1246
+ cmd.completion = data->completion;
1247
+ freez(data);
1248
+ metadata_flag_clear(wc, METADATA_FLAG_SCANNING_HOSTS);
1249
+ }
1250
+ break;
1251
+ case METADATA_STORE_BUFFER:
1252
+ buffer = (BUFFER *) cmd.param[0];
1253
+ db_execute(buffer_tostring(buffer));
1254
+ buffer_free(buffer);
1255
+ break;
1256
+ case METADATA_MAINTENANCE:
1257
+ if (unlikely(metadata_flag_check(wc, METADATA_FLAG_CLEANUP)))
1258
+ break;
1259
+
1260
+ metadata_cleanup_worker.data = wc;
1261
+ metadata_flag_set(wc, METADATA_FLAG_CLEANUP);
1262
+ if (unlikely(
1263
+ uv_queue_work(loop, &metadata_cleanup_worker, start_metadata_cleanup, after_metadata_cleanup))) {
1264
+ metadata_flag_clear(wc, METADATA_FLAG_CLEANUP);
1265
+ }
1266
+ break;
1267
+ case METADATA_UNITTEST:;
1268
+ struct thread_unittest *tu = (struct thread_unittest *) cmd.param[0];
1269
+ sleep_usec(1000); // processing takes 1ms
1270
+ __atomic_fetch_add(&tu->processed, 1, __ATOMIC_SEQ_CST);
1271
+ break;
1272
+ default:
1273
+ break;
1274
+ }
1275
+ if (in_transaction && (commands_in_transaction >= METADATA_MAX_TRANSACTION_BATCH || opcode != next_opcode)) {
1276
+ in_transaction = 0;
1277
+ db_execute("COMMIT TRANSACTION;");
1278
+ commands_in_transaction = 0;
1279
+ }
1280
+
1281
+ if (cmd.completion)
1282
+ completion_mark_complete(cmd.completion);
1283
+ } while (opcode != METADATA_DATABASE_NOOP);
1284
+ }
1285
+
1286
+ if (!uv_timer_stop(&wc->timer_req))
1287
+ uv_close((uv_handle_t *)&wc->timer_req, NULL);
1288
+
1289
+ /*
1290
+ * uv_async_send after uv_close does not seem to crash in linux at the moment,
1291
+ * it is however undocumented behaviour we need to be aware if this becomes
1292
+ * an issue in the future.
1293
+ */
1294
+ uv_close((uv_handle_t *)&wc->async, NULL);
1295
+ uv_run(loop, UV_RUN_DEFAULT);
1296
+
1297
+ uv_cond_destroy(&wc->cmd_cond);
1298
+ /* uv_mutex_destroy(&wc->cmd_mutex); */
1299
+ //fatal_assert(0 == uv_loop_close(loop));
1300
+ int rc;
1301
+
1302
+ do {
1303
+ rc = uv_loop_close(loop);
1304
+ } while (rc != UV_EBUSY);
1305
+
1306
+ freez(loop);
1307
+ worker_unregister();
1308
+
1309
+ buffer_free(work_buffer);
1310
+ info("METADATA: Shutting down event loop");
1311
+ completion_mark_complete(&wc->init_complete);
1312
+ return;
1313
+
1314
+error_after_timer_init:
1315
+ uv_close((uv_handle_t *)&wc->async, NULL);
1316
+error_after_async_init:
1317
+ fatal_assert(0 == uv_loop_close(loop));
1318
+error_after_loop_init:
1319
+ freez(loop);
1320
+ worker_unregister();
1321
+}
1322
+
1323
+struct metadata_wc metasync_worker;
1324
+
1325
+void metadata_sync_shutdown(void)
1326
+{
1327
+ completion_init(&metasync_worker.init_complete);
1328
+
1329
+ struct metadata_cmd cmd;
1330
+ memset(&cmd, 0, sizeof(cmd));
1331
+ info("METADATA: Sending a shutdown command");
1332
+ cmd.opcode = METADATA_SYNC_SHUTDOWN;
1333
+ metadata_enq_cmd(&metasync_worker, &cmd);
1334
+
1335
+ /* wait for metadata thread to shut down */
1336
+ info("METADATA: Waiting for shutdown ACK");
1337
+ completion_wait_for(&metasync_worker.init_complete);
1338
+ completion_destroy(&metasync_worker.init_complete);
1339
+ info("METADATA: Shutdown complete");
1340
+}
1341
+
1342
+void metadata_sync_shutdown_prepare(void)
1343
+{
1344
+ struct metadata_cmd cmd;
1345
+ memset(&cmd, 0, sizeof(cmd));
1346
+
1347
+ struct completion compl;
1348
+ completion_init(&compl);
1349
+
1350
+ info("METADATA: Sending a scan host command");
1351
+ uint32_t max_wait_iterations = 2000;
1352
+ while (unlikely(metadata_flag_check(&metasync_worker, METADATA_FLAG_SCANNING_HOSTS)) && max_wait_iterations--) {
1353
+ if (max_wait_iterations == 1999)
1354
+ info("METADATA: Current worker is running; waiting to finish");
1355
+ sleep_usec(1000);
1356
+ }
1357
+
1358
+ cmd.opcode = METADATA_SCAN_HOSTS;
1359
+ cmd.completion = &compl;
1360
+ metadata_enq_cmd(&metasync_worker, &cmd);
1361
+
1362
+ info("METADATA: Waiting for host scan completion");
1363
+ completion_wait_for(&compl);
1364
+ completion_destroy(&compl);
1365
+ info("METADATA: Host scan complete; can continue with shutdown");
1366
+}
1367
+
1368
+// -------------------------------------------------------------
1369
+// Init function called on agent startup
1370
+
1371
+void metadata_sync_init(void)
1372
+{
1373
+ struct metadata_wc *wc = &metasync_worker;
1374
+
1375
+ fatal_assert(0 == uv_mutex_init(&metadata_async_lock));
1376
+
1377
+ memset(wc, 0, sizeof(*wc));
1378
+ metadata_init_cmd_queue(wc);
1379
+ completion_init(&wc->init_complete);
1380
+
1381
+ fatal_assert(0 == uv_thread_create(&(wc->thread), metadata_event_loop, wc));
1382
+
1383
+ completion_wait_for(&wc->init_complete);
1384
+ completion_destroy(&wc->init_complete);
1385
+
1386
+ info("SQLite metadata sync initialization complete");
1387
+}
1388
+
1389
+
1390
+// Helpers
1391
+
1392
+static inline void queue_metadata_cmd(enum metadata_opcode opcode, const void *param0, const void *param1)
1393
+{
1394
+ struct metadata_cmd cmd;
1395
+ cmd.opcode = opcode;
1396
+ cmd.param[0] = param0;
1397
+ cmd.param[1] = param1;
1398
+ cmd.completion = NULL;
1399
+ metadata_enq_cmd(&metasync_worker, &cmd);
1400
+
1401
+}
1402
+
1403
+// Public
1404
+void metaqueue_chart_update(RRDSET *st)
1405
+{
1406
+ const DICTIONARY_ITEM *acquired_st = dictionary_get_and_acquire_item(st->rrdhost->rrdset_root_index, string2str(st->id));
1407
+ queue_metadata_cmd(METADATA_ADD_CHART, acquired_st, NULL);
1408
+}
1409
+
1410
+//
1411
+// RD may not be collected, so we may store it needlessly
1412
+void metaqueue_dimension_update(RRDDIM *rd)
1413
+{
1414
+ const DICTIONARY_ITEM *acquired_rd =
1415
+ dictionary_get_and_acquire_item(rd->rrdset->rrddim_root_index, string2str(rd->id));
1416
+
1417
+ if (unlikely(rrdset_flag_check(rd->rrdset, RRDSET_FLAG_METADATA_UPDATE))) {
1418
+ metaqueue_chart_update(rd->rrdset);
1419
+ rrdset_flag_clear(rd->rrdset, RRDSET_FLAG_METADATA_UPDATE);
1420
+ }
1421
+
1422
+ queue_metadata_cmd(METADATA_ADD_DIMENSION, acquired_rd, NULL);
1423
+}
1424
+
1425
+void metaqueue_dimension_update_flags(RRDDIM *rd)
1426
+{
1427
+ const DICTIONARY_ITEM *acquired_rd =
1428
+ dictionary_get_and_acquire_item(rd->rrdset->rrddim_root_index, string2str(rd->id));
1429
+ queue_metadata_cmd(METADATA_ADD_DIMENSION_OPTION, acquired_rd, NULL);
1430
+}
1431
+
1432
+void metaqueue_host_update_system_info(RRDHOST *host)
1433
+{
1434
+ BUFFER *work_buffer = sql_store_host_system_info(host);
1435
+
1436
+ if (unlikely(!work_buffer))
1437
+ return;
1438
+
1439
+ queue_metadata_cmd(METADATA_ADD_HOST_SYSTEM_INFO, work_buffer, NULL);
1440
+}
1441
+
1442
+void metaqueue_host_update_info(const char *machine_guid)
1443
+{
1444
+ const DICTIONARY_ITEM *acquired_host = dictionary_get_and_acquire_item(rrdhost_root_index, machine_guid);
1445
+ queue_metadata_cmd(METADATA_ADD_HOST_INFO, acquired_host, NULL);
1446
+}
1447
+
1448
+void metaqueue_delete_dimension_uuid(uuid_t *uuid)
1449
+{
1450
+ uuid_t *use_uuid = mallocz(sizeof(*uuid));
1451
+ uuid_copy(*use_uuid, *uuid);
1452
+ queue_metadata_cmd(METADATA_DEL_DIMENSION, use_uuid, NULL);
1453
+}
1454
+
1455
+void metaqueue_store_claim_id(uuid_t *host_uuid, uuid_t *claim_uuid)
1456
+{
1457
+ if (unlikely(!host_uuid))
1458
+ return;
1459
+
1460
+ uuid_t *local_host_uuid = mallocz(sizeof(*host_uuid));
1461
+ uuid_t *local_claim_uuid = NULL;
1462
+
1463
+ uuid_copy(*local_host_uuid, *host_uuid);
1464
+ if (likely(claim_uuid)) {
1465
+ local_claim_uuid = mallocz(sizeof(*claim_uuid));
1466
+ uuid_copy(*local_claim_uuid, *claim_uuid);
1467
+ }
1468
+ queue_metadata_cmd(METADATA_STORE_CLAIM_ID, local_host_uuid, local_claim_uuid);
1469
+}
1470
+
1471
+void metaqueue_store_host_labels(const char *machine_guid)
1472
+{
1473
+ const DICTIONARY_ITEM *acquired_host = dictionary_get_and_acquire_item(rrdhost_root_index, machine_guid);
1474
+ queue_metadata_cmd(METADATA_STORE_HOST_LABELS, acquired_host, NULL);
1475
+}
1476
+
1477
+void metaqueue_buffer(BUFFER *buffer)
1478
+{
1479
+ queue_metadata_cmd(METADATA_STORE_BUFFER, buffer, NULL);
1480
+}
1481
+
1482
+void metaqueue_chart_labels(RRDSET *st)
1483
+{
1484
+ const DICTIONARY_ITEM *acquired_st = dictionary_get_and_acquire_item(st->rrdhost->rrdset_root_index, string2str(st->id));
1485
+ queue_metadata_cmd(METADATA_ADD_CHART_LABEL, acquired_st, NULL);
1486
+}
1487
+
1488
+
1489
+//
1490
+// unitests
1491
+//
1492
+
1493
+static void *unittest_queue_metadata(void *arg) {
1494
+ struct thread_unittest *tu = arg;
1495
+
1496
+ struct metadata_cmd cmd;
1497
+ cmd.opcode = METADATA_UNITTEST;
1498
+ cmd.param[0] = tu;
1499
+ cmd.param[1] = NULL;
1500
+ cmd.completion = NULL;
1501
+ metadata_enq_cmd(&metasync_worker, &cmd);
1502
+
1503
+ do {
1504
+ __atomic_fetch_add(&tu->added, 1, __ATOMIC_SEQ_CST);
1505
+ metadata_enq_cmd(&metasync_worker, &cmd);
1506
+ sleep_usec(10000);
1507
+ } while (!__atomic_load_n(&tu->join, __ATOMIC_RELAXED));
1508
+ return arg;
1509
+}
1510
+
1511
+static void *metadata_unittest_threads(void)
1512
+{
1513
+
1514
+ unsigned done;
1515
+
1516
+ struct thread_unittest tu = {
1517
+ .join = 0,
1518
+ .added = 0,
1519
+ .processed = 0,
1520
+ .done = &done,
1521
+ };
1522
+
1523
+ // Queue messages / Time it
1524
+ time_t seconds_to_run = 5;
1525
+ int threads_to_create = 4;
1526
+ fprintf(
1527
+ stderr,
1528
+ "\nChecking metadata queue using %d threads for %ld seconds...\n",
1529
+ threads_to_create,
1530
+ seconds_to_run);
1531
+
1532
+ netdata_thread_t threads[threads_to_create];
1533
+ tu.join = 0;
1534
+ for (int i = 0; i < threads_to_create; i++) {
1535
+ char buf[100 + 1];
1536
+ snprintf(buf, 100, "meta%d", i);
1537
+ netdata_thread_create(
1538
+ &threads[i],
1539
+ buf,
1540
+ NETDATA_THREAD_OPTION_DONT_LOG | NETDATA_THREAD_OPTION_JOINABLE,
1541
+ unittest_queue_metadata,
1542
+ &tu);
1543
+ }
1544
+ uv_async_send(&metasync_worker.async);
1545
+ sleep_usec(seconds_to_run * USEC_PER_SEC);
1546
+
1547
+ __atomic_store_n(&tu.join, 1, __ATOMIC_RELAXED);
1548
+ for (int i = 0; i < threads_to_create; i++) {
1549
+ void *retval;
1550
+ netdata_thread_join(threads[i], &retval);
1551
+ }
1552
+// uv_async_send(&metasync_worker.async);
1553
+ sleep_usec(5 * USEC_PER_SEC);
1554
+
1555
+ fprintf(stderr, "Added %u elements, processed %u\n", tu.added, tu.processed);
1556
+
1557
+ return 0;
1558
+}
1559
+
1560
+int metadata_unittest(void)
1561
+{
1562
+ metadata_sync_init();
1563
+
1564
+ // Queue items for a specific period of time
1565
+ metadata_unittest_threads();
1566
+
1567
+ fprintf(stderr, "Items still in queue %u\n", metasync_worker.queue_size);
1568
+ metadata_sync_shutdown();
1569
+
1570
+ return 0;
1571
+}