diff options
Diffstat (limited to 'collectors/ebpf.plugin/ebpf_process.c')
-rw-r--r-- | collectors/ebpf.plugin/ebpf_process.c | 514 |
1 files changed, 514 insertions, 0 deletions
diff --git a/collectors/ebpf.plugin/ebpf_process.c b/collectors/ebpf.plugin/ebpf_process.c new file mode 100644 index 0000000000..c7ccae27b0 --- /dev/null +++ b/collectors/ebpf.plugin/ebpf_process.c @@ -0,0 +1,514 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include <sys/resource.h> + +#include "ebpf.h" +#include "ebpf_process.h" + +/***************************************************************** + * + * GLOBAL VARIABLES + * + *****************************************************************/ + +static char *process_dimension_names[NETDATA_MAX_MONITOR_VECTOR] = { "open", "close", "delete", "read", "write", + "process", "task", "process", "thread" }; +static char *process_id_names[NETDATA_MAX_MONITOR_VECTOR] = { "do_sys_open", "__close_fd", "vfs_unlink", "vfs_read", "vfs_write", + "do_exit", "release_task", "_do_fork", "sys_clone" }; +static char *status[] = { "process", "zombie" }; + +static netdata_idx_t *process_hash_values = NULL; +static netdata_syscall_stat_t *process_aggregated_data = NULL; +static netdata_publish_syscall_t *process_publish_aggregated = NULL; + +static ebpf_functions_t process_functions; + +#ifndef STATIC +/** + * Pointers used when collector is dynamically linked + */ + +//Libbpf (It is necessary to have at least kernel 4.10) +static int (*bpf_map_lookup_elem)(int, const void *, void *); + +static int *map_fd = NULL; +/** + * End of the pointers + */ + #endif + +/***************************************************************** + * + * PROCESS DATA AND SEND TO NETDATA + * + *****************************************************************/ + +/** + * Update publish structure before to send data to Netdata. + * + * @param publish the first output structure with independent dimensions + * @param pvc the second output structure with correlated dimensions + * @param input the structure with the input data. + */ +static void ebpf_update_publish(netdata_publish_syscall_t *publish, + netdata_publish_vfs_common_t *pvc, + netdata_syscall_stat_t *input) { + + netdata_publish_syscall_t *move = publish; + while(move) { + if(input->call != move->pcall) { + //This condition happens to avoid initial values with dimensions higher than normal values. + if(move->pcall) { + move->ncall = (input->call > move->pcall)?input->call - move->pcall: move->pcall - input->call; + move->nbyte = (input->bytes > move->pbyte)?input->bytes - move->pbyte: move->pbyte - input->bytes; + move->nerr = (input->ecall > move->nerr)?input->ecall - move->perr: move->perr - input->ecall; + } else { + move->ncall = 0; + move->nbyte = 0; + move->nerr = 0; + } + + move->pcall = input->call; + move->pbyte = input->bytes; + move->perr = input->ecall; + } else { + move->ncall = 0; + move->nbyte = 0; + move->nerr = 0; + } + + input = input->next; + move = move->next; + } + + pvc->write = -((long)publish[2].nbyte); + pvc->read = (long)publish[3].nbyte; + + pvc->running = (long)publish[7].ncall - (long)publish[8].ncall; + publish[6].ncall = -publish[6].ncall; // release + pvc->zombie = (long)publish[5].ncall + (long)publish[6].ncall; +} + + +/** + * Call the necessary functions to create a chart. + * + * @param family the chart family + * @param move the pointer with the values that will be published + */ +static void write_status_chart(char *family, netdata_publish_vfs_common_t *pvc) { + write_begin_chart(family, NETDATA_PROCESS_STATUS_NAME); + + write_chart_dimension(status[0], (long long) pvc->running); + write_chart_dimension(status[1], (long long) pvc->zombie); + + printf("END\n"); +} + +/** + * Send data to Netdata calling auxiliar functions. + * + * @param em the structure with thread information + */ +static void ebpf_process_send_data(ebpf_module_t *em) { + netdata_publish_vfs_common_t pvc; + ebpf_update_publish(process_publish_aggregated, &pvc, process_aggregated_data); + + write_count_chart(NETDATA_FILE_OPEN_CLOSE_COUNT, NETDATA_EBPF_FAMILY, process_publish_aggregated, 2); + write_count_chart(NETDATA_VFS_FILE_CLEAN_COUNT, + NETDATA_EBPF_FAMILY, + &process_publish_aggregated[NETDATA_DEL_START], + 1); + write_count_chart(NETDATA_VFS_FILE_IO_COUNT, + NETDATA_EBPF_FAMILY, + &process_publish_aggregated[NETDATA_IN_START_BYTE], + 2); + write_count_chart(NETDATA_EXIT_SYSCALL, + NETDATA_EBPF_FAMILY, + &process_publish_aggregated[NETDATA_EXIT_START], + 2); + write_count_chart(NETDATA_PROCESS_SYSCALL, + NETDATA_EBPF_FAMILY, + &process_publish_aggregated[NETDATA_PROCESS_START], + 2); + + write_status_chart(NETDATA_EBPF_FAMILY, &pvc); + if(em->mode < MODE_ENTRY) { + write_err_chart(NETDATA_FILE_OPEN_ERR_COUNT, NETDATA_EBPF_FAMILY, process_publish_aggregated, 2); + write_err_chart(NETDATA_VFS_FILE_ERR_COUNT, + NETDATA_EBPF_FAMILY, + &process_publish_aggregated[2], + NETDATA_VFS_ERRORS); + write_err_chart(NETDATA_PROCESS_ERROR_NAME, + NETDATA_EBPF_FAMILY, + &process_publish_aggregated[NETDATA_PROCESS_START], + 2); + + write_io_chart(NETDATA_VFS_IO_FILE_BYTES, NETDATA_EBPF_FAMILY, process_id_names[3], + process_id_names[4], &pvc); + } +} + +/***************************************************************** + * + * READ INFORMATION FROM KERNEL RING + * + *****************************************************************/ + +/** + * Read the hash table and store data to allocated vectors. + */ +static void read_hash_global_tables() +{ + uint64_t idx; + netdata_idx_t res[NETDATA_GLOBAL_VECTOR]; + + netdata_idx_t *val = process_hash_values; + for (idx = 0; idx < NETDATA_GLOBAL_VECTOR; idx++) { + if(!bpf_map_lookup_elem(map_fd[1], &idx, val)) { + uint64_t total = 0; + int i; + int end = (running_on_kernel < NETDATA_KERNEL_V4_15)?1:ebpf_nprocs; + for (i = 0; i < end; i++) + total += val[i]; + + res[idx] = total; + } else { + res[idx] = 0; + } + } + + process_aggregated_data[0].call = res[NETDATA_KEY_CALLS_DO_SYS_OPEN]; + process_aggregated_data[1].call = res[NETDATA_KEY_CALLS_CLOSE_FD]; + process_aggregated_data[2].call = res[NETDATA_KEY_CALLS_VFS_UNLINK]; + process_aggregated_data[3].call = res[NETDATA_KEY_CALLS_VFS_READ] + res[NETDATA_KEY_CALLS_VFS_READV]; + process_aggregated_data[4].call = res[NETDATA_KEY_CALLS_VFS_WRITE] + res[NETDATA_KEY_CALLS_VFS_WRITEV]; + process_aggregated_data[5].call = res[NETDATA_KEY_CALLS_DO_EXIT]; + process_aggregated_data[6].call = res[NETDATA_KEY_CALLS_RELEASE_TASK]; + process_aggregated_data[7].call = res[NETDATA_KEY_CALLS_DO_FORK]; + process_aggregated_data[8].call = res[NETDATA_KEY_CALLS_SYS_CLONE]; + + process_aggregated_data[0].ecall = res[NETDATA_KEY_ERROR_DO_SYS_OPEN]; + process_aggregated_data[1].ecall = res[NETDATA_KEY_ERROR_CLOSE_FD]; + process_aggregated_data[2].ecall = res[NETDATA_KEY_ERROR_VFS_UNLINK]; + process_aggregated_data[3].ecall = res[NETDATA_KEY_ERROR_VFS_READ] + res[NETDATA_KEY_ERROR_VFS_READV]; + process_aggregated_data[4].ecall = res[NETDATA_KEY_ERROR_VFS_WRITE] + res[NETDATA_KEY_ERROR_VFS_WRITEV]; + process_aggregated_data[7].ecall = res[NETDATA_KEY_ERROR_DO_FORK]; + process_aggregated_data[8].ecall = res[NETDATA_KEY_ERROR_SYS_CLONE]; + + process_aggregated_data[2].bytes = (uint64_t)res[NETDATA_KEY_BYTES_VFS_WRITE] + + (uint64_t)res[NETDATA_KEY_BYTES_VFS_WRITEV]; + process_aggregated_data[3].bytes = (uint64_t)res[NETDATA_KEY_BYTES_VFS_READ] + + (uint64_t)res[NETDATA_KEY_BYTES_VFS_READV]; +} + +/***************************************************************** + * + * FUNCTIONS WITH THE MAIN LOOP + * + *****************************************************************/ + + +/** + * Main loop for this collector. + * + * @param step the number of microseconds used with heart beat + * @param em the structure with thread information + */ +static void process_collector(usec_t step, ebpf_module_t *em) +{ + heartbeat_t hb; + heartbeat_init(&hb); + while(!close_ebpf_plugin) { + usec_t dt = heartbeat_next(&hb, step); + (void)dt; + + read_hash_global_tables(); + + pthread_mutex_lock(&lock); + ebpf_process_send_data(em); + pthread_mutex_unlock(&lock); + + fflush(stdout); + } +} + +/***************************************************************** + * + * FUNCTIONS TO CREATE CHARTS + * + *****************************************************************/ + +/** + * Create IO chart + * + * @param family the chart family + * @param name the chart name + * @param axis the axis label + * @param web the group name used to attach the chart on dashaboard + * @param order the order number of the specified chart + */ +static void ebpf_create_io_chart(char *family, char *name, char *axis, char *web, int order) { + printf("CHART %s.%s '' '' '%s' '%s' '' line %d 1 ''\n" + , family + , name + , axis + , web + , order); + + printf("DIMENSION %s %s absolute 1 1\n", process_id_names[3], NETDATA_VFS_DIM_OUT_FILE_BYTES); + printf("DIMENSION %s %s absolute 1 1\n", process_id_names[4], NETDATA_VFS_DIM_IN_FILE_BYTES); +} + +/** + * Create process status chart + * + * @param family the chart family + * @param name the chart name + * @param axis the axis label + * @param web the group name used to attach the chart on dashaboard + * @param order the order number of the specified chart + */ +static void ebpf_process_status_chart(char *family, char *name, char *axis, char *web, int order) { + printf("CHART %s.%s '' '' '%s' '%s' '' line %d 1 ''\n" + , family + , name + , axis + , web + , order); + + printf("DIMENSION %s '' absolute 1 1\n", status[0]); + printf("DIMENSION %s '' absolute 1 1\n", status[1]); +} + +/** + * Create global charts + * + * Call ebpf_create_chart to create the charts for the collector. + * + * @param em a pointer to the structure with the default values. + */ +static void ebpf_create_global_charts(ebpf_module_t *em) { + ebpf_create_chart(NETDATA_EBPF_FAMILY + , NETDATA_FILE_OPEN_CLOSE_COUNT + , "Calls" + , NETDATA_FILE_GROUP + , 970 + , ebpf_create_global_dimension + , process_publish_aggregated + , 2); + + if (em->mode < MODE_ENTRY) { + ebpf_create_chart(NETDATA_EBPF_FAMILY + , NETDATA_FILE_OPEN_ERR_COUNT + , "Calls" + , NETDATA_FILE_GROUP + , 971 + , ebpf_create_global_dimension + , process_publish_aggregated + , 2); + } + + ebpf_create_chart(NETDATA_EBPF_FAMILY + , NETDATA_VFS_FILE_CLEAN_COUNT + , "Calls" + , NETDATA_VFS_GROUP + , 972 + , ebpf_create_global_dimension + , &process_publish_aggregated[NETDATA_DEL_START] + , 1); + + ebpf_create_chart(NETDATA_EBPF_FAMILY + , NETDATA_VFS_FILE_IO_COUNT + , "Calls" + , NETDATA_VFS_GROUP + , 973 + , ebpf_create_global_dimension + , &process_publish_aggregated[NETDATA_IN_START_BYTE] + , 2); + + if (em->mode < MODE_ENTRY) { + ebpf_create_io_chart(NETDATA_EBPF_FAMILY + , NETDATA_VFS_IO_FILE_BYTES + , "bytes/s" + , NETDATA_VFS_GROUP + , 974); + + ebpf_create_chart(NETDATA_EBPF_FAMILY + , NETDATA_VFS_FILE_ERR_COUNT + , "Calls" + , NETDATA_VFS_GROUP + , 975 + , ebpf_create_global_dimension + , &process_publish_aggregated[2] + , NETDATA_VFS_ERRORS); + + } + + ebpf_create_chart(NETDATA_EBPF_FAMILY + , NETDATA_PROCESS_SYSCALL + , "Calls" + , NETDATA_PROCESS_GROUP + , 976 + , ebpf_create_global_dimension + , &process_publish_aggregated[NETDATA_PROCESS_START] + , 2); + + ebpf_create_chart(NETDATA_EBPF_FAMILY + , NETDATA_EXIT_SYSCALL + , "Calls" + , NETDATA_PROCESS_GROUP + , 977 + , ebpf_create_global_dimension + , &process_publish_aggregated[NETDATA_EXIT_START] + , 2); + + ebpf_process_status_chart(NETDATA_EBPF_FAMILY + , NETDATA_PROCESS_STATUS_NAME + , "Total" + , NETDATA_PROCESS_GROUP + , 978); + + if (em->mode < MODE_ENTRY) { + ebpf_create_chart(NETDATA_EBPF_FAMILY + , NETDATA_PROCESS_ERROR_NAME + , "Calls" + , NETDATA_PROCESS_GROUP + , 979 + , ebpf_create_global_dimension + , &process_publish_aggregated[NETDATA_PROCESS_START] + , 2); + } + +} + +/***************************************************************** + * + * FUNCTIONS TO CLOSE THE THREAD + * + *****************************************************************/ + +/** + * Clean up the main thread. + * + * @param ptr thread data. + */ +static void ebpf_process_cleanup(void *ptr) +{ + (void)ptr; + freez(process_aggregated_data); + freez(process_publish_aggregated); + freez(process_hash_values); + + if (process_functions.libnetdata) { + dlclose(process_functions.libnetdata); + } + + freez(process_functions.map_fd); +} + +/***************************************************************** + * + * FUNCTIONS TO START THREAD + * + *****************************************************************/ + +/** + * Allocate vectors used with this thread. + * We are not testing the return, because callocz does this and shutdown the software + * case it was not possible to allocate. + * + * @param length is the length for the vectors used inside the collector. + */ +static void ebpf_process_allocate_global_vectors(size_t length) { + process_aggregated_data = callocz(length, sizeof(netdata_syscall_stat_t)); + process_publish_aggregated = callocz(length, sizeof(netdata_publish_syscall_t)); + process_hash_values = callocz(ebpf_nprocs, sizeof(netdata_idx_t)); +} + +static void change_collector_event() { + int i; + if (running_on_kernel < NETDATA_KERNEL_V5_3) + process_probes[10].name = NULL; + + for (i = 0; process_probes[i].name ; i++ ) { + process_probes[i].type = 'p'; + } +} + +static void change_syscalls() { + static char *lfork = { "do_fork" }; + process_id_names[7] = lfork; + process_probes[8].name = lfork; +} + +/** + * Set local function pointers, this function will never be compiled with static libraries + */ +static void set_local_pointers(ebpf_module_t *em) { +#ifndef STATIC + bpf_map_lookup_elem = process_functions.bpf_map_lookup_elem; + +#endif + + map_fd = process_functions.map_fd; + + if (em->mode == MODE_ENTRY) { + change_collector_event(); + } + + if (process_functions.isrh >= NETDATA_MINIMUM_RH_VERSION && process_functions.isrh < NETDATA_RH_8) + change_syscalls(); +} + +/***************************************************************** + * + * EBPF PROCESS THREAD + * + *****************************************************************/ + +/** + * Process thread + * + * Thread used to generate process charts. + * + * @param ptr a pointer to `struct ebpf_module` + * + * @return It always return NULL + */ +void *ebpf_process_thread(void *ptr) +{ + netdata_thread_cleanup_push(ebpf_process_cleanup, ptr); + + ebpf_module_t *em = (ebpf_module_t *)ptr; + fill_ebpf_functions(&process_functions); + + if (!em->enabled) + goto endprocess; + + pthread_mutex_lock(&lock); + ebpf_process_allocate_global_vectors(NETDATA_MAX_MONITOR_VECTOR); + + if (ebpf_load_libraries(&process_functions, "libnetdata_ebpf.so", ebpf_plugin_dir)) { + pthread_mutex_unlock(&lock); + goto endprocess; + } + + set_local_pointers(em); + if (ebpf_load_program(ebpf_plugin_dir, em->thread_id, em->mode, kernel_string, + em->thread_name, process_functions.map_fd, process_functions.load_bpf_file) ) { + pthread_mutex_unlock(&lock); + goto endprocess; + } + + ebpf_global_labels(process_aggregated_data, process_publish_aggregated, process_dimension_names, + process_id_names, NETDATA_MAX_MONITOR_VECTOR); + + ebpf_create_global_charts(em); + pthread_mutex_unlock(&lock); + process_collector((usec_t)(em->update_time*USEC_PER_SEC), em); + +endprocess: + netdata_thread_cleanup_pop(1); + return NULL; +} |