summaryrefslogtreecommitdiffstats
path: root/collectors/ebpf.plugin/ebpf_process.c
diff options
context:
space:
mode:
Diffstat (limited to 'collectors/ebpf.plugin/ebpf_process.c')
-rw-r--r--collectors/ebpf.plugin/ebpf_process.c514
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;
+}