sqlite_context.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  1. // SPDX-License-Identifier: GPL-3.0-or-later
  2. #include "sqlite_functions.h"
  3. #include "sqlite_context.h"
  4. #include "sqlite_db_migration.h"
  5. #define DB_CONTEXT_METADATA_VERSION 1
  6. const char *database_context_config[] = {
  7. "CREATE TABLE IF NOT EXISTS context (host_id BLOB, id TEXT NOT NULL, version INT NOT NULL, title TEXT NOT NULL, "
  8. "chart_type TEXT NOT NULL, unit TEXT NOT NULL, priority INT NOT NULL, first_time_t INT NOT NULL, "
  9. "last_time_t INT NOT NULL, deleted INT NOT NULL, "
  10. "family TEXT, PRIMARY KEY (host_id, id))",
  11. NULL
  12. };
  13. const char *database_context_cleanup[] = {
  14. "VACUUM",
  15. NULL
  16. };
  17. sqlite3 *db_context_meta = NULL;
  18. /*
  19. * Initialize the SQLite database
  20. * Return 0 on success
  21. */
  22. int sql_init_context_database(int memory)
  23. {
  24. char sqlite_database[FILENAME_MAX + 1];
  25. int rc;
  26. if (likely(!memory))
  27. snprintfz(sqlite_database, sizeof(sqlite_database) - 1, "%s/context-meta.db", netdata_configured_cache_dir);
  28. else
  29. strcpy(sqlite_database, ":memory:");
  30. rc = sqlite3_open(sqlite_database, &db_context_meta);
  31. if (rc != SQLITE_OK) {
  32. error_report("Failed to initialize database at %s, due to \"%s\"", sqlite_database, sqlite3_errstr(rc));
  33. sqlite3_close(db_context_meta);
  34. db_context_meta = NULL;
  35. return 1;
  36. }
  37. netdata_log_info("SQLite database %s initialization", sqlite_database);
  38. char buf[1024 + 1] = "";
  39. const char *list[2] = { buf, NULL };
  40. int target_version = DB_CONTEXT_METADATA_VERSION;
  41. if (likely(!memory))
  42. target_version = perform_context_database_migration(db_context_meta, DB_CONTEXT_METADATA_VERSION);
  43. if (configure_sqlite_database(db_context_meta, target_version))
  44. return 1;
  45. if (likely(!memory))
  46. snprintfz(buf, sizeof(buf) - 1, "ATTACH DATABASE \"%s/netdata-meta.db\" as meta", netdata_configured_cache_dir);
  47. else
  48. snprintfz(buf, sizeof(buf) - 1, "ATTACH DATABASE ':memory:' as meta");
  49. if(init_database_batch(db_context_meta, list)) return 1;
  50. if (init_database_batch(db_context_meta, &database_context_config[0]))
  51. return 1;
  52. if (init_database_batch(db_context_meta, &database_context_cleanup[0]))
  53. return 1;
  54. return 0;
  55. }
  56. /*
  57. * Close the sqlite database
  58. */
  59. void sql_close_context_database(void)
  60. {
  61. int rc;
  62. if (unlikely(!db_context_meta))
  63. return;
  64. netdata_log_info("Closing context SQLite database");
  65. rc = sqlite3_close_v2(db_context_meta);
  66. if (unlikely(rc != SQLITE_OK))
  67. error_report("Error %d while closing the context SQLite database, %s", rc, sqlite3_errstr(rc));
  68. }
  69. //
  70. // Fetching data
  71. //
  72. #define CTX_GET_CHART_LIST "SELECT c.chart_id, c.type||'.'||c.id, c.name, c.context, c.title, c.unit, c.priority, " \
  73. "c.update_every, c.chart_type, c.family FROM chart c WHERE c.host_id = @host_id AND c.chart_id IS NOT NULL"
  74. void ctx_get_chart_list(uuid_t *host_uuid, void (*dict_cb)(SQL_CHART_DATA *, void *), void *data)
  75. {
  76. int rc;
  77. static __thread sqlite3_stmt *res = NULL;
  78. if (unlikely(!host_uuid)) {
  79. internal_error(true, "Requesting context chart list without host_id");
  80. return;
  81. }
  82. if (unlikely(!res)) {
  83. rc = prepare_statement(db_meta, CTX_GET_CHART_LIST, &res);
  84. if (rc != SQLITE_OK) {
  85. error_report("Failed to prepare statement to fetch chart list");
  86. return;
  87. }
  88. }
  89. rc = sqlite3_bind_blob(res, 1, host_uuid, sizeof(*host_uuid), SQLITE_STATIC);
  90. if (unlikely(rc != SQLITE_OK)) {
  91. error_report("Failed to bind host_id to fetch the chart list");
  92. goto skip_load;
  93. }
  94. SQL_CHART_DATA chart_data = { 0 };
  95. while (sqlite3_step_monitored(res) == SQLITE_ROW) {
  96. uuid_copy(chart_data.chart_id, *((uuid_t *)sqlite3_column_blob(res, 0)));
  97. chart_data.id = (char *) sqlite3_column_text(res, 1);
  98. chart_data.name = (char *) sqlite3_column_text(res, 2);
  99. chart_data.context = (char *) sqlite3_column_text(res, 3);
  100. chart_data.title = (char *) sqlite3_column_text(res, 4);
  101. chart_data.units = (char *) sqlite3_column_text(res, 5);
  102. chart_data.priority = sqlite3_column_int(res, 6);
  103. chart_data.update_every = sqlite3_column_int(res, 7);
  104. chart_data.chart_type = sqlite3_column_int(res, 8);
  105. chart_data.family = (char *) sqlite3_column_text(res, 9);
  106. dict_cb(&chart_data, data);
  107. }
  108. skip_load:
  109. rc = sqlite3_reset(res);
  110. if (rc != SQLITE_OK)
  111. error_report("Failed to reset statement that fetches chart label data, rc = %d", rc);
  112. }
  113. // Dimension list
  114. #define CTX_GET_DIMENSION_LIST "SELECT d.dim_id, d.id, d.name, CASE WHEN INSTR(d.options,\"hidden\") > 0 THEN 1 ELSE 0 END " \
  115. "FROM dimension d WHERE d.chart_id = @id AND d.dim_id IS NOT NULL ORDER BY d.rowid ASC"
  116. void ctx_get_dimension_list(uuid_t *chart_uuid, void (*dict_cb)(SQL_DIMENSION_DATA *, void *), void *data)
  117. {
  118. int rc;
  119. static __thread sqlite3_stmt *res = NULL;
  120. if (unlikely(!res)) {
  121. rc = prepare_statement(db_meta, CTX_GET_DIMENSION_LIST, &res);
  122. if (rc != SQLITE_OK) {
  123. error_report("Failed to prepare statement to fetch chart dimension data");
  124. return;
  125. }
  126. }
  127. rc = sqlite3_bind_blob(res, 1, chart_uuid, sizeof(*chart_uuid), SQLITE_STATIC);
  128. if (unlikely(rc != SQLITE_OK)) {
  129. error_report("Failed to bind chart_id to fetch dimension list");
  130. goto failed;
  131. }
  132. SQL_DIMENSION_DATA dimension_data;
  133. while (sqlite3_step_monitored(res) == SQLITE_ROW) {
  134. uuid_copy(dimension_data.dim_id, *((uuid_t *)sqlite3_column_blob(res, 0)));
  135. dimension_data.id = (char *) sqlite3_column_text(res, 1);
  136. dimension_data.name = (char *) sqlite3_column_text(res, 2);
  137. dimension_data.hidden = sqlite3_column_int(res, 3);
  138. dict_cb(&dimension_data, data);
  139. }
  140. failed:
  141. rc = sqlite3_reset(res);
  142. if (rc != SQLITE_OK)
  143. error_report("Failed to reset statement that fetches the chart dimension list, rc = %d", rc);
  144. }
  145. // LABEL LIST
  146. #define CTX_GET_LABEL_LIST "SELECT l.label_key, l.label_value, l.source_type FROM meta.chart_label l WHERE l.chart_id = @id"
  147. void ctx_get_label_list(uuid_t *chart_uuid, void (*dict_cb)(SQL_CLABEL_DATA *, void *), void *data)
  148. {
  149. int rc;
  150. static __thread sqlite3_stmt *res = NULL;
  151. if (unlikely(!res)) {
  152. rc = prepare_statement(db_context_meta, CTX_GET_LABEL_LIST, &res);
  153. if (rc != SQLITE_OK) {
  154. error_report("Failed to prepare statement to fetch chart labels");
  155. return;
  156. }
  157. }
  158. rc = sqlite3_bind_blob(res, 1, chart_uuid, sizeof(*chart_uuid), SQLITE_STATIC);
  159. if (unlikely(rc != SQLITE_OK)) {
  160. error_report("Failed to bind chart_id to fetch chart labels");
  161. goto failed;
  162. }
  163. SQL_CLABEL_DATA label_data;
  164. while (sqlite3_step_monitored(res) == SQLITE_ROW) {
  165. label_data.label_key = (char *) sqlite3_column_text(res, 0);
  166. label_data.label_value = (char *) sqlite3_column_text(res, 1);
  167. label_data.label_source = sqlite3_column_int(res, 2);
  168. dict_cb(&label_data, data);
  169. }
  170. failed:
  171. rc = sqlite3_reset(res);
  172. if (rc != SQLITE_OK)
  173. error_report("Failed to reset statement that fetches chart label data, rc = %d", rc);
  174. }
  175. // CONTEXT LIST
  176. #define CTX_GET_CONTEXT_LIST "SELECT id, version, title, chart_type, unit, priority, first_time_t, " \
  177. "last_time_t, deleted, family FROM context c WHERE c.host_id = @host_id"
  178. void ctx_get_context_list(uuid_t *host_uuid, void (*dict_cb)(VERSIONED_CONTEXT_DATA *, void *), void *data)
  179. {
  180. if (unlikely(!host_uuid))
  181. return;
  182. int rc;
  183. static __thread sqlite3_stmt *res = NULL;
  184. if (unlikely(!res)) {
  185. rc = prepare_statement(db_context_meta, CTX_GET_CONTEXT_LIST, &res);
  186. if (rc != SQLITE_OK) {
  187. error_report("Failed to prepare statement to fetch stored context list");
  188. return;
  189. }
  190. }
  191. VERSIONED_CONTEXT_DATA context_data = {0};
  192. rc = sqlite3_bind_blob(res, 1, host_uuid, sizeof(*host_uuid), SQLITE_STATIC);
  193. if (unlikely(rc != SQLITE_OK)) {
  194. error_report("Failed to bind host_id to fetch versioned context data");
  195. goto failed;
  196. }
  197. while (sqlite3_step_monitored(res) == SQLITE_ROW) {
  198. context_data.id = (char *) sqlite3_column_text(res, 0);
  199. context_data.version = sqlite3_column_int64(res, 1);
  200. context_data.title = (char *) sqlite3_column_text(res, 2);
  201. context_data.chart_type = (char *) sqlite3_column_text(res, 3);
  202. context_data.units = (char *) sqlite3_column_text(res, 4);
  203. context_data.priority = sqlite3_column_int64(res, 5);
  204. context_data.first_time_s = sqlite3_column_int64(res, 6);
  205. context_data.last_time_s = sqlite3_column_int64(res, 7);
  206. context_data.deleted = sqlite3_column_int(res, 8);
  207. context_data.family = (char *) sqlite3_column_text(res, 9);
  208. dict_cb(&context_data, data);
  209. }
  210. failed:
  211. rc = sqlite3_reset(res);
  212. if (rc != SQLITE_OK)
  213. error_report("Failed to reset statement that fetches stored context versioned data, rc = %d", rc);
  214. }
  215. //
  216. // Storing Data
  217. //
  218. #define CTX_STORE_CONTEXT \
  219. "INSERT OR REPLACE INTO context " \
  220. "(host_id, id, version, title, chart_type, unit, priority, first_time_t, last_time_t, deleted, family) " \
  221. "VALUES (@host_id, @context, @version, @title, @chart_type, @unit, @priority, @first_t, @last_t, @delete, @family)"
  222. int ctx_store_context(uuid_t *host_uuid, VERSIONED_CONTEXT_DATA *context_data)
  223. {
  224. int rc, rc_stored = 1;
  225. sqlite3_stmt *res = NULL;
  226. if (unlikely(!host_uuid || !context_data || !context_data->id))
  227. return 0;
  228. rc = sqlite3_prepare_v2(db_context_meta, CTX_STORE_CONTEXT, -1, &res, 0);
  229. if (unlikely(rc != SQLITE_OK)) {
  230. error_report("Failed to prepare statement to store context");
  231. return 1;
  232. }
  233. rc = sqlite3_bind_blob(res, 1, host_uuid, sizeof(*host_uuid), SQLITE_STATIC);
  234. if (unlikely(rc != SQLITE_OK)) {
  235. error_report("Failed to bind host_uuid to store context details");
  236. goto skip_store;
  237. }
  238. rc = bind_text_null(res, 2, context_data->id, 0);
  239. if (unlikely(rc != SQLITE_OK)) {
  240. error_report("Failed to bind context to store context details");
  241. goto skip_store;
  242. }
  243. rc = sqlite3_bind_int64(res, 3, (time_t) context_data->version);
  244. if (unlikely(rc != SQLITE_OK)) {
  245. error_report("Failed to bind first_time_t to store context details");
  246. goto skip_store;
  247. }
  248. rc = bind_text_null(res, 4, context_data->title, 0);
  249. if (unlikely(rc != SQLITE_OK)) {
  250. error_report("Failed to bind context to store context details");
  251. goto skip_store;
  252. }
  253. rc = bind_text_null(res, 5, context_data->chart_type, 0);
  254. if (unlikely(rc != SQLITE_OK)) {
  255. error_report("Failed to bind context to store context details");
  256. goto skip_store;
  257. }
  258. rc = bind_text_null(res, 6, context_data->units, 0);
  259. if (unlikely(rc != SQLITE_OK)) {
  260. error_report("Failed to bind context to store context details");
  261. goto skip_store;
  262. }
  263. rc = sqlite3_bind_int64(res, 7, (time_t) context_data->priority);
  264. if (unlikely(rc != SQLITE_OK)) {
  265. error_report("Failed to bind first_time_t to store context details");
  266. goto skip_store;
  267. }
  268. rc = sqlite3_bind_int64(res, 8, (time_t) context_data->first_time_s);
  269. if (unlikely(rc != SQLITE_OK)) {
  270. error_report("Failed to bind first_time_t to store context details");
  271. goto skip_store;
  272. }
  273. rc = sqlite3_bind_int64(res, 9, (time_t) context_data->last_time_s);
  274. if (unlikely(rc != SQLITE_OK)) {
  275. error_report("Failed to bind last_time_t to store context details");
  276. goto skip_store;
  277. }
  278. rc = sqlite3_bind_int(res, 10, context_data->deleted);
  279. if (unlikely(rc != SQLITE_OK)) {
  280. error_report("Failed to bind deleted flag to store context details");
  281. goto skip_store;
  282. }
  283. rc = bind_text_null(res, 11, context_data->family, 1);
  284. if (unlikely(rc != SQLITE_OK)) {
  285. error_report("Failed to bind context to store details");
  286. goto skip_store;
  287. }
  288. rc_stored = execute_insert(res);
  289. if (rc_stored != SQLITE_DONE)
  290. error_report("Failed store context details for context %s, rc = %d", context_data->id, rc_stored);
  291. skip_store:
  292. rc = sqlite3_finalize(res);
  293. if (rc != SQLITE_OK)
  294. error_report("Failed to finalize statement that stores context details, rc = %d", rc);
  295. return (rc_stored != SQLITE_DONE);
  296. }
  297. // Delete a context
  298. #define CTX_DELETE_CONTEXT "DELETE FROM context WHERE host_id = @host_id AND id = @context"
  299. int ctx_delete_context(uuid_t *host_uuid, VERSIONED_CONTEXT_DATA *context_data)
  300. {
  301. int rc, rc_stored = 1;
  302. sqlite3_stmt *res = NULL;
  303. if (unlikely(!context_data || !context_data->id))
  304. return 0;
  305. rc = sqlite3_prepare_v2(db_context_meta, CTX_DELETE_CONTEXT, -1, &res, 0);
  306. if (unlikely(rc != SQLITE_OK)) {
  307. error_report("Failed to prepare statement to delete context");
  308. return 1;
  309. }
  310. rc = sqlite3_bind_blob(res, 1, host_uuid, sizeof(*host_uuid), SQLITE_STATIC);
  311. if (unlikely(rc != SQLITE_OK)) {
  312. error_report("Failed to bind host_id for context data deletion");
  313. goto skip_delete;
  314. }
  315. rc = sqlite3_bind_text(res, 2, context_data->id, -1, SQLITE_STATIC);
  316. if (unlikely(rc != SQLITE_OK)) {
  317. error_report("Failed to bind context id for context data deletion");
  318. goto skip_delete;
  319. }
  320. rc_stored = execute_insert(res);
  321. if (rc_stored != SQLITE_DONE)
  322. error_report("Failed to delete context %s, rc = %d", context_data->id, rc_stored);
  323. skip_delete:
  324. rc = sqlite3_finalize(res);
  325. if (rc != SQLITE_OK)
  326. error_report("Failed to finalize statement where deleting a context, rc = %d", rc);
  327. return (rc_stored != SQLITE_DONE);
  328. }
  329. int sql_context_cache_stats(int op)
  330. {
  331. int count, dummy;
  332. if (unlikely(!db_context_meta))
  333. return 0;
  334. netdata_thread_disable_cancelability();
  335. sqlite3_db_status(db_context_meta, op, &count, &dummy, 0);
  336. netdata_thread_enable_cancelability();
  337. return count;
  338. }
  339. //
  340. // TESTING FUNCTIONS
  341. //
  342. static void dict_ctx_get_context_list_cb(VERSIONED_CONTEXT_DATA *context_data, void *data)
  343. {
  344. (void)data;
  345. netdata_log_info(" Context id = %s "
  346. "version = %"PRIu64" "
  347. "title = %s "
  348. "chart_type = %s "
  349. "units = %s "
  350. "priority = %"PRIu64" "
  351. "first time = %"PRIu64" "
  352. "last time = %"PRIu64" "
  353. "deleted = %d "
  354. "family = %s",
  355. context_data->id,
  356. context_data->version,
  357. context_data->title,
  358. context_data->chart_type,
  359. context_data->units,
  360. context_data->priority,
  361. context_data->first_time_s,
  362. context_data->last_time_s,
  363. context_data->deleted,
  364. context_data->family);
  365. }
  366. int ctx_unittest(void)
  367. {
  368. uuid_t host_uuid;
  369. uuid_generate(host_uuid);
  370. initialize_thread_key_pool();
  371. int rc = sql_init_context_database(1);
  372. if (rc != SQLITE_OK)
  373. return 1;
  374. // Store a context
  375. VERSIONED_CONTEXT_DATA context_data;
  376. context_data.id = strdupz("cpu.cpu");
  377. context_data.title = strdupz("TestContextTitle");
  378. context_data.units= strdupz("TestContextUnits");
  379. context_data.chart_type = strdupz("TestContextChartType");
  380. context_data.family = strdupz("TestContextFamily");
  381. context_data.priority = 50000;
  382. context_data.deleted = 0;
  383. context_data.first_time_s = 1657781000;
  384. context_data.last_time_s = 1657781100;
  385. context_data.version = now_realtime_usec();
  386. if (likely(!ctx_store_context(&host_uuid, &context_data)))
  387. netdata_log_info("Entry %s inserted", context_data.id);
  388. else
  389. netdata_log_info("Entry %s not inserted", context_data.id);
  390. if (likely(!ctx_store_context(&host_uuid, &context_data)))
  391. netdata_log_info("Entry %s inserted", context_data.id);
  392. else
  393. netdata_log_info("Entry %s not inserted", context_data.id);
  394. // This will change end time
  395. context_data.first_time_s = 1657781000;
  396. context_data.last_time_s = 1657782001;
  397. if (likely(!ctx_update_context(&host_uuid, &context_data)))
  398. netdata_log_info("Entry %s updated", context_data.id);
  399. else
  400. netdata_log_info("Entry %s not updated", context_data.id);
  401. netdata_log_info("List context start after insert");
  402. ctx_get_context_list(&host_uuid, dict_ctx_get_context_list_cb, NULL);
  403. netdata_log_info("List context end after insert");
  404. // This will change start time
  405. context_data.first_time_s = 1657782000;
  406. context_data.last_time_s = 1657782001;
  407. if (likely(!ctx_update_context(&host_uuid, &context_data)))
  408. netdata_log_info("Entry %s updated", context_data.id);
  409. else
  410. netdata_log_info("Entry %s not updated", context_data.id);
  411. // This will list one entry
  412. netdata_log_info("List context start after insert");
  413. ctx_get_context_list(&host_uuid, dict_ctx_get_context_list_cb, NULL);
  414. netdata_log_info("List context end after insert");
  415. netdata_log_info("List context start after insert");
  416. ctx_get_context_list(&host_uuid, dict_ctx_get_context_list_cb, NULL);
  417. netdata_log_info("List context end after insert");
  418. // This will delete the entry
  419. if (likely(!ctx_delete_context(&host_uuid, &context_data)))
  420. netdata_log_info("Entry %s deleted", context_data.id);
  421. else
  422. netdata_log_info("Entry %s not deleted", context_data.id);
  423. freez((void *)context_data.id);
  424. freez((void *)context_data.title);
  425. freez((void *)context_data.chart_type);
  426. freez((void *)context_data.family);
  427. freez((void *)context_data.units);
  428. // The list should be empty
  429. netdata_log_info("List context start after delete");
  430. ctx_get_context_list(&host_uuid, dict_ctx_get_context_list_cb, NULL);
  431. netdata_log_info("List context end after delete");
  432. sql_close_context_database();
  433. return 0;
  434. }