rrdfunctions.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. // SPDX-License-Identifier: GPL-3.0-or-later
  2. #define NETDATA_RRD_INTERNALS
  3. #include "rrd.h"
  4. #include "rrdfunctions-internals.h"
  5. #define MAX_FUNCTION_LENGTH (PLUGINSD_LINE_MAX - 512) // we need some space for the rest of the line
  6. static unsigned char functions_allowed_chars[256] = {
  7. [0] = '\0', [1] = '_', [2] = '_', [3] = '_', [4] = '_', [5] = '_', [6] = '_', [7] = '_', [8] = '_',
  8. // control
  9. ['\t'] = ' ', ['\n'] = ' ', ['\v'] = ' ', [12] = ' ', ['\r'] = ' ',
  10. [14] = '_', [15] = '_', [16] = '_', [17] = '_', [18] = '_', [19] = '_', [20] = '_', [21] = '_',
  11. [22] = '_', [23] = '_', [24] = '_', [25] = '_', [26] = '_', [27] = '_', [28] = '_', [29] = '_',
  12. [30] = '_', [31] = '_',
  13. // symbols
  14. [' '] = ' ', ['!'] = '!', ['"'] = '\'', ['#'] = '#', ['$'] = '$', ['%'] = '%', ['&'] = '&', ['\''] = '\'',
  15. ['('] = '(', [')'] = ')', ['*'] = '*', ['+'] = '+', [','] = ',', ['-'] = '-', ['.'] = '.', ['/'] = '/',
  16. // numbers
  17. ['0'] = '0', ['1'] = '1', ['2'] = '2', ['3'] = '3', ['4'] = '4', ['5'] = '5', ['6'] = '6', ['7'] = '7',
  18. ['8'] = '8', ['9'] = '9',
  19. // symbols
  20. [':'] = ':', [';'] = ';', ['<'] = '<', ['='] = '=', ['>'] = '>', ['?'] = '?', ['@'] = '@',
  21. // capitals
  22. ['A'] = 'A', ['B'] = 'B', ['C'] = 'C', ['D'] = 'D', ['E'] = 'E', ['F'] = 'F', ['G'] = 'G', ['H'] = 'H',
  23. ['I'] = 'I', ['J'] = 'J', ['K'] = 'K', ['L'] = 'L', ['M'] = 'M', ['N'] = 'N', ['O'] = 'O', ['P'] = 'P',
  24. ['Q'] = 'Q', ['R'] = 'R', ['S'] = 'S', ['T'] = 'T', ['U'] = 'U', ['V'] = 'V', ['W'] = 'W', ['X'] = 'X',
  25. ['Y'] = 'Y', ['Z'] = 'Z',
  26. // symbols
  27. ['['] = '[', ['\\'] = '\\', [']'] = ']', ['^'] = '^', ['_'] = '_', ['`'] = '`',
  28. // lower
  29. ['a'] = 'a', ['b'] = 'b', ['c'] = 'c', ['d'] = 'd', ['e'] = 'e', ['f'] = 'f', ['g'] = 'g', ['h'] = 'h',
  30. ['i'] = 'i', ['j'] = 'j', ['k'] = 'k', ['l'] = 'l', ['m'] = 'm', ['n'] = 'n', ['o'] = 'o', ['p'] = 'p',
  31. ['q'] = 'q', ['r'] = 'r', ['s'] = 's', ['t'] = 't', ['u'] = 'u', ['v'] = 'v', ['w'] = 'w', ['x'] = 'x',
  32. ['y'] = 'y', ['z'] = 'z',
  33. // symbols
  34. ['{'] = '{', ['|'] = '|', ['}'] = '}', ['~'] = '~',
  35. // rest
  36. [127] = '_', [128] = '_', [129] = '_', [130] = '_', [131] = '_', [132] = '_', [133] = '_', [134] = '_',
  37. [135] = '_', [136] = '_', [137] = '_', [138] = '_', [139] = '_', [140] = '_', [141] = '_', [142] = '_',
  38. [143] = '_', [144] = '_', [145] = '_', [146] = '_', [147] = '_', [148] = '_', [149] = '_', [150] = '_',
  39. [151] = '_', [152] = '_', [153] = '_', [154] = '_', [155] = '_', [156] = '_', [157] = '_', [158] = '_',
  40. [159] = '_', [160] = '_', [161] = '_', [162] = '_', [163] = '_', [164] = '_', [165] = '_', [166] = '_',
  41. [167] = '_', [168] = '_', [169] = '_', [170] = '_', [171] = '_', [172] = '_', [173] = '_', [174] = '_',
  42. [175] = '_', [176] = '_', [177] = '_', [178] = '_', [179] = '_', [180] = '_', [181] = '_', [182] = '_',
  43. [183] = '_', [184] = '_', [185] = '_', [186] = '_', [187] = '_', [188] = '_', [189] = '_', [190] = '_',
  44. [191] = '_', [192] = '_', [193] = '_', [194] = '_', [195] = '_', [196] = '_', [197] = '_', [198] = '_',
  45. [199] = '_', [200] = '_', [201] = '_', [202] = '_', [203] = '_', [204] = '_', [205] = '_', [206] = '_',
  46. [207] = '_', [208] = '_', [209] = '_', [210] = '_', [211] = '_', [212] = '_', [213] = '_', [214] = '_',
  47. [215] = '_', [216] = '_', [217] = '_', [218] = '_', [219] = '_', [220] = '_', [221] = '_', [222] = '_',
  48. [223] = '_', [224] = '_', [225] = '_', [226] = '_', [227] = '_', [228] = '_', [229] = '_', [230] = '_',
  49. [231] = '_', [232] = '_', [233] = '_', [234] = '_', [235] = '_', [236] = '_', [237] = '_', [238] = '_',
  50. [239] = '_', [240] = '_', [241] = '_', [242] = '_', [243] = '_', [244] = '_', [245] = '_', [246] = '_',
  51. [247] = '_', [248] = '_', [249] = '_', [250] = '_', [251] = '_', [252] = '_', [253] = '_', [254] = '_',
  52. [255] = '_'
  53. };
  54. size_t rrd_functions_sanitize(char *dst, const char *src, size_t dst_len) {
  55. return text_sanitize((unsigned char *)dst, (const unsigned char *)src, dst_len,
  56. functions_allowed_chars, true, "", NULL);
  57. }
  58. // ----------------------------------------------------------------------------
  59. // we keep a dictionary per RRDSET with these functions
  60. // the dictionary is created on demand (only when a function is added to an RRDSET)
  61. // ----------------------------------------------------------------------------
  62. static void rrd_functions_insert_callback(const DICTIONARY_ITEM *item __maybe_unused, void *func, void *rrdhost) {
  63. RRDHOST *host = rrdhost; (void)host;
  64. struct rrd_host_function *rdcf = func;
  65. rrd_collector_started();
  66. rdcf->collector = rrd_collector_acquire_current_thread();
  67. if(!rdcf->priority)
  68. rdcf->priority = RRDFUNCTIONS_PRIORITY_DEFAULT;
  69. // internal_error(true, "FUNCTIONS: adding function '%s' on host '%s', collection tid %d, %s",
  70. // dictionary_acquired_item_name(item), rrdhost_hostname(host),
  71. // rdcf->collector->tid, rdcf->collector->running ? "running" : "NOT running");
  72. }
  73. static void rrd_functions_delete_callback(const DICTIONARY_ITEM *item __maybe_unused, void *func,
  74. void *rrdhost __maybe_unused) {
  75. struct rrd_host_function *rdcf = func;
  76. rrd_collector_release(rdcf->collector);
  77. }
  78. static bool rrd_functions_conflict_callback(const DICTIONARY_ITEM *item __maybe_unused, void *func,
  79. void *new_func, void *rrdhost) {
  80. RRDHOST *host = rrdhost; (void)host;
  81. struct rrd_host_function *rdcf = func;
  82. struct rrd_host_function *new_rdcf = new_func;
  83. rrd_collector_started();
  84. bool changed = false;
  85. if(rdcf->collector != thread_rrd_collector) {
  86. nd_log(NDLS_DAEMON, NDLP_DEBUG,
  87. "FUNCTIONS: function '%s' of host '%s' changed collector from %d to %d",
  88. dictionary_acquired_item_name(item), rrdhost_hostname(host),
  89. rrd_collector_tid(rdcf->collector), rrd_collector_tid(thread_rrd_collector));
  90. struct rrd_collector *old_rdc = rdcf->collector;
  91. rdcf->collector = rrd_collector_acquire_current_thread();
  92. rrd_collector_release(old_rdc);
  93. changed = true;
  94. }
  95. if(rdcf->execute_cb != new_rdcf->execute_cb) {
  96. nd_log(NDLS_DAEMON, NDLP_DEBUG,
  97. "FUNCTIONS: function '%s' of host '%s' changed execute callback",
  98. dictionary_acquired_item_name(item), rrdhost_hostname(host));
  99. rdcf->execute_cb = new_rdcf->execute_cb;
  100. changed = true;
  101. }
  102. if(rdcf->help != new_rdcf->help) {
  103. nd_log(NDLS_DAEMON, NDLP_DEBUG,
  104. "FUNCTIONS: function '%s' of host '%s' changed help text",
  105. dictionary_acquired_item_name(item), rrdhost_hostname(host));
  106. STRING *old = rdcf->help;
  107. rdcf->help = new_rdcf->help;
  108. string_freez(old);
  109. changed = true;
  110. }
  111. else
  112. string_freez(new_rdcf->help);
  113. if(rdcf->tags != new_rdcf->tags) {
  114. nd_log(NDLS_DAEMON, NDLP_DEBUG,
  115. "FUNCTIONS: function '%s' of host '%s' changed tags",
  116. dictionary_acquired_item_name(item), rrdhost_hostname(host));
  117. STRING *old = rdcf->tags;
  118. rdcf->tags = new_rdcf->tags;
  119. string_freez(old);
  120. changed = true;
  121. }
  122. else
  123. string_freez(new_rdcf->tags);
  124. if(rdcf->timeout != new_rdcf->timeout) {
  125. nd_log(NDLS_DAEMON, NDLP_DEBUG,
  126. "FUNCTIONS: function '%s' of host '%s' changed timeout",
  127. dictionary_acquired_item_name(item), rrdhost_hostname(host));
  128. rdcf->timeout = new_rdcf->timeout;
  129. changed = true;
  130. }
  131. if(rdcf->priority != new_rdcf->priority) {
  132. nd_log(NDLS_DAEMON, NDLP_DEBUG,
  133. "FUNCTIONS: function '%s' of host '%s' changed priority",
  134. dictionary_acquired_item_name(item), rrdhost_hostname(host));
  135. rdcf->priority = new_rdcf->priority;
  136. changed = true;
  137. }
  138. if(rdcf->access != new_rdcf->access) {
  139. nd_log(NDLS_DAEMON, NDLP_DEBUG,
  140. "FUNCTIONS: function '%s' of host '%s' changed access level",
  141. dictionary_acquired_item_name(item), rrdhost_hostname(host));
  142. rdcf->access = new_rdcf->access;
  143. changed = true;
  144. }
  145. if(rdcf->sync != new_rdcf->sync) {
  146. nd_log(NDLS_DAEMON, NDLP_DEBUG,
  147. "FUNCTIONS: function '%s' of host '%s' changed sync/async mode",
  148. dictionary_acquired_item_name(item), rrdhost_hostname(host));
  149. rdcf->sync = new_rdcf->sync;
  150. changed = true;
  151. }
  152. if(rdcf->execute_cb_data != new_rdcf->execute_cb_data) {
  153. nd_log(NDLS_DAEMON, NDLP_DEBUG,
  154. "FUNCTIONS: function '%s' of host '%s' changed execute callback data",
  155. dictionary_acquired_item_name(item), rrdhost_hostname(host));
  156. rdcf->execute_cb_data = new_rdcf->execute_cb_data;
  157. changed = true;
  158. }
  159. // internal_error(true, "FUNCTIONS: adding function '%s' on host '%s', collection tid %d, %s",
  160. // dictionary_acquired_item_name(item), rrdhost_hostname(host),
  161. // rdcf->collector->tid, rdcf->collector->running ? "running" : "NOT running");
  162. return changed;
  163. }
  164. void rrd_functions_host_init(RRDHOST *host) {
  165. if(host->functions) return;
  166. host->functions = dictionary_create_advanced(DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE,
  167. &dictionary_stats_category_functions, sizeof(struct rrd_host_function));
  168. dictionary_register_insert_callback(host->functions, rrd_functions_insert_callback, host);
  169. dictionary_register_delete_callback(host->functions, rrd_functions_delete_callback, host);
  170. dictionary_register_conflict_callback(host->functions, rrd_functions_conflict_callback, host);
  171. }
  172. void rrd_functions_host_destroy(RRDHOST *host) {
  173. dictionary_destroy(host->functions);
  174. }
  175. // ----------------------------------------------------------------------------
  176. static inline bool is_function_dyncfg(const char *name) {
  177. if(!name || !*name)
  178. return false;
  179. if(strncmp(name, PLUGINSD_FUNCTION_CONFIG, sizeof(PLUGINSD_FUNCTION_CONFIG) - 1) != 0)
  180. return false;
  181. char c = name[sizeof(PLUGINSD_FUNCTION_CONFIG) - 1];
  182. if(c == 0 || isspace(c))
  183. return true;
  184. return false;
  185. }
  186. void rrd_function_add(RRDHOST *host, RRDSET *st, const char *name, int timeout, int priority,
  187. const char *help, const char *tags,
  188. HTTP_ACCESS access, bool sync,
  189. rrd_function_execute_cb_t execute_cb, void *execute_cb_data) {
  190. // RRDSET *st may be NULL in this function
  191. // to create a GLOBAL function
  192. if(!tags || !*tags) {
  193. if(strcmp(name, "systemd-journal") == 0)
  194. tags = "logs";
  195. else
  196. tags = "top";
  197. }
  198. if(st && !st->functions_view)
  199. st->functions_view = dictionary_create_view(host->functions);
  200. char key[strlen(name) + 1];
  201. rrd_functions_sanitize(key, name, sizeof(key));
  202. struct rrd_host_function tmp = {
  203. .sync = sync,
  204. .timeout = timeout,
  205. .options = st ? RRD_FUNCTION_LOCAL: (is_function_dyncfg(name) ? RRD_FUNCTION_DYNCFG : RRD_FUNCTION_GLOBAL),
  206. .access = access,
  207. .execute_cb = execute_cb,
  208. .execute_cb_data = execute_cb_data,
  209. .help = string_strdupz(help),
  210. .tags = string_strdupz(tags),
  211. .priority = priority,
  212. };
  213. const DICTIONARY_ITEM *item = dictionary_set_and_acquire_item(host->functions, key, &tmp, sizeof(tmp));
  214. if(st)
  215. dictionary_view_set(st->functions_view, key, item);
  216. else
  217. rrdhost_flag_set(host, RRDHOST_FLAG_GLOBAL_FUNCTIONS_UPDATED);
  218. dictionary_acquired_item_release(host->functions, item);
  219. }
  220. void rrd_function_del(RRDHOST *host, RRDSET *st, const char *name) {
  221. char key[strlen(name) + 1];
  222. rrd_functions_sanitize(key, name, sizeof(key));
  223. dictionary_del(host->functions, key);
  224. if(st)
  225. dictionary_del(st->functions_view, key);
  226. else
  227. rrdhost_flag_set(host, RRDHOST_FLAG_GLOBAL_FUNCTIONS_UPDATED);
  228. dictionary_garbage_collect(host->functions);
  229. }
  230. int rrd_call_function_error(BUFFER *wb, const char *msg, int code) {
  231. char buffer[PLUGINSD_LINE_MAX];
  232. json_escape_string(buffer, msg, PLUGINSD_LINE_MAX);
  233. buffer_flush(wb);
  234. buffer_sprintf(wb, "{\"status\":%d,\"error_message\":\"%s\"}", code, buffer);
  235. wb->content_type = CT_APPLICATION_JSON;
  236. buffer_no_cacheable(wb);
  237. return code;
  238. }
  239. int rrd_functions_find_by_name(RRDHOST *host, BUFFER *wb, const char *name, size_t key_length, const DICTIONARY_ITEM **item) {
  240. char buffer[MAX_FUNCTION_LENGTH + 1];
  241. strncpyz(buffer, name, sizeof(buffer) - 1);
  242. char *s = NULL;
  243. bool found = false;
  244. *item = NULL;
  245. if(host->functions) {
  246. while (buffer[0]) {
  247. if((*item = dictionary_get_and_acquire_item(host->functions, buffer))) {
  248. found = true;
  249. struct rrd_host_function *rdcf = dictionary_acquired_item_value(*item);
  250. if(rrd_collector_running(rdcf->collector)) {
  251. break;
  252. }
  253. else {
  254. dictionary_acquired_item_release(host->functions, *item);
  255. *item = NULL;
  256. }
  257. }
  258. // if s == NULL, set it to the end of the buffer;
  259. // this should happen only the first time
  260. if (unlikely(!s))
  261. s = &buffer[key_length - 1];
  262. // skip a word from the end
  263. while (s >= buffer && !isspace(*s)) *s-- = '\0';
  264. // skip all spaces
  265. while (s >= buffer && isspace(*s)) *s-- = '\0';
  266. }
  267. }
  268. buffer_flush(wb);
  269. if(!(*item)) {
  270. if(found)
  271. return rrd_call_function_error(wb,
  272. "The collector that registered this function, is not currently running.",
  273. HTTP_RESP_SERVICE_UNAVAILABLE);
  274. else
  275. return rrd_call_function_error(wb,
  276. "No collector is supplying this function on this host at this time.",
  277. HTTP_RESP_NOT_FOUND);
  278. }
  279. return HTTP_RESP_OK;
  280. }
  281. bool rrd_function_available(RRDHOST *host, const char *function) {
  282. if(!host || !host->functions)
  283. return false;
  284. bool ret = false;
  285. const DICTIONARY_ITEM *item = dictionary_get_and_acquire_item(host->functions, function);
  286. if(item) {
  287. struct rrd_host_function *rdcf = dictionary_acquired_item_value(item);
  288. if(rrd_collector_running(rdcf->collector))
  289. ret = true;
  290. dictionary_acquired_item_release(host->functions, item);
  291. }
  292. return ret;
  293. }