plugins_d.c 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696
  1. // SPDX-License-Identifier: GPL-3.0-or-later
  2. #include "plugins_d.h"
  3. char *plugin_directories[PLUGINSD_MAX_DIRECTORIES] = { NULL };
  4. char *netdata_configured_plugins_dir_base;
  5. struct plugind *pluginsd_root = NULL;
  6. static inline int pluginsd_space(char c) {
  7. switch(c) {
  8. case ' ':
  9. case '\t':
  10. case '\r':
  11. case '\n':
  12. case '=':
  13. return 1;
  14. default:
  15. return 0;
  16. }
  17. }
  18. inline int config_isspace(char c) {
  19. switch(c) {
  20. case ' ':
  21. case '\t':
  22. case '\r':
  23. case '\n':
  24. case ',':
  25. return 1;
  26. default:
  27. return 0;
  28. }
  29. }
  30. // split a text into words, respecting quotes
  31. inline int quoted_strings_splitter(char *str, char **words, int max_words, int (*custom_isspace)(char)) {
  32. char *s = str, quote = 0;
  33. int i = 0, j;
  34. // skip all white space
  35. while(unlikely(custom_isspace(*s))) s++;
  36. // check for quote
  37. if(unlikely(*s == '\'' || *s == '"')) {
  38. quote = *s; // remember the quote
  39. s++; // skip the quote
  40. }
  41. // store the first word
  42. words[i++] = s;
  43. // while we have something
  44. while(likely(*s)) {
  45. // if it is escape
  46. if(unlikely(*s == '\\' && s[1])) {
  47. s += 2;
  48. continue;
  49. }
  50. // if it is quote
  51. else if(unlikely(*s == quote)) {
  52. quote = 0;
  53. *s = ' ';
  54. continue;
  55. }
  56. // if it is a space
  57. else if(unlikely(quote == 0 && custom_isspace(*s))) {
  58. // terminate the word
  59. *s++ = '\0';
  60. // skip all white space
  61. while(likely(custom_isspace(*s))) s++;
  62. // check for quote
  63. if(unlikely(*s == '\'' || *s == '"')) {
  64. quote = *s; // remember the quote
  65. s++; // skip the quote
  66. }
  67. // if we reached the end, stop
  68. if(unlikely(!*s)) break;
  69. // store the next word
  70. if(likely(i < max_words)) words[i++] = s;
  71. else break;
  72. }
  73. // anything else
  74. else s++;
  75. }
  76. // terminate the words
  77. j = i;
  78. while(likely(j < max_words)) words[j++] = NULL;
  79. return i;
  80. }
  81. inline int pluginsd_split_words(char *str, char **words, int max_words) {
  82. return quoted_strings_splitter(str, words, max_words, pluginsd_space);
  83. }
  84. inline size_t pluginsd_process(RRDHOST *host, struct plugind *cd, FILE *fp, int trust_durations) {
  85. int enabled = cd->enabled;
  86. if(!fp || !enabled) {
  87. cd->enabled = 0;
  88. return 0;
  89. }
  90. size_t count = 0;
  91. char line[PLUGINSD_LINE_MAX + 1];
  92. char *words[PLUGINSD_MAX_WORDS] = { NULL };
  93. uint32_t BEGIN_HASH = simple_hash(PLUGINSD_KEYWORD_BEGIN);
  94. uint32_t END_HASH = simple_hash(PLUGINSD_KEYWORD_END);
  95. uint32_t FLUSH_HASH = simple_hash(PLUGINSD_KEYWORD_FLUSH);
  96. uint32_t CHART_HASH = simple_hash(PLUGINSD_KEYWORD_CHART);
  97. uint32_t DIMENSION_HASH = simple_hash(PLUGINSD_KEYWORD_DIMENSION);
  98. uint32_t DISABLE_HASH = simple_hash(PLUGINSD_KEYWORD_DISABLE);
  99. uint32_t VARIABLE_HASH = simple_hash(PLUGINSD_KEYWORD_VARIABLE);
  100. RRDSET *st = NULL;
  101. uint32_t hash;
  102. errno = 0;
  103. clearerr(fp);
  104. if(unlikely(fileno(fp) == -1)) {
  105. error("file descriptor given is not a valid stream");
  106. goto cleanup;
  107. }
  108. while(!ferror(fp)) {
  109. if(unlikely(netdata_exit)) break;
  110. char *r = fgets(line, PLUGINSD_LINE_MAX, fp);
  111. if(unlikely(!r)) {
  112. error("read failed");
  113. break;
  114. }
  115. if(unlikely(netdata_exit)) break;
  116. line[PLUGINSD_LINE_MAX] = '\0';
  117. int w = pluginsd_split_words(line, words, PLUGINSD_MAX_WORDS);
  118. char *s = words[0];
  119. if(unlikely(!s || !*s || !w)) {
  120. continue;
  121. }
  122. // debug(D_PLUGINSD, "PLUGINSD: words 0='%s' 1='%s' 2='%s' 3='%s' 4='%s' 5='%s' 6='%s' 7='%s' 8='%s' 9='%s'", words[0], words[1], words[2], words[3], words[4], words[5], words[6], words[7], words[8], words[9]);
  123. if(likely(!simple_hash_strcmp(s, "SET", &hash))) {
  124. char *dimension = words[1];
  125. char *value = words[2];
  126. if(unlikely(!dimension || !*dimension)) {
  127. error("requested a SET on chart '%s' of host '%s', without a dimension. Disabling it.", st->id, host->hostname);
  128. enabled = 0;
  129. break;
  130. }
  131. if(unlikely(!value || !*value)) value = NULL;
  132. if(unlikely(!st)) {
  133. error("requested a SET on dimension %s with value %s on host '%s', without a BEGIN. Disabling it.", dimension, value?value:"<nothing>", host->hostname);
  134. enabled = 0;
  135. break;
  136. }
  137. if(unlikely(rrdset_flag_check(st, RRDSET_FLAG_DEBUG)))
  138. debug(D_PLUGINSD, "is setting dimension %s/%s to %s", st->id, dimension, value?value:"<nothing>");
  139. if(value) {
  140. RRDDIM *rd = rrddim_find(st, dimension);
  141. if(unlikely(!rd)) {
  142. error("requested a SET to dimension with id '%s' on stats '%s' (%s) on host '%s', which does not exist. Disabling it.", dimension, st->name, st->id, st->rrdhost->hostname);
  143. enabled = 0;
  144. break;
  145. }
  146. else
  147. rrddim_set_by_pointer(st, rd, strtoll(value, NULL, 0));
  148. }
  149. }
  150. else if(likely(hash == BEGIN_HASH && !strcmp(s, PLUGINSD_KEYWORD_BEGIN))) {
  151. char *id = words[1];
  152. char *microseconds_txt = words[2];
  153. if(unlikely(!id)) {
  154. error("requested a BEGIN without a chart id for host '%s'. Disabling it.", host->hostname);
  155. enabled = 0;
  156. break;
  157. }
  158. st = rrdset_find(host, id);
  159. if(unlikely(!st)) {
  160. error("requested a BEGIN on chart '%s', which does not exist on host '%s'. Disabling it.", id, host->hostname);
  161. enabled = 0;
  162. break;
  163. }
  164. if(likely(st->counter_done)) {
  165. usec_t microseconds = 0;
  166. if(microseconds_txt && *microseconds_txt) microseconds = str2ull(microseconds_txt);
  167. if(likely(microseconds)) {
  168. if(trust_durations)
  169. rrdset_next_usec_unfiltered(st, microseconds);
  170. else
  171. rrdset_next_usec(st, microseconds);
  172. }
  173. else rrdset_next(st);
  174. }
  175. }
  176. else if(likely(hash == END_HASH && !strcmp(s, PLUGINSD_KEYWORD_END))) {
  177. if(unlikely(!st)) {
  178. error("requested an END, without a BEGIN on host '%s'. Disabling it.", host->hostname);
  179. enabled = 0;
  180. break;
  181. }
  182. if(unlikely(rrdset_flag_check(st, RRDSET_FLAG_DEBUG)))
  183. debug(D_PLUGINSD, "requested an END on chart %s", st->id);
  184. rrdset_done(st);
  185. st = NULL;
  186. count++;
  187. }
  188. else if(likely(hash == CHART_HASH && !strcmp(s, PLUGINSD_KEYWORD_CHART))) {
  189. st = NULL;
  190. char *type = words[1];
  191. char *name = words[2];
  192. char *title = words[3];
  193. char *units = words[4];
  194. char *family = words[5];
  195. char *context = words[6];
  196. char *chart = words[7];
  197. char *priority_s = words[8];
  198. char *update_every_s = words[9];
  199. char *options = words[10];
  200. char *plugin = words[11];
  201. char *module = words[12];
  202. // parse the id from type
  203. char *id = NULL;
  204. if(likely(type && (id = strchr(type, '.')))) {
  205. *id = '\0';
  206. id++;
  207. }
  208. // make sure we have the required variables
  209. if(unlikely(!type || !*type || !id || !*id)) {
  210. error("requested a CHART, without a type.id, on host '%s'. Disabling it.", host->hostname);
  211. enabled = 0;
  212. break;
  213. }
  214. // parse the name, and make sure it does not include 'type.'
  215. if(unlikely(name && *name)) {
  216. // when data are coming from slaves
  217. // name will be type.name
  218. // so we have to remove 'type.' from name too
  219. size_t len = strlen(type);
  220. if(strncmp(type, name, len) == 0 && name[len] == '.')
  221. name = &name[len + 1];
  222. // if the name is the same with the id,
  223. // or is just 'NULL', clear it.
  224. if(unlikely(strcmp(name, id) == 0 || strcasecmp(name, "NULL") == 0 || strcasecmp(name, "(NULL)") == 0))
  225. name = NULL;
  226. }
  227. int priority = 1000;
  228. if(likely(priority_s && *priority_s)) priority = str2i(priority_s);
  229. int update_every = cd->update_every;
  230. if(likely(update_every_s && *update_every_s)) update_every = str2i(update_every_s);
  231. if(unlikely(!update_every)) update_every = cd->update_every;
  232. RRDSET_TYPE chart_type = RRDSET_TYPE_LINE;
  233. if(unlikely(chart)) chart_type = rrdset_type_id(chart);
  234. if(unlikely(name && !*name)) name = NULL;
  235. if(unlikely(family && !*family)) family = NULL;
  236. if(unlikely(context && !*context)) context = NULL;
  237. if(unlikely(!title)) title = "";
  238. if(unlikely(!units)) units = "unknown";
  239. debug(D_PLUGINSD, "creating chart type='%s', id='%s', name='%s', family='%s', context='%s', chart='%s', priority=%d, update_every=%d"
  240. , type, id
  241. , name?name:""
  242. , family?family:""
  243. , context?context:""
  244. , rrdset_type_name(chart_type)
  245. , priority
  246. , update_every
  247. );
  248. st = rrdset_create(
  249. host
  250. , type
  251. , id
  252. , name
  253. , family
  254. , context
  255. , title
  256. , units
  257. , (plugin && *plugin)?plugin:cd->filename
  258. , module
  259. , priority
  260. , update_every
  261. , chart_type
  262. );
  263. if(options && *options) {
  264. if(strstr(options, "obsolete"))
  265. rrdset_is_obsolete(st);
  266. else
  267. rrdset_isnot_obsolete(st);
  268. if(strstr(options, "detail"))
  269. rrdset_flag_set(st, RRDSET_FLAG_DETAIL);
  270. else
  271. rrdset_flag_clear(st, RRDSET_FLAG_DETAIL);
  272. if(strstr(options, "hidden"))
  273. rrdset_flag_set(st, RRDSET_FLAG_HIDDEN);
  274. else
  275. rrdset_flag_clear(st, RRDSET_FLAG_HIDDEN);
  276. if(strstr(options, "store_first"))
  277. rrdset_flag_set(st, RRDSET_FLAG_STORE_FIRST);
  278. else
  279. rrdset_flag_clear(st, RRDSET_FLAG_STORE_FIRST);
  280. }
  281. else {
  282. rrdset_isnot_obsolete(st);
  283. rrdset_flag_clear(st, RRDSET_FLAG_DETAIL);
  284. rrdset_flag_clear(st, RRDSET_FLAG_STORE_FIRST);
  285. }
  286. }
  287. else if(likely(hash == DIMENSION_HASH && !strcmp(s, PLUGINSD_KEYWORD_DIMENSION))) {
  288. char *id = words[1];
  289. char *name = words[2];
  290. char *algorithm = words[3];
  291. char *multiplier_s = words[4];
  292. char *divisor_s = words[5];
  293. char *options = words[6];
  294. if(unlikely(!id || !*id)) {
  295. error("requested a DIMENSION, without an id, host '%s' and chart '%s'. Disabling it.", host->hostname, st?st->id:"UNSET");
  296. enabled = 0;
  297. break;
  298. }
  299. if(unlikely(!st)) {
  300. error("requested a DIMENSION, without a CHART, on host '%s'. Disabling it.", host->hostname);
  301. enabled = 0;
  302. break;
  303. }
  304. long multiplier = 1;
  305. if(multiplier_s && *multiplier_s) multiplier = strtol(multiplier_s, NULL, 0);
  306. if(unlikely(!multiplier)) multiplier = 1;
  307. long divisor = 1;
  308. if(likely(divisor_s && *divisor_s)) divisor = strtol(divisor_s, NULL, 0);
  309. if(unlikely(!divisor)) divisor = 1;
  310. if(unlikely(!algorithm || !*algorithm)) algorithm = "absolute";
  311. if(unlikely(rrdset_flag_check(st, RRDSET_FLAG_DEBUG)))
  312. debug(D_PLUGINSD, "creating dimension in chart %s, id='%s', name='%s', algorithm='%s', multiplier=%ld, divisor=%ld, hidden='%s'"
  313. , st->id
  314. , id
  315. , name?name:""
  316. , rrd_algorithm_name(rrd_algorithm_id(algorithm))
  317. , multiplier
  318. , divisor
  319. , options?options:""
  320. );
  321. RRDDIM *rd = rrddim_add(st, id, name, multiplier, divisor, rrd_algorithm_id(algorithm));
  322. rrddim_flag_clear(rd, RRDDIM_FLAG_HIDDEN);
  323. rrddim_flag_clear(rd, RRDDIM_FLAG_DONT_DETECT_RESETS_OR_OVERFLOWS);
  324. if(options && *options) {
  325. if(strstr(options, "hidden") != NULL) rrddim_flag_set(rd, RRDDIM_FLAG_HIDDEN);
  326. if(strstr(options, "noreset") != NULL) rrddim_flag_set(rd, RRDDIM_FLAG_DONT_DETECT_RESETS_OR_OVERFLOWS);
  327. if(strstr(options, "nooverflow") != NULL) rrddim_flag_set(rd, RRDDIM_FLAG_DONT_DETECT_RESETS_OR_OVERFLOWS);
  328. }
  329. }
  330. else if(likely(hash == VARIABLE_HASH && !strcmp(s, PLUGINSD_KEYWORD_VARIABLE))) {
  331. char *name = words[1];
  332. char *value = words[2];
  333. int global = (st)?0:1;
  334. if(name && *name) {
  335. if((strcmp(name, "GLOBAL") == 0 || strcmp(name, "HOST") == 0)) {
  336. global = 1;
  337. name = words[2];
  338. value = words[3];
  339. }
  340. else if((strcmp(name, "LOCAL") == 0 || strcmp(name, "CHART") == 0)) {
  341. global = 0;
  342. name = words[2];
  343. value = words[3];
  344. }
  345. }
  346. if(unlikely(!name || !*name)) {
  347. error("requested a VARIABLE on host '%s', without a variable name. Disabling it.", host->hostname);
  348. enabled = 0;
  349. break;
  350. }
  351. if(unlikely(!value || !*value))
  352. value = NULL;
  353. if(value) {
  354. char *endptr = NULL;
  355. calculated_number v = (calculated_number)str2ld(value, &endptr);
  356. if(unlikely(endptr && *endptr)) {
  357. if(endptr == value)
  358. error("the value '%s' of VARIABLE '%s' on host '%s' cannot be parsed as a number", value, name, host->hostname);
  359. else
  360. error("the value '%s' of VARIABLE '%s' on host '%s' has leftovers: '%s'", value, name, host->hostname, endptr);
  361. }
  362. if(global) {
  363. RRDVAR *rv = rrdvar_custom_host_variable_create(host, name);
  364. if (rv) rrdvar_custom_host_variable_set(host, rv, v);
  365. else error("cannot find/create HOST VARIABLE '%s' on host '%s'", name, host->hostname);
  366. }
  367. else if(st) {
  368. RRDSETVAR *rs = rrdsetvar_custom_chart_variable_create(st, name);
  369. if (rs) rrdsetvar_custom_chart_variable_set(rs, v);
  370. else error("cannot find/create CHART VARIABLE '%s' on host '%s', chart '%s'", name, host->hostname, st->id);
  371. }
  372. else
  373. error("cannot find/create CHART VARIABLE '%s' on host '%s' without a chart", name, host->hostname);
  374. }
  375. else
  376. error("cannot set %s VARIABLE '%s' on host '%s' to an empty value", (global)?"HOST":"CHART", name, host->hostname);
  377. }
  378. else if(likely(hash == FLUSH_HASH && !strcmp(s, PLUGINSD_KEYWORD_FLUSH))) {
  379. debug(D_PLUGINSD, "requested a FLUSH");
  380. st = NULL;
  381. }
  382. else if(unlikely(hash == DISABLE_HASH && !strcmp(s, PLUGINSD_KEYWORD_DISABLE))) {
  383. info("called DISABLE. Disabling it.");
  384. enabled = 0;
  385. break;
  386. }
  387. else {
  388. error("sent command '%s' which is not known by netdata, for host '%s'. Disabling it.", s, host->hostname);
  389. enabled = 0;
  390. break;
  391. }
  392. }
  393. cleanup:
  394. cd->enabled = enabled;
  395. if(likely(count)) {
  396. cd->successful_collections += count;
  397. cd->serial_failures = 0;
  398. }
  399. else
  400. cd->serial_failures++;
  401. return count;
  402. }
  403. static void pluginsd_worker_thread_cleanup(void *arg) {
  404. struct plugind *cd = (struct plugind *)arg;
  405. if(cd->enabled && !cd->obsolete) {
  406. cd->obsolete = 1;
  407. info("data collection thread exiting");
  408. if (cd->pid) {
  409. siginfo_t info;
  410. info("killing child process pid %d", cd->pid);
  411. if (killpid(cd->pid, SIGTERM) != -1) {
  412. info("waiting for child process pid %d to exit...", cd->pid);
  413. waitid(P_PID, (id_t) cd->pid, &info, WEXITED);
  414. }
  415. cd->pid = 0;
  416. }
  417. }
  418. }
  419. void *pluginsd_worker_thread(void *arg) {
  420. netdata_thread_cleanup_push(pluginsd_worker_thread_cleanup, arg);
  421. struct plugind *cd = (struct plugind *)arg;
  422. cd->obsolete = 0;
  423. size_t count = 0;
  424. while(!netdata_exit) {
  425. FILE *fp = mypopen(cd->cmd, &cd->pid);
  426. if(unlikely(!fp)) {
  427. error("Cannot popen(\"%s\", \"r\").", cd->cmd);
  428. break;
  429. }
  430. info("connected to '%s' running on pid %d", cd->fullfilename, cd->pid);
  431. count = pluginsd_process(localhost, cd, fp, 0);
  432. error("'%s' (pid %d) disconnected after %zu successful data collections (ENDs).", cd->fullfilename, cd->pid, count);
  433. killpid(cd->pid, SIGTERM);
  434. // get the return code
  435. int code = mypclose(fp, cd->pid);
  436. if(code != 0) {
  437. // the plugin reports failure
  438. if(likely(!cd->successful_collections)) {
  439. // nothing collected - disable it
  440. error("'%s' (pid %d) exited with error code %d. Disabling it.", cd->fullfilename, cd->pid, code);
  441. cd->enabled = 0;
  442. }
  443. else {
  444. // we have collected something
  445. if(likely(cd->serial_failures <= 10)) {
  446. error("'%s' (pid %d) exited with error code %d, but has given useful output in the past (%zu times). %s", cd->fullfilename, cd->pid, code, cd->successful_collections, cd->enabled?"Waiting a bit before starting it again.":"Will not start it again - it is disabled.");
  447. sleep((unsigned int) (cd->update_every * 10));
  448. }
  449. else {
  450. error("'%s' (pid %d) exited with error code %d, but has given useful output in the past (%zu times). We tried %zu times to restart it, but it failed to generate data. Disabling it.", cd->fullfilename, cd->pid, code, cd->successful_collections, cd->serial_failures);
  451. cd->enabled = 0;
  452. }
  453. }
  454. }
  455. else {
  456. // the plugin reports success
  457. if(unlikely(!cd->successful_collections)) {
  458. // we have collected nothing so far
  459. if(likely(cd->serial_failures <= 10)) {
  460. error("'%s' (pid %d) does not generate useful output but it reports success (exits with 0). %s.", cd->fullfilename, cd->pid, cd->enabled?"Waiting a bit before starting it again.":"Will not start it again - it is now disabled.");
  461. sleep((unsigned int) (cd->update_every * 10));
  462. }
  463. else {
  464. error("'%s' (pid %d) does not generate useful output, although it reports success (exits with 0), but we have tried %zu times to collect something. Disabling it.", cd->fullfilename, cd->pid, cd->serial_failures);
  465. cd->enabled = 0;
  466. }
  467. }
  468. else
  469. sleep((unsigned int) cd->update_every);
  470. }
  471. cd->pid = 0;
  472. if(unlikely(!cd->enabled)) break;
  473. }
  474. netdata_thread_cleanup_pop(1);
  475. return NULL;
  476. }
  477. static void pluginsd_main_cleanup(void *data) {
  478. struct netdata_static_thread *static_thread = (struct netdata_static_thread *)data;
  479. static_thread->enabled = NETDATA_MAIN_THREAD_EXITING;
  480. info("cleaning up...");
  481. struct plugind *cd;
  482. for (cd = pluginsd_root; cd; cd = cd->next) {
  483. if (cd->enabled && !cd->obsolete) {
  484. info("stopping plugin thread: %s", cd->id);
  485. netdata_thread_cancel(cd->thread);
  486. }
  487. }
  488. info("cleanup completed.");
  489. static_thread->enabled = NETDATA_MAIN_THREAD_EXITED;
  490. }
  491. void *pluginsd_main(void *ptr) {
  492. netdata_thread_cleanup_push(pluginsd_main_cleanup, ptr);
  493. int automatic_run = config_get_boolean(CONFIG_SECTION_PLUGINS, "enable running new plugins", 1);
  494. int scan_frequency = (int) config_get_number(CONFIG_SECTION_PLUGINS, "check for new plugins every", 60);
  495. if(scan_frequency < 1) scan_frequency = 1;
  496. // store the errno for each plugins directory
  497. // so that we don't log broken directories on each loop
  498. int directory_errors[PLUGINSD_MAX_DIRECTORIES] = { 0 };
  499. while(!netdata_exit) {
  500. int idx;
  501. const char *directory_name;
  502. for( idx = 0; idx < PLUGINSD_MAX_DIRECTORIES && (directory_name = plugin_directories[idx]) ; idx++ ) {
  503. if(unlikely(netdata_exit)) break;
  504. errno = 0;
  505. DIR *dir = opendir(directory_name);
  506. if(unlikely(!dir)) {
  507. if(directory_errors[idx] != errno) {
  508. directory_errors[idx] = errno;
  509. error("cannot open plugins directory '%s'", directory_name);
  510. }
  511. continue;
  512. }
  513. struct dirent *file = NULL;
  514. while(likely((file = readdir(dir)))) {
  515. if(unlikely(netdata_exit)) break;
  516. debug(D_PLUGINSD, "examining file '%s'", file->d_name);
  517. if(unlikely(strcmp(file->d_name, ".") == 0 || strcmp(file->d_name, "..") == 0)) continue;
  518. int len = (int) strlen(file->d_name);
  519. if(unlikely(len <= (int)PLUGINSD_FILE_SUFFIX_LEN)) continue;
  520. if(unlikely(strcmp(PLUGINSD_FILE_SUFFIX, &file->d_name[len - (int)PLUGINSD_FILE_SUFFIX_LEN]) != 0)) {
  521. debug(D_PLUGINSD, "file '%s' does not end in '%s'", file->d_name, PLUGINSD_FILE_SUFFIX);
  522. continue;
  523. }
  524. char pluginname[CONFIG_MAX_NAME + 1];
  525. snprintfz(pluginname, CONFIG_MAX_NAME, "%.*s", (int)(len - PLUGINSD_FILE_SUFFIX_LEN), file->d_name);
  526. int enabled = config_get_boolean(CONFIG_SECTION_PLUGINS, pluginname, automatic_run);
  527. if(unlikely(!enabled)) {
  528. debug(D_PLUGINSD, "plugin '%s' is not enabled", file->d_name);
  529. continue;
  530. }
  531. // check if it runs already
  532. struct plugind *cd;
  533. for(cd = pluginsd_root ; cd ; cd = cd->next)
  534. if(unlikely(strcmp(cd->filename, file->d_name) == 0)) break;
  535. if(likely(cd && !cd->obsolete)) {
  536. debug(D_PLUGINSD, "plugin '%s' is already running", cd->filename);
  537. continue;
  538. }
  539. // it is not running
  540. // allocate a new one, or use the obsolete one
  541. if(unlikely(!cd)) {
  542. cd = callocz(sizeof(struct plugind), 1);
  543. snprintfz(cd->id, CONFIG_MAX_NAME, "plugin:%s", pluginname);
  544. strncpyz(cd->filename, file->d_name, FILENAME_MAX);
  545. snprintfz(cd->fullfilename, FILENAME_MAX, "%s/%s", directory_name, cd->filename);
  546. cd->enabled = enabled;
  547. cd->update_every = (int) config_get_number(cd->id, "update every", localhost->rrd_update_every);
  548. cd->started_t = now_realtime_sec();
  549. char *def = "";
  550. snprintfz(cd->cmd, PLUGINSD_CMD_MAX, "exec %s %d %s", cd->fullfilename, cd->update_every, config_get(cd->id, "command options", def));
  551. // link it
  552. if(likely(pluginsd_root)) cd->next = pluginsd_root;
  553. pluginsd_root = cd;
  554. // it is not currently running
  555. cd->obsolete = 1;
  556. if(cd->enabled) {
  557. char tag[NETDATA_THREAD_TAG_MAX + 1];
  558. snprintfz(tag, NETDATA_THREAD_TAG_MAX, "PLUGINSD[%s]", pluginname);
  559. // spawn a new thread for it
  560. netdata_thread_create(&cd->thread, tag, NETDATA_THREAD_OPTION_DEFAULT, pluginsd_worker_thread, cd);
  561. }
  562. }
  563. }
  564. closedir(dir);
  565. }
  566. sleep((unsigned int) scan_frequency);
  567. }
  568. netdata_thread_cleanup_pop(1);
  569. return NULL;
  570. }