ebpf_functions.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. // SPDX-License-Identifier: GPL-3.0-or-later
  2. #include "ebpf.h"
  3. #include "ebpf_functions.h"
  4. /*****************************************************************
  5. * EBPF SELECT MODULE
  6. *****************************************************************/
  7. /**
  8. * Select Module
  9. *
  10. * @param thread_name name of the thread we are looking for.
  11. *
  12. * @return it returns a pointer for the module that has thread_name on success or NULL otherwise.
  13. */
  14. ebpf_module_t *ebpf_functions_select_module(const char *thread_name) {
  15. int i;
  16. for (i = 0; i < EBPF_MODULE_FUNCTION_IDX; i++) {
  17. if (strcmp(ebpf_modules[i].thread_name, thread_name) == 0) {
  18. return &ebpf_modules[i];
  19. }
  20. }
  21. return NULL;
  22. }
  23. /*****************************************************************
  24. * EBPF HELP FUNCTIONS
  25. *****************************************************************/
  26. /**
  27. * Thread Help
  28. *
  29. * Shows help with all options accepted by thread function.
  30. *
  31. * @param transaction the transaction id that Netdata sent for this function execution
  32. */
  33. static void ebpf_function_thread_manipulation_help(const char *transaction) {
  34. pthread_mutex_lock(&lock);
  35. pluginsd_function_result_begin_to_stdout(transaction, HTTP_RESP_OK, "text/plain", now_realtime_sec() + 3600);
  36. fprintf(stdout, "%s",
  37. "ebpf.plugin / thread\n"
  38. "\n"
  39. "Function `thread` allows user to control eBPF threads.\n"
  40. "\n"
  41. "The following filters are supported:\n"
  42. "\n"
  43. " thread:NAME\n"
  44. " Shows information for the thread NAME. Names are listed inside `ebpf.d.conf`.\n"
  45. "\n"
  46. " enable:NAME:PERIOD\n"
  47. " Enable a specific thread named `NAME` to run a specific PERIOD in seconds. When PERIOD is not\n"
  48. " specified plugin will use the default 300 seconds\n"
  49. "\n"
  50. " disable:NAME\n"
  51. " Disable a sp.\n"
  52. "\n"
  53. "Filters can be combined. Each filter can be given only one time.\n"
  54. "Process thread is not controlled by functions until we finish the creation of functions per thread..\n"
  55. );
  56. pluginsd_function_result_end_to_stdout();
  57. fflush(stdout);
  58. pthread_mutex_unlock(&lock);
  59. }
  60. /*****************************************************************
  61. * EBPF ERROR FUNCTIONS
  62. *****************************************************************/
  63. /**
  64. * Function error
  65. *
  66. * Show error when a wrong function is given
  67. *
  68. * @param transaction the transaction id that Netdata sent for this function execution
  69. * @param code the error code to show with the message.
  70. * @param msg the error message
  71. */
  72. static void ebpf_function_error(const char *transaction, int code, const char *msg) {
  73. char buffer[PLUGINSD_LINE_MAX + 1];
  74. json_escape_string(buffer, msg, PLUGINSD_LINE_MAX);
  75. pluginsd_function_result_begin_to_stdout(transaction, code, "application/json", now_realtime_sec());
  76. fprintf(stdout, "{\"status\":%d,\"error_message\":\"%s\"}", code, buffer);
  77. pluginsd_function_result_end_to_stdout();
  78. }
  79. /*****************************************************************
  80. * EBPF THREAD FUNCTION
  81. *****************************************************************/
  82. /**
  83. * Function enable
  84. *
  85. * Enable a specific thread.
  86. *
  87. * @param transaction the transaction id that Netdata sent for this function execution
  88. * @param function function name and arguments given to thread.
  89. * @param line_buffer buffer used to parse args
  90. * @param line_max Number of arguments given
  91. * @param timeout The function timeout
  92. * @param em The structure with thread information
  93. */
  94. static void ebpf_function_thread_manipulation(const char *transaction,
  95. char *function __maybe_unused,
  96. char *line_buffer __maybe_unused,
  97. int line_max __maybe_unused,
  98. int timeout __maybe_unused,
  99. ebpf_module_t *em)
  100. {
  101. char *words[PLUGINSD_MAX_WORDS] = { NULL };
  102. char message[512];
  103. uint32_t show_specific_thread = 0;
  104. size_t num_words = quoted_strings_splitter_pluginsd(function, words, PLUGINSD_MAX_WORDS);
  105. for(int i = 1; i < PLUGINSD_MAX_WORDS ;i++) {
  106. const char *keyword = get_word(words, num_words, i);
  107. if (!keyword)
  108. break;
  109. ebpf_module_t *lem;
  110. if(strncmp(keyword, EBPF_THREADS_ENABLE_CATEGORY, sizeof(EBPF_THREADS_ENABLE_CATEGORY) -1) == 0) {
  111. char thread_name[128];
  112. int period = -1;
  113. const char *name = &keyword[sizeof(EBPF_THREADS_ENABLE_CATEGORY) - 1];
  114. char *separator = strchr(name, ':');
  115. if (separator) {
  116. strncpyz(thread_name, name, separator - name);
  117. period = str2i(++separator);
  118. } else {
  119. strncpyz(thread_name, name, strlen(name));
  120. }
  121. lem = ebpf_functions_select_module(thread_name);
  122. if (!lem) {
  123. snprintfz(message, 511, "%s%s", EBPF_PLUGIN_THREAD_FUNCTION_ERROR_THREAD_NOT_FOUND, name);
  124. ebpf_function_error(transaction, HTTP_RESP_NOT_FOUND, message);
  125. return;
  126. }
  127. pthread_mutex_lock(&ebpf_exit_cleanup);
  128. if (lem->enabled > NETDATA_THREAD_EBPF_FUNCTION_RUNNING) {
  129. struct netdata_static_thread *st = lem->thread;
  130. // Load configuration again
  131. ebpf_update_module(lem, default_btf, running_on_kernel, isrh);
  132. // another request for thread that already ran, cleanup and restart
  133. if (st->thread)
  134. freez(st->thread);
  135. if (period <= 0)
  136. period = EBPF_DEFAULT_LIFETIME;
  137. st->thread = mallocz(sizeof(netdata_thread_t));
  138. lem->enabled = NETDATA_THREAD_EBPF_FUNCTION_RUNNING;
  139. lem->lifetime = period;
  140. #ifdef NETDATA_INTERNAL_CHECKS
  141. netdata_log_info("Starting thread %s with lifetime = %d", thread_name, period);
  142. #endif
  143. netdata_thread_create(st->thread, st->name, NETDATA_THREAD_OPTION_DEFAULT,
  144. st->start_routine, lem);
  145. } else {
  146. lem->running_time = 0;
  147. if (period > 0) // user is modifying period to run
  148. lem->lifetime = period;
  149. #ifdef NETDATA_INTERNAL_CHECKS
  150. netdata_log_info("Thread %s had lifetime updated for %d", thread_name, period);
  151. #endif
  152. }
  153. pthread_mutex_unlock(&ebpf_exit_cleanup);
  154. } else if(strncmp(keyword, EBPF_THREADS_DISABLE_CATEGORY, sizeof(EBPF_THREADS_DISABLE_CATEGORY) -1) == 0) {
  155. const char *name = &keyword[sizeof(EBPF_THREADS_DISABLE_CATEGORY) - 1];
  156. lem = ebpf_functions_select_module(name);
  157. if (!lem) {
  158. snprintfz(message, 511, "%s%s", EBPF_PLUGIN_THREAD_FUNCTION_ERROR_THREAD_NOT_FOUND, name);
  159. ebpf_function_error(transaction, HTTP_RESP_NOT_FOUND, message);
  160. return;
  161. }
  162. pthread_mutex_lock(&ebpf_exit_cleanup);
  163. if (lem->enabled < NETDATA_THREAD_EBPF_STOPPING && lem->thread->thread) {
  164. lem->lifetime = 0;
  165. lem->running_time = lem->update_every;
  166. netdata_thread_cancel(*lem->thread->thread);
  167. }
  168. pthread_mutex_unlock(&ebpf_exit_cleanup);
  169. } else if(strncmp(keyword, EBPF_THREADS_SELECT_THREAD, sizeof(EBPF_THREADS_SELECT_THREAD) -1) == 0) {
  170. const char *name = &keyword[sizeof(EBPF_THREADS_SELECT_THREAD) - 1];
  171. lem = ebpf_functions_select_module(name);
  172. if (!lem) {
  173. snprintfz(message, 511, "%s%s", EBPF_PLUGIN_THREAD_FUNCTION_ERROR_THREAD_NOT_FOUND, name);
  174. ebpf_function_error(transaction, HTTP_RESP_NOT_FOUND, message);
  175. return;
  176. }
  177. show_specific_thread |= 1<<lem->thread_id;
  178. } else if(strncmp(keyword, "help", 4) == 0) {
  179. ebpf_function_thread_manipulation_help(transaction);
  180. return;
  181. }
  182. }
  183. time_t expires = now_realtime_sec() + em->update_every;
  184. BUFFER *wb = buffer_create(PLUGINSD_LINE_MAX, NULL);
  185. buffer_json_initialize(wb, "\"", "\"", 0, true, false);
  186. buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK);
  187. buffer_json_member_add_string(wb, "type", "table");
  188. buffer_json_member_add_time_t(wb, "update_every", em->update_every);
  189. buffer_json_member_add_string(wb, "help", EBPF_PLUGIN_THREAD_FUNCTION_DESCRIPTION);
  190. // Collect data
  191. buffer_json_member_add_array(wb, "data");
  192. int i;
  193. for (i = 0; i < EBPF_MODULE_FUNCTION_IDX; i++) {
  194. if (show_specific_thread && !(show_specific_thread & 1<<i))
  195. continue;
  196. ebpf_module_t *wem = &ebpf_modules[i];
  197. buffer_json_add_array_item_array(wb);
  198. // IMPORTANT!
  199. // THE ORDER SHOULD BE THE SAME WITH THE FIELDS!
  200. // thread name
  201. buffer_json_add_array_item_string(wb, wem->thread_name);
  202. // description
  203. buffer_json_add_array_item_string(wb, wem->thread_description);
  204. // Either it is not running or received a disabled signal and it is stopping.
  205. if (wem->enabled > NETDATA_THREAD_EBPF_FUNCTION_RUNNING ||
  206. (!wem->lifetime && (int)wem->running_time == wem->update_every)) {
  207. // status
  208. buffer_json_add_array_item_string(wb, EBPF_THREAD_STATUS_STOPPED);
  209. // Time remaining
  210. buffer_json_add_array_item_uint64(wb, 0);
  211. // action
  212. buffer_json_add_array_item_string(wb, "NULL");
  213. } else {
  214. // status
  215. buffer_json_add_array_item_string(wb, EBPF_THREAD_STATUS_RUNNING);
  216. // Time remaining
  217. buffer_json_add_array_item_uint64(wb, (wem->lifetime) ? (wem->lifetime - wem->running_time) : 0);
  218. // action
  219. buffer_json_add_array_item_string(wb, "Enabled/Disabled");
  220. }
  221. buffer_json_array_close(wb);
  222. }
  223. buffer_json_array_close(wb); // data
  224. buffer_json_member_add_object(wb, "columns");
  225. {
  226. int fields_id = 0;
  227. // IMPORTANT!
  228. // THE ORDER SHOULD BE THE SAME WITH THE VALUES!
  229. buffer_rrdf_table_add_field(wb, fields_id++, "Thread", "Thread Name", RRDF_FIELD_TYPE_STRING,
  230. RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, 0, NULL, NAN,
  231. RRDF_FIELD_SORT_ASCENDING, NULL, RRDF_FIELD_SUMMARY_COUNT,
  232. RRDF_FIELD_FILTER_MULTISELECT,
  233. RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_STICKY, NULL);
  234. buffer_rrdf_table_add_field(wb, fields_id++, "Description", "Thread Desc", RRDF_FIELD_TYPE_STRING,
  235. RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, 0, NULL, NAN,
  236. RRDF_FIELD_SORT_ASCENDING, NULL, RRDF_FIELD_SUMMARY_COUNT,
  237. RRDF_FIELD_FILTER_MULTISELECT,
  238. RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_STICKY, NULL);
  239. buffer_rrdf_table_add_field(wb, fields_id++, "Status", "Thread Status", RRDF_FIELD_TYPE_STRING,
  240. RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, 0, NULL, NAN,
  241. RRDF_FIELD_SORT_ASCENDING, NULL, RRDF_FIELD_SUMMARY_COUNT,
  242. RRDF_FIELD_FILTER_MULTISELECT,
  243. RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_STICKY, NULL);
  244. buffer_rrdf_table_add_field(wb, fields_id++, "Time", "Time Remaining", RRDF_FIELD_TYPE_INTEGER,
  245. RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER, 0, NULL,
  246. NAN, RRDF_FIELD_SORT_ASCENDING, NULL, RRDF_FIELD_SUMMARY_COUNT,
  247. RRDF_FIELD_FILTER_MULTISELECT,
  248. RRDF_FIELD_OPTS_NONE, NULL);
  249. buffer_rrdf_table_add_field(wb, fields_id++, "Action", "Thread Action", RRDF_FIELD_TYPE_STRING,
  250. RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, 0, NULL, NAN,
  251. RRDF_FIELD_SORT_ASCENDING, NULL, RRDF_FIELD_SUMMARY_COUNT,
  252. RRDF_FIELD_FILTER_MULTISELECT,
  253. RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_STICKY, NULL);
  254. }
  255. buffer_json_object_close(wb); // columns
  256. buffer_json_member_add_string(wb, "default_sort_column", "Thread");
  257. buffer_json_member_add_object(wb, "charts");
  258. {
  259. // Threads
  260. buffer_json_member_add_object(wb, "eBPFThreads");
  261. {
  262. buffer_json_member_add_string(wb, "name", "Threads");
  263. buffer_json_member_add_string(wb, "type", "line");
  264. buffer_json_member_add_array(wb, "columns");
  265. {
  266. buffer_json_add_array_item_string(wb, "Threads");
  267. }
  268. buffer_json_array_close(wb);
  269. }
  270. buffer_json_object_close(wb);
  271. // Life Time
  272. buffer_json_member_add_object(wb, "eBPFLifeTime");
  273. {
  274. buffer_json_member_add_string(wb, "name", "LifeTime");
  275. buffer_json_member_add_string(wb, "type", "line");
  276. buffer_json_member_add_array(wb, "columns");
  277. {
  278. buffer_json_add_array_item_string(wb, "Threads");
  279. buffer_json_add_array_item_string(wb, "Time");
  280. }
  281. buffer_json_array_close(wb);
  282. }
  283. buffer_json_object_close(wb);
  284. }
  285. buffer_json_object_close(wb); // charts
  286. // Do we use only on fields that can be groupped?
  287. buffer_json_member_add_object(wb, "group_by");
  288. {
  289. // group by Status
  290. buffer_json_member_add_object(wb, "Status");
  291. {
  292. buffer_json_member_add_string(wb, "name", "Thread status");
  293. buffer_json_member_add_array(wb, "columns");
  294. {
  295. buffer_json_add_array_item_string(wb, "Status");
  296. }
  297. buffer_json_array_close(wb);
  298. }
  299. buffer_json_object_close(wb);
  300. }
  301. buffer_json_object_close(wb); // group_by
  302. buffer_json_member_add_time_t(wb, "expires", expires);
  303. buffer_json_finalize(wb);
  304. // Lock necessary to avoid race condition
  305. pthread_mutex_lock(&lock);
  306. pluginsd_function_result_begin_to_stdout(transaction, HTTP_RESP_OK, "application/json", expires);
  307. fwrite(buffer_tostring(wb), buffer_strlen(wb), 1, stdout);
  308. pluginsd_function_result_end_to_stdout();
  309. fflush(stdout);
  310. pthread_mutex_unlock(&lock);
  311. buffer_free(wb);
  312. }
  313. /*****************************************************************
  314. * EBPF FUNCTION THREAD
  315. *****************************************************************/
  316. /**
  317. * FUNCTION thread.
  318. *
  319. * @param ptr a `ebpf_module_t *`.
  320. *
  321. * @return always NULL.
  322. */
  323. void *ebpf_function_thread(void *ptr)
  324. {
  325. ebpf_module_t *em = (ebpf_module_t *)ptr;
  326. char buffer[PLUGINSD_LINE_MAX + 1];
  327. char *s = NULL;
  328. while(!ebpf_exit_plugin && (s = fgets(buffer, PLUGINSD_LINE_MAX, stdin))) {
  329. char *words[PLUGINSD_MAX_WORDS] = { NULL };
  330. size_t num_words = quoted_strings_splitter_pluginsd(buffer, words, PLUGINSD_MAX_WORDS);
  331. const char *keyword = get_word(words, num_words, 0);
  332. if(keyword && strcmp(keyword, PLUGINSD_KEYWORD_FUNCTION) == 0) {
  333. char *transaction = get_word(words, num_words, 1);
  334. char *timeout_s = get_word(words, num_words, 2);
  335. char *function = get_word(words, num_words, 3);
  336. if(!transaction || !*transaction || !timeout_s || !*timeout_s || !function || !*function) {
  337. netdata_log_error("Received incomplete %s (transaction = '%s', timeout = '%s', function = '%s'). Ignoring it.",
  338. keyword,
  339. transaction?transaction:"(unset)",
  340. timeout_s?timeout_s:"(unset)",
  341. function?function:"(unset)");
  342. }
  343. else {
  344. int timeout = str2i(timeout_s);
  345. if (!strncmp(function, EBPF_FUNCTION_THREAD, sizeof(EBPF_FUNCTION_THREAD) - 1))
  346. ebpf_function_thread_manipulation(transaction,
  347. function,
  348. buffer,
  349. PLUGINSD_LINE_MAX + 1,
  350. timeout,
  351. em);
  352. else
  353. ebpf_function_error(transaction,
  354. HTTP_RESP_NOT_FOUND,
  355. "No function with this name found in ebpf.plugin.");
  356. }
  357. }
  358. else
  359. netdata_log_error("Received unknown command: %s", keyword ? keyword : "(unset)");
  360. }
  361. return NULL;
  362. }