diff options
Diffstat (limited to 'src/collectors/ebpf.plugin')
80 files changed, 35677 insertions, 0 deletions
diff --git a/src/collectors/ebpf.plugin/README.md b/src/collectors/ebpf.plugin/README.md new file mode 100644 index 000000000..e9243966b --- /dev/null +++ b/src/collectors/ebpf.plugin/README.md @@ -0,0 +1,1071 @@ +<!-- +title: "Kernel traces/metrics (eBPF) monitoring with Netdata" +description: "Use Netdata's extended Berkeley Packet Filter (eBPF) collector to monitor kernel-level metrics about yourcomplex applications with per-second granularity." +custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/README.md" +sidebar_label: "Kernel traces/metrics (eBPF)" +learn_status: "Published" +learn_topic_type: "References" +learn_rel_path: "Integrations/Monitor/System metrics" +--> + +# Kernel traces/metrics (eBPF) collector + +The Netdata Agent provides many [eBPF](https://ebpf.io/what-is-ebpf/) programs to help you troubleshoot and debug how applications interact with the Linux kernel. The `ebpf.plugin` uses [tracepoints, trampoline, and2 kprobes](#how-netdata-collects-data-using-probes-and-tracepoints) to collect a wide array of high value data about the host that would otherwise be impossible to capture. + +> ❗ eBPF monitoring only works on Linux systems and with specific Linux kernels, including all kernels newer than `4.11.0`, and all kernels on CentOS 7.6 or later. For kernels older than `4.11.0`, improved support is in active development. + +This document provides comprehensive details about the `ebpf.plugin`. +For hands-on configuration and troubleshooting tips see our [tutorial on troubleshooting apps with eBPF metrics](/docs/developer-and-contributor-corner/monitor-debug-applications-ebpf.md). + +<figure> + <img src="https://user-images.githubusercontent.com/1153921/74746434-ad6a1e00-5222-11ea-858a-a7882617ae02.png" alt="An example of VFS charts, made possible by the eBPF collector plugin" /> + <figcaption>An example of virtual file system (VFS) charts made possible by the eBPF collector plugin.</figcaption> +</figure> + +## How Netdata collects data using probes and tracepoints + +Netdata uses the following features from the Linux kernel to run eBPF programs: + +- Tracepoints are hooks to call specific functions. Tracepoints are more stable than `kprobes` and are preferred when + both options are available. +- Trampolines are bridges between kernel functions, and BPF programs. Netdata uses them by default whenever available. +- Kprobes and return probes (`kretprobe`): Probes can insert virtually into any kernel instruction. When eBPF runs in `entry` mode, it attaches only `kprobes` for internal functions monitoring calls and some arguments every time a function is called. The user can also change configuration to use [`return`](#global-configuration-options) mode, and this will allow users to monitor return from these functions and detect possible failures. + +In each case, wherever a normal kprobe, kretprobe, or tracepoint would have run its hook function, an eBPF program is run instead, performing various collection logic before letting the kernel continue its normal control flow. + +There are more methods to trigger eBPF programs, such as uprobes, but currently are not supported. + +## Configuring ebpf.plugin + +The eBPF collector is installed and enabled by default on most new installations of the Agent. +If your Agent is v1.22 or older, you may to enable the collector yourself. + +### Enable the eBPF collector + +To enable or disable the entire eBPF collector: + +1. Navigate to the [Netdata config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory). + ```bash + cd /etc/netdata + ``` + +2. Use the [`edit-config`](/docs/netdata-agent/configuration/README.md#edit-netdataconf) script to edit `netdata.conf`. + + ```bash + ./edit-config netdata.conf + ``` + +3. Enable the collector by scrolling down to the `[plugins]` section. Uncomment the line `ebpf` (not + `ebpf_process`) and set it to `yes`. + + ```conf + [plugins] + ebpf = yes + ``` + +### Configure the eBPF collector + +You can configure the eBPF collector's behavior to fine-tune which metrics you receive and [optimize performance]\(#performance opimization). + +To edit the `ebpf.d.conf`: + +1. Navigate to the [Netdata config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory). + ```bash + cd /etc/netdata + ``` +2. Use the [`edit-config`](/docs/netdata-agent/configuration/README.md#edit-netdataconf) script to edit [`ebpf.d.conf`](https://github.com/netdata/netdata/blob/master/src/collectors/ebpf.plugin/ebpf.d.conf). + + ```bash + ./edit-config ebpf.d.conf + ``` + + You can now edit the behavior of the eBPF collector. The following sections describe each configuration option in detail. + +### `[global]` configuration options + +The `[global]` section defines settings for the whole eBPF collector. + +#### eBPF load mode + +The collector uses two different eBPF programs. These programs rely on the same functions inside the kernel, but they +monitor, process, and display different kinds of information. + +By default, this plugin uses the `entry` mode. Changing this mode can create significant overhead on your operating +system, but also offer valuable information if you are developing or debugging software. The `ebpf load mode` option +accepts the following values: + +- `entry`: This is the default mode. In this mode, the eBPF collector only monitors calls for the functions described in + the sections above, and does not show charts related to errors. +- `return`: In the `return` mode, the eBPF collector monitors the same kernel functions as `entry`, but also creates new + charts for the return of these functions, such as errors. Monitoring function returns can help in debugging software, + such as failing to close file descriptors or creating zombie processes. + +#### Integration with `apps.plugin` + +The eBPF collector also creates charts for each running application through an integration with the +[`apps.plugin`](/src/collectors/apps.plugin/README.md). This integration helps you understand how specific applications +interact with the Linux kernel. + +If you want to enable `apps.plugin` integration, change the "apps" setting to "yes". + +```conf +[global] + apps = yes +``` + +#### Integration with `cgroups.plugin` + +The eBPF collector also creates charts for each cgroup through an integration with the +[`cgroups.plugin`](/src/collectors/cgroups.plugin/README.md). This integration helps you understand how a specific cgroup +interacts with the Linux kernel. + +The integration with `cgroups.plugin` is disabled by default to avoid creating overhead on your system. If you want to +_enable_ the integration with `cgroups.plugin`, change the `cgroups` setting to `yes`. + +```conf +[global] + cgroups = yes +``` + +If you do not need to monitor specific metrics for your `cgroups`, you can enable `cgroups` inside +`ebpf.d.conf`, and then disable the plugin for a specific `thread` by following the steps in the +[Configuration](#configuring-ebpfplugin) section. + +#### Maps per Core + +When netdata is running on kernels newer than `4.6` users are allowed to modify how the `ebpf.plugin` creates maps (hash or +array). When `maps per core` is defined as `yes`, plugin will create a map per core on host, on the other hand, +when the value is set as `no` only one hash table will be created, this option will use less memory, but it also can +increase overhead for processes. + +#### Collect PID + +When one of the previous integrations is enabled, `ebpf.plugin` will use Process Identifier (`PID`) to identify the +process group for which it needs to plot data. + +There are different ways to collect PID, and you can select the way `ebpf.plugin` collects data with the following +values: + +- `real parent`: This is the default mode. Collection will aggregate data for the real parent, the thread that creates + child threads. +- `parent`: Parent and real parent are the same when a process starts, but this value can be changed during run time. +- `all`: This option will store all PIDs that run on the host. Note, this method can be expensive for the host, + because more memory needs to be allocated and parsed. + +The threads that have integration with other collectors have an internal clean up wherein they attach either a +`trampoline` or a `kprobe` to `release_task` internal function. To avoid `overload` on this function, `ebpf.plugin` +will only enable these threads integrated with other collectors when the kernel is compiled with +`CONFIG_DEBUG_INFO_BTF`, unless you enable them manually. + +#### Collection period + +The plugin uses the option `update every` to define the number of seconds used for eBPF to send data for Netdata. The default value +is 5 seconds. + +#### PID table size + +The option `pid table size` defines the maximum number of PIDs stored inside the application hash table. The default value +is defined according [kernel](https://elixir.bootlin.com/linux/v6.0.19/source/include/linux/threads.h#L28) source code. + +#### Integration Dashboard Elements + +When an integration is enabled, your dashboard will also show the following cgroups and apps charts using low-level +Linux metrics: + +> Note: The parenthetical accompanying each bulleted item provides the chart name. + +- mem + - Number of processes killed due out of memory. (`oomkills`) +- process + - Number of processes created with `do_fork`. (`process_create`) + - Number of threads created with `do_fork` or `clone (2)`, depending on your system's kernel + version. (`thread_create`) + - Number of times that a process called `do_exit`. (`task_exit`) + - Number of times that a process called `release_task`. (`task_close`) + - Number of times that an error happened to create thread or process. (`task_error`) +- swap + - Number of calls to `swap_readpage`. (`swap_read_call`) + - Number of calls to `swap_writepage`. (`swap_write_call`) +- network + - Number of outbound connections using TCP/IPv4. (`outbound_conn_ipv4`) + - Number of outbound connections using TCP/IPv6. (`outbound_conn_ipv6`) + - Number of bytes sent. (`total_bandwidth_sent`) + - Number of bytes received. (`total_bandwidth_recv`) + - Number of calls to `tcp_sendmsg`. (`bandwidth_tcp_send`) + - Number of calls to `tcp_cleanup_rbuf`. (`bandwidth_tcp_recv`) + - Number of calls to `tcp_retransmit_skb`. (`bandwidth_tcp_retransmit`) + - Number of calls to `udp_sendmsg`. (`bandwidth_udp_send`) + - Number of calls to `udp_recvmsg`. (`bandwidth_udp_recv`) +- file access + - Number of calls to open files. (`file_open`) + - Number of calls to open files that returned errors. (`open_error`) + - Number of files closed. (`file_closed`) + - Number of calls to close files that returned errors. (`file_error_closed`) +- vfs + - Number of calls to `vfs_unlink`. (`file_deleted`) + - Number of calls to `vfs_write`. (`vfs_write_call`) + - Number of calls to write a file that returned errors. (`vfs_write_error`) + - Number of calls to `vfs_read`. (`vfs_read_call`) + - - Number of calls to read a file that returned errors. (`vfs_read_error`) + - Number of bytes written with `vfs_write`. (`vfs_write_bytes`) + - Number of bytes read with `vfs_read`. (`vfs_read_bytes`) + - Number of calls to `vfs_fsync`. (`vfs_fsync`) + - Number of calls to sync file that returned errors. (`vfs_fsync_error`) + - Number of calls to `vfs_open`. (`vfs_open`) + - Number of calls to open file that returned errors. (`vfs_open_error`) + - Number of calls to `vfs_create`. (`vfs_create`) + - Number of calls to open file that returned errors. (`vfs_create_error`) +- page cache + - Ratio of pages accessed. (`cachestat_ratio`) + - Number of modified pages ("dirty"). (`cachestat_dirties`) + - Number of accessed pages. (`cachestat_hits`) + - Number of pages brought from disk. (`cachestat_misses`) +- directory cache + - Ratio of files available in directory cache. (`dc_hit_ratio`) + - Number of files accessed. (`dc_reference`) + - Number of files accessed that were not in cache. (`dc_not_cache`) + - Number of files not found. (`dc_not_found`) +- ipc shm + - Number of calls to `shm_get`. (`shmget_call`) + - Number of calls to `shm_at`. (`shmat_call`) + - Number of calls to `shm_dt`. (`shmdt_call`) + - Number of calls to `shm_ctl`. (`shmctl_call`) + +### `[ebpf programs]` configuration options + +The eBPF collector enables and runs the following eBPF programs by default: + +- `cachestat`: Netdata's eBPF data collector creates charts about the memory page cache. When the integration with + [`apps.plugin`](/src/collectors/apps.plugin/README.md) is enabled, this collector creates charts for the whole host _and_ + for each application. +- `fd` : This eBPF program creates charts that show information about calls to open files. +- `mount`: This eBPF program creates charts that show calls to syscalls mount(2) and umount(2). +- `shm`: This eBPF program creates charts that show calls to syscalls shmget(2), shmat(2), shmdt(2) and shmctl(2). +- `process`: This eBPF program creates charts that show information about process life. When in `return` mode, it also + creates charts showing errors when these operations are executed. +- `hardirq`: This eBPF program creates charts that show information about time spent servicing individual hardware + interrupt requests (hard IRQs). +- `softirq`: This eBPF program creates charts that show information about time spent servicing individual software + interrupt requests (soft IRQs). +- `oomkill`: This eBPF program creates a chart that shows OOM kills for all applications recognized via + the `apps.plugin` integration. Note that this program will show application charts regardless of whether apps + integration is turned on or off. + +You can also enable the following eBPF programs: + +- `dcstat` : This eBPF program creates charts that show information about file access using directory cache. It appends + `kprobes` for `lookup_fast()` and `d_lookup()` to identify if files are inside directory cache, outside and files are + not found. +- `disk` : This eBPF program creates charts that show information about disk latency independent of filesystem. +- `filesystem` : This eBPF program creates charts that show information about some filesystem latency. +- `swap` : This eBPF program creates charts that show information about swap access. +- `mdflush`: This eBPF program creates charts that show information about +- `sync`: Monitor calls to syscalls sync(2), fsync(2), fdatasync(2), syncfs(2), msync(2), and sync_file_range(2). +- `socket`: This eBPF program creates charts with information about `TCP` and `UDP` functions, including the + bandwidth consumed by each. + multi-device software flushes. +- `vfs`: This eBPF program creates charts that show information about VFS (Virtual File System) functions. + +### Configuring eBPF threads + +You can configure each thread of the eBPF data collector. This allows you to overwrite global options defined in `/etc/netdata/ebpf.d.conf` and configure specific options for each thread. + +To configure an eBPF thread: + +1. Navigate to the [Netdata config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory). + ```bash + cd /etc/netdata + ``` +2. Use the [`edit-config`](/docs/netdata-agent/configuration/README.md#edit-netdataconf) script to edit a thread configuration file. The following configuration files are available: + + - `network.conf`: Configuration for the [`network` thread](#network-configuration). This config file overwrites the global options and also + lets you specify which network the eBPF collector monitors. + - `process.conf`: Configuration for the [`process` thread](#sync-configuration). + - `cachestat.conf`: Configuration for the `cachestat` thread(#filesystem-configuration). + - `dcstat.conf`: Configuration for the `dcstat` thread. + - `disk.conf`: Configuration for the `disk` thread. + - `fd.conf`: Configuration for the `file descriptor` thread. + - `filesystem.conf`: Configuration for the `filesystem` thread. + - `hardirq.conf`: Configuration for the `hardirq` thread. + - `softirq.conf`: Configuration for the `softirq` thread. + - `sync.conf`: Configuration for the `sync` thread. + - `vfs.conf`: Configuration for the `vfs` thread. + + ```bash + ./edit-config FILE.conf + ``` + +### Network configuration + +The network configuration has specific options to configure which network(s) the eBPF collector monitors. These options +are divided in the following sections: + +#### `[network connections]` + +You can configure the information shown with function `ebpf_socket` using the settings in this section. + +```conf +[network connections] + enabled = yes + resolve hostname ips = no + resolve service names = yes + ports = 1-1024 !145 !domain + hostnames = !example.com + ips = !127.0.0.1/8 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 fc00::/7 +``` + +When you define a `ports` setting, Netdata will collect network metrics for that specific port. For example, if you +write `ports = 19999`, Netdata will collect only connections for itself. The `hostnames` setting accepts +[simple patterns](/src/libnetdata/simple_pattern/README.md). The `ports`, and `ips` settings accept negation (`!`) to deny +specific values or asterisk alone to define all values. + +In the above example, Netdata will collect metrics for all ports between `1` and `1024`, with the exception of `53` (domain) +and `145`. + +The following options are available: + +- `enabled`: Disable network connections monitoring. This can affect directly some funcion output. +- `resolve hostname ips`: Enable resolving IPs to hostnames. It is disabled by default because it can be too slow. +- `resolve service names`: Convert destination ports into service names, for example, port `53` protocol `UDP` becomes `domain`. + all names are read from /etc/services. +- `ports`: Define the destination ports for Netdata to monitor. +- `hostnames`: The list of hostnames that can be resolved to an IP address. +- `ips`: The IP or range of IPs that you want to monitor. You can use IPv4 or IPv6 addresses, use dashes to define a + range of IPs, or use CIDR values. + +By default the traffic table is created using the destination IPs and ports of the sockets. This can be +changed, so that Netdata uses service names (if possible), by specifying `resolve service name = yes` in the configuration +section. + +#### `[service name]` + +Netdata uses the list of services in `/etc/services` to plot network connection charts. If this file does not contain +the name for a particular service you use in your infrastructure, you will need to add it to the `[service name]` +section. + +For example, Netdata's default port (`19999`) is not listed in `/etc/services`. To associate that port with the Netdata +service in network connection charts, and thus see the name of the service instead of its port, define it: + +```conf +[service name] + 19999 = Netdata +``` + +### Sync configuration + +The sync configuration has specific options to disable monitoring for syscalls. All syscalls are monitored by default. + +```conf +[syscalls] + sync = yes + msync = yes + fsync = yes + fdatasync = yes + syncfs = yes + sync_file_range = yes +``` + +### Filesystem configuration + +The filesystem configuration has specific options to disable monitoring for filesystems; by default, all filesystems are +monitored. + +```conf +[filesystem] + btrfsdist = yes + ext4dist = yes + nfsdist = yes + xfsdist = yes + zfsdist = yes +``` + +The ebpf program `nfsdist` monitors only `nfs` mount points. + +## Troubleshooting + +If the eBPF collector does not work, you can troubleshoot it by running the `ebpf.plugin` command and investigating its +output. + +```bash +cd /usr/libexec/netdata/plugins.d/ +sudo su -s /bin/bash ./ebpf.plugin +``` + +You can also use `grep` to search the Agent's `error.log` for messages related to eBPF monitoring. + +```bash +grep -i ebpf /var/log/netdata/error.log +``` + +### Confirm kernel compatibility + +The eBPF collector only works on Linux systems and with specific Linux kernels. We support all kernels more recent than +`4.11.0`, and all kernels on CentOS 7.6 or later. + +You can run our helper script to determine whether your system can support eBPF monitoring. If it returns no output, your system is ready to compile and run the eBPF collector. + +```bash +curl -sSL https://raw.githubusercontent.com/netdata/kernel-collector/master/tools/check-kernel-config.sh | sudo bash +``` + + +If you see a warning about a missing kernel +configuration (`KPROBES KPROBES_ON_FTRACE HAVE_KPROBES BPF BPF_SYSCALL BPF_JIT`), you will need to recompile your kernel +to support this configuration. The process of recompiling Linux kernels varies based on your distribution and version. +Read the documentation for your system's distribution to learn more about the specific workflow for recompiling the +kernel, ensuring that you set all the necessary + +- [Ubuntu](https://wiki.ubuntu.com/Kernel/BuildYourOwnKernel) +- [Debian](https://kernel-team.pages.debian.net/kernel-handbook/ch-common-tasks.html#s-common-official) +- [Fedora](https://fedoraproject.org/wiki/Building_a_custom_kernel) +- [CentOS](https://wiki.centos.org/HowTos/Custom_Kernel) +- [Arch Linux](https://wiki.archlinux.org/index.php/Kernel/Traditional_compilation) +- [Slackware](https://docs.slackware.com/howtos:slackware_admin:kernelbuilding) + +### Mount `debugfs` and `tracefs` + +The eBPF collector also requires both the `tracefs` and `debugfs` filesystems. Try mounting the `tracefs` and `debugfs` +filesystems using the commands below: + +```bash +sudo mount -t debugfs nodev /sys/kernel/debug +sudo mount -t tracefs nodev /sys/kernel/tracing +``` + +If they are already mounted, you will see an error. You can also configure your system's `/etc/fstab` configuration to +mount these filesystems on startup. More information can be found in +the [ftrace documentation](https://www.kernel.org/doc/Documentation/trace/ftrace.txt). + +## Charts + +The eBPF collector creates charts on different menus, like System Overview, Memory, MD arrays, Disks, Filesystem, +Mount Points, Networking Stack, systemd Services, and Applications. + +The collector stores the actual value inside of its process, but charts only show the difference between the values +collected in the previous and current seconds. + +### System overview + +Not all charts within the System Overview menu are enabled by default. Charts that rely on `kprobes` are disabled by default because they add around 100ns overhead for each function call. This is a small number from a human's perspective, but the functions are called many times and create an impact +on host. See the [configuration](#configuring-ebpfplugin) section for details about how to enable them. + +#### Processes + +Internally, the Linux kernel treats both processes and threads as `tasks`. To create a thread, the kernel offers a few +system calls: `fork(2)`, `vfork(2)`, and `clone(2)`. To generate this chart, the eBPF +collector uses the following `tracepoints` and `kprobe`: + +- `sched/sched_process_fork`: Tracepoint called after a call for `fork (2)`, `vfork (2)` and `clone (2)`. +- `sched/sched_process_exec`: Tracepoint called after a exec-family syscall. +- `kprobe/kernel_clone`: This is the main [`fork()`](https://elixir.bootlin.com/linux/v5.10/source/kernel/fork.c#L2415) + routine since kernel `5.10.0` was released. +- `kprobe/_do_fork`: Like `kernel_clone`, but this was the main function between kernels `4.2.0` and `5.9.16` +- `kprobe/do_fork`: This was the main function before kernel `4.2.0`. + +#### Process Exit + +Ending a task requires two steps. The first is a call to the internal function `do_exit`, which notifies the operating +system that the task is finishing its work. The second step is to release the kernel information with the internal +function `release_task`. The difference between the two dimensions can help you discover +[zombie processes](https://en.wikipedia.org/wiki/Zombie_process). To get the metrics, the collector uses: + +- `sched/sched_process_exit`: Tracepoint called after a task exits. +- `kprobe/release_task`: This function is called when a process exits, as the kernel still needs to remove the process + descriptor. + +#### Task error + +The functions responsible for ending tasks do not return values, so this chart contains information about failures on +process and thread creation only. + +#### Swap + +Inside the swap submenu the eBPF plugin creates the chart `swapcalls`; this chart is displaying when processes are +calling functions [`swap_readpage` and `swap_writepage`](https://hzliu123.github.io/linux-kernel/Page%20Cache%20in%20Linux%202.6.pdf), +which are functions responsible for doing IO in swap memory. To collect the exact moment that an access to swap happens, +the collector attaches `kprobes` for cited functions. + +#### Soft IRQ + +The following `tracepoints` are used to measure time usage for soft IRQs: + +- [`irq/softirq_entry`](https://www.kernel.org/doc/html/latest/core-api/tracepoint.html#c.trace_softirq_entry): Called + before softirq handler +- [`irq/softirq_exit`](https://www.kernel.org/doc/html/latest/core-api/tracepoint.html#c.trace_softirq_exit): Called when + softirq handler returns. + +#### Hard IRQ + +The following tracepoints are used to measure the latency of servicing a +hardware interrupt request (hard IRQ). + +- [`irq/irq_handler_entry`](https://www.kernel.org/doc/html/latest/core-api/tracepoint.html#c.trace_irq_handler_entry): + Called immediately before the IRQ action handler. +- [`irq/irq_handler_exit`](https://www.kernel.org/doc/html/latest/core-api/tracepoint.html#c.trace_irq_handler_exit): + Called immediately after the IRQ action handler returns. +- `irq_vectors`: These are traces from `irq_handler_entry` and + `irq_handler_exit` when an IRQ is handled. The following elements from vector + are triggered: + - `irq_vectors/local_timer_entry` + - `irq_vectors/local_timer_exit` + - `irq_vectors/reschedule_entry` + - `irq_vectors/reschedule_exit` + - `irq_vectors/call_function_entry` + - `irq_vectors/call_function_exit` + - `irq_vectors/call_function_single_entry` + - `irq_vectors/call_function_single_xit` + - `irq_vectors/irq_work_entry` + - `irq_vectors/irq_work_exit` + - `irq_vectors/error_apic_entry` + - `irq_vectors/error_apic_exit` + - `irq_vectors/thermal_apic_entry` + - `irq_vectors/thermal_apic_exit` + - `irq_vectors/threshold_apic_entry` + - `irq_vectors/threshold_apic_exit` + - `irq_vectors/deferred_error_entry` + - `irq_vectors/deferred_error_exit` + - `irq_vectors/spurious_apic_entry` + - `irq_vectors/spurious_apic_exit` + - `irq_vectors/x86_platform_ipi_entry` + - `irq_vectors/x86_platform_ipi_exit` + +#### IPC shared memory + +To monitor shared memory system call counts, Netdata attaches tracing in the following functions: + +- `shmget`: Runs when [`shmget`](https://man7.org/linux/man-pages/man2/shmget.2.html) is called. +- `shmat`: Runs when [`shmat`](https://man7.org/linux/man-pages/man2/shmat.2.html) is called. +- `shmdt`: Runs when [`shmdt`](https://man7.org/linux/man-pages/man2/shmat.2.html) is called. +- `shmctl`: Runs when [`shmctl`](https://man7.org/linux/man-pages/man2/shmctl.2.html) is called. + +### Memory + +In the memory submenu the eBPF plugin creates two submenus **page cache** and **synchronization** with the following +organization: + +- Page Cache + - Page cache ratio + - Dirty pages + - Page cache hits + - Page cache misses +- Synchronization + - File sync + - Memory map sync + - File system sync + - File range sync + +#### Page cache hits + +When the processor needs to read or write a location in main memory, it checks for a corresponding entry in the page cache. + If the entry is there, a page cache hit has occurred and the read is from the cache. + +A page cache hit is when the page cache is successfully accessed with a read operation. We do not count pages that were +added relatively recently. + +#### Dirty pages + +A "dirty page" is a page in the page cache that was modified after being created. Since non-dirty pages in the page cache + have identical copies in secondary storage (e.g. hard disk drive or solid-state drive), discarding and reusing their space + is much quicker than paging out application memory, and is often preferred over flushing the dirty pages into secondary storage + and reusing their space. + +On `cachestat_dirties` Netdata demonstrates the number of pages that were modified. This chart shows the number of calls +to the function `mark_buffer_dirty`. + +#### Page cache ratio + +When the processor needs to read or write in a specific memory address, it checks for a corresponding entry in the page cache. +If the processor hits a page cache (`page cache hit`), it reads the entry from the cache. If there is no entry (`page cache miss`), + the kernel allocates a new entry and copies data from the disk. Netdata calculates the percentage of accessed files that are cached on + memory. The ratio is calculated counting the accessed cached pages + (without counting [dirty pages](#dirty-pages) and pages added because of read misses) divided by total access without dirty pages. + +> \_\_**\_\_\_\_**<ins>Number of accessed cached pages</ins>\***\*\_\_\*\***<br/> +> Number of total accessed pages - dirty pages - missed pages + +The chart `cachestat_ratio` shows how processes are accessing page cache. In a normal scenario, we expect values around +100%, which means that the majority of the work on the machine is processed in memory. To calculate the ratio, Netdata +attaches `kprobes` for kernel functions: + +- `add_to_page_cache_lru`: Page addition. +- `mark_page_accessed`: Access to cache. +- `account_page_dirtied`: Dirty (modified) pages. +- `mark_buffer_dirty`: Writes to page cache. + +#### Page cache misses + +A page cache miss means that a page was not inside memory when the process tried to access it. This chart shows the +result of the difference for calls between functions `add_to_page_cache_lru` and `account_page_dirtied`. + +#### File sync + +This chart shows calls to synchronization methods, [`fsync(2)`](https://man7.org/linux/man-pages/man2/fdatasync.2.html) +and [`fdatasync(2)`](https://man7.org/linux/man-pages/man2/fdatasync.2.html), to transfer all modified page caches +for the files on disk devices. These calls block until the disk reports that the transfer has been completed. They flush +data for specific file descriptors. + +#### Memory map sync + +The chart shows calls to [`msync(2)`](https://man7.org/linux/man-pages/man2/msync.2.html) syscalls. This syscall flushes +changes to a file that was mapped into memory using [`mmap(2)`](https://man7.org/linux/man-pages/man2/mmap.2.html). + +#### File system sync + +This chart monitors calls demonstrating commits from filesystem caches to disk. Netdata attaches `tracing` for +[`sync(2)`](https://man7.org/linux/man-pages/man2/sync.2.html), and [`syncfs(2)`](https://man7.org/linux/man-pages/man2/sync.2.html). + +#### File range sync + +This chart shows calls to [`sync_file_range(2)`](https://man7.org/linux/man-pages/man2/sync_file_range.2.html) which +synchronizes file segments with disk. + +> Note: This is the most dangerous syscall to synchronize data, according to its manual. + +### Multiple Device (MD) arrays + +The eBPF plugin shows multi-device flushes happening in real time. This can be used to explain some spikes happening +in [disk latency](#disk) charts. + +By default, MD flush is disabled. To enable it, configure your +`/etc/netdata/ebpf.d.conf` file as: + +```conf +[global] + mdflush = yes +``` + +#### MD flush + +To collect data related to Linux multi-device (MD) flushing, the following kprobe is used: + +- `kprobe/md_flush_request`: called whenever a request for flushing multi-device data is made. + +### Disk + +The eBPF plugin also shows a chart in the Disk section when the `disk` thread is enabled. + +#### Disk Latency + +This will create the chart `disk_latency_io` for each disk on the host. The following tracepoints are used: + +- [`block/block_rq_issue`](https://www.kernel.org/doc/html/latest/core-api/tracepoint.html#c.trace_block_rq_issue): + IO request operation to a device drive. +- [`block/block_rq_complete`](https://www.kernel.org/doc/html/latest/core-api/tracepoint.html#c.trace_block_rq_complete): + IO operation completed by device. + +Disk Latency is the single most important metric to focus on when it comes to storage performance, under most circumstances. +For hard drives, an average latency somewhere between 10 to 20 ms can be considered acceptable. For SSD (Solid State Drives), +in most cases, workloads experience less than 1 ms latency numbers, but workloads should never reach higher than 3 ms. +The dimensions refer to time intervals. + +### Filesystem + +This group has charts demonstrating how applications interact with the Linux kernel to open and close file descriptors. +It also brings latency charts for several different filesystems. + +#### Latency Algorithm + +We calculate the difference between the calling and return times, spanning disk I/O, file system operations (lock, I/O), +run queue latency and all events related to the monitored action. + +#### ext4 + +To measure the latency of executing some actions in an +[ext4](https://elixir.bootlin.com/linux/latest/source/fs/ext4) filesystem, the +collector needs to attach `kprobes` and `kretprobes` for each of the following +functions: + +- `ext4_file_read_iter`: Function used to measure read latency. +- `ext4_file_write_iter`: Function used to measure write latency. +- `ext4_file_open`: Function used to measure open latency. +- `ext4_sync_file`: Function used to measure sync latency. + +#### ZFS + +To measure the latency of executing some actions in a zfs filesystem, the +collector needs to attach `kprobes` and `kretprobes` for each of the following +functions: + +- `zpl_iter_read`: Function used to measure read latency. +- `zpl_iter_write`: Function used to measure write latency. +- `zpl_open`: Function used to measure open latency. +- `zpl_fsync`: Function used to measure sync latency. + +#### XFS + +To measure the latency of executing some actions in an +[xfs](https://elixir.bootlin.com/linux/latest/source/fs/xfs) filesystem, the +collector needs to attach `kprobes` and `kretprobes` for each of the following +functions: + +- `xfs_file_read_iter`: Function used to measure read latency. +- `xfs_file_write_iter`: Function used to measure write latency. +- `xfs_file_open`: Function used to measure open latency. +- `xfs_file_fsync`: Function used to measure sync latency. + +#### NFS + +To measure the latency of executing some actions in an +[nfs](https://elixir.bootlin.com/linux/latest/source/fs/nfs) filesystem, the +collector needs to attach `kprobes` and `kretprobes` for each of the following +functions: + +- `nfs_file_read`: Function used to measure read latency. +- `nfs_file_write`: Function used to measure write latency. +- `nfs_file_open`: Functions used to measure open latency. +- `nfs4_file_open`: Functions used to measure open latency for NFS v4. +- `nfs_getattr`: Function used to measure sync latency. + +#### btrfs + +To measure the latency of executing some actions in a [btrfs](https://elixir.bootlin.com/linux/latest/source/fs/btrfs/file.c) +filesystem, the collector needs to attach `kprobes` and `kretprobes` for each of the following functions: + +> Note: We are listing two functions used to measure `read` latency, but we use either `btrfs_file_read_iter` or +> `generic_file_read_iter`, depending on kernel version. + +- `btrfs_file_read_iter`: Function used to measure read latency since kernel `5.10.0`. +- `generic_file_read_iter`: Like `btrfs_file_read_iter`, but this function was used before kernel `5.10.0`. +- `btrfs_file_write_iter`: Function used to write data. +- `btrfs_file_open`: Function used to open files. +- `btrfs_sync_file`: Function used to synchronize data to filesystem. + +#### File descriptor + +To give metrics related to `open` and `close` events, instead of attaching kprobes for each syscall used to do these +events, the collector attaches `kprobes` for the common function used for syscalls: + +- [`do_sys_open`](https://0xax.gitbooks.io/linux-insides/content/SysCall/linux-syscall-5.html): Internal function used to + open files. +- [`do_sys_openat2`](https://elixir.bootlin.com/linux/v5.6/source/fs/open.c#L1162): + Function called from `do_sys_open` since version `5.6.0`. +- [`close_fd`](https://www.mail-archive.com/linux-kernel@vger.kernel.org/msg2271761.html): Function used to close file + descriptor since kernel `5.11.0`. +- `__close_fd`: Function used to close files before version `5.11.0`. + +#### File error + +This chart shows the number of times some software tried and failed to open or close a file descriptor. + +#### VFS + +The Linux Virtual File System (VFS) is an abstraction layer on top of a +concrete filesystem like the ones listed in the parent section, e.g. `ext4`. + +In this section we list the mechanism by which we gather VFS data, and what +charts are consequently created. + +##### VFS eBPF Hooks + +To measure the latency and total quantity of executing some VFS-level +functions, ebpf.plugin needs to attach kprobes and kretprobes for each of the +following functions: + +- `vfs_write`: Function used monitoring the number of successful & failed + filesystem write calls, as well as the total number of written bytes. +- `vfs_writev`: Same function as `vfs_write` but for vector writes (i.e. a + single write operation using a group of buffers rather than 1). +- `vfs_read`: Function used for monitoring the number of successful & failed + filesystem read calls, as well as the total number of read bytes. +- `vfs_readv` Same function as `vfs_read` but for vector reads (i.e. a single + read operation using a group of buffers rather than 1). +- `vfs_unlink`: Function used for monitoring the number of successful & failed + filesystem unlink calls. +- `vfs_fsync`: Function used for monitoring the number of successful & failed + filesystem fsync calls. +- `vfs_open`: Function used for monitoring the number of successful & failed + filesystem open calls. +- `vfs_create`: Function used for monitoring the number of successful & failed + filesystem create calls. + +##### VFS Deleted objects + +This chart monitors calls to `vfs_unlink`. This function is responsible for removing objects from the file system. + +##### VFS IO + +This chart shows the number of calls to the functions `vfs_read` and `vfs_write`. + +##### VFS IO bytes + +This chart also monitors `vfs_read` and `vfs_write` but, instead of the number of calls, it shows the total amount of +bytes read and written with these functions. + +The Agent displays the number of bytes written as negative because they are moving down to disk. + +##### VFS IO errors + +The Agent counts and shows the number of instances where a running program experiences a read or write error. + +##### VFS Create + +This chart shows the number of calls to `vfs_create`. This function is responsible for creating files. + +##### VFS Synchronization + +This chart shows the number of calls to `vfs_fsync`. This function is responsible for calling `fsync(2)` or +`fdatasync(2)` on a file. You can see more details in the Synchronization section. + +##### VFS Open + +This chart shows the number of calls to `vfs_open`. This function is responsible for opening files. + +#### Directory Cache + +Metrics for directory cache are collected using kprobe for `lookup_fast`, because we are interested in the number of +times this function is accessed. On the other hand, for `d_lookup` we are not only interested in the number of times it +is accessed, but also in possible errors, so we need to attach a `kretprobe`. For this reason, the following is used: + +- [`lookup_fast`](https://lwn.net/Articles/649115/): Called to look at data inside the directory cache. +- [`d_lookup`](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/fs/dcache.c?id=052b398a43a7de8c68c13e7fa05d6b3d16ce6801#n2223): + Called when the desired file is not inside the directory cache. + +##### Directory Cache Interpretation + +When directory cache is showing 100% that means that every accessed file was present in the directory cache. +If files are not present in the directory cache, they are either not present in the file system or the files were not +accessed before. + +### Mount Points + +The following `tracing` are used to collect `mount` & `unmount` call counts: + +- [`mount`](https://man7.org/linux/man-pages/man2/mount.2.html): mount filesystem on host. +- [`umount`](https://man7.org/linux/man-pages/man2/umount.2.html): umount filesystem on host. + +### Networking Stack + +Netdata monitors socket bandwidth attaching `tracing` for internal functions. + +#### TCP outbound connections + +This chart demonstrates calls to `tcp_v4_connection` and `tcp_v6_connection` that start connections for IPV4 and IPV6, respectively. + +#### TCP inbound connections + +This chart demonstrates TCP and UDP connections that the host receives. +To collect this information, netdata attaches a tracing to `inet_csk_accept`. + +#### TCP bandwidth functions + +This chart demonstrates calls to functions `tcp_sendmsg`, `tcp_cleanup_rbuf`, and `tcp_close`; these functions are used +to send & receive data and to close connections when `TCP` protocol is used. + +#### TCP bandwidth + +This chart demonstrates calls to functions: + +- `tcp_sendmsg`: Function responsible to send data for a specified destination. +- `tcp_cleanup_rbuf`: We use this function instead of `tcp_recvmsg`, because the last one misses `tcp_read_sock` traffic + and we would also need to add more `tracing` to get the socket and package size. +- `tcp_close`: Function responsible to close connection. + +#### TCP retransmit + +This chart demonstrates calls to function `tcp_retransmit` that is responsible for executing TCP retransmission when the +receiver did not return the packet during the expected time. + +#### UDP functions + +This chart demonstrates calls to functions `udp_sendmsg` and `udp_recvmsg`, which are responsible for sending & +receiving data for connections when the `UDP` protocol is used. + +#### UDP bandwidth + +Like the previous chart, this one also monitors `udp_sendmsg` and `udp_recvmsg`, but instead of showing the number of +calls, it monitors the number of bytes sent and received. + +### Apps + +#### OOM Killing + +These are tracepoints related to [OOM](https://en.wikipedia.org/wiki/Out_of_memory) killing processes. + +- `oom/mark_victim`: Monitors when an oomkill event happens. + +## Known issues + +### Performance opimization + +eBPF monitoring is complex and produces a large volume of metrics. We've discovered scenarios where the eBPF plugin +significantly increases kernel memory usage by several hundred MB. + +When the integration with apps or cgroup is enabled, the eBPF collector allocates memory for each process running. If your +node is experiencing high memory usage and there is no obvious culprit to be found in the `apps.mem` chart, consider: + +- Modify [maps per core](#maps-per-core) to use only one map. +- Disable [integration with apps](#integration-with-appsplugin). +- Disable [integration with cgroup](#integration-with-cgroupsplugin). + +If with these changes you still suspect eBPF using too much memory, and there is no obvious culprit to be found +in the `apps.mem` chart, consider testing for high kernel memory usage by [disabling eBPF monitoring](#configuring-ebpfplugin). +Next, [restart Netdata](/packaging/installer/README.md#maintaining-a-netdata-agent-installation) with +`sudo systemctl restart netdata` to see if system memory usage (see the `system.ram` chart) has dropped significantly. + +Beginning with `v1.31`, kernel memory usage is configurable via the [`pid table size` setting](#pid-table-size) +in `ebpf.conf`. + +The total memory usage is a well known [issue](https://lore.kernel.org/all/167821082315.1693.6957546778534183486.git-patchwork-notify@kernel.org/) +for eBPF, this is not a bug present in plugin. + +### SELinux + +When [SELinux](https://www.redhat.com/en/topics/linux/what-is-selinux) is enabled, it may prevent `ebpf.plugin` from +starting correctly. Check the Agent's `error.log` file for errors like the ones below: + +```bash +2020-06-14 15:32:08: ebpf.plugin ERROR : EBPF PROCESS : Cannot load program: /usr/libexec/netdata/plugins.d/pnetdata_ebpf_process.3.10.0.o (errno 13, Permission denied) +2020-06-14 15:32:19: netdata ERROR : PLUGINSD[ebpf] : read failed: end of file (errno 9, Bad file descriptor) +``` + +You can also check for errors related to `ebpf.plugin` inside `/var/log/audit/audit.log`: + +```bash +type=AVC msg=audit(1586260134.952:97): avc: denied { map_create } for pid=1387 comm="ebpf.pl" scontext=system_u:system_r:unconfined_service_t:s0 tcontext=system_u:system_r:unconfined_service_t:s0 tclass=bpf permissive=0 +type=SYSCALL msg=audit(1586260134.952:97): arch=c000003e syscall=321 success=no exit=-13 a0=0 a1=7ffe6b36f000 a2=70 a3=0 items=0 ppid=1135 pid=1387 auid=4294967295 uid=994 gid=990 euid=0 suid=0 fsuid=0 egid=990 sgid=990 fsgid=990 tty=(none) ses=4294967295 comm="ebpf_proc +ess.pl" exe="/usr/libexec/netdata/plugins.d/ebpf.plugin" subj=system_u:system_r:unconfined_service_t:s0 key=(null) +``` + +If you see similar errors, you will have to adjust SELinux's policies to enable the eBPF collector. + +#### Creation of bpf policies + +To enable `ebpf.plugin` to run on a distribution with SELinux enabled, it will be necessary to take the following +actions. + +First, stop the Netdata Agent. + +```bash +# systemctl stop netdata +``` + +Next, create a policy with the `audit.log` file you examined earlier. + +```bash +# grep ebpf.plugin /var/log/audit/audit.log | audit2allow -M netdata_ebpf +``` + +This will create two new files: `netdata_ebpf.te` and `netdata_ebpf.mod`. + +Edit the `netdata_ebpf.te` file to change the options `class` and `allow`. You should have the following at the end of +the `netdata_ebpf.te` file. + +```conf +module netdata_ebpf 1.0; +require { + type unconfined_service_t; + class bpf { map_create map_read map_write prog_load prog_run }; +} +#============= unconfined_service_t ============== +allow unconfined_service_t self:bpf { map_create map_read map_write prog_load prog_run }; +``` + +Then compile your `netdata_ebpf.te` file with the following commands to create a binary that loads the new policies: + +```bash +# checkmodule -M -m -o netdata_ebpf.mod netdata_ebpf.te +# semodule_package -o netdata_ebpf.pp -m netdata_ebpf.mod +``` + +Finally, you can load the new policy and start the Netdata agent again: + +```bash +# semodule -i netdata_ebpf.pp +# systemctl start netdata +``` + +### Linux kernel lockdown + +Beginning with [version 5.4](https://www.zdnet.com/article/linux-to-get-kernel-lockdown-feature/), the Linux kernel has +a feature called "lockdown," which may affect `ebpf.plugin` depending how the kernel was compiled. The following table +shows how the lockdown module impacts `ebpf.plugin` based on the selected options: + +| Enforcing kernel lockdown | Enable lockdown LSM early in init | Default lockdown mode | Can `ebpf.plugin` run with this? | +| :------------------------ | :-------------------------------- | :-------------------- | :------------------------------- | +| YES | NO | NO | YES | +| YES | Yes | None | YES | +| YES | Yes | Integrity | YES | +| YES | Yes | Confidentiality | NO | + +If you or your distribution compiled the kernel with the last combination, your system cannot load shared libraries +required to run `ebpf.plugin`. + +## Functions + +### ebpf_thread + +The eBPF plugin has a [function](/docs/top-monitoring-netdata-functions.md) named +`ebpf_thread` that controls its internal threads and helps to reduce the overhead on host. Using the function you +can run the plugin with all threads disabled and enable them only when you want to take a look in specific areas. + +#### List threads + +To list all threads status you can query directly the endpoint function: + +`http://localhost:19999/api/v1/function?function=ebpf_thread` + +It is also possible to query a specific thread adding keyword `thread` and thread name: + +`http://localhost:19999/api/v1/function?function=ebpf_thread%20thread:mount` + +#### Enable thread + +It is possible to enable a specific thread using the keyword `enable`: + +`http://localhost:19999/api/v1/function?function=ebpf_thread%20enable:mount` + +this will run thread `mount` during 300 seconds (5 minutes). You can specify a specific period by appending the period +after the thread name: + +`http://localhost:19999/api/v1/function?function=ebpf_thread%20enable:mount:600` + +in this example thread `mount` will run during 600 seconds (10 minutes). + +#### Disable thread + +It is also possible to stop any thread running using the keyword `disable`. For example, to disable `cachestat` you can +request: + +`http://localhost:19999/api/v1/function?function=ebpf_thread%20disable:cachestat` + +#### Debugging threads + +You can verify the impact of threads on the host by running the +[ebpf_thread_function.sh](https://github.com/netdata/netdata/blob/master/tests/ebpf/ebpf_thread_function.sh) +script on your environment. + +You can check the results of having threads running on your environment in the Netdata monitoring section on your +dashboard + +<img src="https://github.com/netdata/netdata/assets/49162938/91823573-114c-4c16-b634-cc46f7bb1bcf" alt="Threads running." /> + +### ebpf_socket + +The eBPF plugin has a [function](/docs/top-monitoring-netdata-functions.md) named +`ebpf_socket` that shows the current status of open sockets on host. + +#### Families + +The plugin shows by default sockets for IPV4 and IPV6, but it is possible to select a specific family by passing the +family as an argument: + +`http://localhost:19999/api/v1/function?function=ebpf_socket%20family:IPV4` + +#### Resolve + +The plugin resolves ports to service names by default. You can show the port number by disabling the name resolution: + +`http://localhost:19999/api/v1/function?function=ebpf_socket%20resolve:NO` + +#### CIDR + +The plugin shows connections for all possible destination IPs by default. You can limit the range by specifying the CIDR: + +`http://localhost:19999/api/v1/function?function=ebpf_socket%20cidr:192.168.1.0/24` + +#### PORT + +The plugin shows connections for all possible ports by default. You can limit the range by specifying a port or range +of ports: + +`http://localhost:19999/api/v1/function?function=ebpf_socket%20port:1-1024` diff --git a/src/collectors/ebpf.plugin/ebpf.c b/src/collectors/ebpf.plugin/ebpf.c new file mode 100644 index 000000000..de2b6e144 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf.c @@ -0,0 +1,4073 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include <sys/time.h> +#include <sys/resource.h> +#include <ifaddrs.h> + +#include "ebpf.h" +#include "ebpf_socket.h" +#include "ebpf_unittest.h" +#include "libnetdata/required_dummies.h" + +/***************************************************************** + * + * GLOBAL VARIABLES + * + *****************************************************************/ + +char *ebpf_plugin_dir = PLUGINS_DIR; +static char *ebpf_configured_log_dir = LOG_DIR; + +char *ebpf_algorithms[] = { EBPF_CHART_ALGORITHM_ABSOLUTE, EBPF_CHART_ALGORITHM_INCREMENTAL}; +struct config collector_config = { .first_section = NULL, + .last_section = NULL, + .mutex = NETDATA_MUTEX_INITIALIZER, + .index = { .avl_tree = { .root = NULL, .compar = appconfig_section_compare }, + .rwlock = AVL_LOCK_INITIALIZER } }; + +int running_on_kernel = 0; +int ebpf_nprocs; +int isrh = 0; +int main_thread_id = 0; +int process_pid_fd = -1; +static size_t global_iterations_counter = 1; +bool publish_internal_metrics = true; + +pthread_mutex_t lock; +pthread_mutex_t ebpf_exit_cleanup; +pthread_mutex_t collect_data_mutex; + +struct netdata_static_thread cgroup_integration_thread = { + .name = "EBPF CGROUP INT", + .config_section = NULL, + .config_name = NULL, + .env_name = NULL, + .enabled = 1, + .thread = NULL, + .init_routine = NULL, + .start_routine = NULL +}; + +ebpf_module_t ebpf_modules[] = { + { .info = {.thread_name = "process", + .config_name = "process", + .thread_description = NETDATA_EBPF_MODULE_PROCESS_DESC}, + .functions = {.start_routine = ebpf_process_thread, + .apps_routine = ebpf_process_create_apps_charts, + .fnct_routine = NULL}, + .enabled = NETDATA_THREAD_EBPF_NOT_RUNNING, + .update_every = EBPF_DEFAULT_UPDATE_EVERY, .global_charts = 1, .apps_charts = NETDATA_EBPF_APPS_FLAG_NO, + .apps_level = NETDATA_APPS_LEVEL_REAL_PARENT, .cgroup_charts = CONFIG_BOOLEAN_NO, .mode = MODE_ENTRY, .optional = 0, + .maps = NULL, .pid_map_size = ND_EBPF_DEFAULT_PID_SIZE, .names = NULL, .cfg = &process_config, + .config_file = NETDATA_PROCESS_CONFIG_FILE, + .kernels = NETDATA_V3_10 | NETDATA_V4_14 | NETDATA_V4_16 | NETDATA_V4_18 | NETDATA_V5_4 | NETDATA_V5_10 | + NETDATA_V5_14, + .load = EBPF_LOAD_LEGACY, .targets = NULL, .probe_links = NULL, .objects = NULL, + .thread = NULL, .maps_per_core = CONFIG_BOOLEAN_YES, .lifetime = EBPF_DEFAULT_LIFETIME, .running_time = 0 }, + { .info = {.thread_name = "socket", + .config_name = "socket", + .thread_description = NETDATA_EBPF_SOCKET_MODULE_DESC}, + .functions = {.start_routine = ebpf_socket_thread, + .apps_routine = ebpf_socket_create_apps_charts, + .fnct_routine = ebpf_socket_read_open_connections, + .fcnt_name = EBPF_FUNCTION_SOCKET, + .fcnt_desc = EBPF_PLUGIN_SOCKET_FUNCTION_DESCRIPTION, + .fcnt_thread_chart_name = NULL, + .fcnt_thread_lifetime_name = NULL}, + .enabled = NETDATA_THREAD_EBPF_NOT_RUNNING, + .update_every = EBPF_DEFAULT_UPDATE_EVERY, .global_charts = 1, .apps_charts = NETDATA_EBPF_APPS_FLAG_NO, + .apps_level = NETDATA_APPS_LEVEL_REAL_PARENT, .cgroup_charts = CONFIG_BOOLEAN_NO, .mode = MODE_ENTRY, .optional = 0, + .maps = NULL, + .pid_map_size = ND_EBPF_DEFAULT_PID_SIZE, .names = NULL, .cfg = &socket_config, + .config_file = NETDATA_NETWORK_CONFIG_FILE, + .kernels = NETDATA_V3_10 | NETDATA_V4_14 | NETDATA_V4_16 | NETDATA_V4_18 | NETDATA_V5_4 | NETDATA_V5_14, + .load = EBPF_LOAD_LEGACY, .targets = socket_targets, .probe_links = NULL, .objects = NULL, + .thread = NULL, .maps_per_core = CONFIG_BOOLEAN_YES, .lifetime = EBPF_DEFAULT_LIFETIME, .running_time = 0}, + { .info = {.thread_name = "cachestat", .config_name = "cachestat", .thread_description = NETDATA_EBPF_CACHESTAT_MODULE_DESC}, + .functions = {.start_routine = ebpf_cachestat_thread, + .apps_routine = ebpf_cachestat_create_apps_charts, + .fnct_routine = NULL}, + .enabled = NETDATA_THREAD_EBPF_NOT_RUNNING, + .update_every = EBPF_DEFAULT_UPDATE_EVERY, .global_charts = 1, .apps_charts = NETDATA_EBPF_APPS_FLAG_NO, + .apps_level = NETDATA_APPS_LEVEL_REAL_PARENT, .cgroup_charts = CONFIG_BOOLEAN_NO, .mode = MODE_ENTRY, .optional = 0, + .maps = cachestat_maps, .pid_map_size = ND_EBPF_DEFAULT_PID_SIZE, .names = NULL, .cfg = &cachestat_config, + .config_file = NETDATA_CACHESTAT_CONFIG_FILE, + .kernels = NETDATA_V3_10 | NETDATA_V4_14 | NETDATA_V4_16 | NETDATA_V4_18| + NETDATA_V5_4 | NETDATA_V5_14 | NETDATA_V5_15 | NETDATA_V5_16, + .load = EBPF_LOAD_LEGACY, .targets = cachestat_targets, .probe_links = NULL, .objects = NULL, + .thread = NULL, .maps_per_core = CONFIG_BOOLEAN_YES, .lifetime = EBPF_DEFAULT_LIFETIME, .running_time = 0}, + { .info = {.thread_name = "sync", + .config_name = "sync", + .thread_description = NETDATA_EBPF_SYNC_MODULE_DESC}, + .functions = {.start_routine = ebpf_sync_thread, + .apps_routine = NULL, + .fnct_routine = NULL}, + .enabled = NETDATA_THREAD_EBPF_NOT_RUNNING, .maps = NULL, + .update_every = EBPF_DEFAULT_UPDATE_EVERY, .global_charts = 1, .apps_charts = NETDATA_EBPF_APPS_FLAG_NO, + .apps_level = NETDATA_APPS_NOT_SET, .cgroup_charts = CONFIG_BOOLEAN_NO, .mode = MODE_ENTRY, .optional = 0, + .pid_map_size = ND_EBPF_DEFAULT_PID_SIZE, .names = NULL, .cfg = &sync_config, + .config_file = NETDATA_SYNC_CONFIG_FILE, + // All syscalls have the same kernels + .kernels = NETDATA_V3_10 | NETDATA_V4_14 | NETDATA_V4_16 | NETDATA_V4_18 | NETDATA_V5_4 | NETDATA_V5_14, + .load = EBPF_LOAD_LEGACY, .targets = sync_targets, .probe_links = NULL, .objects = NULL, + .thread = NULL, .maps_per_core = CONFIG_BOOLEAN_YES, .lifetime = EBPF_DEFAULT_LIFETIME, .running_time = 0}, + { .info = {.thread_name = "dc", + .config_name = "dc", + .thread_description = NETDATA_EBPF_DC_MODULE_DESC}, + .functions = {.start_routine = ebpf_dcstat_thread, + .apps_routine = ebpf_dcstat_create_apps_charts, + .fnct_routine = NULL}, + .enabled = NETDATA_THREAD_EBPF_NOT_RUNNING, + .update_every = EBPF_DEFAULT_UPDATE_EVERY, .global_charts = 1, .apps_charts = NETDATA_EBPF_APPS_FLAG_NO, + .apps_level = NETDATA_APPS_LEVEL_REAL_PARENT, .cgroup_charts = CONFIG_BOOLEAN_NO, .mode = MODE_ENTRY, .optional = 0, + .maps = dcstat_maps, + .pid_map_size = ND_EBPF_DEFAULT_PID_SIZE, .names = NULL, .cfg = &dcstat_config, + .config_file = NETDATA_DIRECTORY_DCSTAT_CONFIG_FILE, + .kernels = NETDATA_V3_10 | NETDATA_V4_14 | NETDATA_V4_16 | NETDATA_V4_18 | NETDATA_V5_4 | NETDATA_V5_14, + .load = EBPF_LOAD_LEGACY, .targets = dc_targets, .probe_links = NULL, .objects = NULL, + .thread = NULL, .maps_per_core = CONFIG_BOOLEAN_YES, .lifetime = EBPF_DEFAULT_LIFETIME, .running_time = 0}, + { .info = {.thread_name = "swap", .config_name = "swap", .thread_description = NETDATA_EBPF_SWAP_MODULE_DESC}, + .functions = {.start_routine = ebpf_swap_thread, + .apps_routine = ebpf_swap_create_apps_charts, + .fnct_routine = NULL}, + .enabled = NETDATA_THREAD_EBPF_NOT_RUNNING, + .update_every = EBPF_DEFAULT_UPDATE_EVERY, .global_charts = 1, .apps_charts = NETDATA_EBPF_APPS_FLAG_NO, + .apps_level = NETDATA_APPS_LEVEL_REAL_PARENT, .cgroup_charts = CONFIG_BOOLEAN_NO, .mode = MODE_ENTRY, .optional = 0, + .maps = NULL, + .pid_map_size = ND_EBPF_DEFAULT_PID_SIZE, .names = NULL, .cfg = &swap_config, + .config_file = NETDATA_DIRECTORY_SWAP_CONFIG_FILE, + .kernels = NETDATA_V3_10 | NETDATA_V4_14 | NETDATA_V4_16 | NETDATA_V4_18 | NETDATA_V5_4 | NETDATA_V5_14 | NETDATA_V6_8, + .load = EBPF_LOAD_LEGACY, .targets = swap_targets, .probe_links = NULL, .objects = NULL, + .thread = NULL, .maps_per_core = CONFIG_BOOLEAN_YES, .lifetime = EBPF_DEFAULT_LIFETIME, .running_time = 0}, + { .info = {.thread_name = "vfs", + .config_name = "vfs", + .thread_description = NETDATA_EBPF_VFS_MODULE_DESC}, + .functions = {.start_routine = ebpf_vfs_thread, + .apps_routine = ebpf_vfs_create_apps_charts, + .fnct_routine = NULL}, + .enabled = NETDATA_THREAD_EBPF_NOT_RUNNING, + .update_every = EBPF_DEFAULT_UPDATE_EVERY, .global_charts = 1, .apps_charts = NETDATA_EBPF_APPS_FLAG_NO, + .apps_level = NETDATA_APPS_LEVEL_REAL_PARENT, .cgroup_charts = CONFIG_BOOLEAN_NO, .mode = MODE_ENTRY, .optional = 0, + .maps = NULL, + .pid_map_size = ND_EBPF_DEFAULT_PID_SIZE, .names = NULL, .cfg = &vfs_config, + .config_file = NETDATA_DIRECTORY_VFS_CONFIG_FILE, + .kernels = NETDATA_V3_10 | NETDATA_V4_14 | NETDATA_V4_16 | NETDATA_V4_18 | NETDATA_V5_4 | NETDATA_V5_14, + .load = EBPF_LOAD_LEGACY, .targets = vfs_targets, .probe_links = NULL, .objects = NULL, + .thread = NULL, .maps_per_core = CONFIG_BOOLEAN_YES, .lifetime = EBPF_DEFAULT_LIFETIME, .running_time = 0}, + { .info = {.thread_name = "filesystem", .config_name = "filesystem", .thread_description = NETDATA_EBPF_FS_MODULE_DESC}, + .functions = {.start_routine = ebpf_filesystem_thread, + .apps_routine = NULL, + .fnct_routine = NULL}, + .enabled = NETDATA_THREAD_EBPF_NOT_RUNNING, + .update_every = EBPF_DEFAULT_UPDATE_EVERY, .global_charts = 1, .apps_charts = NETDATA_EBPF_APPS_FLAG_NO, + .apps_level = NETDATA_APPS_NOT_SET, .cgroup_charts = CONFIG_BOOLEAN_NO, .mode = MODE_ENTRY, .optional = 0, + .maps = NULL, .pid_map_size = ND_EBPF_DEFAULT_PID_SIZE, .names = NULL, .cfg = &fs_config, + .config_file = NETDATA_FILESYSTEM_CONFIG_FILE, + //We are setting kernels as zero, because we load eBPF programs according the kernel running. + .kernels = 0, .load = EBPF_LOAD_LEGACY, .targets = NULL, .probe_links = NULL, .objects = NULL, + .thread = NULL, .maps_per_core = CONFIG_BOOLEAN_YES, .lifetime = EBPF_DEFAULT_LIFETIME, .running_time = 0}, + { .info = {.thread_name = "disk", + .config_name = "disk", + .thread_description = NETDATA_EBPF_DISK_MODULE_DESC}, + .functions = {.start_routine = ebpf_disk_thread, + .apps_routine = NULL, + .fnct_routine = NULL}, + .enabled = NETDATA_THREAD_EBPF_NOT_RUNNING, + .update_every = EBPF_DEFAULT_UPDATE_EVERY, .global_charts = 1, .apps_charts = NETDATA_EBPF_APPS_FLAG_NO, + .apps_level = NETDATA_APPS_NOT_SET, .cgroup_charts = CONFIG_BOOLEAN_NO, .mode = MODE_ENTRY, .optional = 0, + .maps = NULL, .pid_map_size = ND_EBPF_DEFAULT_PID_SIZE, .names = NULL, .cfg = &disk_config, + .config_file = NETDATA_DISK_CONFIG_FILE, + .kernels = NETDATA_V3_10 | NETDATA_V4_14 | NETDATA_V4_16 | NETDATA_V4_18 | NETDATA_V5_4 | NETDATA_V5_14, + .load = EBPF_LOAD_LEGACY, .targets = NULL, .probe_links = NULL, .objects = NULL, + .thread = NULL, .maps_per_core = CONFIG_BOOLEAN_YES, .lifetime = EBPF_DEFAULT_LIFETIME, .running_time = 0}, + { .info = {.thread_name = "mount", + .config_name = "mount", + .thread_description = NETDATA_EBPF_MOUNT_MODULE_DESC}, + .functions = {.start_routine = ebpf_mount_thread, + .apps_routine = NULL, + .fnct_routine = NULL}, + .enabled = NETDATA_THREAD_EBPF_NOT_RUNNING, + .update_every = EBPF_DEFAULT_UPDATE_EVERY, .global_charts = 1, .apps_charts = NETDATA_EBPF_APPS_FLAG_NO, + .apps_level = NETDATA_APPS_NOT_SET, .cgroup_charts = CONFIG_BOOLEAN_NO, .mode = MODE_ENTRY, .optional = 0, + .maps = NULL, .pid_map_size = ND_EBPF_DEFAULT_PID_SIZE, .names = NULL, .cfg = &mount_config, + .config_file = NETDATA_MOUNT_CONFIG_FILE, + .kernels = NETDATA_V3_10 | NETDATA_V4_14 | NETDATA_V4_16 | NETDATA_V4_18 | NETDATA_V5_4 | NETDATA_V5_14, + .load = EBPF_LOAD_LEGACY, .targets = mount_targets, .probe_links = NULL, .objects = NULL, + .thread = NULL, .maps_per_core = CONFIG_BOOLEAN_YES, .lifetime = EBPF_DEFAULT_LIFETIME, .running_time = 0}, + { .info = { .thread_name = "fd", + .config_name = "fd", + .thread_description = NETDATA_EBPF_FD_MODULE_DESC}, + .functions = {.start_routine = ebpf_fd_thread, + .apps_routine = ebpf_fd_create_apps_charts, + .fnct_routine = NULL}, + .enabled = NETDATA_THREAD_EBPF_NOT_RUNNING, + .update_every = EBPF_DEFAULT_UPDATE_EVERY, .global_charts = 1, .apps_charts = NETDATA_EBPF_APPS_FLAG_NO, + .apps_level = NETDATA_APPS_LEVEL_REAL_PARENT, .cgroup_charts = CONFIG_BOOLEAN_NO, .mode = MODE_ENTRY, .optional = 0, + .maps = NULL, + .pid_map_size = ND_EBPF_DEFAULT_PID_SIZE, .names = NULL, .cfg = &fd_config, + .config_file = NETDATA_FD_CONFIG_FILE, + .kernels = NETDATA_V3_10 | NETDATA_V4_14 | NETDATA_V4_16 | NETDATA_V4_18 | NETDATA_V5_4 | NETDATA_V5_11 | + NETDATA_V5_14, + .load = EBPF_LOAD_LEGACY, .targets = fd_targets, .probe_links = NULL, .objects = NULL, + .thread = NULL, .maps_per_core = CONFIG_BOOLEAN_YES, .lifetime = EBPF_DEFAULT_LIFETIME, .running_time = 0}, + { .info = { .thread_name = "hardirq", + .config_name = "hardirq", + .thread_description = NETDATA_EBPF_HARDIRQ_MODULE_DESC}, + .functions = {.start_routine = ebpf_hardirq_thread, + .apps_routine = NULL, + .fnct_routine = NULL}, + .enabled = NETDATA_THREAD_EBPF_NOT_RUNNING, + .update_every = EBPF_DEFAULT_UPDATE_EVERY, .global_charts = 1, .apps_charts = NETDATA_EBPF_APPS_FLAG_NO, + .apps_level = NETDATA_APPS_NOT_SET, .cgroup_charts = CONFIG_BOOLEAN_NO, .mode = MODE_ENTRY, .optional = 0, + .maps = NULL, .pid_map_size = ND_EBPF_DEFAULT_PID_SIZE, .names = NULL, .cfg = &hardirq_config, + .config_file = NETDATA_HARDIRQ_CONFIG_FILE, + .kernels = NETDATA_V3_10 | NETDATA_V4_14 | NETDATA_V4_16 | NETDATA_V4_18 | NETDATA_V5_4 | NETDATA_V5_14, + .load = EBPF_LOAD_LEGACY, .targets = NULL, .probe_links = NULL, .objects = NULL, + .thread = NULL, .maps_per_core = CONFIG_BOOLEAN_YES, .lifetime = EBPF_DEFAULT_LIFETIME, .running_time = 0}, + { .info = { .thread_name = "softirq", + .config_name = "softirq", + .thread_description = NETDATA_EBPF_SOFTIRQ_MODULE_DESC}, + .functions = {.start_routine = ebpf_softirq_thread, + .apps_routine = NULL, + .fnct_routine = NULL }, + .enabled = NETDATA_THREAD_EBPF_NOT_RUNNING, + .update_every = EBPF_DEFAULT_UPDATE_EVERY, .global_charts = 1, .apps_charts = NETDATA_EBPF_APPS_FLAG_NO, + .apps_level = NETDATA_APPS_NOT_SET, .cgroup_charts = CONFIG_BOOLEAN_NO, .mode = MODE_ENTRY, .optional = 0, + .maps = NULL, .pid_map_size = ND_EBPF_DEFAULT_PID_SIZE, .names = NULL, .cfg = &softirq_config, + .config_file = NETDATA_SOFTIRQ_CONFIG_FILE, + .kernels = NETDATA_V3_10 | NETDATA_V4_14 | NETDATA_V4_16 | NETDATA_V4_18 | NETDATA_V5_4 | NETDATA_V5_14, + .load = EBPF_LOAD_LEGACY, .targets = NULL, .probe_links = NULL, .objects = NULL, + .thread = NULL, .maps_per_core = CONFIG_BOOLEAN_YES, .lifetime = EBPF_DEFAULT_LIFETIME, .running_time = 0}, + { .info = {.thread_name = "oomkill", + .config_name = "oomkill", + .thread_description = NETDATA_EBPF_OOMKILL_MODULE_DESC}, + .functions = {.start_routine = ebpf_oomkill_thread, + .apps_routine = ebpf_oomkill_create_apps_charts, + .fnct_routine = NULL},.enabled = NETDATA_THREAD_EBPF_NOT_RUNNING, + .update_every = EBPF_DEFAULT_UPDATE_EVERY, .global_charts = 1, .apps_charts = NETDATA_EBPF_APPS_FLAG_NO, + .apps_level = NETDATA_APPS_LEVEL_REAL_PARENT, .cgroup_charts = CONFIG_BOOLEAN_NO, .mode = MODE_ENTRY, .optional = 0, + .maps = NULL, + .pid_map_size = ND_EBPF_DEFAULT_PID_SIZE, .names = NULL, .cfg = &oomkill_config, + .config_file = NETDATA_OOMKILL_CONFIG_FILE, + .kernels = NETDATA_V4_14 | NETDATA_V4_16 | NETDATA_V4_18 | NETDATA_V5_4 | NETDATA_V5_14, + .load = EBPF_LOAD_LEGACY, .targets = NULL, .probe_links = NULL, .objects = NULL, + .thread = NULL, .maps_per_core = CONFIG_BOOLEAN_YES, .lifetime = EBPF_DEFAULT_LIFETIME, .running_time = 0}, + { .info = {.thread_name = "shm", + .config_name = "shm", + .thread_description = NETDATA_EBPF_SHM_MODULE_DESC}, + .functions = {.start_routine = ebpf_shm_thread, + .apps_routine = ebpf_shm_create_apps_charts, + .fnct_routine = NULL}, + .enabled = NETDATA_THREAD_EBPF_NOT_RUNNING, + .update_every = EBPF_DEFAULT_UPDATE_EVERY, .global_charts = 1, .apps_charts = NETDATA_EBPF_APPS_FLAG_NO, + .apps_level = NETDATA_APPS_LEVEL_REAL_PARENT, .cgroup_charts = CONFIG_BOOLEAN_NO, .mode = MODE_ENTRY, .optional = 0, + .maps = NULL, + .pid_map_size = ND_EBPF_DEFAULT_PID_SIZE, .names = NULL, .cfg = &shm_config, + .config_file = NETDATA_DIRECTORY_SHM_CONFIG_FILE, + .kernels = NETDATA_V3_10 | NETDATA_V4_14 | NETDATA_V4_16 | NETDATA_V4_18 | NETDATA_V5_4 | NETDATA_V5_14, + .load = EBPF_LOAD_LEGACY, .targets = shm_targets, .probe_links = NULL, .objects = NULL, + .thread = NULL, .maps_per_core = CONFIG_BOOLEAN_YES, .lifetime = EBPF_DEFAULT_LIFETIME, .running_time = 0}, + { .info = { .thread_name = "mdflush", + .config_name = "mdflush", + .thread_description = NETDATA_EBPF_MD_MODULE_DESC}, + .functions = {.start_routine = ebpf_mdflush_thread, + .apps_routine = NULL, + .fnct_routine = NULL}, + .enabled = NETDATA_THREAD_EBPF_NOT_RUNNING, + .update_every = EBPF_DEFAULT_UPDATE_EVERY, .global_charts = 1, .apps_charts = NETDATA_EBPF_APPS_FLAG_NO, + .apps_level = NETDATA_APPS_NOT_SET, .cgroup_charts = CONFIG_BOOLEAN_NO, .mode = MODE_ENTRY, .optional = 0, + .maps = NULL, .pid_map_size = ND_EBPF_DEFAULT_PID_SIZE, .names = NULL, .cfg = &mdflush_config, + .config_file = NETDATA_DIRECTORY_MDFLUSH_CONFIG_FILE, + .kernels = NETDATA_V3_10 | NETDATA_V4_14 | NETDATA_V4_16 | NETDATA_V4_18 | NETDATA_V5_4 | NETDATA_V5_14, + .load = EBPF_LOAD_LEGACY, .targets = mdflush_targets, .probe_links = NULL, .objects = NULL, + .thread = NULL, .maps_per_core = CONFIG_BOOLEAN_YES, .lifetime = EBPF_DEFAULT_LIFETIME, .running_time = 0}, + { .info = { .thread_name = "functions", + .config_name = "functions", + .thread_description = NETDATA_EBPF_FUNCTIONS_MODULE_DESC}, + .functions = {.start_routine = ebpf_function_thread, + .apps_routine = NULL, + .fnct_routine = NULL}, + .enabled = NETDATA_THREAD_EBPF_RUNNING, + .update_every = EBPF_DEFAULT_UPDATE_EVERY, .global_charts = 1, .apps_charts = NETDATA_EBPF_APPS_FLAG_NO, + .apps_level = NETDATA_APPS_NOT_SET, .cgroup_charts = CONFIG_BOOLEAN_NO, .mode = MODE_ENTRY, .optional = 0, + .maps = NULL, .pid_map_size = ND_EBPF_DEFAULT_PID_SIZE, .names = NULL, .cfg = NULL, + .config_file = NETDATA_DIRECTORY_FUNCTIONS_CONFIG_FILE, + .kernels = NETDATA_V3_10 | NETDATA_V4_14 | NETDATA_V4_16 | NETDATA_V4_18 | NETDATA_V5_4 | NETDATA_V5_14, + .load = EBPF_LOAD_LEGACY, .targets = NULL, .probe_links = NULL, .objects = NULL, + .thread = NULL, .maps_per_core = CONFIG_BOOLEAN_YES, .lifetime = EBPF_DEFAULT_LIFETIME, .running_time = 0}, + { .info = {.thread_name = NULL, .config_name = NULL}, + .functions = {.start_routine = NULL, .apps_routine = NULL, .fnct_routine = NULL}, + .enabled = NETDATA_THREAD_EBPF_NOT_RUNNING, .update_every = EBPF_DEFAULT_UPDATE_EVERY, + .global_charts = 0, .apps_charts = NETDATA_EBPF_APPS_FLAG_NO, .apps_level = NETDATA_APPS_NOT_SET, + .cgroup_charts = CONFIG_BOOLEAN_NO, .mode = MODE_ENTRY, .optional = 0, .maps = NULL, + .pid_map_size = 0, .names = NULL, .cfg = NULL, .kernels = 0, .load = EBPF_LOAD_LEGACY, + .targets = NULL, .probe_links = NULL, .objects = NULL, .thread = NULL, .maps_per_core = CONFIG_BOOLEAN_YES}, +}; + +struct netdata_static_thread ebpf_threads[] = { + { + .name = "EBPF PROCESS", + .config_section = NULL, + .config_name = NULL, + .env_name = NULL, + .enabled = 1, + .thread = NULL, + .init_routine = NULL, + .start_routine = NULL + }, + { + .name = "EBPF SOCKET", + .config_section = NULL, + .config_name = NULL, + .env_name = NULL, + .enabled = 1, + .thread = NULL, + .init_routine = NULL, + .start_routine = NULL + }, + { + .name = "EBPF CACHESTAT", + .config_section = NULL, + .config_name = NULL, + .env_name = NULL, + .enabled = 1, + .thread = NULL, + .init_routine = NULL, + .start_routine = NULL + }, + { + .name = "EBPF SYNC", + .config_section = NULL, + .config_name = NULL, + .env_name = NULL, + .enabled = 1, + .thread = NULL, + .init_routine = NULL, + .start_routine = NULL + }, + { + .name = "EBPF DCSTAT", + .config_section = NULL, + .config_name = NULL, + .env_name = NULL, + .enabled = 1, + .thread = NULL, + .init_routine = NULL, + .start_routine = NULL + }, + { + .name = "EBPF SWAP", + .config_section = NULL, + .config_name = NULL, + .env_name = NULL, + .enabled = 1, + .thread = NULL, + .init_routine = NULL, + .start_routine = NULL + }, + { + .name = "EBPF VFS", + .config_section = NULL, + .config_name = NULL, + .env_name = NULL, + .enabled = 1, + .thread = NULL, + .init_routine = NULL, + .start_routine = NULL + }, + { + .name = "EBPF FILESYSTEM", + .config_section = NULL, + .config_name = NULL, + .env_name = NULL, + .enabled = 1, + .thread = NULL, + .init_routine = NULL, + .start_routine = NULL + }, + { + .name = "EBPF DISK", + .config_section = NULL, + .config_name = NULL, + .env_name = NULL, + .enabled = 1, + .thread = NULL, + .init_routine = NULL, + .start_routine = NULL + }, + { + .name = "EBPF MOUNT", + .config_section = NULL, + .config_name = NULL, + .env_name = NULL, + .enabled = 1, + .thread = NULL, + .init_routine = NULL, + .start_routine = NULL + }, + { + .name = "EBPF FD", + .config_section = NULL, + .config_name = NULL, + .env_name = NULL, + .enabled = 1, + .thread = NULL, + .init_routine = NULL, + .start_routine = NULL + }, + { + .name = "EBPF HARDIRQ", + .config_section = NULL, + .config_name = NULL, + .env_name = NULL, + .enabled = 1, + .thread = NULL, + .init_routine = NULL, + .start_routine = NULL + }, + { + .name = "EBPF SOFTIRQ", + .config_section = NULL, + .config_name = NULL, + .env_name = NULL, + .enabled = 1, + .thread = NULL, + .init_routine = NULL, + .start_routine = NULL + }, + { + .name = "EBPF OOMKILL", + .config_section = NULL, + .config_name = NULL, + .env_name = NULL, + .enabled = 1, + .thread = NULL, + .init_routine = NULL, + .start_routine = NULL + }, + { + .name = "EBPF SHM", + .config_section = NULL, + .config_name = NULL, + .env_name = NULL, + .enabled = 1, + .thread = NULL, + .init_routine = NULL, + .start_routine = NULL + }, + { + .name = "EBPF MDFLUSH", + .config_section = NULL, + .config_name = NULL, + .env_name = NULL, + .enabled = 1, + .thread = NULL, + .init_routine = NULL, + .start_routine = NULL + }, + { + .name = "EBPF FUNCTIONS", + .config_section = NULL, + .config_name = NULL, + .env_name = NULL, +#ifdef NETDATA_DEV_MODE + .enabled = 1, +#else + .enabled = 0, +#endif + .thread = NULL, + .init_routine = NULL, + .start_routine = NULL + }, + { + .name = NULL, + .config_section = NULL, + .config_name = NULL, + .env_name = NULL, + .enabled = 0, + .thread = NULL, + .init_routine = NULL, + .start_routine = NULL + }, +}; + +ebpf_filesystem_partitions_t localfs[] = + {{.filesystem = "ext4", + .optional_filesystem = NULL, + .family = "ext4", + .objects = NULL, + .probe_links = NULL, + .flags = NETDATA_FILESYSTEM_FLAG_NO_PARTITION, + .enabled = CONFIG_BOOLEAN_YES, + .addresses = {.function = NULL, .addr = 0}, + .kernels = NETDATA_V3_10 | NETDATA_V4_14 | NETDATA_V4_16 | NETDATA_V4_18 | NETDATA_V5_4, + .fs_maps = NULL, + .fs_obj = NULL, + .functions = { "ext4_file_read_iter", + "ext4_file_write_iter", + "ext4_file_open", + "ext4_sync_file", + NULL }}, + {.filesystem = "xfs", + .optional_filesystem = NULL, + .family = "xfs", + .objects = NULL, + .probe_links = NULL, + .flags = NETDATA_FILESYSTEM_FLAG_NO_PARTITION, + .enabled = CONFIG_BOOLEAN_YES, + .addresses = {.function = NULL, .addr = 0}, + .kernels = NETDATA_V3_10 | NETDATA_V4_14 | NETDATA_V4_16 | NETDATA_V4_18 | NETDATA_V5_4, + .fs_maps = NULL, + .fs_obj = NULL, + .functions = { "xfs_file_read_iter", + "xfs_file_write_iter", + "xfs_file_open", + "xfs_file_fsync", + NULL }}, + {.filesystem = "nfs", + .optional_filesystem = "nfs4", + .family = "nfs", + .objects = NULL, + .probe_links = NULL, + .flags = NETDATA_FILESYSTEM_ATTR_CHARTS, + .enabled = CONFIG_BOOLEAN_YES, + .addresses = {.function = NULL, .addr = 0}, + .kernels = NETDATA_V3_10 | NETDATA_V4_14 | NETDATA_V4_16 | NETDATA_V4_18 | NETDATA_V5_4, + .fs_maps = NULL, + .fs_obj = NULL, + .functions = { "nfs_file_read", + "nfs_file_write", + "nfs_open", + "nfs_getattr", + NULL }}, // // "nfs4_file_open" - not present on all kernels + {.filesystem = "zfs", + .optional_filesystem = NULL, + .family = "zfs", + .objects = NULL, + .probe_links = NULL, + .flags = NETDATA_FILESYSTEM_FLAG_NO_PARTITION, + .enabled = CONFIG_BOOLEAN_YES, + .addresses = {.function = NULL, .addr = 0}, + .kernels = NETDATA_V3_10 | NETDATA_V4_14 | NETDATA_V4_16 | NETDATA_V4_18 | NETDATA_V5_4, + .fs_maps = NULL, + .fs_obj = NULL, + .functions = { "zpl_iter_read", + "zpl_iter_write", + "zpl_open", + "zpl_fsync", + NULL }}, + {.filesystem = "btrfs", + .optional_filesystem = NULL, + .family = "btrfs", + .objects = NULL, + .probe_links = NULL, + .flags = NETDATA_FILESYSTEM_FILL_ADDRESS_TABLE, + .enabled = CONFIG_BOOLEAN_YES, + .addresses = {.function = "btrfs_file_operations", .addr = 0}, + .kernels = NETDATA_V3_10 | NETDATA_V4_14 | NETDATA_V4_16 | NETDATA_V4_18 | NETDATA_V5_4 | NETDATA_V5_10, + .fs_maps = NULL, + .fs_obj = NULL, + .functions = { "btrfs_file_read_iter", + "btrfs_file_write_iter", + "btrfs_file_open", + "btrfs_sync_file", + NULL }}, + {.filesystem = NULL, + .optional_filesystem = NULL, + .family = NULL, + .objects = NULL, + .probe_links = NULL, + .flags = NETDATA_FILESYSTEM_FLAG_NO_PARTITION, + .enabled = CONFIG_BOOLEAN_YES, + .addresses = {.function = NULL, .addr = 0}, + .kernels = 0, .fs_maps = NULL, .fs_obj = NULL}}; + +ebpf_sync_syscalls_t local_syscalls[] = { + {.syscall = NETDATA_SYSCALLS_SYNC, .enabled = CONFIG_BOOLEAN_YES, .objects = NULL, .probe_links = NULL, +#ifdef LIBBPF_MAJOR_VERSION + .sync_obj = NULL, +#endif + .sync_maps = NULL + }, + {.syscall = NETDATA_SYSCALLS_SYNCFS, .enabled = CONFIG_BOOLEAN_YES, .objects = NULL, .probe_links = NULL, +#ifdef LIBBPF_MAJOR_VERSION + .sync_obj = NULL, +#endif + .sync_maps = NULL + }, + {.syscall = NETDATA_SYSCALLS_MSYNC, .enabled = CONFIG_BOOLEAN_YES, .objects = NULL, .probe_links = NULL, +#ifdef LIBBPF_MAJOR_VERSION + .sync_obj = NULL, +#endif + .sync_maps = NULL + }, + {.syscall = NETDATA_SYSCALLS_FSYNC, .enabled = CONFIG_BOOLEAN_YES, .objects = NULL, .probe_links = NULL, +#ifdef LIBBPF_MAJOR_VERSION + .sync_obj = NULL, +#endif + .sync_maps = NULL + }, + {.syscall = NETDATA_SYSCALLS_FDATASYNC, .enabled = CONFIG_BOOLEAN_YES, .objects = NULL, .probe_links = NULL, +#ifdef LIBBPF_MAJOR_VERSION + .sync_obj = NULL, +#endif + .sync_maps = NULL + }, + {.syscall = NETDATA_SYSCALLS_SYNC_FILE_RANGE, .enabled = CONFIG_BOOLEAN_YES, .objects = NULL, .probe_links = NULL, +#ifdef LIBBPF_MAJOR_VERSION + .sync_obj = NULL, +#endif + .sync_maps = NULL + }, + {.syscall = NULL, .enabled = CONFIG_BOOLEAN_NO, .objects = NULL, .probe_links = NULL, +#ifdef LIBBPF_MAJOR_VERSION + .sync_obj = NULL, +#endif + .sync_maps = NULL + } +}; + + +// Link with cgroup.plugin +netdata_ebpf_cgroup_shm_t shm_ebpf_cgroup = {NULL, NULL}; +int shm_fd_ebpf_cgroup = -1; +sem_t *shm_sem_ebpf_cgroup = SEM_FAILED; +pthread_mutex_t mutex_cgroup_shm; + +//Network viewer +ebpf_network_viewer_options_t network_viewer_opt; + +// Statistic +ebpf_plugin_stats_t plugin_statistics = {.core = 0, .legacy = 0, .running = 0, .threads = 0, .tracepoints = 0, + .probes = 0, .retprobes = 0, .trampolines = 0, .memlock_kern = 0, + .hash_tables = 0}; +netdata_ebpf_judy_pid_t ebpf_judy_pid = {.pid_table = NULL, .index = {.JudyLArray = NULL}}; +bool ebpf_plugin_exit = false; + +#ifdef LIBBPF_MAJOR_VERSION +struct btf *default_btf = NULL; +struct cachestat_bpf *cachestat_bpf_obj = NULL; +struct dc_bpf *dc_bpf_obj = NULL; +struct disk_bpf *disk_bpf_obj = NULL; +struct fd_bpf *fd_bpf_obj = NULL; +struct hardirq_bpf *hardirq_bpf_obj = NULL; +struct mdflush_bpf *mdflush_bpf_obj = NULL; +struct mount_bpf *mount_bpf_obj = NULL; +struct shm_bpf *shm_bpf_obj = NULL; +struct socket_bpf *socket_bpf_obj = NULL; +struct swap_bpf *bpf_obj = NULL; +struct vfs_bpf *vfs_bpf_obj = NULL; +#else +void *default_btf = NULL; +#endif +char *btf_path = NULL; + +/***************************************************************** + * + * FUNCTIONS USED TO MANIPULATE JUDY ARRAY + * + *****************************************************************/ + +/** + * Hashtable insert unsafe + * + * Find or create a value associated to the index + * + * @return The lsocket = 0 when new item added to the array otherwise the existing item value is returned in *lsocket + * we return a pointer to a pointer, so that the caller can put anything needed at the value of the index. + * The pointer to pointer we return has to be used before any other operation that may change the index (insert/delete). + * + */ +void **ebpf_judy_insert_unsafe(PPvoid_t arr, Word_t key) +{ + JError_t J_Error; + Pvoid_t *idx = JudyLIns(arr, key, &J_Error); + if (unlikely(idx == PJERR)) { + netdata_log_error("Cannot add PID to JudyL, JU_ERRNO_* == %u, ID == %d", + JU_ERRNO(&J_Error), JU_ERRID(&J_Error)); + } + + return idx; +} + +/** + * Get PID from judy + * + * Get a pointer for the `pid` from judy_array; + * + * @param judy_array a judy array where PID is the primary key + * @param pid pid stored. + */ +netdata_ebpf_judy_pid_stats_t *ebpf_get_pid_from_judy_unsafe(PPvoid_t judy_array, uint32_t pid) +{ + netdata_ebpf_judy_pid_stats_t **pid_pptr = + (netdata_ebpf_judy_pid_stats_t **)ebpf_judy_insert_unsafe(judy_array, pid); + netdata_ebpf_judy_pid_stats_t *pid_ptr = *pid_pptr; + if (likely(*pid_pptr == NULL)) { + // a new PID added to the index + *pid_pptr = aral_mallocz(ebpf_judy_pid.pid_table); + + pid_ptr = *pid_pptr; + + pid_ptr->cmdline = NULL; + pid_ptr->socket_stats.JudyLArray = NULL; + rw_spinlock_init(&pid_ptr->socket_stats.rw_spinlock); + } + + return pid_ptr; +} + +/***************************************************************** + * + * FUNCTIONS USED TO ALLOCATE APPS/CGROUP MEMORIES (ARAL) + * + *****************************************************************/ + +/** + * Allocate PID ARAL + * + * Allocate memory using ARAL functions to speed up processing. + * + * @param name the internal name used for allocated region. + * @param size size of each element inside allocated space + * + * @return It returns the address on success and NULL otherwise. + */ +ARAL *ebpf_allocate_pid_aral(char *name, size_t size) +{ + static size_t max_elements = NETDATA_EBPF_ALLOC_MAX_PID; + if (max_elements < NETDATA_EBPF_ALLOC_MIN_ELEMENTS) { + netdata_log_error("Number of elements given is too small, adjusting it for %d", NETDATA_EBPF_ALLOC_MIN_ELEMENTS); + max_elements = NETDATA_EBPF_ALLOC_MIN_ELEMENTS; + } + + return aral_create(name, size, + 0, max_elements, + NULL, NULL, NULL, false, false); +} + +/***************************************************************** + * + * FUNCTIONS USED TO CLEAN MEMORY AND OPERATE SYSTEM FILES + * + *****************************************************************/ + +/** + * Wait to avoid possible coredumps while process is closing. + */ +static inline void ebpf_check_before2go() +{ + int i = EBPF_OPTION_ALL_CHARTS; + usec_t max = USEC_PER_SEC, step = 200000; + while (i && max) { + max -= step; + sleep_usec(step); + i = 0; + int j; + pthread_mutex_lock(&ebpf_exit_cleanup); + for (j = 0; ebpf_modules[j].info.thread_name != NULL; j++) { + if (ebpf_modules[j].enabled < NETDATA_THREAD_EBPF_STOPPING) + i++; + } + pthread_mutex_unlock(&ebpf_exit_cleanup); + } + + if (i) { + netdata_log_error("eBPF cannot unload all threads on time, but it will go away"); + } +} + +/** + * Close the collector gracefully + */ +static void ebpf_exit() +{ +#ifdef LIBBPF_MAJOR_VERSION + pthread_mutex_lock(&ebpf_exit_cleanup); + if (default_btf) { + btf__free(default_btf); + default_btf = NULL; + } + pthread_mutex_unlock(&ebpf_exit_cleanup); +#endif + + char filename[FILENAME_MAX + 1]; + ebpf_pid_file(filename, FILENAME_MAX); + if (unlink(filename)) + netdata_log_error("Cannot remove PID file %s", filename); + +#ifdef NETDATA_INTERNAL_CHECKS + netdata_log_error("Good bye world! I was PID %d", main_thread_id); +#endif + fprintf(stdout, "EXIT\n"); + fflush(stdout); + + ebpf_check_before2go(); + pthread_mutex_lock(&mutex_cgroup_shm); + if (shm_ebpf_cgroup.header) { + ebpf_unmap_cgroup_shared_memory(); + shm_unlink(NETDATA_SHARED_MEMORY_EBPF_CGROUP_NAME); + } + pthread_mutex_unlock(&mutex_cgroup_shm); + + exit(0); +} + +/** + * Unload loegacy code + * + * @param objects objects loaded from eBPF programs + * @param probe_links links from loader + */ +void ebpf_unload_legacy_code(struct bpf_object *objects, struct bpf_link **probe_links) +{ + if (!probe_links || !objects) + return; + + struct bpf_program *prog; + size_t j = 0 ; + bpf_object__for_each_program(prog, objects) { + bpf_link__destroy(probe_links[j]); + j++; + } + freez(probe_links); + if (objects) + bpf_object__close(objects); +} + +/** + * Unload Unique maps + * + * This function unload all BPF maps from threads using one unique BPF object. + */ +static void ebpf_unload_unique_maps() +{ + int i; + for (i = 0; ebpf_modules[i].info.thread_name; i++) { + // These threads are cleaned with other functions + if (i != EBPF_MODULE_SOCKET_IDX) + continue; + + if (ebpf_modules[i].enabled != NETDATA_THREAD_EBPF_STOPPED) { + if (ebpf_modules[i].enabled != NETDATA_THREAD_EBPF_NOT_RUNNING) + netdata_log_error("Cannot unload maps for thread %s, because it is not stopped.", + ebpf_modules[i].info.thread_name); + + continue; + } + + if (ebpf_modules[i].load == EBPF_LOAD_LEGACY) { + ebpf_unload_legacy_code(ebpf_modules[i].objects, ebpf_modules[i].probe_links); + continue; + } + +#ifdef LIBBPF_MAJOR_VERSION + if (socket_bpf_obj) + socket_bpf__destroy(socket_bpf_obj); +#endif + } +} + +/** + * Unload filesystem maps + * + * This function unload all BPF maps from filesystem thread. + */ +static void ebpf_unload_filesystems() +{ + if (ebpf_modules[EBPF_MODULE_FILESYSTEM_IDX].enabled == NETDATA_THREAD_EBPF_NOT_RUNNING || + ebpf_modules[EBPF_MODULE_FILESYSTEM_IDX].enabled < NETDATA_THREAD_EBPF_STOPPING || + ebpf_modules[EBPF_MODULE_FILESYSTEM_IDX].load != EBPF_LOAD_LEGACY) + return; + + int i; + for (i = 0; localfs[i].filesystem != NULL; i++) { + if (!localfs[i].objects) + continue; + + ebpf_unload_legacy_code(localfs[i].objects, localfs[i].probe_links); + } +} + +/** + * Unload sync maps + * + * This function unload all BPF maps from sync thread. + */ +static void ebpf_unload_sync() +{ + if (ebpf_modules[EBPF_MODULE_SYNC_IDX].enabled == NETDATA_THREAD_EBPF_NOT_RUNNING || + ebpf_modules[EBPF_MODULE_SYNC_IDX].enabled < NETDATA_THREAD_EBPF_STOPPING) + return; + + int i; + for (i = 0; local_syscalls[i].syscall != NULL; i++) { + if (!local_syscalls[i].enabled) + continue; + +#ifdef LIBBPF_MAJOR_VERSION + if (local_syscalls[i].sync_obj) { + sync_bpf__destroy(local_syscalls[i].sync_obj); + continue; + } +#endif + ebpf_unload_legacy_code(local_syscalls[i].objects, local_syscalls[i].probe_links); + } +} + +/** + * Close the collector gracefully + * + * @param sig is the signal number used to close the collector + */ +void ebpf_stop_threads(int sig) +{ + UNUSED(sig); + static int only_one = 0; + + // Child thread should be closed by itself. + pthread_mutex_lock(&ebpf_exit_cleanup); + if (main_thread_id != gettid_cached() || only_one) { + pthread_mutex_unlock(&ebpf_exit_cleanup); + return; + } + only_one = 1; + int i; + for (i = 0; ebpf_modules[i].info.thread_name != NULL; i++) { + if (ebpf_modules[i].enabled < NETDATA_THREAD_EBPF_STOPPING) { + nd_thread_signal_cancel(ebpf_modules[i].thread->thread); +#ifdef NETDATA_DEV_MODE + netdata_log_info("Sending cancel for thread %s", ebpf_modules[i].info.thread_name); +#endif + } + } + pthread_mutex_unlock(&ebpf_exit_cleanup); + + for (i = 0; ebpf_modules[i].info.thread_name != NULL; i++) { + if (ebpf_threads[i].thread) + nd_thread_join(ebpf_threads[i].thread); + } + + ebpf_plugin_exit = true; + + pthread_mutex_lock(&mutex_cgroup_shm); + nd_thread_signal_cancel(cgroup_integration_thread.thread); +#ifdef NETDATA_DEV_MODE + netdata_log_info("Sending cancel for thread %s", cgroup_integration_thread.name); +#endif + pthread_mutex_unlock(&mutex_cgroup_shm); + + ebpf_check_before2go(); + + pthread_mutex_lock(&ebpf_exit_cleanup); + ebpf_unload_unique_maps(); + ebpf_unload_filesystems(); + ebpf_unload_sync(); + pthread_mutex_unlock(&ebpf_exit_cleanup); + + ebpf_exit(); +} + +/***************************************************************** + * + * FUNCTIONS TO CREATE CHARTS + * + *****************************************************************/ + +/** + * Create apps for module + * + * Create apps chart that will be used with specific module + * + * @param em the module main structure. + * @param root a pointer for the targets. + */ +static inline void ebpf_create_apps_for_module(ebpf_module_t *em, struct ebpf_target *root) { + if (em->enabled < NETDATA_THREAD_EBPF_STOPPING && em->apps_charts && em->functions.apps_routine) + em->functions.apps_routine(em, root); +} + +/** + * Create apps charts + * + * Call ebpf_create_chart to create the charts on apps submenu. + * + * @param root a pointer for the targets. + */ +static void ebpf_create_apps_charts(struct ebpf_target *root) +{ + if (unlikely(!ebpf_all_pids)) + return; + + struct ebpf_target *w; + int newly_added = 0; + + for (w = root; w; w = w->next) { + if (w->target) + continue; + + if (unlikely(w->processes && (debug_enabled || w->debug_enabled))) { + struct ebpf_pid_on_target *pid_on_target; + + fprintf( + stderr, "ebpf.plugin: target '%s' has aggregated %u process%s:", w->name, w->processes, + (w->processes == 1) ? "" : "es"); + + for (pid_on_target = w->root_pid; pid_on_target; pid_on_target = pid_on_target->next) { + fprintf(stderr, " %d", pid_on_target->pid); + } + + fputc('\n', stderr); + } + + if (!w->exposed && w->processes) { + newly_added++; + w->exposed = 1; + if (debug_enabled || w->debug_enabled) + debug_log_int("%s just added - regenerating charts.", w->name); + } + } + + int i; + if (!newly_added) { + for (i = 0; i < EBPF_MODULE_FUNCTION_IDX ; i++) { + ebpf_module_t *current = &ebpf_modules[i]; + if (current->apps_charts & NETDATA_EBPF_APPS_FLAG_CHART_CREATED) + continue; + + ebpf_create_apps_for_module(current, root); + } + return; + } + + for (i = 0; i < EBPF_MODULE_FUNCTION_IDX ; i++) { + ebpf_module_t *current = &ebpf_modules[i]; + ebpf_create_apps_for_module(current, root); + } +} + +/** + * Get a value from a structure. + * + * @param basis it is the first address of the structure + * @param offset it is the offset of the data you want to access. + * @return + */ +collected_number get_value_from_structure(char *basis, size_t offset) +{ + collected_number *value = (collected_number *)(basis + offset); + + collected_number ret = (collected_number)llabs(*value); + // this reset is necessary to avoid keep a constant value while processing is not executing a task + *value = 0; + + return ret; +} + +/** + * Write set command on standard output + * + * @param dim the dimension name + * @param value the value for the dimension + */ +void write_chart_dimension(char *dim, long long value) +{ + printf("SET %s = %lld\n", dim, value); +} + +/** + * Call the necessary functions to create a chart. + * + * @param name the chart name + * @param family the chart family + * @param move the pointer with the values that will be published + * @param end the number of values that will be written on standard output + * + * @return It returns a variable that maps the charts that did not have zero values. + */ +void write_count_chart(char *name, char *family, netdata_publish_syscall_t *move, uint32_t end) +{ + ebpf_write_begin_chart(family, name, ""); + + uint32_t i = 0; + while (move && i < end) { + write_chart_dimension(move->name, move->ncall); + + move = move->next; + i++; + } + + ebpf_write_end_chart(); +} + +/** + * Call the necessary functions to create a chart. + * + * @param name the chart name + * @param family the chart family + * @param move the pointer with the values that will be published + * @param end the number of values that will be written on standard output + */ +void write_err_chart(char *name, char *family, netdata_publish_syscall_t *move, int end) +{ + ebpf_write_begin_chart(family, name, ""); + + int i = 0; + while (move && i < end) { + write_chart_dimension(move->name, move->nerr); + + move = move->next; + i++; + } + + ebpf_write_end_chart(); +} + +/** + * Write charts + * + * Write the current information to publish the charts. + * + * @param family chart family + * @param chart chart id + * @param dim dimension name + * @param v1 value. + */ +void ebpf_one_dimension_write_charts(char *family, char *chart, char *dim, long long v1) +{ + ebpf_write_begin_chart(family, chart, ""); + + write_chart_dimension(dim, v1); + + ebpf_write_end_chart(); +} + +/** + * Call the necessary functions to create a chart. + * + * @param chart the chart name + * @param family the chart family + * @param dwrite the dimension name + * @param vwrite the value for previous dimension + * @param dread the dimension name + * @param vread the value for previous dimension + * + * @return It returns a variable that maps the charts that did not have zero values. + */ +void write_io_chart(char *chart, char *family, char *dwrite, long long vwrite, char *dread, long long vread) +{ + ebpf_write_begin_chart(family, chart, ""); + + write_chart_dimension(dwrite, vwrite); + write_chart_dimension(dread, vread); + + ebpf_write_end_chart(); +} + +/** + * Write chart cmd on standard output + * + * @param type chart type + * @param id chart id (the apps group name). + * @param suffix suffix to differentiate charts + * @param title chart title + * @param units units label + * @param family group name used to attach the chart on dashboard + * @param charttype chart type + * @param context chart context + * @param order chart order + * @param update_every update interval used by plugin + * @param module chart module name, this is the eBPF thread. + */ +void ebpf_write_chart_cmd(char *type, char *id, char *suffix, char *title, char *units, char *family, + char *charttype, char *context, int order, int update_every, char *module) +{ + printf("CHART %s.%s%s '' '%s' '%s' '%s' '%s' '%s' %d %d '' 'ebpf.plugin' '%s'\n", + type, + id, + suffix, + title, + units, + (family)?family:"", + (context)?context:"", + (charttype)?charttype:"", + order, + update_every, + module); +} + +/** + * Write chart cmd on standard output + * + * @param type chart type + * @param id chart id + * @param suffix add suffix to obsolete charts. + * @param title chart title + * @param units units label + * @param family group name used to attach the chart on dashboard + * @param charttype chart type + * @param context chart context + * @param order chart order + * @param update_every value to overwrite the update frequency set by the server. + */ +void ebpf_write_chart_obsolete(char *type, char *id, char *suffix, char *title, char *units, char *family, + char *charttype, char *context, int order, int update_every) +{ + printf("CHART %s.%s%s '' '%s' '%s' '%s' '%s' '%s' %d %d 'obsolete'\n", + type, + id, + suffix, + title, + units, + (family)?family:"", + (context)?context:"", + (charttype)?charttype:"", + order, + update_every); +} + +/** + * Write the dimension command on standard output + * + * @param name the dimension name + * @param id the dimension id + * @param algo the dimension algorithm + */ +void ebpf_write_global_dimension(char *name, char *id, char *algorithm) +{ + printf("DIMENSION %s %s %s 1 1\n", name, id, algorithm); +} + +/** + * Call ebpf_write_global_dimension to create the dimensions for a specific chart + * + * @param ptr a pointer to a structure of the type netdata_publish_syscall_t + * @param end the number of dimensions for the structure ptr + */ +void ebpf_create_global_dimension(void *ptr, int end) +{ + netdata_publish_syscall_t *move = ptr; + + int i = 0; + while (move && i < end) { + ebpf_write_global_dimension(move->name, move->dimension, move->algorithm); + + move = move->next; + i++; + } +} + +/** + * Call write_chart_cmd to create the charts + * + * @param type chart type + * @param id chart id + * @param title chart title + * @param units axis label + * @param family group name used to attach the chart on dashboard + * @param context chart context + * @param charttype chart type + * @param order order number of the specified chart + * @param ncd a pointer to a function called to create dimensions + * @param move a pointer for a structure that has the dimensions + * @param end number of dimensions for the chart created + * @param update_every update interval used with chart. + * @param module chart module name, this is the eBPF thread. + */ +void ebpf_create_chart(char *type, + char *id, + char *title, + char *units, + char *family, + char *context, + char *charttype, + int order, + void (*ncd)(void *, int), + void *move, + int end, + int update_every, + char *module) +{ + ebpf_write_chart_cmd(type, id, "", title, units, family, charttype, context, order, update_every, module); + + if (ncd) { + ncd(move, end); + } +} + +/** + * Call the necessary functions to create a name. + * + * @param family family name + * @param name chart name + * @param hist0 histogram values + * @param dimensions dimension values. + * @param end number of bins that will be sent to Netdata. + * + * @return It returns a variable that maps the charts that did not have zero values. + */ +void write_histogram_chart(char *family, char *name, const netdata_idx_t *hist, char **dimensions, uint32_t end) +{ + ebpf_write_begin_chart(family, name, ""); + + uint32_t i; + for (i = 0; i < end; i++) { + write_chart_dimension(dimensions[i], (long long) hist[i]); + } + + ebpf_write_end_chart(); + + fflush(stdout); +} + +/** + * ARAL Charts + * + * Add chart to monitor ARAL usage + * Caller must call this function with mutex locked. + * + * @param name the name used to create aral + * @param em a pointer to the structure with the default values. + */ +int ebpf_statistic_create_aral_chart(char *name, ebpf_module_t *em) +{ + static int priority = NETATA_EBPF_ORDER_STAT_ARAL_BEGIN; + char *mem = { NETDATA_EBPF_STAT_DIMENSION_MEMORY }; + char *aral = { NETDATA_EBPF_STAT_DIMENSION_ARAL }; + + snprintfz(em->memory_usage, NETDATA_EBPF_CHART_MEM_LENGTH -1, "aral_%s_size", name); + snprintfz(em->memory_allocations, NETDATA_EBPF_CHART_MEM_LENGTH -1, "aral_%s_alloc", name); + + ebpf_write_chart_cmd(NETDATA_MONITORING_FAMILY, + em->memory_usage, + "", + "Bytes allocated for ARAL.", + "bytes", + NETDATA_EBPF_FAMILY, + NETDATA_EBPF_CHART_TYPE_STACKED, + "netdata.ebpf_aral_stat_size", + priority++, + em->update_every, + NETDATA_EBPF_MODULE_NAME_PROCESS); + + ebpf_write_global_dimension(mem, + mem, + ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX]); + + ebpf_write_chart_cmd(NETDATA_MONITORING_FAMILY, + em->memory_allocations, + "", + "Calls to allocate memory.", + "calls", + NETDATA_EBPF_FAMILY, + NETDATA_EBPF_CHART_TYPE_STACKED, + "netdata.ebpf_aral_stat_alloc", + priority++, + em->update_every, + NETDATA_EBPF_MODULE_NAME_PROCESS); + + ebpf_write_global_dimension(aral, + aral, + ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX]); + + return priority - 2; +} + +/** + * ARAL Charts + * + * Add chart to monitor ARAL usage + * Caller must call this function with mutex locked. + * + * @param em a pointer to the structure with the default values. + * @param prio the initial priority used to disable charts. + */ +void ebpf_statistic_obsolete_aral_chart(ebpf_module_t *em, int prio) +{ + ebpf_write_chart_obsolete(NETDATA_MONITORING_FAMILY, + em->memory_allocations, + "", + "Calls to allocate memory.", + "calls", + NETDATA_EBPF_FAMILY, + NETDATA_EBPF_CHART_TYPE_STACKED, + "netdata.ebpf_aral_stat_alloc", + prio++, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_MONITORING_FAMILY, + em->memory_allocations, + "", + "Calls to allocate memory.", + "calls", + NETDATA_EBPF_FAMILY, + NETDATA_EBPF_CHART_TYPE_STACKED, + "netdata.ebpf_aral_stat_alloc", + prio++, + em->update_every); +} + +/** + * Send data from aral chart + * + * Send data for eBPF plugin + * + * @param memory a pointer to the allocated address + * @param em a pointer to the structure with the default values. + */ +void ebpf_send_data_aral_chart(ARAL *memory, ebpf_module_t *em) +{ + char *mem = { NETDATA_EBPF_STAT_DIMENSION_MEMORY }; + char *aral = { NETDATA_EBPF_STAT_DIMENSION_ARAL }; + + struct aral_statistics *stats = aral_statistics(memory); + + ebpf_write_begin_chart(NETDATA_MONITORING_FAMILY, em->memory_usage, ""); + write_chart_dimension(mem, (long long)stats->structures.allocated_bytes); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_MONITORING_FAMILY, em->memory_allocations, ""); + write_chart_dimension(aral, (long long)stats->structures.allocations); + ebpf_write_end_chart(); +} + +/***************************************************************** + * + * FUNCTIONS TO READ GLOBAL HASH TABLES + * + *****************************************************************/ + +/** + * Read Global Table Stats + * + * Read data from specified table (map_fd) using array allocated inside thread(values) and storing + * them in stats vector starting from the first position. + * + * For PID tables is recommended to use a function to parse the specific data. + * + * @param stats vector used to store data + * @param values helper to read data from hash tables. + * @param map_fd table that has data + * @param maps_per_core Is necessary to read data from all cores? + * @param begin initial value to query hash table + * @param end last value that will not be used. + */ +void ebpf_read_global_table_stats(netdata_idx_t *stats, + netdata_idx_t *values, + int map_fd, + int maps_per_core, + uint32_t begin, + uint32_t end) +{ + uint32_t idx, order; + + for (idx = begin, order = 0; idx < end; idx++, order++) { + if (!bpf_map_lookup_elem(map_fd, &idx, values)) { + int i; + int before = (maps_per_core) ? ebpf_nprocs: 1; + netdata_idx_t total = 0; + for (i = 0; i < before; i++) + total += values[i]; + + stats[order] = total; + } + } +} + +/***************************************************************** + * + * FUNCTIONS USED WITH SOCKET + * + *****************************************************************/ + +/** + * Netmask + * + * Copied from iprange (https://github.com/firehol/iprange/blob/master/iprange.h) + * + * @param prefix create the netmask based in the CIDR value. + * + * @return + */ +static inline in_addr_t ebpf_netmask(int prefix) { + + if (prefix == 0) + return (~((in_addr_t) - 1)); + else + return (in_addr_t)(~((1 << (32 - prefix)) - 1)); + +} + +/** + * Broadcast + * + * Copied from iprange (https://github.com/firehol/iprange/blob/master/iprange.h) + * + * @param addr is the ip address + * @param prefix is the CIDR value. + * + * @return It returns the last address of the range + */ +static inline in_addr_t ebpf_broadcast(in_addr_t addr, int prefix) +{ + return (addr | ~ebpf_netmask(prefix)); +} + +/** + * Network + * + * Copied from iprange (https://github.com/firehol/iprange/blob/master/iprange.h) + * + * @param addr is the ip address + * @param prefix is the CIDR value. + * + * @return It returns the first address of the range. + */ +static inline in_addr_t ebpf_ipv4_network(in_addr_t addr, int prefix) +{ + return (addr & ebpf_netmask(prefix)); +} + +/** + * Calculate ipv6 first address + * + * @param out the address to store the first address. + * @param in the address used to do the math. + * @param prefix number of bits used to calculate the address + */ +static void get_ipv6_first_addr(union netdata_ip_t *out, union netdata_ip_t *in, uint64_t prefix) +{ + uint64_t mask,tmp; + uint64_t ret[2]; + + memcpy(ret, in->addr32, sizeof(union netdata_ip_t)); + + if (prefix == 128) { + memcpy(out->addr32, in->addr32, sizeof(union netdata_ip_t)); + return; + } else if (!prefix) { + ret[0] = ret[1] = 0; + memcpy(out->addr32, ret, sizeof(union netdata_ip_t)); + return; + } else if (prefix <= 64) { + ret[1] = 0ULL; + + tmp = be64toh(ret[0]); + mask = 0xFFFFFFFFFFFFFFFFULL << (64 - prefix); + tmp &= mask; + ret[0] = htobe64(tmp); + } else { + mask = 0xFFFFFFFFFFFFFFFFULL << (128 - prefix); + tmp = be64toh(ret[1]); + tmp &= mask; + ret[1] = htobe64(tmp); + } + + memcpy(out->addr32, ret, sizeof(union netdata_ip_t)); +} + +/** + * Get IPV6 Last Address + * + * @param out the address to store the last address. + * @param in the address used to do the math. + * @param prefix number of bits used to calculate the address + */ +static void get_ipv6_last_addr(union netdata_ip_t *out, union netdata_ip_t *in, uint64_t prefix) +{ + uint64_t mask,tmp; + uint64_t ret[2]; + memcpy(ret, in->addr32, sizeof(union netdata_ip_t)); + + if (prefix == 128) { + memcpy(out->addr32, in->addr32, sizeof(union netdata_ip_t)); + return; + } else if (!prefix) { + ret[0] = ret[1] = 0xFFFFFFFFFFFFFFFF; + memcpy(out->addr32, ret, sizeof(union netdata_ip_t)); + return; + } else if (prefix <= 64) { + ret[1] = 0xFFFFFFFFFFFFFFFFULL; + + tmp = be64toh(ret[0]); + mask = 0xFFFFFFFFFFFFFFFFULL << (64 - prefix); + tmp |= ~mask; + ret[0] = htobe64(tmp); + } else { + mask = 0xFFFFFFFFFFFFFFFFULL << (128 - prefix); + tmp = be64toh(ret[1]); + tmp |= ~mask; + ret[1] = htobe64(tmp); + } + + memcpy(out->addr32, ret, sizeof(union netdata_ip_t)); +} + +/** + * IP to network long + * + * @param dst the vector to store the result + * @param ip the source ip given by our users. + * @param domain the ip domain (IPV4 or IPV6) + * @param source the original string + * + * @return it returns 0 on success and -1 otherwise. + */ +static inline int ebpf_ip2nl(uint8_t *dst, char *ip, int domain, char *source) +{ + if (inet_pton(domain, ip, dst) <= 0) { + netdata_log_error("The address specified (%s) is invalid ", source); + return -1; + } + + return 0; +} + +/** + * Clean port Structure + * + * Clean the allocated list. + * + * @param clean the list that will be cleaned + */ +void ebpf_clean_port_structure(ebpf_network_viewer_port_list_t **clean) +{ + ebpf_network_viewer_port_list_t *move = *clean; + while (move) { + ebpf_network_viewer_port_list_t *next = move->next; + freez(move->value); + freez(move); + + move = next; + } + *clean = NULL; +} + +/** + * Clean IP structure + * + * Clean the allocated list. + * + * @param clean the list that will be cleaned + */ +void ebpf_clean_ip_structure(ebpf_network_viewer_ip_list_t **clean) +{ + ebpf_network_viewer_ip_list_t *move = *clean; + while (move) { + ebpf_network_viewer_ip_list_t *next = move->next; + freez(move->value); + freez(move); + + move = next; + } + *clean = NULL; +} + +/** + * Parse IP List + * + * Parse IP list and link it. + * + * @param out a pointer to store the link list + * @param ip the value given as parameter + */ +static void ebpf_parse_ip_list_unsafe(void **out, char *ip) +{ + ebpf_network_viewer_ip_list_t **list = (ebpf_network_viewer_ip_list_t **)out; + + char *ipdup = strdupz(ip); + union netdata_ip_t first = { }; + union netdata_ip_t last = { }; + char *is_ipv6; + if (*ip == '*' && *(ip+1) == '\0') { + memset(first.addr8, 0, sizeof(first.addr8)); + memset(last.addr8, 0xFF, sizeof(last.addr8)); + + is_ipv6 = ip; + + ebpf_clean_ip_structure(list); + goto storethisip; + } + + char *end = ip; + // Move while I cannot find a separator + while (*end && *end != '/' && *end != '-') end++; + + // We will use only the classic IPV6 for while, but we could consider the base 85 in a near future + // https://tools.ietf.org/html/rfc1924 + is_ipv6 = strchr(ip, ':'); + + int select; + if (*end && !is_ipv6) { // IPV4 range + select = (*end == '/') ? 0 : 1; + *end++ = '\0'; + if (*end == '!') { + netdata_log_info("The exclusion cannot be in the second part of the range %s, it will be ignored.", ipdup); + goto cleanipdup; + } + + if (!select) { // CIDR + select = ebpf_ip2nl(first.addr8, ip, AF_INET, ipdup); + if (select) + goto cleanipdup; + + select = (int) str2i(end); + if (select < NETDATA_MINIMUM_IPV4_CIDR || select > NETDATA_MAXIMUM_IPV4_CIDR) { + netdata_log_info("The specified CIDR %s is not valid, the IP %s will be ignored.", end, ip); + goto cleanipdup; + } + + last.addr32[0] = htonl(ebpf_broadcast(ntohl(first.addr32[0]), select)); + // This was added to remove + // https://app.codacy.com/manual/netdata/netdata/pullRequest?prid=5810941&bid=19021977 + UNUSED(last.addr32[0]); + + uint32_t ipv4_test = htonl(ebpf_ipv4_network(ntohl(first.addr32[0]), select)); + if (first.addr32[0] != ipv4_test) { + first.addr32[0] = ipv4_test; + struct in_addr ipv4_convert; + ipv4_convert.s_addr = ipv4_test; + char ipv4_msg[INET_ADDRSTRLEN]; + if(inet_ntop(AF_INET, &ipv4_convert, ipv4_msg, INET_ADDRSTRLEN)) + netdata_log_info("The network value of CIDR %s was updated for %s .", ipdup, ipv4_msg); + } + } else { // Range + select = ebpf_ip2nl(first.addr8, ip, AF_INET, ipdup); + if (select) + goto cleanipdup; + + select = ebpf_ip2nl(last.addr8, end, AF_INET, ipdup); + if (select) + goto cleanipdup; + } + + if (htonl(first.addr32[0]) > htonl(last.addr32[0])) { + netdata_log_info("The specified range %s is invalid, the second address is smallest than the first, it will be ignored.", + ipdup); + goto cleanipdup; + } + } else if (is_ipv6) { // IPV6 + if (!*end) { // Unique + select = ebpf_ip2nl(first.addr8, ip, AF_INET6, ipdup); + if (select) + goto cleanipdup; + + memcpy(last.addr8, first.addr8, sizeof(first.addr8)); + } else if (*end == '-') { + *end++ = 0x00; + if (*end == '!') { + netdata_log_info("The exclusion cannot be in the second part of the range %s, it will be ignored.", ipdup); + goto cleanipdup; + } + + select = ebpf_ip2nl(first.addr8, ip, AF_INET6, ipdup); + if (select) + goto cleanipdup; + + select = ebpf_ip2nl(last.addr8, end, AF_INET6, ipdup); + if (select) + goto cleanipdup; + } else { // CIDR + *end++ = 0x00; + if (*end == '!') { + netdata_log_info("The exclusion cannot be in the second part of the range %s, it will be ignored.", ipdup); + goto cleanipdup; + } + + select = str2i(end); + if (select < 0 || select > 128) { + netdata_log_info("The CIDR %s is not valid, the address %s will be ignored.", end, ip); + goto cleanipdup; + } + + uint64_t prefix = (uint64_t)select; + select = ebpf_ip2nl(first.addr8, ip, AF_INET6, ipdup); + if (select) + goto cleanipdup; + + get_ipv6_last_addr(&last, &first, prefix); + + union netdata_ip_t ipv6_test; + get_ipv6_first_addr(&ipv6_test, &first, prefix); + + if (memcmp(first.addr8, ipv6_test.addr8, sizeof(union netdata_ip_t)) != 0) { + memcpy(first.addr8, ipv6_test.addr8, sizeof(union netdata_ip_t)); + + struct in6_addr ipv6_convert; + memcpy(ipv6_convert.s6_addr, ipv6_test.addr8, sizeof(union netdata_ip_t)); + + char ipv6_msg[INET6_ADDRSTRLEN]; + if(inet_ntop(AF_INET6, &ipv6_convert, ipv6_msg, INET6_ADDRSTRLEN)) + netdata_log_info("The network value of CIDR %s was updated for %s .", ipdup, ipv6_msg); + } + } + + if ((be64toh(*(uint64_t *)&first.addr64[1]) > be64toh(*(uint64_t *)&last.addr64[1]) && + !memcmp(first.addr64, last.addr64, sizeof(uint64_t))) || + (be64toh(*(uint64_t *)&first.addr64) > be64toh(*(uint64_t *)&last.addr64)) ) { + netdata_log_info("The specified range %s is invalid, the second address is smallest than the first, it will be ignored.", + ipdup); + goto cleanipdup; + } + } else { // Unique ip + select = ebpf_ip2nl(first.addr8, ip, AF_INET, ipdup); + if (select) + goto cleanipdup; + + memcpy(last.addr8, first.addr8, sizeof(first.addr8)); + } + + ebpf_network_viewer_ip_list_t *store; + + storethisip: + store = callocz(1, sizeof(ebpf_network_viewer_ip_list_t)); + store->value = ipdup; + store->hash = simple_hash(ipdup); + store->ver = (uint8_t)(!is_ipv6)?AF_INET:AF_INET6; + memcpy(store->first.addr8, first.addr8, sizeof(first.addr8)); + memcpy(store->last.addr8, last.addr8, sizeof(last.addr8)); + + ebpf_fill_ip_list_unsafe(list, store, "socket"); + return; + + cleanipdup: + freez(ipdup); +} + +/** + * Parse IP Range + * + * Parse the IP ranges given and create Network Viewer IP Structure + * + * @param ptr is a pointer with the text to parse. + */ +void ebpf_parse_ips_unsafe(char *ptr) +{ + // No value + if (unlikely(!ptr)) + return; + + while (likely(ptr)) { + // Move forward until next valid character + while (isspace(*ptr)) ptr++; + + // No valid value found + if (unlikely(!*ptr)) + return; + + // Find space that ends the list + char *end = strchr(ptr, ' '); + if (end) { + *end++ = '\0'; + } + + int neg = 0; + if (*ptr == '!') { + neg++; + ptr++; + } + + if (isascii(*ptr)) { // Parse port + ebpf_parse_ip_list_unsafe( + (!neg) ? (void **)&network_viewer_opt.included_ips : (void **)&network_viewer_opt.excluded_ips, ptr); + } + + ptr = end; + } +} + +/** + * Fill Port list + * + * @param out a pointer to the link list. + * @param in the structure that will be linked. + */ +static inline void fill_port_list(ebpf_network_viewer_port_list_t **out, ebpf_network_viewer_port_list_t *in) +{ + if (likely(*out)) { + ebpf_network_viewer_port_list_t *move = *out, *store = *out; + uint16_t first = ntohs(in->first); + uint16_t last = ntohs(in->last); + while (move) { + uint16_t cmp_first = ntohs(move->first); + uint16_t cmp_last = ntohs(move->last); + if (cmp_first <= first && first <= cmp_last && + cmp_first <= last && last <= cmp_last ) { + netdata_log_info("The range/value (%u, %u) is inside the range/value (%u, %u) already inserted, it will be ignored.", + first, last, cmp_first, cmp_last); + freez(in->value); + freez(in); + return; + } else if (first <= cmp_first && cmp_first <= last && + first <= cmp_last && cmp_last <= last) { + netdata_log_info("The range (%u, %u) is bigger than previous range (%u, %u) already inserted, the previous will be ignored.", + first, last, cmp_first, cmp_last); + freez(move->value); + move->value = in->value; + move->first = in->first; + move->last = in->last; + freez(in); + return; + } + + store = move; + move = move->next; + } + + store->next = in; + } else { + *out = in; + } + +#ifdef NETDATA_INTERNAL_CHECKS + netdata_log_info("Adding values %s( %u, %u) to %s port list used on network viewer", + in->value, in->first, in->last, + (*out == network_viewer_opt.included_port)?"included":"excluded"); +#endif +} + +/** + * Parse Service List + * + * @param out a pointer to store the link list + * @param service the service used to create the structure that will be linked. + */ +static void ebpf_parse_service_list(void **out, char *service) +{ + ebpf_network_viewer_port_list_t **list = (ebpf_network_viewer_port_list_t **)out; + struct servent *serv = getservbyname((const char *)service, "tcp"); + if (!serv) + serv = getservbyname((const char *)service, "udp"); + + if (!serv) { + netdata_log_info("Cannot resolve the service '%s' with protocols TCP and UDP, it will be ignored", service); + return; + } + + ebpf_network_viewer_port_list_t *w = callocz(1, sizeof(ebpf_network_viewer_port_list_t)); + w->value = strdupz(service); + w->hash = simple_hash(service); + + w->first = w->last = (uint16_t)serv->s_port; + + fill_port_list(list, w); +} + +/** + * Parse port list + * + * Parse an allocated port list with the range given + * + * @param out a pointer to store the link list + * @param range the informed range for the user. + */ +static void ebpf_parse_port_list(void **out, char *range) +{ + int first, last; + ebpf_network_viewer_port_list_t **list = (ebpf_network_viewer_port_list_t **)out; + + char *copied = strdupz(range); + if (*range == '*' && *(range+1) == '\0') { + first = 1; + last = 65535; + + ebpf_clean_port_structure(list); + goto fillenvpl; + } + + char *end = range; + //Move while I cannot find a separator + while (*end && *end != ':' && *end != '-') end++; + + //It has a range + if (likely(*end)) { + *end++ = '\0'; + if (*end == '!') { + netdata_log_info("The exclusion cannot be in the second part of the range, the range %s will be ignored.", copied); + freez(copied); + return; + } + last = str2i((const char *)end); + } else { + last = 0; + } + + first = str2i((const char *)range); + if (first < NETDATA_MINIMUM_PORT_VALUE || first > NETDATA_MAXIMUM_PORT_VALUE) { + netdata_log_info("The first port %d of the range \"%s\" is invalid and it will be ignored!", first, copied); + freez(copied); + return; + } + + if (!last) + last = first; + + if (last < NETDATA_MINIMUM_PORT_VALUE || last > NETDATA_MAXIMUM_PORT_VALUE) { + netdata_log_info("The second port %d of the range \"%s\" is invalid and the whole range will be ignored!", last, copied); + freez(copied); + return; + } + + if (first > last) { + netdata_log_info("The specified order %s is wrong, the smallest value is always the first, it will be ignored!", copied); + freez(copied); + return; + } + + ebpf_network_viewer_port_list_t *w; + fillenvpl: + w = callocz(1, sizeof(ebpf_network_viewer_port_list_t)); + w->value = copied; + w->hash = simple_hash(copied); + w->first = (uint16_t)first; + w->last = (uint16_t)last; + w->cmp_first = (uint16_t)first; + w->cmp_last = (uint16_t)last; + + fill_port_list(list, w); +} + +/** + * Parse Port Range + * + * Parse the port ranges given and create Network Viewer Port Structure + * + * @param ptr is a pointer with the text to parse. + */ +void ebpf_parse_ports(char *ptr) +{ + // No value + if (unlikely(!ptr)) + return; + + while (likely(ptr)) { + // Move forward until next valid character + while (isspace(*ptr)) ptr++; + + // No valid value found + if (unlikely(!*ptr)) + return; + + // Find space that ends the list + char *end = strchr(ptr, ' '); + if (end) { + *end++ = '\0'; + } + + int neg = 0; + if (*ptr == '!') { + neg++; + ptr++; + } + + if (isdigit(*ptr)) { // Parse port + ebpf_parse_port_list( + (!neg) ? (void **)&network_viewer_opt.included_port : (void **)&network_viewer_opt.excluded_port, ptr); + } else if (isalpha(*ptr)) { // Parse service + ebpf_parse_service_list( + (!neg) ? (void **)&network_viewer_opt.included_port : (void **)&network_viewer_opt.excluded_port, ptr); + } else if (*ptr == '*') { // All + ebpf_parse_port_list( + (!neg) ? (void **)&network_viewer_opt.included_port : (void **)&network_viewer_opt.excluded_port, ptr); + } + + ptr = end; + } +} + +/***************************************************************** + * + * FUNCTIONS TO DEFINE OPTIONS + * + *****************************************************************/ + +/** + * Define labels used to generate charts + * + * @param is structure with information about number of calls made for a function. + * @param pio structure used to generate charts. + * @param dim a pointer for the dimensions name + * @param name a pointer for the tensor with the name of the functions. + * @param algorithm a vector with the algorithms used to make the charts + * @param end the number of elements in the previous 4 arguments. + */ +void ebpf_global_labels(netdata_syscall_stat_t *is, netdata_publish_syscall_t *pio, char **dim, + char **name, int *algorithm, int end) +{ + int i; + + netdata_syscall_stat_t *prev = NULL; + netdata_publish_syscall_t *publish_prev = NULL; + for (i = 0; i < end; i++) { + if (prev) { + prev->next = &is[i]; + } + prev = &is[i]; + + pio[i].dimension = dim[i]; + pio[i].name = name[i]; + pio[i].algorithm = ebpf_algorithms[algorithm[i]]; + if (publish_prev) { + publish_prev->next = &pio[i]; + } + publish_prev = &pio[i]; + } +} + +/** + * Define thread mode for all ebpf program. + * + * @param lmode the mode that will be used for them. + */ +static inline void ebpf_set_thread_mode(netdata_run_mode_t lmode) +{ + int i; + for (i = 0; i < EBPF_MODULE_FUNCTION_IDX; i++) { + ebpf_modules[i].mode = lmode; + } +} + +/** + * Enable specific charts selected by user. + * + * @param em the structure that will be changed + * @param disable_cgroup the status about the cgroups charts. + */ +static inline void ebpf_enable_specific_chart(struct ebpf_module *em, int disable_cgroup) +{ + em->enabled = NETDATA_THREAD_EBPF_RUNNING; + + if (!disable_cgroup) { + em->cgroup_charts = CONFIG_BOOLEAN_YES; + } + + em->global_charts = CONFIG_BOOLEAN_YES; +} + +/** + * Disable all Global charts + * + * Disable charts + */ +static inline void disable_all_global_charts() +{ + int i; + for (i = 0; ebpf_modules[i].info.thread_name; i++) { + ebpf_modules[i].enabled = NETDATA_THREAD_EBPF_NOT_RUNNING; + ebpf_modules[i].global_charts = 0; + } +} + +/** + * Enable the specified chart group + * + * @param idx the index of ebpf_modules that I am enabling + */ +static inline void ebpf_enable_chart(int idx, int disable_cgroup) +{ + int i; + for (i = 0; ebpf_modules[i].info.thread_name; i++) { + if (i == idx) { + ebpf_enable_specific_chart(&ebpf_modules[i], disable_cgroup); + break; + } + } +} + +/** + * Disable Cgroups + * + * Disable charts for apps loading only global charts. + */ +static inline void ebpf_disable_cgroups() +{ + int i; + for (i = 0; ebpf_modules[i].info.thread_name; i++) { + ebpf_modules[i].cgroup_charts = 0; + } +} + +/** + * Update Disabled Plugins + * + * This function calls ebpf_update_stats to update statistics for collector. + * + * @param em a pointer to `struct ebpf_module` + */ +void ebpf_update_disabled_plugin_stats(ebpf_module_t *em) +{ + pthread_mutex_lock(&lock); + ebpf_update_stats(&plugin_statistics, em); + pthread_mutex_unlock(&lock); +} + +/** + * Print help on standard error for user knows how to use the collector. + */ +void ebpf_print_help() +{ + const time_t t = time(NULL); + struct tm ct; + struct tm *test = localtime_r(&t, &ct); + int year; + if (test) + year = ct.tm_year; + else + year = 0; + + fprintf(stderr, + "\n" + " Netdata ebpf.plugin %s\n" + " Copyright (C) 2016-%d Costa Tsaousis <costa@tsaousis.gr>\n" + " Released under GNU General Public License v3 or later.\n" + " All rights reserved.\n" + "\n" + " This eBPF.plugin is a data collector plugin for netdata.\n" + "\n" + " This plugin only accepts long options with one or two dashes. The available command line options are:\n" + "\n" + " SECONDS Set the data collection frequency.\n" + "\n" + " [-]-help Show this help.\n" + "\n" + " [-]-version Show software version.\n" + "\n" + " [-]-global Disable charts per application and cgroup.\n" + "\n" + " [-]-all Enable all chart groups (global, apps, and cgroup), unless -g is also given.\n" + "\n" + " [-]-cachestat Enable charts related to process run time.\n" + "\n" + " [-]-dcstat Enable charts related to directory cache.\n" + "\n" + " [-]-disk Enable charts related to disk monitoring.\n" + "\n" + " [-]-filesystem Enable chart related to filesystem run time.\n" + "\n" + " [-]-hardirq Enable chart related to hard IRQ latency.\n" + "\n" + " [-]-mdflush Enable charts related to multi-device flush.\n" + "\n" + " [-]-mount Enable charts related to mount monitoring.\n" + "\n" + " [-]-net Enable network viewer charts.\n" + "\n" + " [-]-oomkill Enable chart related to OOM kill tracking.\n" + "\n" + " [-]-process Enable charts related to process run time.\n" + "\n" + " [-]-return Run the collector in return mode.\n" + "\n" + " [-]-shm Enable chart related to shared memory tracking.\n" + "\n" + " [-]-softirq Enable chart related to soft IRQ latency.\n" + "\n" + " [-]-sync Enable chart related to sync run time.\n" + "\n" + " [-]-swap Enable chart related to swap run time.\n" + "\n" + " [-]-vfs Enable chart related to vfs run time.\n" + "\n" + " [-]-legacy Load legacy eBPF programs.\n" + "\n" + " [-]-core Use CO-RE when available(Working in progress).\n" + "\n", + NETDATA_VERSION, + (year >= 116) ? year + 1900 : 2020); +} + +/***************************************************************** + * + * TRACEPOINT MANAGEMENT FUNCTIONS + * + *****************************************************************/ + +/** + * Enable a tracepoint. + * + * @return 0 on success, -1 on error. + */ +int ebpf_enable_tracepoint(ebpf_tracepoint_t *tp) +{ + int test = ebpf_is_tracepoint_enabled(tp->class, tp->event); + + // err? + if (test == -1) { + return -1; + } + // disabled? + else if (test == 0) { + // enable it then. + if (ebpf_enable_tracing_values(tp->class, tp->event)) { + return -1; + } + } + + // enabled now or already was. + tp->enabled = true; + + return 0; +} + +/** + * Disable a tracepoint if it's enabled. + * + * @return 0 on success, -1 on error. + */ +int ebpf_disable_tracepoint(ebpf_tracepoint_t *tp) +{ + int test = ebpf_is_tracepoint_enabled(tp->class, tp->event); + + // err? + if (test == -1) { + return -1; + } + // enabled? + else if (test == 1) { + // disable it then. + if (ebpf_disable_tracing_values(tp->class, tp->event)) { + return -1; + } + } + + // disable now or already was. + tp->enabled = false; + + return 0; +} + +/** + * Enable multiple tracepoints on a list of tracepoints which end when the + * class is NULL. + * + * @return the number of successful enables. + */ +uint32_t ebpf_enable_tracepoints(ebpf_tracepoint_t *tps) +{ + uint32_t cnt = 0; + for (int i = 0; tps[i].class != NULL; i++) { + if (ebpf_enable_tracepoint(&tps[i]) == -1) { + netdata_log_error("Failed to enable tracepoint %s:%s", tps[i].class, tps[i].event); + } + else { + cnt += 1; + } + } + return cnt; +} + +/***************************************************************** + * + * AUXILIARY FUNCTIONS USED DURING INITIALIZATION + * + *****************************************************************/ + +/** + * Is ip inside the range + * + * Check if the ip is inside a IP range + * + * @param rfirst the first ip address of the range + * @param rlast the last ip address of the range + * @param cmpfirst the first ip to compare + * @param cmplast the last ip to compare + * @param family the IP family + * + * @return It returns 1 if the IP is inside the range and 0 otherwise + */ +static int ebpf_is_ip_inside_range(union netdata_ip_t *rfirst, union netdata_ip_t *rlast, + union netdata_ip_t *cmpfirst, union netdata_ip_t *cmplast, int family) +{ + if (family == AF_INET) { + if ((rfirst->addr32[0] <= cmpfirst->addr32[0]) && (rlast->addr32[0] >= cmplast->addr32[0])) + return 1; + } else { + if (memcmp(rfirst->addr8, cmpfirst->addr8, sizeof(union netdata_ip_t)) <= 0 && + memcmp(rlast->addr8, cmplast->addr8, sizeof(union netdata_ip_t)) >= 0) { + return 1; + } + + } + return 0; +} + +/** + * Fill IP list + * + * @param out a pointer to the link list. + * @param in the structure that will be linked. + * @param table the modified table. + */ +void ebpf_fill_ip_list_unsafe(ebpf_network_viewer_ip_list_t **out, ebpf_network_viewer_ip_list_t *in, + char *table __maybe_unused) +{ + if (in->ver == AF_INET) { // It is simpler to compare using host order + in->first.addr32[0] = ntohl(in->first.addr32[0]); + in->last.addr32[0] = ntohl(in->last.addr32[0]); + } + if (likely(*out)) { + ebpf_network_viewer_ip_list_t *move = *out, *store = *out; + while (move) { + if (in->ver == move->ver && + ebpf_is_ip_inside_range(&move->first, &move->last, &in->first, &in->last, in->ver)) { +#ifdef NETDATA_DEV_MODE + netdata_log_info("The range/value (%s) is inside the range/value (%s) already inserted, it will be ignored.", + in->value, move->value); +#endif + freez(in->value); + freez(in); + return; + } + store = move; + move = move->next; + } + + store->next = in; + } else { + *out = in; + } + +#ifdef NETDATA_DEV_MODE + char first[256], last[512]; + if (in->ver == AF_INET) { + netdata_log_info("Adding values %s: (%u - %u) to %s IP list \"%s\" used on network viewer", + in->value, in->first.addr32[0], in->last.addr32[0], + (*out == network_viewer_opt.included_ips)?"included":"excluded", + table); + } else { + if (inet_ntop(AF_INET6, in->first.addr8, first, INET6_ADDRSTRLEN) && + inet_ntop(AF_INET6, in->last.addr8, last, INET6_ADDRSTRLEN)) + netdata_log_info("Adding values %s - %s to %s IP list \"%s\" used on network viewer", + first, last, + (*out == network_viewer_opt.included_ips)?"included":"excluded", + table); + } +#endif +} + +/** + * Link hostname + * + * @param out is the output link list + * @param in the hostname to add to list. + */ +static void ebpf_link_hostname(ebpf_network_viewer_hostname_list_t **out, ebpf_network_viewer_hostname_list_t *in) +{ + if (likely(*out)) { + ebpf_network_viewer_hostname_list_t *move = *out; + for (; move->next ; move = move->next ) { + if (move->hash == in->hash && !strcmp(move->value, in->value)) { + netdata_log_info("The hostname %s was already inserted, it will be ignored.", in->value); + freez(in->value); + simple_pattern_free(in->value_pattern); + freez(in); + return; + } + } + + move->next = in; + } else { + *out = in; + } +#ifdef NETDATA_INTERNAL_CHECKS + netdata_log_info("Adding value %s to %s hostname list used on network viewer", + in->value, + (*out == network_viewer_opt.included_hostnames)?"included":"excluded"); +#endif +} + +/** + * Link Hostnames + * + * Parse the list of hostnames to create the link list. + * This is not associated with the IP, because simple patterns like *example* cannot be resolved to IP. + * + * @param out is the output link list + * @param parse is a pointer with the text to parser. + */ +static void ebpf_link_hostnames(char *parse) +{ + // No value + if (unlikely(!parse)) + return; + + while (likely(parse)) { + // Find the first valid value + while (isspace(*parse)) parse++; + + // No valid value found + if (unlikely(!*parse)) + return; + + // Find space that ends the list + char *end = strchr(parse, ' '); + if (end) { + *end++ = '\0'; + } + + int neg = 0; + if (*parse == '!') { + neg++; + parse++; + } + + ebpf_network_viewer_hostname_list_t *hostname = callocz(1 , sizeof(ebpf_network_viewer_hostname_list_t)); + hostname->value = strdupz(parse); + hostname->hash = simple_hash(parse); + hostname->value_pattern = simple_pattern_create(parse, NULL, SIMPLE_PATTERN_EXACT, true); + + ebpf_link_hostname((!neg) ? &network_viewer_opt.included_hostnames : + &network_viewer_opt.excluded_hostnames, + hostname); + + parse = end; + } +} + +/** + * Parse network viewer section + * + * @param cfg the configuration structure + */ +void parse_network_viewer_section(struct config *cfg) +{ + network_viewer_opt.hostname_resolution_enabled = appconfig_get_boolean(cfg, + EBPF_NETWORK_VIEWER_SECTION, + EBPF_CONFIG_RESOLVE_HOSTNAME, + CONFIG_BOOLEAN_NO); + + network_viewer_opt.service_resolution_enabled = appconfig_get_boolean(cfg, + EBPF_NETWORK_VIEWER_SECTION, + EBPF_CONFIG_RESOLVE_SERVICE, + CONFIG_BOOLEAN_YES); + + char *value = appconfig_get(cfg, EBPF_NETWORK_VIEWER_SECTION, EBPF_CONFIG_PORTS, NULL); + ebpf_parse_ports(value); + + if (network_viewer_opt.hostname_resolution_enabled) { + value = appconfig_get(cfg, EBPF_NETWORK_VIEWER_SECTION, EBPF_CONFIG_HOSTNAMES, NULL); + ebpf_link_hostnames(value); + } else { + netdata_log_info("Name resolution is disabled, collector will not parse \"hostnames\" list."); + } + + value = appconfig_get(cfg, + EBPF_NETWORK_VIEWER_SECTION, + "ips", + NULL); + //"ips", "!127.0.0.1/8 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 fc00::/7 !::1/128"); + ebpf_parse_ips_unsafe(value); +} + +/** + * Read Local Ports + * + * Parse /proc/net/{tcp,udp} and get the ports Linux is listening. + * + * @param filename the proc file to parse. + * @param proto is the magic number associated to the protocol file we are reading. + */ +static void read_local_ports(char *filename, uint8_t proto) +{ + procfile *ff = procfile_open(filename, " \t:", PROCFILE_FLAG_DEFAULT); + if (!ff) + return; + + ff = procfile_readall(ff); + if (!ff) + return; + + size_t lines = procfile_lines(ff), l; + netdata_passive_connection_t values = {.counter = 0, .tgid = 0, .pid = 0}; + for(l = 0; l < lines ;l++) { + size_t words = procfile_linewords(ff, l); + // This is header or end of file + if (unlikely(words < 14)) + continue; + + // https://elixir.bootlin.com/linux/v5.7.8/source/include/net/tcp_states.h + // 0A = TCP_LISTEN + if (strcmp("0A", procfile_lineword(ff, l, 5))) + continue; + + // Read local port + uint16_t port = (uint16_t)strtol(procfile_lineword(ff, l, 2), NULL, 16); + update_listen_table(htons(port), proto, &values); + } + + procfile_close(ff); +} + +/** + * Read Local addresseses + * + * Read the local address from the interfaces. + */ +void ebpf_read_local_addresses_unsafe() +{ + struct ifaddrs *ifaddr, *ifa; + if (getifaddrs(&ifaddr) == -1) { + netdata_log_error("Cannot get the local IP addresses, it is no possible to do separation between inbound and outbound connections"); + return; + } + + char *notext = { "No text representation" }; + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL) + continue; + + if ((ifa->ifa_addr->sa_family != AF_INET) && (ifa->ifa_addr->sa_family != AF_INET6)) + continue; + + ebpf_network_viewer_ip_list_t *w = callocz(1, sizeof(ebpf_network_viewer_ip_list_t)); + + int family = ifa->ifa_addr->sa_family; + w->ver = (uint8_t) family; + char text[INET6_ADDRSTRLEN]; + if (family == AF_INET) { + struct sockaddr_in *in = (struct sockaddr_in*) ifa->ifa_addr; + + w->first.addr32[0] = in->sin_addr.s_addr; + w->last.addr32[0] = in->sin_addr.s_addr; + + if (inet_ntop(AF_INET, w->first.addr8, text, INET_ADDRSTRLEN)) { + w->value = strdupz(text); + w->hash = simple_hash(text); + } else { + w->value = strdupz(notext); + w->hash = simple_hash(notext); + } + } else { + struct sockaddr_in6 *in6 = (struct sockaddr_in6*) ifa->ifa_addr; + + memcpy(w->first.addr8, (void *)&in6->sin6_addr, sizeof(struct in6_addr)); + memcpy(w->last.addr8, (void *)&in6->sin6_addr, sizeof(struct in6_addr)); + + if (inet_ntop(AF_INET6, w->first.addr8, text, INET_ADDRSTRLEN)) { + w->value = strdupz(text); + w->hash = simple_hash(text); + } else { + w->value = strdupz(notext); + w->hash = simple_hash(notext); + } + } + + ebpf_fill_ip_list_unsafe( + (family == AF_INET) ? &network_viewer_opt.ipv4_local_ip : &network_viewer_opt.ipv6_local_ip, w, "selector"); + } + + freeifaddrs(ifaddr); +} + +/** + * Start Pthread Variable + * + * This function starts all pthread variables. + */ +void ebpf_start_pthread_variables() +{ + pthread_mutex_init(&lock, NULL); + pthread_mutex_init(&ebpf_exit_cleanup, NULL); + pthread_mutex_init(&collect_data_mutex, NULL); + pthread_mutex_init(&mutex_cgroup_shm, NULL); + rw_spinlock_init(&ebpf_judy_pid.index.rw_spinlock); +} + +/** + * Allocate the vectors used for all threads. + */ +static void ebpf_allocate_common_vectors() +{ + ebpf_judy_pid.pid_table = ebpf_allocate_pid_aral(NETDATA_EBPF_PID_SOCKET_ARAL_TABLE_NAME, + sizeof(netdata_ebpf_judy_pid_stats_t)); + ebpf_all_pids = callocz((size_t)pid_max, sizeof(struct ebpf_pid_stat *)); + ebpf_aral_init(); +} + +/** + * Define how to load the ebpf programs + * + * @param ptr the option given by users + */ +static inline void ebpf_how_to_load(char *ptr) +{ + if (!strcasecmp(ptr, EBPF_CFG_LOAD_MODE_RETURN)) + ebpf_set_thread_mode(MODE_RETURN); + else if (!strcasecmp(ptr, EBPF_CFG_LOAD_MODE_DEFAULT)) + ebpf_set_thread_mode(MODE_ENTRY); + else + netdata_log_error("the option %s for \"ebpf load mode\" is not a valid option.", ptr); +} + +/** + * Define whether we should have charts for apps + * + * @param lmode the mode that will be used for them. + */ +static inline void ebpf_set_apps_mode(netdata_apps_integration_flags_t value) +{ + int i; + for (i = 0; i < EBPF_MODULE_FUNCTION_IDX; i++) { + ebpf_modules[i].apps_charts = value; + } +} + + +/** + * Update interval + * + * Update default interval with value from user + * + * @param update_every value to overwrite the update frequency set by the server. + */ +static void ebpf_update_interval(int update_every) +{ + int i; + int value = (int) appconfig_get_number(&collector_config, EBPF_GLOBAL_SECTION, EBPF_CFG_UPDATE_EVERY, + update_every); + for (i = 0; ebpf_modules[i].info.thread_name; i++) { + ebpf_modules[i].update_every = value; + } +} + +/** + * Update PID table size + * + * Update default size with value from user + */ +static void ebpf_update_table_size() +{ + int i; + uint32_t value = (uint32_t) appconfig_get_number(&collector_config, EBPF_GLOBAL_SECTION, + EBPF_CFG_PID_SIZE, ND_EBPF_DEFAULT_PID_SIZE); + for (i = 0; ebpf_modules[i].info.thread_name; i++) { + ebpf_modules[i].pid_map_size = value; + } +} + +/** + * Update lifetime + * + * Update the period of time that specific thread will run + */ +static void ebpf_update_lifetime() +{ + int i; + uint32_t value = (uint32_t) appconfig_get_number(&collector_config, EBPF_GLOBAL_SECTION, + EBPF_CFG_LIFETIME, EBPF_DEFAULT_LIFETIME); + for (i = 0; ebpf_modules[i].info.thread_name; i++) { + ebpf_modules[i].lifetime = value; + } +} + +/** + * Set Load mode + * + * @param origin specify the configuration file loaded + */ +static inline void ebpf_set_load_mode(netdata_ebpf_load_mode_t load, netdata_ebpf_load_mode_t origin) +{ + int i; + for (i = 0; ebpf_modules[i].info.thread_name; i++) { + ebpf_modules[i].load &= ~NETDATA_EBPF_LOAD_METHODS; + ebpf_modules[i].load |= load | origin ; + } +} + +/** + * Update mode + * + * @param str value read from configuration file. + * @param origin specify the configuration file loaded + */ +static inline void epbf_update_load_mode(char *str, netdata_ebpf_load_mode_t origin) +{ + netdata_ebpf_load_mode_t load = epbf_convert_string_to_load_mode(str); + + ebpf_set_load_mode(load, origin); +} + +/** + * Update Map per core + * + * Define the map type used with some hash tables. + */ +static void ebpf_update_map_per_core() +{ + int i; + int value = appconfig_get_boolean(&collector_config, EBPF_GLOBAL_SECTION, + EBPF_CFG_MAPS_PER_CORE, CONFIG_BOOLEAN_YES); + for (i = 0; ebpf_modules[i].info.thread_name; i++) { + ebpf_modules[i].maps_per_core = value; + } +} + +/** + * Read collector values + * + * @param disable_cgroups variable to store information related to cgroups. + * @param update_every value to overwrite the update frequency set by the server. + * @param origin specify the configuration file loaded + */ +static void read_collector_values(int *disable_cgroups, + int update_every, netdata_ebpf_load_mode_t origin) +{ + // Read global section + char *value; + if (appconfig_exists(&collector_config, EBPF_GLOBAL_SECTION, "load")) // Backward compatibility + value = appconfig_get(&collector_config, EBPF_GLOBAL_SECTION, "load", + EBPF_CFG_LOAD_MODE_DEFAULT); + else + value = appconfig_get(&collector_config, EBPF_GLOBAL_SECTION, EBPF_CFG_LOAD_MODE, + EBPF_CFG_LOAD_MODE_DEFAULT); + + ebpf_how_to_load(value); + + btf_path = appconfig_get(&collector_config, EBPF_GLOBAL_SECTION, EBPF_CFG_PROGRAM_PATH, + EBPF_DEFAULT_BTF_PATH); + +#ifdef LIBBPF_MAJOR_VERSION + default_btf = ebpf_load_btf_file(btf_path, EBPF_DEFAULT_BTF_FILE); +#endif + + value = appconfig_get(&collector_config, EBPF_GLOBAL_SECTION, EBPF_CFG_TYPE_FORMAT, EBPF_CFG_DEFAULT_PROGRAM); + + epbf_update_load_mode(value, origin); + + ebpf_update_interval(update_every); + + ebpf_update_table_size(); + + ebpf_update_lifetime(); + + // This is kept to keep compatibility + uint32_t enabled = appconfig_get_boolean(&collector_config, EBPF_GLOBAL_SECTION, "disable apps", + CONFIG_BOOLEAN_NO); + if (!enabled) { + // Apps is a positive sentence, so we need to invert the values to disable apps. + enabled = appconfig_get_boolean(&collector_config, EBPF_GLOBAL_SECTION, EBPF_CFG_APPLICATION, + CONFIG_BOOLEAN_YES); + enabled = (enabled == CONFIG_BOOLEAN_NO)?CONFIG_BOOLEAN_YES:CONFIG_BOOLEAN_NO; + } + + ebpf_set_apps_mode(!enabled); + + // Cgroup is a positive sentence, so we need to invert the values to disable apps. + // We are using the same pattern for cgroup and apps + enabled = appconfig_get_boolean(&collector_config, EBPF_GLOBAL_SECTION, EBPF_CFG_CGROUP, CONFIG_BOOLEAN_NO); + *disable_cgroups = (enabled == CONFIG_BOOLEAN_NO)?CONFIG_BOOLEAN_YES:CONFIG_BOOLEAN_NO; + + ebpf_update_map_per_core(); + + // Read ebpf programs section + enabled = appconfig_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, + ebpf_modules[EBPF_MODULE_PROCESS_IDX].info.config_name, CONFIG_BOOLEAN_YES); + if (enabled) { + ebpf_enable_chart(EBPF_MODULE_PROCESS_IDX, *disable_cgroups); + } + + // This is kept to keep compatibility + enabled = appconfig_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "network viewer", + CONFIG_BOOLEAN_NO); + if (!enabled) + enabled = appconfig_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, + ebpf_modules[EBPF_MODULE_SOCKET_IDX].info.config_name, + CONFIG_BOOLEAN_NO); + if (enabled) { + ebpf_enable_chart(EBPF_MODULE_SOCKET_IDX, *disable_cgroups); + } + + // This is kept to keep compatibility + enabled = appconfig_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "network connection monitoring", + CONFIG_BOOLEAN_YES); + if (!enabled) + enabled = appconfig_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "network connections", + CONFIG_BOOLEAN_YES); + + network_viewer_opt.enabled = enabled; + if (enabled) { + if (!ebpf_modules[EBPF_MODULE_SOCKET_IDX].enabled) + ebpf_enable_chart(EBPF_MODULE_SOCKET_IDX, *disable_cgroups); + + // Read network viewer section if network viewer is enabled + // This is kept here to keep backward compatibility + parse_network_viewer_section(&collector_config); + ebpf_parse_service_name_section(&collector_config); + } + + enabled = appconfig_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "cachestat", + CONFIG_BOOLEAN_NO); + + if (enabled) { + ebpf_enable_chart(EBPF_MODULE_CACHESTAT_IDX, *disable_cgroups); + } + + enabled = appconfig_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "sync", + CONFIG_BOOLEAN_YES); + + if (enabled) { + ebpf_enable_chart(EBPF_MODULE_SYNC_IDX, *disable_cgroups); + } + + enabled = appconfig_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "dcstat", + CONFIG_BOOLEAN_NO); + if (enabled) { + ebpf_enable_chart(EBPF_MODULE_DCSTAT_IDX, *disable_cgroups); + } + + enabled = appconfig_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "swap", + CONFIG_BOOLEAN_NO); + if (enabled) { + ebpf_enable_chart(EBPF_MODULE_SWAP_IDX, *disable_cgroups); + } + + enabled = appconfig_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "vfs", + CONFIG_BOOLEAN_NO); + if (enabled) { + ebpf_enable_chart(EBPF_MODULE_VFS_IDX, *disable_cgroups); + } + + enabled = appconfig_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "filesystem", + CONFIG_BOOLEAN_NO); + if (enabled) { + ebpf_enable_chart(EBPF_MODULE_FILESYSTEM_IDX, *disable_cgroups); + } + + enabled = appconfig_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "disk", + CONFIG_BOOLEAN_NO); + if (enabled) { + ebpf_enable_chart(EBPF_MODULE_DISK_IDX, *disable_cgroups); + } + + enabled = appconfig_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "mount", + CONFIG_BOOLEAN_YES); + if (enabled) { + ebpf_enable_chart(EBPF_MODULE_MOUNT_IDX, *disable_cgroups); + } + + enabled = appconfig_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "fd", + CONFIG_BOOLEAN_YES); + if (enabled) { + ebpf_enable_chart(EBPF_MODULE_FD_IDX, *disable_cgroups); + } + + enabled = appconfig_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "hardirq", + CONFIG_BOOLEAN_YES); + if (enabled) { + ebpf_enable_chart(EBPF_MODULE_HARDIRQ_IDX, *disable_cgroups); + } + + enabled = appconfig_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "softirq", + CONFIG_BOOLEAN_YES); + if (enabled) { + ebpf_enable_chart(EBPF_MODULE_SOFTIRQ_IDX, *disable_cgroups); + } + + enabled = appconfig_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "oomkill", + CONFIG_BOOLEAN_YES); + if (enabled) { + ebpf_enable_chart(EBPF_MODULE_OOMKILL_IDX, *disable_cgroups); + } + + enabled = appconfig_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "shm", + CONFIG_BOOLEAN_YES); + if (enabled) { + ebpf_enable_chart(EBPF_MODULE_SHM_IDX, *disable_cgroups); + } + + enabled = appconfig_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "mdflush", + CONFIG_BOOLEAN_NO); + if (enabled) { + ebpf_enable_chart(EBPF_MODULE_MDFLUSH_IDX, *disable_cgroups); + } +} + +/** + * Load collector config + * + * @param path the path where the file ebpf.conf is stored. + * @param disable_cgroups variable to store the information about cgroups plugin status. + * @param update_every value to overwrite the update frequency set by the server. + * + * @return 0 on success and -1 otherwise. + */ +static int ebpf_load_collector_config(char *path, int *disable_cgroups, int update_every) +{ + char lpath[4096]; + netdata_ebpf_load_mode_t origin; + + snprintf(lpath, 4095, "%s/%s", path, NETDATA_EBPF_CONFIG_FILE); + if (!appconfig_load(&collector_config, lpath, 0, NULL)) { + snprintf(lpath, 4095, "%s/%s", path, NETDATA_EBPF_OLD_CONFIG_FILE); + if (!appconfig_load(&collector_config, lpath, 0, NULL)) { + return -1; + } + origin = EBPF_LOADED_FROM_STOCK; + } else + origin = EBPF_LOADED_FROM_USER; + + read_collector_values(disable_cgroups, update_every, origin); + + return 0; +} + +/** + * Set global variables reading environment variables + */ +void set_global_variables() +{ + // Get environment variables + ebpf_plugin_dir = getenv("NETDATA_PLUGINS_DIR"); + if (!ebpf_plugin_dir) + ebpf_plugin_dir = PLUGINS_DIR; + + ebpf_user_config_dir = getenv("NETDATA_USER_CONFIG_DIR"); + if (!ebpf_user_config_dir) + ebpf_user_config_dir = CONFIG_DIR; + + ebpf_stock_config_dir = getenv("NETDATA_STOCK_CONFIG_DIR"); + if (!ebpf_stock_config_dir) + ebpf_stock_config_dir = LIBCONFIG_DIR; + + ebpf_configured_log_dir = getenv("NETDATA_LOG_DIR"); + if (!ebpf_configured_log_dir) + ebpf_configured_log_dir = LOG_DIR; + + ebpf_nprocs = (int)sysconf(_SC_NPROCESSORS_ONLN); + if (ebpf_nprocs < 0) { + ebpf_nprocs = NETDATA_MAX_PROCESSOR; + netdata_log_error("Cannot identify number of process, using default value %d", ebpf_nprocs); + } + + isrh = get_redhat_release(); + pid_max = os_get_system_pid_max(); + running_on_kernel = ebpf_get_kernel_version(); +} + +/** + * Load collector config + */ +static inline void ebpf_load_thread_config() +{ + int i; + for (i = 0; i < EBPF_MODULE_FUNCTION_IDX; i++) { + ebpf_update_module(&ebpf_modules[i], default_btf, running_on_kernel, isrh); + } +} + +/** + * Parse arguments given from user. + * + * @param argc the number of arguments + * @param argv the pointer to the arguments + */ +static void ebpf_parse_args(int argc, char **argv) +{ + int disable_cgroups = 1; + int freq = 0; + int option_index = 0; + uint64_t select_threads = 0; + static struct option long_options[] = { + {"process", no_argument, 0, 0 }, + {"net", no_argument, 0, 0 }, + {"cachestat", no_argument, 0, 0 }, + {"sync", no_argument, 0, 0 }, + {"dcstat", no_argument, 0, 0 }, + {"swap", no_argument, 0, 0 }, + {"vfs", no_argument, 0, 0 }, + {"filesystem", no_argument, 0, 0 }, + {"disk", no_argument, 0, 0 }, + {"mount", no_argument, 0, 0 }, + {"filedescriptor", no_argument, 0, 0 }, + {"hardirq", no_argument, 0, 0 }, + {"softirq", no_argument, 0, 0 }, + {"oomkill", no_argument, 0, 0 }, + {"shm", no_argument, 0, 0 }, + {"mdflush", no_argument, 0, 0 }, + /* INSERT NEW THREADS BEFORE THIS COMMENT TO KEEP COMPATIBILITY WITH enum ebpf_module_indexes */ + {"all", no_argument, 0, 0 }, + {"version", no_argument, 0, 0 }, + {"help", no_argument, 0, 0 }, + {"global", no_argument, 0, 0 }, + {"return", no_argument, 0, 0 }, + {"legacy", no_argument, 0, 0 }, + {"core", no_argument, 0, 0 }, + {"unittest", no_argument, 0, 0 }, + {0, 0, 0, 0} + }; + + memset(&network_viewer_opt, 0, sizeof(network_viewer_opt)); + rw_spinlock_init(&network_viewer_opt.rw_spinlock); + + if (argc > 1) { + int n = (int)str2l(argv[1]); + if (n > 0) { + freq = n; + } + } + + if (!freq) + freq = EBPF_DEFAULT_UPDATE_EVERY; + + //rw_spinlock_write_lock(&network_viewer_opt.rw_spinlock); + if (ebpf_load_collector_config(ebpf_user_config_dir, &disable_cgroups, freq)) { + netdata_log_info( + "Does not have a configuration file inside `%s/ebpf.d.conf. It will try to load stock file.", + ebpf_user_config_dir); + if (ebpf_load_collector_config(ebpf_stock_config_dir, &disable_cgroups, freq)) { + netdata_log_info("Does not have a stock file. It is starting with default options."); + } + } + + ebpf_load_thread_config(); + //rw_spinlock_write_unlock(&network_viewer_opt.rw_spinlock); + + while (1) { + int c = getopt_long_only(argc, argv, "", long_options, &option_index); + if (c == -1) + break; + + switch (option_index) { + case EBPF_MODULE_PROCESS_IDX: { + select_threads |= 1<<EBPF_MODULE_PROCESS_IDX; +#ifdef NETDATA_INTERNAL_CHECKS + netdata_log_info("EBPF enabling \"PROCESS\" charts, because it was started with the option \"[-]-process\"."); +#endif + break; + } + case EBPF_MODULE_SOCKET_IDX: { + select_threads |= 1<<EBPF_MODULE_SOCKET_IDX; +#ifdef NETDATA_INTERNAL_CHECKS + netdata_log_info("EBPF enabling \"NET\" charts, because it was started with the option \"[-]-net\"."); +#endif + break; + } + case EBPF_MODULE_CACHESTAT_IDX: { + select_threads |= 1<<EBPF_MODULE_CACHESTAT_IDX; +#ifdef NETDATA_INTERNAL_CHECKS + netdata_log_info("EBPF enabling \"CACHESTAT\" charts, because it was started with the option \"[-]-cachestat\"."); +#endif + break; + } + case EBPF_MODULE_SYNC_IDX: { + select_threads |= 1<<EBPF_MODULE_SYNC_IDX; +#ifdef NETDATA_INTERNAL_CHECKS + netdata_log_info("EBPF enabling \"SYNC\" chart, because it was started with the option \"[-]-sync\"."); +#endif + break; + } + case EBPF_MODULE_DCSTAT_IDX: { + select_threads |= 1<<EBPF_MODULE_DCSTAT_IDX; +#ifdef NETDATA_INTERNAL_CHECKS + netdata_log_info("EBPF enabling \"DCSTAT\" charts, because it was started with the option \"[-]-dcstat\"."); +#endif + break; + } + case EBPF_MODULE_SWAP_IDX: { + select_threads |= 1<<EBPF_MODULE_SWAP_IDX; +#ifdef NETDATA_INTERNAL_CHECKS + netdata_log_info("EBPF enabling \"SWAP\" chart, because it was started with the option \"[-]-swap\"."); +#endif + break; + } + case EBPF_MODULE_VFS_IDX: { + select_threads |= 1<<EBPF_MODULE_VFS_IDX; +#ifdef NETDATA_INTERNAL_CHECKS + netdata_log_info("EBPF enabling \"VFS\" chart, because it was started with the option \"[-]-vfs\"."); +#endif + break; + } + case EBPF_MODULE_FILESYSTEM_IDX: { + select_threads |= 1<<EBPF_MODULE_FILESYSTEM_IDX; +#ifdef NETDATA_INTERNAL_CHECKS + netdata_log_info("EBPF enabling \"FILESYSTEM\" chart, because it was started with the option \"[-]-filesystem\"."); +#endif + break; + } + case EBPF_MODULE_DISK_IDX: { + select_threads |= 1<<EBPF_MODULE_DISK_IDX; +#ifdef NETDATA_INTERNAL_CHECKS + netdata_log_info("EBPF enabling \"DISK\" chart, because it was started with the option \"[-]-disk\"."); +#endif + break; + } + case EBPF_MODULE_MOUNT_IDX: { + select_threads |= 1<<EBPF_MODULE_MOUNT_IDX; +#ifdef NETDATA_INTERNAL_CHECKS + netdata_log_info("EBPF enabling \"MOUNT\" chart, because it was started with the option \"[-]-mount\"."); +#endif + break; + } + case EBPF_MODULE_FD_IDX: { + select_threads |= 1<<EBPF_MODULE_FD_IDX; +#ifdef NETDATA_INTERNAL_CHECKS + netdata_log_info("EBPF enabling \"FILEDESCRIPTOR\" chart, because it was started with the option \"[-]-filedescriptor\"."); +#endif + break; + } + case EBPF_MODULE_HARDIRQ_IDX: { + select_threads |= 1<<EBPF_MODULE_HARDIRQ_IDX; +#ifdef NETDATA_INTERNAL_CHECKS + netdata_log_info("EBPF enabling \"HARDIRQ\" chart, because it was started with the option \"[-]-hardirq\"."); +#endif + break; + } + case EBPF_MODULE_SOFTIRQ_IDX: { + select_threads |= 1<<EBPF_MODULE_SOFTIRQ_IDX; +#ifdef NETDATA_INTERNAL_CHECKS + netdata_log_info("EBPF enabling \"SOFTIRQ\" chart, because it was started with the option \"[-]-softirq\"."); +#endif + break; + } + case EBPF_MODULE_OOMKILL_IDX: { + select_threads |= 1<<EBPF_MODULE_OOMKILL_IDX; +#ifdef NETDATA_INTERNAL_CHECKS + netdata_log_info("EBPF enabling \"OOMKILL\" chart, because it was started with the option \"[-]-oomkill\"."); +#endif + break; + } + case EBPF_MODULE_SHM_IDX: { + select_threads |= 1<<EBPF_MODULE_SHM_IDX; +#ifdef NETDATA_INTERNAL_CHECKS + netdata_log_info("EBPF enabling \"SHM\" chart, because it was started with the option \"[-]-shm\"."); +#endif + break; + } + case EBPF_MODULE_MDFLUSH_IDX: { + select_threads |= 1<<EBPF_MODULE_MDFLUSH_IDX; +#ifdef NETDATA_INTERNAL_CHECKS + netdata_log_info("EBPF enabling \"MDFLUSH\" chart, because it was started with the option \"[-]-mdflush\"."); +#endif + break; + } + case EBPF_OPTION_ALL_CHARTS: { + ebpf_set_apps_mode(NETDATA_EBPF_APPS_FLAG_YES); + disable_cgroups = 0; +#ifdef NETDATA_INTERNAL_CHECKS + netdata_log_info("EBPF running with all chart groups, because it was started with the option \"[-]-all\"."); +#endif + break; + } + case EBPF_OPTION_VERSION: { + printf("ebpf.plugin %s\n", NETDATA_VERSION); + exit(0); + } + case EBPF_OPTION_HELP: { + ebpf_print_help(); + exit(0); + } + case EBPF_OPTION_GLOBAL_CHART: { + disable_cgroups = 1; +#ifdef NETDATA_INTERNAL_CHECKS + netdata_log_info("EBPF running with global chart group, because it was started with the option \"[-]-global\"."); +#endif + break; + } + case EBPF_OPTION_RETURN_MODE: { + ebpf_set_thread_mode(MODE_RETURN); +#ifdef NETDATA_INTERNAL_CHECKS + netdata_log_info("EBPF running in \"RETURN\" mode, because it was started with the option \"[-]-return\"."); +#endif + break; + } + case EBPF_OPTION_LEGACY: { + ebpf_set_load_mode(EBPF_LOAD_LEGACY, EBPF_LOADED_FROM_USER); +#ifdef NETDATA_INTERNAL_CHECKS + netdata_log_info("EBPF running with \"LEGACY\" code, because it was started with the option \"[-]-legacy\"."); +#endif + break; + } + case EBPF_OPTION_CORE: { + ebpf_set_load_mode(EBPF_LOAD_CORE, EBPF_LOADED_FROM_USER); +#ifdef NETDATA_INTERNAL_CHECKS + netdata_log_info("EBPF running with \"CO-RE\" code, because it was started with the option \"[-]-core\"."); +#endif + break; + } + case EBPF_OPTION_UNITTEST: { + // if we cannot run until the end, we will cancel the unittest + int exit_code = ECANCELED; + if (ebpf_can_plugin_load_code(running_on_kernel, NETDATA_EBPF_PLUGIN_NAME)) + goto unittest; + + if (ebpf_adjust_memory_limit()) + goto unittest; + + // Load binary in entry mode + ebpf_ut_initialize_structure(MODE_ENTRY); + if (ebpf_ut_load_real_binary()) + goto unittest; + + ebpf_ut_cleanup_memory(); + + // Do not load a binary in entry mode + ebpf_ut_initialize_structure(MODE_ENTRY); + if (ebpf_ut_load_fake_binary()) + goto unittest; + + ebpf_ut_cleanup_memory(); + + exit_code = 0; +unittest: + exit(exit_code); + } + default: { + break; + } + } + } + + if (disable_cgroups) { + ebpf_disable_cgroups(); + } + + if (select_threads) { + disable_all_global_charts(); + uint64_t idx; + for (idx = 0; idx < EBPF_OPTION_ALL_CHARTS; idx++) { + if (select_threads & 1<<idx) + ebpf_enable_specific_chart(&ebpf_modules[idx], disable_cgroups); + } + } + + // Load apps_groups.conf + if (ebpf_read_apps_groups_conf( + &apps_groups_default_target, &apps_groups_root_target, ebpf_user_config_dir, "groups")) { + netdata_log_info("Cannot read process groups configuration file '%s/apps_groups.conf'. Will try '%s/apps_groups.conf'", + ebpf_user_config_dir, ebpf_stock_config_dir); + if (ebpf_read_apps_groups_conf( + &apps_groups_default_target, &apps_groups_root_target, ebpf_stock_config_dir, "groups")) { + netdata_log_error("Cannot read process groups '%s/apps_groups.conf'. There are no internal defaults. Failing.", + ebpf_stock_config_dir); + ebpf_exit(); + } + } else + netdata_log_info("Loaded config file '%s/apps_groups.conf'", ebpf_user_config_dir); +} + +/***************************************************************** + * + * Collector charts + * + *****************************************************************/ + +static char *load_event_stat[NETDATA_EBPF_LOAD_STAT_END] = {"legacy", "co-re"}; +static char *memlock_stat = {"memory_locked"}; +static char *hash_table_stat = {"hash_table"}; +static char *hash_table_core[NETDATA_EBPF_LOAD_STAT_END] = {"per_core", "unique"}; + +/** + * Send Hash Table PID data + * + * Send all information associated with a specific pid table. + * + * @param chart chart id + * @param idx index position in hash_table_stats + */ +static inline void ebpf_send_hash_table_pid_data(char *chart, uint32_t idx) +{ + int i; + ebpf_write_begin_chart(NETDATA_MONITORING_FAMILY, chart, ""); + for (i = 0; i < EBPF_MODULE_FUNCTION_IDX; i++) { + ebpf_module_t *wem = &ebpf_modules[i]; + if (wem->functions.apps_routine) + write_chart_dimension((char *)wem->info.thread_name, + (wem->enabled < NETDATA_THREAD_EBPF_STOPPING) ? + wem->hash_table_stats[idx]: + 0); + } + ebpf_write_end_chart(); +} + +/** + * Send Global Hash Table data + * + * Send all information associated with a specific pid table. + * + */ +static inline void ebpf_send_global_hash_table_data() +{ + int i; + ebpf_write_begin_chart(NETDATA_MONITORING_FAMILY, NETDATA_EBPF_HASH_TABLES_GLOBAL_ELEMENTS, ""); + for (i = 0; i < EBPF_MODULE_FUNCTION_IDX; i++) { + ebpf_module_t *wem = &ebpf_modules[i]; + write_chart_dimension((char *)wem->info.thread_name, + (wem->enabled < NETDATA_THREAD_EBPF_STOPPING) ? NETDATA_CONTROLLER_END: 0); + } + ebpf_write_end_chart(); +} + +/** + * Send Statistic Data + * + * Send statistic information to netdata. + */ +void ebpf_send_statistic_data() +{ + if (!publish_internal_metrics) + return; + + ebpf_write_begin_chart(NETDATA_MONITORING_FAMILY, NETDATA_EBPF_THREADS, ""); + int i; + for (i = 0; i < EBPF_MODULE_FUNCTION_IDX; i++) { + ebpf_module_t *wem = &ebpf_modules[i]; + if (wem->functions.fnct_routine) + continue; + + write_chart_dimension((char *)wem->info.thread_name, (wem->enabled < NETDATA_THREAD_EBPF_STOPPING) ? 1 : 0); + } + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_MONITORING_FAMILY, NETDATA_EBPF_LIFE_TIME, ""); + for (i = 0; i < EBPF_MODULE_FUNCTION_IDX ; i++) { + ebpf_module_t *wem = &ebpf_modules[i]; + // Threads like VFS is slow to load and this can create an invalid number, this is the motive + // we are also testing wem->lifetime value. + if (wem->functions.fnct_routine) + continue; + + write_chart_dimension((char *)wem->info.thread_name, + (wem->lifetime && wem->enabled < NETDATA_THREAD_EBPF_STOPPING) ? + (long long) (wem->lifetime - wem->running_time): + 0) ; + } + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_MONITORING_FAMILY, NETDATA_EBPF_LOAD_METHOD, ""); + write_chart_dimension(load_event_stat[NETDATA_EBPF_LOAD_STAT_LEGACY], (long long)plugin_statistics.legacy); + write_chart_dimension(load_event_stat[NETDATA_EBPF_LOAD_STAT_CORE], (long long)plugin_statistics.core); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_MONITORING_FAMILY, NETDATA_EBPF_KERNEL_MEMORY, ""); + write_chart_dimension(memlock_stat, (long long)plugin_statistics.memlock_kern); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_MONITORING_FAMILY, NETDATA_EBPF_HASH_TABLES_LOADED, ""); + write_chart_dimension(hash_table_stat, (long long)plugin_statistics.hash_tables); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_MONITORING_FAMILY, NETDATA_EBPF_HASH_TABLES_PER_CORE, ""); + write_chart_dimension(hash_table_core[NETDATA_EBPF_THREAD_PER_CORE], (long long)plugin_statistics.hash_percpu); + write_chart_dimension(hash_table_core[NETDATA_EBPF_THREAD_UNIQUE], (long long)plugin_statistics.hash_unique); + ebpf_write_end_chart(); + + ebpf_send_global_hash_table_data(); + + ebpf_send_hash_table_pid_data(NETDATA_EBPF_HASH_TABLES_INSERT_PID_ELEMENTS, NETDATA_EBPF_GLOBAL_TABLE_PID_TABLE_ADD); + ebpf_send_hash_table_pid_data(NETDATA_EBPF_HASH_TABLES_REMOVE_PID_ELEMENTS, NETDATA_EBPF_GLOBAL_TABLE_PID_TABLE_DEL); + + for (i = 0; i < EBPF_MODULE_FUNCTION_IDX; i++) { + ebpf_module_t *wem = &ebpf_modules[i]; + if (!wem->functions.fnct_routine) + continue; + + ebpf_write_begin_chart(NETDATA_MONITORING_FAMILY, (char *)wem->functions.fcnt_thread_chart_name, ""); + write_chart_dimension((char *)wem->info.thread_name, (wem->enabled < NETDATA_THREAD_EBPF_STOPPING) ? 1 : 0); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_MONITORING_FAMILY, (char *)wem->functions.fcnt_thread_lifetime_name, ""); + write_chart_dimension((char *)wem->info.thread_name, + (wem->lifetime && wem->enabled < NETDATA_THREAD_EBPF_STOPPING) ? + (long long) (wem->lifetime - wem->running_time): + 0) ; + ebpf_write_end_chart(); + } +} + +/** + * Update Internal Metric variable + * + * By default eBPF.plugin sends internal metrics for netdata, but user can + * disable this. + * + * The function updates the variable used to send charts. + */ +static void update_internal_metric_variable() +{ + const char *s = getenv("NETDATA_INTERNALS_MONITORING"); + if (s && *s && strcmp(s, "NO") == 0) + publish_internal_metrics = false; +} + +/** + * Create Thread Chart + * + * Write to standard output current values for threads charts. + * + * @param name is the chart name + * @param title chart title. + * @param units chart units + * @param order is the chart order + * @param update_every time used to update charts + * @param module a module to create a specific chart. + */ +static void ebpf_create_thread_chart(char *name, + char *title, + char *units, + int order, + int update_every, + ebpf_module_t *module) +{ + // common call for specific and all charts. + ebpf_write_chart_cmd(NETDATA_MONITORING_FAMILY, + name, + "", + title, + units, + NETDATA_EBPF_FAMILY, + NETDATA_EBPF_CHART_TYPE_LINE, + NULL, + order, + update_every, + "main"); + + if (module) { + ebpf_write_global_dimension((char *)module->info.thread_name, + (char *)module->info.thread_name, + ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX]); + return; + } + + int i; + for (i = 0; i < EBPF_MODULE_FUNCTION_IDX; i++) { + ebpf_module_t *em = &ebpf_modules[i]; + if (em->functions.fnct_routine) + continue; + + ebpf_write_global_dimension((char *)em->info.thread_name, + (char *)em->info.thread_name, + ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX]); + } +} + +/** + * Create chart for Load Thread + * + * Write to standard output current values for load mode. + * + * @param update_every time used to update charts + */ +static inline void ebpf_create_statistic_load_chart(int update_every) +{ + ebpf_write_chart_cmd(NETDATA_MONITORING_FAMILY, + NETDATA_EBPF_LOAD_METHOD, + "", + "Load info.", + "methods", + NETDATA_EBPF_FAMILY, + NETDATA_EBPF_CHART_TYPE_LINE, + NULL, + NETDATA_EBPF_ORDER_STAT_LOAD_METHOD, + update_every, + NETDATA_EBPF_MODULE_NAME_PROCESS); + + ebpf_write_global_dimension(load_event_stat[NETDATA_EBPF_LOAD_STAT_LEGACY], + load_event_stat[NETDATA_EBPF_LOAD_STAT_LEGACY], + ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX]); + + ebpf_write_global_dimension(load_event_stat[NETDATA_EBPF_LOAD_STAT_CORE], + load_event_stat[NETDATA_EBPF_LOAD_STAT_CORE], + ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX]); +} + +/** + * Create chart for Kernel Memory + * + * Write to standard output current values for allocated memory. + * + * @param update_every time used to update charts + */ +static inline void ebpf_create_statistic_kernel_memory(int update_every) +{ + ebpf_write_chart_cmd(NETDATA_MONITORING_FAMILY, + NETDATA_EBPF_KERNEL_MEMORY, + "", + "Memory allocated for hash tables.", + "bytes", + NETDATA_EBPF_FAMILY, + NETDATA_EBPF_CHART_TYPE_LINE, + NULL, + NETDATA_EBPF_ORDER_STAT_KERNEL_MEMORY, + update_every, + NETDATA_EBPF_MODULE_NAME_PROCESS); + + ebpf_write_global_dimension(memlock_stat, + memlock_stat, + ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX]); +} + +/** + * Create chart Hash Table + * + * Write to standard output number of hash tables used with this software. + * + * @param update_every time used to update charts + */ +static inline void ebpf_create_statistic_hash_tables(int update_every) +{ + ebpf_write_chart_cmd(NETDATA_MONITORING_FAMILY, + NETDATA_EBPF_HASH_TABLES_LOADED, + "", + "Number of hash tables loaded.", + "hash tables", + NETDATA_EBPF_FAMILY, + NETDATA_EBPF_CHART_TYPE_LINE, + NULL, + NETDATA_EBPF_ORDER_STAT_HASH_TABLES, + update_every, + NETDATA_EBPF_MODULE_NAME_PROCESS); + + ebpf_write_global_dimension(hash_table_stat, + hash_table_stat, + ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX]); +} + +/** + * Create chart for percpu stats + * + * Write to standard output current values for threads. + * + * @param update_every time used to update charts + */ +static inline void ebpf_create_statistic_hash_per_core(int update_every) +{ + ebpf_write_chart_cmd(NETDATA_MONITORING_FAMILY, + NETDATA_EBPF_HASH_TABLES_PER_CORE, + "", + "How threads are loading hash/array tables.", + "threads", + NETDATA_EBPF_FAMILY, + NETDATA_EBPF_CHART_TYPE_LINE, + NULL, + NETDATA_EBPF_ORDER_STAT_HASH_CORE, + update_every, + NETDATA_EBPF_MODULE_NAME_PROCESS); + + ebpf_write_global_dimension(hash_table_core[NETDATA_EBPF_THREAD_PER_CORE], + hash_table_core[NETDATA_EBPF_THREAD_PER_CORE], + ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX]); + + ebpf_write_global_dimension(hash_table_core[NETDATA_EBPF_THREAD_UNIQUE], + hash_table_core[NETDATA_EBPF_THREAD_UNIQUE], + ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX]); +} + +/** + * Hash table global elements + * + * Write to standard output current values inside global tables. + * + * @param update_every time used to update charts + */ +static void ebpf_create_statistic_hash_global_elements(int update_every) +{ + ebpf_write_chart_cmd(NETDATA_MONITORING_FAMILY, + NETDATA_EBPF_HASH_TABLES_GLOBAL_ELEMENTS, + "", + "Controllers inside global table", + "rows", + NETDATA_EBPF_FAMILY, + NETDATA_EBPF_CHART_TYPE_LINE, + NULL, + NETDATA_EBPF_ORDER_STAT_HASH_GLOBAL_TABLE_TOTAL, + update_every, + NETDATA_EBPF_MODULE_NAME_PROCESS); + + int i; + for (i = 0; i < EBPF_MODULE_FUNCTION_IDX; i++) { + ebpf_write_global_dimension((char *)ebpf_modules[i].info.thread_name, + (char *)ebpf_modules[i].info.thread_name, + ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX]); + } +} + +/** + * Hash table global elements + * + * Write to standard output current values inside global tables. + * + * @param update_every time used to update charts + * @param id chart id + * @param title chart title + * @param order ordder chart will be shown on dashboard. + */ +static void ebpf_create_statistic_hash_pid_table(int update_every, char *id, char *title, int order) +{ + ebpf_write_chart_cmd(NETDATA_MONITORING_FAMILY, + id, + "", + title, + "rows", + NETDATA_EBPF_FAMILY, + NETDATA_EBPF_CHART_TYPE_LINE, + NULL, + order, + update_every, + NETDATA_EBPF_MODULE_NAME_PROCESS); + + int i; + for (i = 0; i < EBPF_MODULE_FUNCTION_IDX; i++) { + ebpf_module_t *wem = &ebpf_modules[i]; + if (wem->functions.apps_routine) + ebpf_write_global_dimension((char *)wem->info.thread_name, + (char *)wem->info.thread_name, + ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + } +} + +/** + * Create Statistics Charts + * + * Create charts that will show statistics related to eBPF plugin. + * + * @param update_every time used to update charts + */ +static void ebpf_create_statistic_charts(int update_every) +{ + static char create_charts = 1; + update_internal_metric_variable(); + if (!publish_internal_metrics) + return; + + if (!create_charts) + return; + + create_charts = 0; + + ebpf_create_thread_chart(NETDATA_EBPF_THREADS, + "Threads running.", + "boolean", + NETDATA_EBPF_ORDER_STAT_THREADS, + update_every, + NULL); + + ebpf_create_thread_chart(NETDATA_EBPF_LIFE_TIME, + "Time remaining for thread.", + "seconds", + NETDATA_EBPF_ORDER_STAT_LIFE_TIME, + update_every, + NULL); + + int i,j; + char name[256]; + for (i = 0, j = NETDATA_EBPF_ORDER_FUNCTION_PER_THREAD; i < EBPF_MODULE_FUNCTION_IDX; i++) { + ebpf_module_t *em = &ebpf_modules[i]; + if (!em->functions.fnct_routine) + continue; + + em->functions.order_thread_chart = j; + snprintfz(name, sizeof(name) - 1, "%s_%s", NETDATA_EBPF_THREADS, em->info.thread_name); + em->functions.fcnt_thread_chart_name = strdupz(name); + ebpf_create_thread_chart(name, + "Threads running.", + "boolean", + j++, + update_every, + em); + + em->functions.order_thread_lifetime = j; + snprintfz(name, sizeof(name) - 1, "%s_%s", NETDATA_EBPF_LIFE_TIME, em->info.thread_name); + em->functions.fcnt_thread_lifetime_name = strdupz(name); + ebpf_create_thread_chart(name, + "Time remaining for thread.", + "seconds", + j++, + update_every, + em); + } + + ebpf_create_statistic_load_chart(update_every); + + ebpf_create_statistic_kernel_memory(update_every); + + ebpf_create_statistic_hash_tables(update_every); + + ebpf_create_statistic_hash_per_core(update_every); + + ebpf_create_statistic_hash_global_elements(update_every); + + ebpf_create_statistic_hash_pid_table(update_every, + NETDATA_EBPF_HASH_TABLES_INSERT_PID_ELEMENTS, + "Elements inserted into PID table", + NETDATA_EBPF_ORDER_STAT_HASH_PID_TABLE_ADDED); + + ebpf_create_statistic_hash_pid_table(update_every, + NETDATA_EBPF_HASH_TABLES_REMOVE_PID_ELEMENTS, + "Elements removed from PID table", + NETDATA_EBPF_ORDER_STAT_HASH_PID_TABLE_REMOVED); + + fflush(stdout); +} + +/***************************************************************** + * + * COLLECTOR ENTRY POINT + * + *****************************************************************/ + +/** + * Update PID file + * + * Update the content of PID file + * + * @param filename is the full name of the file. + * @param pid that identifies the process + */ +static void ebpf_update_pid_file(char *filename, pid_t pid) +{ + FILE *fp = fopen(filename, "w"); + if (!fp) + return; + + fprintf(fp, "%d", pid); + fclose(fp); +} + +/** + * Get Process Name + * + * Get process name from /proc/PID/status + * + * @param pid that identifies the process + */ +static char *ebpf_get_process_name(pid_t pid) +{ + char *name = NULL; + char filename[FILENAME_MAX + 1]; + snprintfz(filename, FILENAME_MAX, "/proc/%d/status", pid); + + procfile *ff = procfile_open(filename, " \t", PROCFILE_FLAG_DEFAULT); + if(unlikely(!ff)) { + netdata_log_error("Cannot open %s", filename); + return name; + } + + ff = procfile_readall(ff); + if(unlikely(!ff)) + return name; + + unsigned long i, lines = procfile_lines(ff); + for(i = 0; i < lines ; i++) { + char *cmp = procfile_lineword(ff, i, 0); + if (!strcmp(cmp, "Name:")) { + name = strdupz(procfile_lineword(ff, i, 1)); + break; + } + } + + procfile_close(ff); + + return name; +} + +/** + * Read Previous PID + * + * @param filename is the full name of the file. + * + * @return It returns the PID used during previous execution on success or 0 otherwise + */ +static pid_t ebpf_read_previous_pid(char *filename) +{ + FILE *fp = fopen(filename, "r"); + if (!fp) + return 0; + + char buffer[64]; + size_t length = fread(buffer, sizeof(*buffer), 63, fp); + pid_t old_pid = 0; + if (length) { + if (length > 63) + length = 63; + + buffer[length] = '\0'; + old_pid = (pid_t) str2uint32_t(buffer, NULL); + } + fclose(fp); + + return old_pid; +} + +/** + * Kill previous process + * + * Kill previous process whether it was not closed. + * + * @param filename is the full name of the file. + * @param pid that identifies the process + */ +static void ebpf_kill_previous_process(char *filename, pid_t pid) +{ + pid_t old_pid = ebpf_read_previous_pid(filename); + if (!old_pid) + return; + + // Process is not running + char *prev_name = ebpf_get_process_name(old_pid); + if (!prev_name) + return; + + char *current_name = ebpf_get_process_name(pid); + + if (!strcmp(prev_name, current_name)) + kill(old_pid, SIGKILL); + + freez(prev_name); + freez(current_name); + + // wait few microseconds before start new plugin + sleep_usec(USEC_PER_MS * 300); +} + +/** + * PID file + * + * Write the filename for PID inside the given vector. + * + * @param filename vector where we will store the name. + * @param length number of bytes available in filename vector + */ +void ebpf_pid_file(char *filename, size_t length) +{ + snprintfz(filename, length, "%s/var/run/ebpf.pid", netdata_configured_host_prefix); +} + +/** + * Manage PID + * + * This function kills another instance of eBPF whether it is necessary and update the file content. + * + * @param pid that identifies the process + */ +static void ebpf_manage_pid(pid_t pid) +{ + char filename[FILENAME_MAX + 1]; + ebpf_pid_file(filename, FILENAME_MAX); + + ebpf_kill_previous_process(filename, pid); + ebpf_update_pid_file(filename, pid); +} + +/** + * Set start routine + * + * Set static routine before threads to be created. + */ + static void ebpf_set_static_routine() + { + int i; + for (i = 0; ebpf_modules[i].info.thread_name; i++) { + ebpf_threads[i].start_routine = ebpf_modules[i].functions.start_routine; + } + } + +/** + * Entry point + * + * @param argc the number of arguments + * @param argv the pointer to the arguments + * + * @return it returns 0 on success and another integer otherwise + */ +int main(int argc, char **argv) +{ + clocks_init(); + nd_log_initialize_for_external_plugins(NETDATA_EBPF_PLUGIN_NAME); + + main_thread_id = gettid_cached(); + + set_global_variables(); + ebpf_parse_args(argc, argv); + ebpf_manage_pid(getpid()); + + if (ebpf_can_plugin_load_code(running_on_kernel, NETDATA_EBPF_PLUGIN_NAME)) + return 2; + + if (ebpf_adjust_memory_limit()) + return 3; + + signal(SIGINT, ebpf_stop_threads); + signal(SIGQUIT, ebpf_stop_threads); + signal(SIGTERM, ebpf_stop_threads); + signal(SIGPIPE, ebpf_stop_threads); + + ebpf_start_pthread_variables(); + + netdata_configured_host_prefix = getenv("NETDATA_HOST_PREFIX"); + if(verify_netdata_host_prefix(true) == -1) ebpf_exit(6); + + ebpf_allocate_common_vectors(); + +#ifdef LIBBPF_MAJOR_VERSION + libbpf_set_strict_mode(LIBBPF_STRICT_ALL); +#endif + + ebpf_read_local_addresses_unsafe(); + read_local_ports("/proc/net/tcp", IPPROTO_TCP); + read_local_ports("/proc/net/tcp6", IPPROTO_TCP); + read_local_ports("/proc/net/udp", IPPROTO_UDP); + read_local_ports("/proc/net/udp6", IPPROTO_UDP); + + ebpf_set_static_routine(); + + cgroup_integration_thread.start_routine = ebpf_cgroup_integration; + + cgroup_integration_thread.thread = nd_thread_create( + cgroup_integration_thread.name, + NETDATA_THREAD_OPTION_DEFAULT, + ebpf_cgroup_integration, + NULL); + + int i; + for (i = 0; ebpf_threads[i].name != NULL; i++) { + struct netdata_static_thread *st = &ebpf_threads[i]; + + ebpf_module_t *em = &ebpf_modules[i]; + em->thread = st; + em->thread_id = i; + if (em->enabled != NETDATA_THREAD_EBPF_NOT_RUNNING) { + em->enabled = NETDATA_THREAD_EBPF_RUNNING; + em->lifetime = EBPF_NON_FUNCTION_LIFE_TIME; + st->thread = nd_thread_create(st->name, NETDATA_THREAD_OPTION_JOINABLE, st->start_routine, em); + } else { + em->lifetime = EBPF_DEFAULT_LIFETIME; + } + } + + usec_t step = USEC_PER_SEC; + heartbeat_t hb; + heartbeat_init(&hb); + int update_apps_every = (int) EBPF_CFG_UPDATE_APPS_EVERY_DEFAULT; + int max_period = update_apps_every * EBPF_CLEANUP_FACTOR; + int update_apps_list = update_apps_every - 1; + int process_maps_per_core = ebpf_modules[EBPF_MODULE_PROCESS_IDX].maps_per_core; + //Plugin will be killed when it receives a signal + for ( ; !ebpf_plugin_stop(); global_iterations_counter++) { + (void)heartbeat_next(&hb, step); + + if (global_iterations_counter % EBPF_DEFAULT_UPDATE_EVERY == 0) { + pthread_mutex_lock(&lock); + ebpf_create_statistic_charts(EBPF_DEFAULT_UPDATE_EVERY); + + ebpf_send_statistic_data(); + pthread_mutex_unlock(&lock); + fflush(stdout); + } + + if (++update_apps_list == update_apps_every) { + update_apps_list = 0; + pthread_mutex_lock(&lock); + pthread_mutex_lock(&collect_data_mutex); + ebpf_cleanup_exited_pids(max_period); + collect_data_for_all_processes(process_pid_fd, process_maps_per_core); + + ebpf_create_apps_charts(apps_groups_root_target); + pthread_mutex_unlock(&collect_data_mutex); + pthread_mutex_unlock(&lock); + } + } + + ebpf_stop_threads(0); + + return 0; +} diff --git a/src/collectors/ebpf.plugin/ebpf.d.conf b/src/collectors/ebpf.plugin/ebpf.d.conf new file mode 100644 index 000000000..833c8fd99 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf.d.conf @@ -0,0 +1,77 @@ +# +# Global options +# +# The `ebpf load mode` option accepts the following values : +# `entry` : The eBPF collector only monitors calls for the functions, and does not show charts related to errors. +# `return : In the `return` mode, the eBPF collector monitors the same kernel functions as `entry`, but also creates +# new charts for the return of these functions, such as errors. +# +# The eBPF collector also creates charts for each running application through an integration with the `apps.plugin` +# or `cgroups.plugin`. +# If you want to disable the integration with `apps.plugin` or `cgroups.plugin` along with the above charts, change the setting +# `apps` and `cgroups` to 'no'. +# +# The `update every` option defines the number of seconds used to read data from kernel and send to netdata +# +# The `pid table size` defines the maximum number of PIDs stored in the application hash tables. +# +# The `btf path` specifies where to find the BTF files. +# +# The `maps per core` defines if hash tables will be per core or not. This option is ignored on kernels older than 4.15. +# +# The `lifetime` defines the time length a thread will run when it is enabled by a function. +# +[global] + ebpf load mode = entry + apps = no + cgroups = no + update every = 5 + pid table size = 32768 + btf path = /sys/kernel/btf/ + maps per core = yes + lifetime = 300 + +# +# eBPF Programs +# +# The eBPF collector has the following eBPF programs: +# +# `cachestat` : Make charts for kernel functions related to page cache. +# `dcstat` : Make charts for kernel functions related to directory cache. +# `disk` : Monitor I/O latencies for disks +# `fd` : This eBPF program creates charts that show information about file manipulation. +# `filesystem`: Monitor calls for functions used to manipulate specific filesystems +# `hardirq` : Monitor latency of serving hardware interrupt requests (hard IRQs). +# `mdflush` : Monitors flush counts for multi-devices. +# `mount` : Monitor calls for syscalls mount and umount +# `oomkill` : This eBPF program creates a chart that shows which process got OOM killed and when. +# `process` : This eBPF program creates charts that show information about process life. +# `shm` : Monitor calls for syscalls shmget, shmat, shmdt and shmctl. +# `socket` : This eBPF program creates charts with information about `TCP` and `UDP` functions, including the +# bandwidth consumed by each. +# `softirq` : Monitor latency of serving software interrupt requests (soft IRQs). +# `sync` : Monitor calls for syscall sync(2). +# `swap` : Monitor calls for internal swap functions. +# `vfs` : This eBPF program creates charts that show information about process VFS IO, VFS file manipulation and +# files removed. +# +# When plugin detects that system has support to BTF, it enables integration with apps.plugin. +# +[ebpf programs] + cachestat = no + dcstat = no + disk = no + fd = no + filesystem = no + hardirq = no + mdflush = no + mount = yes + oomkill = yes + process = no + shm = no + socket = no + softirq = yes + sync = no + swap = no + vfs = no + network connections = no diff --git a/src/collectors/ebpf.plugin/ebpf.d/cachestat.conf b/src/collectors/ebpf.plugin/ebpf.d/cachestat.conf new file mode 100644 index 000000000..c378e82e8 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf.d/cachestat.conf @@ -0,0 +1,42 @@ +# The `ebpf load mode` option accepts the following values : +# `entry` : The eBPF collector only monitors calls for the functions, and does not show charts related to errors. +# `return : In the `return` mode, the eBPF collector monitors the same kernel functions as `entry`, but also creates +# new charts for the return of these functions, such as errors. +# +# The eBPF collector also creates charts for each running application through an integration with the `apps.plugin` +# or `cgroups.plugin`. +# If you want to disable the integration with `apps.plugin` or `cgroups.plugin` along with the above charts, change +# the setting `apps` and `cgroups` to 'no'. +# +# The `pid table size` defines the maximum number of PIDs stored inside the application hash table. +# +# The `ebpf type format` option accepts the following values : +# `auto` : The eBPF collector will investigate hardware and select between the two next options. +# `legacy`: The eBPF collector will load the legacy code. Note: This has a bigger overload. +# `co-re` : The eBPF collector will use latest tracing method. Note: This is not available on all platforms. +# +# The `ebpf co-re tracing` option accepts the following values: +# `trampoline`: This is the default mode used by the eBPF collector, due the small overhead added to host. +# `probe` : This is the same as legacy code. +# +# The `collect pid` option defines the PID stored inside hash tables and accepts the following options: +# `real parent`: Only stores real parent inside PID +# `parent` : Only stores parent PID. +# `all` : Stores all PIDs used by software. This is the most expensive option. +# +# The `maps per core` defines if hash tables will be per core or not. This option is ignored on kernels older than 4.6. +# +# The `lifetime` defines the time length a thread will run when it is enabled by a function. +# +# Uncomment lines to define specific options for thread. +[global] +# ebpf load mode = entry +# apps = yes +# cgroups = no +# update every = 10 +# pid table size = 32768 + ebpf type format = auto + ebpf co-re tracing = trampoline + collect pid = all +# maps per core = yes + lifetime = 300 diff --git a/src/collectors/ebpf.plugin/ebpf.d/dcstat.conf b/src/collectors/ebpf.plugin/ebpf.d/dcstat.conf new file mode 100644 index 000000000..2d54bce97 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf.d/dcstat.conf @@ -0,0 +1,40 @@ +# The `ebpf load mode` option accepts the following values : +# `entry` : The eBPF collector only monitors calls for the functions, and does not show charts related to errors. +# `return : In the `return` mode, the eBPF collector monitors the same kernel functions as `entry`, but also creates +# new charts for the return of these functions, such as errors. +# +# The eBPF collector also creates charts for each running application through an integration with the `apps.plugin` +# or `cgroups.plugin`. +# If you want to disable the integration with `apps.plugin` or `cgroups.plugin` along with the above charts, change +# the setting `apps` and `cgroups` to 'no'. +# +# The `ebpf type format` option accepts the following values : +# `auto` : The eBPF collector will investigate hardware and select between the two next options. +# `legacy`: The eBPF collector will load the legacy code. Note: This has a bigger overload. +# `co-re` : The eBPF collector will use latest tracing method. Note: This is not available on all platforms. +# +# The `ebpf co-re tracing` option accepts the following values: +# `trampoline`: This is the default mode used by the eBPF collector, due the small overhead added to host. +# `probe` : This is the same as legacy code. +# +# The `collect pid` option defines the PID stored inside hash tables and accepts the following options: +# `real parent`: Only stores real parent inside PID +# `parent` : Only stores parent PID. +# `all` : Stores all PIDs used by software. This is the most expensive option. +# +# The `maps per core` defines if hash tables will be per core or not. This option is ignored on kernels older than 4.6. +# +# The `lifetime` defines the time length a thread will run when it is enabled by a function. +# +# Uncomment lines to define specific options for thread. +[global] +# ebpf load mode = entry +# apps = yes +# cgroups = no +# update every = 10 +# pid table size = 32768 + ebpf type format = auto + ebpf co-re tracing = trampoline + collect pid = all +# maps per core = yes + lifetime = 300 diff --git a/src/collectors/ebpf.plugin/ebpf.d/disk.conf b/src/collectors/ebpf.plugin/ebpf.d/disk.conf new file mode 100644 index 000000000..c5a0a2708 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf.d/disk.conf @@ -0,0 +1,12 @@ +# The `ebpf load mode` option accepts the following values : +# `entry` : The eBPF collector only monitors calls for the functions, and does not show charts related to errors. +# `return : In the `return` mode, the eBPF collector monitors the same kernel functions as `entry`, but also creates +# new charts for the return of these functions, such as errors. +# +# The `lifetime` defines the time length a thread will run when it is enabled by a function. +# +[global] +# ebpf load mode = entry +# update every = 10 + lifetime = 300 + diff --git a/src/collectors/ebpf.plugin/ebpf.d/ebpf_kernel_reject_list.txt b/src/collectors/ebpf.plugin/ebpf.d/ebpf_kernel_reject_list.txt new file mode 100644 index 000000000..539bf357f --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf.d/ebpf_kernel_reject_list.txt @@ -0,0 +1 @@ +Ubuntu 4.18.0 diff --git a/src/collectors/ebpf.plugin/ebpf.d/fd.conf b/src/collectors/ebpf.plugin/ebpf.d/fd.conf new file mode 100644 index 000000000..d48230323 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf.d/fd.conf @@ -0,0 +1,27 @@ +# The `ebpf load mode` option accepts the following values : +# `entry` : The eBPF collector only monitors calls for the functions, and does not show charts related to errors. +# `return : In the `return` mode, the eBPF collector monitors the same kernel functions as `entry`, but also creates +# new charts for the return of these functions, such as errors. +# +# The eBPF collector also creates charts for each running application through an integration with the `apps.plugin` +# or `cgroups.plugin`. +# If you want to disable the integration with `apps.plugin` or `cgroups.plugin` along with the above charts, change +# the setting `apps` and `cgroups` to 'no'. +# +# The `pid table size` defines the maximum number of PIDs stored inside the hash table. +# +# The `maps per core` defines if hash tables will be per core or not. This option is ignored on kernels older than 4.6. +# +# The `lifetime` defines the time length a thread will run when it is enabled by a function. +# +# Uncomment lines to define specific options for thread. +[global] +# ebpf load mode = entry +# apps = yes +# cgroups = no +# update every = 10 +# pid table size = 32768 + ebpf type format = auto + ebpf co-re tracing = trampoline +# maps per core = yes + lifetime = 300 diff --git a/src/collectors/ebpf.plugin/ebpf.d/filesystem.conf b/src/collectors/ebpf.plugin/ebpf.d/filesystem.conf new file mode 100644 index 000000000..209abba77 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf.d/filesystem.conf @@ -0,0 +1,23 @@ +# The `ebpf load mode` option accepts the following values : +# `entry` : The eBPF collector only monitors calls for the functions, and does not show charts related to errors. +# `return : In the `return` mode, the eBPF collector monitors the same kernel functions as `entry`, but also creates +# new charts for the return of these functions, such as errors. +# +# The `lifetime` defines the time length a thread will run when it is enabled by a function. +# +# The eBPF collector also creates charts for each running application through an integration with the `apps plugin`. +# If you want to disable the integration with `apps.plugin` along with the above charts, change the setting `apps` to +# 'no'. +# +[global] +# ebpf load mode = entry +# update every = 10 + lifetime = 300 + +# All filesystems are named as 'NAMEdist' where NAME is the filesystem name while 'dist' is a reference for distribution. +[filesystem] + btrfsdist = yes + ext4dist = yes + nfsdist = yes + xfsdist = yes + zfsdist = yes diff --git a/src/collectors/ebpf.plugin/ebpf.d/functions.conf b/src/collectors/ebpf.plugin/ebpf.d/functions.conf new file mode 100644 index 000000000..a4f57f641 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf.d/functions.conf @@ -0,0 +1,3 @@ +#[global] +# update every = 5 + diff --git a/src/collectors/ebpf.plugin/ebpf.d/hardirq.conf b/src/collectors/ebpf.plugin/ebpf.d/hardirq.conf new file mode 100644 index 000000000..6a47a94bf --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf.d/hardirq.conf @@ -0,0 +1,11 @@ +# The `ebpf load mode` option accepts the following values : +# `entry` : The eBPF collector only monitors calls for the functions, and does not show charts related to errors. +# `return : In the `return` mode, the eBPF collector monitors the same kernel functions as `entry`, but also creates +# new charts for the return of these functions, such as errors. +# +# The `lifetime` defines the time length a thread will run when it is enabled by a function. +# +[global] +# ebpf load mode = entry +# update every = 10 + lifetime = 300 diff --git a/src/collectors/ebpf.plugin/ebpf.d/mdflush.conf b/src/collectors/ebpf.plugin/ebpf.d/mdflush.conf new file mode 100644 index 000000000..ea97ebe85 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf.d/mdflush.conf @@ -0,0 +1,11 @@ +# The `ebpf load mode` option accepts the following values : +# `entry` : The eBPF collector only monitors calls for the functions, and does not show charts related to errors. +# `return : In the `return` mode, the eBPF collector monitors the same kernel functions as `entry`, but also creates +# new charts for the return of these functions, such as errors. +# +# The `lifetime` defines the time length a thread will run when it is enabled by a function. +# +[global] +# ebpf load mode = entry +# update every = 1 + lifetime = 300 diff --git a/src/collectors/ebpf.plugin/ebpf.d/mount.conf b/src/collectors/ebpf.plugin/ebpf.d/mount.conf new file mode 100644 index 000000000..ff9a2948c --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf.d/mount.conf @@ -0,0 +1,23 @@ +# The `ebpf load mode` option accepts the following values : +# `entry` : The eBPF collector only monitors calls for the functions, and does not show charts related to errors. +# `return : In the `return` mode, the eBPF collector monitors the same kernel functions as `entry`, but also creates +# new charts for the return of these functions, such as errors. +# +# The `ebpf type format` option accepts the following values : +# `auto` : The eBPF collector will investigate hardware and select between the two next options. +# `legacy`: The eBPF collector will load the legacy code. Note: This has a bigger overload. +# `co-re` : The eBPF collector will use latest tracing method. Note: This is not available on all platforms. +# +# The `ebpf co-re tracing` option accepts the following values: +# `trampoline`: This is the default mode used by the eBPF collector, due the small overhead added to host. +# `tracepoint`: When available, the eBPF collector will use kernel tracepoint to monitor syscall. +# `probe` : This is the same as legacy code. +# +# The `lifetime` defines the time length a thread will run when it is enabled by a function. +# +[global] +# ebpf load mode = entry +# update every = 1 + ebpf type format = auto + ebpf co-re tracing = trampoline + lifetime = 300 diff --git a/src/collectors/ebpf.plugin/ebpf.d/network.conf b/src/collectors/ebpf.plugin/ebpf.d/network.conf new file mode 100644 index 000000000..99c32edc1 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf.d/network.conf @@ -0,0 +1,66 @@ +# The `ebpf load mode` option accepts the following values : +# `entry` : The eBPF collector only monitors calls for the functions, and does not show charts related to errors. +# `return : In the `return` mode, the eBPF collector monitors the same kernel functions as `entry`, but also creates +# new charts for the return of these functions, such as errors. +# +# The eBPF collector also creates charts for each running application through an integration with the `apps.plugin` +# or `cgroups.plugin`. +# If you want to disable the integration with `apps.plugin` or `cgroups.plugin` along with the above charts, change +# the setting `apps` and `cgroups` to 'no'. +# +# The following options change the hash table size: +# `bandwidth table size`: Maximum number of connections monitored +# `ipv4 connection table size`: Maximum number of IPV4 connections monitored +# `ipv6 connection table size`: Maximum number of IPV6 connections monitored +# `udp connection table size`: Maximum number of UDP connections monitored +# +# The `ebpf type format` option accepts the following values : +# `auto` : The eBPF collector will investigate hardware and select between the two next options. +# `legacy`: The eBPF collector will load the legacy code. Note: This has a bigger overload. +# `co-re` : The eBPF collector will use latest tracing method. Note: This is not available on all platforms. +# +# The `ebpf co-re tracing` option accepts the following values: +# `trampoline`: This is the default mode used by the eBPF collector, due the small overhead added to host. +# `tracepoint`: When available, the eBPF collector will use kernel tracepoint to monitor syscall. +# `probe` : This is the same as legacy code. +# +# The `maps per core` defines if hash tables will be per core or not. This option is ignored on kernels older than 4.6. +# +# The `collect pid` option defines the PID stored inside hash tables and accepts the following options: +# `real parent`: Only stores real parent inside PID +# `parent` : Only stores parent PID. +# `all` : Stores all PIDs used by software. This is the most expensive option. +# +# The `lifetime` defines the time length a thread will run when it is enabled by a function. +# +# Uncomment lines to define specific options for thread. +[global] +# ebpf load mode = entry +# apps = yes +# cgroups = no +# update every = 10 + bandwidth table size = 16384 + socket monitoring table size = 16384 + udp connection table size = 4096 + ebpf type format = auto + ebpf co-re tracing = probe + maps per core = no + collect pid = all + lifetime = 300 + +# +# Network Connection +# +# This is a feature with status WIP(Work in Progress) +# +[network connections] + enabled = yes + resolve hostnames = no + resolve service names = yes + ports = * +# ips = !127.0.0.1/8 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 fc00::/7 !::1/128 + ips = * + hostnames = * + +[service name] + 19999 = Netdata diff --git a/src/collectors/ebpf.plugin/ebpf.d/oomkill.conf b/src/collectors/ebpf.plugin/ebpf.d/oomkill.conf new file mode 100644 index 000000000..ea97ebe85 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf.d/oomkill.conf @@ -0,0 +1,11 @@ +# The `ebpf load mode` option accepts the following values : +# `entry` : The eBPF collector only monitors calls for the functions, and does not show charts related to errors. +# `return : In the `return` mode, the eBPF collector monitors the same kernel functions as `entry`, but also creates +# new charts for the return of these functions, such as errors. +# +# The `lifetime` defines the time length a thread will run when it is enabled by a function. +# +[global] +# ebpf load mode = entry +# update every = 1 + lifetime = 300 diff --git a/src/collectors/ebpf.plugin/ebpf.d/process.conf b/src/collectors/ebpf.plugin/ebpf.d/process.conf new file mode 100644 index 000000000..6f6477003 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf.d/process.conf @@ -0,0 +1,31 @@ +# The `ebpf load mode` option accepts the following values : +# `entry` : The eBPF collector only monitors calls for the functions, and does not show charts related to errors. +# `return : In the `return` mode, the eBPF collector monitors the same kernel functions as `entry`, but also creates +# new charts for the return of these functions, such as errors. +# +# The eBPF collector also creates charts for each running application through an integration with the `apps.plugin` +# or `cgroups.plugin`. +# If you want to disable the integration with `apps.plugin` or `cgroups.plugin` along with the above charts, change +# the setting `apps` and `cgroups` to 'no'. +# +# The `pid table size` defines the maximum number of PIDs stored inside the hash table. +# +# The `collect pid` option defines the PID stored inside hash tables and accepts the following options: +# `real parent`: Only stores real parent inside PID +# `parent` : Only stores parent PID. +# `all` : Stores all PIDs used by software. This is the most expensive option. +# +# The `maps per core` defines if hash tables will be per core or not. This option is ignored on kernels older than 4.6. +# +# The `lifetime` defines the time length a thread will run when it is enabled by a function. +# +# Uncomment lines to define specific options for thread. +[global] +# ebpf load mode = entry +# apps = yes +# cgroups = no +# update every = 10 +# pid table size = 32768 + collect pid = all +# maps per core = yes + lifetime = 300 diff --git a/src/collectors/ebpf.plugin/ebpf.d/shm.conf b/src/collectors/ebpf.plugin/ebpf.d/shm.conf new file mode 100644 index 000000000..0314bdc95 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf.d/shm.conf @@ -0,0 +1,43 @@ +# The `ebpf load mode` option accepts the following values : +# `entry` : The eBPF collector only monitors calls for the functions, and does not show charts related to errors. +# `return : In the `return` mode, the eBPF collector monitors the same kernel functions as `entry`, but also creates +# new charts for the return of these functions, such as errors. +# +# The eBPF collector also creates charts for each running application through an integration with the `apps.plugin` +# or `cgroups.plugin`. +# If you want to disable the integration with `apps.plugin` or `cgroups.plugin` along with the above charts, change +# the setting `apps` and `cgroups` to 'no'. +# +# The `ebpf type format` option accepts the following values : +# `auto` : The eBPF collector will investigate hardware and select between the two next options. +# `legacy`: The eBPF collector will load the legacy code. Note: This has a bigger overload. +# `co-re` : The eBPF collector will use latest tracing method. Note: This is not available on all platforms. +# +# The `ebpf co-re tracing` option accepts the following values: +# `trampoline`: This is the default mode used by the eBPF collector, due the small overhead added to host. +# `tracepoint`: When available, the eBPF collector will use kernel tracepoint to monitor syscall. +# `probe` : This is the same as legacy code. +# +# The `maps per core` defines if hash tables will be per core or not. This option is ignored on kernels older than 4.6. +# +# The `lifetime` defines the time length a thread will run when it is enabled by a function. +# +# Uncomment lines to define specific options for thread. +[global] +# ebpf load mode = entry +# apps = yes +# cgroups = no +# update every = 10 +# pid table size = 32768 + ebpf type format = auto + ebpf co-re tracing = trampoline + collect pid = all +# maps per core = yes + lifetime = 300 + +# List of monitored syscalls +[syscalls] + shmget = yes + shmat = yes + shmdt = yes + shmctl = yes diff --git a/src/collectors/ebpf.plugin/ebpf.d/softirq.conf b/src/collectors/ebpf.plugin/ebpf.d/softirq.conf new file mode 100644 index 000000000..6a47a94bf --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf.d/softirq.conf @@ -0,0 +1,11 @@ +# The `ebpf load mode` option accepts the following values : +# `entry` : The eBPF collector only monitors calls for the functions, and does not show charts related to errors. +# `return : In the `return` mode, the eBPF collector monitors the same kernel functions as `entry`, but also creates +# new charts for the return of these functions, such as errors. +# +# The `lifetime` defines the time length a thread will run when it is enabled by a function. +# +[global] +# ebpf load mode = entry +# update every = 10 + lifetime = 300 diff --git a/src/collectors/ebpf.plugin/ebpf.d/swap.conf b/src/collectors/ebpf.plugin/ebpf.d/swap.conf new file mode 100644 index 000000000..6d76b9880 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf.d/swap.conf @@ -0,0 +1,35 @@ +# The `ebpf load mode` option accepts the following values : +# `entry` : The eBPF collector only monitors calls for the functions, and does not show charts related to errors. +# `return : In the `return` mode, the eBPF collector monitors the same kernel functions as `entry`, but also creates +# new charts for the return of these functions, such as errors. +# +# The eBPF collector also creates charts for each running application through an integration with the `apps.plugin` +# or `cgroups.plugin`. +# If you want to disable the integration with `apps.plugin` or `cgroups.plugin` along with the above charts, change +# the setting `apps` and `cgroups` to 'no'. +# +# The `ebpf type format` option accepts the following values : +# `auto` : The eBPF collector will investigate hardware and select between the two next options. +# `legacy`: The eBPF collector will load the legacy code. Note: This has a bigger overload. +# `co-re` : The eBPF collector will use latest tracing method. Note: This is not available on all platforms. +# +# The `ebpf co-re tracing` option accepts the following values: +# `trampoline`: This is the default mode used by the eBPF collector, due the small overhead added to host. +# `probe` : This is the same as legacy code. +# +# The `maps per core` defines if hash tables will be per core or not. This option is ignored on kernels older than 4.6. +# +# The `lifetime` defines the time length a thread will run when it is enabled by a function. +# +# Uncomment lines to define specific options for thread. +[global] +# ebpf load mode = entry +# apps = yes +# cgroups = no +# update every = 10 +# pid table size = 32768 + ebpf type format = auto + ebpf co-re tracing = trampoline + collect pid = all +# maps per core = yes + lifetime = 300 diff --git a/src/collectors/ebpf.plugin/ebpf.d/sync.conf b/src/collectors/ebpf.plugin/ebpf.d/sync.conf new file mode 100644 index 000000000..a086ed4db --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf.d/sync.conf @@ -0,0 +1,43 @@ +# The `ebpf load mode` option accepts the following values : +# `entry` : The eBPF collector only monitors calls for the functions, and does not show charts related to errors. +# `return : In the `return` mode, the eBPF collector monitors the same kernel functions as `entry`, but also creates +# new charts for the return of these functions, such as errors. +# +# The eBPF collector also creates charts for each running application through an integration with the `apps.plugin` +# or `cgroups.plugin`. +# If you want to disable the integration with `apps.plugin` or `cgroups.plugin` along with the above charts, change +# the setting `apps` and `cgroups` to 'no'. +# +# The `ebpf type format` option accepts the following values : +# `auto` : The eBPF collector will investigate hardware and select between the two next options. +# `legacy`: The eBPF collector will load the legacy code. Note: This has a bigger overload. +# `co-re` : The eBPF collector will use latest tracing method. Note: This is not available on all platforms. +# +# The `ebpf co-re tracing` option accepts the following values: +# `trampoline`: This is the default mode used by the eBPF collector, due the small overhead added to host. +# `tracepoint`: When available, the eBPF collector will use kernel tracepoint to monitor syscall. +# `probe` : This is the same as legacy code. +# +# The `maps per core` defines if hash tables will be per core or not. This option is ignored on kernels older than 4.6. +# +# The `lifetime` defines the time length a thread will run when it is enabled by a function. +# +# Uncomment lines to define specific options for thread. +[global] +# ebpf load mode = entry +# apps = yes +# cgroups = no +# update every = 10 + ebpf type format = auto + ebpf co-re tracing = trampoline +# maps per core = yes + lifetime = 300 + +# List of monitored syscalls +[syscalls] + sync = yes + msync = yes + fsync = yes + fdatasync = yes + syncfs = yes + sync_file_range = yes diff --git a/src/collectors/ebpf.plugin/ebpf.d/vfs.conf b/src/collectors/ebpf.plugin/ebpf.d/vfs.conf new file mode 100644 index 000000000..f511581b8 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf.d/vfs.conf @@ -0,0 +1,35 @@ +# The `ebpf load mode` option accepts the following values : +# `entry` : The eBPF collector only monitors calls for the functions, and does not show charts related to errors. +# `return : In the `return` mode, the eBPF collector monitors the same kernel functions as `entry`, but also creates +# new charts for the return of these functions, such as errors. +# +# The eBPF collector also creates charts for each running application through an integration with the `apps.plugin` +# or `cgroups.plugin`. +# If you want to disable the integration with `apps.plugin` or `cgroups.plugin` along with the above charts, change +# the setting `apps` and `cgroups` to 'no'. +# +# The `ebpf type format` option accepts the following values : +# `auto` : The eBPF collector will investigate hardware and select between the two next options. +# `legacy`: The eBPF collector will load the legacy code. Note: This has a bigger overload. +# `co-re` : The eBPF collector will use latest tracing method. Note: This is not available on all platforms. +# +# The `ebpf co-re tracing` option accepts the following values: +# `trampoline`: This is the default mode used by the eBPF collector, due the small overhead added to host. +# `tracepoint`: When available, the eBPF collector will use kernel tracepoint to monitor syscall. +# `probe` : This is the same as legacy code. +# +# The `maps per core` defines if hash tables will be per core or not. This option is ignored on kernels older than 4.6. +# +# The `lifetime` defines the time length a thread will run when it is enabled by a function. +# +# Uncomment lines to define specific options for thread. +[global] +# ebpf load mode = entry +# apps = yes +# cgroups = no +# update every = 10 +# pid table size = 32768 + ebpf type format = auto + ebpf co-re tracing = trampoline +# maps per core = yes + lifetime = 300 diff --git a/src/collectors/ebpf.plugin/ebpf.h b/src/collectors/ebpf.plugin/ebpf.h new file mode 100644 index 000000000..c54b5900d --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf.h @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_COLLECTOR_EBPF_H +#define NETDATA_COLLECTOR_EBPF_H 1 + +#ifndef __FreeBSD__ +#include <linux/perf_event.h> +#endif +#include <stdint.h> +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <dlfcn.h> + +#include <fcntl.h> +#include <ctype.h> +#include <dirent.h> + +// From libnetdata.h +#include "libnetdata/threads/threads.h" +#include "libnetdata/locks/locks.h" +#include "libnetdata/avl/avl.h" +#include "libnetdata/clocks/clocks.h" +#include "libnetdata/config/appconfig.h" +#include "libnetdata/ebpf/ebpf.h" +#include "libnetdata/procfile/procfile.h" +#include "collectors/cgroups.plugin/sys_fs_cgroup.h" +#include "daemon/main.h" + +#include "ebpf_apps.h" +#include "ebpf_functions.h" +#include "ebpf_cgroup.h" + +#define NETDATA_EBPF_OLD_CONFIG_FILE "ebpf.conf" +#define NETDATA_EBPF_CONFIG_FILE "ebpf.d.conf" + +#ifdef LIBBPF_MAJOR_VERSION // BTF code +#include "cachestat.skel.h" +#include "dc.skel.h" +#include "disk.skel.h" +#include "fd.skel.h" +#include "filesystem.skel.h" +#include "hardirq.skel.h" +#include "mdflush.skel.h" +#include "mount.skel.h" +#include "shm.skel.h" +#include "sync.skel.h" +#include "socket.skel.h" +#include "swap.skel.h" +#include "vfs.skel.h" + +extern struct cachestat_bpf *cachestat_bpf_obj; +extern struct dc_bpf *dc_bpf_obj; +extern struct disk_bpf *disk_bpf_obj; +extern struct fd_bpf *fd_bpf_obj; +extern struct hardirq_bpf *hardirq_bpf_obj; +extern struct mount_bpf *mount_bpf_obj; +extern struct mdflush_bpf *mdflush_bpf_obj; +extern struct shm_bpf *shm_bpf_obj; +extern struct socket_bpf *socket_bpf_obj; +extern struct swap_bpf *bpf_obj; +extern struct vfs_bpf *vfs_bpf_obj; +#endif + +typedef struct netdata_syscall_stat { + unsigned long bytes; // total number of bytes + uint64_t call; // total number of calls + uint64_t ecall; // number of calls that returned error + struct netdata_syscall_stat *next; // Link list +} netdata_syscall_stat_t; + +typedef struct netdata_publish_syscall { + char *dimension; + char *name; + char *algorithm; + unsigned long nbyte; + unsigned long pbyte; + uint64_t ncall; + uint64_t pcall; + uint64_t nerr; + uint64_t perr; + struct netdata_publish_syscall *next; +} netdata_publish_syscall_t; + +typedef struct netdata_publish_vfs_common { + long write; + long read; + + long running; + long zombie; +} netdata_publish_vfs_common_t; + +typedef struct netdata_error_report { + char comm[16]; + __u32 pid; + + int type; + int err; +} netdata_error_report_t; + +typedef struct netdata_ebpf_judy_pid { + ARAL *pid_table; + + // Index for PIDs + struct { // support for multiple indexing engines + Pvoid_t JudyLArray; // the hash table + RW_SPINLOCK rw_spinlock; // protect the index + } index; +} netdata_ebpf_judy_pid_t; + +typedef struct netdata_ebpf_judy_pid_stats { + char *cmdline; + + // Index for Socket timestamp + struct { // support for multiple indexing engines + Pvoid_t JudyLArray; // the hash table + RW_SPINLOCK rw_spinlock; // protect the index + } socket_stats; +} netdata_ebpf_judy_pid_stats_t; + +extern ebpf_module_t ebpf_modules[]; +enum ebpf_main_index { + EBPF_MODULE_PROCESS_IDX, + EBPF_MODULE_SOCKET_IDX, + EBPF_MODULE_CACHESTAT_IDX, + EBPF_MODULE_SYNC_IDX, + EBPF_MODULE_DCSTAT_IDX, + EBPF_MODULE_SWAP_IDX, + EBPF_MODULE_VFS_IDX, + EBPF_MODULE_FILESYSTEM_IDX, + EBPF_MODULE_DISK_IDX, + EBPF_MODULE_MOUNT_IDX, + EBPF_MODULE_FD_IDX, + EBPF_MODULE_HARDIRQ_IDX, + EBPF_MODULE_SOFTIRQ_IDX, + EBPF_MODULE_OOMKILL_IDX, + EBPF_MODULE_SHM_IDX, + EBPF_MODULE_MDFLUSH_IDX, + EBPF_MODULE_FUNCTION_IDX, + /* THREADS MUST BE INCLUDED BEFORE THIS COMMENT */ + EBPF_OPTION_ALL_CHARTS, + EBPF_OPTION_VERSION, + EBPF_OPTION_HELP, + EBPF_OPTION_GLOBAL_CHART, + EBPF_OPTION_RETURN_MODE, + EBPF_OPTION_LEGACY, + EBPF_OPTION_CORE, + EBPF_OPTION_UNITTEST +}; + +typedef struct ebpf_tracepoint { + bool enabled; + char *class; + char *event; +} ebpf_tracepoint_t; + +// Copied from musl header +#ifndef offsetof +#if __GNUC__ > 3 +#define offsetof(type, member) __builtin_offsetof(type, member) +#else +#define offsetof(type, member) ((size_t)((char *)&(((type *)0)->member) - (char *)0)) +#endif +#endif + +// Messages +#define NETDATA_EBPF_DEFAULT_FNT_NOT_FOUND "Cannot find the necessary functions to monitor" + +// Chart definitions +#define NETDATA_EBPF_FAMILY "ebpf" +#define NETDATA_EBPF_IP_FAMILY "ip" +#define NETDATA_FILESYSTEM_FAMILY "filesystem" +#define NETDATA_EBPF_MOUNT_GLOBAL_FAMILY "mount_points" +#define NETDATA_EBPF_CHART_TYPE_LINE "line" +#define NETDATA_EBPF_CHART_TYPE_STACKED "stacked" +#define NETDATA_EBPF_MEMORY_GROUP "mem" +#define NETDATA_EBPF_SYSTEM_GROUP "system" +#define NETDATA_SYSTEM_SWAP_SUBMENU "swap" +#define NETDATA_SYSTEM_IPC_SHM_SUBMENU "ipc shared memory" +#define NETDATA_MONITORING_FAMILY "netdata" + +// Statistics charts +#define NETDATA_EBPF_THREADS "ebpf_threads" +#define NETDATA_EBPF_LIFE_TIME "ebpf_life_time" +#define NETDATA_EBPF_LOAD_METHOD "ebpf_load_methods" +#define NETDATA_EBPF_KERNEL_MEMORY "ebpf_kernel_memory" +#define NETDATA_EBPF_HASH_TABLES_LOADED "ebpf_hash_tables_count" +#define NETDATA_EBPF_HASH_TABLES_PER_CORE "ebpf_hash_tables_per_core" +#define NETDATA_EBPF_HASH_TABLES_GLOBAL_ELEMENTS "ebpf_hash_tables_global_elements" +#define NETDATA_EBPF_HASH_TABLES_INSERT_PID_ELEMENTS "ebpf_hash_tables_insert_pid_elements" +#define NETDATA_EBPF_HASH_TABLES_REMOVE_PID_ELEMENTS "ebpf_hash_tables_remove_pid_elements" + +// Log file +#define NETDATA_DEVELOPER_LOG_FILE "developer.log" + +// Maximum number of processors monitored on perf events +#define NETDATA_MAX_PROCESSOR 512 + +// Kernel versions calculated with the formula: +// R = MAJOR*65536 + MINOR*256 + PATCH +#define NETDATA_KERNEL_V5_3 328448 +#define NETDATA_KERNEL_V4_15 265984 + +#define EBPF_SYS_CLONE_IDX 11 +#define EBPF_MAX_MAPS 32 + +#define EBPF_DEFAULT_UPDATE_EVERY 10 + +enum ebpf_algorithms_list { + NETDATA_EBPF_ABSOLUTE_IDX, + NETDATA_EBPF_INCREMENTAL_IDX +}; + +// Threads +void *ebpf_process_thread(void *ptr); +void *ebpf_socket_thread(void *ptr); + +// Common variables +extern pthread_mutex_t lock; +extern pthread_mutex_t ebpf_exit_cleanup; +extern int ebpf_nprocs; +extern int running_on_kernel; +extern int isrh; +extern char *ebpf_plugin_dir; +extern int process_pid_fd; + +extern pthread_mutex_t collect_data_mutex; + +// Common functions +void ebpf_global_labels(netdata_syscall_stat_t *is, + netdata_publish_syscall_t *pio, + char **dim, + char **name, + int *algorithm, + int end); + +void ebpf_write_chart_cmd(char *type, + char *id, + char *suffix, + char *title, + char *units, + char *family, + char *charttype, + char *context, + int order, + int update_every, + char *module); + +void ebpf_write_global_dimension(char *name, char *id, char *algorithm); + +void ebpf_create_global_dimension(void *ptr, int end); + +void ebpf_create_chart(char *type, + char *id, + char *title, + char *units, + char *family, + char *context, + char *charttype, + int order, + void (*ncd)(void *, int), + void *move, + int end, + int update_every, + char *module); + +void write_chart_dimension(char *dim, long long value); + +void write_count_chart(char *name, char *family, netdata_publish_syscall_t *move, uint32_t end); + +void write_err_chart(char *name, char *family, netdata_publish_syscall_t *move, int end); + +void write_io_chart(char *chart, char *family, char *dwrite, long long vwrite, + char *dread, long long vread); + +/** + * Create Chart labels + * + * @param name the label name. + * @param value the label value. + * @param origin the labeel source. + */ +static inline void ebpf_create_chart_labels(char *name, char *value, int source) +{ + fprintf(stdout, "CLABEL '%s' '%s' %d\n", name, value, source); +} + +/** + * Commit label + * + * Write commit label to stdout + */ +static inline void ebpf_commit_label() +{ + fprintf(stdout, "CLABEL_COMMIT\n"); +} + +/** + * Write begin command on standard output + * + * @param family the chart family name + * @param name the chart name + * @param metric the chart suffix (used with apps and cgroups) + */ +static inline void ebpf_write_begin_chart(char *family, char *name, char *metric) +{ + printf("BEGIN %s.%s%s\n", family, name, metric); +} + +/** + * Write END command on stdout. + */ +static inline void ebpf_write_end_chart() +{ + printf("END\n"); +} + +int ebpf_enable_tracepoint(ebpf_tracepoint_t *tp); +int ebpf_disable_tracepoint(ebpf_tracepoint_t *tp); +uint32_t ebpf_enable_tracepoints(ebpf_tracepoint_t *tps); + +void ebpf_pid_file(char *filename, size_t length); + +#define EBPF_PROGRAMS_SECTION "ebpf programs" + +#define EBPF_COMMON_UNITS_PERCENTAGE "%" +#define EBPF_COMMON_UNITS_CALLS_PER_SEC "calls/s" +#define EBPF_COMMON_UNITS_CALLS "calls" +#define EBPF_COMMON_UNITS_MILLISECONDS "milliseconds" + +#define EBPF_CHART_ALGORITHM_ABSOLUTE "absolute" +#define EBPF_CHART_ALGORITHM_INCREMENTAL "incremental" + +// Common variables +extern int debug_enabled; +extern struct ebpf_pid_stat *ebpf_root_of_pids; +extern ebpf_cgroup_target_t *ebpf_cgroup_pids; +extern char *ebpf_algorithms[]; +extern struct config collector_config; +extern netdata_ebpf_cgroup_shm_t shm_ebpf_cgroup; +extern int shm_fd_ebpf_cgroup; +extern sem_t *shm_sem_ebpf_cgroup; +extern pthread_mutex_t mutex_cgroup_shm; +extern size_t ebpf_all_pids_count; +extern ebpf_plugin_stats_t plugin_statistics; +#ifdef LIBBPF_MAJOR_VERSION +extern struct btf *default_btf; +#else +extern void *default_btf; +#endif + +// Socket functions and variables +// Common functions +void ebpf_process_create_apps_charts(struct ebpf_module *em, void *ptr); +void ebpf_socket_create_apps_charts(struct ebpf_module *em, void *ptr); +void ebpf_cachestat_create_apps_charts(struct ebpf_module *em, void *root); +void ebpf_one_dimension_write_charts(char *family, char *chart, char *dim, long long v1); +collected_number get_value_from_structure(char *basis, size_t offset); +void ebpf_update_pid_table(ebpf_local_maps_t *pid, ebpf_module_t *em); +void ebpf_write_chart_obsolete(char *type, char *id, char *suffix, char *title, char *units, char *family, + char *charttype, char *context, int order, int update_every); +void write_histogram_chart(char *family, char *name, const netdata_idx_t *hist, char **dimensions, uint32_t end); +void ebpf_update_disabled_plugin_stats(ebpf_module_t *em); +ARAL *ebpf_allocate_pid_aral(char *name, size_t size); +void ebpf_unload_legacy_code(struct bpf_object *objects, struct bpf_link **probe_links); + +void ebpf_read_global_table_stats(netdata_idx_t *stats, netdata_idx_t *values, int map_fd, + int maps_per_core, uint32_t begin, uint32_t end); +void **ebpf_judy_insert_unsafe(PPvoid_t arr, Word_t key); +netdata_ebpf_judy_pid_stats_t *ebpf_get_pid_from_judy_unsafe(PPvoid_t judy_array, uint32_t pid); + +void parse_network_viewer_section(struct config *cfg); +void ebpf_clean_ip_structure(ebpf_network_viewer_ip_list_t **clean); +void ebpf_clean_port_structure(ebpf_network_viewer_port_list_t **clean); +void ebpf_read_local_addresses_unsafe(); + +extern ebpf_filesystem_partitions_t localfs[]; +extern ebpf_sync_syscalls_t local_syscalls[]; +extern bool ebpf_plugin_exit; + +static inline bool ebpf_plugin_stop(void) { + return ebpf_plugin_exit || nd_thread_signaled_to_cancel(); +} + +void ebpf_stop_threads(int sig); +extern netdata_ebpf_judy_pid_t ebpf_judy_pid; + +#define EBPF_MAX_SYNCHRONIZATION_TIME 300 + +#endif /* NETDATA_COLLECTOR_EBPF_H */ diff --git a/src/collectors/ebpf.plugin/ebpf_apps.c b/src/collectors/ebpf.plugin/ebpf_apps.c new file mode 100644 index 000000000..a17cdb33d --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_apps.c @@ -0,0 +1,1160 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ebpf.h" +#include "ebpf_socket.h" +#include "ebpf_apps.h" + +// ---------------------------------------------------------------------------- +// ARAL vectors used to speed up processing +ARAL *ebpf_aral_apps_pid_stat = NULL; + +/** + * eBPF ARAL Init + * + * Initiallize array allocator that will be used when integration with apps and ebpf is created. + */ +void ebpf_aral_init(void) +{ + size_t max_elements = NETDATA_EBPF_ALLOC_MAX_PID; + if (max_elements < NETDATA_EBPF_ALLOC_MIN_ELEMENTS) { + netdata_log_error("Number of elements given is too small, adjusting it for %d", NETDATA_EBPF_ALLOC_MIN_ELEMENTS); + max_elements = NETDATA_EBPF_ALLOC_MIN_ELEMENTS; + } + + ebpf_aral_apps_pid_stat = ebpf_allocate_pid_aral("ebpf_pid_stat", sizeof(struct ebpf_pid_stat)); + +#ifdef NETDATA_DEV_MODE + netdata_log_info("Plugin is using ARAL with values %d", NETDATA_EBPF_ALLOC_MAX_PID); +#endif +} + +/** + * eBPF pid stat get + * + * Get a ebpf_pid_stat entry to be used with a specific PID. + * + * @return it returns the address on success. + */ +struct ebpf_pid_stat *ebpf_pid_stat_get(void) +{ + struct ebpf_pid_stat *target = aral_mallocz(ebpf_aral_apps_pid_stat); + memset(target, 0, sizeof(struct ebpf_pid_stat)); + return target; +} + +/** + * eBPF target release + * + * @param stat Release a target after usage. + */ +void ebpf_pid_stat_release(struct ebpf_pid_stat *stat) +{ + aral_freez(ebpf_aral_apps_pid_stat, stat); +} + +// ---------------------------------------------------------------------------- +// internal flags +// handled in code (automatically set) + +static int proc_pid_cmdline_is_needed = 0; // 1 when we need to read /proc/cmdline + +/***************************************************************** + * + * FUNCTIONS USED TO READ HASH TABLES + * + *****************************************************************/ + +/** + * Read statistic hash table. + * + * @param ep the output structure. + * @param fd the file descriptor mapped from kernel ring. + * @param pid the index used to select the data. + * @param bpf_map_lookup_elem a pointer for the function used to read data. + * + * @return It returns 0 when the data was copied and -1 otherwise + */ +int ebpf_read_hash_table(void *ep, int fd, uint32_t pid) +{ + if (!ep) + return -1; + + if (!bpf_map_lookup_elem(fd, &pid, ep)) + return 0; + + return -1; +} + +/***************************************************************** + * + * FUNCTIONS CALLED FROM COLLECTORS + * + *****************************************************************/ + +/** + * Reset the target values + * + * @param root the pointer to the chain that will be reset. + * + * @return it returns the number of structures that was reset. + */ +size_t zero_all_targets(struct ebpf_target *root) +{ + struct ebpf_target *w; + size_t count = 0; + + for (w = root; w; w = w->next) { + count++; + + if (unlikely(w->root_pid)) { + struct ebpf_pid_on_target *pid_on_target = w->root_pid; + + while (pid_on_target) { + struct ebpf_pid_on_target *pid_on_target_to_free = pid_on_target; + pid_on_target = pid_on_target->next; + freez(pid_on_target_to_free); + } + + w->root_pid = NULL; + } + } + + return count; +} + +/** + * Clean the allocated structures + * + * @param agrt the pointer to be cleaned. + */ +void clean_apps_groups_target(struct ebpf_target *agrt) +{ + struct ebpf_target *current_target; + while (agrt) { + current_target = agrt; + agrt = current_target->target; + + freez(current_target); + } +} + +/** + * Find or create a new target + * there are targets that are just aggregated to other target (the second argument) + * + * @param id + * @param target + * @param name + * + * @return It returns the target on success and NULL otherwise + */ +struct ebpf_target *get_apps_groups_target(struct ebpf_target **agrt, const char *id, struct ebpf_target *target, const char *name) +{ + int tdebug = 0, thidden = target ? target->hidden : 0, ends_with = 0; + const char *nid = id; + + // extract the options + while (nid[0] == '-' || nid[0] == '+' || nid[0] == '*') { + if (nid[0] == '-') + thidden = 1; + if (nid[0] == '+') + tdebug = 1; + if (nid[0] == '*') + ends_with = 1; + nid++; + } + uint32_t hash = simple_hash(id); + + // find if it already exists + struct ebpf_target *w, *last = *agrt; + for (w = *agrt; w; w = w->next) { + if (w->idhash == hash && strncmp(nid, w->id, EBPF_MAX_NAME) == 0) + return w; + + last = w; + } + + // find an existing target + if (unlikely(!target)) { + while (*name == '-') { + if (*name == '-') + thidden = 1; + name++; + } + + for (target = *agrt; target != NULL; target = target->next) { + if (!target->target && strcmp(name, target->name) == 0) + break; + } + } + + if (target && target->target) + fatal( + "Internal Error: request to link process '%s' to target '%s' which is linked to target '%s'", id, + target->id, target->target->id); + + w = callocz(1, sizeof(struct ebpf_target)); + strncpyz(w->id, nid, EBPF_MAX_NAME); + w->idhash = simple_hash(w->id); + + if (unlikely(!target)) + // copy the name + strncpyz(w->name, name, EBPF_MAX_NAME); + else + // copy the id + strncpyz(w->name, nid, EBPF_MAX_NAME); + + strncpyz(w->clean_name, w->name, EBPF_MAX_NAME); + netdata_fix_chart_name(w->clean_name); + for (char *d = w->clean_name; *d; d++) { + if (*d == '.') + *d = '_'; + } + + strncpyz(w->compare, nid, EBPF_MAX_COMPARE_NAME); + size_t len = strlen(w->compare); + if (w->compare[len - 1] == '*') { + w->compare[len - 1] = '\0'; + w->starts_with = 1; + } + w->ends_with = ends_with; + + if (w->starts_with && w->ends_with) + proc_pid_cmdline_is_needed = 1; + + w->comparehash = simple_hash(w->compare); + w->comparelen = strlen(w->compare); + + w->hidden = thidden; +#ifdef NETDATA_INTERNAL_CHECKS + w->debug_enabled = tdebug; +#else + if (tdebug) + fprintf(stderr, "apps.plugin has been compiled without debugging\n"); +#endif + w->target = target; + + // append it, to maintain the order in apps_groups.conf + if (last) + last->next = w; + else + *agrt = w; + + return w; +} + +/** + * Read the apps_groups.conf file + * + * @param agrt a pointer to apps_group_root_target + * @param path the directory to search apps_%s.conf + * @param file the word to complement the file name. + * + * @return It returns 0 on success and -1 otherwise + */ +int ebpf_read_apps_groups_conf(struct ebpf_target **agdt, struct ebpf_target **agrt, const char *path, const char *file) +{ + char filename[FILENAME_MAX + 1]; + + snprintfz(filename, FILENAME_MAX, "%s/apps_%s.conf", path, file); + + // ---------------------------------------- + + procfile *ff = procfile_open_no_log(filename, " :\t", PROCFILE_FLAG_DEFAULT); + if (!ff) + return -1; + + procfile_set_quotes(ff, "'\""); + + ff = procfile_readall(ff); + if (!ff) + return -1; + + size_t line, lines = procfile_lines(ff); + + for (line = 0; line < lines; line++) { + size_t word, words = procfile_linewords(ff, line); + if (!words) + continue; + + char *name = procfile_lineword(ff, line, 0); + if (!name || !*name) + continue; + + // find a possibly existing target + struct ebpf_target *w = NULL; + + // loop through all words, skipping the first one (the name) + for (word = 0; word < words; word++) { + char *s = procfile_lineword(ff, line, word); + if (!s || !*s) + continue; + if (*s == '#') + break; + + // is this the first word? skip it + if (s == name) + continue; + + // add this target + struct ebpf_target *n = get_apps_groups_target(agrt, s, w, name); + if (!n) { + netdata_log_error("Cannot create target '%s' (line %zu, word %zu)", s, line, word); + continue; + } + + // just some optimization + // to avoid searching for a target for each process + if (!w) + w = n->target ? n->target : n; + } + } + + procfile_close(ff); + + *agdt = get_apps_groups_target(agrt, "p+!o@w#e$i^r&7*5(-i)l-o_", NULL, "other"); // match nothing + if (!*agdt) + fatal("Cannot create default target"); + + struct ebpf_target *ptr = *agdt; + if (ptr->target) + *agdt = ptr->target; + + return 0; +} + +// the minimum PID of the system +// this is also the pid of the init process +#define INIT_PID 1 + +// ---------------------------------------------------------------------------- +// string lengths + +#define MAX_CMDLINE 16384 + +struct ebpf_pid_stat **ebpf_all_pids = NULL; // to avoid allocations, we pre-allocate the + // the entire pid space. +struct ebpf_pid_stat *ebpf_root_of_pids = NULL; // global list of all processes running + +size_t ebpf_all_pids_count = 0; // the number of processes running + +struct ebpf_target + *apps_groups_default_target = NULL, // the default target + *apps_groups_root_target = NULL, // apps_groups.conf defined + *users_root_target = NULL, // users + *groups_root_target = NULL; // user groups + +size_t apps_groups_targets_count = 0; // # of apps_groups.conf targets + +// ---------------------------------------------------------------------------- +// internal counters + +static size_t + // global_iterations_counter = 1, + calls_counter = 0, + // file_counter = 0, + // filenames_allocated_counter = 0, + // inodes_changed_counter = 0, + // links_changed_counter = 0, + targets_assignment_counter = 0; + +// ---------------------------------------------------------------------------- +// debugging + +// log each problem once per process +// log flood protection flags (log_thrown) +#define PID_LOG_IO 0x00000001 +#define PID_LOG_STATUS 0x00000002 +#define PID_LOG_CMDLINE 0x00000004 +#define PID_LOG_FDS 0x00000008 +#define PID_LOG_STAT 0x00000010 + +int debug_enabled = 0; + +#ifdef NETDATA_INTERNAL_CHECKS + +#define debug_log(fmt, args...) \ + do { \ + if (unlikely(debug_enabled)) \ + debug_log_int(fmt, ##args); \ + } while (0) + +#else + +static inline void debug_log_dummy(void) +{ +} +#define debug_log(fmt, args...) debug_log_dummy() + +#endif + +/** + * Managed log + * + * Store log information if it is necessary. + * + * @param p the pid stat structure + * @param log the log id + * @param status the return from a function. + * + * @return It returns the status value. + */ +static inline int managed_log(struct ebpf_pid_stat *p, uint32_t log, int status) +{ + if (unlikely(!status)) { + // netdata_log_error("command failed log %u, errno %d", log, errno); + + if (unlikely(debug_enabled || errno != ENOENT)) { + if (unlikely(debug_enabled || !(p->log_thrown & log))) { + p->log_thrown |= log; + switch (log) { + case PID_LOG_IO: + netdata_log_error( + "Cannot process %s/proc/%d/io (command '%s')", netdata_configured_host_prefix, p->pid, + p->comm); + break; + + case PID_LOG_STATUS: + netdata_log_error( + "Cannot process %s/proc/%d/status (command '%s')", netdata_configured_host_prefix, p->pid, + p->comm); + break; + + case PID_LOG_CMDLINE: + netdata_log_error( + "Cannot process %s/proc/%d/cmdline (command '%s')", netdata_configured_host_prefix, p->pid, + p->comm); + break; + + case PID_LOG_FDS: + netdata_log_error( + "Cannot process entries in %s/proc/%d/fd (command '%s')", netdata_configured_host_prefix, + p->pid, p->comm); + break; + + case PID_LOG_STAT: + break; + + default: + netdata_log_error("unhandled error for pid %d, command '%s'", p->pid, p->comm); + break; + } + } + } + errno = 0; + } else if (unlikely(p->log_thrown & log)) { + // netdata_log_error("unsetting log %u on pid %d", log, p->pid); + p->log_thrown &= ~log; + } + + return status; +} + +/** + * Get PID entry + * + * Get or allocate the PID entry for the specified pid. + * + * @param pid the pid to search the data. + * @param tgid the task group id + * + * @return It returns the pid entry structure + */ +ebpf_pid_stat_t *ebpf_get_pid_entry(pid_t pid, pid_t tgid) +{ + ebpf_pid_stat_t *ptr = ebpf_all_pids[pid]; + if (unlikely(ptr)) { + if (!ptr->ppid && tgid) + ptr->ppid = tgid; + return ebpf_all_pids[pid]; + } + + struct ebpf_pid_stat *p = ebpf_pid_stat_get(); + + if (likely(ebpf_root_of_pids)) + ebpf_root_of_pids->prev = p; + + p->next = ebpf_root_of_pids; + ebpf_root_of_pids = p; + + p->pid = pid; + p->ppid = tgid; + + ebpf_all_pids[pid] = p; + ebpf_all_pids_count++; + + return p; +} + +/** + * Assign the PID to a target. + * + * @param p the pid_stat structure to assign for a target. + */ +static inline void assign_target_to_pid(struct ebpf_pid_stat *p) +{ + targets_assignment_counter++; + + uint32_t hash = simple_hash(p->comm); + size_t pclen = strlen(p->comm); + + struct ebpf_target *w; + for (w = apps_groups_root_target; w; w = w->next) { + // if(debug_enabled || (p->target && p->target->debug_enabled)) debug_log_int("\t\tcomparing '%s' with '%s'", w->compare, p->comm); + + // find it - 4 cases: + // 1. the target is not a pattern + // 2. the target has the prefix + // 3. the target has the suffix + // 4. the target is something inside cmdline + + if (unlikely( + ((!w->starts_with && !w->ends_with && w->comparehash == hash && !strcmp(w->compare, p->comm)) || + (w->starts_with && !w->ends_with && !strncmp(w->compare, p->comm, w->comparelen)) || + (!w->starts_with && w->ends_with && pclen >= w->comparelen && !strcmp(w->compare, &p->comm[pclen - w->comparelen])) || + (proc_pid_cmdline_is_needed && w->starts_with && w->ends_with && p->cmdline && strstr(p->cmdline, w->compare))))) { + if (w->target) + p->target = w->target; + else + p->target = w; + + if (debug_enabled || (p->target && p->target->debug_enabled)) + debug_log_int("%s linked to target %s", p->comm, p->target->name); + + break; + } + } +} + +// ---------------------------------------------------------------------------- +// update pids from proc + +/** + * Read cmd line from /proc/PID/cmdline + * + * @param p the ebpf_pid_stat_structure. + * + * @return It returns 1 on success and 0 otherwise. + */ +static inline int read_proc_pid_cmdline(struct ebpf_pid_stat *p) +{ + static char cmdline[MAX_CMDLINE + 1]; + + int ret = 0; + if (unlikely(!p->cmdline_filename)) { + char filename[FILENAME_MAX + 1]; + snprintfz(filename, FILENAME_MAX, "%s/proc/%d/cmdline", netdata_configured_host_prefix, p->pid); + p->cmdline_filename = strdupz(filename); + } + + int fd = open(p->cmdline_filename, procfile_open_flags, 0666); + if (unlikely(fd == -1)) + goto cleanup; + + ssize_t i, bytes = read(fd, cmdline, MAX_CMDLINE); + close(fd); + + if (unlikely(bytes < 0)) + goto cleanup; + + cmdline[bytes] = '\0'; + for (i = 0; i < bytes; i++) { + if (unlikely(!cmdline[i])) + cmdline[i] = ' '; + } + + debug_log("Read file '%s' contents: %s", p->cmdline_filename, p->cmdline); + + ret = 1; + +cleanup: + // copy the command to the command line + if (p->cmdline) + freez(p->cmdline); + p->cmdline = strdupz(p->comm); + + rw_spinlock_write_lock(&ebpf_judy_pid.index.rw_spinlock); + netdata_ebpf_judy_pid_stats_t *pid_ptr = ebpf_get_pid_from_judy_unsafe(&ebpf_judy_pid.index.JudyLArray, p->pid); + if (pid_ptr) + pid_ptr->cmdline = p->cmdline; + rw_spinlock_write_unlock(&ebpf_judy_pid.index.rw_spinlock); + + return ret; +} + +/** + * Read information from /proc/PID/stat and /proc/PID/cmdline + * Assign target to pid + * + * @param p the pid stat structure to store the data. + * @param ptr an useless argument. + */ +static inline int read_proc_pid_stat(struct ebpf_pid_stat *p, void *ptr) +{ + UNUSED(ptr); + + static procfile *ff = NULL; + + if (unlikely(!p->stat_filename)) { + char filename[FILENAME_MAX + 1]; + snprintfz(filename, FILENAME_MAX, "%s/proc/%d/stat", netdata_configured_host_prefix, p->pid); + p->stat_filename = strdupz(filename); + } + + int set_quotes = (!ff) ? 1 : 0; + + struct stat statbuf; + if (stat(p->stat_filename, &statbuf)) + return 0; + + ff = procfile_reopen(ff, p->stat_filename, NULL, PROCFILE_FLAG_NO_ERROR_ON_FILE_IO); + if (unlikely(!ff)) + return 0; + + if (unlikely(set_quotes)) + procfile_set_open_close(ff, "(", ")"); + + ff = procfile_readall(ff); + if (unlikely(!ff)) + return 0; + + p->last_stat_collected_usec = p->stat_collected_usec; + p->stat_collected_usec = now_monotonic_usec(); + calls_counter++; + + char *comm = procfile_lineword(ff, 0, 1); + p->ppid = (int32_t)str2pid_t(procfile_lineword(ff, 0, 3)); + + if (strcmp(p->comm, comm) != 0) { + if (unlikely(debug_enabled)) { + if (p->comm[0]) + debug_log("\tpid %d (%s) changed name to '%s'", p->pid, p->comm, comm); + else + debug_log("\tJust added %d (%s)", p->pid, comm); + } + + strncpyz(p->comm, comm, EBPF_MAX_COMPARE_NAME); + + // /proc/<pid>/cmdline + if (likely(proc_pid_cmdline_is_needed)) + managed_log(p, PID_LOG_CMDLINE, read_proc_pid_cmdline(p)); + + assign_target_to_pid(p); + } + + if (unlikely(debug_enabled || (p->target && p->target->debug_enabled))) + debug_log_int( + "READ PROC/PID/STAT: %s/proc/%d/stat, process: '%s' on target '%s' (dt=%llu)", + netdata_configured_host_prefix, p->pid, p->comm, (p->target) ? p->target->name : "UNSET", + p->stat_collected_usec - p->last_stat_collected_usec); + + return 1; +} + +/** + * Collect data for PID + * + * @param pid the current pid that we are working + * @param ptr a NULL value + * + * @return It returns 1 on success and 0 otherwise + */ +static inline int ebpf_collect_data_for_pid(pid_t pid, void *ptr) +{ + if (unlikely(pid < 0 || pid > pid_max)) { + netdata_log_error("Invalid pid %d read (expected %d to %d). Ignoring process.", pid, 0, pid_max); + return 0; + } + + ebpf_pid_stat_t *p = ebpf_get_pid_entry(pid, 0); + if (unlikely(!p || p->read)) + return 0; + p->read = 1; + + if (unlikely(!managed_log(p, PID_LOG_STAT, read_proc_pid_stat(p, ptr)))) + // there is no reason to proceed if we cannot get its status + return 0; + + // check its parent pid + if (unlikely(p->ppid < 0 || p->ppid > pid_max)) { + netdata_log_error("Pid %d (command '%s') states invalid parent pid %d. Using 0.", pid, p->comm, p->ppid); + p->ppid = 0; + } + + // mark it as updated + p->updated = 1; + p->keep = 0; + p->keeploops = 0; + + return 1; +} + +/** + * Fill link list of parents with children PIDs + */ +static inline void link_all_processes_to_their_parents(void) +{ + struct ebpf_pid_stat *p, *pp; + + // link all children to their parents + // and update children count on parents + for (p = ebpf_root_of_pids; p; p = p->next) { + // for each process found + + p->sortlist = 0; + p->parent = NULL; + + if (unlikely(!p->ppid)) { + p->parent = NULL; + continue; + } + + pp = ebpf_all_pids[p->ppid]; + if (likely(pp)) { + p->parent = pp; + pp->children_count++; + + if (unlikely(debug_enabled || (p->target && p->target->debug_enabled))) + debug_log_int( + "child %d (%s, %s) on target '%s' has parent %d (%s, %s).", p->pid, p->comm, + p->updated ? "running" : "exited", (p->target) ? p->target->name : "UNSET", pp->pid, pp->comm, + pp->updated ? "running" : "exited"); + } else { + p->parent = NULL; + debug_log("pid %d %s states parent %d, but the later does not exist.", p->pid, p->comm, p->ppid); + } + } +} + +/** + * Aggregate PIDs to targets. + */ +static void apply_apps_groups_targets_inheritance(void) +{ + struct ebpf_pid_stat *p = NULL; + + // children that do not have a target + // inherit their target from their parent + int found = 1, loops = 0; + while (found) { + if (unlikely(debug_enabled)) + loops++; + found = 0; + for (p = ebpf_root_of_pids; p; p = p->next) { + // if this process does not have a target + // and it has a parent + // and its parent has a target + // then, set the parent's target to this process + if (unlikely(!p->target && p->parent && p->parent->target)) { + p->target = p->parent->target; + found++; + + if (debug_enabled || (p->target && p->target->debug_enabled)) + debug_log_int( + "TARGET INHERITANCE: %s is inherited by %d (%s) from its parent %d (%s).", p->target->name, + p->pid, p->comm, p->parent->pid, p->parent->comm); + } + } + } + + // find all the procs with 0 childs and merge them to their parents + // repeat, until nothing more can be done. + int sortlist = 1; + found = 1; + while (found) { + if (unlikely(debug_enabled)) + loops++; + found = 0; + + for (p = ebpf_root_of_pids; p; p = p->next) { + if (unlikely(!p->sortlist && !p->children_count)) + p->sortlist = sortlist++; + + if (unlikely( + !p->children_count // if this process does not have any children + && !p->merged // and is not already merged + && p->parent // and has a parent + && p->parent->children_count // and its parent has children + // and the target of this process and its parent is the same, + // or the parent does not have a target + && (p->target == p->parent->target || !p->parent->target) && + p->ppid != INIT_PID // and its parent is not init + )) { + // mark it as merged + p->parent->children_count--; + p->merged = 1; + + // the parent inherits the child's target, if it does not have a target itself + if (unlikely(p->target && !p->parent->target)) { + p->parent->target = p->target; + + if (debug_enabled || (p->target && p->target->debug_enabled)) + debug_log_int( + "TARGET INHERITANCE: %s is inherited by %d (%s) from its child %d (%s).", p->target->name, + p->parent->pid, p->parent->comm, p->pid, p->comm); + } + + found++; + } + } + + debug_log("TARGET INHERITANCE: merged %d processes", found); + } + + // init goes always to default target + if (ebpf_all_pids[INIT_PID]) + ebpf_all_pids[INIT_PID]->target = apps_groups_default_target; + + // pid 0 goes always to default target + if (ebpf_all_pids[0]) + ebpf_all_pids[0]->target = apps_groups_default_target; + + // give a default target on all top level processes + if (unlikely(debug_enabled)) + loops++; + for (p = ebpf_root_of_pids; p; p = p->next) { + // if the process is not merged itself + // then is is a top level process + if (unlikely(!p->merged && !p->target)) + p->target = apps_groups_default_target; + + // make sure all processes have a sortlist + if (unlikely(!p->sortlist)) + p->sortlist = sortlist++; + } + + if (ebpf_all_pids[1]) + ebpf_all_pids[1]->sortlist = sortlist++; + + // give a target to all merged child processes + found = 1; + while (found) { + if (unlikely(debug_enabled)) + loops++; + found = 0; + for (p = ebpf_root_of_pids; p; p = p->next) { + if (unlikely(!p->target && p->merged && p->parent && p->parent->target)) { + p->target = p->parent->target; + found++; + + if (debug_enabled || (p->target && p->target->debug_enabled)) + debug_log_int( + "TARGET INHERITANCE: %s is inherited by %d (%s) from its parent %d (%s) at phase 2.", + p->target->name, p->pid, p->comm, p->parent->pid, p->parent->comm); + } + } + } + + debug_log("apply_apps_groups_targets_inheritance() made %d loops on the process tree", loops); +} + +/** + * Update target timestamp. + * + * @param root the targets that will be updated. + */ +static inline void post_aggregate_targets(struct ebpf_target *root) +{ + struct ebpf_target *w; + for (w = root; w; w = w->next) { + if (w->collected_starttime) { + if (!w->starttime || w->collected_starttime < w->starttime) { + w->starttime = w->collected_starttime; + } + } else { + w->starttime = 0; + } + } +} + +/** + * Remove PID from the link list. + * + * @param pid the PID that will be removed. + */ +static inline void ebpf_del_pid_entry(pid_t pid) +{ + struct ebpf_pid_stat *p = ebpf_all_pids[pid]; + + if (unlikely(!p)) { + netdata_log_error("attempted to free pid %d that is not allocated.", pid); + return; + } + + debug_log("process %d %s exited, deleting it.", pid, p->comm); + + if (ebpf_root_of_pids == p) + ebpf_root_of_pids = p->next; + + if (p->next) + p->next->prev = p->prev; + if (p->prev) + p->prev->next = p->next; + + freez(p->stat_filename); + freez(p->status_filename); + freez(p->io_filename); + freez(p->cmdline_filename); + + rw_spinlock_write_lock(&ebpf_judy_pid.index.rw_spinlock); + netdata_ebpf_judy_pid_stats_t *pid_ptr = ebpf_get_pid_from_judy_unsafe(&ebpf_judy_pid.index.JudyLArray, p->pid); + if (pid_ptr) { + if (pid_ptr->socket_stats.JudyLArray) { + Word_t local_socket = 0; + Pvoid_t *socket_value; + bool first_socket = true; + while ((socket_value = JudyLFirstThenNext(pid_ptr->socket_stats.JudyLArray, &local_socket, &first_socket))) { + netdata_socket_plus_t *socket_clean = *socket_value; + aral_freez(aral_socket_table, socket_clean); + } + JudyLFreeArray(&pid_ptr->socket_stats.JudyLArray, PJE0); + } + aral_freez(ebpf_judy_pid.pid_table, pid_ptr); + JudyLDel(&ebpf_judy_pid.index.JudyLArray, p->pid, PJE0); + } + rw_spinlock_write_unlock(&ebpf_judy_pid.index.rw_spinlock); + + freez(p->cmdline); + ebpf_pid_stat_release(p); + + ebpf_all_pids[pid] = NULL; + ebpf_all_pids_count--; +} + +/** + * Get command string associated with a PID. + * This can only safely be used when holding the `collect_data_mutex` lock. + * + * @param pid the pid to search the data. + * @param n the maximum amount of bytes to copy into dest. + * if this is greater than the size of the command, it is clipped. + * @param dest the target memory buffer to write the command into. + * @return -1 if the PID hasn't been scraped yet, 0 otherwise. + */ +int get_pid_comm(pid_t pid, size_t n, char *dest) +{ + struct ebpf_pid_stat *stat; + + stat = ebpf_all_pids[pid]; + if (unlikely(stat == NULL)) { + return -1; + } + + if (unlikely(n > sizeof(stat->comm))) { + n = sizeof(stat->comm); + } + + strncpyz(dest, stat->comm, n); + return 0; +} + +/** + * Remove PIDs when they are not running more. + */ +void ebpf_cleanup_exited_pids(int max) +{ + struct ebpf_pid_stat *p = NULL; + + for (p = ebpf_root_of_pids; p;) { + if (p->not_updated > max) { + if (unlikely(debug_enabled && (p->keep || p->keeploops))) + debug_log(" > CLEANUP cannot keep exited process %d (%s) anymore - removing it.", p->pid, p->comm); + + pid_t r = p->pid; + p = p->next; + + ebpf_del_pid_entry(r); + } + p = p->next; + } +} + +/** + * Read proc filesystem for the first time. + * + * @return It returns 0 on success and -1 otherwise. + */ +static inline void read_proc_filesystem() +{ + char dirname[FILENAME_MAX + 1]; + + snprintfz(dirname, FILENAME_MAX, "%s/proc", netdata_configured_host_prefix); + DIR *dir = opendir(dirname); + if (!dir) + return; + + struct dirent *de = NULL; + + while ((de = readdir(dir))) { + char *endptr = de->d_name; + + if (unlikely(de->d_type != DT_DIR || de->d_name[0] < '0' || de->d_name[0] > '9')) + continue; + + pid_t pid = (pid_t)strtoul(de->d_name, &endptr, 10); + + // make sure we read a valid number + if (unlikely(endptr == de->d_name || *endptr != '\0')) + continue; + + ebpf_collect_data_for_pid(pid, NULL); + } + closedir(dir); +} + +/** + * Aggregated PID on target + * + * @param w the target output + * @param p the pid with information to update + * @param o never used + */ +static inline void aggregate_pid_on_target(struct ebpf_target *w, struct ebpf_pid_stat *p, struct ebpf_target *o) +{ + UNUSED(o); + + if (unlikely(!p->updated)) { + // the process is not running + return; + } + + if (unlikely(!w)) { + netdata_log_error("pid %d %s was left without a target!", p->pid, p->comm); + return; + } + + w->processes++; + struct ebpf_pid_on_target *pid_on_target = mallocz(sizeof(struct ebpf_pid_on_target)); + pid_on_target->pid = p->pid; + pid_on_target->next = w->root_pid; + w->root_pid = pid_on_target; +} + +/** + * Process Accumulator + * + * Sum all values read from kernel and store in the first address. + * + * @param out the vector with read values. + * @param maps_per_core do I need to read all cores? + */ +void ebpf_process_apps_accumulator(ebpf_process_stat_t *out, int maps_per_core) +{ + int i, end = (maps_per_core) ? ebpf_nprocs : 1; + ebpf_process_stat_t *total = &out[0]; + for (i = 1; i < end; i++) { + ebpf_process_stat_t *w = &out[i]; + total->exit_call += w->exit_call; + total->task_err += w->task_err; + total->create_thread += w->create_thread; + total->create_process += w->create_process; + total->release_call += w->release_call; + } +} + +/** + * Sum values for pid + * + * @param structure to store result. + * @param root the structure with all available PIDs + */ +void ebpf_process_sum_values_for_pids(ebpf_process_stat_t *process, struct ebpf_pid_on_target *root) +{ + memset(process, 0, sizeof(ebpf_process_stat_t)); + while (root) { + int32_t pid = root->pid; + ebpf_pid_stat_t *local_pid = ebpf_get_pid_entry(pid, 0); + if (local_pid) { + ebpf_process_stat_t *in = &local_pid->process; + process->task_err += in->task_err; + process->release_call += in->release_call; + process->exit_call += in->exit_call; + process->create_thread += in->create_thread; + process->create_process += in->create_process; + } + + root = root->next; + } +} + +/** + * Collect data for all process + * + * Read data from hash table and store it in appropriate vectors. + * It also creates the link between targets and PIDs. + * + * @param tbl_pid_stats_fd The mapped file descriptor for the hash table. + * @param maps_per_core do I have hash maps per core? + */ +void collect_data_for_all_processes(int tbl_pid_stats_fd, int maps_per_core) +{ + if (unlikely(!ebpf_all_pids)) + return; + + struct ebpf_pid_stat *pids = ebpf_root_of_pids; // global list of all processes running + while (pids) { + if (pids->updated_twice) { + pids->read = 0; // mark it as not read, so that collect_data_for_pid() will read it + pids->updated = 0; + pids->merged = 0; + pids->children_count = 0; + pids->parent = NULL; + } else { + if (pids->updated) + pids->updated_twice = 1; + } + + pids = pids->next; + } + + read_proc_filesystem(); + + pids = ebpf_root_of_pids; // global list of all processes running + + if (tbl_pid_stats_fd != -1) { + size_t length = sizeof(ebpf_process_stat_t); + if (maps_per_core) + length *= ebpf_nprocs; + + uint32_t key = 0, next_key = 0; + while (bpf_map_get_next_key(tbl_pid_stats_fd, &key, &next_key) == 0) { + ebpf_pid_stat_t *local_pid = ebpf_get_pid_entry(key, 0); + if (!local_pid) + goto end_process_loop; + + ebpf_process_stat_t *w = &local_pid->process; + if (bpf_map_lookup_elem(tbl_pid_stats_fd, &key, process_stat_vector)) { + goto end_process_loop; + } + + ebpf_process_apps_accumulator(process_stat_vector, maps_per_core); + + memcpy(w, process_stat_vector, sizeof(ebpf_process_stat_t)); + +end_process_loop: + memset(process_stat_vector, 0, length); + key = next_key; + } + } + + link_all_processes_to_their_parents(); + + apply_apps_groups_targets_inheritance(); + + apps_groups_targets_count = zero_all_targets(apps_groups_root_target); + + // this has to be done, before the cleanup + // // concentrate everything on the targets + for (pids = ebpf_root_of_pids; pids; pids = pids->next) + aggregate_pid_on_target(pids->target, pids, NULL); + + post_aggregate_targets(apps_groups_root_target); + + struct ebpf_target *w; + for (w = apps_groups_root_target; w; w = w->next) { + if (unlikely(!(w->processes))) + continue; + + ebpf_process_sum_values_for_pids(&w->process, w->root_pid); + } +} diff --git a/src/collectors/ebpf.plugin/ebpf_apps.h b/src/collectors/ebpf.plugin/ebpf_apps.h new file mode 100644 index 000000000..a2cbaf3b7 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_apps.h @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_EBPF_APPS_H +#define NETDATA_EBPF_APPS_H 1 + +#include "libnetdata/locks/locks.h" +#include "libnetdata/avl/avl.h" +#include "libnetdata/clocks/clocks.h" +#include "libnetdata/config/appconfig.h" +#include "libnetdata/ebpf/ebpf.h" + +#define NETDATA_APPS_FAMILY "apps" +#define NETDATA_APP_FAMILY "app" +#define NETDATA_APPS_FILE_GROUP "file_access" +#define NETDATA_APPS_FILE_FDS "fds" +#define NETDATA_APPS_PROCESS_GROUP "process" +#define NETDATA_APPS_NET_GROUP "net" +#define NETDATA_APPS_IPC_SHM_GROUP "ipc shm" + +#ifndef TASK_COMM_LEN +#define TASK_COMM_LEN 16 +#endif + +#include "ebpf_process.h" +#include "ebpf_dcstat.h" +#include "ebpf_disk.h" +#include "ebpf_fd.h" +#include "ebpf_filesystem.h" +#include "ebpf_functions.h" +#include "ebpf_hardirq.h" +#include "ebpf_cachestat.h" +#include "ebpf_mdflush.h" +#include "ebpf_mount.h" +#include "ebpf_oomkill.h" +#include "ebpf_shm.h" +#include "ebpf_socket.h" +#include "ebpf_softirq.h" +#include "ebpf_sync.h" +#include "ebpf_swap.h" +#include "ebpf_vfs.h" + +#define EBPF_MAX_COMPARE_NAME 100 +#define EBPF_MAX_NAME 100 + +#define EBPF_CLEANUP_FACTOR 10 + +// ---------------------------------------------------------------------------- +// Structures used to read information from kernel ring +typedef struct ebpf_process_stat { + uint64_t ct; + uint32_t uid; + uint32_t gid; + char name[TASK_COMM_LEN]; + + uint32_t tgid; + uint32_t pid; + + //Counter + uint32_t exit_call; + uint32_t release_call; + uint32_t create_process; + uint32_t create_thread; + + //Counter + uint32_t task_err; + + uint8_t removeme; +} ebpf_process_stat_t; + +// ---------------------------------------------------------------------------- +// pid_stat +// +struct ebpf_target { + char compare[EBPF_MAX_COMPARE_NAME + 1]; + uint32_t comparehash; + size_t comparelen; + + char id[EBPF_MAX_NAME + 1]; + uint32_t idhash; + uint32_t charts_created; + + char name[EBPF_MAX_NAME + 1]; + char clean_name[EBPF_MAX_NAME + 1]; // sanitized name used in chart id (need to replace at least dots) + + // Changes made to simplify integration between apps and eBPF. + netdata_publish_cachestat_t cachestat; + netdata_publish_dcstat_t dcstat; + netdata_publish_swap_t swap; + netdata_publish_vfs_t vfs; + netdata_fd_stat_t fd; + netdata_publish_shm_t shm; + ebpf_process_stat_t process; + ebpf_socket_publish_apps_t socket; + + kernel_uint_t starttime; + kernel_uint_t collected_starttime; + + unsigned int processes; // how many processes have been merged to this + int exposed; // if set, we have sent this to netdata + int hidden; // if set, we set the hidden flag on the dimension + int debug_enabled; + int ends_with; + int starts_with; // if set, the compare string matches only the + // beginning of the command + + struct ebpf_pid_on_target *root_pid; // list of aggregated pids for target debugging + + struct ebpf_target *target; // the one that will be reported to netdata + struct ebpf_target *next; +}; + +extern struct ebpf_target *apps_groups_default_target; +extern struct ebpf_target *apps_groups_root_target; +extern struct ebpf_target *users_root_target; +extern struct ebpf_target *groups_root_target; + +typedef struct ebpf_pid_stat { + int32_t pid; + char comm[EBPF_MAX_COMPARE_NAME + 1]; + char *cmdline; + + uint32_t log_thrown; + + // char state; + int32_t ppid; + + int children_count; // number of processes directly referencing this + unsigned char keep : 1; // 1 when we need to keep this process in memory even after it exited + int keeploops; // increases by 1 every time keep is 1 and updated 0 + unsigned char updated : 1; // 1 when the process is currently running + unsigned char updated_twice : 1; // 1 when the process was running in the previous iteration + unsigned char merged : 1; // 1 when it has been merged to its parent + unsigned char read : 1; // 1 when we have already read this process for this iteration + + int sortlist; // higher numbers = top on the process tree + + // each process gets a unique number + netdata_publish_cachestat_t cachestat; + netdata_publish_dcstat_t dc; + netdata_fd_stat_t fd; + ebpf_process_stat_t process; + netdata_publish_shm_t shm; + netdata_publish_swap_t swap; + ebpf_socket_publish_apps_t socket; + netdata_publish_vfs_t vfs; + + int not_updated; + + struct ebpf_target *target; // app_groups.conf targets + struct ebpf_target *user_target; // uid based targets + struct ebpf_target *group_target; // gid based targets + + usec_t stat_collected_usec; + usec_t last_stat_collected_usec; + + netdata_publish_cachestat_t cache; + + char *stat_filename; + char *status_filename; + char *io_filename; + char *cmdline_filename; + + struct ebpf_pid_stat *parent; + struct ebpf_pid_stat *prev; + struct ebpf_pid_stat *next; +} ebpf_pid_stat_t; + +// ---------------------------------------------------------------------------- +// target +// +// target is the structure that processes are aggregated to be reported +// to netdata. +// +// - Each entry in /etc/apps_groups.conf creates a target. +// - Each user and group used by a process in the system, creates a target. +struct ebpf_pid_on_target { + int32_t pid; + struct ebpf_pid_on_target *next; +}; + +/** + * Internal function used to write debug messages. + * + * @param fmt the format to create the message. + * @param ... the arguments to fill the format. + */ +static inline void debug_log_int(const char *fmt, ...) +{ + va_list args; + + fprintf(stderr, "apps.plugin: "); + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + + fputc('\n', stderr); +} + +// ---------------------------------------------------------------------------- +// Exported variabled and functions +// +extern struct ebpf_pid_stat **ebpf_all_pids; + +int ebpf_read_apps_groups_conf(struct ebpf_target **apps_groups_default_target, + struct ebpf_target **apps_groups_root_target, + const char *path, + const char *file); + +void clean_apps_groups_target(struct ebpf_target *apps_groups_root_target); + +size_t zero_all_targets(struct ebpf_target *root); + +void cleanup_exited_pids(); + +int ebpf_read_hash_table(void *ep, int fd, uint32_t pid); + +int get_pid_comm(pid_t pid, size_t n, char *dest); + +void collect_data_for_all_processes(int tbl_pid_stats_fd, int maps_per_core); +void ebpf_process_apps_accumulator(ebpf_process_stat_t *out, int maps_per_core); + +// The default value is at least 32 times smaller than maximum number of PIDs allowed on system, +// this is only possible because we are using ARAL (https://github.com/netdata/netdata/tree/master/src/libnetdata/aral). +#ifndef NETDATA_EBPF_ALLOC_MAX_PID +# define NETDATA_EBPF_ALLOC_MAX_PID 1024 +#endif +#define NETDATA_EBPF_ALLOC_MIN_ELEMENTS 256 + +// ARAL Sectiion +extern void ebpf_aral_init(void); +extern ebpf_pid_stat_t *ebpf_get_pid_entry(pid_t pid, pid_t tgid); +extern ebpf_process_stat_t *process_stat_vector; + +extern ARAL *ebpf_aral_vfs_pid; +void ebpf_vfs_aral_init(); +netdata_publish_vfs_t *ebpf_vfs_get(void); +void ebpf_vfs_release(netdata_publish_vfs_t *stat); + +extern ARAL *ebpf_aral_shm_pid; +void ebpf_shm_aral_init(); +netdata_publish_shm_t *ebpf_shm_stat_get(void); +void ebpf_shm_release(netdata_publish_shm_t *stat); +void ebpf_cleanup_exited_pids(int max); + +// ARAL Section end + +// Threads integrated with apps +// Threads integrated with apps + +#include "libnetdata/threads/threads.h" + +#endif /* NETDATA_EBPF_APPS_H */ diff --git a/src/collectors/ebpf.plugin/ebpf_cachestat.c b/src/collectors/ebpf.plugin/ebpf_cachestat.c new file mode 100644 index 000000000..379ff05bb --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_cachestat.c @@ -0,0 +1,1642 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ebpf.h" +#include "ebpf_cachestat.h" + +static char *cachestat_counter_dimension_name[NETDATA_CACHESTAT_END] = { "ratio", "dirty", "hit", + "miss" }; +static netdata_syscall_stat_t cachestat_counter_aggregated_data[NETDATA_CACHESTAT_END]; +static netdata_publish_syscall_t cachestat_counter_publish_aggregated[NETDATA_CACHESTAT_END]; + +netdata_cachestat_pid_t *cachestat_vector = NULL; + +static netdata_idx_t cachestat_hash_values[NETDATA_CACHESTAT_END]; +static netdata_idx_t *cachestat_values = NULL; + +ebpf_local_maps_t cachestat_maps[] = {{.name = "cstat_global", .internal_input = NETDATA_CACHESTAT_END, + .user_input = 0, .type = NETDATA_EBPF_MAP_STATIC, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }, + {.name = "cstat_pid", .internal_input = ND_EBPF_DEFAULT_PID_SIZE, + .user_input = 0, + .type = NETDATA_EBPF_MAP_RESIZABLE | NETDATA_EBPF_MAP_PID, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_HASH +#endif + }, + {.name = "cstat_ctrl", .internal_input = NETDATA_CONTROLLER_END, + .user_input = 0, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }, + {.name = NULL, .internal_input = 0, .user_input = 0, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION +#endif + }}; + +struct config cachestat_config = { .first_section = NULL, + .last_section = NULL, + .mutex = NETDATA_MUTEX_INITIALIZER, + .index = { .avl_tree = { .root = NULL, .compar = appconfig_section_compare }, + .rwlock = AVL_LOCK_INITIALIZER } }; + +netdata_ebpf_targets_t cachestat_targets[] = { {.name = "add_to_page_cache_lru", .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = "mark_page_accessed", .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = NULL, .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = "mark_buffer_dirty", .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = NULL, .mode = EBPF_LOAD_TRAMPOLINE}}; + +static char *account_page[NETDATA_CACHESTAT_ACCOUNT_DIRTY_END] ={ "account_page_dirtied", + "__set_page_dirty", "__folio_mark_dirty" }; + +struct netdata_static_thread ebpf_read_cachestat = { + .name = "EBPF_READ_CACHESTAT", + .config_section = NULL, + .config_name = NULL, + .env_name = NULL, + .enabled = 1, + .thread = NULL, + .init_routine = NULL, + .start_routine = NULL +}; + +#ifdef LIBBPF_MAJOR_VERSION +/** + * Disable probe + * + * Disable all probes to use exclusively another method. + * + * @param obj is the main structure for bpf objects + */ +static void ebpf_cachestat_disable_probe(struct cachestat_bpf *obj) +{ + bpf_program__set_autoload(obj->progs.netdata_add_to_page_cache_lru_kprobe, false); + bpf_program__set_autoload(obj->progs.netdata_mark_page_accessed_kprobe, false); + bpf_program__set_autoload(obj->progs.netdata_folio_mark_dirty_kprobe, false); + bpf_program__set_autoload(obj->progs.netdata_set_page_dirty_kprobe, false); + bpf_program__set_autoload(obj->progs.netdata_account_page_dirtied_kprobe, false); + bpf_program__set_autoload(obj->progs.netdata_mark_buffer_dirty_kprobe, false); +} + +/* + * Disable specific probe + * + * Disable probes according the kernel version + * + * @param obj is the main structure for bpf objects + */ +static void ebpf_cachestat_disable_specific_probe(struct cachestat_bpf *obj) +{ + if (!strcmp(cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name, + account_page[NETDATA_CACHESTAT_FOLIO_DIRTY])) { + bpf_program__set_autoload(obj->progs.netdata_account_page_dirtied_kprobe, false); + bpf_program__set_autoload(obj->progs.netdata_set_page_dirty_kprobe, false); + } else if (!strcmp(cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name, + account_page[NETDATA_CACHESTAT_SET_PAGE_DIRTY])) { + bpf_program__set_autoload(obj->progs.netdata_folio_mark_dirty_kprobe, false); + bpf_program__set_autoload(obj->progs.netdata_account_page_dirtied_kprobe, false); + } else { + bpf_program__set_autoload(obj->progs.netdata_folio_mark_dirty_kprobe, false); + bpf_program__set_autoload(obj->progs.netdata_set_page_dirty_kprobe, false); + } +} + +/* + * Disable trampoline + * + * Disable all trampoline to use exclusively another method. + * + * @param obj is the main structure for bpf objects. + */ +static void ebpf_cachestat_disable_trampoline(struct cachestat_bpf *obj) +{ + bpf_program__set_autoload(obj->progs.netdata_add_to_page_cache_lru_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_mark_page_accessed_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_folio_mark_dirty_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_set_page_dirty_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_account_page_dirtied_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_mark_buffer_dirty_fentry, false); +} + +/* + * Disable specific trampoline + * + * Disable trampoline according to kernel version. + * + * @param obj is the main structure for bpf objects. + */ +static void ebpf_cachestat_disable_specific_trampoline(struct cachestat_bpf *obj) +{ + if (!strcmp(cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name, + account_page[NETDATA_CACHESTAT_FOLIO_DIRTY])) { + bpf_program__set_autoload(obj->progs.netdata_account_page_dirtied_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_set_page_dirty_fentry, false); + } else if (!strcmp(cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name, + account_page[NETDATA_CACHESTAT_SET_PAGE_DIRTY])) { + bpf_program__set_autoload(obj->progs.netdata_folio_mark_dirty_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_account_page_dirtied_fentry, false); + } else { + bpf_program__set_autoload(obj->progs.netdata_folio_mark_dirty_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_set_page_dirty_fentry, false); + } +} + +/** + * Set trampoline target + * + * Set the targets we will monitor. + * + * @param obj is the main structure for bpf objects. + */ +static inline void netdata_set_trampoline_target(struct cachestat_bpf *obj) +{ + bpf_program__set_attach_target(obj->progs.netdata_add_to_page_cache_lru_fentry, 0, + cachestat_targets[NETDATA_KEY_CALLS_ADD_TO_PAGE_CACHE_LRU].name); + + bpf_program__set_attach_target(obj->progs.netdata_mark_page_accessed_fentry, 0, + cachestat_targets[NETDATA_KEY_CALLS_MARK_PAGE_ACCESSED].name); + + if (!strcmp(cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name, + account_page[NETDATA_CACHESTAT_FOLIO_DIRTY])) { + bpf_program__set_attach_target(obj->progs.netdata_folio_mark_dirty_fentry, 0, + cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name); + } else if (!strcmp(cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name, + account_page[NETDATA_CACHESTAT_SET_PAGE_DIRTY])) { + bpf_program__set_attach_target(obj->progs.netdata_set_page_dirty_fentry, 0, + cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name); + } else { + bpf_program__set_attach_target(obj->progs.netdata_account_page_dirtied_fentry, 0, + cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name); + } + + bpf_program__set_attach_target(obj->progs.netdata_mark_buffer_dirty_fentry, 0, + cachestat_targets[NETDATA_KEY_CALLS_MARK_BUFFER_DIRTY].name); +} + +/** + * Mount Attach Probe + * + * Attach probes to target + * + * @param obj is the main structure for bpf objects. + * + * @return It returns 0 on success and -1 otherwise. + */ +static int ebpf_cachestat_attach_probe(struct cachestat_bpf *obj) +{ + obj->links.netdata_add_to_page_cache_lru_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_add_to_page_cache_lru_kprobe, + false, + cachestat_targets[NETDATA_KEY_CALLS_ADD_TO_PAGE_CACHE_LRU].name); + long ret = libbpf_get_error(obj->links.netdata_add_to_page_cache_lru_kprobe); + if (ret) + return -1; + + obj->links.netdata_mark_page_accessed_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_mark_page_accessed_kprobe, + false, + cachestat_targets[NETDATA_KEY_CALLS_MARK_PAGE_ACCESSED].name); + ret = libbpf_get_error(obj->links.netdata_mark_page_accessed_kprobe); + if (ret) + return -1; + + if (!strcmp(cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name, + account_page[NETDATA_CACHESTAT_FOLIO_DIRTY])) { + obj->links.netdata_folio_mark_dirty_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_folio_mark_dirty_kprobe, + false, + cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name); + ret = libbpf_get_error(obj->links.netdata_folio_mark_dirty_kprobe); + } else if (!strcmp(cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name, + account_page[NETDATA_CACHESTAT_SET_PAGE_DIRTY])) { + obj->links.netdata_set_page_dirty_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_set_page_dirty_kprobe, + false, + cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name); + ret = libbpf_get_error(obj->links.netdata_set_page_dirty_kprobe); + } else { + obj->links.netdata_account_page_dirtied_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_account_page_dirtied_kprobe, + false, + cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name); + ret = libbpf_get_error(obj->links.netdata_account_page_dirtied_kprobe); + } + + if (ret) + return -1; + + obj->links.netdata_mark_buffer_dirty_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_mark_buffer_dirty_kprobe, + false, + cachestat_targets[NETDATA_KEY_CALLS_MARK_BUFFER_DIRTY].name); + ret = libbpf_get_error(obj->links.netdata_mark_buffer_dirty_kprobe); + if (ret) + return -1; + + return 0; +} + +/** + * Adjust Map Size + * + * Resize maps according input from users. + * + * @param obj is the main structure for bpf objects. + * @param em structure with configuration + */ +static void ebpf_cachestat_adjust_map(struct cachestat_bpf *obj, ebpf_module_t *em) +{ + ebpf_update_map_size(obj->maps.cstat_pid, &cachestat_maps[NETDATA_CACHESTAT_PID_STATS], + em, bpf_map__name(obj->maps.cstat_pid)); + + ebpf_update_map_type(obj->maps.cstat_global, &cachestat_maps[NETDATA_CACHESTAT_GLOBAL_STATS]); + ebpf_update_map_type(obj->maps.cstat_pid, &cachestat_maps[NETDATA_CACHESTAT_PID_STATS]); + ebpf_update_map_type(obj->maps.cstat_ctrl, &cachestat_maps[NETDATA_CACHESTAT_CTRL]); +} + +/** + * Set hash tables + * + * Set the values for maps according the value given by kernel. + * + * @param obj is the main structure for bpf objects. + */ +static void ebpf_cachestat_set_hash_tables(struct cachestat_bpf *obj) +{ + cachestat_maps[NETDATA_CACHESTAT_GLOBAL_STATS].map_fd = bpf_map__fd(obj->maps.cstat_global); + cachestat_maps[NETDATA_CACHESTAT_PID_STATS].map_fd = bpf_map__fd(obj->maps.cstat_pid); + cachestat_maps[NETDATA_CACHESTAT_CTRL].map_fd = bpf_map__fd(obj->maps.cstat_ctrl); +} + +/** + * Load and attach + * + * Load and attach the eBPF code in kernel. + * + * @param obj is the main structure for bpf objects. + * @param em structure with configuration + * + * @return it returns 0 on success and -1 otherwise + */ +static inline int ebpf_cachestat_load_and_attach(struct cachestat_bpf *obj, ebpf_module_t *em) +{ + netdata_ebpf_targets_t *mt = em->targets; + netdata_ebpf_program_loaded_t test = mt[NETDATA_KEY_CALLS_ADD_TO_PAGE_CACHE_LRU].mode; + + if (test == EBPF_LOAD_TRAMPOLINE) { + ebpf_cachestat_disable_probe(obj); + ebpf_cachestat_disable_specific_trampoline(obj); + + netdata_set_trampoline_target(obj); + } else { + ebpf_cachestat_disable_trampoline(obj); + ebpf_cachestat_disable_specific_probe(obj); + } + + ebpf_cachestat_adjust_map(obj, em); + + int ret = cachestat_bpf__load(obj); + if (ret) { + return ret; + } + + ret = (test == EBPF_LOAD_TRAMPOLINE) ? cachestat_bpf__attach(obj) : ebpf_cachestat_attach_probe(obj); + if (!ret) { + ebpf_cachestat_set_hash_tables(obj); + + ebpf_update_controller(cachestat_maps[NETDATA_CACHESTAT_CTRL].map_fd, em); + } + + return ret; +} +#endif +/***************************************************************** + * + * FUNCTIONS TO CLOSE THE THREAD + * + *****************************************************************/ + +static void ebpf_obsolete_specific_cachestat_charts(char *type, int update_every); + +/** + * Obsolete services + * + * Obsolete all service charts created + * + * @param em a pointer to `struct ebpf_module` + */ +static void ebpf_obsolete_cachestat_services(ebpf_module_t *em, char *id) +{ + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_CACHESTAT_HIT_RATIO_CHART, + "Hit ratio", + EBPF_COMMON_UNITS_PERCENTAGE, + NETDATA_CACHESTAT_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_SYSTEMD_CACHESTAT_HIT_RATIO_CONTEXT, + 21100, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_CACHESTAT_DIRTY_CHART, + "Number of dirty pages", + EBPF_CACHESTAT_UNITS_PAGE, + NETDATA_CACHESTAT_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_SYSTEMD_CACHESTAT_MODIFIED_CACHE_CONTEXT, + 21101, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_CACHESTAT_HIT_CHART, + "Number of accessed files", + EBPF_CACHESTAT_UNITS_HITS, + NETDATA_CACHESTAT_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_SYSTEMD_CACHESTAT_HIT_FILE_CONTEXT, + 21102, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_CACHESTAT_MISSES_CHART, + "Files out of page cache", + EBPF_CACHESTAT_UNITS_MISSES, + NETDATA_CACHESTAT_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_SYSTEMD_CACHESTAT_MISS_FILES_CONTEXT, + 21103, + em->update_every); +} + +/** + * Obsolete cgroup chart + * + * Send obsolete for all charts created before to close. + * + * @param em a pointer to `struct ebpf_module` + */ +static inline void ebpf_obsolete_cachestat_cgroup_charts(ebpf_module_t *em) { + pthread_mutex_lock(&mutex_cgroup_shm); + + ebpf_cgroup_target_t *ect; + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + if (ect->systemd) { + ebpf_obsolete_cachestat_services(em, ect->name); + + continue; + } + + ebpf_obsolete_specific_cachestat_charts(ect->name, em->update_every); + } + pthread_mutex_unlock(&mutex_cgroup_shm); +} + +/** + * Obsolete global + * + * Obsolete global charts created by thread. + * + * @param em a pointer to `struct ebpf_module` + */ +static void ebpf_obsolete_cachestat_global(ebpf_module_t *em) +{ + ebpf_write_chart_obsolete(NETDATA_EBPF_MEMORY_GROUP, + NETDATA_CACHESTAT_HIT_RATIO_CHART, + "", + "Hit ratio", + EBPF_COMMON_UNITS_PERCENTAGE, + NETDATA_CACHESTAT_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_MEM_CACHESTAT_HIT_RATIO_CONTEXT, + 21100, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_EBPF_MEMORY_GROUP, + NETDATA_CACHESTAT_DIRTY_CHART, + "", + "Number of dirty pages", + EBPF_CACHESTAT_UNITS_PAGE, + NETDATA_CACHESTAT_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_MEM_CACHESTAT_MODIFIED_CACHE_CONTEXT, + 21101, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_EBPF_MEMORY_GROUP, + NETDATA_CACHESTAT_HIT_CHART, + "", + "Number of accessed files", + EBPF_CACHESTAT_UNITS_HITS, + NETDATA_CACHESTAT_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_MEM_CACHESTAT_HIT_FILES_CONTEXT, + 21102, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_EBPF_MEMORY_GROUP, + NETDATA_CACHESTAT_MISSES_CHART, + "", + "Files out of page cache", + EBPF_CACHESTAT_UNITS_MISSES, + NETDATA_CACHESTAT_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_MEM_CACHESTAT_MISS_FILES_CONTEXT, + 21103, + em->update_every); +} + +/** + * Obsolette apps charts + * + * Obsolete apps charts. + * + * @param em a pointer to the structure with the default values. + */ +void ebpf_obsolete_cachestat_apps_charts(struct ebpf_module *em) +{ + struct ebpf_target *w; + int update_every = em->update_every; + pthread_mutex_lock(&collect_data_mutex); + for (w = apps_groups_root_target; w; w = w->next) { + if (unlikely(!(w->charts_created & (1<<EBPF_MODULE_CACHESTAT_IDX)))) + continue; + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_cachestat_hit_ratio", + "Hit ratio", + EBPF_COMMON_UNITS_PERCENTAGE, + NETDATA_CACHESTAT_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, + "app.ebpf_cachestat_hit_ratio", + 20260, + update_every); + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_cachestat_dirty_pages", + "Number of dirty pages", + EBPF_CACHESTAT_UNITS_PAGE, + NETDATA_CACHESTAT_SUBMENU, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_cachestat_dirty_pages", + 20261, + update_every); + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_cachestat_access", + "Number of accessed files", + EBPF_CACHESTAT_UNITS_HITS, + NETDATA_CACHESTAT_SUBMENU, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_cachestat_access", + 20262, + update_every); + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_cachestat_misses", + "Files out of page cache", + EBPF_CACHESTAT_UNITS_MISSES, + NETDATA_CACHESTAT_SUBMENU, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_cachestat_misses", + 20263, + update_every); + w->charts_created &= ~(1<<EBPF_MODULE_CACHESTAT_IDX); + } + pthread_mutex_unlock(&collect_data_mutex); +} + +/** + * Cachestat exit. + * + * Cancel child and exit. + * + * @param ptr thread data. + */ +static void ebpf_cachestat_exit(void *pptr) +{ + ebpf_module_t *em = CLEANUP_FUNCTION_GET_PTR(pptr); + if(!em) return; + + if (ebpf_read_cachestat.thread) + nd_thread_signal_cancel(ebpf_read_cachestat.thread); + + if (em->enabled == NETDATA_THREAD_EBPF_FUNCTION_RUNNING) { + pthread_mutex_lock(&lock); + if (em->cgroup_charts) { + ebpf_obsolete_cachestat_cgroup_charts(em); + fflush(stdout); + } + + if (em->apps_charts & NETDATA_EBPF_APPS_FLAG_CHART_CREATED) { + ebpf_obsolete_cachestat_apps_charts(em); + } + + ebpf_obsolete_cachestat_global(em); + + fflush(stdout); + pthread_mutex_unlock(&lock); + } + + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps, EBPF_ACTION_STAT_REMOVE); + +#ifdef LIBBPF_MAJOR_VERSION + if (cachestat_bpf_obj) { + cachestat_bpf__destroy(cachestat_bpf_obj); + cachestat_bpf_obj = NULL; + } +#endif + + if (em->objects) { + ebpf_unload_legacy_code(em->objects, em->probe_links); + em->objects = NULL; + em->probe_links = NULL; + } + + pthread_mutex_lock(&ebpf_exit_cleanup); + em->enabled = NETDATA_THREAD_EBPF_STOPPED; + ebpf_update_stats(&plugin_statistics, em); + pthread_mutex_unlock(&ebpf_exit_cleanup); +} + +/***************************************************************** + * + * COMMON FUNCTIONS + * + *****************************************************************/ + +/** + * Update publish + * + * Update publish values before to write dimension. + * + * @param out structure that will receive data. + * @param mpa calls for mark_page_accessed during the last second. + * @param mbd calls for mark_buffer_dirty during the last second. + * @param apcl calls for add_to_page_cache_lru during the last second. + * @param apd calls for account_page_dirtied during the last second. + */ +void cachestat_update_publish(netdata_publish_cachestat_t *out, uint64_t mpa, uint64_t mbd, + uint64_t apcl, uint64_t apd) +{ + // Adapted algorithm from https://github.com/iovisor/bcc/blob/master/tools/cachestat.py#L126-L138 + NETDATA_DOUBLE total = (NETDATA_DOUBLE) (((long long)mpa) - ((long long)mbd)); + if (total < 0) + total = 0; + + NETDATA_DOUBLE misses = (NETDATA_DOUBLE) ( ((long long) apcl) - ((long long) apd) ); + if (misses < 0) + misses = 0; + + // If hits are < 0, then its possible misses are overestimate due to possibly page cache read ahead adding + // more pages than needed. In this case just assume misses as total and reset hits. + NETDATA_DOUBLE hits = total - misses; + if (hits < 0 ) { + misses = total; + hits = 0; + } + + NETDATA_DOUBLE ratio = (total > 0) ? hits/total : 1; + + out->ratio = (long long )(ratio*100); + out->hit = (long long)hits; + out->miss = (long long)misses; +} + +/** + * Save previous values + * + * Save values used this time. + * + * @param publish + */ +static void save_previous_values(netdata_publish_cachestat_t *publish) { + publish->prev.mark_page_accessed = cachestat_hash_values[NETDATA_KEY_CALLS_MARK_PAGE_ACCESSED]; + publish->prev.account_page_dirtied = cachestat_hash_values[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED]; + publish->prev.add_to_page_cache_lru = cachestat_hash_values[NETDATA_KEY_CALLS_ADD_TO_PAGE_CACHE_LRU]; + publish->prev.mark_buffer_dirty = cachestat_hash_values[NETDATA_KEY_CALLS_MARK_BUFFER_DIRTY]; +} + +/** + * Calculate statistics + * + * @param publish the structure where we will store the data. + */ +static void calculate_stats(netdata_publish_cachestat_t *publish) { + if (!publish->prev.mark_page_accessed) { + save_previous_values(publish); + return; + } + + uint64_t mpa = cachestat_hash_values[NETDATA_KEY_CALLS_MARK_PAGE_ACCESSED] - publish->prev.mark_page_accessed; + uint64_t mbd = cachestat_hash_values[NETDATA_KEY_CALLS_MARK_BUFFER_DIRTY] - publish->prev.mark_buffer_dirty; + uint64_t apcl = cachestat_hash_values[NETDATA_KEY_CALLS_ADD_TO_PAGE_CACHE_LRU] - publish->prev.add_to_page_cache_lru; + uint64_t apd = cachestat_hash_values[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED] - publish->prev.account_page_dirtied; + + save_previous_values(publish); + + // We are changing the original algorithm to have a smooth ratio. + cachestat_update_publish(publish, mpa, mbd, apcl, apd); +} + + +/***************************************************************** + * + * APPS + * + *****************************************************************/ + +/** + * Apps Accumulator + * + * Sum all values read from kernel and store in the first address. + * + * @param out the vector with read values. + * @param maps_per_core do I need to read all cores? + */ +static void cachestat_apps_accumulator(netdata_cachestat_pid_t *out, int maps_per_core) +{ + int i, end = (maps_per_core) ? ebpf_nprocs : 1; + netdata_cachestat_pid_t *total = &out[0]; + uint64_t ct = total->ct; + for (i = 1; i < end; i++) { + netdata_cachestat_pid_t *w = &out[i]; + total->account_page_dirtied += w->account_page_dirtied; + total->add_to_page_cache_lru += w->add_to_page_cache_lru; + total->mark_buffer_dirty += w->mark_buffer_dirty; + total->mark_page_accessed += w->mark_page_accessed; + if (w->ct > ct) + ct = w->ct; + } + total->ct = ct; +} + +/** + * Save Pid values + * + * Save the current values inside the structure + * + * @param out vector used to plot charts + * @param in vector with values read from hash tables. + */ +static inline void cachestat_save_pid_values(netdata_publish_cachestat_t *out, netdata_cachestat_pid_t *in) +{ + out->ct = in->ct; + if (!out->current.mark_page_accessed) { + memcpy(&out->current, &in[0], sizeof(netdata_cachestat_pid_t)); + return; + } + + memcpy(&out->prev, &out->current, sizeof(netdata_cachestat_pid_t)); + memcpy(&out->current, &in[0], sizeof(netdata_cachestat_pid_t)); +} + +/** + * Read APPS table + * + * Read the apps table and store data inside the structure. + * + * @param maps_per_core do I need to read all cores? + */ +static void ebpf_read_cachestat_apps_table(int maps_per_core, int max_period) +{ + netdata_cachestat_pid_t *cv = cachestat_vector; + int fd = cachestat_maps[NETDATA_CACHESTAT_PID_STATS].map_fd; + size_t length = sizeof(netdata_cachestat_pid_t); + if (maps_per_core) + length *= ebpf_nprocs; + + uint32_t key = 0, next_key = 0; + while (bpf_map_get_next_key(fd, &key, &next_key) == 0) { + if (bpf_map_lookup_elem(fd, &key, cv)) { + goto end_cachestat_loop; + } + + cachestat_apps_accumulator(cv, maps_per_core); + + ebpf_pid_stat_t *local_pid = ebpf_get_pid_entry(key, cv->tgid); + if (!local_pid) + goto end_cachestat_loop; + + netdata_publish_cachestat_t *publish = &local_pid->cachestat; + if (!publish->ct || publish->ct != cv->ct){ + cachestat_save_pid_values(publish, cv); + local_pid->not_updated = 0; + } else if (++local_pid->not_updated >= max_period) { + bpf_map_delete_elem(fd, &key); + local_pid->not_updated = 0; + } + +end_cachestat_loop: + // We are cleaning to avoid passing data read from one process to other. + memset(cv, 0, length); + key = next_key; + } +} + +/** + * Update cgroup + * + * Update cgroup data based in + * + * @param maps_per_core do I need to read all cores? + */ +static void ebpf_update_cachestat_cgroup() +{ + ebpf_cgroup_target_t *ect; + pthread_mutex_lock(&mutex_cgroup_shm); + for (ect = ebpf_cgroup_pids; ect; ect = ect->next) { + struct pid_on_target2 *pids; + for (pids = ect->pids; pids; pids = pids->next) { + int pid = pids->pid; + netdata_cachestat_pid_t *out = &pids->cachestat; + ebpf_pid_stat_t *local_pid = ebpf_get_pid_entry(pid, 0); + if (local_pid) { + netdata_publish_cachestat_t *in = &local_pid->cachestat; + + memcpy(out, &in->current, sizeof(netdata_cachestat_pid_t)); + } + } + } + pthread_mutex_unlock(&mutex_cgroup_shm); +} + +/** + * Cachestat sum PIDs + * + * Sum values for all PIDs associated to a group + * + * @param publish output structure. + * @param root structure with listed IPs + */ +void ebpf_cachestat_sum_pids(netdata_publish_cachestat_t *publish, struct ebpf_pid_on_target *root) +{ + memcpy(&publish->prev, &publish->current,sizeof(publish->current)); + memset(&publish->current, 0, sizeof(publish->current)); + + netdata_cachestat_pid_t *dst = &publish->current; + while (root) { + int32_t pid = root->pid; + ebpf_pid_stat_t *local_pid = ebpf_get_pid_entry(pid, 0); + if (local_pid) { + netdata_publish_cachestat_t *w = &local_pid->cachestat; + netdata_cachestat_pid_t *src = &w->current; + dst->account_page_dirtied += src->account_page_dirtied; + dst->add_to_page_cache_lru += src->add_to_page_cache_lru; + dst->mark_buffer_dirty += src->mark_buffer_dirty; + dst->mark_page_accessed += src->mark_page_accessed; + } + + root = root->next; + } +} + +/** + * Resume apps data + */ +void ebpf_resume_apps_data() +{ + struct ebpf_target *w; + + for (w = apps_groups_root_target; w; w = w->next) { + if (unlikely(!(w->charts_created & (1 << EBPF_MODULE_CACHESTAT_IDX)))) + continue; + + ebpf_cachestat_sum_pids(&w->cachestat, w->root_pid); + } +} + +/** + * Cachestat thread + * + * Thread used to generate cachestat charts. + * + * @param ptr a pointer to `struct ebpf_module` + * + * @return It always return NULL + */ +void *ebpf_read_cachestat_thread(void *ptr) +{ + heartbeat_t hb; + heartbeat_init(&hb); + + ebpf_module_t *em = (ebpf_module_t *)ptr; + + int maps_per_core = em->maps_per_core; + int update_every = em->update_every; + int max_period = update_every * EBPF_CLEANUP_FACTOR; + + int counter = update_every - 1; + + uint32_t lifetime = em->lifetime; + uint32_t running_time = 0; + usec_t period = update_every * USEC_PER_SEC; + while (!ebpf_plugin_stop() && running_time < lifetime) { + (void)heartbeat_next(&hb, period); + if (ebpf_plugin_stop() || ++counter != update_every) + continue; + + pthread_mutex_lock(&collect_data_mutex); + ebpf_read_cachestat_apps_table(maps_per_core, max_period); + ebpf_resume_apps_data(); + pthread_mutex_unlock(&collect_data_mutex); + + counter = 0; + + pthread_mutex_lock(&ebpf_exit_cleanup); + if (running_time && !em->running_time) + running_time = update_every; + else + running_time += update_every; + + em->running_time = running_time; + pthread_mutex_unlock(&ebpf_exit_cleanup); + } + + return NULL; +} + +/** + * Create apps charts + * + * Call ebpf_create_chart to create the charts on apps submenu. + * + * @param em a pointer to the structure with the default values. + */ +void ebpf_cachestat_create_apps_charts(struct ebpf_module *em, void *ptr) +{ + struct ebpf_target *root = ptr; + struct ebpf_target *w; + int update_every = em->update_every; + for (w = root; w; w = w->next) { + if (unlikely(!w->exposed)) + continue; + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_cachestat_hit_ratio", + "Hit ratio", + EBPF_COMMON_UNITS_PERCENTAGE, + NETDATA_CACHESTAT_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, + "app.ebpf_cachestat_hit_ratio", + 20260, + update_every, + NETDATA_EBPF_MODULE_NAME_CACHESTAT); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION ratio '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX]); + + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_cachestat_dirty_pages", + "Number of dirty pages", + EBPF_CACHESTAT_UNITS_PAGE, + NETDATA_CACHESTAT_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, + "app.ebpf_cachestat_dirty_pages", + 20261, + update_every, + NETDATA_EBPF_MODULE_NAME_CACHESTAT); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION pages '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_cachestat_access", + "Number of accessed files", + EBPF_CACHESTAT_UNITS_HITS, + NETDATA_CACHESTAT_SUBMENU, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_cachestat_access", + 20262, + update_every, + NETDATA_EBPF_MODULE_NAME_CACHESTAT); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION hits '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX]); + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_cachestat_misses", + "Files out of page cache", + EBPF_CACHESTAT_UNITS_MISSES, + NETDATA_CACHESTAT_SUBMENU, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_cachestat_misses", + 20263, + update_every, + NETDATA_EBPF_MODULE_NAME_CACHESTAT); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION misses '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX]); + w->charts_created |= 1<<EBPF_MODULE_CACHESTAT_IDX; + } + + em->apps_charts |= NETDATA_EBPF_APPS_FLAG_CHART_CREATED; +} + +/***************************************************************** + * + * MAIN LOOP + * + *****************************************************************/ + +/** + * Read global counter + * + * Read the table with number of calls for all functions + * + * @param stats vector used to read data from control table. + * @param maps_per_core do I need to read all cores? + */ +static void ebpf_cachestat_read_global_tables(netdata_idx_t *stats, int maps_per_core) +{ + ebpf_read_global_table_stats(cachestat_hash_values, + cachestat_values, + cachestat_maps[NETDATA_CACHESTAT_GLOBAL_STATS].map_fd, + maps_per_core, + NETDATA_KEY_CALLS_ADD_TO_PAGE_CACHE_LRU, + NETDATA_CACHESTAT_END); + + ebpf_read_global_table_stats(stats, + cachestat_values, + cachestat_maps[NETDATA_CACHESTAT_CTRL].map_fd, + maps_per_core, + NETDATA_CONTROLLER_PID_TABLE_ADD, + NETDATA_CONTROLLER_END); +} + +/** + * Send global + * + * Send global charts to Netdata + */ +static void cachestat_send_global(netdata_publish_cachestat_t *publish) +{ + calculate_stats(publish); + + netdata_publish_syscall_t *ptr = cachestat_counter_publish_aggregated; + ebpf_one_dimension_write_charts( + NETDATA_EBPF_MEMORY_GROUP, NETDATA_CACHESTAT_HIT_RATIO_CHART, ptr[NETDATA_CACHESTAT_IDX_RATIO].dimension, + publish->ratio); + + ebpf_one_dimension_write_charts( + NETDATA_EBPF_MEMORY_GROUP, NETDATA_CACHESTAT_DIRTY_CHART, ptr[NETDATA_CACHESTAT_IDX_DIRTY].dimension, + (long long)cachestat_hash_values[NETDATA_KEY_CALLS_MARK_BUFFER_DIRTY]); + + ebpf_one_dimension_write_charts( + NETDATA_EBPF_MEMORY_GROUP, NETDATA_CACHESTAT_HIT_CHART, ptr[NETDATA_CACHESTAT_IDX_HIT].dimension, publish->hit); + + ebpf_one_dimension_write_charts( + NETDATA_EBPF_MEMORY_GROUP, NETDATA_CACHESTAT_MISSES_CHART, ptr[NETDATA_CACHESTAT_IDX_MISS].dimension, + publish->miss); +} + +/** + * Send data to Netdata calling auxiliary functions. + * + * @param root the target list. +*/ +void ebpf_cache_send_apps_data(struct ebpf_target *root) +{ + struct ebpf_target *w; + collected_number value; + + pthread_mutex_lock(&collect_data_mutex); + for (w = root; w; w = w->next) { + if (unlikely(!(w->charts_created & (1<<EBPF_MODULE_CACHESTAT_IDX)))) + continue; + + netdata_cachestat_pid_t *current = &w->cachestat.current; + netdata_cachestat_pid_t *prev = &w->cachestat.prev; + + uint64_t mpa = current->mark_page_accessed - prev->mark_page_accessed; + uint64_t mbd = current->mark_buffer_dirty - prev->mark_buffer_dirty; + w->cachestat.dirty = (long long)mbd; + uint64_t apcl = current->add_to_page_cache_lru - prev->add_to_page_cache_lru; + uint64_t apd = current->account_page_dirtied - prev->account_page_dirtied; + + cachestat_update_publish(&w->cachestat, mpa, mbd, apcl, apd); + + value = (collected_number) w->cachestat.ratio; + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_cachestat_hit_ratio"); + write_chart_dimension("ratio", value); + ebpf_write_end_chart(); + + value = (collected_number) w->cachestat.dirty; + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_cachestat_dirty_pages"); + write_chart_dimension("pages", value); + ebpf_write_end_chart(); + + value = (collected_number) w->cachestat.hit; + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_cachestat_access"); + write_chart_dimension("hits", value); + ebpf_write_end_chart(); + + value = (collected_number) w->cachestat.miss; + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_cachestat_misses"); + write_chart_dimension("misses", value); + ebpf_write_end_chart(); + } + pthread_mutex_unlock(&collect_data_mutex); +} + +/** + * Cachestat sum PIDs + * + * Sum values for all PIDs associated to a group + * + * @param publish output structure. + * @param root structure with listed IPs + */ +void ebpf_cachestat_sum_cgroup_pids(netdata_publish_cachestat_t *publish, struct pid_on_target2 *root) +{ + memcpy(&publish->prev, &publish->current,sizeof(publish->current)); + memset(&publish->current, 0, sizeof(publish->current)); + + netdata_cachestat_pid_t *dst = &publish->current; + while (root) { + netdata_cachestat_pid_t *src = &root->cachestat; + + dst->account_page_dirtied += src->account_page_dirtied; + dst->add_to_page_cache_lru += src->add_to_page_cache_lru; + dst->mark_buffer_dirty += src->mark_buffer_dirty; + dst->mark_page_accessed += src->mark_page_accessed; + + root = root->next; + } +} + +/** + * Calc chart values + * + * Do necessary math to plot charts. + */ +void ebpf_cachestat_calc_chart_values() +{ + ebpf_cgroup_target_t *ect; + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + ebpf_cachestat_sum_cgroup_pids(&ect->publish_cachestat, ect->pids); + + netdata_cachestat_pid_t *current = &ect->publish_cachestat.current; + netdata_cachestat_pid_t *prev = &ect->publish_cachestat.prev; + + uint64_t mpa = current->mark_page_accessed - prev->mark_page_accessed; + uint64_t mbd = current->mark_buffer_dirty - prev->mark_buffer_dirty; + ect->publish_cachestat.dirty = (long long)mbd; + uint64_t apcl = current->add_to_page_cache_lru - prev->add_to_page_cache_lru; + uint64_t apd = current->account_page_dirtied - prev->account_page_dirtied; + + cachestat_update_publish(&ect->publish_cachestat, mpa, mbd, apcl, apd); + } +} + +/** + * Create Systemd cachestat Charts + * + * Create charts when systemd is enabled + * + * @param update_every value to overwrite the update frequency set by the server. + **/ +static void ebpf_create_systemd_cachestat_charts(int update_every) +{ + static ebpf_systemd_args_t data_hit_ratio = { + .title = "Hit ratio", + .units = EBPF_COMMON_UNITS_PERCENTAGE, + .family = NETDATA_CACHESTAT_SUBMENU, + .charttype = NETDATA_EBPF_CHART_TYPE_LINE, + .order = 21100, + .algorithm = EBPF_CHART_ALGORITHM_ABSOLUTE, + .context = NETDATA_SYSTEMD_CACHESTAT_HIT_RATIO_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_CACHESTAT, + .update_every = 0, + .suffix = NETDATA_CACHESTAT_HIT_RATIO_CHART, + .dimension = "percentage" + }; + + static ebpf_systemd_args_t data_dirty = { + .title = "Number of dirty pages", + .units = EBPF_CACHESTAT_UNITS_PAGE, + .family = NETDATA_CACHESTAT_SUBMENU, + .charttype = NETDATA_EBPF_CHART_TYPE_LINE, + .order = 21101, + .algorithm = EBPF_CHART_ALGORITHM_ABSOLUTE, + .context = NETDATA_SYSTEMD_CACHESTAT_MODIFIED_CACHE_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_CACHESTAT, + .update_every = 0, + .suffix = NETDATA_CACHESTAT_DIRTY_CHART, + .dimension = "pages" + }; + + static ebpf_systemd_args_t data_hit = { + .title = "Number of accessed files", + .units = EBPF_CACHESTAT_UNITS_HITS, + .family = NETDATA_CACHESTAT_SUBMENU, + .charttype = NETDATA_EBPF_CHART_TYPE_LINE, + .order = 21102, + .algorithm = EBPF_CHART_ALGORITHM_ABSOLUTE, + .context = NETDATA_SYSTEMD_CACHESTAT_HIT_FILE_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_CACHESTAT, + .update_every = 0, + .suffix = NETDATA_CACHESTAT_HIT_CHART, + .dimension = "hits" + }; + + static ebpf_systemd_args_t data_miss = { + .title = "Files out of page cache", + .units = EBPF_CACHESTAT_UNITS_MISSES, + .family = NETDATA_CACHESTAT_SUBMENU, + .charttype = NETDATA_EBPF_CHART_TYPE_LINE, + .order = 21103, + .algorithm = EBPF_CHART_ALGORITHM_ABSOLUTE, + .context = NETDATA_SYSTEMD_CACHESTAT_MISS_FILES_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_CACHESTAT, + .update_every = 0, + .suffix = NETDATA_CACHESTAT_MISSES_CHART, + .dimension = "misses" + }; + + if (!data_miss.update_every) + data_hit_ratio.update_every = data_dirty.update_every = + data_hit.update_every = data_miss.update_every = update_every; + + ebpf_cgroup_target_t *w; + for (w = ebpf_cgroup_pids; w; w = w->next) { + if (unlikely(!w->systemd || w->flags & NETDATA_EBPF_SERVICES_HAS_CACHESTAT_CHART)) + continue; + + data_hit_ratio.id = data_dirty.id = data_hit.id = data_miss.id = w->name; + ebpf_create_charts_on_systemd(&data_hit_ratio); + + ebpf_create_charts_on_systemd(&data_dirty); + + ebpf_create_charts_on_systemd(&data_hit); + + ebpf_create_charts_on_systemd(&data_miss); + + w->flags |= NETDATA_EBPF_SERVICES_HAS_CACHESTAT_CHART; + } +} + +/** + * Send Cache Stat charts + * + * Send collected data to Netdata. + */ +static void ebpf_send_systemd_cachestat_charts() +{ + ebpf_cgroup_target_t *ect; + + for (ect = ebpf_cgroup_pids; ect; ect = ect->next) { + if (unlikely(!(ect->flags & NETDATA_EBPF_SERVICES_HAS_CACHESTAT_CHART)) ) { + continue; + } + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_CACHESTAT_HIT_RATIO_CHART); + write_chart_dimension("percentage", (long long)ect->publish_cachestat.ratio); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_CACHESTAT_DIRTY_CHART); + write_chart_dimension("pages", (long long)ect->publish_cachestat.dirty); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_CACHESTAT_HIT_CHART); + write_chart_dimension("hits", (long long)ect->publish_cachestat.hit); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_CACHESTAT_MISSES_CHART); + write_chart_dimension("misses", (long long)ect->publish_cachestat.miss); + ebpf_write_end_chart(); + } +} + +/** + * Send Directory Cache charts + * + * Send collected data to Netdata. + */ +static void ebpf_send_specific_cachestat_data(char *type, netdata_publish_cachestat_t *npc) +{ + ebpf_write_begin_chart(type, NETDATA_CACHESTAT_HIT_RATIO_CHART, ""); + write_chart_dimension(cachestat_counter_publish_aggregated[NETDATA_CACHESTAT_IDX_RATIO].name, (long long)npc->ratio); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(type, NETDATA_CACHESTAT_DIRTY_CHART, ""); + write_chart_dimension(cachestat_counter_publish_aggregated[NETDATA_CACHESTAT_IDX_DIRTY].name, (long long)npc->dirty); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(type, NETDATA_CACHESTAT_HIT_CHART, ""); + write_chart_dimension(cachestat_counter_publish_aggregated[NETDATA_CACHESTAT_IDX_HIT].name, (long long)npc->hit); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(type, NETDATA_CACHESTAT_MISSES_CHART, ""); + write_chart_dimension(cachestat_counter_publish_aggregated[NETDATA_CACHESTAT_IDX_MISS].name, (long long)npc->miss); + ebpf_write_end_chart(); +} + +/** + * Create specific cache Stat charts + * + * Create charts for cgroup/application. + * + * @param type the chart type. + * @param update_every value to overwrite the update frequency set by the server. + */ +static void ebpf_create_specific_cachestat_charts(char *type, int update_every) +{ + char *label = (!strncmp(type, "cgroup_", 7)) ? &type[7] : type; + ebpf_create_chart(type, NETDATA_CACHESTAT_HIT_RATIO_CHART, + "Hit ratio", + EBPF_COMMON_UNITS_PERCENTAGE, NETDATA_CACHESTAT_SUBMENU, + NETDATA_CGROUP_CACHESTAT_HIT_RATIO_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5200, + ebpf_create_global_dimension, + cachestat_counter_publish_aggregated, 1, update_every, NETDATA_EBPF_MODULE_NAME_CACHESTAT); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + + ebpf_create_chart(type, NETDATA_CACHESTAT_DIRTY_CHART, + "Number of dirty pages", + EBPF_CACHESTAT_UNITS_PAGE, NETDATA_CACHESTAT_SUBMENU, + NETDATA_CGROUP_CACHESTAT_MODIFIED_CACHE_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5201, + ebpf_create_global_dimension, + &cachestat_counter_publish_aggregated[NETDATA_CACHESTAT_IDX_DIRTY], 1, + update_every, NETDATA_EBPF_MODULE_NAME_CACHESTAT); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + + ebpf_create_chart(type, NETDATA_CACHESTAT_HIT_CHART, + "Number of accessed files", + EBPF_CACHESTAT_UNITS_HITS, NETDATA_CACHESTAT_SUBMENU, + NETDATA_CGROUP_CACHESTAT_HIT_FILES_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5202, + ebpf_create_global_dimension, + &cachestat_counter_publish_aggregated[NETDATA_CACHESTAT_IDX_HIT], 1, + update_every, NETDATA_EBPF_MODULE_NAME_CACHESTAT); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + + ebpf_create_chart(type, NETDATA_CACHESTAT_MISSES_CHART, + "Files out of page cache", + EBPF_CACHESTAT_UNITS_MISSES, NETDATA_CACHESTAT_SUBMENU, + NETDATA_CGROUP_CACHESTAT_MISS_FILES_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5203, + ebpf_create_global_dimension, + &cachestat_counter_publish_aggregated[NETDATA_CACHESTAT_IDX_MISS], 1, + update_every, NETDATA_EBPF_MODULE_NAME_CACHESTAT); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); +} + +/** + * Obsolete specific cache stat charts + * + * Obsolete charts for cgroup/application. + * + * @param type the chart type. + * @param update_every value to overwrite the update frequency set by the server. + */ +static void ebpf_obsolete_specific_cachestat_charts(char *type, int update_every) +{ + ebpf_write_chart_obsolete(type, NETDATA_CACHESTAT_HIT_RATIO_CHART, + "", + "Hit ratio", + EBPF_COMMON_UNITS_PERCENTAGE, NETDATA_CACHESTAT_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_CACHESTAT_HIT_RATIO_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5200, update_every); + + ebpf_write_chart_obsolete(type, NETDATA_CACHESTAT_DIRTY_CHART, + "", + "Number of dirty pages", + EBPF_CACHESTAT_UNITS_PAGE, NETDATA_CACHESTAT_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_CACHESTAT_MODIFIED_CACHE_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5201, update_every); + + ebpf_write_chart_obsolete(type, NETDATA_CACHESTAT_HIT_CHART, + "", + "Number of accessed files", + EBPF_CACHESTAT_UNITS_HITS, NETDATA_CACHESTAT_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_CACHESTAT_HIT_FILES_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5202, update_every); + + ebpf_write_chart_obsolete(type, NETDATA_CACHESTAT_MISSES_CHART, + "", + "Files out of page cache", + EBPF_CACHESTAT_UNITS_MISSES, NETDATA_CACHESTAT_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_CACHESTAT_MISS_FILES_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5203, update_every); +} + +/** + * Send data to Netdata calling auxiliary functions. + * + * @param update_every value to overwrite the update frequency set by the server. +*/ +void ebpf_cachestat_send_cgroup_data(int update_every) +{ + pthread_mutex_lock(&mutex_cgroup_shm); + ebpf_cgroup_target_t *ect; + ebpf_cachestat_calc_chart_values(); + + if (shm_ebpf_cgroup.header->systemd_enabled) { + if (send_cgroup_chart) { + ebpf_create_systemd_cachestat_charts(update_every); + } + + ebpf_send_systemd_cachestat_charts(); + } + + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + if (ect->systemd) + continue; + + if (!(ect->flags & NETDATA_EBPF_CGROUP_HAS_CACHESTAT_CHART) && ect->updated) { + ebpf_create_specific_cachestat_charts(ect->name, update_every); + ect->flags |= NETDATA_EBPF_CGROUP_HAS_CACHESTAT_CHART; + } + + if (ect->flags & NETDATA_EBPF_CGROUP_HAS_CACHESTAT_CHART) { + if (ect->updated) { + ebpf_send_specific_cachestat_data(ect->name, &ect->publish_cachestat); + } else { + ebpf_obsolete_specific_cachestat_charts(ect->name, update_every); + ect->flags &= ~NETDATA_EBPF_CGROUP_HAS_CACHESTAT_CHART; + } + } + } + + pthread_mutex_unlock(&mutex_cgroup_shm); +} + +/** +* Main loop for this collector. +*/ +static void cachestat_collector(ebpf_module_t *em) +{ + netdata_publish_cachestat_t publish; + memset(&publish, 0, sizeof(publish)); + int cgroups = em->cgroup_charts; + int update_every = em->update_every; + int maps_per_core = em->maps_per_core; + heartbeat_t hb; + heartbeat_init(&hb); + int counter = update_every - 1; + //This will be cancelled by its parent + uint32_t running_time = 0; + uint32_t lifetime = em->lifetime; + netdata_idx_t *stats = em->hash_table_stats; + memset(stats, 0, sizeof(em->hash_table_stats)); + while (!ebpf_plugin_stop() && running_time < lifetime) { + (void)heartbeat_next(&hb, USEC_PER_SEC); + + if (ebpf_plugin_stop() || ++counter != update_every) + continue; + + counter = 0; + netdata_apps_integration_flags_t apps = em->apps_charts; + ebpf_cachestat_read_global_tables(stats, maps_per_core); + + if (cgroups && shm_ebpf_cgroup.header) + ebpf_update_cachestat_cgroup(); + + pthread_mutex_lock(&lock); + + cachestat_send_global(&publish); + + if (apps & NETDATA_EBPF_APPS_FLAG_CHART_CREATED) + ebpf_cache_send_apps_data(apps_groups_root_target); + + if (cgroups && shm_ebpf_cgroup.header) + ebpf_cachestat_send_cgroup_data(update_every); + + pthread_mutex_unlock(&lock); + + pthread_mutex_lock(&ebpf_exit_cleanup); + if (running_time && !em->running_time) + running_time = update_every; + else + running_time += update_every; + + em->running_time = running_time; + pthread_mutex_unlock(&ebpf_exit_cleanup); + } +} + +/***************************************************************** + * + * INITIALIZE THREAD + * + *****************************************************************/ + +/** + * Create global charts + * + * Call ebpf_create_chart to create the charts for the collector. + * + * @param em a pointer to `struct ebpf_module` + */ +static void ebpf_create_memory_charts(ebpf_module_t *em) +{ + ebpf_create_chart(NETDATA_EBPF_MEMORY_GROUP, NETDATA_CACHESTAT_HIT_RATIO_CHART, + "Hit ratio", + EBPF_COMMON_UNITS_PERCENTAGE, NETDATA_CACHESTAT_SUBMENU, + NETDATA_MEM_CACHESTAT_HIT_RATIO_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, + 21100, + ebpf_create_global_dimension, + cachestat_counter_publish_aggregated, 1, em->update_every, NETDATA_EBPF_MODULE_NAME_CACHESTAT); + + ebpf_create_chart(NETDATA_EBPF_MEMORY_GROUP, NETDATA_CACHESTAT_DIRTY_CHART, + "Number of dirty pages", + EBPF_CACHESTAT_UNITS_PAGE, NETDATA_CACHESTAT_SUBMENU, + NETDATA_MEM_CACHESTAT_MODIFIED_CACHE_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, + 21101, + ebpf_create_global_dimension, + &cachestat_counter_publish_aggregated[NETDATA_CACHESTAT_IDX_DIRTY], 1, + em->update_every, NETDATA_EBPF_MODULE_NAME_CACHESTAT); + + ebpf_create_chart(NETDATA_EBPF_MEMORY_GROUP, NETDATA_CACHESTAT_HIT_CHART, + "Number of accessed files", + EBPF_CACHESTAT_UNITS_HITS, NETDATA_CACHESTAT_SUBMENU, + NETDATA_MEM_CACHESTAT_HIT_FILES_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, + 21102, + ebpf_create_global_dimension, + &cachestat_counter_publish_aggregated[NETDATA_CACHESTAT_IDX_HIT], 1, + em->update_every, NETDATA_EBPF_MODULE_NAME_CACHESTAT); + + ebpf_create_chart(NETDATA_EBPF_MEMORY_GROUP, NETDATA_CACHESTAT_MISSES_CHART, + "Files out of page cache", + EBPF_CACHESTAT_UNITS_MISSES, NETDATA_CACHESTAT_SUBMENU, + NETDATA_MEM_CACHESTAT_MISS_FILES_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, + 21103, + ebpf_create_global_dimension, + &cachestat_counter_publish_aggregated[NETDATA_CACHESTAT_IDX_MISS], 1, + em->update_every, NETDATA_EBPF_MODULE_NAME_CACHESTAT); + + fflush(stdout); +} + +/** + * 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. + */ +static void ebpf_cachestat_allocate_global_vectors() +{ + cachestat_vector = callocz((size_t)ebpf_nprocs, sizeof(netdata_cachestat_pid_t)); + cachestat_values = callocz((size_t)ebpf_nprocs, sizeof(netdata_idx_t)); + + memset(cachestat_hash_values, 0, NETDATA_CACHESTAT_END * sizeof(netdata_idx_t)); + memset(cachestat_counter_aggregated_data, 0, NETDATA_CACHESTAT_END * sizeof(netdata_syscall_stat_t)); + memset(cachestat_counter_publish_aggregated, 0, NETDATA_CACHESTAT_END * sizeof(netdata_publish_syscall_t)); +} + +/***************************************************************** + * + * MAIN THREAD + * + *****************************************************************/ + +/** + * Update Internal value + * + * Update values used during runtime. + * + * @return It returns 0 when one of the functions is present and -1 otherwise. + */ +static int ebpf_cachestat_set_internal_value() +{ + ebpf_addresses_t address = {.function = NULL, .hash = 0, .addr = 0}; + int i; + for (i = 0; i < NETDATA_CACHESTAT_ACCOUNT_DIRTY_END ; i++) { + address.function = account_page[i]; + ebpf_load_addresses(&address, -1); + if (address.addr) + break; + } + + if (!address.addr) { + netdata_log_error("%s cachestat.", NETDATA_EBPF_DEFAULT_FNT_NOT_FOUND); + return -1; + } + + cachestat_targets[NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED].name = address.function; + + return 0; +} + +/* + * Load BPF + * + * Load BPF files. + * + * @param em the structure with configuration + */ +static int ebpf_cachestat_load_bpf(ebpf_module_t *em) +{ +#ifdef LIBBPF_MAJOR_VERSION + ebpf_define_map_type(cachestat_maps, em->maps_per_core, running_on_kernel); +#endif + + int ret = 0; + ebpf_adjust_apps_cgroup(em, em->targets[NETDATA_KEY_CALLS_ADD_TO_PAGE_CACHE_LRU].mode); + if (em->load & EBPF_LOAD_LEGACY) { + em->probe_links = ebpf_load_program(ebpf_plugin_dir, em, running_on_kernel, isrh, &em->objects); + if (!em->probe_links) { + ret = -1; + } + } +#ifdef LIBBPF_MAJOR_VERSION + else { + cachestat_bpf_obj = cachestat_bpf__open(); + if (!cachestat_bpf_obj) + ret = -1; + else + ret = ebpf_cachestat_load_and_attach(cachestat_bpf_obj, em); + } +#endif + + if (ret) + netdata_log_error("%s %s", EBPF_DEFAULT_ERROR_MSG, em->info.thread_name); + + return ret; +} + +/** + * Cachestat thread + * + * Thread used to make cachestat thread + * + * @param ptr a pointer to `struct ebpf_module` + * + * @return It always return NULL + */ +void *ebpf_cachestat_thread(void *ptr) +{ + ebpf_module_t *em = (ebpf_module_t *)ptr; + + CLEANUP_FUNCTION_REGISTER(ebpf_cachestat_exit) cleanup_ptr = em; + + em->maps = cachestat_maps; + + ebpf_update_pid_table(&cachestat_maps[NETDATA_CACHESTAT_PID_STATS], em); + + if (ebpf_cachestat_set_internal_value()) { + goto endcachestat; + } + +#ifdef LIBBPF_MAJOR_VERSION + ebpf_adjust_thread_load(em, default_btf); +#endif + if (ebpf_cachestat_load_bpf(em)) { + goto endcachestat; + } + + ebpf_cachestat_allocate_global_vectors(); + + int algorithms[NETDATA_CACHESTAT_END] = { + NETDATA_EBPF_ABSOLUTE_IDX, NETDATA_EBPF_INCREMENTAL_IDX, NETDATA_EBPF_ABSOLUTE_IDX, NETDATA_EBPF_ABSOLUTE_IDX + }; + + ebpf_global_labels(cachestat_counter_aggregated_data, cachestat_counter_publish_aggregated, + cachestat_counter_dimension_name, cachestat_counter_dimension_name, + algorithms, NETDATA_CACHESTAT_END); + + pthread_mutex_lock(&lock); + ebpf_update_stats(&plugin_statistics, em); + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps, EBPF_ACTION_STAT_ADD); + ebpf_create_memory_charts(em); + + pthread_mutex_unlock(&lock); + + ebpf_read_cachestat.thread = nd_thread_create( + ebpf_read_cachestat.name, + NETDATA_THREAD_OPTION_DEFAULT, + ebpf_read_cachestat_thread, + em); + + cachestat_collector(em); + +endcachestat: + ebpf_update_disabled_plugin_stats(em); + + return NULL; +} diff --git a/src/collectors/ebpf.plugin/ebpf_cachestat.h b/src/collectors/ebpf.plugin/ebpf_cachestat.h new file mode 100644 index 000000000..79d22b43d --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_cachestat.h @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_EBPF_CACHESTAT_H +#define NETDATA_EBPF_CACHESTAT_H 1 + +// Module name & description +#define NETDATA_EBPF_MODULE_NAME_CACHESTAT "cachestat" +#define NETDATA_EBPF_CACHESTAT_MODULE_DESC "Monitor Linux page cache internal functions. This thread is integrated with apps and cgroup." + +// charts +#define NETDATA_CACHESTAT_HIT_RATIO_CHART "cachestat_ratio" +#define NETDATA_CACHESTAT_DIRTY_CHART "cachestat_dirties" +#define NETDATA_CACHESTAT_HIT_CHART "cachestat_hits" +#define NETDATA_CACHESTAT_MISSES_CHART "cachestat_misses" + +#define NETDATA_CACHESTAT_SUBMENU "page_cache" + +#define EBPF_CACHESTAT_UNITS_PAGE "pages/s" +#define EBPF_CACHESTAT_UNITS_HITS "hits/s" +#define EBPF_CACHESTAT_UNITS_MISSES "misses/s" + +// configuration file +#define NETDATA_CACHESTAT_CONFIG_FILE "cachestat.conf" + +// Contexts +#define NETDATA_MEM_CACHESTAT_HIT_RATIO_CONTEXT "mem.cachestat_ratio" +#define NETDATA_MEM_CACHESTAT_MODIFIED_CACHE_CONTEXT "mem.cachestat_dirties" +#define NETDATA_MEM_CACHESTAT_HIT_FILES_CONTEXT "mem.cachestat_hits" +#define NETDATA_MEM_CACHESTAT_MISS_FILES_CONTEXT "mem.cachestat_misses" + +#define NETDATA_CGROUP_CACHESTAT_HIT_RATIO_CONTEXT "cgroup.cachestat_ratio" +#define NETDATA_CGROUP_CACHESTAT_MODIFIED_CACHE_CONTEXT "cgroup.cachestat_dirties" +#define NETDATA_CGROUP_CACHESTAT_HIT_FILES_CONTEXT "cgroup.cachestat_hits" +#define NETDATA_CGROUP_CACHESTAT_MISS_FILES_CONTEXT "cgroup.cachestat_misses" + +#define NETDATA_SYSTEMD_CACHESTAT_HIT_RATIO_CONTEXT "systemd.services.cachestat_ratio" +#define NETDATA_SYSTEMD_CACHESTAT_MODIFIED_CACHE_CONTEXT "systemd.services.cachestat_dirties" +#define NETDATA_SYSTEMD_CACHESTAT_HIT_FILE_CONTEXT "systemd.services.cachestat_hits" +#define NETDATA_SYSTEMD_CACHESTAT_MISS_FILES_CONTEXT "systemd.services.cachestat_misses" + +// variables +enum cachestat_counters { + NETDATA_KEY_CALLS_ADD_TO_PAGE_CACHE_LRU, + NETDATA_KEY_CALLS_MARK_PAGE_ACCESSED, + NETDATA_KEY_CALLS_ACCOUNT_PAGE_DIRTIED, + NETDATA_KEY_CALLS_MARK_BUFFER_DIRTY, + + NETDATA_CACHESTAT_END +}; + +enum cachestat_account_dirty_pages { + NETDATA_CACHESTAT_ACCOUNT_PAGE_DIRTY, + NETDATA_CACHESTAT_SET_PAGE_DIRTY, + NETDATA_CACHESTAT_FOLIO_DIRTY, + + NETDATA_CACHESTAT_ACCOUNT_DIRTY_END +}; + +enum cachestat_indexes { + NETDATA_CACHESTAT_IDX_RATIO, + NETDATA_CACHESTAT_IDX_DIRTY, + NETDATA_CACHESTAT_IDX_HIT, + NETDATA_CACHESTAT_IDX_MISS +}; + +enum cachestat_tables { + NETDATA_CACHESTAT_GLOBAL_STATS, + NETDATA_CACHESTAT_PID_STATS, + NETDATA_CACHESTAT_CTRL +}; + +typedef struct netdata_publish_cachestat_pid { + uint64_t ct; + uint32_t tgid; + uint32_t uid; + uint32_t gid; + char name[TASK_COMM_LEN]; + + uint64_t add_to_page_cache_lru; + uint64_t mark_page_accessed; + uint64_t account_page_dirtied; + uint64_t mark_buffer_dirty; +} netdata_cachestat_pid_t; + +typedef struct netdata_publish_cachestat { + uint64_t ct; + + long long ratio; + long long dirty; + long long hit; + long long miss; + + netdata_cachestat_pid_t current; + netdata_cachestat_pid_t prev; +} netdata_publish_cachestat_t; + +void *ebpf_cachestat_thread(void *ptr); +void ebpf_cachestat_release(netdata_publish_cachestat_t *stat); + +extern struct config cachestat_config; +extern netdata_ebpf_targets_t cachestat_targets[]; +extern ebpf_local_maps_t cachestat_maps[]; + +#endif // NETDATA_EBPF_CACHESTAT_H diff --git a/src/collectors/ebpf.plugin/ebpf_cgroup.c b/src/collectors/ebpf.plugin/ebpf_cgroup.c new file mode 100644 index 000000000..ae3bf3f8a --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_cgroup.c @@ -0,0 +1,382 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include <sys/resource.h> + +#include "ebpf.h" +#include "ebpf_cgroup.h" + +ebpf_cgroup_target_t *ebpf_cgroup_pids = NULL; +static void *ebpf_mapped_memory = NULL; +int send_cgroup_chart = 0; + +// -------------------------------------------------------------------------------------------------------------------- +// Map shared memory + +/** + * Map Shared Memory locally + * + * Map the shared memory for current process + * + * @param fd file descriptor returned after shm_open was called. + * @param length length of the shared memory + * + * @return It returns a pointer to the region mapped on success and MAP_FAILED otherwise. + */ +static inline void *ebpf_cgroup_map_shm_locally(int fd, size_t length) +{ + void *value; + + value = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (!value) { + netdata_log_error("Cannot map shared memory used between eBPF and cgroup, integration between processes won't happen"); + close(shm_fd_ebpf_cgroup); + shm_fd_ebpf_cgroup = -1; + shm_unlink(NETDATA_SHARED_MEMORY_EBPF_CGROUP_NAME); + } + + return value; +} + +/** + * Unmap Shared Memory + * + * Unmap shared memory used to integrate eBPF and cgroup plugin + */ +void ebpf_unmap_cgroup_shared_memory() +{ + munmap(ebpf_mapped_memory, shm_ebpf_cgroup.header->body_length); +} + +/** + * Map cgroup shared memory + * + * Map cgroup shared memory from cgroup to plugin + */ +void ebpf_map_cgroup_shared_memory() +{ + static int limit_try = 0; + static time_t next_try = 0; + + if (shm_ebpf_cgroup.header || limit_try > NETDATA_EBPF_CGROUP_MAX_TRIES) + return; + + time_t curr_time = time(NULL); + if (curr_time < next_try) + return; + + limit_try++; + next_try = curr_time + NETDATA_EBPF_CGROUP_NEXT_TRY_SEC; + + if (shm_fd_ebpf_cgroup < 0) { + shm_fd_ebpf_cgroup = shm_open(NETDATA_SHARED_MEMORY_EBPF_CGROUP_NAME, O_RDWR, 0660); + if (shm_fd_ebpf_cgroup < 0) { + if (limit_try == NETDATA_EBPF_CGROUP_MAX_TRIES) + netdata_log_error("Shared memory was not initialized, integration between processes won't happen."); + + return; + } + } + + // Map only header + void *mapped = (netdata_ebpf_cgroup_shm_header_t *) ebpf_cgroup_map_shm_locally(shm_fd_ebpf_cgroup, + sizeof(netdata_ebpf_cgroup_shm_header_t)); + if (unlikely(mapped == SEM_FAILED)) { + return; + } + netdata_ebpf_cgroup_shm_header_t *header = mapped; + + size_t length = header->body_length; + + munmap(header, sizeof(netdata_ebpf_cgroup_shm_header_t)); + + if (length <= ((sizeof(netdata_ebpf_cgroup_shm_header_t) + sizeof(netdata_ebpf_cgroup_shm_body_t)))) { + return; + } + + ebpf_mapped_memory = (void *)ebpf_cgroup_map_shm_locally(shm_fd_ebpf_cgroup, length); + if (unlikely(ebpf_mapped_memory == MAP_FAILED)) { + return; + } + shm_ebpf_cgroup.header = ebpf_mapped_memory; + shm_ebpf_cgroup.body = ebpf_mapped_memory + sizeof(netdata_ebpf_cgroup_shm_header_t); + + shm_sem_ebpf_cgroup = sem_open(NETDATA_NAMED_SEMAPHORE_EBPF_CGROUP_NAME, O_CREAT, 0660, 1); + + if (shm_sem_ebpf_cgroup == SEM_FAILED) { + netdata_log_error("Cannot create semaphore, integration between eBPF and cgroup won't happen"); + limit_try = NETDATA_EBPF_CGROUP_MAX_TRIES + 1; + munmap(ebpf_mapped_memory, length); + shm_ebpf_cgroup.header = NULL; + shm_ebpf_cgroup.body = NULL; + close(shm_fd_ebpf_cgroup); + shm_fd_ebpf_cgroup = -1; + shm_unlink(NETDATA_SHARED_MEMORY_EBPF_CGROUP_NAME); + } +} + +// -------------------------------------------------------------------------------------------------------------------- +// Close and Cleanup + +/** + * Clean Specific cgroup pid + * + * Clean all PIDs associated with cgroup. + * + * @param pt structure pid on target that will have your PRs removed + */ +static inline void ebpf_clean_specific_cgroup_pids(struct pid_on_target2 *pt) +{ + while (pt) { + struct pid_on_target2 *next_pid = pt->next; + + freez(pt); + pt = next_pid; + } +} + +/** + * Remove Cgroup Update Target Update List + * + * Remove from cgroup target and update the link list + */ +static void ebpf_remove_cgroup_target_update_list() +{ + ebpf_cgroup_target_t *next, *ect = ebpf_cgroup_pids; + ebpf_cgroup_target_t *prev = ebpf_cgroup_pids; + while (ect) { + next = ect->next; + if (!ect->updated) { + if (ect == ebpf_cgroup_pids) { + ebpf_cgroup_pids = next; + prev = next; + } else { + prev->next = next; + } + + ebpf_clean_specific_cgroup_pids(ect->pids); + freez(ect); + } else { + prev = ect; + } + + ect = next; + } +} + +// -------------------------------------------------------------------------------------------------------------------- +// Fill variables + +/** + * Set Target Data + * + * Set local variable values according shared memory information. + * + * @param out local output variable. + * @param ptr input from shared memory. + */ +static inline void ebpf_cgroup_set_target_data(ebpf_cgroup_target_t *out, netdata_ebpf_cgroup_shm_body_t *ptr) +{ + out->hash = ptr->hash; + snprintfz(out->name, 255, "%s", ptr->name); + out->systemd = ptr->options & CGROUP_OPTIONS_SYSTEM_SLICE_SERVICE; + out->updated = 1; +} + +/** + * Find or create + * + * Find the structure inside the link list or allocate and link when it is not present. + * + * @param ptr Input from shared memory. + * + * @return It returns a pointer for the structure associated with the input. + */ +static ebpf_cgroup_target_t * ebpf_cgroup_find_or_create(netdata_ebpf_cgroup_shm_body_t *ptr) +{ + ebpf_cgroup_target_t *ect, *prev; + for (ect = ebpf_cgroup_pids, prev = ebpf_cgroup_pids; ect; prev = ect, ect = ect->next) { + if (ect->hash == ptr->hash && !strcmp(ect->name, ptr->name)) { + ect->updated = 1; + return ect; + } + } + + ebpf_cgroup_target_t *new_ect = callocz(1, sizeof(ebpf_cgroup_target_t)); + + ebpf_cgroup_set_target_data(new_ect, ptr); + if (!ebpf_cgroup_pids) { + ebpf_cgroup_pids = new_ect; + } else { + prev->next = new_ect; + } + + return new_ect; +} + +/** + * Update pid link list + * + * Update PIDs list associated with specific cgroup. + * + * @param ect cgroup structure where pids will be stored + * @param path file with PIDs associated to cgroup. + */ +static void ebpf_update_pid_link_list(ebpf_cgroup_target_t *ect, char *path) +{ + procfile *ff = procfile_open_no_log(path, " \t:", PROCFILE_FLAG_DEFAULT); + if (!ff) + return; + + ff = procfile_readall(ff); + if (!ff) + return; + + size_t lines = procfile_lines(ff), l; + for (l = 0; l < lines ;l++) { + int pid = (int)str2l(procfile_lineword(ff, l, 0)); + if (pid) { + struct pid_on_target2 *pt, *prev; + for (pt = ect->pids, prev = ect->pids; pt; prev = pt, pt = pt->next) { + if (pt->pid == pid) + break; + } + + if (!pt) { + struct pid_on_target2 *w = callocz(1, sizeof(struct pid_on_target2)); + w->pid = pid; + if (!ect->pids) + ect->pids = w; + else + prev->next = w; + } + } + } + + procfile_close(ff); +} + +/** + * Set remove var + * + * Set variable remove. If this variable is not reset, the structure will be removed from link list. + */ +void ebpf_reset_updated_var() + { + ebpf_cgroup_target_t *ect; + for (ect = ebpf_cgroup_pids; ect; ect = ect->next) { + ect->updated = 0; + } + } + +/** + * Parse cgroup shared memory + * + * This function is responsible to copy necessary data from shared memory to local memory. + */ +void ebpf_parse_cgroup_shm_data() +{ + static int previous = 0; + if (!shm_ebpf_cgroup.header || shm_sem_ebpf_cgroup == SEM_FAILED) + return; + + sem_wait(shm_sem_ebpf_cgroup); + int i, end = shm_ebpf_cgroup.header->cgroup_root_count; + if (end <= 0) { + sem_post(shm_sem_ebpf_cgroup); + return; + } + + pthread_mutex_lock(&mutex_cgroup_shm); + ebpf_remove_cgroup_target_update_list(); + + ebpf_reset_updated_var(); + + for (i = 0; i < end; i++) { + netdata_ebpf_cgroup_shm_body_t *ptr = &shm_ebpf_cgroup.body[i]; + if (ptr->enabled) { + ebpf_cgroup_target_t *ect = ebpf_cgroup_find_or_create(ptr); + ebpf_update_pid_link_list(ect, ptr->path); + } + } + send_cgroup_chart = previous != shm_ebpf_cgroup.header->cgroup_root_count; + previous = shm_ebpf_cgroup.header->cgroup_root_count; + sem_post(shm_sem_ebpf_cgroup); + pthread_mutex_unlock(&mutex_cgroup_shm); +#ifdef NETDATA_DEV_MODE + netdata_log_info("Updating cgroup %d (Previous: %d, Current: %d)", + send_cgroup_chart, previous, shm_ebpf_cgroup.header->cgroup_root_count); +#endif + + sem_post(shm_sem_ebpf_cgroup); +} + +// -------------------------------------------------------------------------------------------------------------------- +// Create charts + +/** + * Create charts on systemd submenu + * + * @param id the chart id + * @param title the value displayed on vertical axis. + * @param units the value displayed on vertical axis. + * @param family Submenu that the chart will be attached on dashboard. + * @param charttype chart type + * @param order the chart order + * @param algorithm the algorithm used by dimension + * @param context add context for chart + * @param module chart module name, this is the eBPF thread. + * @param update_every value to overwrite the update frequency set by the server. + */ +void ebpf_create_charts_on_systemd(ebpf_systemd_args_t *chart) +{ + ebpf_write_chart_cmd(NETDATA_SERVICE_FAMILY, + chart->id, + chart->suffix, + chart->title, + chart->units, + chart->family, + chart->charttype, + chart->context, + chart->order, + chart->update_every, + chart->module); + ebpf_create_chart_labels("service_name", chart->id, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION %s '' %s 1 1\n", chart->dimension, chart->algorithm); +} + +// -------------------------------------------------------------------------------------------------------------------- +// Cgroup main thread + +/** + * Cgroup integratin + * + * Thread responsible to call functions responsible to sync data between plugins. + * + * @param ptr It is a NULL value for this thread. + * + * @return It always returns NULL. + */ +void *ebpf_cgroup_integration(void *ptr __maybe_unused) +{ + usec_t step = USEC_PER_SEC; + int counter = NETDATA_EBPF_CGROUP_UPDATE - 1; + heartbeat_t hb; + heartbeat_init(&hb); + //Plugin will be killed when it receives a signal + while (!ebpf_plugin_stop()) { + (void)heartbeat_next(&hb, step); + + // We are using a small heartbeat time to wake up thread, + // but we should not update so frequently the shared memory data + if (++counter >= NETDATA_EBPF_CGROUP_UPDATE) { + counter = 0; + if (!shm_ebpf_cgroup.header) + ebpf_map_cgroup_shared_memory(); + else + ebpf_parse_cgroup_shm_data(); + } + } + + return NULL; +} diff --git a/src/collectors/ebpf.plugin/ebpf_cgroup.h b/src/collectors/ebpf.plugin/ebpf_cgroup.h new file mode 100644 index 000000000..87df7bed2 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_cgroup.h @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_EBPF_CGROUP_H +#define NETDATA_EBPF_CGROUP_H 1 + +#define NETDATA_EBPF_CGROUP_MAX_TRIES 3 +#define NETDATA_EBPF_CGROUP_NEXT_TRY_SEC 30 + +#include "ebpf.h" +#include "ebpf_apps.h" + +#define NETDATA_SERVICE_FAMILY "systemd" + +struct pid_on_target2 { + int32_t pid; + int updated; + + netdata_publish_swap_t swap; + netdata_fd_stat_t fd; + netdata_publish_vfs_t vfs; + ebpf_process_stat_t ps; + netdata_dcstat_pid_t dc; + netdata_publish_shm_t shm; + netdata_socket_t socket; + netdata_cachestat_pid_t cachestat; + + struct pid_on_target2 *next; +}; + +enum ebpf_cgroup_flags { + NETDATA_EBPF_CGROUP_HAS_PROCESS_CHART = 1, + NETDATA_EBPF_CGROUP_HAS_SWAP_CHART = 1<<2, + NETDATA_EBPF_CGROUP_HAS_SOCKET_CHART = 1<<3, + NETDATA_EBPF_CGROUP_HAS_FD_CHART = 1<<4, + NETDATA_EBPF_CGROUP_HAS_VFS_CHART = 1<<5, + NETDATA_EBPF_CGROUP_HAS_OOMKILL_CHART = 1<<6, + NETDATA_EBPF_CGROUP_HAS_CACHESTAT_CHART = 1<<7, + NETDATA_EBPF_CGROUP_HAS_DC_CHART = 1<<8, + NETDATA_EBPF_CGROUP_HAS_SHM_CHART = 1<<9, + + NETDATA_EBPF_SERVICES_HAS_PROCESS_CHART = 1<<16, + NETDATA_EBPF_SERVICES_HAS_SWAP_CHART = 1<<17, + NETDATA_EBPF_SERVICES_HAS_SOCKET_CHART = 1<<18, + NETDATA_EBPF_SERVICES_HAS_FD_CHART = 1<<19, + NETDATA_EBPF_SERVICES_HAS_VFS_CHART = 1<<20, + NETDATA_EBPF_SERVICES_HAS_OOMKILL_CHART = 1<<21, + NETDATA_EBPF_SERVICES_HAS_CACHESTAT_CHART = 1<<22, + NETDATA_EBPF_SERVICES_HAS_DC_CHART = 1<<23, + NETDATA_EBPF_SERVICES_HAS_SHM_CHART = 1<<24 +}; + +typedef struct ebpf_cgroup_target { + char name[256]; // title + uint32_t hash; + uint32_t flags; + uint32_t systemd; + uint32_t updated; + + netdata_publish_swap_t publish_systemd_swap; + netdata_fd_stat_t publish_systemd_fd; + netdata_publish_vfs_t publish_systemd_vfs; + ebpf_process_stat_t publish_systemd_ps; + netdata_publish_dcstat_t publish_dc; + int oomkill; + netdata_publish_shm_t publish_shm; + ebpf_socket_publish_apps_t publish_socket; + netdata_publish_cachestat_t publish_cachestat; + + struct pid_on_target2 *pids; + struct ebpf_cgroup_target *next; +} ebpf_cgroup_target_t; + +typedef struct ebpf_systemd_args { + char *id; + char *title; + char *units; + char *family; + char *charttype; + int order; + char *algorithm; + char *context; + char *module; + int update_every; + char *suffix; + char *dimension; +} ebpf_systemd_args_t; + +void ebpf_map_cgroup_shared_memory(); +void ebpf_parse_cgroup_shm_data(); +void ebpf_create_charts_on_systemd(ebpf_systemd_args_t *chart); +void *ebpf_cgroup_integration(void *ptr); +void ebpf_unmap_cgroup_shared_memory(); +extern int send_cgroup_chart; + +#endif /* NETDATA_EBPF_CGROUP_H */ diff --git a/src/collectors/ebpf.plugin/ebpf_dcstat.c b/src/collectors/ebpf.plugin/ebpf_dcstat.c new file mode 100644 index 000000000..d9455ed9c --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_dcstat.c @@ -0,0 +1,1447 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ebpf.h" +#include "ebpf_dcstat.h" + +static char *dcstat_counter_dimension_name[NETDATA_DCSTAT_IDX_END] = { "ratio", "reference", "slow", "miss" }; +static netdata_syscall_stat_t dcstat_counter_aggregated_data[NETDATA_DCSTAT_IDX_END]; +static netdata_publish_syscall_t dcstat_counter_publish_aggregated[NETDATA_DCSTAT_IDX_END]; + +netdata_dcstat_pid_t *dcstat_vector = NULL; + +static netdata_idx_t dcstat_hash_values[NETDATA_DCSTAT_IDX_END]; +static netdata_idx_t *dcstat_values = NULL; + +struct config dcstat_config = { .first_section = NULL, + .last_section = NULL, + .mutex = NETDATA_MUTEX_INITIALIZER, + .index = { .avl_tree = { .root = NULL, .compar = appconfig_section_compare }, + .rwlock = AVL_LOCK_INITIALIZER } }; + +ebpf_local_maps_t dcstat_maps[] = {{.name = "dcstat_global", .internal_input = NETDATA_DIRECTORY_CACHE_END, + .user_input = 0, .type = NETDATA_EBPF_MAP_STATIC, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }, + {.name = "dcstat_pid", .internal_input = ND_EBPF_DEFAULT_PID_SIZE, + .user_input = 0, + .type = NETDATA_EBPF_MAP_RESIZABLE | NETDATA_EBPF_MAP_PID, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_HASH +#endif + }, + {.name = "dcstat_ctrl", .internal_input = NETDATA_CONTROLLER_END, + .user_input = 0, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }, + {.name = NULL, .internal_input = 0, .user_input = 0, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }}; + +static ebpf_specify_name_t dc_optional_name[] = { {.program_name = "netdata_lookup_fast", + .function_to_attach = "lookup_fast", + .optional = NULL, + .retprobe = CONFIG_BOOLEAN_NO}, + {.program_name = NULL}}; + +netdata_ebpf_targets_t dc_targets[] = { {.name = "lookup_fast", .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = "d_lookup", .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = NULL, .mode = EBPF_LOAD_TRAMPOLINE}}; + +struct netdata_static_thread ebpf_read_dcstat = { + .name = "EBPF_READ_DCSTAT", + .config_section = NULL, + .config_name = NULL, + .env_name = NULL, + .enabled = 1, + .thread = NULL, + .init_routine = NULL, + .start_routine = NULL +}; + +#ifdef LIBBPF_MAJOR_VERSION +/** + * Disable probe + * + * Disable all probes to use exclusively another method. + * + * @param obj is the main structure for bpf objects + */ +static inline void ebpf_dc_disable_probes(struct dc_bpf *obj) +{ + bpf_program__set_autoload(obj->progs.netdata_lookup_fast_kprobe, false); + bpf_program__set_autoload(obj->progs.netdata_d_lookup_kretprobe, false); +} + +/* + * Disable trampoline + * + * Disable all trampoline to use exclusively another method. + * + * @param obj is the main structure for bpf objects. + */ +static inline void ebpf_dc_disable_trampoline(struct dc_bpf *obj) +{ + bpf_program__set_autoload(obj->progs.netdata_lookup_fast_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_d_lookup_fexit, false); +} + +/** + * Set trampoline target + * + * Set the targets we will monitor. + * + * @param obj is the main structure for bpf objects. + */ +static void ebpf_dc_set_trampoline_target(struct dc_bpf *obj) +{ + bpf_program__set_attach_target(obj->progs.netdata_lookup_fast_fentry, 0, + dc_targets[NETDATA_DC_TARGET_LOOKUP_FAST].name); + + bpf_program__set_attach_target(obj->progs.netdata_d_lookup_fexit, 0, + dc_targets[NETDATA_DC_TARGET_D_LOOKUP].name); +} + +/** + * Mount Attach Probe + * + * Attach probes to target + * + * @param obj is the main structure for bpf objects. + * + * @return It returns 0 on success and -1 otherwise. + */ +static int ebpf_dc_attach_probes(struct dc_bpf *obj) +{ + obj->links.netdata_d_lookup_kretprobe = bpf_program__attach_kprobe(obj->progs.netdata_d_lookup_kretprobe, + true, + dc_targets[NETDATA_DC_TARGET_D_LOOKUP].name); + long ret = libbpf_get_error(obj->links.netdata_d_lookup_kretprobe); + if (ret) + return -1; + + char *lookup_name = (dc_optional_name[NETDATA_DC_TARGET_LOOKUP_FAST].optional) ? + dc_optional_name[NETDATA_DC_TARGET_LOOKUP_FAST].optional : + dc_targets[NETDATA_DC_TARGET_LOOKUP_FAST].name ; + + obj->links.netdata_lookup_fast_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_lookup_fast_kprobe, + false, + lookup_name); + ret = libbpf_get_error(obj->links.netdata_lookup_fast_kprobe); + if (ret) + return -1; + + return 0; +} + +/** + * Adjust Map Size + * + * Resize maps according input from users. + * + * @param obj is the main structure for bpf objects. + * @param em structure with configuration + */ +static void ebpf_dc_adjust_map(struct dc_bpf *obj, ebpf_module_t *em) +{ + ebpf_update_map_size(obj->maps.dcstat_pid, &dcstat_maps[NETDATA_DCSTAT_PID_STATS], + em, bpf_map__name(obj->maps.dcstat_pid)); + + ebpf_update_map_type(obj->maps.dcstat_global, &dcstat_maps[NETDATA_DCSTAT_GLOBAL_STATS]); + ebpf_update_map_type(obj->maps.dcstat_pid, &dcstat_maps[NETDATA_DCSTAT_PID_STATS]); + ebpf_update_map_type(obj->maps.dcstat_ctrl, &dcstat_maps[NETDATA_DCSTAT_CTRL]); +} + +/** + * Set hash tables + * + * Set the values for maps according the value given by kernel. + * + * @param obj is the main structure for bpf objects. + */ +static void ebpf_dc_set_hash_tables(struct dc_bpf *obj) +{ + dcstat_maps[NETDATA_DCSTAT_GLOBAL_STATS].map_fd = bpf_map__fd(obj->maps.dcstat_global); + dcstat_maps[NETDATA_DCSTAT_PID_STATS].map_fd = bpf_map__fd(obj->maps.dcstat_pid); + dcstat_maps[NETDATA_DCSTAT_CTRL].map_fd = bpf_map__fd(obj->maps.dcstat_ctrl); +} + +/** + * Update Load + * + * For directory cache, some distributions change the function name, and we do not have condition to use + * TRAMPOLINE like other functions. + * + * @param em structure with configuration + * + * @return When then symbols were not modified, it returns TRAMPOLINE, else it returns RETPROBE. + */ +netdata_ebpf_program_loaded_t ebpf_dc_update_load(ebpf_module_t *em) +{ + if (!strcmp(dc_optional_name[NETDATA_DC_TARGET_LOOKUP_FAST].optional, + dc_optional_name[NETDATA_DC_TARGET_LOOKUP_FAST].function_to_attach)) + return EBPF_LOAD_TRAMPOLINE; + + if (em->targets[NETDATA_DC_TARGET_LOOKUP_FAST].mode != EBPF_LOAD_RETPROBE) + netdata_log_info("When your kernel was compiled the symbol %s was modified, instead to use `trampoline`, the plugin will use `probes`.", + dc_optional_name[NETDATA_DC_TARGET_LOOKUP_FAST].function_to_attach); + + return EBPF_LOAD_RETPROBE; +} + +/** + * Load and attach + * + * Load and attach the eBPF code in kernel. + * + * @param obj is the main structure for bpf objects. + * @param em structure with configuration + * + * @return it returns 0 on success and -1 otherwise + */ +static inline int ebpf_dc_load_and_attach(struct dc_bpf *obj, ebpf_module_t *em) +{ + netdata_ebpf_program_loaded_t test = ebpf_dc_update_load(em); + if (test == EBPF_LOAD_TRAMPOLINE) { + ebpf_dc_disable_probes(obj); + + ebpf_dc_set_trampoline_target(obj); + } else { + ebpf_dc_disable_trampoline(obj); + } + + ebpf_dc_adjust_map(obj, em); + + int ret = dc_bpf__load(obj); + if (ret) { + return ret; + } + + ret = (test == EBPF_LOAD_TRAMPOLINE) ? dc_bpf__attach(obj) : ebpf_dc_attach_probes(obj); + if (!ret) { + ebpf_dc_set_hash_tables(obj); + + ebpf_update_controller(dcstat_maps[NETDATA_DCSTAT_CTRL].map_fd, em); + } + + return ret; +} +#endif + +/***************************************************************** + * + * COMMON FUNCTIONS + * + *****************************************************************/ + +/** + * Update publish + * + * Update publish values before to write dimension. + * + * @param out structure that will receive data. + * @param cache_access number of access to directory cache. + * @param not_found number of files not found on the file system + */ +void dcstat_update_publish(netdata_publish_dcstat_t *out, uint64_t cache_access, uint64_t not_found) +{ + NETDATA_DOUBLE successful_access = (NETDATA_DOUBLE) (((long long)cache_access) - ((long long)not_found)); + NETDATA_DOUBLE ratio = (cache_access) ? successful_access/(NETDATA_DOUBLE)cache_access : 0; + + out->ratio = (long long )(ratio*100); +} + +/***************************************************************** + * + * FUNCTIONS TO CLOSE THE THREAD + * + *****************************************************************/ + +static void ebpf_obsolete_specific_dc_charts(char *type, int update_every); + +/** + * Obsolete services + * + * Obsolete all service charts created + * + * @param em a pointer to `struct ebpf_module` + */ +static void ebpf_obsolete_dc_services(ebpf_module_t *em, char *id) +{ + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_DC_HIT_CHART, + "Percentage of files inside directory cache", + EBPF_COMMON_UNITS_PERCENTAGE, + NETDATA_DIRECTORY_CACHE_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_SYSTEMD_DC_HIT_RATIO_CONTEXT, + 21200, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_DC_REFERENCE_CHART, + "Count file access", + EBPF_COMMON_UNITS_FILES, + NETDATA_DIRECTORY_CACHE_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_SYSTEMD_DC_REFERENCE_CONTEXT, + 21201, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_DC_REQUEST_NOT_CACHE_CHART, + "Files not present inside directory cache", + EBPF_COMMON_UNITS_FILES, + NETDATA_DIRECTORY_CACHE_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_SYSTEMD_DC_NOT_CACHE_CONTEXT, + 21202, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_DC_REQUEST_NOT_FOUND_CHART, + "Files not found", + EBPF_COMMON_UNITS_FILES, + NETDATA_DIRECTORY_CACHE_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_SYSTEMD_DC_NOT_FOUND_CONTEXT, + 21203, + em->update_every); +} + +/** + * Obsolete cgroup chart + * + * Send obsolete for all charts created before to close. + * + * @param em a pointer to `struct ebpf_module` + */ +static inline void ebpf_obsolete_dc_cgroup_charts(ebpf_module_t *em) { + pthread_mutex_lock(&mutex_cgroup_shm); + + ebpf_cgroup_target_t *ect; + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + if (ect->systemd) { + ebpf_obsolete_dc_services(em, ect->name); + + continue; + } + + ebpf_obsolete_specific_dc_charts(ect->name, em->update_every); + } + pthread_mutex_unlock(&mutex_cgroup_shm); +} + +/** + * Obsolette apps charts + * + * Obsolete apps charts. + * + * @param em a pointer to the structure with the default values. + */ +void ebpf_obsolete_dc_apps_charts(struct ebpf_module *em) +{ + struct ebpf_target *w; + int update_every = em->update_every; + pthread_mutex_lock(&collect_data_mutex); + for (w = apps_groups_root_target; w; w = w->next) { + if (unlikely(!(w->charts_created & (1<<EBPF_MODULE_DCSTAT_IDX)))) + continue; + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_dc_hit", + "Percentage of files inside directory cache.", + EBPF_COMMON_UNITS_PERCENTAGE, + NETDATA_DIRECTORY_CACHE_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, + "app.ebpf_dc_hit", + 20265, + update_every); + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_dc_reference", + "Count file access.", + EBPF_COMMON_UNITS_FILES, + NETDATA_DIRECTORY_CACHE_SUBMENU, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_dc_reference", + 20266, + update_every); + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_not_cache", + "Files not present inside directory cache.", + EBPF_COMMON_UNITS_FILES, + NETDATA_DIRECTORY_CACHE_SUBMENU, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_dc_not_cache", + 20267, + update_every); + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_not_found", + "Files not found.", + EBPF_COMMON_UNITS_FILES, + NETDATA_DIRECTORY_CACHE_SUBMENU, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_dc_not_found", + 20268, + update_every); + + w->charts_created &= ~(1<<EBPF_MODULE_DCSTAT_IDX); + } + pthread_mutex_unlock(&collect_data_mutex); +} + +/** + * Obsolete global + * + * Obsolete global charts created by thread. + * + * @param em a pointer to `struct ebpf_module` + */ +static void ebpf_obsolete_dc_global(ebpf_module_t *em) +{ + ebpf_write_chart_obsolete(NETDATA_FILESYSTEM_FAMILY, + NETDATA_DC_HIT_CHART, + "", + "Percentage of files inside directory cache", + EBPF_COMMON_UNITS_PERCENTAGE, + NETDATA_DIRECTORY_CACHE_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_FS_DC_HIT_RATIO_CONTEXT, + 21200, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_FILESYSTEM_FAMILY, + NETDATA_DC_REFERENCE_CHART, + "", + "Variables used to calculate hit ratio.", + EBPF_COMMON_UNITS_FILES, + NETDATA_DIRECTORY_CACHE_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_FS_DC_REFERENCE_CONTEXT, + 21201, + em->update_every); +} + +/** + * DCstat exit + * + * Cancel child and exit. + * + * @param ptr thread data. + */ +static void ebpf_dcstat_exit(void *pptr) +{ + ebpf_module_t *em = CLEANUP_FUNCTION_GET_PTR(pptr); + if(!em) return; + + if (ebpf_read_dcstat.thread) + nd_thread_signal_cancel(ebpf_read_dcstat.thread); + + if (em->enabled == NETDATA_THREAD_EBPF_FUNCTION_RUNNING) { + pthread_mutex_lock(&lock); + if (em->cgroup_charts) { + ebpf_obsolete_dc_cgroup_charts(em); + fflush(stdout); + } + + if (em->apps_charts & NETDATA_EBPF_APPS_FLAG_CHART_CREATED) { + ebpf_obsolete_dc_apps_charts(em); + } + + ebpf_obsolete_dc_global(em); + + fflush(stdout); + pthread_mutex_unlock(&lock); + } + + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps, EBPF_ACTION_STAT_REMOVE); + +#ifdef LIBBPF_MAJOR_VERSION + if (dc_bpf_obj) { + dc_bpf__destroy(dc_bpf_obj); + dc_bpf_obj = NULL; + } +#endif + + if (em->objects){ + ebpf_unload_legacy_code(em->objects, em->probe_links); + em->objects = NULL; + em->probe_links = NULL; + } + + pthread_mutex_lock(&ebpf_exit_cleanup); + em->enabled = NETDATA_THREAD_EBPF_STOPPED; + ebpf_update_stats(&plugin_statistics, em); + pthread_mutex_unlock(&ebpf_exit_cleanup); +} + +/***************************************************************** + * + * APPS + * + *****************************************************************/ + +/** + * Apps Accumulator + * + * Sum all values read from kernel and store in the first address. + * + * @param out the vector with read values. + * @param maps_per_core do I need to read all cores? + */ +static void ebpf_dcstat_apps_accumulator(netdata_dcstat_pid_t *out, int maps_per_core) +{ + int i, end = (maps_per_core) ? ebpf_nprocs : 1; + netdata_dcstat_pid_t *total = &out[0]; + uint64_t ct = total->ct; + for (i = 1; i < end; i++) { + netdata_dcstat_pid_t *w = &out[i]; + total->cache_access += w->cache_access; + total->file_system += w->file_system; + total->not_found += w->not_found; + + if (w->ct > ct) + ct = w->ct; + } + total->ct = ct; +} + +/** + * Read Directory Cache APPS table + * + * Read the apps table and store data inside the structure. + * + * @param maps_per_core do I need to read all cores? + */ +static void ebpf_read_dc_apps_table(int maps_per_core, int max_period) +{ + netdata_dcstat_pid_t *cv = dcstat_vector; + int fd = dcstat_maps[NETDATA_DCSTAT_PID_STATS].map_fd; + size_t length = sizeof(netdata_dcstat_pid_t); + if (maps_per_core) + length *= ebpf_nprocs; + + uint32_t key = 0, next_key = 0; + while (bpf_map_get_next_key(fd, &key, &next_key) == 0) { + if (bpf_map_lookup_elem(fd, &key, cv)) { + goto end_dc_loop; + } + + ebpf_dcstat_apps_accumulator(cv, maps_per_core); + + ebpf_pid_stat_t *pid_stat = ebpf_get_pid_entry(key, cv->tgid); + if (pid_stat) { + netdata_publish_dcstat_t *publish = &pid_stat->dc; + if (!publish->ct || publish->ct != cv->ct) { + memcpy(&publish->curr, &cv[0], sizeof(netdata_dcstat_pid_t)); + pid_stat->not_updated = 0; + } else if (++pid_stat->not_updated >= max_period) { + bpf_map_delete_elem(fd, &key); + pid_stat->not_updated = 0; + } + } + +end_dc_loop: + // We are cleaning to avoid passing data read from one process to other. + memset(cv, 0, length); + key = next_key; + } +} + +/** + * Cachestat sum PIDs + * + * Sum values for all PIDs associated to a group + * + * @param publish output structure. + * @param root structure with listed IPs + */ +void ebpf_dcstat_sum_pids(netdata_publish_dcstat_t *publish, struct ebpf_pid_on_target *root) +{ + memset(&publish->curr, 0, sizeof(netdata_dcstat_pid_t)); + netdata_dcstat_pid_t *dst = &publish->curr; + while (root) { + int32_t pid = root->pid; + ebpf_pid_stat_t *pid_stat = ebpf_get_pid_entry(pid, 0); + if (pid_stat) { + netdata_publish_dcstat_t *w = &pid_stat->dc; + netdata_dcstat_pid_t *src = &w->curr; + dst->cache_access += src->cache_access; + dst->file_system += src->file_system; + dst->not_found += src->not_found; + } + + root = root->next; + } +} + +/** + * Resume apps data + */ +void ebpf_dc_resume_apps_data() +{ + struct ebpf_target *w; + + for (w = apps_groups_root_target; w; w = w->next) { + if (unlikely(!(w->charts_created & (1<<EBPF_MODULE_DCSTAT_IDX)))) + continue; + + ebpf_dcstat_sum_pids(&w->dcstat, w->root_pid); + + uint64_t cache = w->dcstat.curr.cache_access; + uint64_t not_found = w->dcstat.curr.not_found; + + dcstat_update_publish(&w->dcstat, cache, not_found); + } +} + +/** + * DCstat thread + * + * Thread used to generate dcstat charts. + * + * @param ptr a pointer to `struct ebpf_module` + * + * @return It always return NULL + */ +void *ebpf_read_dcstat_thread(void *ptr) +{ + heartbeat_t hb; + heartbeat_init(&hb); + + ebpf_module_t *em = (ebpf_module_t *)ptr; + + int maps_per_core = em->maps_per_core; + int update_every = em->update_every; + + int counter = update_every - 1; + + uint32_t lifetime = em->lifetime; + uint32_t running_time = 0; + usec_t period = update_every * USEC_PER_SEC; + int max_period = update_every * EBPF_CLEANUP_FACTOR; + while (!ebpf_plugin_stop() && running_time < lifetime) { + (void)heartbeat_next(&hb, period); + if (ebpf_plugin_stop() || ++counter != update_every) + continue; + + pthread_mutex_lock(&collect_data_mutex); + ebpf_read_dc_apps_table(maps_per_core, max_period); + ebpf_dc_resume_apps_data(); + pthread_mutex_unlock(&collect_data_mutex); + + counter = 0; + + pthread_mutex_lock(&ebpf_exit_cleanup); + if (running_time && !em->running_time) + running_time = update_every; + else + running_time += update_every; + + em->running_time = running_time; + pthread_mutex_unlock(&ebpf_exit_cleanup); + } + + return NULL; +} + +/** + * Create apps charts + * + * Call ebpf_create_chart to create the charts on apps submenu. + * + * @param em a pointer to the structure with the default values. + */ +void ebpf_dcstat_create_apps_charts(struct ebpf_module *em, void *ptr) +{ + struct ebpf_target *root = ptr; + struct ebpf_target *w; + int update_every = em->update_every; + for (w = root; w; w = w->next) { + if (unlikely(!w->exposed)) + continue; + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_dc_hit", + "Percentage of files inside directory cache.", + EBPF_COMMON_UNITS_PERCENTAGE, + NETDATA_DIRECTORY_CACHE_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, + "app.ebpf_dc_hit", + 20265, + update_every, + NETDATA_EBPF_MODULE_NAME_DCSTAT); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION ratio '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX]); + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_dc_reference", + "Count file access.", + EBPF_COMMON_UNITS_FILES, + NETDATA_DIRECTORY_CACHE_SUBMENU, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_dc_reference", + 20266, + update_every, + NETDATA_EBPF_MODULE_NAME_DCSTAT); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION files '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX]); + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_not_cache", + "Files not present inside directory cache.", + EBPF_COMMON_UNITS_FILES, + NETDATA_DIRECTORY_CACHE_SUBMENU, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_dc_not_cache", + 20267, + update_every, + NETDATA_EBPF_MODULE_NAME_DCSTAT); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION files '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX]); + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_not_found", + "Files not found.", + EBPF_COMMON_UNITS_FILES, + NETDATA_DIRECTORY_CACHE_SUBMENU, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_dc_not_found", + 20268, + update_every, + NETDATA_EBPF_MODULE_NAME_DCSTAT); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION files '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX]); + + w->charts_created |= 1<<EBPF_MODULE_DCSTAT_IDX; + } + + em->apps_charts |= NETDATA_EBPF_APPS_FLAG_CHART_CREATED; +} + +/***************************************************************** + * + * MAIN LOOP + * + *****************************************************************/ + +/** + * Update cgroup + * + * Update cgroup data based in collected PID. + * + * @param maps_per_core do I need to read all cores? + */ +static void ebpf_update_dc_cgroup() +{ + ebpf_cgroup_target_t *ect; + pthread_mutex_lock(&mutex_cgroup_shm); + for (ect = ebpf_cgroup_pids; ect; ect = ect->next) { + struct pid_on_target2 *pids; + for (pids = ect->pids; pids; pids = pids->next) { + int pid = pids->pid; + netdata_dcstat_pid_t *out = &pids->dc; + ebpf_pid_stat_t *local_pid = ebpf_get_pid_entry(pid, 0); + if (local_pid) { + netdata_publish_dcstat_t *in = &local_pid->dc; + + memcpy(out, &in->curr, sizeof(netdata_dcstat_pid_t)); + } + } + } + pthread_mutex_unlock(&mutex_cgroup_shm); +} + +/** + * Read global table + * + * Read the table with number of calls for all functions + * + * @param stats vector used to read data from control table. + * @param maps_per_core do I need to read all cores? + */ +static void ebpf_dc_read_global_tables(netdata_idx_t *stats, int maps_per_core) +{ + ebpf_read_global_table_stats(dcstat_hash_values, + dcstat_values, + dcstat_maps[NETDATA_DCSTAT_GLOBAL_STATS].map_fd, + maps_per_core, + NETDATA_KEY_DC_REFERENCE, + NETDATA_DIRECTORY_CACHE_END); + + ebpf_read_global_table_stats(stats, + dcstat_values, + dcstat_maps[NETDATA_DCSTAT_CTRL].map_fd, + maps_per_core, + NETDATA_CONTROLLER_PID_TABLE_ADD, + NETDATA_CONTROLLER_END); +} + +/** + * Send data to Netdata calling auxiliary functions. + * + * @param root the target list. +*/ +void ebpf_dcache_send_apps_data(struct ebpf_target *root) +{ + struct ebpf_target *w; + collected_number value; + + pthread_mutex_lock(&collect_data_mutex); + for (w = root; w; w = w->next) { + if (unlikely(!(w->charts_created & (1<<EBPF_MODULE_DCSTAT_IDX)))) + continue; + + value = (collected_number) w->dcstat.ratio; + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_dc_hit"); + write_chart_dimension("ratio", value); + ebpf_write_end_chart(); + + if (w->dcstat.curr.cache_access < w->dcstat.prev.cache_access) { + w->dcstat.prev.cache_access = 0; + } + w->dcstat.cache_access = (long long)w->dcstat.curr.cache_access - (long long)w->dcstat.prev.cache_access; + + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_dc_reference"); + value = (collected_number) w->dcstat.cache_access; + write_chart_dimension("files", value); + ebpf_write_end_chart(); + w->dcstat.prev.cache_access = w->dcstat.curr.cache_access; + + if (w->dcstat.curr.file_system < w->dcstat.prev.file_system) { + w->dcstat.prev.file_system = 0; + } + value = (collected_number) (!w->dcstat.cache_access) ? 0 : + (long long )w->dcstat.curr.file_system - (long long)w->dcstat.prev.file_system; + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_not_cache"); + write_chart_dimension("files", value); + ebpf_write_end_chart(); + w->dcstat.prev.file_system = w->dcstat.curr.file_system; + + if (w->dcstat.curr.not_found < w->dcstat.prev.not_found) { + w->dcstat.prev.not_found = 0; + } + value = (collected_number) (!w->dcstat.cache_access) ? 0 : + (long long)w->dcstat.curr.not_found - (long long)w->dcstat.prev.not_found; + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_not_found"); + write_chart_dimension("files", value); + ebpf_write_end_chart(); + w->dcstat.prev.not_found = w->dcstat.curr.not_found; + } + pthread_mutex_unlock(&collect_data_mutex); +} + +/** + * Send global + * + * Send global charts to Netdata + */ +static void dcstat_send_global(netdata_publish_dcstat_t *publish) +{ + dcstat_update_publish(publish, dcstat_hash_values[NETDATA_KEY_DC_REFERENCE], + dcstat_hash_values[NETDATA_KEY_DC_MISS]); + + netdata_publish_syscall_t *ptr = dcstat_counter_publish_aggregated; + netdata_idx_t value = dcstat_hash_values[NETDATA_KEY_DC_REFERENCE]; + if (value != ptr[NETDATA_DCSTAT_IDX_REFERENCE].pcall) { + ptr[NETDATA_DCSTAT_IDX_REFERENCE].ncall = value - ptr[NETDATA_DCSTAT_IDX_REFERENCE].pcall; + ptr[NETDATA_DCSTAT_IDX_REFERENCE].pcall = value; + + value = dcstat_hash_values[NETDATA_KEY_DC_SLOW]; + ptr[NETDATA_DCSTAT_IDX_SLOW].ncall = value - ptr[NETDATA_DCSTAT_IDX_SLOW].pcall; + ptr[NETDATA_DCSTAT_IDX_SLOW].pcall = value; + + value = dcstat_hash_values[NETDATA_KEY_DC_MISS]; + ptr[NETDATA_DCSTAT_IDX_MISS].ncall = value - ptr[NETDATA_DCSTAT_IDX_MISS].pcall; + ptr[NETDATA_DCSTAT_IDX_MISS].pcall = value; + } else { + ptr[NETDATA_DCSTAT_IDX_REFERENCE].ncall = 0; + ptr[NETDATA_DCSTAT_IDX_SLOW].ncall = 0; + ptr[NETDATA_DCSTAT_IDX_MISS].ncall = 0; + } + + ebpf_one_dimension_write_charts(NETDATA_FILESYSTEM_FAMILY, NETDATA_DC_HIT_CHART, + ptr[NETDATA_DCSTAT_IDX_RATIO].dimension, publish->ratio); + + write_count_chart( + NETDATA_DC_REFERENCE_CHART, NETDATA_FILESYSTEM_FAMILY, + &dcstat_counter_publish_aggregated[NETDATA_DCSTAT_IDX_REFERENCE], 3); +} + +/** + * Create specific directory cache charts + * + * Create charts for cgroup/application. + * + * @param type the chart type. + * @param update_every value to overwrite the update frequency set by the server. + */ +static void ebpf_create_specific_dc_charts(char *type, int update_every) +{ + char *label = (!strncmp(type, "cgroup_", 7)) ? &type[7] : type; + ebpf_create_chart(type, NETDATA_DC_HIT_CHART, "Percentage of files inside directory cache", + EBPF_COMMON_UNITS_PERCENTAGE, NETDATA_DIRECTORY_CACHE_SUBMENU, + NETDATA_CGROUP_DC_HIT_RATIO_CONTEXT, NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5700, + ebpf_create_global_dimension, + dcstat_counter_publish_aggregated, 1, update_every, NETDATA_EBPF_MODULE_NAME_DCSTAT); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + + ebpf_create_chart(type, NETDATA_DC_REFERENCE_CHART, "Count file access", + EBPF_COMMON_UNITS_FILES, NETDATA_DIRECTORY_CACHE_SUBMENU, + NETDATA_CGROUP_DC_REFERENCE_CONTEXT, NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5701, + ebpf_create_global_dimension, + &dcstat_counter_publish_aggregated[NETDATA_DCSTAT_IDX_REFERENCE], 1, + update_every, NETDATA_EBPF_MODULE_NAME_DCSTAT); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + + ebpf_create_chart(type, NETDATA_DC_REQUEST_NOT_CACHE_CHART, + "Files not present inside directory cache", + EBPF_COMMON_UNITS_FILES, NETDATA_DIRECTORY_CACHE_SUBMENU, + NETDATA_CGROUP_DC_NOT_CACHE_CONTEXT, NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5702, + ebpf_create_global_dimension, + &dcstat_counter_publish_aggregated[NETDATA_DCSTAT_IDX_SLOW], 1, + update_every, NETDATA_EBPF_MODULE_NAME_DCSTAT); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + + ebpf_create_chart(type, NETDATA_DC_REQUEST_NOT_FOUND_CHART, + "Files not found", + EBPF_COMMON_UNITS_FILES, NETDATA_DIRECTORY_CACHE_SUBMENU, + NETDATA_CGROUP_DC_NOT_FOUND_CONTEXT, NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5703, + ebpf_create_global_dimension, + &dcstat_counter_publish_aggregated[NETDATA_DCSTAT_IDX_MISS], 1, + update_every, NETDATA_EBPF_MODULE_NAME_DCSTAT); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); +} + +/** + * Obsolete specific directory cache charts + * + * Obsolete charts for cgroup/application. + * + * @param type the chart type. + * @param update_every value to overwrite the update frequency set by the server. + */ +static void ebpf_obsolete_specific_dc_charts(char *type, int update_every) +{ + ebpf_write_chart_obsolete(type, NETDATA_DC_HIT_CHART, + "", + "Percentage of files inside directory cache", + EBPF_COMMON_UNITS_PERCENTAGE, NETDATA_DIRECTORY_CACHE_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_DC_HIT_RATIO_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5700, update_every); + + ebpf_write_chart_obsolete(type, NETDATA_DC_REFERENCE_CHART, + "", + "Count file access", + EBPF_COMMON_UNITS_FILES, NETDATA_DIRECTORY_CACHE_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_DC_REFERENCE_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5701, update_every); + + ebpf_write_chart_obsolete(type, NETDATA_DC_REQUEST_NOT_CACHE_CHART, + "", + "Files not present inside directory cache", + EBPF_COMMON_UNITS_FILES, NETDATA_DIRECTORY_CACHE_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_DC_NOT_CACHE_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5702, update_every); + + ebpf_write_chart_obsolete(type, NETDATA_DC_REQUEST_NOT_FOUND_CHART, + "", + "Files not found", + EBPF_COMMON_UNITS_FILES, NETDATA_DIRECTORY_CACHE_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_DC_NOT_FOUND_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5703, update_every); +} + +/** + * Cachestat sum PIDs + * + * Sum values for all PIDs associated to a group + * + * @param publish output structure. + * @param root structure with listed IPs + */ +void ebpf_dc_sum_cgroup_pids(netdata_publish_dcstat_t *publish, struct pid_on_target2 *root) +{ + memset(&publish->curr, 0, sizeof(netdata_dcstat_pid_t)); + netdata_dcstat_pid_t *dst = &publish->curr; + while (root) { + netdata_dcstat_pid_t *src = &root->dc; + + dst->cache_access += src->cache_access; + dst->file_system += src->file_system; + dst->not_found += src->not_found; + + root = root->next; + } +} + +/** + * Calc chart values + * + * Do necessary math to plot charts. + */ +void ebpf_dc_calc_chart_values() +{ + ebpf_cgroup_target_t *ect; + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + ebpf_dc_sum_cgroup_pids(&ect->publish_dc, ect->pids); + uint64_t cache = ect->publish_dc.curr.cache_access; + uint64_t not_found = ect->publish_dc.curr.not_found; + + dcstat_update_publish(&ect->publish_dc, cache, not_found); + + ect->publish_dc.cache_access = (long long)ect->publish_dc.curr.cache_access - + (long long)ect->publish_dc.prev.cache_access; + ect->publish_dc.prev.cache_access = ect->publish_dc.curr.cache_access; + + if (ect->publish_dc.curr.not_found < ect->publish_dc.prev.not_found) { + ect->publish_dc.prev.not_found = 0; + } + } +} + +/** + * Create Systemd directory cache Charts + * + * Create charts when systemd is enabled + * + * @param update_every value to overwrite the update frequency set by the server. + **/ +static void ebpf_create_systemd_dc_charts(int update_every) +{ + static ebpf_systemd_args_t data_dc_hit_ratio = { + .title = "Percentage of files inside directory cache", + .units = EBPF_COMMON_UNITS_PERCENTAGE, + .family = NETDATA_DIRECTORY_CACHE_SUBMENU, + .charttype = NETDATA_EBPF_CHART_TYPE_LINE, + .order = 21200, + .algorithm = EBPF_CHART_ALGORITHM_ABSOLUTE, + .context = NETDATA_SYSTEMD_DC_HIT_RATIO_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_DCSTAT, + .update_every = 0, + .suffix = NETDATA_DC_HIT_CHART, + .dimension = "percentage" + }; + + static ebpf_systemd_args_t data_dc_references = { + .title = "Count file access", + .units = EBPF_COMMON_UNITS_FILES, + .family = NETDATA_DIRECTORY_CACHE_SUBMENU, + .charttype = NETDATA_EBPF_CHART_TYPE_LINE, + .order = 21201, + .algorithm = EBPF_CHART_ALGORITHM_ABSOLUTE, + .context = NETDATA_SYSTEMD_DC_REFERENCE_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_DCSTAT, + .update_every = 0, + .suffix = NETDATA_DC_REFERENCE_CHART, + .dimension = "files" + }; + + static ebpf_systemd_args_t data_dc_not_cache = { + .title = "Files not present inside directory cache", + .units = EBPF_COMMON_UNITS_FILES, + .family = NETDATA_DIRECTORY_CACHE_SUBMENU, + .charttype = NETDATA_EBPF_CHART_TYPE_LINE, + .order = 21202, + .algorithm = EBPF_CHART_ALGORITHM_ABSOLUTE, + .context = NETDATA_SYSTEMD_DC_NOT_CACHE_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_DCSTAT, + .update_every = 0, + .suffix = NETDATA_DC_REQUEST_NOT_CACHE_CHART, + .dimension = "files" + }; + + static ebpf_systemd_args_t data_dc_not_found = { + .title = "Files not found", + .units = EBPF_COMMON_UNITS_FILES, + .family = NETDATA_DIRECTORY_CACHE_SUBMENU, + .charttype = NETDATA_EBPF_CHART_TYPE_LINE, + .order = 21203, + .algorithm = EBPF_CHART_ALGORITHM_ABSOLUTE, + .context = NETDATA_SYSTEMD_DC_NOT_CACHE_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_DCSTAT, + .update_every = 0, + .suffix = NETDATA_DC_REQUEST_NOT_FOUND_CHART, + .dimension = "files" + }; + + if (!data_dc_not_cache.update_every) + data_dc_hit_ratio.update_every = data_dc_not_cache.update_every = + data_dc_not_found.update_every = data_dc_references.update_every = update_every; + + ebpf_cgroup_target_t *w; + for (w = ebpf_cgroup_pids; w; w = w->next) { + if (unlikely(!w->systemd || w->flags & NETDATA_EBPF_SERVICES_HAS_DC_CHART)) + continue; + + data_dc_hit_ratio.id = data_dc_not_cache.id = data_dc_not_found.id = data_dc_references.id = w->name; + ebpf_create_charts_on_systemd(&data_dc_hit_ratio); + + ebpf_create_charts_on_systemd(&data_dc_not_found); + + ebpf_create_charts_on_systemd(&data_dc_not_cache); + + ebpf_create_charts_on_systemd(&data_dc_references); + + w->flags |= NETDATA_EBPF_SERVICES_HAS_DC_CHART; + } +} + +/** + * Send Directory Cache charts + * + * Send collected data to Netdata. + */ +static void ebpf_send_systemd_dc_charts() +{ + ebpf_cgroup_target_t *ect; + collected_number value; + for (ect = ebpf_cgroup_pids; ect; ect = ect->next) { + if (unlikely(!(ect->flags & NETDATA_EBPF_SERVICES_HAS_DC_CHART)) ) { + continue; + } + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_DC_HIT_CHART); + write_chart_dimension("percentage", (long long) ect->publish_dc.ratio); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_DC_REFERENCE_CHART); + write_chart_dimension("files", (long long) ect->publish_dc.cache_access); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_DC_REQUEST_NOT_CACHE_CHART); + value = (collected_number) (!ect->publish_dc.cache_access) ? 0 : + (long long )ect->publish_dc.curr.file_system - (long long)ect->publish_dc.prev.file_system; + ect->publish_dc.prev.file_system = ect->publish_dc.curr.file_system; + write_chart_dimension("files", (long long) value); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_DC_REQUEST_NOT_FOUND_CHART); + value = (collected_number) (!ect->publish_dc.cache_access) ? 0 : + (long long)ect->publish_dc.curr.not_found - (long long)ect->publish_dc.prev.not_found; + + ect->publish_dc.prev.not_found = ect->publish_dc.curr.not_found; + + write_chart_dimension("files", (long long) value); + ebpf_write_end_chart(); + } +} + +/** + * Send Directory Cache charts + * + * Send collected data to Netdata. + * + */ +static void ebpf_send_specific_dc_data(char *type, netdata_publish_dcstat_t *pdc) +{ + collected_number value; + ebpf_write_begin_chart(type, NETDATA_DC_HIT_CHART, ""); + write_chart_dimension(dcstat_counter_publish_aggregated[NETDATA_DCSTAT_IDX_RATIO].name, + (long long) pdc->ratio); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(type, NETDATA_DC_REFERENCE_CHART, ""); + write_chart_dimension(dcstat_counter_publish_aggregated[NETDATA_DCSTAT_IDX_REFERENCE].name, + (long long) pdc->cache_access); + ebpf_write_end_chart(); + + value = (collected_number) (!pdc->cache_access) ? 0 : + (long long )pdc->curr.file_system - (long long)pdc->prev.file_system; + pdc->prev.file_system = pdc->curr.file_system; + + ebpf_write_begin_chart(type, NETDATA_DC_REQUEST_NOT_CACHE_CHART, ""); + write_chart_dimension(dcstat_counter_publish_aggregated[NETDATA_DCSTAT_IDX_SLOW].name, (long long) value); + ebpf_write_end_chart(); + + value = (collected_number) (!pdc->cache_access) ? 0 : + (long long)pdc->curr.not_found - (long long)pdc->prev.not_found; + pdc->prev.not_found = pdc->curr.not_found; + + ebpf_write_begin_chart(type, NETDATA_DC_REQUEST_NOT_FOUND_CHART, ""); + write_chart_dimension(dcstat_counter_publish_aggregated[NETDATA_DCSTAT_IDX_MISS].name, (long long) value); + ebpf_write_end_chart(); +} + +/** + * Send data to Netdata calling auxiliary functions. + * + * @param update_every value to overwrite the update frequency set by the server. +*/ +void ebpf_dc_send_cgroup_data(int update_every) +{ + pthread_mutex_lock(&mutex_cgroup_shm); + ebpf_cgroup_target_t *ect; + ebpf_dc_calc_chart_values(); + + if (shm_ebpf_cgroup.header->systemd_enabled) { + if (send_cgroup_chart) { + ebpf_create_systemd_dc_charts(update_every); + } + + ebpf_send_systemd_dc_charts(); + } + + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + if (ect->systemd) + continue; + + if (!(ect->flags & NETDATA_EBPF_CGROUP_HAS_DC_CHART) && ect->updated) { + ebpf_create_specific_dc_charts(ect->name, update_every); + ect->flags |= NETDATA_EBPF_CGROUP_HAS_DC_CHART; + } + + if (ect->flags & NETDATA_EBPF_CGROUP_HAS_DC_CHART) { + if (ect->updated) { + ebpf_send_specific_dc_data(ect->name, &ect->publish_dc); + } else { + ebpf_obsolete_specific_dc_charts(ect->name, update_every); + ect->flags &= ~NETDATA_EBPF_CGROUP_HAS_DC_CHART; + } + } + } + + pthread_mutex_unlock(&mutex_cgroup_shm); +} + +/** +* Main loop for this collector. +*/ +static void dcstat_collector(ebpf_module_t *em) +{ + netdata_publish_dcstat_t publish; + memset(&publish, 0, sizeof(publish)); + int cgroups = em->cgroup_charts; + int update_every = em->update_every; + heartbeat_t hb; + heartbeat_init(&hb); + int counter = update_every - 1; + int maps_per_core = em->maps_per_core; + uint32_t running_time = 0; + uint32_t lifetime = em->lifetime; + netdata_idx_t *stats = em->hash_table_stats; + memset(stats, 0, sizeof(em->hash_table_stats)); + while (!ebpf_plugin_stop() && running_time < lifetime) { + (void)heartbeat_next(&hb, USEC_PER_SEC); + + if (ebpf_plugin_stop() || ++counter != update_every) + continue; + + counter = 0; + netdata_apps_integration_flags_t apps = em->apps_charts; + ebpf_dc_read_global_tables(stats, maps_per_core); + + if (cgroups && shm_ebpf_cgroup.header) + ebpf_update_dc_cgroup(); + + pthread_mutex_lock(&lock); + + dcstat_send_global(&publish); + + if (apps & NETDATA_EBPF_APPS_FLAG_CHART_CREATED) + ebpf_dcache_send_apps_data(apps_groups_root_target); + + if (cgroups && shm_ebpf_cgroup.header) + ebpf_dc_send_cgroup_data(update_every); + + pthread_mutex_unlock(&lock); + + pthread_mutex_lock(&ebpf_exit_cleanup); + if (running_time && !em->running_time) + running_time = update_every; + else + running_time += update_every; + + em->running_time = running_time; + pthread_mutex_unlock(&ebpf_exit_cleanup); + } +} + +/***************************************************************** + * + * INITIALIZE THREAD + * + *****************************************************************/ + +/** + * Create filesystem charts + * + * Call ebpf_create_chart to create the charts for the collector. + * + * @param update_every value to overwrite the update frequency set by the server. + */ +static void ebpf_create_dc_global_charts(int update_every) +{ + ebpf_create_chart(NETDATA_FILESYSTEM_FAMILY, NETDATA_DC_HIT_CHART, + "Percentage of files inside directory cache", + EBPF_COMMON_UNITS_PERCENTAGE, NETDATA_DIRECTORY_CACHE_SUBMENU, + NETDATA_FS_DC_HIT_RATIO_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, + 21200, + ebpf_create_global_dimension, + dcstat_counter_publish_aggregated, 1, update_every, NETDATA_EBPF_MODULE_NAME_DCSTAT); + + ebpf_create_chart(NETDATA_FILESYSTEM_FAMILY, NETDATA_DC_REFERENCE_CHART, + "Variables used to calculate hit ratio.", + EBPF_COMMON_UNITS_FILES, NETDATA_DIRECTORY_CACHE_SUBMENU, + NETDATA_FS_DC_REFERENCE_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, + 21201, + ebpf_create_global_dimension, + &dcstat_counter_publish_aggregated[NETDATA_DCSTAT_IDX_REFERENCE], 3, + update_every, NETDATA_EBPF_MODULE_NAME_DCSTAT); + + fflush(stdout); +} + +/** + * 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. + */ +static void ebpf_dcstat_allocate_global_vectors() +{ + dcstat_vector = callocz((size_t)ebpf_nprocs, sizeof(netdata_dcstat_pid_t)); + dcstat_values = callocz((size_t)ebpf_nprocs, sizeof(netdata_idx_t)); + + memset(dcstat_counter_aggregated_data, 0, NETDATA_DCSTAT_IDX_END * sizeof(netdata_syscall_stat_t)); + memset(dcstat_counter_publish_aggregated, 0, NETDATA_DCSTAT_IDX_END * sizeof(netdata_publish_syscall_t)); +} + +/***************************************************************** + * + * MAIN THREAD + * + *****************************************************************/ + +/* + * Load BPF + * + * Load BPF files. + * + * @param em the structure with configuration + */ +static int ebpf_dcstat_load_bpf(ebpf_module_t *em) +{ +#ifdef LIBBPF_MAJOR_VERSION + ebpf_define_map_type(dcstat_maps, em->maps_per_core, running_on_kernel); +#endif + + int ret = 0; + ebpf_adjust_apps_cgroup(em, em->targets[NETDATA_DC_TARGET_LOOKUP_FAST].mode); + if (em->load & EBPF_LOAD_LEGACY) { + em->probe_links = ebpf_load_program(ebpf_plugin_dir, em, running_on_kernel, isrh, &em->objects); + if (!em->probe_links) { + ret = -1; + } + } +#ifdef LIBBPF_MAJOR_VERSION + else { + dc_bpf_obj = dc_bpf__open(); + if (!dc_bpf_obj) + ret = -1; + else + ret = ebpf_dc_load_and_attach(dc_bpf_obj, em); + } +#endif + + if (ret) + netdata_log_error("%s %s", EBPF_DEFAULT_ERROR_MSG, em->info.thread_name); + + return ret; +} + +/** + * Directory Cache thread + * + * Thread used to make dcstat thread + * + * @param ptr a pointer to `struct ebpf_module` + * + * @return It always returns NULL + */ +void *ebpf_dcstat_thread(void *ptr) +{ + ebpf_module_t *em = (ebpf_module_t *)ptr; + CLEANUP_FUNCTION_REGISTER(ebpf_dcstat_exit) cleanup_ptr = em; + + em->maps = dcstat_maps; + + ebpf_update_pid_table(&dcstat_maps[NETDATA_DCSTAT_PID_STATS], em); + + ebpf_update_names(dc_optional_name, em); + +#ifdef LIBBPF_MAJOR_VERSION + ebpf_adjust_thread_load(em, default_btf); +#endif + if (ebpf_dcstat_load_bpf(em)) { + goto enddcstat; + } + + ebpf_dcstat_allocate_global_vectors(); + + int algorithms[NETDATA_DCSTAT_IDX_END] = { + NETDATA_EBPF_ABSOLUTE_IDX, NETDATA_EBPF_ABSOLUTE_IDX, NETDATA_EBPF_ABSOLUTE_IDX, + NETDATA_EBPF_ABSOLUTE_IDX + }; + + ebpf_global_labels(dcstat_counter_aggregated_data, dcstat_counter_publish_aggregated, + dcstat_counter_dimension_name, dcstat_counter_dimension_name, + algorithms, NETDATA_DCSTAT_IDX_END); + + pthread_mutex_lock(&lock); + ebpf_create_dc_global_charts(em->update_every); + ebpf_update_stats(&plugin_statistics, em); + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps, EBPF_ACTION_STAT_ADD); + + pthread_mutex_unlock(&lock); + + ebpf_read_dcstat.thread = nd_thread_create(ebpf_read_dcstat.name, NETDATA_THREAD_OPTION_DEFAULT, + ebpf_read_dcstat_thread, em); + + dcstat_collector(em); + +enddcstat: + ebpf_update_disabled_plugin_stats(em); + + return NULL; +} diff --git a/src/collectors/ebpf.plugin/ebpf_dcstat.h b/src/collectors/ebpf.plugin/ebpf_dcstat.h new file mode 100644 index 000000000..82f21f48c --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_dcstat.h @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_EBPF_DCSTAT_H +#define NETDATA_EBPF_DCSTAT_H 1 + +// Module name & description +#define NETDATA_EBPF_MODULE_NAME_DCSTAT "dcstat" +#define NETDATA_EBPF_DC_MODULE_DESC "Monitor file access using directory cache. This thread is integrated with apps and cgroup." + +// charts +#define NETDATA_DC_HIT_CHART "dc_hit_ratio" +#define NETDATA_DC_REFERENCE_CHART "dc_reference" +#define NETDATA_DC_REQUEST_NOT_CACHE_CHART "dc_not_cache" +#define NETDATA_DC_REQUEST_NOT_FOUND_CHART "dc_not_found" + +#define NETDATA_DIRECTORY_CACHE_SUBMENU "directory cache" + +// configuration file +#define NETDATA_DIRECTORY_DCSTAT_CONFIG_FILE "dcstat.conf" + +// Contexts +#define NETDATA_FS_DC_HIT_RATIO_CONTEXT "filesystem.dc_hit_ratio" +#define NETDATA_FS_DC_REFERENCE_CONTEXT "filesystem.dc_reference" + +#define NETDATA_CGROUP_DC_HIT_RATIO_CONTEXT "cgroup.dc_ratio" +#define NETDATA_CGROUP_DC_REFERENCE_CONTEXT "cgroup.dc_reference" +#define NETDATA_CGROUP_DC_NOT_CACHE_CONTEXT "cgroup.dc_not_cache" +#define NETDATA_CGROUP_DC_NOT_FOUND_CONTEXT "cgroup.dc_not_found" + +#define NETDATA_SYSTEMD_DC_HIT_RATIO_CONTEXT "systemd.services.dc_ratio" +#define NETDATA_SYSTEMD_DC_REFERENCE_CONTEXT "systemd.services.dc_reference" +#define NETDATA_SYSTEMD_DC_NOT_CACHE_CONTEXT "systemd.services.dc_not_cache" +#define NETDATA_SYSTEMD_DC_NOT_FOUND_CONTEXT "systemd.services.dc_not_found" + +// ARAL name +#define NETDATA_EBPF_DCSTAT_ARAL_NAME "ebpf_dcstat" + +// Unity +#define EBPF_COMMON_UNITS_FILES "files" + +enum directory_cache_indexes { + NETDATA_DCSTAT_IDX_RATIO, + NETDATA_DCSTAT_IDX_REFERENCE, + NETDATA_DCSTAT_IDX_SLOW, + NETDATA_DCSTAT_IDX_MISS, + + // Keep this as last and don't skip numbers as it is used as element counter + NETDATA_DCSTAT_IDX_END +}; + +enum directory_cache_tables { + NETDATA_DCSTAT_GLOBAL_STATS, + NETDATA_DCSTAT_PID_STATS, + NETDATA_DCSTAT_CTRL +}; + +// variables +enum directory_cache_counters { + NETDATA_KEY_DC_REFERENCE, + NETDATA_KEY_DC_SLOW, + NETDATA_KEY_DC_MISS, + + // Keep this as last and don't skip numbers as it is used as element counter + NETDATA_DIRECTORY_CACHE_END +}; + +enum directory_cache_targets { + NETDATA_DC_TARGET_LOOKUP_FAST, + NETDATA_DC_TARGET_D_LOOKUP +}; + +typedef struct netdata_publish_dcstat_pid { + uint64_t ct; + uint32_t tgid; + uint32_t uid; + uint32_t gid; + char name[TASK_COMM_LEN]; + + uint64_t cache_access; + uint64_t file_system; + uint64_t not_found; +} netdata_dcstat_pid_t; + +typedef struct netdata_publish_dcstat { + uint64_t ct; + + long long ratio; + long long cache_access; + + netdata_dcstat_pid_t curr; + netdata_dcstat_pid_t prev; +} netdata_publish_dcstat_t; + +void *ebpf_dcstat_thread(void *ptr); +void ebpf_dcstat_create_apps_charts(struct ebpf_module *em, void *ptr); +void ebpf_dcstat_release(netdata_publish_dcstat_t *stat); +extern struct config dcstat_config; +extern netdata_ebpf_targets_t dc_targets[]; +extern ebpf_local_maps_t dcstat_maps[]; + +#endif // NETDATA_EBPF_DCSTAT_H diff --git a/src/collectors/ebpf.plugin/ebpf_disk.c b/src/collectors/ebpf.plugin/ebpf_disk.c new file mode 100644 index 000000000..246f98702 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_disk.c @@ -0,0 +1,940 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include <sys/resource.h> +#include <stdlib.h> + +#include "ebpf.h" +#include "ebpf_disk.h" + +struct config disk_config = { .first_section = NULL, + .last_section = NULL, + .mutex = NETDATA_MUTEX_INITIALIZER, + .index = { .avl_tree = { .root = NULL, .compar = appconfig_section_compare }, + .rwlock = AVL_LOCK_INITIALIZER } }; + +static ebpf_local_maps_t disk_maps[] = {{.name = "tbl_disk_iocall", .internal_input = NETDATA_DISK_HISTOGRAM_LENGTH, + .user_input = 0, .type = NETDATA_EBPF_MAP_STATIC, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_HASH +#endif + }, + {.name = "tmp_disk_tp_stat", .internal_input = 8192, .user_input = 8192, + .type = NETDATA_EBPF_MAP_STATIC, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_HASH +#endif + }, + {.name = NULL, .internal_input = 0, .user_input = 0, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }}; +static avl_tree_lock disk_tree; +netdata_ebpf_disks_t *disk_list = NULL; + +char *tracepoint_block_type = { "block"} ; +char *tracepoint_block_issue = { "block_rq_issue" }; +char *tracepoint_block_rq_complete = { "block_rq_complete" }; + +static int was_block_issue_enabled = 0; +static int was_block_rq_complete_enabled = 0; + +static char **dimensions = NULL; +static netdata_syscall_stat_t disk_aggregated_data[NETDATA_EBPF_HIST_MAX_BINS]; +static netdata_publish_syscall_t disk_publish_aggregated[NETDATA_EBPF_HIST_MAX_BINS]; + +static netdata_idx_t *disk_hash_values = NULL; + +ebpf_publish_disk_t *plot_disks = NULL; +pthread_mutex_t plot_mutex; + +#ifdef LIBBPF_MAJOR_VERSION +/** + * Set hash table + * + * Set the values for maps according the value given by kernel. + * + * @param obj is the main structure for bpf objects. + */ +static inline void ebpf_disk_set_hash_table(struct disk_bpf *obj) + { + disk_maps[NETDATA_DISK_IO].map_fd = bpf_map__fd(obj->maps.tbl_disk_iocall); + } + +/** + * Load and attach + * + * Load and attach the eBPF code in kernel. + * + * @param obj is the main structure for bpf objects. + * + * @return it returns 0 on success and -1 otherwise + */ +static inline int ebpf_disk_load_and_attach(struct disk_bpf *obj) +{ + int ret = disk_bpf__load(obj); + if (ret) { + return ret; + } + + return disk_bpf__attach(obj); +} +#endif + +/***************************************************************** + * + * FUNCTIONS TO MANIPULATE HARD DISKS + * + *****************************************************************/ + +/** + * Parse start + * + * Parse start address of disk + * + * @param w structure where data is stored + * @param filename variable used to store value + * + * @return It returns 0 on success and -1 otherwise + */ +static inline int ebpf_disk_parse_start(netdata_ebpf_disks_t *w, char *filename) +{ + char content[FILENAME_MAX + 1]; + int fd = open(filename, O_RDONLY, 0); + if (fd < 0) { + return -1; + } + + ssize_t file_length = read(fd, content, 4095); + if (file_length > 0) { + if (file_length > FILENAME_MAX) + file_length = FILENAME_MAX; + + content[file_length] = '\0'; + w->start = strtoul(content, NULL, 10); + } + close(fd); + + return 0; +} + +/** + * Parse uevent + * + * Parse uevent file + * + * @param w structure where data is stored + * @param filename variable used to store value + * + * @return It returns 0 on success and -1 otherwise + */ +static inline int ebpf_parse_uevent(netdata_ebpf_disks_t *w, char *filename) +{ + char content[FILENAME_MAX + 1]; + int fd = open(filename, O_RDONLY, 0); + if (fd < 0) { + return -1; + } + + ssize_t file_length = read(fd, content, FILENAME_MAX); + if (file_length > 0) { + if (file_length > FILENAME_MAX) + file_length = FILENAME_MAX; + + content[file_length] = '\0'; + + char *s = strstr(content, "PARTNAME=EFI"); + if (s) { + w->main->boot_partition = w; + w->flags |= NETDATA_DISK_HAS_EFI; + w->boot_chart = strdupz("disk_bootsector"); + } + } + close(fd); + + return 0; +} + +/** + * Parse Size + * + * @param w structure where data is stored + * @param filename variable used to store value + * + * @return It returns 0 on success and -1 otherwise + */ +static inline int ebpf_parse_size(netdata_ebpf_disks_t *w, char *filename) +{ + char content[FILENAME_MAX + 1]; + int fd = open(filename, O_RDONLY, 0); + if (fd < 0) { + return -1; + } + + ssize_t file_length = read(fd, content, FILENAME_MAX); + if (file_length > 0) { + if (file_length > FILENAME_MAX) + file_length = FILENAME_MAX; + + content[file_length] = '\0'; + w->end = w->start + strtoul(content, NULL, 10) -1; + } + close(fd); + + return 0; +} + +/** + * Read Disk information + * + * Read disk information from /sys/block + * + * @param w structure where data is stored + * @param name disk name + */ +static void ebpf_read_disk_info(netdata_ebpf_disks_t *w, char *name) +{ + static netdata_ebpf_disks_t *main_disk = NULL; + static uint32_t key = 0; + char *path = { "/sys/block" }; + char disk[NETDATA_DISK_NAME_LEN + 1]; + char filename[FILENAME_MAX + 1]; + snprintfz(disk, NETDATA_DISK_NAME_LEN, "%s", name); + size_t length = strlen(disk); + if (!length) { + return; + } + + length--; + size_t curr = length; + while (isdigit((int)disk[length])) { + disk[length--] = '\0'; + } + + // We are looking for partition information, if it is a device we will ignore it. + if (curr == length) { + main_disk = w; + key = MKDEV(w->major, w->minor); + w->bootsector_key = key; + return; + } + w->bootsector_key = key; + w->main = main_disk; + + snprintfz(filename, FILENAME_MAX, "%s/%s/%s/uevent", path, disk, name); + if (ebpf_parse_uevent(w, filename)) + return; + + snprintfz(filename, FILENAME_MAX, "%s/%s/%s/start", path, disk, name); + if (ebpf_disk_parse_start(w, filename)) + return; + + snprintfz(filename, FILENAME_MAX, "%s/%s/%s/size", path, disk, name); + ebpf_parse_size(w, filename); +} + +/** + * New encode dev + * + * New encode algorithm extracted from https://elixir.bootlin.com/linux/v5.10.8/source/include/linux/kdev_t.h#L39 + * + * @param major driver major number + * @param minor driver minor number + * + * @return + */ +static inline uint32_t netdata_new_encode_dev(uint32_t major, uint32_t minor) { + return (minor & 0xff) | (major << 8) | ((minor & ~0xff) << 12); +} + +/** + * Compare disks + * + * Compare major and minor values to add disks to tree. + * + * @param a pointer to netdata_ebpf_disks + * @param b pointer to netdata_ebpf_disks + * + * @return It returns 0 case the values are equal, 1 case a is bigger than b and -1 case a is smaller than b. +*/ +static int ebpf_compare_disks(void *a, void *b) +{ + netdata_ebpf_disks_t *ptr1 = a; + netdata_ebpf_disks_t *ptr2 = b; + + if (ptr1->dev > ptr2->dev) + return 1; + if (ptr1->dev < ptr2->dev) + return -1; + + return 0; +} + +/** + * Update listen table + * + * Update link list when it is necessary. + * + * @param name disk name + * @param major major disk identifier + * @param minor minor disk identifier + * @param current_time current timestamp + */ +static void update_disk_table(char *name, int major, int minor, time_t current_time) +{ + netdata_ebpf_disks_t find; + netdata_ebpf_disks_t *w; + size_t length; + + uint32_t dev = netdata_new_encode_dev(major, minor); + find.dev = dev; + netdata_ebpf_disks_t *ret = (netdata_ebpf_disks_t *) avl_search_lock(&disk_tree, (avl_t *)&find); + if (ret) { // Disk is already present + ret->flags |= NETDATA_DISK_IS_HERE; + ret->last_update = current_time; + return; + } + + netdata_ebpf_disks_t *update_next = disk_list; + if (likely(disk_list)) { + netdata_ebpf_disks_t *move = disk_list; + while (move) { + if (dev == move->dev) + return; + + update_next = move; + move = move->next; + } + + w = callocz(1, sizeof(netdata_ebpf_disks_t)); + length = strlen(name); + if (length >= NETDATA_DISK_NAME_LEN) + length = NETDATA_DISK_NAME_LEN; + + memcpy(w->family, name, length); + w->family[length] = '\0'; + w->major = major; + w->minor = minor; + w->dev = netdata_new_encode_dev(major, minor); + update_next->next = w; + } else { + disk_list = callocz(1, sizeof(netdata_ebpf_disks_t)); + length = strlen(name); + if (length >= NETDATA_DISK_NAME_LEN) + length = NETDATA_DISK_NAME_LEN; + + memcpy(disk_list->family, name, length); + disk_list->family[length] = '\0'; + disk_list->major = major; + disk_list->minor = minor; + disk_list->dev = netdata_new_encode_dev(major, minor); + + w = disk_list; + } + + ebpf_read_disk_info(w, name); + + netdata_ebpf_disks_t *check; + check = (netdata_ebpf_disks_t *) avl_insert_lock(&disk_tree, (avl_t *)w); + if (check != w) + netdata_log_error("Internal error, cannot insert the AVL tree."); + +#ifdef NETDATA_INTERNAL_CHECKS + netdata_log_info("The Latency is monitoring the hard disk %s (Major = %d, Minor = %d, Device = %u)", name, major, minor,w->dev); +#endif + + w->flags |= NETDATA_DISK_IS_HERE; +} + +/** + * Read Local Disks + * + * Parse /proc/partitions to get block disks used to measure latency. + * + * @return It returns 0 on success and -1 otherwise + */ +static int read_local_disks() +{ + char filename[FILENAME_MAX + 1]; + snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, NETDATA_EBPF_PROC_PARTITIONS); + procfile *ff = procfile_open(filename, " \t:", PROCFILE_FLAG_DEFAULT); + if (!ff) + return -1; + + ff = procfile_readall(ff); + if (!ff) + return -1; + + size_t lines = procfile_lines(ff), l; + time_t current_time = now_realtime_sec(); + for(l = 2; l < lines ;l++) { + size_t words = procfile_linewords(ff, l); + // This is header or end of file + if (unlikely(words < 4)) + continue; + + int major = (int)strtol(procfile_lineword(ff, l, 0), NULL, 10); + // The main goal of this thread is to measure block devices, so any block device with major number + // smaller than 7 according /proc/devices is not "important". + if (major > 7) { + int minor = (int)strtol(procfile_lineword(ff, l, 1), NULL, 10); + update_disk_table(procfile_lineword(ff, l, 3), major, minor, current_time); + } + } + + procfile_close(ff); + + return 0; +} + +/** + * Update disks + * + * @param em main thread structure + */ +void ebpf_update_disks(ebpf_module_t *em) +{ + static time_t update_every = 0; + time_t curr = now_realtime_sec(); + if (curr < update_every) + return; + + update_every = curr + 5 * em->update_every; + + (void)read_local_disks(); +} + +/***************************************************************** + * + * FUNCTIONS TO CLOSE THE THREAD + * + *****************************************************************/ + +/** + * Disk disable tracepoints + * + * Disable tracepoints when the plugin was responsible to enable it. + */ +static void ebpf_disk_disable_tracepoints() +{ + char *default_message = { "Cannot disable the tracepoint" }; + if (!was_block_issue_enabled) { + if (ebpf_disable_tracing_values(tracepoint_block_type, tracepoint_block_issue)) + netdata_log_error("%s %s/%s.", default_message, tracepoint_block_type, tracepoint_block_issue); + } + + if (!was_block_rq_complete_enabled) { + if (ebpf_disable_tracing_values(tracepoint_block_type, tracepoint_block_rq_complete)) + netdata_log_error("%s %s/%s.", default_message, tracepoint_block_type, tracepoint_block_rq_complete); + } +} + +/** + * Cleanup plot disks + * + * Clean disk list + */ +static void ebpf_cleanup_plot_disks() +{ + ebpf_publish_disk_t *move = plot_disks, *next; + while (move) { + next = move->next; + + freez(move); + + move = next; + } + plot_disks = NULL; +} + +/** + * Cleanup Disk List + */ +static void ebpf_cleanup_disk_list() +{ + netdata_ebpf_disks_t *move = disk_list; + while (move) { + netdata_ebpf_disks_t *next = move->next; + + freez(move->histogram.name); + freez(move->boot_chart); + freez(move); + + move = next; + } + disk_list = NULL; +} + +/** + * Obsolete global + * + * Obsolete global charts created by thread. + * + * @param em a pointer to `struct ebpf_module` + */ +static void ebpf_obsolete_disk_global(ebpf_module_t *em) +{ + ebpf_publish_disk_t *move = plot_disks; + while (move) { + netdata_ebpf_disks_t *ned = move->plot; + uint32_t flags = ned->flags; + if (flags & NETDATA_DISK_CHART_CREATED) { + ebpf_write_chart_obsolete(ned->histogram.name, + ned->family, + "", + "Disk latency", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + ned->family, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_EBPF_DISK_LATENCY_CONTEXT, + ned->histogram.order, + em->update_every); + } + + move = move->next; + } +} + +/** + * Disk exit. + * + * Cancel child and exit. + * + * @param ptr thread data. + */ +static void ebpf_disk_exit(void *pptr) +{ + ebpf_module_t *em = CLEANUP_FUNCTION_GET_PTR(pptr); + if(!em) return; + + if (em->enabled == NETDATA_THREAD_EBPF_FUNCTION_RUNNING) { + pthread_mutex_lock(&lock); + + ebpf_obsolete_disk_global(em); + + pthread_mutex_unlock(&lock); + fflush(stdout); + } + ebpf_disk_disable_tracepoints(); + + ebpf_update_kernel_memory_with_vector(&plugin_statistics, disk_maps, EBPF_ACTION_STAT_REMOVE); + + if (em->objects) { + ebpf_unload_legacy_code(em->objects, em->probe_links); + em->objects = NULL; + em->probe_links = NULL; + } + + if (dimensions) + ebpf_histogram_dimension_cleanup(dimensions, NETDATA_EBPF_HIST_MAX_BINS); + + freez(disk_hash_values); + disk_hash_values = NULL; + pthread_mutex_destroy(&plot_mutex); + + ebpf_cleanup_plot_disks(); + ebpf_cleanup_disk_list(); + + pthread_mutex_lock(&ebpf_exit_cleanup); + em->enabled = NETDATA_THREAD_EBPF_STOPPED; + ebpf_update_stats(&plugin_statistics, em); + pthread_mutex_unlock(&ebpf_exit_cleanup); +} + +/***************************************************************** + * + * MAIN LOOP + * + *****************************************************************/ + +/** + * Fill Plot list + * + * @param ptr a pointer for current disk + */ +static void ebpf_fill_plot_disks(netdata_ebpf_disks_t *ptr) +{ + pthread_mutex_lock(&plot_mutex); + ebpf_publish_disk_t *w; + if (likely(plot_disks)) { + ebpf_publish_disk_t *move = plot_disks, *store = plot_disks; + while (move) { + if (move->plot == ptr) { + pthread_mutex_unlock(&plot_mutex); + return; + } + + store = move; + move = move->next; + } + + w = callocz(1, sizeof(ebpf_publish_disk_t)); + w->plot = ptr; + store->next = w; + } else { + plot_disks = callocz(1, sizeof(ebpf_publish_disk_t)); + plot_disks->plot = ptr; + } + pthread_mutex_unlock(&plot_mutex); + + ptr->flags |= NETDATA_DISK_ADDED_TO_PLOT_LIST; +} + +/** + * Read hard disk table + * + * Read the table with number of calls for all functions + * + * @param table file descriptor for table + * @param maps_per_core do I need to read all cores? + */ +static void read_hard_disk_tables(int table, int maps_per_core) +{ + netdata_idx_t *values = disk_hash_values; + block_key_t key = {}; + block_key_t next_key = {}; + + netdata_ebpf_disks_t *ret = NULL; + + while (bpf_map_get_next_key(table, &key, &next_key) == 0) { + int test = bpf_map_lookup_elem(table, &key, values); + if (test < 0) { + key = next_key; + continue; + } + + netdata_ebpf_disks_t find; + find.dev = key.dev; + + if (likely(ret)) { + if (find.dev != ret->dev) + ret = (netdata_ebpf_disks_t *)avl_search_lock(&disk_tree, (avl_t *)&find); + } else + ret = (netdata_ebpf_disks_t *)avl_search_lock(&disk_tree, (avl_t *)&find); + + // Disk was inserted after we parse /proc/partitions + if (!ret) { + if (read_local_disks()) { + key = next_key; + continue; + } + + ret = (netdata_ebpf_disks_t *)avl_search_lock(&disk_tree, (avl_t *)&find); + if (!ret) { + // We should never reach this point, but we are adding it to keep a safe code + key = next_key; + continue; + } + } + + uint64_t total = 0; + int i; + int end = (maps_per_core) ? 1 : ebpf_nprocs; + for (i = 0; i < end; i++) { + total += values[i]; + } + + ret->histogram.histogram[key.bin] = total; + + if (!(ret->flags & NETDATA_DISK_ADDED_TO_PLOT_LIST)) + ebpf_fill_plot_disks(ret); + + key = next_key; + } +} + +/** + * Obsolete Hard Disk charts + * + * Make Hard disk charts and fill chart name + * + * @param w the structure with necessary information to create the chart + * @param update_every value to overwrite the update frequency set by the server. + */ +static void ebpf_obsolete_hd_charts(netdata_ebpf_disks_t *w, int update_every) +{ + ebpf_write_chart_obsolete(w->histogram.name, w->family, "", "Disk latency", EBPF_COMMON_UNITS_CALLS_PER_SEC, + w->family, NETDATA_EBPF_CHART_TYPE_STACKED, NETDATA_EBPF_DISK_LATENCY_CONTEXT, + w->histogram.order, update_every); + + w->flags = NETDATA_DISK_NONE; +} + +/** + * Create Hard Disk charts + * + * Make Hard disk charts and fill chart name + * + * @param w the structure with necessary information to create the chart + * @param update_every value to overwrite the update frequency set by the server. + */ +static void ebpf_create_hd_charts(netdata_ebpf_disks_t *w, int update_every) +{ + int order = NETDATA_CHART_PRIO_DISK_LATENCY; + char *family = w->family; + + w->histogram.name = strdupz("disk_latency_io"); + w->histogram.title = NULL; + w->histogram.order = order; + + ebpf_create_chart(w->histogram.name, family, "Disk latency", EBPF_COMMON_UNITS_CALLS_PER_SEC, + family, NETDATA_EBPF_DISK_LATENCY_CONTEXT, NETDATA_EBPF_CHART_TYPE_STACKED, order, + ebpf_create_global_dimension, disk_publish_aggregated, NETDATA_EBPF_HIST_MAX_BINS, + update_every, NETDATA_EBPF_MODULE_NAME_DISK); + order++; + + w->flags |= NETDATA_DISK_CHART_CREATED; + + fflush(stdout); +} + +/** + * Remove pointer from plot + * + * Remove pointer from plot list when the disk is not present. + */ +static void ebpf_remove_pointer_from_plot_disk(ebpf_module_t *em) +{ + time_t current_time = now_realtime_sec(); + time_t limit = 10 * em->update_every; + pthread_mutex_lock(&plot_mutex); + ebpf_publish_disk_t *move = plot_disks, *prev = plot_disks; + int update_every = em->update_every; + while (move) { + netdata_ebpf_disks_t *ned = move->plot; + uint32_t flags = ned->flags; + + if (!(flags & NETDATA_DISK_IS_HERE) && ((current_time - ned->last_update) > limit)) { + ebpf_obsolete_hd_charts(ned, update_every); + avl_t *ret = (avl_t *)avl_remove_lock(&disk_tree, (avl_t *)ned); + UNUSED(ret); + if (move == plot_disks) { + freez(move); + plot_disks = NULL; + break; + } else { + prev->next = move->next; + ebpf_publish_disk_t *clean = move; + move = move->next; + freez(clean); + continue; + } + } + + prev = move; + move = move->next; + } + pthread_mutex_unlock(&plot_mutex); +} + +/** + * Send Hard disk data + * + * Send hard disk information to Netdata. + * + * @param update_every value to overwrite the update frequency set by the server. + */ +static void ebpf_latency_send_hd_data(int update_every) +{ + pthread_mutex_lock(&plot_mutex); + if (!plot_disks) { + pthread_mutex_unlock(&plot_mutex); + return; + } + + ebpf_publish_disk_t *move = plot_disks; + while (move) { + netdata_ebpf_disks_t *ned = move->plot; + uint32_t flags = ned->flags; + if (!(flags & NETDATA_DISK_CHART_CREATED)) { + ebpf_create_hd_charts(ned, update_every); + } + + if ((flags & NETDATA_DISK_CHART_CREATED)) { + write_histogram_chart(ned->histogram.name, ned->family, + ned->histogram.histogram, dimensions, NETDATA_EBPF_HIST_MAX_BINS); + } + + ned->flags &= ~NETDATA_DISK_IS_HERE; + + move = move->next; + } + pthread_mutex_unlock(&plot_mutex); +} + +/** +* Main loop for this collector. +*/ +static void disk_collector(ebpf_module_t *em) +{ + disk_hash_values = callocz(ebpf_nprocs, sizeof(netdata_idx_t)); + + int update_every = em->update_every; + heartbeat_t hb; + heartbeat_init(&hb); + int counter = update_every - 1; + int maps_per_core = em->maps_per_core; + uint32_t running_time = 0; + uint32_t lifetime = em->lifetime; + while (!ebpf_plugin_stop() && running_time < lifetime) { + (void)heartbeat_next(&hb, USEC_PER_SEC); + + if (ebpf_plugin_stop() || ++counter != update_every) + continue; + + counter = 0; + read_hard_disk_tables(disk_maps[NETDATA_DISK_IO].map_fd, maps_per_core); + pthread_mutex_lock(&lock); + ebpf_remove_pointer_from_plot_disk(em); + ebpf_latency_send_hd_data(update_every); + + pthread_mutex_unlock(&lock); + + ebpf_update_disks(em); + + pthread_mutex_lock(&ebpf_exit_cleanup); + if (running_time && !em->running_time) + running_time = update_every; + else + running_time += update_every; + + em->running_time = running_time; + pthread_mutex_unlock(&ebpf_exit_cleanup); + } +} + +/***************************************************************** + * + * EBPF DISK THREAD + * + *****************************************************************/ + +/** + * Enable tracepoints + * + * Enable necessary tracepoints for thread. + * + * @return It returns 0 on success and -1 otherwise + */ +static int ebpf_disk_enable_tracepoints() +{ + int test = ebpf_is_tracepoint_enabled(tracepoint_block_type, tracepoint_block_issue); + if (test == -1) + return -1; + else if (!test) { + if (ebpf_enable_tracing_values(tracepoint_block_type, tracepoint_block_issue)) + return -1; + } + was_block_issue_enabled = test; + + test = ebpf_is_tracepoint_enabled(tracepoint_block_type, tracepoint_block_rq_complete); + if (test == -1) + return -1; + else if (!test) { + if (ebpf_enable_tracing_values(tracepoint_block_type, tracepoint_block_rq_complete)) + return -1; + } + was_block_rq_complete_enabled = test; + + return 0; +} + +/* + * Load BPF + * + * Load BPF files. + * + * @param em the structure with configuration + * + * @return It returns 0 on success and -1 otherwise. + */ +static int ebpf_disk_load_bpf(ebpf_module_t *em) +{ + int ret = 0; + if (em->load & EBPF_LOAD_LEGACY) { + em->probe_links = ebpf_load_program(ebpf_plugin_dir, em, running_on_kernel, isrh, &em->objects); + if (!em->probe_links) { + ret = -1; + } + } +#ifdef LIBBPF_MAJOR_VERSION + else { + disk_bpf_obj = disk_bpf__open(); + if (!disk_bpf_obj) + ret = -1; + else { + ret = ebpf_disk_load_and_attach(disk_bpf_obj); + if (!ret) + ebpf_disk_set_hash_table(disk_bpf_obj); + } + } +#endif + + if (ret) + netdata_log_error("%s %s", EBPF_DEFAULT_ERROR_MSG, em->info.thread_name); + + return ret; +} + +/** + * Disk thread + * + * Thread used to generate disk charts. + * + * @param ptr a pointer to `struct ebpf_module` + * + * @return It always return NULL + */ +void *ebpf_disk_thread(void *ptr) +{ + ebpf_module_t *em = (ebpf_module_t *)ptr; + + CLEANUP_FUNCTION_REGISTER(ebpf_disk_exit) cleanup_ptr = em; + + em->maps = disk_maps; + + if (ebpf_disk_enable_tracepoints()) { + goto enddisk; + } + + avl_init_lock(&disk_tree, ebpf_compare_disks); + if (read_local_disks()) { + goto enddisk; + } + + if (pthread_mutex_init(&plot_mutex, NULL)) { + netdata_log_error("Cannot initialize local mutex"); + goto enddisk; + } + +#ifdef LIBBPF_MAJOR_VERSION + ebpf_define_map_type(disk_maps, em->maps_per_core, running_on_kernel); + ebpf_adjust_thread_load(em, default_btf); +#endif + if (ebpf_disk_load_bpf(em)) { + goto enddisk; + } + + int algorithms[NETDATA_EBPF_HIST_MAX_BINS]; + ebpf_fill_algorithms(algorithms, NETDATA_EBPF_HIST_MAX_BINS, NETDATA_EBPF_INCREMENTAL_IDX); + dimensions = ebpf_fill_histogram_dimension(NETDATA_EBPF_HIST_MAX_BINS); + + ebpf_global_labels(disk_aggregated_data, disk_publish_aggregated, dimensions, dimensions, algorithms, + NETDATA_EBPF_HIST_MAX_BINS); + + pthread_mutex_lock(&lock); + ebpf_update_stats(&plugin_statistics, em); + ebpf_update_kernel_memory_with_vector(&plugin_statistics, disk_maps, EBPF_ACTION_STAT_ADD); + pthread_mutex_unlock(&lock); + + disk_collector(em); + +enddisk: + ebpf_update_disabled_plugin_stats(em); + + return NULL; +} diff --git a/src/collectors/ebpf.plugin/ebpf_disk.h b/src/collectors/ebpf.plugin/ebpf_disk.h new file mode 100644 index 000000000..f278b0139 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_disk.h @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_EBPF_DISK_H +#define NETDATA_EBPF_DISK_H 1 + +// Module name & description +#define NETDATA_EBPF_MODULE_NAME_DISK "disk" +#define NETDATA_EBPF_DISK_MODULE_DESC "Monitor disk latency independent of filesystem." + +#include "libnetdata/avl/avl.h" +#include "libnetdata/ebpf/ebpf.h" + +#define NETDATA_EBPF_PROC_PARTITIONS "/proc/partitions" + +// Process configuration name +#define NETDATA_DISK_CONFIG_FILE "disk.conf" + +// Decode function extracted from: https://elixir.bootlin.com/linux/v5.10.8/source/include/linux/kdev_t.h#L7 +#define MINORBITS 20 +#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) + +enum netdata_latency_disks_flags { + NETDATA_DISK_NONE = 0, + NETDATA_DISK_ADDED_TO_PLOT_LIST = 1, + NETDATA_DISK_CHART_CREATED = 2, + NETDATA_DISK_IS_HERE = 4, + NETDATA_DISK_HAS_EFI = 8 +}; + +/* + * The definition (DISK_NAME_LEN) has been a stable value since Kernel 3.0, + * I decided to bring it as internal definition, to avoid include linux/genhd.h. + */ +#define NETDATA_DISK_NAME_LEN 32 +typedef struct netdata_ebpf_disks { + // Search + avl_t avl; + uint32_t dev; + uint32_t major; + uint32_t minor; + uint32_t bootsector_key; + uint64_t start; // start sector + uint64_t end; // end sector + + // Print information + char family[NETDATA_DISK_NAME_LEN + 1]; + char *boot_chart; + + netdata_ebpf_histogram_t histogram; + + uint32_t flags; + time_t last_update; + + struct netdata_ebpf_disks *main; + struct netdata_ebpf_disks *boot_partition; + struct netdata_ebpf_disks *next; +} netdata_ebpf_disks_t; + +enum ebpf_disk_tables { NETDATA_DISK_IO }; + +typedef struct block_key { + uint32_t bin; + uint32_t dev; +} block_key_t; + +typedef struct netdata_ebpf_publish_disk { + netdata_ebpf_disks_t *plot; + struct netdata_ebpf_publish_disk *next; +} ebpf_publish_disk_t; + +#define NETDATA_EBPF_DISK_LATENCY_CONTEXT "disk.latency_io" + +extern struct config disk_config; + +void *ebpf_disk_thread(void *ptr); + +#endif /* NETDATA_EBPF_DISK_H */ + diff --git a/src/collectors/ebpf.plugin/ebpf_fd.c b/src/collectors/ebpf.plugin/ebpf_fd.c new file mode 100644 index 000000000..4025931f7 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_fd.c @@ -0,0 +1,1474 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ebpf.h" +#include "ebpf_fd.h" + +static char *fd_dimension_names[NETDATA_FD_SYSCALL_END] = { "open", "close" }; +static char *fd_id_names[NETDATA_FD_SYSCALL_END] = { "do_sys_open", "__close_fd" }; + +#ifdef LIBBPF_MAJOR_VERSION +static char *close_targets[NETDATA_EBPF_MAX_FD_TARGETS] = {"close_fd", "__close_fd"}; +static char *open_targets[NETDATA_EBPF_MAX_FD_TARGETS] = {"do_sys_openat2", "do_sys_open"}; +#endif + +static netdata_syscall_stat_t fd_aggregated_data[NETDATA_FD_SYSCALL_END]; +static netdata_publish_syscall_t fd_publish_aggregated[NETDATA_FD_SYSCALL_END]; + +static ebpf_local_maps_t fd_maps[] = {{.name = "tbl_fd_pid", .internal_input = ND_EBPF_DEFAULT_PID_SIZE, + .user_input = 0, + .type = NETDATA_EBPF_MAP_RESIZABLE | NETDATA_EBPF_MAP_PID, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_HASH +#endif + }, + {.name = "tbl_fd_global", .internal_input = NETDATA_KEY_END_VECTOR, + .user_input = 0, .type = NETDATA_EBPF_MAP_STATIC, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }, + {.name = "fd_ctrl", .internal_input = NETDATA_CONTROLLER_END, + .user_input = 0, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }, + {.name = NULL, .internal_input = 0, .user_input = 0, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }}; + + +struct config fd_config = { .first_section = NULL, .last_section = NULL, .mutex = NETDATA_MUTEX_INITIALIZER, + .index = {.avl_tree = { .root = NULL, .compar = appconfig_section_compare }, + .rwlock = AVL_LOCK_INITIALIZER } }; + +static netdata_idx_t fd_hash_values[NETDATA_FD_COUNTER]; +static netdata_idx_t *fd_values = NULL; + +netdata_fd_stat_t *fd_vector = NULL; + +netdata_ebpf_targets_t fd_targets[] = { {.name = "open", .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = "close", .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = NULL, .mode = EBPF_LOAD_TRAMPOLINE}}; + +struct netdata_static_thread ebpf_read_fd = { + .name = "EBPF_READ_FD", + .config_section = NULL, + .config_name = NULL, + .env_name = NULL, + .enabled = 1, + .thread = NULL, + .init_routine = NULL, + .start_routine = NULL +}; + +#ifdef LIBBPF_MAJOR_VERSION +/** + * Disable probe + * + * Disable all probes to use exclusively another method. + * + * @param obj is the main structure for bpf objects +*/ +static inline void ebpf_fd_disable_probes(struct fd_bpf *obj) +{ + bpf_program__set_autoload(obj->progs.netdata_sys_open_kprobe, false); + bpf_program__set_autoload(obj->progs.netdata_sys_open_kretprobe, false); + if (!strcmp(fd_targets[NETDATA_FD_SYSCALL_CLOSE].name, close_targets[NETDATA_FD_CLOSE_FD])) { + bpf_program__set_autoload(obj->progs.netdata___close_fd_kretprobe, false); + bpf_program__set_autoload(obj->progs.netdata___close_fd_kprobe, false); + bpf_program__set_autoload(obj->progs.netdata_close_fd_kprobe, false); + } else { + bpf_program__set_autoload(obj->progs.netdata___close_fd_kprobe, false); + bpf_program__set_autoload(obj->progs.netdata_close_fd_kretprobe, false); + bpf_program__set_autoload(obj->progs.netdata_close_fd_kprobe, false); + } +} + +/* + * Disable specific probe + * + * Disable probes according the kernel version + * + * @param obj is the main structure for bpf objects + */ +static inline void ebpf_disable_specific_probes(struct fd_bpf *obj) +{ + if (!strcmp(fd_targets[NETDATA_FD_SYSCALL_CLOSE].name, close_targets[NETDATA_FD_CLOSE_FD])) { + bpf_program__set_autoload(obj->progs.netdata___close_fd_kretprobe, false); + bpf_program__set_autoload(obj->progs.netdata___close_fd_kprobe, false); + } else { + bpf_program__set_autoload(obj->progs.netdata_close_fd_kretprobe, false); + bpf_program__set_autoload(obj->progs.netdata_close_fd_kprobe, false); + } +} + +/* + * Disable trampoline + * + * Disable all trampoline to use exclusively another method. + * + * @param obj is the main structure for bpf objects. + */ +static inline void ebpf_disable_trampoline(struct fd_bpf *obj) +{ + bpf_program__set_autoload(obj->progs.netdata_sys_open_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_sys_open_fexit, false); + bpf_program__set_autoload(obj->progs.netdata_close_fd_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_close_fd_fexit, false); + bpf_program__set_autoload(obj->progs.netdata___close_fd_fentry, false); + bpf_program__set_autoload(obj->progs.netdata___close_fd_fexit, false); +} + +/* + * Disable specific trampoline + * + * Disable trampoline according to kernel version. + * + * @param obj is the main structure for bpf objects. + */ +static inline void ebpf_disable_specific_trampoline(struct fd_bpf *obj) +{ + if (!strcmp(fd_targets[NETDATA_FD_SYSCALL_CLOSE].name, close_targets[NETDATA_FD_CLOSE_FD])) { + bpf_program__set_autoload(obj->progs.netdata___close_fd_fentry, false); + bpf_program__set_autoload(obj->progs.netdata___close_fd_fexit, false); + } else { + bpf_program__set_autoload(obj->progs.netdata_close_fd_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_close_fd_fexit, false); + } +} + +/** + * Set trampoline target + * + * Set the targets we will monitor. + * + * @param obj is the main structure for bpf objects. + */ +static void ebpf_set_trampoline_target(struct fd_bpf *obj) +{ + bpf_program__set_attach_target(obj->progs.netdata_sys_open_fentry, 0, fd_targets[NETDATA_FD_SYSCALL_OPEN].name); + bpf_program__set_attach_target(obj->progs.netdata_sys_open_fexit, 0, fd_targets[NETDATA_FD_SYSCALL_OPEN].name); + + if (!strcmp(fd_targets[NETDATA_FD_SYSCALL_CLOSE].name, close_targets[NETDATA_FD_CLOSE_FD])) { + bpf_program__set_attach_target( + obj->progs.netdata_close_fd_fentry, 0, fd_targets[NETDATA_FD_SYSCALL_CLOSE].name); + bpf_program__set_attach_target(obj->progs.netdata_close_fd_fexit, 0, fd_targets[NETDATA_FD_SYSCALL_CLOSE].name); + } else { + bpf_program__set_attach_target( + obj->progs.netdata___close_fd_fentry, 0, fd_targets[NETDATA_FD_SYSCALL_CLOSE].name); + bpf_program__set_attach_target( + obj->progs.netdata___close_fd_fexit, 0, fd_targets[NETDATA_FD_SYSCALL_CLOSE].name); + } +} + +/** + * Mount Attach Probe + * + * Attach probes to target + * + * @param obj is the main structure for bpf objects. + * + * @return It returns 0 on success and -1 otherwise. + */ +static int ebpf_fd_attach_probe(struct fd_bpf *obj) +{ + obj->links.netdata_sys_open_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_sys_open_kprobe, false, + fd_targets[NETDATA_FD_SYSCALL_OPEN].name); + long ret = libbpf_get_error(obj->links.netdata_sys_open_kprobe); + if (ret) + return -1; + + obj->links.netdata_sys_open_kretprobe = bpf_program__attach_kprobe(obj->progs.netdata_sys_open_kretprobe, true, + fd_targets[NETDATA_FD_SYSCALL_OPEN].name); + ret = libbpf_get_error(obj->links.netdata_sys_open_kretprobe); + if (ret) + return -1; + + if (!strcmp(fd_targets[NETDATA_FD_SYSCALL_CLOSE].name, close_targets[NETDATA_FD_CLOSE_FD])) { + obj->links.netdata_close_fd_kretprobe = bpf_program__attach_kprobe(obj->progs.netdata_close_fd_kretprobe, true, + fd_targets[NETDATA_FD_SYSCALL_CLOSE].name); + ret = libbpf_get_error(obj->links.netdata_close_fd_kretprobe); + if (ret) + return -1; + + obj->links.netdata_close_fd_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_close_fd_kprobe, false, + fd_targets[NETDATA_FD_SYSCALL_CLOSE].name); + ret = libbpf_get_error(obj->links.netdata_close_fd_kprobe); + if (ret) + return -1; + } else { + obj->links.netdata___close_fd_kretprobe = bpf_program__attach_kprobe(obj->progs.netdata___close_fd_kretprobe, + true, + fd_targets[NETDATA_FD_SYSCALL_CLOSE].name); + ret = libbpf_get_error(obj->links.netdata___close_fd_kretprobe); + if (ret) + return -1; + + obj->links.netdata___close_fd_kprobe = bpf_program__attach_kprobe(obj->progs.netdata___close_fd_kprobe, + false, + fd_targets[NETDATA_FD_SYSCALL_CLOSE].name); + ret = libbpf_get_error(obj->links.netdata___close_fd_kprobe); + if (ret) + return -1; + } + + return 0; +} + +/** + * FD Fill Address + * + * Fill address value used to load probes/trampoline. + */ +static inline void ebpf_fd_fill_address(ebpf_addresses_t *address, char **targets) +{ + int i; + for (i = 0; i < NETDATA_EBPF_MAX_FD_TARGETS; i++) { + address->function = targets[i]; + ebpf_load_addresses(address, -1); + if (address->addr) + break; + } +} + +/** + * Set target values + * + * Set pointers used to load data. + * + * @return It returns 0 on success and -1 otherwise. + */ +static int ebpf_fd_set_target_values() +{ + ebpf_addresses_t address = {.function = NULL, .hash = 0, .addr = 0}; + ebpf_fd_fill_address(&address, close_targets); + + if (!address.addr) + return -1; + + fd_targets[NETDATA_FD_SYSCALL_CLOSE].name = address.function; + + address.addr = 0; + ebpf_fd_fill_address(&address, open_targets); + + if (!address.addr) + return -1; + + fd_targets[NETDATA_FD_SYSCALL_OPEN].name = address.function; + + return 0; +} + +/** + * Set hash tables + * + * Set the values for maps according the value given by kernel. + * + * @param obj is the main structure for bpf objects. + */ +static void ebpf_fd_set_hash_tables(struct fd_bpf *obj) +{ + fd_maps[NETDATA_FD_GLOBAL_STATS].map_fd = bpf_map__fd(obj->maps.tbl_fd_global); + fd_maps[NETDATA_FD_PID_STATS].map_fd = bpf_map__fd(obj->maps.tbl_fd_pid); + fd_maps[NETDATA_FD_CONTROLLER].map_fd = bpf_map__fd(obj->maps.fd_ctrl); +} + +/** + * Adjust Map Size + * + * Resize maps according input from users. + * + * @param obj is the main structure for bpf objects. + * @param em structure with configuration + */ +static void ebpf_fd_adjust_map(struct fd_bpf *obj, ebpf_module_t *em) +{ + ebpf_update_map_size(obj->maps.tbl_fd_pid, &fd_maps[NETDATA_FD_PID_STATS], + em, bpf_map__name(obj->maps.tbl_fd_pid)); + + ebpf_update_map_type(obj->maps.tbl_fd_global, &fd_maps[NETDATA_FD_GLOBAL_STATS]); + ebpf_update_map_type(obj->maps.tbl_fd_pid, &fd_maps[NETDATA_FD_PID_STATS]); + ebpf_update_map_type(obj->maps.fd_ctrl, &fd_maps[NETDATA_FD_CONTROLLER]); +} + +/** + * Load and attach + * + * Load and attach the eBPF code in kernel. + * + * @param obj is the main structure for bpf objects. + * @param em structure with configuration + * + * @return it returns 0 on success and -1 otherwise + */ +static inline int ebpf_fd_load_and_attach(struct fd_bpf *obj, ebpf_module_t *em) +{ + netdata_ebpf_targets_t *mt = em->targets; + netdata_ebpf_program_loaded_t test = mt[NETDATA_FD_SYSCALL_OPEN].mode; + + if (ebpf_fd_set_target_values()) { + netdata_log_error("%s file descriptor.", NETDATA_EBPF_DEFAULT_FNT_NOT_FOUND); + return -1; + } + + if (test == EBPF_LOAD_TRAMPOLINE) { + ebpf_fd_disable_probes(obj); + ebpf_disable_specific_trampoline(obj); + + ebpf_set_trampoline_target(obj); + } else { + ebpf_disable_trampoline(obj); + ebpf_disable_specific_probes(obj); + } + + ebpf_fd_adjust_map(obj, em); + + int ret = fd_bpf__load(obj); + if (ret) { + return ret; + } + + ret = (test == EBPF_LOAD_TRAMPOLINE) ? fd_bpf__attach(obj) : ebpf_fd_attach_probe(obj); + if (!ret) { + ebpf_fd_set_hash_tables(obj); + + ebpf_update_controller(fd_maps[NETDATA_FD_CONTROLLER].map_fd, em); + } + + return ret; +} +#endif + +/***************************************************************** + * + * FUNCTIONS TO CLOSE THE THREAD + * + *****************************************************************/ + +static void ebpf_obsolete_specific_fd_charts(char *type, ebpf_module_t *em); + +/** + * Obsolete services + * + * Obsolete all service charts created + * + * @param em a pointer to `struct ebpf_module` + */ +static void ebpf_obsolete_fd_services(ebpf_module_t *em, char *id) +{ + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_SYSCALL_APPS_FILE_OPEN, + "Number of open files", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_FILE_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_CGROUP_FD_OPEN_CONTEXT, + 20270, + em->update_every); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_SYSCALL_APPS_FILE_OPEN_ERROR, + "Fails to open files", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_FILE_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_CGROUP_FD_OPEN_ERR_CONTEXT, + 20271, + em->update_every); + } + + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_SYSCALL_APPS_FILE_CLOSED, + "Files closed", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_FILE_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_CGROUP_FD_CLOSE_CONTEXT, + 20272, + em->update_every); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_SYSCALL_APPS_FILE_CLOSE_ERROR, + "Fails to close files", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_FILE_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_CGROUP_FD_CLOSE_ERR_CONTEXT, + 20273, + em->update_every); + } +} + +/** + * Obsolete cgroup chart + * + * Send obsolete for all charts created before to close. + * + * @param em a pointer to `struct ebpf_module` + */ +static inline void ebpf_obsolete_fd_cgroup_charts(ebpf_module_t *em) { + pthread_mutex_lock(&mutex_cgroup_shm); + + ebpf_cgroup_target_t *ect; + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + if (ect->systemd) { + ebpf_obsolete_fd_services(em, ect->name); + + continue; + } + + ebpf_obsolete_specific_fd_charts(ect->name, em); + } + pthread_mutex_unlock(&mutex_cgroup_shm); +} + +/** + * Obsolette apps charts + * + * Obsolete apps charts. + * + * @param em a pointer to the structure with the default values. + */ +void ebpf_obsolete_fd_apps_charts(struct ebpf_module *em) +{ + struct ebpf_target *w; + int update_every = em->update_every; + pthread_mutex_lock(&collect_data_mutex); + for (w = apps_groups_root_target; w; w = w->next) { + if (unlikely(!(w->charts_created & (1<<EBPF_MODULE_FD_IDX)))) + continue; + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_file_open", + "Number of open files", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_FILE_FDS, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_file_open", + 20220, + update_every); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_file_open_error", + "Fails to open files.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_FILE_FDS, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_file_open_error", + 20221, + update_every); + } + + ebpf_write_chart_obsolete(NETDATA_APPS_FAMILY, + w->clean_name, + "_ebpf_file_closed", + "Files closed.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_FILE_FDS, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_file_closed", + 20222, + update_every); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_obsolete(NETDATA_APPS_FAMILY, + w->clean_name, + "_ebpf_file_close_error", + "Fails to close files.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_FILE_FDS, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_fd_close_error", + 20223, + update_every); + } + w->charts_created &= ~(1<<EBPF_MODULE_FD_IDX); + } + pthread_mutex_unlock(&collect_data_mutex); +} + +/** + * Obsolete global + * + * Obsolete global charts created by thread. + * + * @param em a pointer to `struct ebpf_module` + */ +static void ebpf_obsolete_fd_global(ebpf_module_t *em) +{ + ebpf_write_chart_obsolete(NETDATA_FILESYSTEM_FAMILY, + NETDATA_FILE_OPEN_CLOSE_COUNT, + "", + "Open and close calls", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_FILE_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_FS_FILEDESCRIPTOR_CONTEXT, + NETDATA_CHART_PRIO_EBPF_FD_CHARTS, + em->update_every); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_obsolete(NETDATA_FILESYSTEM_FAMILY, + NETDATA_FILE_OPEN_ERR_COUNT, + "", + "Open fails", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_FILE_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_FS_FILEDESCRIPTOR_ERROR_CONTEXT, + NETDATA_CHART_PRIO_EBPF_FD_CHARTS + 1, + em->update_every); + } +} + +/** + * FD Exit + * + * Cancel child thread and exit. + * + * @param ptr thread data. + */ +static void ebpf_fd_exit(void *pptr) +{ + ebpf_module_t *em = CLEANUP_FUNCTION_GET_PTR(pptr); + if(!em) return; + + if (ebpf_read_fd.thread) + nd_thread_signal_cancel(ebpf_read_fd.thread); + + if (em->enabled == NETDATA_THREAD_EBPF_FUNCTION_RUNNING) { + pthread_mutex_lock(&lock); + if (em->cgroup_charts) { + ebpf_obsolete_fd_cgroup_charts(em); + fflush(stdout); + } + + if (em->apps_charts & NETDATA_EBPF_APPS_FLAG_CHART_CREATED) { + ebpf_obsolete_fd_apps_charts(em); + } + + ebpf_obsolete_fd_global(em); + + fflush(stdout); + pthread_mutex_unlock(&lock); + } + + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps, EBPF_ACTION_STAT_REMOVE); + +#ifdef LIBBPF_MAJOR_VERSION + if (fd_bpf_obj) { + fd_bpf__destroy(fd_bpf_obj); + fd_bpf_obj = NULL; + } +#endif + if (em->objects) { + ebpf_unload_legacy_code(em->objects, em->probe_links); + em->objects = NULL; + em->probe_links = NULL; + } + + pthread_mutex_lock(&ebpf_exit_cleanup); + em->enabled = NETDATA_THREAD_EBPF_STOPPED; + ebpf_update_stats(&plugin_statistics, em); + pthread_mutex_unlock(&ebpf_exit_cleanup); +} + +/***************************************************************** + * + * MAIN LOOP + * + *****************************************************************/ + +/** + * Send data to Netdata calling auxiliary functions. + * + * @param em the structure with thread information + */ +static void ebpf_fd_send_data(ebpf_module_t *em) +{ + fd_publish_aggregated[NETDATA_FD_SYSCALL_OPEN].ncall = fd_hash_values[NETDATA_KEY_CALLS_DO_SYS_OPEN]; + fd_publish_aggregated[NETDATA_FD_SYSCALL_OPEN].nerr = fd_hash_values[NETDATA_KEY_ERROR_DO_SYS_OPEN]; + + fd_publish_aggregated[NETDATA_FD_SYSCALL_CLOSE].ncall = fd_hash_values[NETDATA_KEY_CALLS_CLOSE_FD]; + fd_publish_aggregated[NETDATA_FD_SYSCALL_CLOSE].nerr = fd_hash_values[NETDATA_KEY_ERROR_CLOSE_FD]; + + write_count_chart(NETDATA_FILE_OPEN_CLOSE_COUNT, NETDATA_FILESYSTEM_FAMILY, fd_publish_aggregated, + NETDATA_FD_SYSCALL_END); + + if (em->mode < MODE_ENTRY) { + write_err_chart(NETDATA_FILE_OPEN_ERR_COUNT, NETDATA_FILESYSTEM_FAMILY, + fd_publish_aggregated, NETDATA_FD_SYSCALL_END); + } +} + +/** + * Read global counter + * + * Read the table with number of calls for all functions + * + * @param stats vector used to read data from control table. + * @param maps_per_core do I need to read all cores? + */ +static void ebpf_fd_read_global_tables(netdata_idx_t *stats, int maps_per_core) +{ + ebpf_read_global_table_stats(fd_hash_values, + fd_values, + fd_maps[NETDATA_FD_GLOBAL_STATS].map_fd, + maps_per_core, + NETDATA_KEY_CALLS_DO_SYS_OPEN, + NETDATA_FD_COUNTER); + + ebpf_read_global_table_stats(stats, + fd_values, + fd_maps[NETDATA_FD_CONTROLLER].map_fd, + maps_per_core, + NETDATA_CONTROLLER_PID_TABLE_ADD, + NETDATA_CONTROLLER_END); +} + +/** + * Apps Accumulator + * + * Sum all values read from kernel and store in the first address. + * + * @param out the vector with read values. + * @param maps_per_core do I need to read all cores? + */ +static void fd_apps_accumulator(netdata_fd_stat_t *out, int maps_per_core) +{ + int i, end = (maps_per_core) ? ebpf_nprocs : 1; + netdata_fd_stat_t *total = &out[0]; + for (i = 1; i < end; i++) { + netdata_fd_stat_t *w = &out[i]; + total->open_call += w->open_call; + total->close_call += w->close_call; + total->open_err += w->open_err; + total->close_err += w->close_err; + } +} + +/** + * Read APPS table + * + * Read the apps table and store data inside the structure. + * + * @param maps_per_core do I need to read all cores? + */ +static void ebpf_read_fd_apps_table(int maps_per_core, int max_period) +{ + netdata_fd_stat_t *fv = fd_vector; + int fd = fd_maps[NETDATA_FD_PID_STATS].map_fd; + size_t length = sizeof(netdata_fd_stat_t); + if (maps_per_core) + length *= ebpf_nprocs; + + uint32_t key = 0, next_key = 0; + while (bpf_map_get_next_key(fd, &key, &next_key) == 0) { + if (bpf_map_lookup_elem(fd, &key, fv)) { + goto end_fd_loop; + } + + fd_apps_accumulator(fv, maps_per_core); + + ebpf_pid_stat_t *pid_stat = ebpf_get_pid_entry(key, fv->tgid); + if (pid_stat) { + netdata_fd_stat_t *publish_fd = &pid_stat->fd; + if (!publish_fd->ct || publish_fd->ct != fv->ct) { + memcpy(publish_fd, &fv[0], sizeof(netdata_fd_stat_t)); + pid_stat->not_updated = 0; + } else if (++pid_stat->not_updated >= max_period) { + bpf_map_delete_elem(fd, &key); + pid_stat->not_updated = 0; + } + } + +end_fd_loop: + // We are cleaning to avoid passing data read from one process to other. + memset(fv, 0, length); + key = next_key; + } +} + +/** + * Sum PIDs + * + * Sum values for all targets. + * + * @param fd the output + * @param root list of pids + */ +static void ebpf_fd_sum_pids(netdata_fd_stat_t *fd, struct ebpf_pid_on_target *root) +{ + memset(fd, 0, sizeof(netdata_fd_stat_t)); + + while (root) { + int32_t pid = root->pid; + ebpf_pid_stat_t *pid_stat = ebpf_get_pid_entry(pid, 0); + if (pid_stat) { + netdata_fd_stat_t *w = &pid_stat->fd; + fd->open_call += w->open_call; + fd->close_call += w->close_call; + fd->open_err += w->open_err; + fd->close_err += w->close_err; + } + + root = root->next; + } +} + +/** + * Resume apps data + */ +void ebpf_fd_resume_apps_data() +{ + struct ebpf_target *w; + + for (w = apps_groups_root_target; w; w = w->next) { + if (unlikely(!(w->charts_created & (1<<EBPF_MODULE_FD_IDX)))) + continue; + + ebpf_fd_sum_pids(&w->fd, w->root_pid); + } +} + +/** + * DCstat thread + * + * Thread used to generate dcstat charts. + * + * @param ptr a pointer to `struct ebpf_module` + * + * @return It always return NULL + */ +void *ebpf_read_fd_thread(void *ptr) +{ + heartbeat_t hb; + heartbeat_init(&hb); + + ebpf_module_t *em = (ebpf_module_t *)ptr; + + int maps_per_core = em->maps_per_core; + int update_every = em->update_every; + + int counter = update_every - 1; + + uint32_t lifetime = em->lifetime; + uint32_t running_time = 0; + usec_t period = update_every * USEC_PER_SEC; + int max_period = update_every * EBPF_CLEANUP_FACTOR; + while (!ebpf_plugin_stop() && running_time < lifetime) { + (void)heartbeat_next(&hb, period); + if (ebpf_plugin_stop() || ++counter != update_every) + continue; + + pthread_mutex_lock(&collect_data_mutex); + ebpf_read_fd_apps_table(maps_per_core, max_period); + ebpf_fd_resume_apps_data(); + pthread_mutex_unlock(&collect_data_mutex); + + counter = 0; + + pthread_mutex_lock(&ebpf_exit_cleanup); + if (running_time && !em->running_time) + running_time = update_every; + else + running_time += update_every; + + em->running_time = running_time; + pthread_mutex_unlock(&ebpf_exit_cleanup); + } + + return NULL; +} + +/** + * Update cgroup + * + * Update cgroup data collected per PID. + * + * @param maps_per_core do I need to read all cores? + */ +static void ebpf_update_fd_cgroup() +{ + ebpf_cgroup_target_t *ect ; + + pthread_mutex_lock(&mutex_cgroup_shm); + for (ect = ebpf_cgroup_pids; ect; ect = ect->next) { + struct pid_on_target2 *pids; + for (pids = ect->pids; pids; pids = pids->next) { + int pid = pids->pid; + netdata_fd_stat_t *out = &pids->fd; + ebpf_pid_stat_t *local_pid = ebpf_get_pid_entry(pid, 0); + if (local_pid) { + netdata_fd_stat_t *in = &local_pid->fd; + + memcpy(out, in, sizeof(netdata_fd_stat_t)); + } + } + } + pthread_mutex_unlock(&mutex_cgroup_shm); +} + +/** + * Send data to Netdata calling auxiliary functions. + * + * @param em the structure with thread information + * @param root the target list. +*/ +void ebpf_fd_send_apps_data(ebpf_module_t *em, struct ebpf_target *root) +{ + struct ebpf_target *w; + pthread_mutex_lock(&collect_data_mutex); + for (w = root; w; w = w->next) { + if (unlikely(!(w->charts_created & (1<<EBPF_MODULE_FD_IDX)))) + continue; + + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_file_open"); + write_chart_dimension("calls", w->fd.open_call); + ebpf_write_end_chart(); + + if (em->mode < MODE_ENTRY) { + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_file_open_error"); + write_chart_dimension("calls", w->fd.open_err); + ebpf_write_end_chart(); + } + + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_file_closed"); + write_chart_dimension("calls", w->fd.close_call); + ebpf_write_end_chart(); + + if (em->mode < MODE_ENTRY) { + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_file_close_error"); + write_chart_dimension("calls", w->fd.close_err); + ebpf_write_end_chart(); + } + } + pthread_mutex_unlock(&collect_data_mutex); +} + +/** + * Sum PIDs + * + * Sum values for all targets. + * + * @param fd structure used to store data + * @param pids input data + */ +static void ebpf_fd_sum_cgroup_pids(netdata_fd_stat_t *fd, struct pid_on_target2 *pids) +{ + netdata_fd_stat_t accumulator; + memset(&accumulator, 0, sizeof(accumulator)); + + while (pids) { + netdata_fd_stat_t *w = &pids->fd; + + accumulator.open_err += w->open_err; + accumulator.open_call += w->open_call; + accumulator.close_call += w->close_call; + accumulator.close_err += w->close_err; + + pids = pids->next; + } + + fd->open_call = (accumulator.open_call >= fd->open_call) ? accumulator.open_call : fd->open_call; + fd->open_err = (accumulator.open_err >= fd->open_err) ? accumulator.open_err : fd->open_err; + fd->close_call = (accumulator.close_call >= fd->close_call) ? accumulator.close_call : fd->close_call; + fd->close_err = (accumulator.close_err >= fd->close_err) ? accumulator.close_err : fd->close_err; +} + +/** + * Create specific file descriptor charts + * + * Create charts for cgroup/application. + * + * @param type the chart type. + * @param em the main thread structure. + */ +static void ebpf_create_specific_fd_charts(char *type, ebpf_module_t *em) +{ + char *label = (!strncmp(type, "cgroup_", 7)) ? &type[7] : type; + ebpf_create_chart(type, NETDATA_SYSCALL_APPS_FILE_OPEN, "Number of open files", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_APPS_FILE_GROUP, + NETDATA_CGROUP_FD_OPEN_CONTEXT, NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5400, + ebpf_create_global_dimension, + &fd_publish_aggregated[NETDATA_FD_SYSCALL_OPEN], + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_FD); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + + if (em->mode < MODE_ENTRY) { + ebpf_create_chart(type, NETDATA_SYSCALL_APPS_FILE_OPEN_ERROR, "Fails to open files", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_APPS_FILE_GROUP, + NETDATA_CGROUP_FD_OPEN_ERR_CONTEXT, NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5401, + ebpf_create_global_dimension, + &fd_publish_aggregated[NETDATA_FD_SYSCALL_OPEN], + 1, em->update_every, + NETDATA_EBPF_MODULE_NAME_FD); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + } + + ebpf_create_chart(type, NETDATA_SYSCALL_APPS_FILE_CLOSED, "Files closed", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_APPS_FILE_GROUP, + NETDATA_CGROUP_FD_CLOSE_CONTEXT, NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5402, + ebpf_create_global_dimension, + &fd_publish_aggregated[NETDATA_FD_SYSCALL_CLOSE], + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_FD); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + + if (em->mode < MODE_ENTRY) { + ebpf_create_chart(type, NETDATA_SYSCALL_APPS_FILE_CLOSE_ERROR, "Fails to close files", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_APPS_FILE_GROUP, + NETDATA_CGROUP_FD_CLOSE_ERR_CONTEXT, NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5403, + ebpf_create_global_dimension, + &fd_publish_aggregated[NETDATA_FD_SYSCALL_CLOSE], + 1, em->update_every, + NETDATA_EBPF_MODULE_NAME_FD); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + } +} + +/** + * Obsolete specific file descriptor charts + * + * Obsolete charts for cgroup/application. + * + * @param type the chart type. + * @param em the main thread structure. + */ +static void ebpf_obsolete_specific_fd_charts(char *type, ebpf_module_t *em) +{ + ebpf_write_chart_obsolete(type, NETDATA_SYSCALL_APPS_FILE_OPEN, "", "Number of open files", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_APPS_FILE_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_FD_OPEN_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5400, em->update_every); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_obsolete(type, NETDATA_SYSCALL_APPS_FILE_OPEN_ERROR, "", "Fails to open files", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_APPS_FILE_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_FD_OPEN_ERR_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5401, em->update_every); + } + + ebpf_write_chart_obsolete(type, NETDATA_SYSCALL_APPS_FILE_CLOSED, "", "Files closed", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_APPS_FILE_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_FD_CLOSE_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5402, em->update_every); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_obsolete(type, NETDATA_SYSCALL_APPS_FILE_CLOSE_ERROR, "", "Fails to close files", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_APPS_FILE_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_FD_CLOSE_ERR_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5403, em->update_every); + } +} + +/* + * Send specific file descriptor data + * + * Send data for specific cgroup/apps. + * + * @param type chart type + * @param values structure with values that will be sent to netdata + */ +static void ebpf_send_specific_fd_data(char *type, netdata_fd_stat_t *values, ebpf_module_t *em) +{ + ebpf_write_begin_chart(type, NETDATA_SYSCALL_APPS_FILE_OPEN, ""); + write_chart_dimension(fd_publish_aggregated[NETDATA_FD_SYSCALL_OPEN].name, (long long)values->open_call); + ebpf_write_end_chart(); + + if (em->mode < MODE_ENTRY) { + ebpf_write_begin_chart(type, NETDATA_SYSCALL_APPS_FILE_OPEN_ERROR, ""); + write_chart_dimension(fd_publish_aggregated[NETDATA_FD_SYSCALL_OPEN].name, (long long)values->open_err); + ebpf_write_end_chart(); + } + + ebpf_write_begin_chart(type, NETDATA_SYSCALL_APPS_FILE_CLOSED, ""); + write_chart_dimension(fd_publish_aggregated[NETDATA_FD_SYSCALL_CLOSE].name, (long long)values->close_call); + ebpf_write_end_chart(); + + if (em->mode < MODE_ENTRY) { + ebpf_write_begin_chart(type, NETDATA_SYSCALL_APPS_FILE_CLOSE_ERROR, ""); + write_chart_dimension(fd_publish_aggregated[NETDATA_FD_SYSCALL_CLOSE].name, (long long)values->close_err); + ebpf_write_end_chart(); + } +} + +/** + * Create systemd file descriptor charts + * + * Create charts when systemd is enabled + * + * @param em the main collector structure + **/ +static void ebpf_create_systemd_fd_charts(ebpf_module_t *em) +{ + static ebpf_systemd_args_t data_open = { + .title = "Number of open files", + .units = EBPF_COMMON_UNITS_CALLS_PER_SEC, + .family = NETDATA_APPS_FILE_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20270, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SYSTEMD_FD_OPEN_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_FD, + .update_every = 0, + .suffix = NETDATA_SYSCALL_APPS_FILE_OPEN, + .dimension = "calls" + }; + + static ebpf_systemd_args_t data_open_error = { + .title = "Fails to open files", + .units = EBPF_COMMON_UNITS_CALLS_PER_SEC, + .family = NETDATA_APPS_FILE_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20271, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SYSTEMD_FD_OPEN_ERR_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_FD, + .update_every = 0, + .suffix = NETDATA_SYSCALL_APPS_FILE_OPEN_ERROR, + .dimension = "calls" + }; + + static ebpf_systemd_args_t data_close = { + .title = "Files closed", + .units = EBPF_COMMON_UNITS_CALLS_PER_SEC, + .family = NETDATA_APPS_FILE_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20272, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SYSTEMD_FD_CLOSE_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_FD, + .update_every = 0, + .suffix = NETDATA_SYSCALL_APPS_FILE_CLOSED, + .dimension = "calls" + }; + + static ebpf_systemd_args_t data_close_error = { + .title = "Fails to close files", + .units = EBPF_COMMON_UNITS_CALLS_PER_SEC, + .family = NETDATA_APPS_FILE_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20273, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SYSTEMD_FD_OPEN_ERR_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_FD, + .update_every = 0, + .suffix = NETDATA_SYSCALL_APPS_FILE_CLOSE_ERROR, + .dimension = "calls" + }; + + if (!data_open.update_every) + data_open.update_every = data_open_error.update_every = + data_close.update_every = data_close_error.update_every = em->update_every; + + ebpf_cgroup_target_t *w; + netdata_run_mode_t mode = em->mode; + for (w = ebpf_cgroup_pids; w; w = w->next) { + if (unlikely(!w->systemd || w->flags & NETDATA_EBPF_SERVICES_HAS_FD_CHART)) + continue; + + data_open.id = data_open_error.id = data_close.id = data_close_error.id = w->name; + ebpf_create_charts_on_systemd(&data_open); + ebpf_create_charts_on_systemd(&data_close); + if (mode < MODE_ENTRY) { + ebpf_create_charts_on_systemd(&data_open_error); + ebpf_create_charts_on_systemd(&data_close_error); + } + + w->flags |= NETDATA_EBPF_SERVICES_HAS_FD_CHART; + } +} + +/** + * Send Systemd charts + * + * Send collected data to Netdata. + * + * @param em the main collector structure + */ +static void ebpf_send_systemd_fd_charts(ebpf_module_t *em) +{ + ebpf_cgroup_target_t *ect; + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + if (unlikely(!(ect->flags & NETDATA_EBPF_SERVICES_HAS_FD_CHART)) ) { + continue; + } + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_SYSCALL_APPS_FILE_OPEN); + write_chart_dimension("calls", ect->publish_systemd_fd.open_call); + ebpf_write_end_chart(); + + if (em->mode < MODE_ENTRY) { + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_SYSCALL_APPS_FILE_OPEN_ERROR); + write_chart_dimension("calls", ect->publish_systemd_fd.open_err); + ebpf_write_end_chart(); + } + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_SYSCALL_APPS_FILE_CLOSED); + write_chart_dimension("calls", ect->publish_systemd_fd.close_call); + ebpf_write_end_chart(); + + if (em->mode < MODE_ENTRY) { + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_SYSCALL_APPS_FILE_CLOSE_ERROR); + write_chart_dimension("calls", ect->publish_systemd_fd.close_err); + ebpf_write_end_chart(); + } + } +} + +/** + * Send data to Netdata calling auxiliary functions. + * + * @param em the main collector structure +*/ +static void ebpf_fd_send_cgroup_data(ebpf_module_t *em) +{ + pthread_mutex_lock(&mutex_cgroup_shm); + ebpf_cgroup_target_t *ect; + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + ebpf_fd_sum_cgroup_pids(&ect->publish_systemd_fd, ect->pids); + } + + if (shm_ebpf_cgroup.header->systemd_enabled) { + if (send_cgroup_chart) { + ebpf_create_systemd_fd_charts(em); + } + + ebpf_send_systemd_fd_charts(em); + } + + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + if (ect->systemd) + continue; + + if (!(ect->flags & NETDATA_EBPF_CGROUP_HAS_FD_CHART) && ect->updated) { + ebpf_create_specific_fd_charts(ect->name, em); + ect->flags |= NETDATA_EBPF_CGROUP_HAS_FD_CHART; + } + + if (ect->flags & NETDATA_EBPF_CGROUP_HAS_FD_CHART ) { + if (ect->updated) { + ebpf_send_specific_fd_data(ect->name, &ect->publish_systemd_fd, em); + } else { + ebpf_obsolete_specific_fd_charts(ect->name, em); + ect->flags &= ~NETDATA_EBPF_CGROUP_HAS_FD_CHART; + } + } + } + + pthread_mutex_unlock(&mutex_cgroup_shm); +} + +/** +* Main loop for this collector. +*/ +static void fd_collector(ebpf_module_t *em) +{ + int cgroups = em->cgroup_charts; + heartbeat_t hb; + heartbeat_init(&hb); + int update_every = em->update_every; + int counter = update_every - 1; + int maps_per_core = em->maps_per_core; + uint32_t running_time = 0; + uint32_t lifetime = em->lifetime; + netdata_idx_t *stats = em->hash_table_stats; + memset(stats, 0, sizeof(em->hash_table_stats)); + while (!ebpf_plugin_stop() && running_time < lifetime) { + (void)heartbeat_next(&hb, USEC_PER_SEC); + + if (ebpf_plugin_stop() || ++counter != update_every) + continue; + + counter = 0; + netdata_apps_integration_flags_t apps = em->apps_charts; + ebpf_fd_read_global_tables(stats, maps_per_core); + + if (cgroups && shm_ebpf_cgroup.header) + ebpf_update_fd_cgroup(); + + pthread_mutex_lock(&lock); + + ebpf_fd_send_data(em); + + if (apps & NETDATA_EBPF_APPS_FLAG_CHART_CREATED) + ebpf_fd_send_apps_data(em, apps_groups_root_target); + + if (cgroups && shm_ebpf_cgroup.header) + ebpf_fd_send_cgroup_data(em); + + pthread_mutex_unlock(&lock); + + pthread_mutex_lock(&ebpf_exit_cleanup); + if (running_time && !em->running_time) + running_time = update_every; + else + running_time += update_every; + + em->running_time = running_time; + pthread_mutex_unlock(&ebpf_exit_cleanup); + } +} + +/***************************************************************** + * + * CREATE CHARTS + * + *****************************************************************/ + +/** + * Create apps charts + * + * Call ebpf_create_chart to create the charts on apps submenu. + * + * @param em a pointer to the structure with the default values. + */ +void ebpf_fd_create_apps_charts(struct ebpf_module *em, void *ptr) +{ + struct ebpf_target *root = ptr; + struct ebpf_target *w; + int update_every = em->update_every; + for (w = root; w; w = w->next) { + if (unlikely(!w->exposed)) + continue; + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_file_open", + "Number of open files", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_FILE_FDS, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_file_open", + 20220, + update_every, + NETDATA_EBPF_MODULE_NAME_FD); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION calls '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_file_open_error", + "Fails to open files.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_FILE_FDS, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_file_open_error", + 20221, + update_every, + NETDATA_EBPF_MODULE_NAME_FD); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION calls '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + } + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_file_closed", + "Files closed.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_FILE_FDS, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_file_closed", + 20222, + update_every, + NETDATA_EBPF_MODULE_NAME_FD); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION calls '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_file_close_error", + "Fails to close files.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_FILE_FDS, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_file_close_error", + 20223, + update_every, + NETDATA_EBPF_MODULE_NAME_FD); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION calls '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + } + + w->charts_created |= 1<<EBPF_MODULE_FD_IDX; + } + + em->apps_charts |= NETDATA_EBPF_APPS_FLAG_CHART_CREATED; +} + +/** + * 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_fd_global_charts(ebpf_module_t *em) +{ + ebpf_create_chart(NETDATA_FILESYSTEM_FAMILY, + NETDATA_FILE_OPEN_CLOSE_COUNT, + "Open and close calls", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_FILE_GROUP, + NETDATA_FS_FILEDESCRIPTOR_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_EBPF_FD_CHARTS, + ebpf_create_global_dimension, + fd_publish_aggregated, + NETDATA_FD_SYSCALL_END, + em->update_every, NETDATA_EBPF_MODULE_NAME_FD); + + if (em->mode < MODE_ENTRY) { + ebpf_create_chart(NETDATA_FILESYSTEM_FAMILY, + NETDATA_FILE_OPEN_ERR_COUNT, + "Open fails", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_FILE_GROUP, + NETDATA_FS_FILEDESCRIPTOR_ERROR_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_EBPF_FD_CHARTS + 1, + ebpf_create_global_dimension, + fd_publish_aggregated, + NETDATA_FD_SYSCALL_END, + em->update_every, NETDATA_EBPF_MODULE_NAME_FD); + } + + fflush(stdout); +} + +/***************************************************************** + * + * MAIN 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. + */ +static inline void ebpf_fd_allocate_global_vectors() +{ + fd_vector = callocz((size_t)ebpf_nprocs, sizeof(netdata_fd_stat_t)); + fd_values = callocz((size_t)ebpf_nprocs, sizeof(netdata_idx_t)); +} + +/* + * Load BPF + * + * Load BPF files. + * + * @param em the structure with configuration + */ +static int ebpf_fd_load_bpf(ebpf_module_t *em) +{ +#ifdef LIBBPF_MAJOR_VERSION + ebpf_define_map_type(fd_maps, em->maps_per_core, running_on_kernel); +#endif + + int ret = 0; + ebpf_adjust_apps_cgroup(em, em->targets[NETDATA_FD_SYSCALL_OPEN].mode); + if (em->load & EBPF_LOAD_LEGACY) { + em->probe_links = ebpf_load_program(ebpf_plugin_dir, em, running_on_kernel, isrh, &em->objects); + if (!em->probe_links) { + ret = -1; + } + } +#ifdef LIBBPF_MAJOR_VERSION + else { + fd_bpf_obj = fd_bpf__open(); + if (!fd_bpf_obj) + ret = -1; + else + ret = ebpf_fd_load_and_attach(fd_bpf_obj, em); + } +#endif + + if (ret) + netdata_log_error("%s %s", EBPF_DEFAULT_ERROR_MSG, em->info.thread_name); + + return ret; +} + +/** + * Directory Cache thread + * + * Thread used to make dcstat thread + * + * @param ptr a pointer to `struct ebpf_module` + * + * @return It always returns NULL + */ +void *ebpf_fd_thread(void *ptr) +{ + ebpf_module_t *em = (ebpf_module_t *)ptr; + + CLEANUP_FUNCTION_REGISTER(ebpf_fd_exit) cleanup_ptr = em; + + em->maps = fd_maps; + +#ifdef LIBBPF_MAJOR_VERSION + ebpf_adjust_thread_load(em, default_btf); +#endif + if (ebpf_fd_load_bpf(em)) { + goto endfd; + } + + ebpf_fd_allocate_global_vectors(); + + int algorithms[NETDATA_FD_SYSCALL_END] = { + NETDATA_EBPF_INCREMENTAL_IDX, NETDATA_EBPF_INCREMENTAL_IDX + }; + + ebpf_global_labels(fd_aggregated_data, fd_publish_aggregated, fd_dimension_names, fd_id_names, + algorithms, NETDATA_FD_SYSCALL_END); + + pthread_mutex_lock(&lock); + ebpf_create_fd_global_charts(em); + ebpf_update_stats(&plugin_statistics, em); + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps, EBPF_ACTION_STAT_ADD); + + pthread_mutex_unlock(&lock); + + ebpf_read_fd.thread = nd_thread_create(ebpf_read_fd.name, NETDATA_THREAD_OPTION_DEFAULT, ebpf_read_fd_thread, em); + + fd_collector(em); + +endfd: + ebpf_update_disabled_plugin_stats(em); + + return NULL; +} diff --git a/src/collectors/ebpf.plugin/ebpf_fd.h b/src/collectors/ebpf.plugin/ebpf_fd.h new file mode 100644 index 000000000..d4975940e --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_fd.h @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_EBPF_FD_H +#define NETDATA_EBPF_FD_H 1 + +// Module name & File description +#define NETDATA_EBPF_MODULE_NAME_FD "filedescriptor" +#define NETDATA_EBPF_FD_MODULE_DESC "Monitor when files are open and closed. This thread is integrated with apps and cgroup." + +// Menu group +#define NETDATA_FILE_GROUP "file_access" + +// Global chart name +#define NETDATA_FILE_OPEN_CLOSE_COUNT "file_descriptor" +#define NETDATA_FILE_OPEN_ERR_COUNT "file_error" + +// Charts created on Apps submenu +#define NETDATA_SYSCALL_APPS_FILE_OPEN "file_open" +#define NETDATA_SYSCALL_APPS_FILE_CLOSED "file_closed" +#define NETDATA_SYSCALL_APPS_FILE_OPEN_ERROR "file_open_error" +#define NETDATA_SYSCALL_APPS_FILE_CLOSE_ERROR "file_close_error" + +// Process configuration name +#define NETDATA_FD_CONFIG_FILE "fd.conf" + +// Contexts +#define NETDATA_FS_FILEDESCRIPTOR_CONTEXT "filesystem.file_descriptor" +#define NETDATA_FS_FILEDESCRIPTOR_ERROR_CONTEXT "filesystem.file_error" + +#define NETDATA_CGROUP_FD_OPEN_CONTEXT "cgroup.fd_open" +#define NETDATA_CGROUP_FD_OPEN_ERR_CONTEXT "cgroup.fd_open_error" +#define NETDATA_CGROUP_FD_CLOSE_CONTEXT "cgroup.fd_close" +#define NETDATA_CGROUP_FD_CLOSE_ERR_CONTEXT "cgroup.fd_close_error" + +#define NETDATA_SYSTEMD_FD_OPEN_CONTEXT "systemd.services.fd_open" +#define NETDATA_SYSTEMD_FD_OPEN_ERR_CONTEXT "systemd.services.fd_open_error" +#define NETDATA_SYSTEMD_FD_CLOSE_CONTEXT "systemd.services.fd_close" +#define NETDATA_SYSTEMD_FD_CLOSE_ERR_CONTEXT "systemd.services.fd_close_error" + +// ARAL name +#define NETDATA_EBPF_FD_ARAL_NAME "ebpf_fd" + +typedef struct netdata_fd_stat { + uint64_t ct; + uint32_t tgid; + uint32_t uid; + uint32_t gid; + char name[TASK_COMM_LEN]; + + uint32_t open_call; // Open syscalls (open and openat) + uint32_t close_call; // Close syscall (close) + + // Errors + uint32_t open_err; + uint32_t close_err; +} netdata_fd_stat_t; + +enum fd_tables { + NETDATA_FD_PID_STATS, + NETDATA_FD_GLOBAL_STATS, + + // Keep this as last and don't skip numbers as it is used as element counter + NETDATA_FD_CONTROLLER +}; + +enum fd_counters { + NETDATA_KEY_CALLS_DO_SYS_OPEN, + NETDATA_KEY_ERROR_DO_SYS_OPEN, + + NETDATA_KEY_CALLS_CLOSE_FD, + NETDATA_KEY_ERROR_CLOSE_FD, + + // Keep this as last and don't skip numbers as it is used as element counter + NETDATA_FD_COUNTER +}; + +enum fd_syscalls { + NETDATA_FD_SYSCALL_OPEN, + NETDATA_FD_SYSCALL_CLOSE, + + // Do not insert nothing after this value + NETDATA_FD_SYSCALL_END +}; + +enum fd_close_syscall { + NETDATA_FD_CLOSE_FD, + NETDATA_FD___CLOSE_FD, + + NETDATA_FD_CLOSE_END +}; + +#define NETDATA_EBPF_MAX_FD_TARGETS 2 + +void *ebpf_fd_thread(void *ptr); +void ebpf_fd_create_apps_charts(struct ebpf_module *em, void *ptr); +void ebpf_fd_release(netdata_fd_stat_t *stat); +extern struct config fd_config; +extern netdata_ebpf_targets_t fd_targets[]; + +#endif /* NETDATA_EBPF_FD_H */ + diff --git a/src/collectors/ebpf.plugin/ebpf_filesystem.c b/src/collectors/ebpf.plugin/ebpf_filesystem.c new file mode 100644 index 000000000..c56dea4b1 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_filesystem.c @@ -0,0 +1,1053 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ebpf_filesystem.h" + +struct config fs_config = { .first_section = NULL, + .last_section = NULL, + .mutex = NETDATA_MUTEX_INITIALIZER, + .index = { .avl_tree = { .root = NULL, .compar = appconfig_section_compare }, + .rwlock = AVL_LOCK_INITIALIZER } }; + +ebpf_local_maps_t ext4_maps[] = {{.name = "tbl_ext4", .internal_input = NETDATA_KEY_CALLS_SYNC, + .user_input = 0, .type = NETDATA_EBPF_MAP_STATIC, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }, + {.name = "tmp_ext4", .internal_input = 4192, .user_input = 4192, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_HASH +#endif + }, + {.name = NULL, .internal_input = 0, .user_input = 0, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_HASH +#endif + }}; + +ebpf_local_maps_t xfs_maps[] = {{.name = "tbl_xfs", .internal_input = NETDATA_KEY_CALLS_SYNC, + .user_input = 0, .type = NETDATA_EBPF_MAP_STATIC, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }, + {.name = "tmp_xfs", .internal_input = 4192, .user_input = 4192, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_HASH +#endif + }, + {.name = NULL, .internal_input = 0, .user_input = 0, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_HASH +#endif + }}; + +ebpf_local_maps_t nfs_maps[] = {{.name = "tbl_nfs", .internal_input = NETDATA_KEY_CALLS_SYNC, + .user_input = 0, .type = NETDATA_EBPF_MAP_STATIC, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }, + {.name = "tmp_nfs", .internal_input = 4192, .user_input = 4192, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_HASH +#endif + }, + {.name = NULL, .internal_input = 0, .user_input = 0, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_HASH +#endif + }}; + +ebpf_local_maps_t zfs_maps[] = {{.name = "tbl_zfs", .internal_input = NETDATA_KEY_CALLS_SYNC, + .user_input = 0, .type = NETDATA_EBPF_MAP_STATIC, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }, + {.name = "tmp_zfs", .internal_input = 4192, .user_input = 4192, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_HASH +#endif + }, + {.name = NULL, .internal_input = 0, .user_input = 0, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_HASH +#endif + }}; + +ebpf_local_maps_t btrfs_maps[] = {{.name = "tbl_btrfs", .internal_input = NETDATA_KEY_CALLS_SYNC, + .user_input = 0, .type = NETDATA_EBPF_MAP_STATIC, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }, + {.name = "tbl_ext_addr", .internal_input = 1, .user_input = 1, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_HASH +#endif + }, + {.name = "tmp_btrfs", .internal_input = 4192, .user_input = 4192, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_HASH +#endif + }, + {.name = NULL, .internal_input = 0, .user_input = 0, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_HASH +#endif + }}; + +static netdata_syscall_stat_t filesystem_aggregated_data[NETDATA_EBPF_HIST_MAX_BINS]; +static netdata_publish_syscall_t filesystem_publish_aggregated[NETDATA_EBPF_HIST_MAX_BINS]; + +char **dimensions = NULL; +static netdata_idx_t *filesystem_hash_values = NULL; + +#ifdef LIBBPF_MAJOR_VERSION +/** + * FS disable kprobe + * + * Disable kprobes, because system will use trampolines. + * We are not calling this function for while, because we are prioritizing kprobes. We opted by this road, because + * distribution are still not deliverying necessary btf files per FS. + * + * @param obj FS object loaded. + */ +static void ebpf_fs_disable_kprobe(struct filesystem_bpf *obj) + { + // kprobe + bpf_program__set_autoload(obj->progs.netdata_fs_file_read_probe, false); + bpf_program__set_autoload(obj->progs.netdata_fs_file_write_probe, false); + bpf_program__set_autoload(obj->progs.netdata_fs_file_open_probe, false); + bpf_program__set_autoload(obj->progs.netdata_fs_2nd_file_open_probe, false); + bpf_program__set_autoload(obj->progs.netdata_fs_getattr_probe, false); + // kretprobe + bpf_program__set_autoload(obj->progs.netdata_fs_file_read_retprobe, false); + bpf_program__set_autoload(obj->progs.netdata_fs_file_write_retprobe, false); + bpf_program__set_autoload(obj->progs.netdata_fs_file_open_retprobe, false); + bpf_program__set_autoload(obj->progs.netdata_fs_2nd_file_open_retprobe, false); + bpf_program__set_autoload(obj->progs.netdata_fs_getattr_retprobe, false); + } + + /** + * Disable trampoline + * + * Disable trampolines to use kprobes. + * + * @param obj FS object loaded. + */ + static void ebpf_fs_disable_trampoline(struct filesystem_bpf *obj) + { + // entry + bpf_program__set_autoload(obj->progs.netdata_fs_file_read_entry, false); + bpf_program__set_autoload(obj->progs.netdata_fs_file_write_entry, false); + bpf_program__set_autoload(obj->progs.netdata_fs_file_open_entry, false); + bpf_program__set_autoload(obj->progs.netdata_fs_getattr_entry, false); + bpf_program__set_autoload(obj->progs.netdata_fs_2nd_file_open_entry, false); + + // exit + bpf_program__set_autoload(obj->progs.netdata_fs_file_read_exit, false); + bpf_program__set_autoload(obj->progs.netdata_fs_file_write_exit, false); + bpf_program__set_autoload(obj->progs.netdata_fs_file_open_exit, false); + bpf_program__set_autoload(obj->progs.netdata_fs_getattr_exit, false); + bpf_program__set_autoload(obj->progs.netdata_fs_2nd_file_open_exit, false); + } + + /** + * Set targets + * + * Set targets for each objects. + * + * @param obj FS object loaded. + * @param functions array with function names. + */ + static void ebpf_fs_set_target(struct filesystem_bpf *obj, const char **functions) +{ + // entry + bpf_program__set_attach_target(obj->progs.netdata_fs_file_read_entry, 0, + functions[NETDATA_KEY_BTF_READ]); + bpf_program__set_attach_target(obj->progs.netdata_fs_file_write_entry, 0, + functions[NETDATA_KEY_BTF_WRITE]); + bpf_program__set_attach_target(obj->progs.netdata_fs_file_open_entry, 0, + functions[NETDATA_KEY_BTF_OPEN]); + bpf_program__set_attach_target(obj->progs.netdata_fs_getattr_entry, 0, + functions[NETDATA_KEY_BTF_SYNC_ATTR]); + + // exit + bpf_program__set_attach_target(obj->progs.netdata_fs_file_read_exit, 0, + functions[NETDATA_KEY_BTF_READ]); + bpf_program__set_attach_target(obj->progs.netdata_fs_file_write_exit, 0, + functions[NETDATA_KEY_BTF_WRITE]); + bpf_program__set_attach_target(obj->progs.netdata_fs_file_open_exit, 0, + functions[NETDATA_KEY_BTF_OPEN]); + bpf_program__set_attach_target(obj->progs.netdata_fs_getattr_exit, 0, + functions[NETDATA_KEY_BTF_SYNC_ATTR]); + + if (functions[NETDATA_KEY_BTF_OPEN2]) { + bpf_program__set_attach_target(obj->progs.netdata_fs_2nd_file_open_entry, 0, + functions[NETDATA_KEY_BTF_OPEN2]); + bpf_program__set_attach_target(obj->progs.netdata_fs_2nd_file_open_exit, 0, + functions[NETDATA_KEY_BTF_OPEN2]); + } else { + bpf_program__set_autoload(obj->progs.netdata_fs_2nd_file_open_entry, false); + bpf_program__set_autoload(obj->progs.netdata_fs_2nd_file_open_exit, false); + } +} + +/** + * Attach Kprobe + * + * Attach kprobe on targets + * + * @param obj FS object loaded. + * @param functions array with function names. + */ +static int ebpf_fs_attach_kprobe(struct filesystem_bpf *obj, const char **functions) +{ + // kprobe + obj->links.netdata_fs_file_read_probe = bpf_program__attach_kprobe(obj->progs.netdata_fs_file_read_probe, + false, functions[NETDATA_KEY_BTF_READ]); + if (libbpf_get_error(obj->links.netdata_fs_file_read_probe)) + return -1; + + obj->links.netdata_fs_file_write_probe = bpf_program__attach_kprobe(obj->progs.netdata_fs_file_write_probe, + false, functions[NETDATA_KEY_BTF_WRITE]); + if (libbpf_get_error(obj->links.netdata_fs_file_write_probe)) + return -1; + + obj->links.netdata_fs_file_open_probe = bpf_program__attach_kprobe(obj->progs.netdata_fs_file_open_probe, + false, functions[NETDATA_KEY_BTF_OPEN]); + if (libbpf_get_error(obj->links.netdata_fs_file_open_probe)) + return -1; + + obj->links.netdata_fs_getattr_probe = bpf_program__attach_kprobe(obj->progs.netdata_fs_getattr_probe, + false, functions[NETDATA_KEY_BTF_SYNC_ATTR]); + if (libbpf_get_error(obj->links.netdata_fs_getattr_probe)) + return -1; + + // kretprobe + obj->links.netdata_fs_file_read_retprobe = bpf_program__attach_kprobe(obj->progs.netdata_fs_file_read_retprobe, + false, functions[NETDATA_KEY_BTF_READ]); + if (libbpf_get_error(obj->links.netdata_fs_file_read_retprobe)) + return -1; + + obj->links.netdata_fs_file_write_retprobe = bpf_program__attach_kprobe(obj->progs.netdata_fs_file_write_retprobe, + false, functions[NETDATA_KEY_BTF_WRITE]); + if (libbpf_get_error(obj->links.netdata_fs_file_write_retprobe)) + return -1; + + obj->links.netdata_fs_file_open_retprobe = bpf_program__attach_kprobe(obj->progs.netdata_fs_file_open_retprobe, + false, functions[NETDATA_KEY_BTF_OPEN]); + if (libbpf_get_error(obj->links.netdata_fs_file_open_retprobe)) + return -1; + + obj->links.netdata_fs_getattr_retprobe = bpf_program__attach_kprobe(obj->progs.netdata_fs_getattr_retprobe, + false, functions[NETDATA_KEY_BTF_SYNC_ATTR]); + if (libbpf_get_error(obj->links.netdata_fs_getattr_retprobe)) + return -1; + + if (functions[NETDATA_KEY_BTF_OPEN2]) { + obj->links.netdata_fs_2nd_file_open_probe = bpf_program__attach_kprobe(obj->progs.netdata_fs_2nd_file_open_probe, + false, functions[NETDATA_KEY_BTF_OPEN2]); + if (libbpf_get_error(obj->links.netdata_fs_2nd_file_open_probe)) + return -1; + + obj->links.netdata_fs_2nd_file_open_retprobe = bpf_program__attach_kprobe(obj->progs.netdata_fs_2nd_file_open_retprobe, + false, functions[NETDATA_KEY_BTF_OPEN2]); + if (libbpf_get_error(obj->links.netdata_fs_2nd_file_open_retprobe)) + return -1; + } + + return 0; +} + +/** + * Load and Attach + * + * Load binary and attach to targets. + * + * @param map Structure with information about maps. + * @param obj FS object loaded. + * @param functions array with function names. + * @param bf sttruct with btf file loaded. + */ +static inline int ebpf_fs_load_and_attach(ebpf_local_maps_t *map, struct filesystem_bpf *obj, + const char **functions, struct btf *bf) +{ + if (bf) { + ebpf_fs_disable_kprobe(obj); + ebpf_fs_set_target(obj, functions); + } else { + ebpf_fs_disable_trampoline(obj); + } + + int ret = filesystem_bpf__load(obj); + if (ret) { + fprintf(stderr, "failed to load BPF object: %d\n", ret); + return -1; + } + + if (bf) + ret = filesystem_bpf__attach(obj); + else + ret = ebpf_fs_attach_kprobe(obj, functions); + + if (!ret) + map->map_fd = bpf_map__fd(obj->maps.tbl_fs); + + return ret; +} +#endif + +/***************************************************************** + * + * COMMON FUNCTIONS + * + *****************************************************************/ + +/** + * Create Filesystem chart + * + * Create latency charts + * + * @param update_every value to overwrite the update frequency set by the server. + */ +static void ebpf_obsolete_fs_charts(int update_every) +{ + int i; + uint32_t test = NETDATA_FILESYSTEM_FLAG_CHART_CREATED | NETDATA_FILESYSTEM_REMOVE_CHARTS; + for (i = 0; localfs[i].filesystem; i++) { + ebpf_filesystem_partitions_t *efp = &localfs[i]; + uint32_t flags = efp->flags; + if ((flags & test) == test) { + flags &= ~NETDATA_FILESYSTEM_FLAG_CHART_CREATED; + + ebpf_write_chart_obsolete(NETDATA_FILESYSTEM_FAMILY, efp->hread.name, + "", + efp->hread.title, + EBPF_COMMON_UNITS_CALLS_PER_SEC, efp->family_name, + NULL, NETDATA_EBPF_CHART_TYPE_STACKED, efp->hread.order, update_every); + + ebpf_write_chart_obsolete(NETDATA_FILESYSTEM_FAMILY, efp->hwrite.name, + "", + efp->hwrite.title, + EBPF_COMMON_UNITS_CALLS_PER_SEC, efp->family_name, + NULL, NETDATA_EBPF_CHART_TYPE_STACKED, efp->hwrite.order, update_every); + + ebpf_write_chart_obsolete(NETDATA_FILESYSTEM_FAMILY, efp->hopen.name, "", efp->hopen.title, + EBPF_COMMON_UNITS_CALLS_PER_SEC, efp->family_name, + NULL, NETDATA_EBPF_CHART_TYPE_STACKED, efp->hopen.order, update_every); + + ebpf_write_chart_obsolete(NETDATA_FILESYSTEM_FAMILY, efp->hadditional.name,"", efp->hadditional.title, + EBPF_COMMON_UNITS_CALLS_PER_SEC, efp->family_name, + NULL, NETDATA_EBPF_CHART_TYPE_STACKED, efp->hadditional.order, + update_every); + } + efp->flags = flags; + } +} + +/** + * Create Filesystem chart + * + * Create latency charts + * + * @param update_every value to overwrite the update frequency set by the server. + */ +static void ebpf_create_fs_charts(int update_every) +{ + static int order = NETDATA_CHART_PRIO_EBPF_FILESYSTEM_CHARTS; + char chart_name[64], title[256], family[64], ctx[64]; + int i; + uint32_t test = NETDATA_FILESYSTEM_FLAG_CHART_CREATED|NETDATA_FILESYSTEM_REMOVE_CHARTS; + for (i = 0; localfs[i].filesystem; i++) { + ebpf_filesystem_partitions_t *efp = &localfs[i]; + uint32_t flags = efp->flags; + if (flags & NETDATA_FILESYSTEM_FLAG_HAS_PARTITION && !(flags & test)) { + snprintfz(title, sizeof(title) - 1, "%s latency for each read request.", efp->filesystem); + snprintfz(family, sizeof(family) - 1, "%s_latency", efp->family); + snprintfz(chart_name, sizeof(chart_name) - 1, "%s_read_latency", efp->filesystem); + efp->hread.name = strdupz(chart_name); + efp->hread.title = strdupz(title); + efp->hread.ctx = NULL; + efp->hread.order = order; + efp->family_name = strdupz(family); + + ebpf_create_chart(NETDATA_FILESYSTEM_FAMILY, efp->hread.name, + efp->hread.title, + EBPF_COMMON_UNITS_CALLS_PER_SEC, efp->family_name, + "filesystem.read_latency", NETDATA_EBPF_CHART_TYPE_STACKED, order, + ebpf_create_global_dimension, + filesystem_publish_aggregated, NETDATA_EBPF_HIST_MAX_BINS, + update_every, NETDATA_EBPF_MODULE_NAME_FILESYSTEM); + order++; + + snprintfz(title, sizeof(title) - 1, "%s latency for each write request.", efp->filesystem); + snprintfz(chart_name, sizeof(chart_name) - 1, "%s_write_latency", efp->filesystem); + efp->hwrite.name = strdupz(chart_name); + efp->hwrite.title = strdupz(title); + efp->hwrite.ctx = NULL; + efp->hwrite.order = order; + ebpf_create_chart(NETDATA_FILESYSTEM_FAMILY, efp->hwrite.name, + efp->hwrite.title, + EBPF_COMMON_UNITS_CALLS_PER_SEC, efp->family_name, + "filesystem.write_latency", NETDATA_EBPF_CHART_TYPE_STACKED, order, + ebpf_create_global_dimension, + filesystem_publish_aggregated, NETDATA_EBPF_HIST_MAX_BINS, + update_every, NETDATA_EBPF_MODULE_NAME_FILESYSTEM); + order++; + + snprintfz(title, sizeof(title) - 1, "%s latency for each open request.", efp->filesystem); + snprintfz(chart_name, sizeof(chart_name) - 1, "%s_open_latency", efp->filesystem); + efp->hopen.name = strdupz(chart_name); + efp->hopen.title = strdupz(title); + efp->hopen.ctx = NULL; + efp->hopen.order = order; + ebpf_create_chart(NETDATA_FILESYSTEM_FAMILY, efp->hopen.name, + efp->hopen.title, + EBPF_COMMON_UNITS_CALLS_PER_SEC, efp->family_name, + "filesystem.open_latency", NETDATA_EBPF_CHART_TYPE_STACKED, order, + ebpf_create_global_dimension, + filesystem_publish_aggregated, NETDATA_EBPF_HIST_MAX_BINS, + update_every, NETDATA_EBPF_MODULE_NAME_FILESYSTEM); + order++; + + char *type = (efp->flags & NETDATA_FILESYSTEM_ATTR_CHARTS) ? "attribute" : "sync"; + snprintfz(title, sizeof(title) - 1, "%s latency for each %s request.", efp->filesystem, type); + snprintfz(chart_name, sizeof(chart_name) - 1, "%s_%s_latency", efp->filesystem, type); + snprintfz(ctx, sizeof(ctx) - 1, "filesystem.%s_latency", type); + efp->hadditional.name = strdupz(chart_name); + efp->hadditional.title = strdupz(title); + efp->hadditional.ctx = strdupz(ctx); + efp->hadditional.order = order; + ebpf_create_chart(NETDATA_FILESYSTEM_FAMILY, efp->hadditional.name, efp->hadditional.title, + EBPF_COMMON_UNITS_CALLS_PER_SEC, efp->family_name, + ctx, NETDATA_EBPF_CHART_TYPE_STACKED, order, ebpf_create_global_dimension, + filesystem_publish_aggregated, NETDATA_EBPF_HIST_MAX_BINS, + update_every, NETDATA_EBPF_MODULE_NAME_FILESYSTEM); + order++; + efp->flags |= NETDATA_FILESYSTEM_FLAG_CHART_CREATED; + } + } + + fflush(stdout); +} + +/** + * Initialize eBPF data + * + * @param em main thread structure. + * + * @return it returns 0 on success and -1 otherwise. + */ +int ebpf_filesystem_initialize_ebpf_data(ebpf_module_t *em) +{ + pthread_mutex_lock(&lock); + int i; + const char *saved_name = em->info.thread_name; + uint64_t kernels = em->kernels; + for (i = 0; localfs[i].filesystem; i++) { + ebpf_filesystem_partitions_t *efp = &localfs[i]; + if (!efp->probe_links && efp->flags & NETDATA_FILESYSTEM_LOAD_EBPF_PROGRAM) { + em->info.thread_name = efp->filesystem; + em->kernels = efp->kernels; + em->maps = efp->fs_maps; +#ifdef LIBBPF_MAJOR_VERSION + ebpf_define_map_type(em->maps, em->maps_per_core, running_on_kernel); +#endif + if (em->load & EBPF_LOAD_LEGACY) { + efp->probe_links = ebpf_load_program(ebpf_plugin_dir, em, running_on_kernel, isrh, &efp->objects); + if (!efp->probe_links) { + em->info.thread_name = saved_name; + em->kernels = kernels; + em->maps = NULL; + pthread_mutex_unlock(&lock); + return -1; + } + } +#ifdef LIBBPF_MAJOR_VERSION + else { + efp->fs_obj = filesystem_bpf__open(); + if (!efp->fs_obj) { + em->info.thread_name = saved_name; + em->kernels = kernels; + return -1; + } else { + if (ebpf_fs_load_and_attach(em->maps, efp->fs_obj, + efp->functions, NULL)) + return -1; + } + } +#endif + efp->flags |= NETDATA_FILESYSTEM_FLAG_HAS_PARTITION; + ebpf_update_kernel_memory(&plugin_statistics, efp->fs_maps, EBPF_ACTION_STAT_ADD); + + // Nedeed for filesystems like btrfs + if ((efp->flags & NETDATA_FILESYSTEM_FILL_ADDRESS_TABLE) && (efp->addresses.function)) { + ebpf_load_addresses(&efp->addresses, efp->fs_maps[NETDATA_ADDR_FS_TABLE].map_fd); + } + } + efp->flags &= ~NETDATA_FILESYSTEM_LOAD_EBPF_PROGRAM; + } + em->info.thread_name = saved_name; + pthread_mutex_unlock(&lock); + em->kernels = kernels; + em->maps = NULL; + + if (!dimensions) { + dimensions = ebpf_fill_histogram_dimension(NETDATA_EBPF_HIST_MAX_BINS); + + memset(filesystem_aggregated_data, 0 , NETDATA_EBPF_HIST_MAX_BINS * sizeof(netdata_syscall_stat_t)); + memset(filesystem_publish_aggregated, 0 , NETDATA_EBPF_HIST_MAX_BINS * sizeof(netdata_publish_syscall_t)); + + filesystem_hash_values = callocz(ebpf_nprocs, sizeof(netdata_idx_t)); + } + + return 0; +} + +/** + * Read Local partitions + * + * @return the total of partitions that will be monitored + */ +static int ebpf_read_local_partitions() +{ + char filename[FILENAME_MAX + 1]; + snprintfz(filename, FILENAME_MAX, "%s/proc/self/mountinfo", netdata_configured_host_prefix); + procfile *ff = procfile_open(filename, " \t", PROCFILE_FLAG_DEFAULT); + if(unlikely(!ff)) { + snprintfz(filename, FILENAME_MAX, "%s/proc/1/mountinfo", netdata_configured_host_prefix); + ff = procfile_open(filename, " \t", PROCFILE_FLAG_DEFAULT); + if(unlikely(!ff)) return 0; + } + + ff = procfile_readall(ff); + if(unlikely(!ff)) + return 0; + + int count = 0; + unsigned long l, i, lines = procfile_lines(ff); + for (i = 0; localfs[i].filesystem; i++) { + localfs[i].flags |= NETDATA_FILESYSTEM_REMOVE_CHARTS; + } + + for(l = 0; l < lines ; l++) { + // In "normal" situation the expected value is at column 7 + // When `shared` options is added to mount information, the filesystem is at column 8 + // Finally when we have systemd starting netdata, it will be at column 9 + unsigned long index = procfile_linewords(ff, l) - 3; + + char *fs = procfile_lineword(ff, l, index); + + for (i = 0; localfs[i].filesystem; i++) { + ebpf_filesystem_partitions_t *w = &localfs[i]; + if (w->enabled && (!strcmp(fs, w->filesystem) || + (w->optional_filesystem && !strcmp(fs, w->optional_filesystem)))) { + localfs[i].flags |= NETDATA_FILESYSTEM_LOAD_EBPF_PROGRAM; + localfs[i].flags &= ~NETDATA_FILESYSTEM_REMOVE_CHARTS; + count++; + break; + } + } + } + procfile_close(ff); + + return count; +} + +/** + * Update partition + * + * Update the partition structures before to plot + * + * @param em main thread structure + * + * @return 0 on success and -1 otherwise. + */ +static int ebpf_update_partitions(ebpf_module_t *em) +{ + static time_t update_every = 0; + time_t curr = now_realtime_sec(); + if (curr < update_every) + return 0; + + update_every = curr + 5 * em->update_every; + if (!ebpf_read_local_partitions()) { + em->optional = -1; + return -1; + } + + if (ebpf_filesystem_initialize_ebpf_data(em)) { + return -1; + } + + return 0; +} + +/***************************************************************** + * + * CLEANUP FUNCTIONS + * + *****************************************************************/ + +/* + * Cleanup eBPF data + */ +void ebpf_filesystem_cleanup_ebpf_data() +{ + int i; + for (i = 0; localfs[i].filesystem; i++) { + ebpf_filesystem_partitions_t *efp = &localfs[i]; + if (efp->probe_links) { + freez(efp->family_name); + efp->family_name = NULL; + + freez(efp->hread.name); + efp->hread.name = NULL; + freez(efp->hread.title); + efp->hread.title = NULL; + + freez(efp->hwrite.name); + efp->hwrite.name = NULL; + freez(efp->hwrite.title); + efp->hwrite.title = NULL; + + freez(efp->hopen.name); + efp->hopen.name = NULL; + freez(efp->hopen.title); + efp->hopen.title = NULL; + + freez(efp->hadditional.name); + efp->hadditional.name = NULL; + freez(efp->hadditional.title); + efp->hadditional.title = NULL; + freez(efp->hadditional.ctx); + efp->hadditional.ctx = NULL; + } + } +} + +/** + * Cleanup FS Histograms + * + * @param ptr pointer to structure to be cleaned + */ +static void ebpf_cleanup_fs_histograms(netdata_ebpf_histogram_t *ptr) +{ + freez(ptr->name); + ptr->name = NULL; + + freez(ptr->title); + ptr->title = NULL; + + freez(ptr->ctx); + ptr->ctx = NULL; +} + +/** + * Obsolete global + * + * Obsolete global charts created by thread. + * + * @param em a pointer to `struct ebpf_module` + */ +static void ebpf_obsolete_filesystem_global(ebpf_module_t *em) +{ + int i; + for (i = 0; localfs[i].filesystem; i++) { + ebpf_filesystem_partitions_t *efp = &localfs[i]; + if (!efp->objects) + continue; + + ebpf_write_chart_obsolete(NETDATA_FILESYSTEM_FAMILY, + efp->hread.name, + "", + efp->hread.title, + EBPF_COMMON_UNITS_CALLS_PER_SEC, + efp->family_name, + NETDATA_EBPF_CHART_TYPE_STACKED, + "filesystem.read_latency", + efp->hread.order, + em->update_every); + ebpf_cleanup_fs_histograms(&efp->hread); + + ebpf_write_chart_obsolete(NETDATA_FILESYSTEM_FAMILY, + efp->hwrite.name, + "", + efp->hwrite.title, + EBPF_COMMON_UNITS_CALLS_PER_SEC, + efp->family_name, + NETDATA_EBPF_CHART_TYPE_STACKED, + "filesystem.write_latency", + efp->hwrite.order, + em->update_every); + ebpf_cleanup_fs_histograms(&efp->hwrite); + + ebpf_write_chart_obsolete(NETDATA_FILESYSTEM_FAMILY, + efp->hopen.name, + "", + efp->hopen.title, + EBPF_COMMON_UNITS_CALLS_PER_SEC, + efp->family_name, + NETDATA_EBPF_CHART_TYPE_STACKED, + "filesystem.open_latency", + efp->hopen.order, + em->update_every); + ebpf_cleanup_fs_histograms(&efp->hopen); + + ebpf_write_chart_obsolete(NETDATA_FILESYSTEM_FAMILY, + efp->hadditional.name, + "", + efp->hadditional.title, + EBPF_COMMON_UNITS_CALLS_PER_SEC, + efp->family_name, + NETDATA_EBPF_CHART_TYPE_STACKED, + efp->hadditional.ctx, + efp->hadditional.order, + em->update_every); + ebpf_cleanup_fs_histograms(&efp->hadditional); + + efp->flags = NETDATA_FILESYSTEM_FLAG_NO_PARTITION; + } +} + +/** + * Filesystem exit + * + * Cancel child thread. + * + * @param ptr thread data. + */ +static void ebpf_filesystem_exit(void *pptr) +{ + ebpf_module_t *em = CLEANUP_FUNCTION_GET_PTR(pptr); + if(!em) return; + + if (em->enabled == NETDATA_THREAD_EBPF_FUNCTION_RUNNING) { + pthread_mutex_lock(&lock); + ebpf_obsolete_filesystem_global(em); + + pthread_mutex_unlock(&lock); + fflush(stdout); + } + + ebpf_filesystem_cleanup_ebpf_data(); + if (dimensions) { + ebpf_histogram_dimension_cleanup(dimensions, NETDATA_EBPF_HIST_MAX_BINS); + dimensions = NULL; + } + + freez(filesystem_hash_values); + + int i; + for (i = 0; localfs[i].filesystem; i++) { + ebpf_filesystem_partitions_t *efp = &localfs[i]; + if (!efp->probe_links) + continue; + + ebpf_unload_legacy_code(efp->objects, efp->probe_links); + efp->objects = NULL; + efp->probe_links = NULL; + efp->flags = NETDATA_FILESYSTEM_FLAG_NO_PARTITION; + } + + pthread_mutex_lock(&ebpf_exit_cleanup); + em->enabled = NETDATA_THREAD_EBPF_STOPPED; + ebpf_update_stats(&plugin_statistics, em); + pthread_mutex_unlock(&ebpf_exit_cleanup); +} + +/***************************************************************** + * + * MAIN THREAD + * + *****************************************************************/ + +/** + * Select hist + * + * Select a histogram to store data. + * + * @param efp pointer for the structure with pointers. + * @param id histogram selector + * + * @return It returns a pointer for the histogram + */ +static inline netdata_ebpf_histogram_t *select_hist(ebpf_filesystem_partitions_t *efp, uint32_t *idx, uint32_t id) +{ + if (id < NETDATA_KEY_CALLS_READ) { + *idx = id; + return &efp->hread; + } else if (id < NETDATA_KEY_CALLS_WRITE) { + *idx = id - NETDATA_KEY_CALLS_READ; + return &efp->hwrite; + } else if (id < NETDATA_KEY_CALLS_OPEN) { + *idx = id - NETDATA_KEY_CALLS_WRITE; + return &efp->hopen; + } else if (id < NETDATA_KEY_CALLS_SYNC ){ + *idx = id - NETDATA_KEY_CALLS_OPEN; + return &efp->hadditional; + } + + return NULL; +} + +/** + * Read hard disk table + * + * @param efp structure with filesystem monitored + * @param fd file descriptor to get data. + * @param maps_per_core do I need to read all cores? + * + * Read the table with number of calls for all functions + */ +static void read_filesystem_table(ebpf_filesystem_partitions_t *efp, int fd, int maps_per_core) +{ + netdata_idx_t *values = filesystem_hash_values; + uint32_t key; + uint32_t idx; + for (key = 0; key < NETDATA_KEY_CALLS_SYNC; key++) { + netdata_ebpf_histogram_t *w = select_hist(efp, &idx, key); + if (!w) { + continue; + } + + int test = bpf_map_lookup_elem(fd, &key, values); + if (test < 0) { + continue; + } + + uint64_t total = 0; + int i; + int end = (maps_per_core) ? ebpf_nprocs : 1; + for (i = 0; i < end; i++) { + total += values[i]; + } + + if (idx >= NETDATA_EBPF_HIST_MAX_BINS) + idx = NETDATA_EBPF_HIST_MAX_BINS - 1; + w->histogram[idx] = total; + } +} + +/** + * Read hard disk table + * + * Read the table with number of calls for all functions + * + * @param maps_per_core do I need to read all cores? + */ +static void read_filesystem_tables(int maps_per_core) +{ + int i; + for (i = 0; localfs[i].filesystem; i++) { + ebpf_filesystem_partitions_t *efp = &localfs[i]; + if (efp->flags & NETDATA_FILESYSTEM_FLAG_HAS_PARTITION) { + read_filesystem_table(efp, efp->fs_maps[NETDATA_MAIN_FS_TABLE].map_fd, maps_per_core); + } + } +} + +/** + * Socket read hash + * + * This is the thread callback. + * This thread is necessary, because we cannot freeze the whole plugin to read the data on very busy socket. + * + * @param ptr It is a NULL value for this thread. + * + * @return It always returns NULL. + */ +void ebpf_filesystem_read_hash(ebpf_module_t *em) +{ + ebpf_obsolete_fs_charts(em->update_every); + + (void) ebpf_update_partitions(em); + + if (em->optional) + return; + + read_filesystem_tables(em->maps_per_core); +} + +/** + * Send Hard disk data + * + * Send hard disk information to Netdata. + */ +static void ebpf_histogram_send_data() +{ + uint32_t i; + uint32_t test = NETDATA_FILESYSTEM_FLAG_HAS_PARTITION | NETDATA_FILESYSTEM_REMOVE_CHARTS; + for (i = 0; localfs[i].filesystem; i++) { + ebpf_filesystem_partitions_t *efp = &localfs[i]; + if ((efp->flags & test) == NETDATA_FILESYSTEM_FLAG_HAS_PARTITION) { + write_histogram_chart(NETDATA_FILESYSTEM_FAMILY, efp->hread.name, + efp->hread.histogram, dimensions, NETDATA_EBPF_HIST_MAX_BINS); + + write_histogram_chart(NETDATA_FILESYSTEM_FAMILY, efp->hwrite.name, + efp->hwrite.histogram, dimensions, NETDATA_EBPF_HIST_MAX_BINS); + + write_histogram_chart(NETDATA_FILESYSTEM_FAMILY, efp->hopen.name, + efp->hopen.histogram, dimensions, NETDATA_EBPF_HIST_MAX_BINS); + + write_histogram_chart(NETDATA_FILESYSTEM_FAMILY, efp->hadditional.name, + efp->hadditional.histogram, dimensions, NETDATA_EBPF_HIST_MAX_BINS); + } + } +} + +/** + * Main loop for this collector. + * + * @param em main structure for this thread + */ +static void filesystem_collector(ebpf_module_t *em) +{ + int update_every = em->update_every; + heartbeat_t hb; + heartbeat_init(&hb); + int counter = update_every - 1; + uint32_t running_time = 0; + uint32_t lifetime = em->lifetime; + while (!ebpf_plugin_stop() && running_time < lifetime) { + (void)heartbeat_next(&hb, USEC_PER_SEC); + + if (ebpf_plugin_stop() || ++counter != update_every) + continue; + + counter = 0; + ebpf_filesystem_read_hash(em); + pthread_mutex_lock(&lock); + + ebpf_create_fs_charts(update_every); + ebpf_histogram_send_data(); + + pthread_mutex_unlock(&lock); + + pthread_mutex_lock(&ebpf_exit_cleanup); + if (running_time && !em->running_time) + running_time = update_every; + else + running_time += update_every; + + em->running_time = running_time; + pthread_mutex_unlock(&ebpf_exit_cleanup); + } +} + +/***************************************************************** + * + * ENTRY THREAD + * + *****************************************************************/ + +/** + * Update Filesystem + * + * Update file system structure using values read from configuration file. + */ +static void ebpf_update_filesystem() +{ + char dist[NETDATA_FS_MAX_DIST_NAME + 1]; + int i; + for (i = 0; localfs[i].filesystem; i++) { + snprintfz(dist, NETDATA_FS_MAX_DIST_NAME, "%sdist", localfs[i].filesystem); + + localfs[i].enabled = appconfig_get_boolean(&fs_config, NETDATA_FILESYSTEM_CONFIG_NAME, dist, + CONFIG_BOOLEAN_YES); + } +} + +/** + * Set maps + * + * When thread is initialized the variable fs_maps is set as null, + * this function fills the variable before to use. + */ +static void ebpf_set_maps() +{ + localfs[NETDATA_FS_LOCALFS_EXT4].fs_maps = ext4_maps; + localfs[NETDATA_FS_LOCALFS_XFS].fs_maps = xfs_maps; + localfs[NETDATA_FS_LOCALFS_NFS].fs_maps = nfs_maps; + localfs[NETDATA_FS_LOCALFS_ZFS].fs_maps = zfs_maps; + localfs[NETDATA_FS_LOCALFS_BTRFS].fs_maps = btrfs_maps; +} + +/** + * Filesystem thread + * + * Thread used to generate socket charts. + * + * @param ptr a pointer to `struct ebpf_module` + * + * @return It always return NULL + */ +void *ebpf_filesystem_thread(void *ptr) +{ + ebpf_module_t *em = (ebpf_module_t *)ptr; + + CLEANUP_FUNCTION_REGISTER(ebpf_filesystem_exit) cleanup_ptr = em; + + ebpf_set_maps(); + ebpf_update_filesystem(); + + // Initialize optional as zero, to identify when there are not partitions to monitor + em->optional = 0; + +#ifdef LIBBPF_MAJOR_VERSION + ebpf_adjust_thread_load(em, default_btf); +#endif + if (ebpf_update_partitions(em)) { + if (em->optional) + netdata_log_info("Netdata cannot monitor the filesystems used on this host."); + + goto endfilesystem; + } + + int algorithms[NETDATA_EBPF_HIST_MAX_BINS]; + ebpf_fill_algorithms(algorithms, NETDATA_EBPF_HIST_MAX_BINS, NETDATA_EBPF_INCREMENTAL_IDX); + ebpf_global_labels(filesystem_aggregated_data, filesystem_publish_aggregated, dimensions, dimensions, + algorithms, NETDATA_EBPF_HIST_MAX_BINS); + + pthread_mutex_lock(&lock); + ebpf_create_fs_charts(em->update_every); + ebpf_update_stats(&plugin_statistics, em); + pthread_mutex_unlock(&lock); + + filesystem_collector(em); + +endfilesystem: + ebpf_update_disabled_plugin_stats(em); + + return NULL; +} diff --git a/src/collectors/ebpf.plugin/ebpf_filesystem.h b/src/collectors/ebpf.plugin/ebpf_filesystem.h new file mode 100644 index 000000000..cd54be57e --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_filesystem.h @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_EBPF_FILESYSTEM_H +#define NETDATA_EBPF_FILESYSTEM_H 1 + +// Module name & description +#define NETDATA_EBPF_MODULE_NAME_FILESYSTEM "filesystem" +#define NETDATA_EBPF_FS_MODULE_DESC "Monitor filesystem latency for: btrfs, ext4, nfs, xfs and zfs." + +#include "ebpf.h" + +#define NETDATA_FS_MAX_DIST_NAME 64UL + +#define NETDATA_FILESYSTEM_CONFIG_NAME "filesystem" + +// Process configuration name +#define NETDATA_FILESYSTEM_CONFIG_FILE "filesystem.conf" + +typedef struct netdata_fs_hist { + uint32_t hist_id; + uint32_t bin; +} netdata_fs_hist_t; + +enum filesystem_limit { + NETDATA_KEY_CALLS_READ = 24, + NETDATA_KEY_CALLS_WRITE = 48, + NETDATA_KEY_CALLS_OPEN = 72, + NETDATA_KEY_CALLS_SYNC = 96 +}; + +enum netdata_filesystem_flags { + NETDATA_FILESYSTEM_FLAG_NO_PARTITION = 0, + NETDATA_FILESYSTEM_LOAD_EBPF_PROGRAM = 1, + NETDATA_FILESYSTEM_FLAG_HAS_PARTITION = 2, + NETDATA_FILESYSTEM_FLAG_CHART_CREATED = 4, + NETDATA_FILESYSTEM_FILL_ADDRESS_TABLE = 8, + NETDATA_FILESYSTEM_REMOVE_CHARTS = 16, + NETDATA_FILESYSTEM_ATTR_CHARTS = 32 +}; + +enum netdata_filesystem_table { + NETDATA_MAIN_FS_TABLE, + NETDATA_ADDR_FS_TABLE +}; + +enum netdata_filesystem_localfs_idx { + NETDATA_FS_LOCALFS_EXT4, + NETDATA_FS_LOCALFS_XFS, + NETDATA_FS_LOCALFS_NFS, + NETDATA_FS_LOCALFS_ZFS, + NETDATA_FS_LOCALFS_BTRFS, + + NETDATA_FS_LOCALFS_END, +}; + +void *ebpf_filesystem_thread(void *ptr); +extern struct config fs_config; + +#endif /* NETDATA_EBPF_FILESYSTEM_H */ diff --git a/src/collectors/ebpf.plugin/ebpf_functions.c b/src/collectors/ebpf.plugin/ebpf_functions.c new file mode 100644 index 000000000..4a43bf434 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_functions.c @@ -0,0 +1,724 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ebpf.h" +#include "ebpf_functions.h" + +/***************************************************************** + * EBPF FUNCTION COMMON + *****************************************************************/ + +/** + * Function Start thread + * + * Start a specific thread after user request. + * + * @param em The structure with thread information + * @param period + * @return + */ +static int ebpf_function_start_thread(ebpf_module_t *em, int period) +{ + struct netdata_static_thread *st = em->thread; + // another request for thread that already ran, cleanup and restart + if (period <= 0) + period = EBPF_DEFAULT_LIFETIME; + + st->thread = NULL; + em->enabled = NETDATA_THREAD_EBPF_FUNCTION_RUNNING; + em->lifetime = period; + +#ifdef NETDATA_INTERNAL_CHECKS + netdata_log_info("Starting thread %s with lifetime = %d", em->info.thread_name, period); +#endif + + st->thread = nd_thread_create(st->name, NETDATA_THREAD_OPTION_DEFAULT, st->start_routine, em); + return st->thread ? 0 : 1; +} + +/***************************************************************** + * EBPF ERROR FUNCTIONS + *****************************************************************/ + +/** + * Function error + * + * Show error when a wrong function is given + * + * @param transaction the transaction id that Netdata sent for this function execution + * @param code the error code to show with the message. + * @param msg the error message + */ +static inline void ebpf_function_error(const char *transaction, int code, const char *msg) { + pluginsd_function_json_error_to_stdout(transaction, code, msg); +} + +/** + * Thread Help + * + * Shows help with all options accepted by thread function. + * + * @param transaction the transaction id that Netdata sent for this function execution +*/ +static inline void ebpf_function_help(const char *transaction, const char *message) { + pluginsd_function_result_begin_to_stdout(transaction, HTTP_RESP_OK, "text/plain", now_realtime_sec() + 3600); + fprintf(stdout, "%s", message); + pluginsd_function_result_end_to_stdout(); + fflush(stdout); +} + +/***************************************************************** + * EBPF SOCKET FUNCTION + *****************************************************************/ + + +/** + * Fill Fake socket + * + * Fill socket with an invalid request. + * + * @param fake_values is the structure where we are storing the value. + */ +static inline void ebpf_socket_fill_fake_socket(netdata_socket_plus_t *fake_values) +{ + snprintfz(fake_values->socket_string.src_ip, INET6_ADDRSTRLEN, "%s", "127.0.0.1"); + snprintfz(fake_values->socket_string.dst_ip, INET6_ADDRSTRLEN, "%s", "127.0.0.1"); + fake_values->pid = getpid(); + //fake_values->socket_string.src_port = 0; + fake_values->socket_string.dst_port[0] = 0; + snprintfz(fake_values->socket_string.dst_ip, NI_MAXSERV, "%s", "none"); + fake_values->data.family = AF_INET; + fake_values->data.protocol = AF_UNSPEC; +} + +static NETDATA_DOUBLE bytes_to_mb(uint64_t bytes) { + return (NETDATA_DOUBLE)bytes / (1024 * 1024); +} + +/** + * Fill function buffer + * + * Fill buffer with data to be shown on cloud. + * + * @param wb buffer where we store data. + * @param values data read from hash table + * @param name the process name + */ +static void ebpf_fill_function_buffer(BUFFER *wb, netdata_socket_plus_t *values, char *name) +{ + buffer_json_add_array_item_array(wb); + + // IMPORTANT! + // THE ORDER SHOULD BE THE SAME WITH THE FIELDS! + + // PID + buffer_json_add_array_item_uint64(wb, (uint64_t)values->pid); + + // NAME + if (!values->data.name[0]) + buffer_json_add_array_item_string(wb, (name) ? name : "unknown"); + else + buffer_json_add_array_item_string(wb, values->data.name); + + // Origin + buffer_json_add_array_item_string(wb, (values->data.external_origin) ? "in" : "out"); + + // Source IP + buffer_json_add_array_item_string(wb, values->socket_string.src_ip); + + // SRC Port + //buffer_json_add_array_item_uint64(wb, (uint64_t) values->socket_string.src_port); + + // Destination IP + buffer_json_add_array_item_string(wb, values->socket_string.dst_ip); + + // DST Port + buffer_json_add_array_item_string(wb, values->socket_string.dst_port); + + uint64_t connections; + if (values->data.protocol == IPPROTO_TCP) { + buffer_json_add_array_item_string(wb, "TCP"); + buffer_json_add_array_item_double(wb, bytes_to_mb(values->data.tcp.tcp_bytes_received)); + buffer_json_add_array_item_double(wb, bytes_to_mb(values->data.tcp.tcp_bytes_sent)); + connections = values->data.tcp.ipv4_connect + values->data.tcp.ipv6_connect; + } else if (values->data.protocol == IPPROTO_UDP) { + buffer_json_add_array_item_string(wb, "UDP"); + buffer_json_add_array_item_double(wb, bytes_to_mb(values->data.udp.udp_bytes_received)); + buffer_json_add_array_item_double(wb, bytes_to_mb(values->data.udp.udp_bytes_sent)); + connections = values->data.udp.call_udp_sent + values->data.udp.call_udp_received; + } else { + buffer_json_add_array_item_string(wb, "UNSPEC"); + buffer_json_add_array_item_double(wb, 0); + buffer_json_add_array_item_double(wb, 0); + connections = 1; + } + + // Connections + if (values->flags & NETDATA_SOCKET_FLAGS_ALREADY_OPEN) { + connections++; + } else if (!connections) { + // If no connections, this means that we lost when connection was opened + values->flags |= NETDATA_SOCKET_FLAGS_ALREADY_OPEN; + connections++; + } + buffer_json_add_array_item_uint64(wb, connections); + + buffer_json_array_close(wb); +} + +/** + * Clean Judy array unsafe + * + * Clean all Judy Array allocated to show table when a function is called. + * Before to call this function it is necessary to lock `ebpf_judy_pid.index.rw_spinlock`. + **/ +static void ebpf_socket_clean_judy_array_unsafe() +{ + if (!ebpf_judy_pid.index.JudyLArray) + return; + + Pvoid_t *pid_value, *socket_value; + Word_t local_pid = 0, local_socket = 0; + bool first_pid = true, first_socket = true; + while ((pid_value = JudyLFirstThenNext(ebpf_judy_pid.index.JudyLArray, &local_pid, &first_pid))) { + netdata_ebpf_judy_pid_stats_t *pid_ptr = (netdata_ebpf_judy_pid_stats_t *)*pid_value; + rw_spinlock_write_lock(&pid_ptr->socket_stats.rw_spinlock); + if (pid_ptr->socket_stats.JudyLArray) { + while ((socket_value = JudyLFirstThenNext(pid_ptr->socket_stats.JudyLArray, &local_socket, &first_socket))) { + netdata_socket_plus_t *socket_clean = *socket_value; + aral_freez(aral_socket_table, socket_clean); + } + JudyLFreeArray(&pid_ptr->socket_stats.JudyLArray, PJE0); + pid_ptr->socket_stats.JudyLArray = NULL; + } + rw_spinlock_write_unlock(&pid_ptr->socket_stats.rw_spinlock); + } +} + +/** + * Fill function buffer unsafe + * + * Fill the function buffer with socket information. Before to call this function it is necessary to lock + * ebpf_judy_pid.index.rw_spinlock + * + * @param buf buffer used to store data to be shown by function. + * + * @return it returns 0 on success and -1 otherwise. + */ +static void ebpf_socket_fill_function_buffer_unsafe(BUFFER *buf) +{ + int counter = 0; + + Pvoid_t *pid_value, *socket_value; + Word_t local_pid = 0; + bool first_pid = true; + while ((pid_value = JudyLFirstThenNext(ebpf_judy_pid.index.JudyLArray, &local_pid, &first_pid))) { + netdata_ebpf_judy_pid_stats_t *pid_ptr = (netdata_ebpf_judy_pid_stats_t *)*pid_value; + bool first_socket = true; + Word_t local_timestamp = 0; + rw_spinlock_read_lock(&pid_ptr->socket_stats.rw_spinlock); + if (pid_ptr->socket_stats.JudyLArray) { + while ((socket_value = JudyLFirstThenNext(pid_ptr->socket_stats.JudyLArray, &local_timestamp, &first_socket))) { + netdata_socket_plus_t *values = (netdata_socket_plus_t *)*socket_value; + ebpf_fill_function_buffer(buf, values, pid_ptr->cmdline); + } + counter++; + } + rw_spinlock_read_unlock(&pid_ptr->socket_stats.rw_spinlock); + } + + if (!counter) { + netdata_socket_plus_t fake_values = { }; + ebpf_socket_fill_fake_socket(&fake_values); + ebpf_fill_function_buffer(buf, &fake_values, NULL); + } +} + +/** + * Socket read hash + * + * This is the thread callback. + * This thread is necessary, because we cannot freeze the whole plugin to read the data on very busy socket. + * + * @param buf the buffer to store data; + * @param em the module main structure. + * + * @return It always returns NULL. + */ +void ebpf_socket_read_open_connections(BUFFER *buf, struct ebpf_module *em) +{ + // thread was not initialized or Array was reset + rw_spinlock_read_lock(&ebpf_judy_pid.index.rw_spinlock); + if (!em->maps || (em->maps[NETDATA_SOCKET_OPEN_SOCKET].map_fd == ND_EBPF_MAP_FD_NOT_INITIALIZED) || + !ebpf_judy_pid.index.JudyLArray){ + netdata_socket_plus_t fake_values = { }; + + ebpf_socket_fill_fake_socket(&fake_values); + + ebpf_fill_function_buffer(buf, &fake_values, NULL); + rw_spinlock_read_unlock(&ebpf_judy_pid.index.rw_spinlock); + return; + } + + rw_spinlock_read_lock(&network_viewer_opt.rw_spinlock); + ebpf_socket_fill_function_buffer_unsafe(buf); + rw_spinlock_read_unlock(&network_viewer_opt.rw_spinlock); + rw_spinlock_read_unlock(&ebpf_judy_pid.index.rw_spinlock); +} + +/** + * Function: Socket + * + * Show information for sockets stored in hash tables. + * + * @param transaction the transaction id that Netdata sent for this function execution + * @param function function name and arguments given to thread. + * @param timeout The function timeout + * @param cancelled Variable used to store function status. + */ +static void ebpf_function_socket_manipulation(const char *transaction, + char *function __maybe_unused, + usec_t *stop_monotonic_ut __maybe_unused, + bool *cancelled __maybe_unused, + BUFFER *payload __maybe_unused, + HTTP_ACCESS access __maybe_unused, + const char *source __maybe_unused, + void *data __maybe_unused) +{ + ebpf_module_t *em = &ebpf_modules[EBPF_MODULE_SOCKET_IDX]; + + char *words[PLUGINSD_MAX_WORDS] = {NULL}; + size_t num_words = quoted_strings_splitter_pluginsd(function, words, PLUGINSD_MAX_WORDS); + const char *name; + int period = -1; + rw_spinlock_write_lock(&ebpf_judy_pid.index.rw_spinlock); + network_viewer_opt.enabled = CONFIG_BOOLEAN_YES; + uint32_t previous; + bool info = false; + time_t now_s = now_realtime_sec(); + + static const char *socket_help = { + "ebpf.plugin / socket\n" + "\n" + "Function `socket` display information for all open sockets during ebpf.plugin runtime.\n" + "During thread runtime the plugin is always collecting data, but when an option is modified, the plugin\n" + "resets completely the previous table and can show a clean data for the first request before to bring the\n" + "modified request.\n" + "\n" + "The following filters are supported:\n" + "\n" + " family:FAMILY\n" + " Shows information for the FAMILY specified. Option accepts IPV4, IPV6 and all, that is the default.\n" + "\n" + " period:PERIOD\n" + " Enable socket to run a specific PERIOD in seconds. When PERIOD is not\n" + " specified plugin will use the default 300 seconds\n" + "\n" + " resolve:BOOL\n" + " Resolve service name, default value is YES.\n" + "\n" + " range:CIDR\n" + " Show sockets that have only a specific destination. Default all addresses.\n" + "\n" + " port:range\n" + " Show sockets that have only a specific destination.\n" + "\n" + " reset\n" + " Send a reset to collector. When a collector receives this command, it uses everything defined in configuration file.\n" + "\n" + " interfaces\n" + " When the collector receives this command, it read all available interfaces on host.\n" + "\n" + "Filters can be combined. Each filter can be given only one time. Default all ports\n" + }; + +for (int i = 1; i < PLUGINSD_MAX_WORDS; i++) { + const char *keyword = get_word(words, num_words, i); + if (!keyword) + break; + + if (strncmp(keyword, EBPF_FUNCTION_SOCKET_FAMILY, sizeof(EBPF_FUNCTION_SOCKET_FAMILY) - 1) == 0) { + name = &keyword[sizeof(EBPF_FUNCTION_SOCKET_FAMILY) - 1]; + previous = network_viewer_opt.family; + uint32_t family = AF_UNSPEC; + if (!strcmp(name, "IPV4")) + family = AF_INET; + else if (!strcmp(name, "IPV6")) + family = AF_INET6; + + if (family != previous) { + rw_spinlock_write_lock(&network_viewer_opt.rw_spinlock); + network_viewer_opt.family = family; + rw_spinlock_write_unlock(&network_viewer_opt.rw_spinlock); + ebpf_socket_clean_judy_array_unsafe(); + } + } else if (strncmp(keyword, EBPF_FUNCTION_SOCKET_PERIOD, sizeof(EBPF_FUNCTION_SOCKET_PERIOD) - 1) == 0) { + name = &keyword[sizeof(EBPF_FUNCTION_SOCKET_PERIOD) - 1]; + pthread_mutex_lock(&ebpf_exit_cleanup); + period = str2i(name); + if (period > 0) { + em->lifetime = period; + } else + em->lifetime = EBPF_NON_FUNCTION_LIFE_TIME; + +#ifdef NETDATA_DEV_MODE + collector_info("Lifetime modified for %u", em->lifetime); +#endif + pthread_mutex_unlock(&ebpf_exit_cleanup); + } else if (strncmp(keyword, EBPF_FUNCTION_SOCKET_RESOLVE, sizeof(EBPF_FUNCTION_SOCKET_RESOLVE) - 1) == 0) { + previous = network_viewer_opt.service_resolution_enabled; + uint32_t resolution; + name = &keyword[sizeof(EBPF_FUNCTION_SOCKET_RESOLVE) - 1]; + resolution = (!strcasecmp(name, "YES")) ? CONFIG_BOOLEAN_YES : CONFIG_BOOLEAN_NO; + + if (previous != resolution) { + rw_spinlock_write_lock(&network_viewer_opt.rw_spinlock); + network_viewer_opt.service_resolution_enabled = resolution; + rw_spinlock_write_unlock(&network_viewer_opt.rw_spinlock); + + ebpf_socket_clean_judy_array_unsafe(); + } + } else if (strncmp(keyword, EBPF_FUNCTION_SOCKET_RANGE, sizeof(EBPF_FUNCTION_SOCKET_RANGE) - 1) == 0) { + name = &keyword[sizeof(EBPF_FUNCTION_SOCKET_RANGE) - 1]; + rw_spinlock_write_lock(&network_viewer_opt.rw_spinlock); + ebpf_clean_ip_structure(&network_viewer_opt.included_ips); + ebpf_clean_ip_structure(&network_viewer_opt.excluded_ips); + ebpf_parse_ips_unsafe((char *)name); + rw_spinlock_write_unlock(&network_viewer_opt.rw_spinlock); + + ebpf_socket_clean_judy_array_unsafe(); + } else if (strncmp(keyword, EBPF_FUNCTION_SOCKET_PORT, sizeof(EBPF_FUNCTION_SOCKET_PORT) - 1) == 0) { + name = &keyword[sizeof(EBPF_FUNCTION_SOCKET_PORT) - 1]; + rw_spinlock_write_lock(&network_viewer_opt.rw_spinlock); + ebpf_clean_port_structure(&network_viewer_opt.included_port); + ebpf_clean_port_structure(&network_viewer_opt.excluded_port); + ebpf_parse_ports((char *)name); + rw_spinlock_write_unlock(&network_viewer_opt.rw_spinlock); + + ebpf_socket_clean_judy_array_unsafe(); + } else if (strncmp(keyword, EBPF_FUNCTION_SOCKET_RESET, sizeof(EBPF_FUNCTION_SOCKET_RESET) - 1) == 0) { + rw_spinlock_write_lock(&network_viewer_opt.rw_spinlock); + ebpf_clean_port_structure(&network_viewer_opt.included_port); + ebpf_clean_port_structure(&network_viewer_opt.excluded_port); + + ebpf_clean_ip_structure(&network_viewer_opt.included_ips); + ebpf_clean_ip_structure(&network_viewer_opt.excluded_ips); + ebpf_clean_ip_structure(&network_viewer_opt.ipv4_local_ip); + ebpf_clean_ip_structure(&network_viewer_opt.ipv6_local_ip); + + parse_network_viewer_section(&socket_config); + ebpf_read_local_addresses_unsafe(); + network_viewer_opt.enabled = CONFIG_BOOLEAN_YES; + rw_spinlock_write_unlock(&network_viewer_opt.rw_spinlock); + } else if (strncmp(keyword, EBPF_FUNCTION_SOCKET_INTERFACES, sizeof(EBPF_FUNCTION_SOCKET_INTERFACES) - 1) == 0) { + rw_spinlock_write_lock(&network_viewer_opt.rw_spinlock); + ebpf_read_local_addresses_unsafe(); + rw_spinlock_write_unlock(&network_viewer_opt.rw_spinlock); + } else if (strncmp(keyword, "help", 4) == 0) { + ebpf_function_help(transaction, socket_help); + rw_spinlock_write_unlock(&ebpf_judy_pid.index.rw_spinlock); + return; + } else if (strncmp(keyword, "info", 4) == 0) + info = true; + } + rw_spinlock_write_unlock(&ebpf_judy_pid.index.rw_spinlock); + + if (em->enabled > NETDATA_THREAD_EBPF_FUNCTION_RUNNING) { + // Cleanup when we already had a thread running + rw_spinlock_write_lock(&ebpf_judy_pid.index.rw_spinlock); + ebpf_socket_clean_judy_array_unsafe(); + rw_spinlock_write_unlock(&ebpf_judy_pid.index.rw_spinlock); + + pthread_mutex_lock(&ebpf_exit_cleanup); + if (ebpf_function_start_thread(em, period)) { + ebpf_function_error(transaction, + HTTP_RESP_INTERNAL_SERVER_ERROR, + "Cannot start thread."); + pthread_mutex_unlock(&ebpf_exit_cleanup); + return; + } + } else { + pthread_mutex_lock(&ebpf_exit_cleanup); + if (period < 0) + em->lifetime = (em->enabled != NETDATA_THREAD_EBPF_FUNCTION_RUNNING) ? EBPF_NON_FUNCTION_LIFE_TIME : EBPF_DEFAULT_LIFETIME; + } + pthread_mutex_unlock(&ebpf_exit_cleanup); + + BUFFER *wb = buffer_create(4096, NULL); + buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_NEWLINE_ON_ARRAY_ITEMS); + buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK); + buffer_json_member_add_string(wb, "type", "table"); + buffer_json_member_add_time_t(wb, "update_every", em->update_every); + buffer_json_member_add_boolean(wb, "has_history", false); + buffer_json_member_add_string(wb, "help", EBPF_PLUGIN_SOCKET_FUNCTION_DESCRIPTION); + + if(info) + goto close_and_send; + + // Collect data + buffer_json_member_add_array(wb, "data"); + ebpf_socket_read_open_connections(wb, em); + buffer_json_array_close(wb); // data + + buffer_json_member_add_object(wb, "columns"); + { + int fields_id = 0; + + // IMPORTANT! + // THE ORDER SHOULD BE THE SAME WITH THE VALUES! + buffer_rrdf_table_add_field(wb, fields_id++, "PID", "Process ID", RRDF_FIELD_TYPE_INTEGER, + RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER, 0, NULL, NAN, + RRDF_FIELD_SORT_ASCENDING, NULL, RRDF_FIELD_SUMMARY_COUNT, + RRDF_FIELD_FILTER_MULTISELECT, + RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_STICKY, + NULL); + + buffer_rrdf_table_add_field(wb, fields_id++, "Name", "Process Name", RRDF_FIELD_TYPE_STRING, + RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, 0, NULL, NAN, + RRDF_FIELD_SORT_ASCENDING, NULL, RRDF_FIELD_SUMMARY_COUNT, + RRDF_FIELD_FILTER_MULTISELECT, + RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_UNIQUE_KEY | RRDF_FIELD_OPTS_STICKY | RRDF_FIELD_OPTS_FULL_WIDTH, + NULL); + + buffer_rrdf_table_add_field(wb, fields_id++, "Origin", "Connection Origin", RRDF_FIELD_TYPE_STRING, + RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, 0, NULL, NAN, + RRDF_FIELD_SORT_ASCENDING, NULL, RRDF_FIELD_SUMMARY_COUNT, + RRDF_FIELD_FILTER_MULTISELECT, + RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_UNIQUE_KEY | RRDF_FIELD_OPTS_STICKY, + NULL); + + buffer_rrdf_table_add_field(wb, fields_id++, "Src", "Source IP Address", RRDF_FIELD_TYPE_STRING, + RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, 0, NULL, NAN, + RRDF_FIELD_SORT_ASCENDING, NULL, RRDF_FIELD_SUMMARY_COUNT, + RRDF_FIELD_FILTER_MULTISELECT, + RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_UNIQUE_KEY | RRDF_FIELD_OPTS_STICKY, + NULL); + + /* + buffer_rrdf_table_add_field(wb, fields_id++, "SrcPort", "Source Port", RRDF_FIELD_TYPE_INTEGER, + RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER, 0, NULL, NAN, + RRDF_FIELD_SORT_ASCENDING, NULL, RRDF_FIELD_SUMMARY_COUNT, + RRDF_FIELD_FILTER_MULTISELECT, + RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_STICKY, + NULL); + */ + + buffer_rrdf_table_add_field(wb, fields_id++, "Dst", "Destination IP Address", RRDF_FIELD_TYPE_STRING, + RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, 0, NULL, NAN, + RRDF_FIELD_SORT_ASCENDING, NULL, RRDF_FIELD_SUMMARY_COUNT, + RRDF_FIELD_FILTER_MULTISELECT, + RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_UNIQUE_KEY | RRDF_FIELD_OPTS_STICKY, + NULL); + + buffer_rrdf_table_add_field(wb, fields_id++, "DstPort", "Destination Port", RRDF_FIELD_TYPE_STRING, + RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, 0, NULL, NAN, + RRDF_FIELD_SORT_ASCENDING, NULL, RRDF_FIELD_SUMMARY_COUNT, + RRDF_FIELD_FILTER_MULTISELECT, + RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_UNIQUE_KEY | RRDF_FIELD_OPTS_STICKY, + NULL); + + buffer_rrdf_table_add_field(wb, fields_id++, "Protocol", "Transport Layer Protocol", RRDF_FIELD_TYPE_STRING, + RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, 0, NULL, NAN, + RRDF_FIELD_SORT_ASCENDING, NULL, RRDF_FIELD_SUMMARY_COUNT, + RRDF_FIELD_FILTER_MULTISELECT, + RRDF_FIELD_OPTS_NONE | RRDF_FIELD_OPTS_UNIQUE_KEY | RRDF_FIELD_OPTS_STICKY, + NULL); + + buffer_rrdf_table_add_field(wb, fields_id++, "Rcvd", "Traffic Received", RRDF_FIELD_TYPE_INTEGER, + RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER, 3, "MB", NAN, + RRDF_FIELD_SORT_DESCENDING, NULL, RRDF_FIELD_SUMMARY_SUM, + RRDF_FIELD_FILTER_NONE, + RRDF_FIELD_OPTS_VISIBLE, + NULL); + + buffer_rrdf_table_add_field(wb, fields_id++, "Sent", "Traffic Sent", RRDF_FIELD_TYPE_INTEGER, + RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER, 3, "MB", NAN, + RRDF_FIELD_SORT_DESCENDING, NULL, RRDF_FIELD_SUMMARY_SUM, + RRDF_FIELD_FILTER_NONE, + RRDF_FIELD_OPTS_VISIBLE, + NULL); + + buffer_rrdf_table_add_field(wb, fields_id, "Conns", "Connections", RRDF_FIELD_TYPE_INTEGER, + RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NUMBER, 0, "connections", NAN, + RRDF_FIELD_SORT_DESCENDING, NULL, RRDF_FIELD_SUMMARY_SUM, + RRDF_FIELD_FILTER_NONE, + RRDF_FIELD_OPTS_VISIBLE, + NULL); + } + buffer_json_object_close(wb); // columns + + buffer_json_member_add_string(wb, "default_sort_column", "Rcvd"); + + buffer_json_member_add_object(wb, "charts"); + { + buffer_json_member_add_object(wb, "Traffic"); + { + buffer_json_member_add_string(wb, "name", "Traffic"); + buffer_json_member_add_string(wb, "type", "stacked-bar"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "Rcvd"); + buffer_json_add_array_item_string(wb, "Sent"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + + buffer_json_member_add_object(wb, "Connections"); + { + buffer_json_member_add_string(wb, "name", "Connections"); + buffer_json_member_add_string(wb, "type", "stacked-bar"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "Conns"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + } + buffer_json_object_close(wb); // charts + + buffer_json_member_add_array(wb, "default_charts"); + { + buffer_json_add_array_item_array(wb); + buffer_json_add_array_item_string(wb, "Traffic"); + buffer_json_add_array_item_string(wb, "Name"); + buffer_json_array_close(wb); + + buffer_json_add_array_item_array(wb); + buffer_json_add_array_item_string(wb, "Connections"); + buffer_json_add_array_item_string(wb, "Name"); + buffer_json_array_close(wb); + } + buffer_json_array_close(wb); + + buffer_json_member_add_object(wb, "group_by"); + { + buffer_json_member_add_object(wb, "Name"); + { + buffer_json_member_add_string(wb, "name", "Process Name"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "Name"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + + buffer_json_member_add_object(wb, "Origin"); + { + buffer_json_member_add_string(wb, "name", "Origin"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "Origin"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + + buffer_json_member_add_object(wb, "Src"); + { + buffer_json_member_add_string(wb, "name", "Source IP"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "Src"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + + buffer_json_member_add_object(wb, "Dst"); + { + buffer_json_member_add_string(wb, "name", "Destination IP"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "Dst"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + + buffer_json_member_add_object(wb, "DstPort"); + { + buffer_json_member_add_string(wb, "name", "Destination Port"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "DstPort"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + + buffer_json_member_add_object(wb, "Protocol"); + { + buffer_json_member_add_string(wb, "name", "Protocol"); + buffer_json_member_add_array(wb, "columns"); + { + buffer_json_add_array_item_string(wb, "Protocol"); + } + buffer_json_array_close(wb); + } + buffer_json_object_close(wb); + } + buffer_json_object_close(wb); // group_by + +close_and_send: + buffer_json_member_add_time_t(wb, "expires", now_s + em->update_every); + buffer_json_finalize(wb); + + // Lock necessary to avoid race condition + pluginsd_function_result_begin_to_stdout(transaction, HTTP_RESP_OK, "application/json", now_s + em->update_every); + + fwrite(buffer_tostring(wb), buffer_strlen(wb), 1, stdout); + + pluginsd_function_result_end_to_stdout(); + fflush(stdout); + + buffer_free(wb); +} + +/***************************************************************** + * EBPF FUNCTION THREAD + *****************************************************************/ + +/** + * FUNCTION thread. + * + * @param ptr a `ebpf_module_t *`. + * + * @return always NULL. + */ +void *ebpf_function_thread(void *ptr) +{ + (void)ptr; + + struct functions_evloop_globals *wg = functions_evloop_init(1, + "EBPF", + &lock, + &ebpf_plugin_exit); + + functions_evloop_add_function( + wg, EBPF_FUNCTION_SOCKET, ebpf_function_socket_manipulation, PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT, NULL); + + pthread_mutex_lock(&lock); + int i; + for (i = 0; i < EBPF_MODULE_FUNCTION_IDX; i++) { + ebpf_module_t *em = &ebpf_modules[i]; + if (!em->functions.fnct_routine) + continue; + + EBPF_PLUGIN_FUNCTIONS(em->functions.fcnt_name, em->functions.fcnt_desc, em->update_every); + } + pthread_mutex_unlock(&lock); + + heartbeat_t hb; + heartbeat_init(&hb); + while(!ebpf_plugin_stop()) { + (void)heartbeat_next(&hb, USEC_PER_SEC); + + if (ebpf_plugin_stop()) { + break; + } + } + + return NULL; +} diff --git a/src/collectors/ebpf.plugin/ebpf_functions.h b/src/collectors/ebpf.plugin/ebpf_functions.h new file mode 100644 index 000000000..1a5215f4d --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_functions.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_EBPF_FUNCTIONS_H +#define NETDATA_EBPF_FUNCTIONS_H 1 + +// Common +static inline void EBPF_PLUGIN_FUNCTIONS(const char *NAME, const char *DESC, int update_every) { + fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\" \"top\" "HTTP_ACCESS_FORMAT" %d\n", + NAME, update_every, DESC, + (HTTP_ACCESS_FORMAT_CAST)(HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_SENSITIVE_DATA), + RRDFUNCTIONS_PRIORITY_DEFAULT); +} + +// configuration file & description +#define NETDATA_DIRECTORY_FUNCTIONS_CONFIG_FILE "functions.conf" +#define NETDATA_EBPF_FUNCTIONS_MODULE_DESC "Show information about current function status." + +// function list +#define EBPF_FUNCTION_SOCKET "network-sockets-tracing" + +// socket constants +#define EBPF_PLUGIN_SOCKET_FUNCTION_DESCRIPTION "Detailed information about open sockets." +#define EBPF_FUNCTION_SOCKET_FAMILY "family:" +#define EBPF_FUNCTION_SOCKET_PERIOD "period:" +#define EBPF_FUNCTION_SOCKET_RESOLVE "resolve:" +#define EBPF_FUNCTION_SOCKET_RANGE "range:" +#define EBPF_FUNCTION_SOCKET_PORT "port:" +#define EBPF_FUNCTION_SOCKET_RESET "reset" +#define EBPF_FUNCTION_SOCKET_INTERFACES "interfaces" + +void *ebpf_function_thread(void *ptr); + +#endif diff --git a/src/collectors/ebpf.plugin/ebpf_hardirq.c b/src/collectors/ebpf.plugin/ebpf_hardirq.c new file mode 100644 index 000000000..911425e54 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_hardirq.c @@ -0,0 +1,686 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ebpf.h" +#include "ebpf_hardirq.h" + +struct config hardirq_config = { .first_section = NULL, + .last_section = NULL, + .mutex = NETDATA_MUTEX_INITIALIZER, + .index = { .avl_tree = { .root = NULL, .compar = appconfig_section_compare }, + .rwlock = AVL_LOCK_INITIALIZER } }; + +static ebpf_local_maps_t hardirq_maps[] = { + { + .name = "tbl_hardirq", + .internal_input = NETDATA_HARDIRQ_MAX_IRQS, + .user_input = 0, + .type = NETDATA_EBPF_MAP_STATIC, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_HASH +#endif + }, + { + .name = "tbl_hardirq_static", + .internal_input = HARDIRQ_EBPF_STATIC_END, + .user_input = 0, + .type = NETDATA_EBPF_MAP_STATIC, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }, + /* end */ + { + .name = NULL, + .internal_input = 0, + .user_input = 0, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + } +}; + +#define HARDIRQ_TP_CLASS_IRQ "irq" +#define HARDIRQ_TP_CLASS_IRQ_VECTORS "irq_vectors" +static ebpf_tracepoint_t hardirq_tracepoints[] = { + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ, .event = "irq_handler_entry"}, + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ, .event = "irq_handler_exit"}, + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "thermal_apic_entry"}, + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "thermal_apic_exit"}, + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "threshold_apic_entry"}, + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "threshold_apic_exit"}, + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "error_apic_entry"}, + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "error_apic_exit"}, + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "deferred_error_apic_entry"}, + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "deferred_error_apic_exit"}, + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "spurious_apic_entry"}, + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "spurious_apic_exit"}, + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "call_function_entry"}, + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "call_function_exit"}, + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "call_function_single_entry"}, + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "call_function_single_exit"}, + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "reschedule_entry"}, + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "reschedule_exit"}, + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "local_timer_entry"}, + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "local_timer_exit"}, + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "irq_work_entry"}, + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "irq_work_exit"}, + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "x86_platform_ipi_entry"}, + {.enabled = false, .class = HARDIRQ_TP_CLASS_IRQ_VECTORS, .event = "x86_platform_ipi_exit"}, + /* end */ + {.enabled = false, .class = NULL, .event = NULL} +}; + +static hardirq_static_val_t hardirq_static_vals[] = { + { + .idx = HARDIRQ_EBPF_STATIC_APIC_THERMAL, + .name = "apic_thermal", + .latency = 0 + }, + { + .idx = HARDIRQ_EBPF_STATIC_APIC_THRESHOLD, + .name = "apic_threshold", + .latency = 0 + }, + { + .idx = HARDIRQ_EBPF_STATIC_APIC_ERROR, + .name = "apic_error", + .latency = 0 + }, + { + .idx = HARDIRQ_EBPF_STATIC_APIC_DEFERRED_ERROR, + .name = "apic_deferred_error", + .latency = 0 + }, + { + .idx = HARDIRQ_EBPF_STATIC_APIC_SPURIOUS, + .name = "apic_spurious", + .latency = 0 + }, + { + .idx = HARDIRQ_EBPF_STATIC_FUNC_CALL, + .name = "func_call", + .latency = 0 + }, + { + .idx = HARDIRQ_EBPF_STATIC_FUNC_CALL_SINGLE, + .name = "func_call_single", + .latency = 0 + }, + { + .idx = HARDIRQ_EBPF_STATIC_RESCHEDULE, + .name = "reschedule", + .latency = 0 + }, + { + .idx = HARDIRQ_EBPF_STATIC_LOCAL_TIMER, + .name = "local_timer", + .latency = 0 + }, + { + .idx = HARDIRQ_EBPF_STATIC_IRQ_WORK, + .name = "irq_work", + .latency = 0 + }, + { + .idx = HARDIRQ_EBPF_STATIC_X86_PLATFORM_IPI, + .name = "x86_platform_ipi", + .latency = 0 + }, +}; + +// store for "published" data from the reader thread, which the collector +// thread will write to netdata agent. +static avl_tree_lock hardirq_pub; + +#ifdef LIBBPF_MAJOR_VERSION +/** + * Set hash table + * + * Set the values for maps according the value given by kernel. + * + * @param obj is the main structure for bpf objects. + */ +static inline void ebpf_hardirq_set_hash_table(struct hardirq_bpf *obj) +{ + hardirq_maps[HARDIRQ_MAP_LATENCY].map_fd = bpf_map__fd(obj->maps.tbl_hardirq); + hardirq_maps[HARDIRQ_MAP_LATENCY_STATIC].map_fd = bpf_map__fd(obj->maps.tbl_hardirq_static); +} + +/** + * Load and Attach + * + * Load and attach bpf software. + */ +static inline int ebpf_hardirq_load_and_attach(struct hardirq_bpf *obj) +{ + int ret = hardirq_bpf__load(obj); + if (ret) { + return -1; + } + + return hardirq_bpf__attach(obj); +} +#endif + +/***************************************************************** + * + * ARAL SECTION + * + *****************************************************************/ + +// ARAL vectors used to speed up processing +ARAL *ebpf_aral_hardirq = NULL; + +/** + * eBPF hardirq Aral init + * + * Initiallize array allocator that will be used when integration with apps is enabled. + */ +static inline void ebpf_hardirq_aral_init() +{ + ebpf_aral_hardirq = ebpf_allocate_pid_aral(NETDATA_EBPF_HARDIRQ_ARAL_NAME, sizeof(hardirq_val_t)); +} + +/** + * eBPF hardirq get + * + * Get a hardirq_val_t entry to be used with a specific IRQ. + * + * @return it returns the address on success. + */ +hardirq_val_t *ebpf_hardirq_get(void) +{ + hardirq_val_t *target = aral_mallocz(ebpf_aral_hardirq); + memset(target, 0, sizeof(hardirq_val_t)); + return target; +} + +/** + * eBPF hardirq release + * + * @param stat Release a target after usage. + */ +void ebpf_hardirq_release(hardirq_val_t *stat) +{ + aral_freez(ebpf_aral_hardirq, stat); +} + +/***************************************************************** + * + * EXIT FUNCTIONS + * + *****************************************************************/ + +/** + * Obsolete global + * + * Obsolete global charts created by thread. + * + * @param em a pointer to `struct ebpf_module` + */ +static void ebpf_obsolete_hardirq_global(ebpf_module_t *em) +{ + ebpf_write_chart_obsolete(NETDATA_EBPF_SYSTEM_GROUP, + "hardirq_latency", + "", + "Hardware IRQ latency", + EBPF_COMMON_UNITS_MILLISECONDS, + "interrupts", + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_EBPF_SYSTEM_HARDIRQ_LATENCY_CTX, + NETDATA_CHART_PRIO_HARDIRQ_LATENCY, + em->update_every + ); +} + +/** + * Hardirq Exit + * + * Cancel child and exit. + * + * @param ptr thread data. + */ +static void hardirq_exit(void *pptr) +{ + ebpf_module_t *em = CLEANUP_FUNCTION_GET_PTR(pptr); + if(!em) return; + + if (em->enabled == NETDATA_THREAD_EBPF_FUNCTION_RUNNING) { + pthread_mutex_lock(&lock); + + ebpf_obsolete_hardirq_global(em); + + pthread_mutex_unlock(&lock); + fflush(stdout); + } + + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps, EBPF_ACTION_STAT_REMOVE); + + if (em->objects) { + ebpf_unload_legacy_code(em->objects, em->probe_links); + em->objects = NULL; + em->probe_links = NULL; + } + + for (int i = 0; hardirq_tracepoints[i].class != NULL; i++) { + ebpf_disable_tracepoint(&hardirq_tracepoints[i]); + } + + pthread_mutex_lock(&ebpf_exit_cleanup); + em->enabled = NETDATA_THREAD_EBPF_STOPPED; + ebpf_update_stats(&plugin_statistics, em); + pthread_mutex_unlock(&ebpf_exit_cleanup); +} + +/***************************************************************** + * MAIN LOOP + *****************************************************************/ + +/** + * Compare hard IRQ values. + * + * @param a `hardirq_val_t *`. + * @param b `hardirq_val_t *`. + * + * @return 0 if a==b, 1 if a>b, -1 if a<b. +*/ +static int hardirq_val_cmp(void *a, void *b) +{ + hardirq_val_t *ptr1 = a; + hardirq_val_t *ptr2 = b; + + if (ptr1->irq > ptr2->irq) { + return 1; + } + else if (ptr1->irq < ptr2->irq) { + return -1; + } + else { + return 0; + } +} + +/** + * Parse interrupts + * + * Parse /proc/interrupts to get names used in metrics + * + * @param irq_name vector to store data. + * @param irq irq value + * + * @return It returns 0 on success and -1 otherwise + */ +static int hardirq_parse_interrupts(char *irq_name, int irq) +{ + static procfile *ff = NULL; + static int cpus = -1; + if(unlikely(!ff)) { + char filename[FILENAME_MAX + 1]; + snprintfz(filename, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/proc/interrupts"); + ff = procfile_open(filename, " \t:", PROCFILE_FLAG_DEFAULT); + } + if(unlikely(!ff)) + return -1; + + ff = procfile_readall(ff); + if(unlikely(!ff)) + return -1; // we return 0, so that we will retry to open it next time + + size_t words = procfile_linewords(ff, 0); + if(unlikely(cpus == -1)) { + uint32_t w; + cpus = 0; + for(w = 0; w < words ; w++) { + if(likely(strncmp(procfile_lineword(ff, 0, w), "CPU", 3) == 0)) + cpus++; + } + } + + size_t lines = procfile_lines(ff), l; + if(unlikely(!lines)) { + collector_error("Cannot read /proc/interrupts, zero lines reported."); + return -1; + } + + for(l = 1; l < lines ;l++) { + words = procfile_linewords(ff, l); + if(unlikely(!words)) continue; + const char *id = procfile_lineword(ff, l, 0); + if (!isdigit(id[0])) + continue; + + int cmp = str2i(id); + if (cmp != irq) + continue; + + if(unlikely((uint32_t)(cpus + 2) < words)) { + const char *name = procfile_lineword(ff, l, words - 1); + // On some motherboards IRQ can have the same name, so we append IRQ id to differentiate. + snprintfz(irq_name, NETDATA_HARDIRQ_NAME_LEN - 1, "%d_%s", irq, name); + } + } + + return 0; +} + +/** + * Read Latency MAP + * + * Read data from kernel ring to user ring. + * + * @param mapfd hash map id. + * + * @return it returns 0 on success and -1 otherwise + */ +static int hardirq_read_latency_map(int mapfd) +{ + static hardirq_ebpf_static_val_t *hardirq_ebpf_vals = NULL; + if (!hardirq_ebpf_vals) + hardirq_ebpf_vals = callocz(ebpf_nprocs + 1, sizeof(hardirq_ebpf_static_val_t)); + + hardirq_ebpf_key_t key = {}; + hardirq_ebpf_key_t next_key = {}; + hardirq_val_t search_v = {}; + hardirq_val_t *v = NULL; + + while (bpf_map_get_next_key(mapfd, &key, &next_key) == 0) { + // get val for this key. + int test = bpf_map_lookup_elem(mapfd, &key, hardirq_ebpf_vals); + if (unlikely(test < 0)) { + key = next_key; + continue; + } + + // is this IRQ saved yet? + // + // if not, make a new one, mark it as unsaved for now, and continue; we + // will insert it at the end after all of its values are correctly set, + // so that we can safely publish it to the collector within a single, + // short locked operation. + // + // otherwise simply continue; we will only update the latency, which + // can be republished safely without a lock. + // + // NOTE: lock isn't strictly necessary for this initial search, as only + // this thread does writing, but the AVL is using a read-write lock so + // there is no congestion. + bool v_is_new = false; + search_v.irq = key.irq; + v = (hardirq_val_t *)avl_search_lock(&hardirq_pub, (avl_t *)&search_v); + if (unlikely(v == NULL)) { + // latency/name can only be added reliably at a later time. + // when they're added, only then will we AVL insert. + v = ebpf_hardirq_get(); + v->irq = key.irq; + v->dim_exists = false; + + v_is_new = true; + } + + // note two things: + // 1. we must add up latency value for this IRQ across all CPUs. + // 2. the name is unfortunately *not* available on all CPU maps - only + // a single map contains the name, so we must find it. we only need + // to copy it though if the IRQ is new for us. + uint64_t total_latency = 0; + int i; + for (i = 0; i < ebpf_nprocs; i++) { + total_latency += hardirq_ebpf_vals[i].latency/1000; + } + + // can now safely publish latency for existing IRQs. + v->latency = total_latency; + + // can now safely publish new IRQ. + if (v_is_new) { + if (hardirq_parse_interrupts(v->name, v->irq)) { + ebpf_hardirq_release(v); + return -1; + } + + avl_t *check = avl_insert_lock(&hardirq_pub, (avl_t *)v); + if (check != (avl_t *)v) { + netdata_log_error("Internal error, cannot insert the AVL tree."); + } + } + + key = next_key; + } + + return 0; +} + +static void hardirq_read_latency_static_map(int mapfd) +{ + static hardirq_ebpf_static_val_t *hardirq_ebpf_static_vals = NULL; + if (!hardirq_ebpf_static_vals) + hardirq_ebpf_static_vals = callocz(ebpf_nprocs + 1, sizeof(hardirq_ebpf_static_val_t)); + + uint32_t i; + for (i = 0; i < HARDIRQ_EBPF_STATIC_END; i++) { + uint32_t map_i = hardirq_static_vals[i].idx; + int test = bpf_map_lookup_elem(mapfd, &map_i, hardirq_ebpf_static_vals); + if (unlikely(test < 0)) { + continue; + } + + uint64_t total_latency = 0; + int cpu_i; + int end = (running_on_kernel < NETDATA_KERNEL_V4_15) ? 1 : ebpf_nprocs; + for (cpu_i = 0; cpu_i < end; cpu_i++) { + total_latency += hardirq_ebpf_static_vals[cpu_i].latency/1000; + } + + hardirq_static_vals[i].latency = total_latency; + } +} + +/** + * Read eBPF maps for hard IRQ. + * + * @return When it is not possible to parse /proc, it returns -1, on success it returns 0; + */ +static int hardirq_reader() +{ + if (hardirq_read_latency_map(hardirq_maps[HARDIRQ_MAP_LATENCY].map_fd)) + return -1; + + hardirq_read_latency_static_map(hardirq_maps[HARDIRQ_MAP_LATENCY_STATIC].map_fd); + + return 0; +} + +static void hardirq_create_charts(int update_every) +{ + ebpf_create_chart( + NETDATA_EBPF_SYSTEM_GROUP, + "hardirq_latency", + "Hardware IRQ latency", + EBPF_COMMON_UNITS_MILLISECONDS, + "interrupts", + NETDATA_EBPF_SYSTEM_HARDIRQ_LATENCY_CTX, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_CHART_PRIO_HARDIRQ_LATENCY, + NULL, NULL, 0, update_every, + NETDATA_EBPF_MODULE_NAME_HARDIRQ + ); + + fflush(stdout); +} + +static void hardirq_create_static_dims() +{ + uint32_t i; + for (i = 0; i < HARDIRQ_EBPF_STATIC_END; i++) { + ebpf_write_global_dimension( + hardirq_static_vals[i].name, hardirq_static_vals[i].name, + ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX] + ); + } +} + +// callback for avl tree traversal on `hardirq_pub`. +static int hardirq_write_dims(void *entry, void *data) +{ + UNUSED(data); + + hardirq_val_t *v = entry; + + // IRQs get dynamically added in, so add the dimension if we haven't yet. + if (!v->dim_exists) { + ebpf_write_global_dimension( + v->name, v->name, + ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX] + ); + v->dim_exists = true; + } + + write_chart_dimension(v->name, v->latency); + + return 1; +} + +static inline void hardirq_write_static_dims() +{ + uint32_t i; + for (i = 0; i < HARDIRQ_EBPF_STATIC_END; i++) { + write_chart_dimension( + hardirq_static_vals[i].name, + hardirq_static_vals[i].latency + ); + } +} + +/** +* Main loop for this collector. + * + * @param em the main thread structure. +*/ +static void hardirq_collector(ebpf_module_t *em) +{ + memset(&hardirq_pub, 0, sizeof(hardirq_pub)); + avl_init_lock(&hardirq_pub, hardirq_val_cmp); + ebpf_hardirq_aral_init(); + + // create chart and static dims. + pthread_mutex_lock(&lock); + hardirq_create_charts(em->update_every); + hardirq_create_static_dims(); + ebpf_update_stats(&plugin_statistics, em); + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps, EBPF_ACTION_STAT_ADD); + pthread_mutex_unlock(&lock); + + // loop and read from published data until ebpf plugin is closed. + heartbeat_t hb; + heartbeat_init(&hb); + int update_every = em->update_every; + int counter = update_every - 1; + //This will be cancelled by its parent + uint32_t running_time = 0; + uint32_t lifetime = em->lifetime; + while (!ebpf_plugin_stop() && running_time < lifetime) { + (void)heartbeat_next(&hb, USEC_PER_SEC); + + if (ebpf_plugin_stop() || ++counter != update_every) + continue; + + counter = 0; + if (hardirq_reader()) + break; + + pthread_mutex_lock(&lock); + + // write dims now for all hitherto discovered IRQs. + ebpf_write_begin_chart(NETDATA_EBPF_SYSTEM_GROUP, "hardirq_latency", ""); + avl_traverse_lock(&hardirq_pub, hardirq_write_dims, NULL); + hardirq_write_static_dims(); + ebpf_write_end_chart(); + + pthread_mutex_unlock(&lock); + + pthread_mutex_lock(&ebpf_exit_cleanup); + if (running_time && !em->running_time) + running_time = update_every; + else + running_time += update_every; + + em->running_time = running_time; + pthread_mutex_unlock(&ebpf_exit_cleanup); + } +} + +/***************************************************************** + * EBPF HARDIRQ THREAD + *****************************************************************/ + +/* + * Load BPF + * + * Load BPF files. + * + * @param em the structure with configuration + * + * @return It returns 0 on success and -1 otherwise. + */ +static int ebpf_hardirq_load_bpf(ebpf_module_t *em) +{ + int ret = 0; + if (em->load & EBPF_LOAD_LEGACY) { + em->probe_links = ebpf_load_program(ebpf_plugin_dir, em, running_on_kernel, isrh, &em->objects); + if (!em->probe_links) { + ret = -1; + } + } +#ifdef LIBBPF_MAJOR_VERSION + else { + hardirq_bpf_obj = hardirq_bpf__open(); + if (!hardirq_bpf_obj) + ret = -1; + else { + ret = ebpf_hardirq_load_and_attach(hardirq_bpf_obj); + if (!ret) + ebpf_hardirq_set_hash_table(hardirq_bpf_obj); + } + } +#endif + + return ret; +} + +/** + * Hard IRQ latency thread. + * + * @param ptr a `ebpf_module_t *`. + * @return always NULL. + */ +void *ebpf_hardirq_thread(void *ptr) +{ + ebpf_module_t *em = (ebpf_module_t *)ptr; + + CLEANUP_FUNCTION_REGISTER(hardirq_exit) cleanup_ptr = em; + + em->maps = hardirq_maps; + + if (ebpf_enable_tracepoints(hardirq_tracepoints) == 0) { + goto endhardirq; + } + +#ifdef LIBBPF_MAJOR_VERSION + ebpf_define_map_type(em->maps, em->maps_per_core, running_on_kernel); + ebpf_adjust_thread_load(em, default_btf); +#endif + if (ebpf_hardirq_load_bpf(em)) { + goto endhardirq; + } + + hardirq_collector(em); + +endhardirq: + ebpf_update_disabled_plugin_stats(em); + + return NULL; +} diff --git a/src/collectors/ebpf.plugin/ebpf_hardirq.h b/src/collectors/ebpf.plugin/ebpf_hardirq.h new file mode 100644 index 000000000..fbecac873 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_hardirq.h @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_EBPF_HARDIRQ_H +#define NETDATA_EBPF_HARDIRQ_H 1 + +// Module description +#define NETDATA_EBPF_HARDIRQ_MODULE_DESC "Show time spent servicing individual hardware interrupt requests (hard IRQs)." + +#include <stdint.h> +#include "libnetdata/avl/avl.h" + +/***************************************************************** + * copied from kernel-collectors repo, with modifications needed + * for inclusion here. + *****************************************************************/ + +#define NETDATA_HARDIRQ_NAME_LEN 32 +#define NETDATA_HARDIRQ_MAX_IRQS 1024L + +typedef struct hardirq_ebpf_key { + int irq; +} hardirq_ebpf_key_t; + +enum hardirq_ebpf_static { + HARDIRQ_EBPF_STATIC_APIC_THERMAL, + HARDIRQ_EBPF_STATIC_APIC_THRESHOLD, + HARDIRQ_EBPF_STATIC_APIC_ERROR, + HARDIRQ_EBPF_STATIC_APIC_DEFERRED_ERROR, + HARDIRQ_EBPF_STATIC_APIC_SPURIOUS, + HARDIRQ_EBPF_STATIC_FUNC_CALL, + HARDIRQ_EBPF_STATIC_FUNC_CALL_SINGLE, + HARDIRQ_EBPF_STATIC_RESCHEDULE, + HARDIRQ_EBPF_STATIC_LOCAL_TIMER, + HARDIRQ_EBPF_STATIC_IRQ_WORK, + HARDIRQ_EBPF_STATIC_X86_PLATFORM_IPI, + + HARDIRQ_EBPF_STATIC_END +}; + +enum hardirq_maps { + HARDIRQ_MAP_LATENCY, + HARDIRQ_MAP_LATENCY_STATIC +}; + +typedef struct hardirq_ebpf_static_val { + uint64_t latency; + uint64_t ts; +} hardirq_ebpf_static_val_t; + + +/***************************************************************** + * below this is eBPF plugin-specific code. + *****************************************************************/ + +// ARAL Name +#define NETDATA_EBPF_HARDIRQ_ARAL_NAME "ebpf_harddirq" + +#define NETDATA_EBPF_MODULE_NAME_HARDIRQ "hardirq" +#define NETDATA_HARDIRQ_CONFIG_FILE "hardirq.conf" + +typedef struct hardirq_val { + // must be at top for simplified AVL tree usage. + // if it's not at the top, we need to use `containerof` for almost all ops. + avl_t avl; + + int irq; + bool dim_exists; // keep this after `int irq` for alignment byte savings. + uint64_t latency; + char name[NETDATA_HARDIRQ_NAME_LEN]; +} hardirq_val_t; + +typedef struct hardirq_static_val { + enum hardirq_ebpf_static idx; + char *name; + uint64_t latency; +} hardirq_static_val_t; + +#define NETDATA_EBPF_SYSTEM_HARDIRQ_LATENCY_CTX "system.hardirq_latency" + +extern struct config hardirq_config; +void *ebpf_hardirq_thread(void *ptr); + +#endif /* NETDATA_EBPF_HARDIRQ_H */ diff --git a/src/collectors/ebpf.plugin/ebpf_mdflush.c b/src/collectors/ebpf.plugin/ebpf_mdflush.c new file mode 100644 index 000000000..77c109bff --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_mdflush.c @@ -0,0 +1,455 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ebpf.h" +#include "ebpf_mdflush.h" + +struct config mdflush_config = { .first_section = NULL, + .last_section = NULL, + .mutex = NETDATA_MUTEX_INITIALIZER, + .index = { .avl_tree = { .root = NULL, .compar = appconfig_section_compare }, + .rwlock = AVL_LOCK_INITIALIZER } }; + +#define MDFLUSH_MAP_COUNT 0 +static ebpf_local_maps_t mdflush_maps[] = { + { + .name = "tbl_mdflush", + .internal_input = 1024, + .user_input = 0, + .type = NETDATA_EBPF_MAP_STATIC, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_HASH +#endif + }, + /* end */ + { + .name = NULL, + .internal_input = 0, + .user_input = 0, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED + } +}; + +netdata_ebpf_targets_t mdflush_targets[] = { {.name = "md_flush_request", .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = NULL, .mode = EBPF_LOAD_TRAMPOLINE}}; + + +// store for "published" data from the reader thread, which the collector +// thread will write to netdata agent. +static avl_tree_lock mdflush_pub; + +// tmp store for mdflush values we get from a per-CPU eBPF map. +static mdflush_ebpf_val_t *mdflush_ebpf_vals = NULL; + +#ifdef LIBBPF_MAJOR_VERSION +/** + * Disable probes + * + * Disable probes to use trampolines. + * + * @param obj the loaded object structure. + */ +static inline void ebpf_disable_probes(struct mdflush_bpf *obj) +{ + bpf_program__set_autoload(obj->progs.netdata_md_flush_request_kprobe, false); +} + +/** + * Disable trampolines + * + * Disable trampoliness to use probes. + * + * @param obj the loaded object structure. + */ +static inline void ebpf_disable_trampoline(struct mdflush_bpf *obj) +{ + bpf_program__set_autoload(obj->progs.netdata_md_flush_request_fentry, false); +} + +/** + * Set Trampoline + * + * Define target to attach trampoline + * + * @param obj the loaded object structure. + */ +static void ebpf_set_trampoline_target(struct mdflush_bpf *obj) +{ + bpf_program__set_attach_target(obj->progs.netdata_md_flush_request_fentry, 0, + mdflush_targets[NETDATA_MD_FLUSH_REQUEST].name); +} + +/** + * Load probe + * + * Load probe to monitor internal function. + * + * @param obj the loaded object structure. + */ +static inline int ebpf_load_probes(struct mdflush_bpf *obj) +{ + obj->links.netdata_md_flush_request_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_md_flush_request_kprobe, + false, + mdflush_targets[NETDATA_MD_FLUSH_REQUEST].name); + return libbpf_get_error(obj->links.netdata_md_flush_request_kprobe); +} + +/** + * Load and Attach + * + * Load and attach bpf codes according user selection. + * + * @param obj the loaded object structure. + * @param em the structure with configuration + */ +static inline int ebpf_mdflush_load_and_attach(struct mdflush_bpf *obj, ebpf_module_t *em) +{ + int mode = em->targets[NETDATA_MD_FLUSH_REQUEST].mode; + if (mode == EBPF_LOAD_TRAMPOLINE) { // trampoline + ebpf_disable_probes(obj); + + ebpf_set_trampoline_target(obj); + } else // kprobe + ebpf_disable_trampoline(obj); + + int ret = mdflush_bpf__load(obj); + if (ret) { + fprintf(stderr, "failed to load BPF object: %d\n", ret); + return -1; + } + + if (mode == EBPF_LOAD_TRAMPOLINE) + ret = mdflush_bpf__attach(obj); + else + ret = ebpf_load_probes(obj); + + return ret; +} + +#endif + +/** + * Obsolete global + * + * Obsolete global charts created by thread. + * + * @param em a pointer to `struct ebpf_module` + */ +static void ebpf_obsolete_mdflush_global(ebpf_module_t *em) +{ + ebpf_write_chart_obsolete("mdstat", + "mdstat_flush", + "", + "MD flushes", + "flushes", + "flush (eBPF)", + NETDATA_EBPF_CHART_TYPE_STACKED, + "mdstat.mdstat_flush", + NETDATA_CHART_PRIO_MDSTAT_FLUSH, + em->update_every); +} + +/** + * MDflush exit + * + * Cancel thread and exit. + * + * @param ptr thread data. + */ +static void mdflush_exit(void *pptr) +{ + ebpf_module_t *em = CLEANUP_FUNCTION_GET_PTR(pptr); + if(!em) return; + + if (em->enabled == NETDATA_THREAD_EBPF_FUNCTION_RUNNING) { + pthread_mutex_lock(&lock); + + ebpf_obsolete_mdflush_global(em); + + pthread_mutex_unlock(&lock); + fflush(stdout); + } + + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps, EBPF_ACTION_STAT_REMOVE); + + if (em->objects) { + ebpf_unload_legacy_code(em->objects, em->probe_links); + em->objects = NULL; + em->probe_links = NULL; + } + + pthread_mutex_lock(&ebpf_exit_cleanup); + em->enabled = NETDATA_THREAD_EBPF_STOPPED; + ebpf_update_stats(&plugin_statistics, em); + pthread_mutex_unlock(&ebpf_exit_cleanup); +} + +/** + * Compare mdflush values. + * + * @param a `netdata_mdflush_t *`. + * @param b `netdata_mdflush_t *`. + * + * @return 0 if a==b, 1 if a>b, -1 if a<b. +*/ +static int mdflush_val_cmp(void *a, void *b) +{ + netdata_mdflush_t *ptr1 = a; + netdata_mdflush_t *ptr2 = b; + + if (ptr1->unit > ptr2->unit) { + return 1; + } + else if (ptr1->unit < ptr2->unit) { + return -1; + } + else { + return 0; + } +} + +/** + * Read count map + * + * Read the hash table and store data to allocated vectors. + * + * @param maps_per_core do I need to read all cores? + */ +static void mdflush_read_count_map(int maps_per_core) +{ + int mapfd = mdflush_maps[MDFLUSH_MAP_COUNT].map_fd; + mdflush_ebpf_key_t curr_key = (uint32_t)-1; + mdflush_ebpf_key_t key = (uint32_t)-1; + netdata_mdflush_t search_v; + netdata_mdflush_t *v = NULL; + + while (bpf_map_get_next_key(mapfd, &curr_key, &key) == 0) { + curr_key = key; + + // get val for this key. + int test = bpf_map_lookup_elem(mapfd, &key, mdflush_ebpf_vals); + if (unlikely(test < 0)) { + continue; + } + + // is this record saved yet? + // + // if not, make a new one, mark it as unsaved for now, and continue; we + // will insert it at the end after all of its values are correctly set, + // so that we can safely publish it to the collector within a single, + // short locked operation. + // + // otherwise simply continue; we will only update the flush count, + // which can be republished safely without a lock. + // + // NOTE: lock isn't strictly necessary for this initial search, as only + // this thread does writing, but the AVL is using a read-write lock so + // there is no congestion. + bool v_is_new = false; + search_v.unit = key; + v = (netdata_mdflush_t *)avl_search_lock( + &mdflush_pub, + (avl_t *)&search_v + ); + if (unlikely(v == NULL)) { + // flush count can only be added reliably at a later time. + // when they're added, only then will we AVL insert. + v = callocz(1, sizeof(netdata_mdflush_t)); + v->unit = key; + sprintf(v->disk_name, "md%u", key); + v->dim_exists = false; + + v_is_new = true; + } + + // we must add up count value for this record across all CPUs. + uint64_t total_cnt = 0; + int i; + int end = (!maps_per_core) ? 1 : ebpf_nprocs; + for (i = 0; i < end; i++) { + total_cnt += mdflush_ebpf_vals[i]; + } + + // can now safely publish count for existing records. + v->cnt = total_cnt; + + // can now safely publish new record. + if (v_is_new) { + avl_t *check = avl_insert_lock(&mdflush_pub, (avl_t *)v); + if (check != (avl_t *)v) { + netdata_log_error("Internal error, cannot insert the AVL tree."); + } + } + } +} + +static void mdflush_create_charts(int update_every) +{ + ebpf_create_chart( + "mdstat", + "mdstat_flush", + "MD flushes", + "flushes", + "flush (eBPF)", + "mdstat.mdstat_flush", + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_CHART_PRIO_MDSTAT_FLUSH, + NULL, NULL, 0, update_every, + NETDATA_EBPF_MODULE_NAME_MDFLUSH + ); + + fflush(stdout); +} + +// callback for avl tree traversal on `mdflush_pub`. +static int mdflush_write_dims(void *entry, void *data) +{ + UNUSED(data); + + netdata_mdflush_t *v = entry; + + // records get dynamically added in, so add the dim if we haven't yet. + if (!v->dim_exists) { + ebpf_write_global_dimension( + v->disk_name, v->disk_name, + ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX] + ); + v->dim_exists = true; + } + + write_chart_dimension(v->disk_name, v->cnt); + + return 1; +} + +/** +* Main loop for this collector. +*/ +static void mdflush_collector(ebpf_module_t *em) +{ + mdflush_ebpf_vals = callocz(ebpf_nprocs, sizeof(mdflush_ebpf_val_t)); + + int update_every = em->update_every; + avl_init_lock(&mdflush_pub, mdflush_val_cmp); + + // create chart and static dims. + pthread_mutex_lock(&lock); + mdflush_create_charts(update_every); + ebpf_update_stats(&plugin_statistics, em); + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps, EBPF_ACTION_STAT_ADD); + pthread_mutex_unlock(&lock); + + // loop and read from published data until ebpf plugin is closed. + heartbeat_t hb; + heartbeat_init(&hb); + int counter = update_every - 1; + int maps_per_core = em->maps_per_core; + uint32_t running_time = 0; + uint32_t lifetime = em->lifetime; + while (!ebpf_plugin_stop() && running_time < lifetime) { + (void)heartbeat_next(&hb, USEC_PER_SEC); + + if (ebpf_plugin_stop() || ++counter != update_every) + continue; + + counter = 0; + mdflush_read_count_map(maps_per_core); + pthread_mutex_lock(&lock); + // write dims now for all hitherto discovered devices. + ebpf_write_begin_chart("mdstat", "mdstat_flush", ""); + avl_traverse_lock(&mdflush_pub, mdflush_write_dims, NULL); + ebpf_write_end_chart(); + + pthread_mutex_unlock(&lock); + + pthread_mutex_lock(&ebpf_exit_cleanup); + if (running_time && !em->running_time) + running_time = update_every; + else + running_time += update_every; + + em->running_time = running_time; + pthread_mutex_unlock(&ebpf_exit_cleanup); + } +} + +/* + * Load BPF + * + * Load BPF files. + * + * @param em the structure with configuration + * + * @return It returns 0 on success and -1 otherwise. + */ +static int ebpf_mdflush_load_bpf(ebpf_module_t *em) +{ + int ret = 0; + if (em->load & EBPF_LOAD_LEGACY) { + em->probe_links = ebpf_load_program(ebpf_plugin_dir, em, running_on_kernel, isrh, &em->objects); + if (!em->probe_links) { + ret = -1; + } + } +#ifdef LIBBPF_MAJOR_VERSION + else { + mdflush_bpf_obj = mdflush_bpf__open(); + if (!mdflush_bpf_obj) + ret = -1; + else { + ret = ebpf_mdflush_load_and_attach(mdflush_bpf_obj, em); + if (ret && em->targets[NETDATA_MD_FLUSH_REQUEST].mode == EBPF_LOAD_TRAMPOLINE) { + mdflush_bpf__destroy(mdflush_bpf_obj); + mdflush_bpf_obj = mdflush_bpf__open(); + if (!mdflush_bpf_obj) + ret = -1; + else { + em->targets[NETDATA_MD_FLUSH_REQUEST].mode = EBPF_LOAD_PROBE; + ret = ebpf_mdflush_load_and_attach(mdflush_bpf_obj, em); + } + } + } + } +#endif + + return ret; +} + + +/** + * mdflush thread. + * + * @param ptr a `ebpf_module_t *`. + * @return always NULL. + */ +void *ebpf_mdflush_thread(void *ptr) +{ + ebpf_module_t *em = (ebpf_module_t *)ptr; + CLEANUP_FUNCTION_REGISTER(mdflush_exit) cleanup_ptr = em; + + em->maps = mdflush_maps; + + char *md_flush_request = ebpf_find_symbol("md_flush_request"); + if (!md_flush_request) { + netdata_log_error("Cannot monitor MD devices, because md is not loaded."); + goto endmdflush; + } + +#ifdef LIBBPF_MAJOR_VERSION + ebpf_define_map_type(em->maps, em->maps_per_core, running_on_kernel); + ebpf_adjust_thread_load(em, default_btf); +#endif + if (ebpf_mdflush_load_bpf(em)) { + netdata_log_error("Cannot load eBPF software."); + goto endmdflush; + } + + mdflush_collector(em); + +endmdflush: + freez(md_flush_request); + ebpf_update_disabled_plugin_stats(em); + + return NULL; +} diff --git a/src/collectors/ebpf.plugin/ebpf_mdflush.h b/src/collectors/ebpf.plugin/ebpf_mdflush.h new file mode 100644 index 000000000..629550746 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_mdflush.h @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_EBPF_MDFLUSH_H +#define NETDATA_EBPF_MDFLUSH_H 1 + +// Module name & description +#define NETDATA_EBPF_MODULE_NAME_MDFLUSH "mdflush" +#define NETDATA_EBPF_MD_MODULE_DESC "Show information about multi-device software flushes." + +// charts +#define NETDATA_MDFLUSH_GLOBAL_CHART "mdflush" + +// configuration file +#define NETDATA_DIRECTORY_MDFLUSH_CONFIG_FILE "mdflush.conf" + +// copy of mdflush types from kernel-collectors repo. +typedef uint32_t mdflush_ebpf_key_t; +typedef uint64_t mdflush_ebpf_val_t; + +typedef struct netdata_mdflush { + // must be at top for simplified AVL tree usage. + // if it's not at the top, we need to use `containerof` for almost all ops. + avl_t avl; + + // key & name of device. + // the name is generated by the key, usually as `md<unit>`. + uint32_t unit; + char disk_name[32]; + + // have we defined the dimension for this device yet? + bool dim_exists; + + // incremental flush count value. + uint64_t cnt; +} netdata_mdflush_t; + +enum netdata_mdflush_targets { + NETDATA_MD_FLUSH_REQUEST, + + NETDATA_MD_FLUSH_END +}; + +void *ebpf_mdflush_thread(void *ptr); + +extern struct config mdflush_config; +extern netdata_ebpf_targets_t mdflush_targets[]; + +#endif diff --git a/src/collectors/ebpf.plugin/ebpf_mount.c b/src/collectors/ebpf.plugin/ebpf_mount.c new file mode 100644 index 000000000..7441cc6e2 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_mount.c @@ -0,0 +1,517 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ebpf.h" +#include "ebpf_mount.h" + +static ebpf_local_maps_t mount_maps[] = {{.name = "tbl_mount", .internal_input = NETDATA_MOUNT_END, + .user_input = 0, .type = NETDATA_EBPF_MAP_STATIC, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }, + {.name = NULL, .internal_input = 0, .user_input = 0, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }}; + +static char *mount_dimension_name[NETDATA_EBPF_MOUNT_SYSCALL] = { "mount", "umount" }; +static netdata_syscall_stat_t mount_aggregated_data[NETDATA_EBPF_MOUNT_SYSCALL]; +static netdata_publish_syscall_t mount_publish_aggregated[NETDATA_EBPF_MOUNT_SYSCALL]; + +struct config mount_config = { .first_section = NULL, .last_section = NULL, .mutex = NETDATA_MUTEX_INITIALIZER, + .index = {.avl_tree = { .root = NULL, .compar = appconfig_section_compare }, + .rwlock = AVL_LOCK_INITIALIZER } }; + +static netdata_idx_t mount_hash_values[NETDATA_MOUNT_END]; + +netdata_ebpf_targets_t mount_targets[] = { {.name = "mount", .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = "umount", .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = NULL, .mode = EBPF_LOAD_TRAMPOLINE}}; + +#ifdef LIBBPF_MAJOR_VERSION +/***************************************************************** + * + * BTF FUNCTIONS + * + *****************************************************************/ + +/* + * Disable probe + * + * Disable all probes to use exclusively another method. + * + * @param obj is the main structure for bpf objects. + */ +static inline void ebpf_mount_disable_probe(struct mount_bpf *obj) +{ + bpf_program__set_autoload(obj->progs.netdata_mount_probe, false); + bpf_program__set_autoload(obj->progs.netdata_umount_probe, false); + + bpf_program__set_autoload(obj->progs.netdata_mount_retprobe, false); + bpf_program__set_autoload(obj->progs.netdata_umount_retprobe, false); +} + +/* + * Disable tracepoint + * + * Disable all tracepoints to use exclusively another method. + * + * @param obj is the main structure for bpf objects. + */ +static inline void ebpf_mount_disable_tracepoint(struct mount_bpf *obj) +{ + bpf_program__set_autoload(obj->progs.netdata_mount_exit, false); + bpf_program__set_autoload(obj->progs.netdata_umount_exit, false); +} + +/* + * Disable trampoline + * + * Disable all trampoline to use exclusively another method. + * + * @param obj is the main structure for bpf objects. + */ +static inline void ebpf_mount_disable_trampoline(struct mount_bpf *obj) +{ + bpf_program__set_autoload(obj->progs.netdata_mount_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_umount_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_mount_fexit, false); + bpf_program__set_autoload(obj->progs.netdata_umount_fexit, false); +} + +/** + * Set trampoline target + * + * Set the targets we will monitor. + * + * @param obj is the main structure for bpf objects. + */ +static inline void netdata_set_trampoline_target(struct mount_bpf *obj) +{ + char syscall[NETDATA_EBPF_MAX_SYSCALL_LENGTH + 1]; + ebpf_select_host_prefix(syscall, NETDATA_EBPF_MAX_SYSCALL_LENGTH, + mount_targets[NETDATA_MOUNT_SYSCALL].name, running_on_kernel); + + bpf_program__set_attach_target(obj->progs.netdata_mount_fentry, 0, + syscall); + + bpf_program__set_attach_target(obj->progs.netdata_mount_fexit, 0, + syscall); + + ebpf_select_host_prefix(syscall, NETDATA_EBPF_MAX_SYSCALL_LENGTH, + mount_targets[NETDATA_UMOUNT_SYSCALL].name, running_on_kernel); + + bpf_program__set_attach_target(obj->progs.netdata_umount_fentry, 0, + syscall); + + bpf_program__set_attach_target(obj->progs.netdata_umount_fexit, 0, + syscall); +} + +/** + * Mount Attach Probe + * + * Attach probes to target + * + * @param obj is the main structure for bpf objects. + * + * @return It returns 0 on success and -1 otherwise. + */ +static int ebpf_mount_attach_probe(struct mount_bpf *obj) +{ + char syscall[NETDATA_EBPF_MAX_SYSCALL_LENGTH + 1]; + + ebpf_select_host_prefix(syscall, NETDATA_EBPF_MAX_SYSCALL_LENGTH, + mount_targets[NETDATA_MOUNT_SYSCALL].name, running_on_kernel); + + obj->links.netdata_mount_probe = bpf_program__attach_kprobe(obj->progs.netdata_mount_probe, + false, syscall); + int ret = (int)libbpf_get_error(obj->links.netdata_mount_probe); + if (ret) + return -1; + + obj->links.netdata_mount_retprobe = bpf_program__attach_kprobe(obj->progs.netdata_mount_retprobe, + true, syscall); + ret = (int)libbpf_get_error(obj->links.netdata_mount_retprobe); + if (ret) + return -1; + + ebpf_select_host_prefix(syscall, NETDATA_EBPF_MAX_SYSCALL_LENGTH, + mount_targets[NETDATA_UMOUNT_SYSCALL].name, running_on_kernel); + + obj->links.netdata_umount_probe = bpf_program__attach_kprobe(obj->progs.netdata_umount_probe, + false, syscall); + ret = (int)libbpf_get_error(obj->links.netdata_umount_probe); + if (ret) + return -1; + + obj->links.netdata_umount_retprobe = bpf_program__attach_kprobe(obj->progs.netdata_umount_retprobe, + true, syscall); + ret = (int)libbpf_get_error(obj->links.netdata_umount_retprobe); + if (ret) + return -1; + + return 0; +} + +/** + * Set hash tables + * + * Set the values for maps according the value given by kernel. + * + * @param obj is the main structure for bpf objects. + */ +static void ebpf_mount_set_hash_tables(struct mount_bpf *obj) +{ + mount_maps[NETDATA_KEY_MOUNT_TABLE].map_fd = bpf_map__fd(obj->maps.tbl_mount); +} + +/** + * Load and attach + * + * Load and attach the eBPF code in kernel. + * + * @param obj is the main structure for bpf objects. + * @param em structure with configuration + * + * @return it returns 0 on success and -1 otherwise + */ +static inline int ebpf_mount_load_and_attach(struct mount_bpf *obj, ebpf_module_t *em) +{ + netdata_ebpf_targets_t *mt = em->targets; + netdata_ebpf_program_loaded_t test = mt[NETDATA_MOUNT_SYSCALL].mode; + + // We are testing only one, because all will have the same behavior + if (test == EBPF_LOAD_TRAMPOLINE ) { + ebpf_mount_disable_probe(obj); + ebpf_mount_disable_tracepoint(obj); + + netdata_set_trampoline_target(obj); + } else if (test == EBPF_LOAD_PROBE || + test == EBPF_LOAD_RETPROBE ) { + ebpf_mount_disable_tracepoint(obj); + ebpf_mount_disable_trampoline(obj); + } else { + ebpf_mount_disable_probe(obj); + ebpf_mount_disable_trampoline(obj); + } + + ebpf_update_map_type(obj->maps.tbl_mount, &mount_maps[NETDATA_KEY_MOUNT_TABLE]); + + int ret = mount_bpf__load(obj); + if (!ret) { + if (test != EBPF_LOAD_PROBE && test != EBPF_LOAD_RETPROBE ) + ret = mount_bpf__attach(obj); + else + ret = ebpf_mount_attach_probe(obj); + + if (!ret) + ebpf_mount_set_hash_tables(obj); + } + + return ret; +} +#endif +/***************************************************************** + * + * FUNCTIONS TO CLOSE THE THREAD + * + *****************************************************************/ + +/** + * Obsolete global + * + * Obsolete global charts created by thread. + * + * @param em a pointer to `struct ebpf_module` + */ +static void ebpf_obsolete_mount_global(ebpf_module_t *em) +{ + ebpf_write_chart_obsolete(NETDATA_EBPF_MOUNT_GLOBAL_FAMILY, + NETDATA_EBPF_MOUNT_CALLS, + "", + "Calls to mount and umount syscalls", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_EBPF_MOUNT_FAMILY, + NETDATA_EBPF_CHART_TYPE_LINE, + "mount_points.call", + NETDATA_CHART_PRIO_EBPF_MOUNT_CHARTS, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_EBPF_MOUNT_GLOBAL_FAMILY, + NETDATA_EBPF_MOUNT_ERRORS, + "", + "Errors to mount and umount file systems", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_EBPF_MOUNT_FAMILY, + NETDATA_EBPF_CHART_TYPE_LINE, + "mount_points.error", + NETDATA_CHART_PRIO_EBPF_MOUNT_CHARTS + 1, + em->update_every); +} + +/** + * Mount Exit + * + * Cancel child thread. + * + * @param ptr thread data. + */ +static void ebpf_mount_exit(void *pptr) +{ + ebpf_module_t *em = CLEANUP_FUNCTION_GET_PTR(pptr); + if(!em) return; + + if (em->enabled == NETDATA_THREAD_EBPF_FUNCTION_RUNNING) { + pthread_mutex_lock(&lock); + + ebpf_obsolete_mount_global(em); + + fflush(stdout); + pthread_mutex_unlock(&lock); + } + + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps, EBPF_ACTION_STAT_REMOVE); + +#ifdef LIBBPF_MAJOR_VERSION + if (mount_bpf_obj) { + mount_bpf__destroy(mount_bpf_obj); + mount_bpf_obj = NULL; + } +#endif + if (em->objects) { + ebpf_unload_legacy_code(em->objects, em->probe_links); + em->objects = NULL; + em->probe_links = NULL; + } + + pthread_mutex_lock(&ebpf_exit_cleanup); + em->enabled = NETDATA_THREAD_EBPF_STOPPED; + ebpf_update_stats(&plugin_statistics, em); + pthread_mutex_unlock(&ebpf_exit_cleanup); +} + +/***************************************************************** + * + * MAIN LOOP + * + *****************************************************************/ + +/** + * Read global table + * + * Read the table with number of calls for all functions + * + * @param maps_per_core do I need to read all cores? + */ +static void ebpf_mount_read_global_table(int maps_per_core) +{ + static netdata_idx_t *mount_values = NULL; + if (!mount_values) + mount_values = callocz((size_t)ebpf_nprocs + 1, sizeof(netdata_idx_t)); + + uint32_t idx; + netdata_idx_t *val = mount_hash_values; + netdata_idx_t *stored = mount_values; + size_t length = sizeof(netdata_idx_t); + if (maps_per_core) + length *= ebpf_nprocs; + + int fd = mount_maps[NETDATA_KEY_MOUNT_TABLE].map_fd; + + for (idx = NETDATA_KEY_MOUNT_CALL; idx < NETDATA_MOUNT_END; idx++) { + if (!bpf_map_lookup_elem(fd, &idx, stored)) { + int i; + int end = (maps_per_core) ? ebpf_nprocs : 1; + netdata_idx_t total = 0; + for (i = 0; i < end; i++) + total += stored[i]; + + val[idx] = total; + memset(stored, 0, length); + } + } +} + +/** + * Send data to Netdata calling auxiliary functions. +*/ +static void ebpf_mount_send_data() +{ + int i, j; + int end = NETDATA_EBPF_MOUNT_SYSCALL; + for (i = NETDATA_KEY_MOUNT_CALL, j = NETDATA_KEY_MOUNT_ERROR; i < end; i++, j++) { + mount_publish_aggregated[i].ncall = mount_hash_values[i]; + mount_publish_aggregated[i].nerr = mount_hash_values[j]; + } + + write_count_chart(NETDATA_EBPF_MOUNT_CALLS, NETDATA_EBPF_MOUNT_GLOBAL_FAMILY, + mount_publish_aggregated, NETDATA_EBPF_MOUNT_SYSCALL); + + write_err_chart(NETDATA_EBPF_MOUNT_ERRORS, NETDATA_EBPF_MOUNT_GLOBAL_FAMILY, + mount_publish_aggregated, NETDATA_EBPF_MOUNT_SYSCALL); +} + +/** +* Main loop for this collector. +*/ +static void mount_collector(ebpf_module_t *em) +{ + memset(mount_hash_values, 0, sizeof(mount_hash_values)); + + heartbeat_t hb; + heartbeat_init(&hb); + int update_every = em->update_every; + int counter = update_every - 1; + int maps_per_core = em->maps_per_core; + uint32_t running_time = 0; + uint32_t lifetime = em->lifetime; + while (!ebpf_plugin_stop() && running_time < lifetime) { + (void)heartbeat_next(&hb, USEC_PER_SEC); + if (ebpf_plugin_stop() || ++counter != update_every) + continue; + + counter = 0; + ebpf_mount_read_global_table(maps_per_core); + pthread_mutex_lock(&lock); + + ebpf_mount_send_data(); + + pthread_mutex_unlock(&lock); + + pthread_mutex_lock(&ebpf_exit_cleanup); + if (running_time && !em->running_time) + running_time = update_every; + else + running_time += update_every; + + em->running_time = running_time; + pthread_mutex_unlock(&ebpf_exit_cleanup); + } +} + +/***************************************************************** + * + * INITIALIZE THREAD + * + *****************************************************************/ + +/** + * Create mount charts + * + * Call ebpf_create_chart to create the charts for the collector. + * + * @param update_every value to overwrite the update frequency set by the server. + */ +static void ebpf_create_mount_charts(int update_every) +{ + ebpf_create_chart(NETDATA_EBPF_MOUNT_GLOBAL_FAMILY, NETDATA_EBPF_MOUNT_CALLS, + "Calls to mount and umount syscalls", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_EBPF_MOUNT_FAMILY, + "mount_points.call", + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_EBPF_MOUNT_CHARTS, + ebpf_create_global_dimension, + mount_publish_aggregated, NETDATA_EBPF_MOUNT_SYSCALL, + update_every, NETDATA_EBPF_MODULE_NAME_MOUNT); + + ebpf_create_chart(NETDATA_EBPF_MOUNT_GLOBAL_FAMILY, NETDATA_EBPF_MOUNT_ERRORS, + "Errors to mount and umount file systems", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_EBPF_MOUNT_FAMILY, + "mount_points.error", + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_EBPF_MOUNT_CHARTS + 1, + ebpf_create_global_dimension, + mount_publish_aggregated, NETDATA_EBPF_MOUNT_SYSCALL, + update_every, NETDATA_EBPF_MODULE_NAME_MOUNT); + + fflush(stdout); +} + +/***************************************************************** + * + * MAIN THREAD + * + *****************************************************************/ + +/* + * Load BPF + * + * Load BPF files. + * + * @param em the structure with configuration + */ +static int ebpf_mount_load_bpf(ebpf_module_t *em) +{ +#ifdef LIBBPF_MAJOR_VERSION + ebpf_define_map_type(em->maps, em->maps_per_core, running_on_kernel); +#endif + + int ret = 0; + if (em->load & EBPF_LOAD_LEGACY) { + em->probe_links = ebpf_load_program(ebpf_plugin_dir, em, running_on_kernel, isrh, &em->objects); + if (!em->probe_links) { + ret = -1; + } + } +#ifdef LIBBPF_MAJOR_VERSION + else { + mount_bpf_obj = mount_bpf__open(); + if (!mount_bpf_obj) + ret = -1; + else + ret = ebpf_mount_load_and_attach(mount_bpf_obj, em); + } +#endif + + if (ret) + netdata_log_error("%s %s", EBPF_DEFAULT_ERROR_MSG, em->info.thread_name); + + return ret; +} + +/** + * Mount thread + * + * Thread used to make mount thread + * + * @param ptr a pointer to `struct ebpf_module` + * + * @return It always returns NULL + */ +void *ebpf_mount_thread(void *ptr) +{ + ebpf_module_t *em = ptr; + CLEANUP_FUNCTION_REGISTER(ebpf_mount_exit) cleanup_ptr = em; + + em->maps = mount_maps; + +#ifdef LIBBPF_MAJOR_VERSION + ebpf_adjust_thread_load(em, default_btf); +#endif + if (ebpf_mount_load_bpf(em)) { + goto endmount; + } + + int algorithms[NETDATA_EBPF_MOUNT_SYSCALL] = { NETDATA_EBPF_INCREMENTAL_IDX, NETDATA_EBPF_INCREMENTAL_IDX }; + + ebpf_global_labels(mount_aggregated_data, mount_publish_aggregated, mount_dimension_name, mount_dimension_name, + algorithms, NETDATA_EBPF_MOUNT_SYSCALL); + + pthread_mutex_lock(&lock); + ebpf_create_mount_charts(em->update_every); + ebpf_update_stats(&plugin_statistics, em); + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps, EBPF_ACTION_STAT_ADD); + pthread_mutex_unlock(&lock); + + mount_collector(em); + +endmount: + ebpf_update_disabled_plugin_stats(em); + + return NULL; +} diff --git a/src/collectors/ebpf.plugin/ebpf_mount.h b/src/collectors/ebpf.plugin/ebpf_mount.h new file mode 100644 index 000000000..768914b02 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_mount.h @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_EBPF_MOUNT_H +#define NETDATA_EBPF_MOUNT_H 1 + +// Module name & description +#define NETDATA_EBPF_MODULE_NAME_MOUNT "mount" +#define NETDATA_EBPF_MOUNT_MODULE_DESC "Show calls to syscalls mount(2) and umount(2)." + +#define NETDATA_EBPF_MOUNT_SYSCALL 2 + +#define NETDATA_EBPF_MOUNT_CALLS "call" +#define NETDATA_EBPF_MOUNT_ERRORS "error" +#define NETDATA_EBPF_MOUNT_FAMILY "mount (eBPF)" + +// Process configuration name +#define NETDATA_MOUNT_CONFIG_FILE "mount.conf" + +enum mount_counters { + NETDATA_KEY_MOUNT_CALL, + NETDATA_KEY_UMOUNT_CALL, + NETDATA_KEY_MOUNT_ERROR, + NETDATA_KEY_UMOUNT_ERROR, + + NETDATA_MOUNT_END +}; + +enum mount_tables { + NETDATA_KEY_MOUNT_TABLE +}; + +enum netdata_mount_syscalls { + NETDATA_MOUNT_SYSCALL, + NETDATA_UMOUNT_SYSCALL, + + NETDATA_MOUNT_SYSCALLS_END +}; + +extern struct config mount_config; +void *ebpf_mount_thread(void *ptr); +extern netdata_ebpf_targets_t mount_targets[]; + +#endif /* NETDATA_EBPF_MOUNT_H */ diff --git a/src/collectors/ebpf.plugin/ebpf_oomkill.c b/src/collectors/ebpf.plugin/ebpf_oomkill.c new file mode 100644 index 000000000..8ecd0883c --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_oomkill.c @@ -0,0 +1,593 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ebpf.h" +#include "ebpf_oomkill.h" + +struct config oomkill_config = { .first_section = NULL, + .last_section = NULL, + .mutex = NETDATA_MUTEX_INITIALIZER, + .index = { .avl_tree = { .root = NULL, .compar = appconfig_section_compare }, + .rwlock = AVL_LOCK_INITIALIZER } }; + +#define OOMKILL_MAP_KILLCNT 0 +static ebpf_local_maps_t oomkill_maps[] = { + { + .name = "tbl_oomkill", + .internal_input = NETDATA_OOMKILL_MAX_ENTRIES, + .user_input = 0, + .type = NETDATA_EBPF_MAP_STATIC, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_HASH +#endif + }, + /* end */ + { + .name = NULL, + .internal_input = 0, + .user_input = 0, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_HASH +#endif + } +}; + +static ebpf_tracepoint_t oomkill_tracepoints[] = { + {.enabled = false, .class = "oom", .event = "mark_victim"}, + /* end */ + {.enabled = false, .class = NULL, .event = NULL} +}; + +static netdata_publish_syscall_t oomkill_publish_aggregated = {.name = "kills", .dimension = "kills", + .algorithm = "absolute", + .next = NULL}; + +static void ebpf_obsolete_specific_oomkill_charts(char *type, int update_every); + +/** + * Obsolete services + * + * Obsolete all service charts created + * + * @param em a pointer to `struct ebpf_module` + */ +static void ebpf_obsolete_oomkill_services(ebpf_module_t *em, char *id) +{ + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_OOMKILL_CHART, + "Systemd service OOM kills.", + EBPF_OOMKILL_UNIT_KILLS, + NETDATA_EBPF_MEMORY_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_CGROUP_OOMKILLS_CONTEXT, + 20191, + em->update_every); +} + +/** + * Obsolete cgroup chart + * + * Send obsolete for all charts created before to close. + * + * @param em a pointer to `struct ebpf_module` + */ +static inline void ebpf_obsolete_oomkill_cgroup_charts(ebpf_module_t *em) +{ + pthread_mutex_lock(&mutex_cgroup_shm); + + ebpf_cgroup_target_t *ect; + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + if (ect->systemd) { + ebpf_obsolete_oomkill_services(em, ect->name); + + continue; + } + + ebpf_obsolete_specific_oomkill_charts(ect->name, em->update_every); + } + pthread_mutex_unlock(&mutex_cgroup_shm); +} + +/** + * Obsolete global + * + * Obsolete global charts created by thread. + * + * @param em a pointer to `struct ebpf_module` + */ +static void ebpf_obsolete_oomkill_apps(ebpf_module_t *em) +{ + struct ebpf_target *w; + int update_every = em->update_every; + pthread_mutex_lock(&collect_data_mutex); + for (w = apps_groups_root_target; w; w = w->next) { + if (unlikely(!(w->charts_created & (1<<EBPF_MODULE_OOMKILL_IDX)))) + continue; + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + NETDATA_OOMKILL_CHART, + "Processes OOM kills.", + EBPF_OOMKILL_UNIT_KILLS, + NETDATA_EBPF_MEMORY_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "ebpf.app_oomkill", + 20072, + update_every); + + w->charts_created &= ~(1<<EBPF_MODULE_OOMKILL_IDX); + } + pthread_mutex_unlock(&collect_data_mutex); +} + +/** + * Clean up the main thread. + * + * @param ptr thread data. + */ +static void oomkill_cleanup(void *pptr) +{ + ebpf_module_t *em = CLEANUP_FUNCTION_GET_PTR(pptr); + if(!em) return; + + if (em->enabled == NETDATA_THREAD_EBPF_FUNCTION_RUNNING) { + pthread_mutex_lock(&lock); + + if (em->cgroup_charts) { + ebpf_obsolete_oomkill_cgroup_charts(em); + } + + ebpf_obsolete_oomkill_apps(em); + + fflush(stdout); + pthread_mutex_unlock(&lock); + } + + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps, EBPF_ACTION_STAT_REMOVE); + + if (em->objects) { + ebpf_unload_legacy_code(em->objects, em->probe_links); + em->objects = NULL; + em->probe_links = NULL; + } + + pthread_mutex_lock(&ebpf_exit_cleanup); + em->enabled = NETDATA_THREAD_EBPF_STOPPED; + ebpf_update_stats(&plugin_statistics, em); + pthread_mutex_unlock(&ebpf_exit_cleanup); +} + +static void oomkill_write_data(int32_t *keys, uint32_t total) +{ + // for each app, see if it was OOM killed. record as 1 if so otherwise 0. + struct ebpf_target *w; + uint32_t used_pid = 0; + pthread_mutex_lock(&collect_data_mutex); + for (w = apps_groups_root_target; w != NULL; w = w->next) { + if (unlikely(!(w->charts_created & (1 << EBPF_MODULE_OOMKILL_IDX)))) + continue; + + bool was_oomkilled = false; + if (total) { + struct ebpf_pid_on_target *pids = w->root_pid; + while (pids) { + uint32_t j; + for (j = 0; j < total; j++) { + if (pids->pid == keys[j]) { + used_pid++; + was_oomkilled = true; + // set to 0 so we consider it "done". + keys[j] = 0; + goto write_dim; + } + } + pids = pids->next; + } + } +write_dim: + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, NETDATA_OOMKILL_CHART); + write_chart_dimension(oomkill_publish_aggregated.dimension, was_oomkilled); + ebpf_write_end_chart(); + } + + if (total != used_pid) { + // for any remaining keys for which we couldn't find a group, this could be + // for various reasons, but the primary one is that the PID has not yet + // been picked up by the process thread when parsing the proc filesystem. + // since it's been OOM killed, it will never be parsed in the future, so + // we have no choice but to dump it into `other`. + uint32_t rem_count = total - used_pid; + ebpf_write_begin_chart(NETDATA_APP_FAMILY, "other", NETDATA_OOMKILL_CHART); + write_chart_dimension(oomkill_publish_aggregated.dimension, rem_count); + ebpf_write_end_chart(); + } + + pthread_mutex_unlock(&collect_data_mutex); +} + +/** + * Create specific OOMkill charts + * + * Create charts for cgroup/application. + * + * @param type the chart type. + * @param update_every value to overwrite the update frequency set by the server. + */ +static void ebpf_create_specific_oomkill_charts(char *type, int update_every) +{ + ebpf_create_chart(type, NETDATA_OOMKILL_CHART, "Cgroup OOM kills.", + EBPF_OOMKILL_UNIT_KILLS, NETDATA_EBPF_MEMORY_GROUP, + NETDATA_CGROUP_OOMKILLS_CONTEXT, NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5600, + ebpf_create_global_dimension, + &oomkill_publish_aggregated, 1, update_every, NETDATA_EBPF_MODULE_NAME_OOMKILL); +} + +/** + * Create Systemd OOMkill Charts + * + * Create charts when systemd is enabled + * + * @param update_every value to overwrite the update frequency set by the server. + **/ +static void ebpf_create_systemd_oomkill_charts(int update_every) +{ + static ebpf_systemd_args_t data_oom = { + .title = "Systemd service OOM kills.", + .units = EBPF_OOMKILL_UNIT_KILLS, + .family = NETDATA_EBPF_MEMORY_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20191, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_CGROUP_OOMKILLS_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_OOMKILL, + .update_every = 0, + .suffix = NETDATA_OOMKILL_CHART, + .dimension = "kills" + }; + + if (!data_oom.update_every) + data_oom.update_every = update_every; + + ebpf_cgroup_target_t *w; + for (w = ebpf_cgroup_pids; w ; w = w->next) { + if (unlikely(!w->systemd || w->flags & NETDATA_EBPF_SERVICES_HAS_OOMKILL_CHART)) + continue; + + data_oom.id = w->name; + ebpf_create_charts_on_systemd(&data_oom); + + w->flags |= NETDATA_EBPF_SERVICES_HAS_OOMKILL_CHART; + } +} + +/** + * Send Systemd charts + * + * Send collected data to Netdata. + */ +static void ebpf_send_systemd_oomkill_charts() +{ + ebpf_cgroup_target_t *ect; + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + if (unlikely(!(ect->flags & NETDATA_EBPF_SERVICES_HAS_OOMKILL_CHART)) ) { + continue; + } + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_OOMKILL_CHART); + write_chart_dimension(oomkill_publish_aggregated.dimension, (long long) ect->oomkill); + ect->oomkill = 0; + ebpf_write_end_chart(); + } +} + +/* + * Send Specific OOMkill data + * + * Send data for specific cgroup/apps. + * + * @param type chart type + * @param value value for oomkill + */ +static void ebpf_send_specific_oomkill_data(char *type, int value) +{ + ebpf_write_begin_chart(type, NETDATA_OOMKILL_CHART, ""); + write_chart_dimension(oomkill_publish_aggregated.dimension, (long long)value); + ebpf_write_end_chart(); +} + +/** + * Create specific OOMkill charts + * + * Create charts for cgroup/application. + * + * @param type the chart type. + * @param update_every value to overwrite the update frequency set by the server. + */ +static void ebpf_obsolete_specific_oomkill_charts(char *type, int update_every) +{ + ebpf_write_chart_obsolete(type, NETDATA_OOMKILL_CHART, "", "Cgroup OOM kills.", + EBPF_OOMKILL_UNIT_KILLS, NETDATA_EBPF_MEMORY_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_OOMKILLS_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5600, update_every); +} + +/** + * Send data to Netdata calling auxiliary functions. + * + * @param update_every value to overwrite the update frequency set by the server. +*/ +void ebpf_oomkill_send_cgroup_data(int update_every) +{ + pthread_mutex_lock(&mutex_cgroup_shm); + ebpf_cgroup_target_t *ect; + + if (shm_ebpf_cgroup.header->systemd_enabled) { + if (send_cgroup_chart) { + ebpf_create_systemd_oomkill_charts(update_every); + } + ebpf_send_systemd_oomkill_charts(); + } + + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + if (ect->systemd) + continue; + + if (!(ect->flags & NETDATA_EBPF_CGROUP_HAS_OOMKILL_CHART) && ect->updated) { + ebpf_create_specific_oomkill_charts(ect->name, update_every); + ect->flags |= NETDATA_EBPF_CGROUP_HAS_OOMKILL_CHART; + } + + if (ect->flags & NETDATA_EBPF_CGROUP_HAS_OOMKILL_CHART) { + if (ect->updated) { + ebpf_send_specific_oomkill_data(ect->name, ect->oomkill); + } else { + ebpf_obsolete_specific_oomkill_charts(ect->name, update_every); + ect->flags &= ~NETDATA_EBPF_CGROUP_HAS_OOMKILL_CHART; + } + } + } + + pthread_mutex_unlock(&mutex_cgroup_shm); +} + +/** + * Read data + * + * Read OOMKILL events from table. + * + * @param keys vector where data will be stored + * + * @return It returns the number of read elements + */ +static uint32_t oomkill_read_data(int32_t *keys) +{ + // the first `i` entries of `keys` will contain the currently active PIDs + // in the eBPF map. + uint32_t i = 0; + + uint32_t curr_key = 0; + uint32_t key = 0; + int mapfd = oomkill_maps[OOMKILL_MAP_KILLCNT].map_fd; + uint32_t limit = NETDATA_OOMKILL_MAX_ENTRIES -1; + while (bpf_map_get_next_key(mapfd, &curr_key, &key) == 0) { + curr_key = key; + + keys[i] = (int32_t)key; + i += 1; + + // delete this key now that we've recorded its existence. there's no + // race here, as the same PID will only get OOM killed once. + int test = bpf_map_delete_elem(mapfd, &key); + if (unlikely(test < 0)) { + // since there's only 1 thread doing these deletions, it should be + // impossible to get this condition. + netdata_log_error("key unexpectedly not available for deletion."); + } + if (i > limit) + break; + } + + return i; +} + +/** + * Update cgroup + * + * Update cgroup data based in + * + * @param keys vector with pids that had oomkill event + * @param total number of elements in keys vector. + */ +static void ebpf_update_oomkill_cgroup(int32_t *keys, uint32_t total) +{ + ebpf_cgroup_target_t *ect; + pthread_mutex_lock(&mutex_cgroup_shm); + for (ect = ebpf_cgroup_pids; ect; ect = ect->next) { + ect->oomkill = 0; + struct pid_on_target2 *pids; + for (pids = ect->pids; pids; pids = pids->next) { + uint32_t j; + int32_t pid = pids->pid; + for (j = 0; j < total; j++) { + if (pid == keys[j]) { + ect->oomkill = 1; + break; + } + } + } + } + pthread_mutex_unlock(&mutex_cgroup_shm); +} + +/** + * Update OOMkill period + * + * Update oomkill period according function arguments. + * + * @param running_time current value of running_value. + * @param em the thread main structure. + * + * @return It returns new running_time value. + */ +static int ebpf_update_oomkill_period(int running_time, ebpf_module_t *em) +{ + pthread_mutex_lock(&ebpf_exit_cleanup); + if (running_time && !em->running_time) + running_time = em->update_every; + else + running_time += em->update_every; + + em->running_time = running_time; + pthread_mutex_unlock(&ebpf_exit_cleanup); + + return running_time; +} + +/** +* Main loop for this collector. + * + * @param em the thread main structure. +*/ +static void oomkill_collector(ebpf_module_t *em) +{ + int cgroups = em->cgroup_charts; + int update_every = em->update_every; + int32_t keys[NETDATA_OOMKILL_MAX_ENTRIES]; + memset(keys, 0, sizeof(keys)); + + // loop and read until ebpf plugin is closed. + heartbeat_t hb; + heartbeat_init(&hb); + int counter = update_every - 1; + uint32_t running_time = 0; + uint32_t lifetime = em->lifetime; + netdata_idx_t *stats = em->hash_table_stats; + while (!ebpf_plugin_stop() && running_time < lifetime) { + (void)heartbeat_next(&hb, USEC_PER_SEC); + if (ebpf_plugin_stop() || ++counter != update_every) + continue; + + counter = 0; + + uint32_t count = oomkill_read_data(keys); + + stats[NETDATA_CONTROLLER_PID_TABLE_ADD] += (uint64_t) count; + stats[NETDATA_CONTROLLER_PID_TABLE_DEL] += (uint64_t) count; + + if (cgroups && shm_ebpf_cgroup.header) + ebpf_update_oomkill_cgroup(keys, count); + + netdata_apps_integration_flags_t apps = em->apps_charts; + pthread_mutex_lock(&lock); + // write everything from the ebpf map. + if (cgroups && shm_ebpf_cgroup.header) + ebpf_oomkill_send_cgroup_data(update_every); + + if (apps & NETDATA_EBPF_APPS_FLAG_CHART_CREATED) + oomkill_write_data(keys, count); + + pthread_mutex_unlock(&lock); + + running_time = ebpf_update_oomkill_period(running_time, em); + } +} + +/** + * Create apps charts + * + * Call ebpf_create_chart to create the charts on apps submenu. + * + * @param em a pointer to the structure with the default values. + */ +void ebpf_oomkill_create_apps_charts(struct ebpf_module *em, void *ptr) +{ + struct ebpf_target *root = ptr; + struct ebpf_target *w; + int update_every = em->update_every; + for (w = root; w; w = w->next) { + if (unlikely(!w->exposed)) + continue; + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + NETDATA_OOMKILL_CHART, + "Processes OOM kills.", + EBPF_OOMKILL_UNIT_KILLS, + NETDATA_EBPF_MEMORY_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_oomkill", + 20072, + update_every, + NETDATA_EBPF_MODULE_NAME_OOMKILL); + ebpf_create_chart_labels("app_group", w->clean_name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION '%s' '' %s 1 1\n", + oomkill_publish_aggregated.dimension, + ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX]); + + w->charts_created |= 1<<EBPF_MODULE_OOMKILL_IDX; + } + + em->apps_charts |= NETDATA_EBPF_APPS_FLAG_CHART_CREATED; +} + +/** + * OOM kill tracking thread. + * + * @param ptr a `ebpf_module_t *`. + * @return always NULL. + */ +void *ebpf_oomkill_thread(void *ptr) +{ + ebpf_module_t *em = (ebpf_module_t *)ptr; + + CLEANUP_FUNCTION_REGISTER(oomkill_cleanup) cleanup_ptr = em; + + em->maps = oomkill_maps; + +#define NETDATA_DEFAULT_OOM_DISABLED_MSG "Disabling OOMKILL thread, because" + if (unlikely(!ebpf_all_pids || !em->apps_charts)) { + // When we are not running integration with apps, we won't fill necessary variables for this thread to run, so + // we need to disable it. + pthread_mutex_lock(&ebpf_exit_cleanup); + if (em->enabled) + netdata_log_info("%s apps integration is completely disabled.", NETDATA_DEFAULT_OOM_DISABLED_MSG); + pthread_mutex_unlock(&ebpf_exit_cleanup); + + goto endoomkill; + } else if (running_on_kernel < NETDATA_EBPF_KERNEL_4_14) { + pthread_mutex_lock(&ebpf_exit_cleanup); + if (em->enabled) + netdata_log_info("%s kernel does not have necessary tracepoints.", NETDATA_DEFAULT_OOM_DISABLED_MSG); + pthread_mutex_unlock(&ebpf_exit_cleanup); + + goto endoomkill; + } + + if (ebpf_enable_tracepoints(oomkill_tracepoints) == 0) { + goto endoomkill; + } + +#ifdef LIBBPF_MAJOR_VERSION + ebpf_define_map_type(em->maps, em->maps_per_core, running_on_kernel); +#endif + em->probe_links = ebpf_load_program(ebpf_plugin_dir, em, running_on_kernel, isrh, &em->objects); + if (!em->probe_links) { + goto endoomkill; + } + + pthread_mutex_lock(&lock); + ebpf_update_stats(&plugin_statistics, em); + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps, EBPF_ACTION_STAT_ADD); + pthread_mutex_unlock(&lock); + + oomkill_collector(em); + +endoomkill: + ebpf_update_disabled_plugin_stats(em); + + return NULL; +} diff --git a/src/collectors/ebpf.plugin/ebpf_oomkill.h b/src/collectors/ebpf.plugin/ebpf_oomkill.h new file mode 100644 index 000000000..0d02da9d3 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_oomkill.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_EBPF_OOMKILL_H +#define NETDATA_EBPF_OOMKILL_H 1 + +// Module description +#define NETDATA_EBPF_OOMKILL_MODULE_DESC "Show OOM kills for all applications recognized via the apps.plugin." + +/***************************************************************** + * copied from kernel-collectors repo, with modifications needed + * for inclusion here. + *****************************************************************/ + +#define NETDATA_OOMKILL_MAX_ENTRIES 64 + +typedef uint8_t oomkill_ebpf_val_t; + +/***************************************************************** + * below this is eBPF plugin-specific code. + *****************************************************************/ + +#define NETDATA_EBPF_MODULE_NAME_OOMKILL "oomkill" +#define NETDATA_OOMKILL_CONFIG_FILE "oomkill.conf" + +#define NETDATA_OOMKILL_CHART "_ebpf_oomkill" + +#define EBPF_OOMKILL_UNIT_KILLS "kills/s" + +// Contexts +#define NETDATA_CGROUP_OOMKILLS_CONTEXT "cgroup.oomkills" + +extern struct config oomkill_config; +void *ebpf_oomkill_thread(void *ptr); +void ebpf_oomkill_create_apps_charts(struct ebpf_module *em, void *ptr); + +#endif /* NETDATA_EBPF_OOMKILL_H */ diff --git a/src/collectors/ebpf.plugin/ebpf_process.c b/src/collectors/ebpf.plugin/ebpf_process.c new file mode 100644 index 000000000..e5756fa3c --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_process.c @@ -0,0 +1,1328 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ebpf.h" +#include "ebpf_process.h" + +/***************************************************************** + * + * GLOBAL VARIABLES + * + *****************************************************************/ + +static char *process_dimension_names[NETDATA_KEY_PUBLISH_PROCESS_END] = { "process", "task", "process", "thread" }; +static char *process_id_names[NETDATA_KEY_PUBLISH_PROCESS_END] = { "do_exit", "release_task", "_do_fork", "sys_clone" }; +static char *status[] = { "process", "zombie" }; + +static ebpf_local_maps_t process_maps[] = {{.name = "tbl_pid_stats", .internal_input = ND_EBPF_DEFAULT_PID_SIZE, + .user_input = 0, + .type = NETDATA_EBPF_MAP_RESIZABLE | NETDATA_EBPF_MAP_PID, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_HASH +#endif + }, + {.name = "tbl_total_stats", .internal_input = NETDATA_KEY_END_VECTOR, + .user_input = 0, .type = NETDATA_EBPF_MAP_STATIC, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }, + {.name = "process_ctrl", .internal_input = NETDATA_CONTROLLER_END, + .user_input = 0, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }, + {.name = NULL, .internal_input = 0, .user_input = 0, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }}; + +char *tracepoint_sched_type = { "sched" } ; +char *tracepoint_sched_process_exit = { "sched_process_exit" }; +char *tracepoint_sched_process_exec = { "sched_process_exec" }; +char *tracepoint_sched_process_fork = { "sched_process_fork" }; +static int was_sched_process_exit_enabled = 0; +static int was_sched_process_exec_enabled = 0; +static int was_sched_process_fork_enabled = 0; + +static netdata_idx_t *process_hash_values = NULL; +ebpf_process_stat_t *process_stat_vector = NULL; +static netdata_syscall_stat_t process_aggregated_data[NETDATA_KEY_PUBLISH_PROCESS_END]; +static netdata_publish_syscall_t process_publish_aggregated[NETDATA_KEY_PUBLISH_PROCESS_END]; + +struct config process_config = { .first_section = NULL, + .last_section = NULL, + .mutex = NETDATA_MUTEX_INITIALIZER, + .index = { .avl_tree = { .root = NULL, .compar = appconfig_section_compare }, + .rwlock = AVL_LOCK_INITIALIZER } }; + +/***************************************************************** + * + * 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_global_publish(netdata_publish_syscall_t *publish, netdata_publish_vfs_common_t *pvc, + netdata_syscall_stat_t *input) +{ + netdata_publish_syscall_t *move = publish; + int selector = NETDATA_KEY_PUBLISH_PROCESS_EXIT; + while (move) { + 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; + + move->pcall = input->call; + move->pbyte = input->bytes; + move->perr = input->ecall; + + input = input->next; + move = move->next; + selector++; + } + + pvc->running = (long)publish[NETDATA_KEY_PUBLISH_PROCESS_FORK].ncall - + (long)publish[NETDATA_KEY_PUBLISH_PROCESS_CLONE].ncall; + publish[NETDATA_KEY_PUBLISH_PROCESS_RELEASE_TASK].ncall = -publish[NETDATA_KEY_PUBLISH_PROCESS_RELEASE_TASK].ncall; + pvc->zombie = (long)publish[NETDATA_KEY_PUBLISH_PROCESS_EXIT].ncall + + (long)publish[NETDATA_KEY_PUBLISH_PROCESS_RELEASE_TASK].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) +{ + ebpf_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); + + ebpf_write_end_chart(); +} + +/** + * Send data to Netdata calling auxiliary 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_global_publish(process_publish_aggregated, &pvc, process_aggregated_data); + + write_count_chart(NETDATA_EXIT_SYSCALL, NETDATA_EBPF_SYSTEM_GROUP, + &process_publish_aggregated[NETDATA_KEY_PUBLISH_PROCESS_EXIT], 2); + write_count_chart(NETDATA_PROCESS_SYSCALL, NETDATA_EBPF_SYSTEM_GROUP, + &process_publish_aggregated[NETDATA_KEY_PUBLISH_PROCESS_FORK], 2); + + write_status_chart(NETDATA_EBPF_SYSTEM_GROUP, &pvc); + if (em->mode < MODE_ENTRY) { + write_err_chart(NETDATA_PROCESS_ERROR_NAME, NETDATA_EBPF_SYSTEM_GROUP, + &process_publish_aggregated[NETDATA_KEY_PUBLISH_PROCESS_FORK], 2); + } +} + +/** + * Send data to Netdata calling auxiliary functions. + * + * @param root the target list. + */ +void ebpf_process_send_apps_data(struct ebpf_target *root, ebpf_module_t *em) +{ + struct ebpf_target *w; + + for (w = root; w; w = w->next) { + if (unlikely(!(w->charts_created & (1<<EBPF_MODULE_PROCESS_IDX)))) + continue; + + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_process_start"); + write_chart_dimension("calls", w->process.create_process); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_thread_start"); + write_chart_dimension("calls", w->process.create_thread); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_task_exit"); + write_chart_dimension("calls", w->process.exit_call); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_task_released"); + write_chart_dimension("calls", w->process.release_call); + ebpf_write_end_chart(); + + if (em->mode < MODE_ENTRY) { + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_task_error"); + write_chart_dimension("calls", w->process.task_err); + ebpf_write_end_chart(); + } + } +} + +/***************************************************************** + * + * READ INFORMATION FROM KERNEL RING + * + *****************************************************************/ + +/** + * Read the hash table and store data to allocated vectors. + * + * @param maps_per_core do I need to read all cores? + */ +static void ebpf_read_process_hash_global_tables(netdata_idx_t *stats, int maps_per_core) +{ + netdata_idx_t res[NETDATA_KEY_END_VECTOR]; + ebpf_read_global_table_stats(res, + process_hash_values, + process_maps[NETDATA_PROCESS_GLOBAL_TABLE].map_fd, + maps_per_core, + 0, + NETDATA_KEY_END_VECTOR); + + ebpf_read_global_table_stats(stats, + process_hash_values, + process_maps[NETDATA_PROCESS_CTRL_TABLE].map_fd, + maps_per_core, + NETDATA_CONTROLLER_PID_TABLE_ADD, + NETDATA_CONTROLLER_END); + + process_aggregated_data[NETDATA_KEY_PUBLISH_PROCESS_EXIT].call = res[NETDATA_KEY_CALLS_DO_EXIT]; + process_aggregated_data[NETDATA_KEY_PUBLISH_PROCESS_RELEASE_TASK].call = res[NETDATA_KEY_CALLS_RELEASE_TASK]; + process_aggregated_data[NETDATA_KEY_PUBLISH_PROCESS_FORK].call = res[NETDATA_KEY_CALLS_DO_FORK]; + process_aggregated_data[NETDATA_KEY_PUBLISH_PROCESS_CLONE].call = res[NETDATA_KEY_CALLS_SYS_CLONE]; + + process_aggregated_data[NETDATA_KEY_PUBLISH_PROCESS_FORK].ecall = res[NETDATA_KEY_ERROR_DO_FORK]; + process_aggregated_data[NETDATA_KEY_PUBLISH_PROCESS_CLONE].ecall = res[NETDATA_KEY_ERROR_SYS_CLONE]; +} + +/** + * Update cgroup + * + * Update cgroup data based in PID running. + * + * @param maps_per_core do I need to read all cores? + */ +static void ebpf_update_process_cgroup() +{ + ebpf_cgroup_target_t *ect ; + pthread_mutex_lock(&mutex_cgroup_shm); + for (ect = ebpf_cgroup_pids; ect; ect = ect->next) { + struct pid_on_target2 *pids; + for (pids = ect->pids; pids; pids = pids->next) { + int pid = pids->pid; + ebpf_process_stat_t *out = &pids->ps; + ebpf_pid_stat_t *local_pid = ebpf_get_pid_entry(pid, 0); + if (local_pid) { + ebpf_process_stat_t *in = &local_pid->process; + + memcpy(out, in, sizeof(ebpf_process_stat_t)); + } + } + } + pthread_mutex_unlock(&mutex_cgroup_shm); +} + +/***************************************************************** + * + * FUNCTIONS TO CREATE CHARTS + * + *****************************************************************/ + +/** + * 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 dashboard + * @param order the order number of the specified chart + * @param update_every value to overwrite the update frequency set by the server. + */ +static void ebpf_process_status_chart(char *family, char *name, char *axis, + char *web, char *algorithm, int order, int update_every) +{ + printf("CHART %s.%s '' 'Process not closed' '%s' '%s' 'system.process_status' line %d %d '' 'ebpf.plugin' 'process'\n", + family, + name, + axis, + web, + order, + update_every); + + printf("DIMENSION %s '' %s 1 1\n", status[0], algorithm); + printf("DIMENSION %s '' %s 1 1\n", status[1], algorithm); +} + +/** + * 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_SYSTEM_GROUP, + NETDATA_PROCESS_SYSCALL, + "Start process", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_PROCESS_GROUP, + "system.process_thread", + NETDATA_EBPF_CHART_TYPE_LINE, + 21002, + ebpf_create_global_dimension, + &process_publish_aggregated[NETDATA_KEY_PUBLISH_PROCESS_FORK], + 2, em->update_every, NETDATA_EBPF_MODULE_NAME_PROCESS); + + ebpf_create_chart(NETDATA_EBPF_SYSTEM_GROUP, + NETDATA_EXIT_SYSCALL, + "Exit process", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_PROCESS_GROUP, + "system.exit", + NETDATA_EBPF_CHART_TYPE_LINE, + 21003, + ebpf_create_global_dimension, + &process_publish_aggregated[NETDATA_KEY_PUBLISH_PROCESS_EXIT], + 2, em->update_every, NETDATA_EBPF_MODULE_NAME_PROCESS); + + ebpf_process_status_chart(NETDATA_EBPF_SYSTEM_GROUP, + NETDATA_PROCESS_STATUS_NAME, + EBPF_COMMON_UNITS_CALLS, + NETDATA_PROCESS_GROUP, + ebpf_algorithms[NETDATA_EBPF_ABSOLUTE_IDX], + 21004, em->update_every); + + if (em->mode < MODE_ENTRY) { + ebpf_create_chart(NETDATA_EBPF_SYSTEM_GROUP, + NETDATA_PROCESS_ERROR_NAME, + "Fails to create process", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_PROCESS_GROUP, + "system.task_error", + NETDATA_EBPF_CHART_TYPE_LINE, + 21005, + ebpf_create_global_dimension, + &process_publish_aggregated[NETDATA_KEY_PUBLISH_PROCESS_FORK], + 2, em->update_every, NETDATA_EBPF_MODULE_NAME_PROCESS); + } + + fflush(stdout); +} + +/** + * Create process apps charts + * + * Call ebpf_create_chart to create the charts on apps submenu. + * + * @param em a pointer to the structure with the default values. + * @param ptr a pointer for the targets. + */ +void ebpf_process_create_apps_charts(struct ebpf_module *em, void *ptr) +{ + struct ebpf_target *root = ptr; + struct ebpf_target *w; + int update_every = em->update_every; + for (w = root; w; w = w->next) { + if (unlikely(!w->exposed)) + continue; + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_process_start", + "Process started.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_PROCESS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_process_start", + 20161, + update_every, + NETDATA_EBPF_MODULE_NAME_PROCESS); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION calls '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_thread_start", + "Threads started.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_PROCESS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_thread_start", + 20162, + update_every, + NETDATA_EBPF_MODULE_NAME_PROCESS); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION calls '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_task_exit", + "Tasks starts exit process.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_PROCESS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_task_exit", + 20163, + update_every, + NETDATA_EBPF_MODULE_NAME_PROCESS); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION calls '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_task_released", + "Tasks released.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_PROCESS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_task_released", + 20164, + update_every, + NETDATA_EBPF_MODULE_NAME_PROCESS); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION calls '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_task_error", + "Errors to create process or threads.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_PROCESS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_task_error", + 20165, + update_every, + NETDATA_EBPF_MODULE_NAME_PROCESS); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION calls '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + } + w->charts_created |= 1<<EBPF_MODULE_PROCESS_IDX; + } + + em->apps_charts |= NETDATA_EBPF_APPS_FLAG_CHART_CREATED; +} + +/***************************************************************** + * + * FUNCTIONS TO CLOSE THE THREAD + * + *****************************************************************/ + +static void ebpf_obsolete_specific_process_charts(char *type, ebpf_module_t *em); + +/** + * Obsolete services + * + * Obsolete all service charts created + * + * @param em a pointer to `struct ebpf_module` + */ +static void ebpf_obsolete_process_services(ebpf_module_t *em, char *id) +{ + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_SYSCALL_APPS_TASK_PROCESS, + "Process started", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_PROCESS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_SYSTEMD_PROCESS_CREATE_CONTEXT, + 20065, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_SYSCALL_APPS_TASK_THREAD, + "Threads started", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_PROCESS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_SYSTEMD_THREAD_CREATE_CONTEXT, + 20066, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_SYSCALL_APPS_TASK_CLOSE, + "Tasks starts exit process.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_PROCESS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_SYSTEMD_PROCESS_EXIT_CONTEXT, + 20067, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_SYSCALL_APPS_TASK_EXIT, + "Tasks closed", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_PROCESS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_SYSTEMD_PROCESS_CLOSE_CONTEXT, + 20068, + em->update_every); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_SYSCALL_APPS_TASK_ERROR, + "Errors to create process or threads.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_PROCESS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_SYSTEMD_PROCESS_ERROR_CONTEXT, + 20069, + em->update_every); + } +} + +/** + * Obsolete cgroup chart + * + * Send obsolete for all charts created before to close. + * + * @param em a pointer to `struct ebpf_module` + */ +static inline void ebpf_obsolete_process_cgroup_charts(ebpf_module_t *em) { + pthread_mutex_lock(&mutex_cgroup_shm); + + ebpf_cgroup_target_t *ect; + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + if (ect->systemd) { + ebpf_obsolete_process_services(em, ect->name); + + continue; + } + + ebpf_obsolete_specific_process_charts(ect->name, em); + } + pthread_mutex_unlock(&mutex_cgroup_shm); +} + +/** + * Obsolette apps charts + * + * Obsolete apps charts. + * + * @param em a pointer to the structure with the default values. + */ +void ebpf_obsolete_process_apps_charts(struct ebpf_module *em) +{ + struct ebpf_target *w; + int update_every = em->update_every; + for (w = apps_groups_root_target; w; w = w->next) { + if (unlikely(!(w->charts_created & (1<<EBPF_MODULE_PROCESS_IDX)))) + continue; + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_process_start", + "Process started.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_PROCESS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_process_start", + 20161, + update_every); + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_thread_start", + "Threads started.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_PROCESS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_thread_start", + 20162, + update_every); + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_task_exit", + "Tasks starts exit process.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_PROCESS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_task_exit", + 20163, + update_every); + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_task_released", + "Tasks released.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_PROCESS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_task_released", + 20164, + update_every); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_task_error", + "Errors to create process or threads.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_PROCESS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_task_error", + 20165, + update_every); + } + + w->charts_created &= ~(1<<EBPF_MODULE_PROCESS_IDX); + } +} + +/** + * Obsolete global + * + * Obsolete global charts created by thread. + * + * @param em a pointer to `struct ebpf_module` + */ +static void ebpf_obsolete_process_global(ebpf_module_t *em) +{ + ebpf_write_chart_obsolete(NETDATA_EBPF_SYSTEM_GROUP, + NETDATA_PROCESS_SYSCALL, + "", + "Start process", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_PROCESS_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, + "system.process_thread", + 21002, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_EBPF_SYSTEM_GROUP, + NETDATA_EXIT_SYSCALL, + "", + "Exit process", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_PROCESS_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, + "system.exit", + 21003, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_EBPF_SYSTEM_GROUP, + NETDATA_PROCESS_STATUS_NAME, + "", + "Process not closed", + EBPF_COMMON_UNITS_CALLS, + NETDATA_PROCESS_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, + "system.process_status", + 21004, + em->update_every); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_obsolete(NETDATA_EBPF_SYSTEM_GROUP, + NETDATA_PROCESS_ERROR_NAME, + "", + "Fails to create process", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_PROCESS_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, + "system.task_error", + 21005, + em->update_every); + } +} + +/** + * Process disable tracepoints + * + * Disable tracepoints when the plugin was responsible to enable it. + */ +static void ebpf_process_disable_tracepoints() +{ + char *default_message = { "Cannot disable the tracepoint" }; + if (!was_sched_process_exit_enabled) { + if (ebpf_disable_tracing_values(tracepoint_sched_type, tracepoint_sched_process_exit)) + netdata_log_error("%s %s/%s.", default_message, tracepoint_sched_type, tracepoint_sched_process_exit); + } + + if (!was_sched_process_exec_enabled) { + if (ebpf_disable_tracing_values(tracepoint_sched_type, tracepoint_sched_process_exec)) + netdata_log_error("%s %s/%s.", default_message, tracepoint_sched_type, tracepoint_sched_process_exec); + } + + if (!was_sched_process_fork_enabled) { + if (ebpf_disable_tracing_values(tracepoint_sched_type, tracepoint_sched_process_fork)) + netdata_log_error("%s %s/%s.", default_message, tracepoint_sched_type, tracepoint_sched_process_fork); + } +} + +/** + * Process Exit + * + * Cancel child thread. + * + * @param ptr thread data. + */ +static void ebpf_process_exit(void *pptr) +{ + ebpf_module_t *em = CLEANUP_FUNCTION_GET_PTR(pptr); + if(!em) return; + + if (em->enabled == NETDATA_THREAD_EBPF_FUNCTION_RUNNING) { + pthread_mutex_lock(&lock); + if (em->cgroup_charts) { + ebpf_obsolete_process_cgroup_charts(em); + fflush(stdout); + } + + if (em->apps_charts & NETDATA_EBPF_APPS_FLAG_CHART_CREATED) { + ebpf_obsolete_process_apps_charts(em); + } + + ebpf_obsolete_process_global(em); + + fflush(stdout); + pthread_mutex_unlock(&lock); + } + + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps, EBPF_ACTION_STAT_REMOVE); + + if (em->objects) { + ebpf_unload_legacy_code(em->objects, em->probe_links); + em->objects = NULL; + em->probe_links = NULL; + } + + freez(process_hash_values); + freez(process_stat_vector); + + ebpf_process_disable_tracepoints(); + + pthread_mutex_lock(&ebpf_exit_cleanup); + process_pid_fd = -1; + em->enabled = NETDATA_THREAD_EBPF_STOPPED; + ebpf_update_stats(&plugin_statistics, em); + pthread_mutex_unlock(&ebpf_exit_cleanup); +} + +/***************************************************************** + * + * FUNCTIONS WITH THE MAIN LOOP + * + *****************************************************************/ + + +/** + * Sum PIDs + * + * Sum values for all targets. + * + * @param ps structure used to store data + * @param pids input data + */ +static void ebpf_process_sum_cgroup_pids(ebpf_process_stat_t *ps, struct pid_on_target2 *pids) +{ + ebpf_process_stat_t accumulator; + memset(&accumulator, 0, sizeof(accumulator)); + + while (pids) { + ebpf_process_stat_t *pps = &pids->ps; + + accumulator.exit_call += pps->exit_call; + accumulator.release_call += pps->release_call; + accumulator.create_process += pps->create_process; + accumulator.create_thread += pps->create_thread; + + accumulator.task_err += pps->task_err; + + pids = pids->next; + } + + ps->exit_call = (accumulator.exit_call >= ps->exit_call) ? accumulator.exit_call : ps->exit_call; + ps->release_call = (accumulator.release_call >= ps->release_call) ? accumulator.release_call : ps->release_call; + ps->create_process = (accumulator.create_process >= ps->create_process) ? accumulator.create_process : ps->create_process; + ps->create_thread = (accumulator.create_thread >= ps->create_thread) ? accumulator.create_thread : ps->create_thread; + + ps->task_err = (accumulator.task_err >= ps->task_err) ? accumulator.task_err : ps->task_err; +} + +/* + * Send Specific Process data + * + * Send data for specific cgroup/apps. + * + * @param type chart type + * @param values structure with values that will be sent to netdata + * @param em the structure with thread information + */ +static void ebpf_send_specific_process_data(char *type, ebpf_process_stat_t *values, ebpf_module_t *em) +{ + ebpf_write_begin_chart(type, NETDATA_SYSCALL_APPS_TASK_PROCESS, ""); + write_chart_dimension(process_publish_aggregated[NETDATA_KEY_PUBLISH_PROCESS_FORK].name, + (long long) values->create_process); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(type, NETDATA_SYSCALL_APPS_TASK_THREAD, ""); + write_chart_dimension(process_publish_aggregated[NETDATA_KEY_PUBLISH_PROCESS_CLONE].name, + (long long) values->create_thread); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(type, NETDATA_SYSCALL_APPS_TASK_EXIT, ""); + write_chart_dimension(process_publish_aggregated[NETDATA_KEY_PUBLISH_PROCESS_EXIT].name, + (long long) values->release_call); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(type, NETDATA_SYSCALL_APPS_TASK_CLOSE, ""); + write_chart_dimension(process_publish_aggregated[NETDATA_KEY_PUBLISH_PROCESS_RELEASE_TASK].name, + (long long) values->release_call); + ebpf_write_end_chart(); + + if (em->mode < MODE_ENTRY) { + ebpf_write_begin_chart(type, NETDATA_SYSCALL_APPS_TASK_ERROR, ""); + write_chart_dimension(process_publish_aggregated[NETDATA_KEY_PUBLISH_PROCESS_EXIT].name, + (long long) values->task_err); + ebpf_write_end_chart(); + } +} + +/** + * Create specific process charts + * + * Create charts for cgroup/application + * + * @param type the chart type. + * @param em the structure with thread information + */ +static void ebpf_create_specific_process_charts(char *type, ebpf_module_t *em) +{ + char *label = (!strncmp(type, "cgroup_", 7)) ? &type[7] : type; + ebpf_create_chart(type, NETDATA_SYSCALL_APPS_TASK_PROCESS, "Process started", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_PROCESS_GROUP, + NETDATA_CGROUP_PROCESS_CREATE_CONTEXT, NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5000, + ebpf_create_global_dimension, &process_publish_aggregated[NETDATA_KEY_PUBLISH_PROCESS_FORK], + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_PROCESS); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + + ebpf_create_chart(type, NETDATA_SYSCALL_APPS_TASK_THREAD, "Threads started", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_PROCESS_GROUP, + NETDATA_CGROUP_THREAD_CREATE_CONTEXT, NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5001, + ebpf_create_global_dimension, + &process_publish_aggregated[NETDATA_KEY_PUBLISH_PROCESS_CLONE], + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_PROCESS); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + + ebpf_create_chart(type, NETDATA_SYSCALL_APPS_TASK_EXIT, "Tasks starts exit process.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_PROCESS_GROUP, + NETDATA_CGROUP_PROCESS_EXIT_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5002, + ebpf_create_global_dimension, + &process_publish_aggregated[NETDATA_KEY_PUBLISH_PROCESS_EXIT], + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_PROCESS); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + + ebpf_create_chart(type, NETDATA_SYSCALL_APPS_TASK_CLOSE, "Tasks closed", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_PROCESS_GROUP, + NETDATA_CGROUP_PROCESS_CLOSE_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5003, + ebpf_create_global_dimension, + &process_publish_aggregated[NETDATA_KEY_PUBLISH_PROCESS_RELEASE_TASK], + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_PROCESS); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + + if (em->mode < MODE_ENTRY) { + ebpf_create_chart(type, NETDATA_SYSCALL_APPS_TASK_ERROR, "Errors to create process or threads.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_PROCESS_GROUP, + NETDATA_CGROUP_PROCESS_ERROR_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5004, + ebpf_create_global_dimension, + &process_publish_aggregated[NETDATA_KEY_PUBLISH_PROCESS_EXIT], + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_PROCESS); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + } +} + +/** + * Obsolete specific process charts + * + * Obsolete charts for cgroup/application + * + * @param type the chart type. + * @param em the structure with thread information + */ +static void ebpf_obsolete_specific_process_charts(char *type, ebpf_module_t *em) +{ + ebpf_write_chart_obsolete(type, NETDATA_SYSCALL_APPS_TASK_PROCESS, "", "Process started", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_PROCESS_GROUP, NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CGROUP_PROCESS_CREATE_CONTEXT, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5000, + em->update_every); + + ebpf_write_chart_obsolete(type, NETDATA_SYSCALL_APPS_TASK_THREAD, "", "Threads started", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_PROCESS_GROUP, NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CGROUP_THREAD_CREATE_CONTEXT, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5001, + em->update_every); + + ebpf_write_chart_obsolete(type, NETDATA_SYSCALL_APPS_TASK_EXIT, "","Tasks starts exit process.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_PROCESS_GROUP, NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CGROUP_PROCESS_EXIT_CONTEXT, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5002, + em->update_every); + + ebpf_write_chart_obsolete(type, NETDATA_SYSCALL_APPS_TASK_CLOSE, "","Tasks closed", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_PROCESS_GROUP, NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CGROUP_PROCESS_CLOSE_CONTEXT, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5003, + em->update_every); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_obsolete(type, NETDATA_SYSCALL_APPS_TASK_ERROR, "","Errors to create process or threads.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_PROCESS_GROUP, NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CGROUP_PROCESS_ERROR_CONTEXT, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5004, + em->update_every); + } +} + +/** + * Create Systemd process Charts + * + * Create charts when systemd is enabled + * + * @param em the structure with thread information + **/ +static void ebpf_create_systemd_process_charts(ebpf_module_t *em) +{ + static ebpf_systemd_args_t data_process = { + .title = "Process started", + .units = EBPF_COMMON_UNITS_CALLS_PER_SEC, + .family = NETDATA_APPS_PROCESS_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20065, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SYSTEMD_PROCESS_CREATE_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_PROCESS, + .update_every = 0, + .suffix = NETDATA_SYSCALL_APPS_TASK_PROCESS, + .dimension = "calls" + }; + + static ebpf_systemd_args_t data_thread = { + .title = "Threads started", + .units = EBPF_COMMON_UNITS_CALLS_PER_SEC, + .family = NETDATA_APPS_PROCESS_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20066, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SYSTEMD_THREAD_CREATE_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_PROCESS, + .update_every = 0, + .suffix = NETDATA_SYSCALL_APPS_TASK_THREAD, + .dimension = "calls" + }; + + static ebpf_systemd_args_t task_exit = { + .title = "Tasks starts exit process.", + .units = EBPF_COMMON_UNITS_CALLS_PER_SEC, + .family = NETDATA_APPS_PROCESS_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20067, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SYSTEMD_PROCESS_EXIT_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_PROCESS, + .update_every = 0, + .suffix = NETDATA_SYSCALL_APPS_TASK_CLOSE, + .dimension = "calls" + }; + + static ebpf_systemd_args_t task_closed = { + .title = "Tasks closed", + .units = EBPF_COMMON_UNITS_CALLS_PER_SEC, + .family = NETDATA_APPS_PROCESS_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20068, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SYSTEMD_PROCESS_CLOSE_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_PROCESS, + .update_every = 0, + .suffix = NETDATA_SYSCALL_APPS_TASK_EXIT, + .dimension = "calls" + }; + + static ebpf_systemd_args_t task_error = { + .title = "Errors to create process or threads.", + .units = EBPF_COMMON_UNITS_CALLS_PER_SEC, + .family = NETDATA_APPS_PROCESS_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20069, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SYSTEMD_PROCESS_ERROR_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_PROCESS, + .update_every = 0, + .suffix = NETDATA_SYSCALL_APPS_TASK_ERROR, + .dimension = "calls" + }; + + ebpf_cgroup_target_t *w; + netdata_run_mode_t mode = em->mode; + if (!task_exit.update_every) + data_process.update_every = data_thread.update_every = task_exit.update_every = + task_closed.update_every = task_error.update_every = em->update_every; + + for (w = ebpf_cgroup_pids; w; w = w->next) { + if (unlikely(!w->systemd || w->flags & NETDATA_EBPF_SERVICES_HAS_PROCESS_CHART)) + continue; + + data_process.id = data_thread.id = task_exit.id = task_closed.id = task_error.id = w->name; + ebpf_create_charts_on_systemd(&data_process); + + ebpf_create_charts_on_systemd(&data_thread); + + ebpf_create_charts_on_systemd(&task_exit); + + ebpf_create_charts_on_systemd(&task_closed); + if (mode < MODE_ENTRY) { + ebpf_create_charts_on_systemd(&task_error); + } + w->flags |= NETDATA_EBPF_SERVICES_HAS_PROCESS_CHART; + } +} + +/** + * Send Systemd charts + * + * Send collected data to Netdata. + * + * @param em the structure with thread information + */ +static void ebpf_send_systemd_process_charts(ebpf_module_t *em) +{ + ebpf_cgroup_target_t *ect; + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + if (unlikely(!(ect->flags & NETDATA_EBPF_SERVICES_HAS_PROCESS_CHART)) ) { + continue; + } + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_SYSCALL_APPS_TASK_PROCESS); + write_chart_dimension("calls", ect->publish_systemd_ps.create_process); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_SYSCALL_APPS_TASK_THREAD); + write_chart_dimension("calls", ect->publish_systemd_ps.create_thread); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_SYSCALL_APPS_TASK_EXIT); + write_chart_dimension("calls", ect->publish_systemd_ps.exit_call); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_SYSCALL_APPS_TASK_CLOSE); + write_chart_dimension("calls", ect->publish_systemd_ps.release_call); + ebpf_write_end_chart(); + + if (em->mode < MODE_ENTRY) { + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_SYSCALL_APPS_TASK_ERROR); + write_chart_dimension("calls", ect->publish_systemd_ps.task_err); + ebpf_write_end_chart(); + } + } +} + +/** + * Send data to Netdata calling auxiliary functions. + * + * @param em the structure with thread information +*/ +static void ebpf_process_send_cgroup_data(ebpf_module_t *em) +{ + pthread_mutex_lock(&mutex_cgroup_shm); + ebpf_cgroup_target_t *ect; + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + ebpf_process_sum_cgroup_pids(&ect->publish_systemd_ps, ect->pids); + } + + if (shm_ebpf_cgroup.header->systemd_enabled) { + if (send_cgroup_chart) { + ebpf_create_systemd_process_charts(em); + } + + ebpf_send_systemd_process_charts(em); + } + + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + if (ect->systemd) + continue; + + if (!(ect->flags & NETDATA_EBPF_CGROUP_HAS_PROCESS_CHART) && ect->updated) { + ebpf_create_specific_process_charts(ect->name, em); + ect->flags |= NETDATA_EBPF_CGROUP_HAS_PROCESS_CHART; + } + + if (ect->flags & NETDATA_EBPF_CGROUP_HAS_PROCESS_CHART) { + if (ect->updated) { + ebpf_send_specific_process_data(ect->name, &ect->publish_systemd_ps, em); + } else { + ebpf_obsolete_specific_process_charts(ect->name, em); + ect->flags &= ~NETDATA_EBPF_CGROUP_HAS_PROCESS_CHART; + } + } + } + + pthread_mutex_unlock(&mutex_cgroup_shm); +} + +/** + * Update Cgroup algorithm + * + * Change algorithm from absolute to incremental + */ +void ebpf_process_update_cgroup_algorithm() +{ + int i; + for (i = 0; i < NETDATA_KEY_PUBLISH_PROCESS_END; i++) { + netdata_publish_syscall_t *ptr = &process_publish_aggregated[i]; + ptr->algorithm = ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]; + } +} + +/** + * Main loop for this collector. + * + * @param em the structure with thread information + */ +static void process_collector(ebpf_module_t *em) +{ + heartbeat_t hb; + heartbeat_init(&hb); + int publish_global = em->global_charts; + int cgroups = em->cgroup_charts; + pthread_mutex_lock(&ebpf_exit_cleanup); + process_pid_fd = process_maps[NETDATA_PROCESS_PID_TABLE].map_fd; + pthread_mutex_unlock(&ebpf_exit_cleanup); + if (cgroups) + ebpf_process_update_cgroup_algorithm(); + + int update_every = em->update_every; + int counter = update_every - 1; + int maps_per_core = em->maps_per_core; + uint32_t running_time = 0; + uint32_t lifetime = em->lifetime; + netdata_idx_t *stats = em->hash_table_stats; + memset(stats, 0, sizeof(em->hash_table_stats)); + while (!ebpf_plugin_stop() && running_time < lifetime) { + usec_t dt = heartbeat_next(&hb, USEC_PER_SEC); + (void)dt; + if (ebpf_plugin_stop()) + break; + + if (++counter == update_every) { + counter = 0; + + ebpf_read_process_hash_global_tables(stats, maps_per_core); + + netdata_apps_integration_flags_t apps_enabled = em->apps_charts; + pthread_mutex_lock(&collect_data_mutex); + + if (ebpf_all_pids_count > 0) { + if (cgroups && shm_ebpf_cgroup.header) { + ebpf_update_process_cgroup(); + } + } + + pthread_mutex_lock(&lock); + + if (publish_global) { + ebpf_process_send_data(em); + } + + if (apps_enabled & NETDATA_EBPF_APPS_FLAG_CHART_CREATED) { + ebpf_process_send_apps_data(apps_groups_root_target, em); + } + + if (cgroups && shm_ebpf_cgroup.header) { + ebpf_process_send_cgroup_data(em); + } + + pthread_mutex_unlock(&lock); + pthread_mutex_unlock(&collect_data_mutex); + + pthread_mutex_lock(&ebpf_exit_cleanup); + if (running_time && !em->running_time) + running_time = update_every; + else + running_time += update_every; + + em->running_time = running_time; + pthread_mutex_unlock(&ebpf_exit_cleanup); + } + + fflush(stdout); + } +} + +/***************************************************************** + * + * 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) +{ + memset(process_aggregated_data, 0, length * sizeof(netdata_syscall_stat_t)); + memset(process_publish_aggregated, 0, length * sizeof(netdata_publish_syscall_t)); + process_hash_values = callocz(ebpf_nprocs, sizeof(netdata_idx_t)); + process_stat_vector = callocz(ebpf_nprocs, sizeof(ebpf_process_stat_t)); +} + +static void change_syscalls() +{ + static char *lfork = { "do_fork" }; + process_id_names[NETDATA_KEY_PUBLISH_PROCESS_FORK] = lfork; +} + +/** + * Set local variables + * + */ +static void set_local_pointers() +{ + if (isrh >= NETDATA_MINIMUM_RH_VERSION && isrh < NETDATA_RH_8) + change_syscalls(); +} + +/***************************************************************** + * + * EBPF PROCESS THREAD + * + *****************************************************************/ + +/** + * Enable tracepoints + * + * Enable necessary tracepoints for thread. + * + * @return It returns 0 on success and -1 otherwise + */ +static int ebpf_process_enable_tracepoints() +{ + int test = ebpf_is_tracepoint_enabled(tracepoint_sched_type, tracepoint_sched_process_exit); + if (test == -1) + return -1; + else if (!test) { + if (ebpf_enable_tracing_values(tracepoint_sched_type, tracepoint_sched_process_exit)) + return -1; + } + was_sched_process_exit_enabled = test; + + test = ebpf_is_tracepoint_enabled(tracepoint_sched_type, tracepoint_sched_process_exec); + if (test == -1) + return -1; + else if (!test) { + if (ebpf_enable_tracing_values(tracepoint_sched_type, tracepoint_sched_process_exec)) + return -1; + } + was_sched_process_exec_enabled = test; + + test = ebpf_is_tracepoint_enabled(tracepoint_sched_type, tracepoint_sched_process_fork); + if (test == -1) + return -1; + else if (!test) { + if (ebpf_enable_tracing_values(tracepoint_sched_type, tracepoint_sched_process_fork)) + return -1; + } + was_sched_process_fork_enabled = test; + + return 0; +} + +/** + * 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) +{ + ebpf_module_t *em = (ebpf_module_t *)ptr; + + CLEANUP_FUNCTION_REGISTER(ebpf_process_exit) cleanup_ptr = em; + + em->maps = process_maps; + + pthread_mutex_lock(&ebpf_exit_cleanup); + if (ebpf_process_enable_tracepoints()) { + em->enabled = em->global_charts = em->apps_charts = em->cgroup_charts = NETDATA_THREAD_EBPF_STOPPING; + } + pthread_mutex_unlock(&ebpf_exit_cleanup); + + pthread_mutex_lock(&lock); + ebpf_process_allocate_global_vectors(NETDATA_KEY_PUBLISH_PROCESS_END); + + ebpf_update_pid_table(&process_maps[0], em); + + set_local_pointers(); + em->probe_links = ebpf_load_program(ebpf_plugin_dir, em, running_on_kernel, isrh, &em->objects); + if (!em->probe_links) { + em->enabled = em->global_charts = em->apps_charts = em->cgroup_charts = NETDATA_THREAD_EBPF_STOPPING; + } + + int algorithms[NETDATA_KEY_PUBLISH_PROCESS_END] = { + NETDATA_EBPF_ABSOLUTE_IDX, NETDATA_EBPF_ABSOLUTE_IDX, NETDATA_EBPF_ABSOLUTE_IDX, NETDATA_EBPF_ABSOLUTE_IDX + }; + + ebpf_global_labels( + process_aggregated_data, process_publish_aggregated, process_dimension_names, process_id_names, + algorithms, NETDATA_KEY_PUBLISH_PROCESS_END); + + ebpf_create_global_charts(em); + + ebpf_update_stats(&plugin_statistics, em); + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps, EBPF_ACTION_STAT_ADD); + + pthread_mutex_unlock(&lock); + + process_collector(em); + + pthread_mutex_lock(&ebpf_exit_cleanup); + ebpf_update_disabled_plugin_stats(em); + pthread_mutex_unlock(&ebpf_exit_cleanup); + + return NULL; +} diff --git a/src/collectors/ebpf.plugin/ebpf_process.h b/src/collectors/ebpf.plugin/ebpf_process.h new file mode 100644 index 000000000..18ffec1ff --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_process.h @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_EBPF_PROCESS_H +#define NETDATA_EBPF_PROCESS_H 1 + +// Module name & description +#define NETDATA_EBPF_MODULE_NAME_PROCESS "process" +#define NETDATA_EBPF_MODULE_PROCESS_DESC "Monitor information about process life. This thread is integrated with apps and cgroup." + +// Groups used on Dashboard +#define NETDATA_PROCESS_GROUP "processes" + +// Global chart name +#define NETDATA_EXIT_SYSCALL "exit" +#define NETDATA_PROCESS_SYSCALL "process_thread" +#define NETDATA_PROCESS_ERROR_NAME "task_error" +#define NETDATA_PROCESS_STATUS_NAME "process_status" + +// Charts created on Apps submenu +#define NETDATA_SYSCALL_APPS_TASK_PROCESS "process_create" +#define NETDATA_SYSCALL_APPS_TASK_THREAD "thread_create" +#define NETDATA_SYSCALL_APPS_TASK_EXIT "task_exit" +#define NETDATA_SYSCALL_APPS_TASK_CLOSE "task_close" +#define NETDATA_SYSCALL_APPS_TASK_ERROR "task_error" + +// Process configuration name +#define NETDATA_PROCESS_CONFIG_FILE "process.conf" + +// Contexts +#define NETDATA_CGROUP_PROCESS_CREATE_CONTEXT "cgroup.process_create" +#define NETDATA_CGROUP_THREAD_CREATE_CONTEXT "cgroup.thread_create" +#define NETDATA_CGROUP_PROCESS_CLOSE_CONTEXT "cgroup.task_close" +#define NETDATA_CGROUP_PROCESS_EXIT_CONTEXT "cgroup.task_exit" +#define NETDATA_CGROUP_PROCESS_ERROR_CONTEXT "cgroup.task_error" + +#define NETDATA_SYSTEMD_PROCESS_CREATE_CONTEXT "systemd.services.process_create" +#define NETDATA_SYSTEMD_THREAD_CREATE_CONTEXT "systemd.services.thread_create" +#define NETDATA_SYSTEMD_PROCESS_CLOSE_CONTEXT "systemd.services.task_close" +#define NETDATA_SYSTEMD_PROCESS_EXIT_CONTEXT "systemd.services.task_exit" +#define NETDATA_SYSTEMD_PROCESS_ERROR_CONTEXT "systemd.services.task_error" + +#define NETDATA_EBPF_CGROUP_UPDATE 30 + +enum netdata_ebpf_stats_order { + NETDATA_EBPF_ORDER_STAT_THREADS = 140000, + NETDATA_EBPF_ORDER_STAT_LIFE_TIME, + NETDATA_EBPF_ORDER_STAT_LOAD_METHOD, + NETDATA_EBPF_ORDER_STAT_KERNEL_MEMORY, + NETDATA_EBPF_ORDER_STAT_HASH_TABLES, + NETDATA_EBPF_ORDER_STAT_HASH_CORE, + NETDATA_EBPF_ORDER_STAT_HASH_GLOBAL_TABLE_TOTAL, + NETDATA_EBPF_ORDER_STAT_HASH_PID_TABLE_ADDED, + NETDATA_EBPF_ORDER_STAT_HASH_PID_TABLE_REMOVED, + NETATA_EBPF_ORDER_STAT_ARAL_BEGIN, + NETDATA_EBPF_ORDER_FUNCTION_PER_THREAD, +}; + +enum netdata_ebpf_load_mode_stats{ + NETDATA_EBPF_LOAD_STAT_LEGACY, + NETDATA_EBPF_LOAD_STAT_CORE, + + NETDATA_EBPF_LOAD_STAT_END +}; + +enum netdata_ebpf_thread_per_core{ + NETDATA_EBPF_THREAD_PER_CORE, + NETDATA_EBPF_THREAD_UNIQUE, + + NETDATA_EBPF_PER_CORE_END +}; + +// Index from kernel +typedef enum ebpf_process_index { + NETDATA_KEY_CALLS_DO_EXIT, + + NETDATA_KEY_CALLS_RELEASE_TASK, + + NETDATA_KEY_CALLS_DO_FORK, + NETDATA_KEY_ERROR_DO_FORK, + + NETDATA_KEY_CALLS_SYS_CLONE, + NETDATA_KEY_ERROR_SYS_CLONE, + + NETDATA_KEY_END_VECTOR +} ebpf_process_index_t; + +// This enum acts as an index for publish vector. +// Do not change the enum order because we use +// different algorithms to make charts with incremental +// values (the three initial positions) and absolute values +// (the remaining charts). +typedef enum netdata_publish_process { + NETDATA_KEY_PUBLISH_PROCESS_EXIT, + NETDATA_KEY_PUBLISH_PROCESS_RELEASE_TASK, + NETDATA_KEY_PUBLISH_PROCESS_FORK, + NETDATA_KEY_PUBLISH_PROCESS_CLONE, + + NETDATA_KEY_PUBLISH_PROCESS_END +} netdata_publish_process_t; + +enum ebpf_process_tables { + NETDATA_PROCESS_PID_TABLE, + NETDATA_PROCESS_GLOBAL_TABLE, + NETDATA_PROCESS_CTRL_TABLE +}; + +extern struct config process_config; + +#endif /* NETDATA_EBPF_PROCESS_H */ diff --git a/src/collectors/ebpf.plugin/ebpf_shm.c b/src/collectors/ebpf.plugin/ebpf_shm.c new file mode 100644 index 000000000..8e1999526 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_shm.c @@ -0,0 +1,1374 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ebpf.h" +#include "ebpf_shm.h" + +static char *shm_dimension_name[NETDATA_SHM_END] = { "get", "at", "dt", "ctl" }; +static netdata_syscall_stat_t shm_aggregated_data[NETDATA_SHM_END]; +static netdata_publish_syscall_t shm_publish_aggregated[NETDATA_SHM_END]; + +netdata_publish_shm_t *shm_vector = NULL; + +static netdata_idx_t shm_hash_values[NETDATA_SHM_END]; +static netdata_idx_t *shm_values = NULL; + +struct config shm_config = { .first_section = NULL, + .last_section = NULL, + .mutex = NETDATA_MUTEX_INITIALIZER, + .index = { .avl_tree = { .root = NULL, .compar = appconfig_section_compare }, + .rwlock = AVL_LOCK_INITIALIZER } }; + +static ebpf_local_maps_t shm_maps[] = {{.name = "tbl_pid_shm", .internal_input = ND_EBPF_DEFAULT_PID_SIZE, + .user_input = 0, + .type = NETDATA_EBPF_MAP_RESIZABLE | NETDATA_EBPF_MAP_PID, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_HASH +#endif + }, + {.name = "shm_ctrl", .internal_input = NETDATA_CONTROLLER_END, + .user_input = 0, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }, + {.name = "tbl_shm", .internal_input = NETDATA_SHM_END, + .user_input = 0, + .type = NETDATA_EBPF_MAP_STATIC, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }, + {.name = NULL, .internal_input = 0, .user_input = 0}}; + +netdata_ebpf_targets_t shm_targets[] = { {.name = "shmget", .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = "shmat", .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = "shmdt", .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = "shmctl", .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = NULL, .mode = EBPF_LOAD_TRAMPOLINE}}; + +#ifdef NETDATA_DEV_MODE +int shm_disable_priority; +#endif + +struct netdata_static_thread ebpf_read_shm = { + .name = "EBPF_READ_SHM", + .config_section = NULL, + .config_name = NULL, + .env_name = NULL, + .enabled = 1, + .thread = NULL, + .init_routine = NULL, + .start_routine = NULL +}; + +#ifdef LIBBPF_MAJOR_VERSION +/***************************************************************** + * + * BTF FUNCTIONS + * + *****************************************************************/ + +/* + * Disable tracepoint + * + * Disable all tracepoints to use exclusively another method. + * + * @param obj is the main structure for bpf objects. + */ +static void ebpf_shm_disable_tracepoint(struct shm_bpf *obj) +{ + bpf_program__set_autoload(obj->progs.netdata_syscall_shmget, false); + bpf_program__set_autoload(obj->progs.netdata_syscall_shmat, false); + bpf_program__set_autoload(obj->progs.netdata_syscall_shmdt, false); + bpf_program__set_autoload(obj->progs.netdata_syscall_shmctl, false); +} + +/* + * Disable probe + * + * Disable all probes to use exclusively another method. + * + * @param obj is the main structure for bpf objects. + */ +static void ebpf_disable_probe(struct shm_bpf *obj) +{ + bpf_program__set_autoload(obj->progs.netdata_shmget_probe, false); + bpf_program__set_autoload(obj->progs.netdata_shmat_probe, false); + bpf_program__set_autoload(obj->progs.netdata_shmdt_probe, false); + bpf_program__set_autoload(obj->progs.netdata_shmctl_probe, false); +} + +/* + * Disable trampoline + * + * Disable all trampoline to use exclusively another method. + * + * @param obj is the main structure for bpf objects. + */ +static void ebpf_disable_trampoline(struct shm_bpf *obj) +{ + bpf_program__set_autoload(obj->progs.netdata_shmget_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_shmat_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_shmdt_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_shmctl_fentry, false); +} + +/** + * Set trampoline target + * + * Set the targets we will monitor. + * + * @param obj is the main structure for bpf objects. + */ +static void ebpf_set_trampoline_target(struct shm_bpf *obj) +{ + char syscall[NETDATA_EBPF_MAX_SYSCALL_LENGTH + 1]; + ebpf_select_host_prefix(syscall, NETDATA_EBPF_MAX_SYSCALL_LENGTH, + shm_targets[NETDATA_KEY_SHMGET_CALL].name, running_on_kernel); + + bpf_program__set_attach_target(obj->progs.netdata_shmget_fentry, 0, + syscall); + + ebpf_select_host_prefix(syscall, NETDATA_EBPF_MAX_SYSCALL_LENGTH, + shm_targets[NETDATA_KEY_SHMAT_CALL].name, running_on_kernel); + bpf_program__set_attach_target(obj->progs.netdata_shmat_fentry, 0, + syscall); + + ebpf_select_host_prefix(syscall, NETDATA_EBPF_MAX_SYSCALL_LENGTH, + shm_targets[NETDATA_KEY_SHMDT_CALL].name, running_on_kernel); + bpf_program__set_attach_target(obj->progs.netdata_shmdt_fentry, 0, + syscall); + + ebpf_select_host_prefix(syscall, NETDATA_EBPF_MAX_SYSCALL_LENGTH, + shm_targets[NETDATA_KEY_SHMCTL_CALL].name, running_on_kernel); + bpf_program__set_attach_target(obj->progs.netdata_shmctl_fentry, 0, + syscall); +} + +/** + * SHM Attach Probe + * + * Attach probes to target + * + * @param obj is the main structure for bpf objects. + * + * @return It returns 0 on success and -1 otherwise. + */ +static int ebpf_shm_attach_probe(struct shm_bpf *obj) +{ + char syscall[NETDATA_EBPF_MAX_SYSCALL_LENGTH + 1]; + ebpf_select_host_prefix(syscall, NETDATA_EBPF_MAX_SYSCALL_LENGTH, + shm_targets[NETDATA_KEY_SHMGET_CALL].name, running_on_kernel); + + obj->links.netdata_shmget_probe = bpf_program__attach_kprobe(obj->progs.netdata_shmget_probe, + false, syscall); + long ret = libbpf_get_error(obj->links.netdata_shmget_probe); + if (ret) + return -1; + + ebpf_select_host_prefix(syscall, NETDATA_EBPF_MAX_SYSCALL_LENGTH, + shm_targets[NETDATA_KEY_SHMAT_CALL].name, running_on_kernel); + obj->links.netdata_shmat_probe = bpf_program__attach_kprobe(obj->progs.netdata_shmat_probe, + false, syscall); + ret = libbpf_get_error(obj->links.netdata_shmat_probe); + if (ret) + return -1; + + ebpf_select_host_prefix(syscall, NETDATA_EBPF_MAX_SYSCALL_LENGTH, + shm_targets[NETDATA_KEY_SHMDT_CALL].name, running_on_kernel); + obj->links.netdata_shmdt_probe = bpf_program__attach_kprobe(obj->progs.netdata_shmdt_probe, + false, syscall); + ret = libbpf_get_error(obj->links.netdata_shmdt_probe); + if (ret) + return -1; + + ebpf_select_host_prefix(syscall, NETDATA_EBPF_MAX_SYSCALL_LENGTH, + shm_targets[NETDATA_KEY_SHMCTL_CALL].name, running_on_kernel); + obj->links.netdata_shmctl_probe = bpf_program__attach_kprobe(obj->progs.netdata_shmctl_probe, + false, syscall); + ret = libbpf_get_error(obj->links.netdata_shmctl_probe); + if (ret) + return -1; + + return 0; +} + +/** + * Set hash tables + * + * Set the values for maps according the value given by kernel. + */ +static void ebpf_shm_set_hash_tables(struct shm_bpf *obj) +{ + shm_maps[NETDATA_PID_SHM_TABLE].map_fd = bpf_map__fd(obj->maps.tbl_pid_shm); + shm_maps[NETDATA_SHM_CONTROLLER].map_fd = bpf_map__fd(obj->maps.shm_ctrl); + shm_maps[NETDATA_SHM_GLOBAL_TABLE].map_fd = bpf_map__fd(obj->maps.tbl_shm); +} + +/** + * Adjust Map Size + * + * Resize maps according input from users. + * + * @param obj is the main structure for bpf objects. + * @param em structure with configuration + */ +static void ebpf_shm_adjust_map(struct shm_bpf *obj, ebpf_module_t *em) +{ + ebpf_update_map_size(obj->maps.tbl_pid_shm, &shm_maps[NETDATA_PID_SHM_TABLE], + em, bpf_map__name(obj->maps.tbl_pid_shm)); + + ebpf_update_map_type(obj->maps.tbl_shm, &shm_maps[NETDATA_SHM_GLOBAL_TABLE]); + ebpf_update_map_type(obj->maps.tbl_pid_shm, &shm_maps[NETDATA_PID_SHM_TABLE]); + ebpf_update_map_type(obj->maps.shm_ctrl, &shm_maps[NETDATA_SHM_CONTROLLER]); +} + +/** + * Load and attach + * + * Load and attach the eBPF code in kernel. + * + * @param obj is the main structure for bpf objects. + * @param em structure with configuration + * + * @return it returns 0 on success and -1 otherwise + */ +static inline int ebpf_shm_load_and_attach(struct shm_bpf *obj, ebpf_module_t *em) +{ + netdata_ebpf_targets_t *shmt = em->targets; + netdata_ebpf_program_loaded_t test = shmt[NETDATA_KEY_SHMGET_CALL].mode; + + // We are testing only one, because all will have the same behavior + if (test == EBPF_LOAD_TRAMPOLINE ) { + ebpf_shm_disable_tracepoint(obj); + ebpf_disable_probe(obj); + + ebpf_set_trampoline_target(obj); + } else if (test == EBPF_LOAD_PROBE || test == EBPF_LOAD_RETPROBE ) { + ebpf_shm_disable_tracepoint(obj); + ebpf_disable_trampoline(obj); + } else { + ebpf_disable_probe(obj); + ebpf_disable_trampoline(obj); + } + + ebpf_shm_adjust_map(obj, em); + + int ret = shm_bpf__load(obj); + if (!ret) { + if (test != EBPF_LOAD_PROBE && test != EBPF_LOAD_RETPROBE) + shm_bpf__attach(obj); + else + ret = ebpf_shm_attach_probe(obj); + + if (!ret) + ebpf_shm_set_hash_tables(obj); + } + + return ret; +} +#endif +/***************************************************************** + * FUNCTIONS TO CLOSE THE THREAD + *****************************************************************/ + +static void ebpf_obsolete_specific_shm_charts(char *type, int update_every); + +/** + * Obsolete services + * + * Obsolete all service charts created + * + * @param em a pointer to `struct ebpf_module` + */ +static void ebpf_obsolete_shm_services(ebpf_module_t *em, char *id) +{ + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_SHMGET_CHART, + "Calls to syscall shmget(2).", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_IPC_SHM_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_SYSTEMD_SHM_GET_CONTEXT, + 20191, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_SHMAT_CHART, + "Calls to syscall shmat(2).", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_IPC_SHM_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_SYSTEMD_SHM_AT_CONTEXT, + 20192, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_SHMDT_CHART, + "Calls to syscall shmdt(2).", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_IPC_SHM_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_SYSTEMD_SHM_DT_CONTEXT, + 20193, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_SHMCTL_CHART, + "Calls to syscall shmctl(2).", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_IPC_SHM_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_SYSTEMD_SHM_CTL_CONTEXT, + 20194, + em->update_every); +} + +/** + * Obsolete cgroup chart + * + * Send obsolete for all charts created before to close. + * + * @param em a pointer to `struct ebpf_module` + */ +static inline void ebpf_obsolete_shm_cgroup_charts(ebpf_module_t *em) { + pthread_mutex_lock(&mutex_cgroup_shm); + + ebpf_cgroup_target_t *ect; + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + if (ect->systemd) { + ebpf_obsolete_shm_services(em, ect->name); + + continue; + } + + ebpf_obsolete_specific_shm_charts(ect->name, em->update_every); + } + pthread_mutex_unlock(&mutex_cgroup_shm); +} + +/** + * Obsolette apps charts + * + * Obsolete apps charts. + * + * @param em a pointer to the structure with the default values. + */ +void ebpf_obsolete_shm_apps_charts(struct ebpf_module *em) +{ + struct ebpf_target *w; + int update_every = em->update_every; + pthread_mutex_lock(&collect_data_mutex); + for (w = apps_groups_root_target; w; w = w->next) { + if (unlikely(!(w->charts_created & (1<<EBPF_MODULE_SHM_IDX)))) + continue; + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_shmget_call", + "Calls to syscall shmget(2).", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_IPC_SHM_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_shmget_call", + 20191, + update_every); + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_shmat_call", + "Calls to syscall shmat(2).", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_IPC_SHM_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_shmat_call", + 20192, + update_every); + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_shmdt_call", + "Calls to syscall shmdt(2).", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_IPC_SHM_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_shmdt_call", + 20193, + update_every); + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_shmctl_call", + "Calls to syscall shmctl(2).", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_IPC_SHM_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_shmctl_call", + 20194, + update_every); + + w->charts_created &= ~(1<<EBPF_MODULE_SHM_IDX); + } + pthread_mutex_unlock(&collect_data_mutex); +} + +/** + * Obsolete global + * + * Obsolete global charts created by thread. + * + * @param em a pointer to `struct ebpf_module` + */ +static void ebpf_obsolete_shm_global(ebpf_module_t *em) +{ + ebpf_write_chart_obsolete(NETDATA_EBPF_SYSTEM_GROUP, + NETDATA_SHM_GLOBAL_CHART, + "", + "Calls to shared memory system calls", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_SYSTEM_IPC_SHM_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, + "system.shared_memory_calls", + NETDATA_CHART_PRIO_SYSTEM_IPC_SHARED_MEM_CALLS, + em->update_every); +} + +/** + * SHM Exit + * + * Cancel child thread. + * + * @param ptr thread data. + */ +static void ebpf_shm_exit(void *pptr) +{ + ebpf_module_t *em = CLEANUP_FUNCTION_GET_PTR(pptr); + if(!em) return; + + if (ebpf_read_shm.thread) + nd_thread_signal_cancel(ebpf_read_shm.thread); + + if (em->enabled == NETDATA_THREAD_EBPF_FUNCTION_RUNNING) { + pthread_mutex_lock(&lock); + if (em->cgroup_charts) { + ebpf_obsolete_shm_cgroup_charts(em); + fflush(stdout); + } + + if (em->apps_charts & NETDATA_EBPF_APPS_FLAG_CHART_CREATED) { + ebpf_obsolete_shm_apps_charts(em); + } + + ebpf_obsolete_shm_global(em); + + fflush(stdout); + pthread_mutex_unlock(&lock); + } + + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps, EBPF_ACTION_STAT_REMOVE); + +#ifdef LIBBPF_MAJOR_VERSION + if (shm_bpf_obj) { + shm_bpf__destroy(shm_bpf_obj); + shm_bpf_obj = NULL; + } +#endif + + if (em->objects) { + ebpf_unload_legacy_code(em->objects, em->probe_links); + em->objects = NULL; + em->probe_links = NULL; + } + + pthread_mutex_lock(&ebpf_exit_cleanup); + em->enabled = NETDATA_THREAD_EBPF_STOPPED; + ebpf_update_stats(&plugin_statistics, em); + pthread_mutex_unlock(&ebpf_exit_cleanup); +} + +/***************************************************************** + * COLLECTOR THREAD + *****************************************************************/ + +/** + * Apps Accumulator + * + * Sum all values read from kernel and store in the first address. + * + * @param out the vector with read values. + * @param maps_per_core do I need to read all cores? + */ +static void shm_apps_accumulator(netdata_publish_shm_t *out, int maps_per_core) +{ + int i, end = (maps_per_core) ? ebpf_nprocs : 1; + netdata_publish_shm_t *total = &out[0]; + for (i = 1; i < end; i++) { + netdata_publish_shm_t *w = &out[i]; + total->get += w->get; + total->at += w->at; + total->dt += w->dt; + total->ctl += w->ctl; + } +} + +/** + * Update cgroup + * + * Update cgroup data based in + * + * @param maps_per_core do I need to read all cores? + */ +static void ebpf_update_shm_cgroup() +{ + netdata_publish_shm_t *cv = shm_vector; + size_t length = sizeof(netdata_publish_shm_t); + + ebpf_cgroup_target_t *ect; + + memset(cv, 0, length); + + pthread_mutex_lock(&mutex_cgroup_shm); + for (ect = ebpf_cgroup_pids; ect; ect = ect->next) { + struct pid_on_target2 *pids; + for (pids = ect->pids; pids; pids = pids->next) { + int pid = pids->pid; + netdata_publish_shm_t *out = &pids->shm; + ebpf_pid_stat_t *local_pid = ebpf_get_pid_entry(pid, 0); + if (local_pid) { + netdata_publish_shm_t *in = &local_pid->shm; + + memcpy(out, in, sizeof(netdata_publish_shm_t)); + } + } + } + pthread_mutex_unlock(&mutex_cgroup_shm); +} + +/** + * Read APPS table + * + * Read the apps table and store data inside the structure. + * + * @param maps_per_core do I need to read all cores? + */ +static void ebpf_read_shm_apps_table(int maps_per_core, int max_period) +{ + netdata_publish_shm_t *cv = shm_vector; + int fd = shm_maps[NETDATA_PID_SHM_TABLE].map_fd; + size_t length = sizeof(netdata_publish_shm_t); + if (maps_per_core) + length *= ebpf_nprocs; + + uint32_t key = 0, next_key = 0; + while (bpf_map_get_next_key(fd, &key, &next_key) == 0) { + if (bpf_map_lookup_elem(fd, &key, cv)) { + goto end_shm_loop; + } + + shm_apps_accumulator(cv, maps_per_core); + + ebpf_pid_stat_t *local_pid = ebpf_get_pid_entry(key, 0); + if (!local_pid) + goto end_shm_loop; + + + netdata_publish_shm_t *publish = &local_pid->shm; + if (!publish->ct || publish->ct != cv->ct) { + memcpy(publish, &cv[0], sizeof(netdata_publish_shm_t)); + local_pid->not_updated = 0; + } else if (++local_pid->not_updated >= max_period){ + bpf_map_delete_elem(fd, &key); + local_pid->not_updated = 0; + } + +end_shm_loop: + // now that we've consumed the value, zero it out in the map. + memset(cv, 0, length); + bpf_map_update_elem(fd, &key, cv, BPF_EXIST); + + key = next_key; + } +} + +/** +* Send global charts to netdata agent. +*/ +static void shm_send_global() +{ + ebpf_write_begin_chart(NETDATA_EBPF_SYSTEM_GROUP, NETDATA_SHM_GLOBAL_CHART, ""); + write_chart_dimension( + shm_publish_aggregated[NETDATA_KEY_SHMGET_CALL].dimension, + (long long) shm_hash_values[NETDATA_KEY_SHMGET_CALL] + ); + write_chart_dimension( + shm_publish_aggregated[NETDATA_KEY_SHMAT_CALL].dimension, + (long long) shm_hash_values[NETDATA_KEY_SHMAT_CALL] + ); + write_chart_dimension( + shm_publish_aggregated[NETDATA_KEY_SHMDT_CALL].dimension, + (long long) shm_hash_values[NETDATA_KEY_SHMDT_CALL] + ); + write_chart_dimension( + shm_publish_aggregated[NETDATA_KEY_SHMCTL_CALL].dimension, + (long long) shm_hash_values[NETDATA_KEY_SHMCTL_CALL] + ); + ebpf_write_end_chart(); +} + +/** + * Read global counter + * + * Read the table with number of calls for all functions + * + * @param stats vector used to read data from control table. + * @param maps_per_core do I need to read all cores? + */ +static void ebpf_shm_read_global_table(netdata_idx_t *stats, int maps_per_core) +{ + ebpf_read_global_table_stats(shm_hash_values, + shm_values, + shm_maps[NETDATA_SHM_GLOBAL_TABLE].map_fd, + maps_per_core, + NETDATA_KEY_SHMGET_CALL, + NETDATA_SHM_END); + + ebpf_read_global_table_stats(stats, + shm_values, + shm_maps[NETDATA_SHM_CONTROLLER].map_fd, + maps_per_core, + NETDATA_CONTROLLER_PID_TABLE_ADD, + NETDATA_CONTROLLER_END); +} + +/** + * Sum values for all targets. + */ +static void ebpf_shm_sum_pids(netdata_publish_shm_t *shm, struct ebpf_pid_on_target *root) +{ + memset(shm, 0, sizeof(netdata_publish_shm_t)); + while (root) { + int32_t pid = root->pid; + ebpf_pid_stat_t *pid_stat = ebpf_get_pid_entry(pid, 0); + if (pid_stat) { + netdata_publish_shm_t *w = &pid_stat->shm; + shm->get += w->get; + shm->at += w->at; + shm->dt += w->dt; + shm->ctl += w->ctl; + + // reset for next collection. + w->get = 0; + w->at = 0; + w->dt = 0; + w->ctl = 0; + } + root = root->next; + } +} + +/** + * Send data to Netdata calling auxiliary functions. + * + * @param root the target list. +*/ +void ebpf_shm_send_apps_data(struct ebpf_target *root) +{ + struct ebpf_target *w; + pthread_mutex_lock(&collect_data_mutex); + for (w = root; w; w = w->next) { + if (unlikely(!(w->charts_created & (1<<EBPF_MODULE_SHM_IDX)))) + continue; + + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_shmget_call"); + write_chart_dimension("calls", (long long) w->shm.get); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_shmat_call"); + write_chart_dimension("calls", (long long) w->shm.at); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_shmdt_call"); + write_chart_dimension("calls", (long long) w->shm.dt); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_shmctl_call"); + write_chart_dimension("calls", (long long) w->shm.ctl); + ebpf_write_end_chart(); + } + pthread_mutex_unlock(&collect_data_mutex); +} + +/** + * Sum values for all targets. + */ +static void ebpf_shm_sum_cgroup_pids(netdata_publish_shm_t *shm, struct pid_on_target2 *root) +{ + netdata_publish_shm_t shmv; + memset(&shmv, 0, sizeof(shmv)); + while (root) { + netdata_publish_shm_t *w = &root->shm; + shmv.get += w->get; + shmv.at += w->at; + shmv.dt += w->dt; + shmv.ctl += w->ctl; + + root = root->next; + } + + memcpy(shm, &shmv, sizeof(shmv)); +} + +/** + * Create specific shared memory charts + * + * Create charts for cgroup/application. + * + * @param type the chart type. + * @param update_every value to overwrite the update frequency set by the server. + */ +static void ebpf_create_specific_shm_charts(char *type, int update_every) +{ + char *label = (!strncmp(type, "cgroup_", 7)) ? &type[7] : type; + ebpf_create_chart(type, NETDATA_SHMGET_CHART, + "Calls to syscall shmget(2).", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_IPC_SHM_GROUP, + NETDATA_CGROUP_SHM_GET_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5800, + ebpf_create_global_dimension, + &shm_publish_aggregated[NETDATA_KEY_SHMGET_CALL], + 1, + update_every, + NETDATA_EBPF_MODULE_NAME_SHM); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + + ebpf_create_chart(type, NETDATA_SHMAT_CHART, + "Calls to syscall shmat(2).", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_IPC_SHM_GROUP, + NETDATA_CGROUP_SHM_AT_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5801, + ebpf_create_global_dimension, + &shm_publish_aggregated[NETDATA_KEY_SHMAT_CALL], + 1, + update_every, + NETDATA_EBPF_MODULE_NAME_SHM); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + + ebpf_create_chart(type, NETDATA_SHMDT_CHART, + "Calls to syscall shmdt(2).", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_IPC_SHM_GROUP, + NETDATA_CGROUP_SHM_DT_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5802, + ebpf_create_global_dimension, + &shm_publish_aggregated[NETDATA_KEY_SHMDT_CALL], + 1, + update_every, + NETDATA_EBPF_MODULE_NAME_SHM); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + + ebpf_create_chart(type, NETDATA_SHMCTL_CHART, + "Calls to syscall shmctl(2).", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_IPC_SHM_GROUP, + NETDATA_CGROUP_SHM_CTL_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5803, + ebpf_create_global_dimension, + &shm_publish_aggregated[NETDATA_KEY_SHMCTL_CALL], + 1, + update_every, + NETDATA_EBPF_MODULE_NAME_SHM); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); +} + +/** + * Obsolete specific shared memory charts + * + * Obsolete charts for cgroup/application. + * + * @param type the chart type. + * @param update_every value to overwrite the update frequency set by the server. + */ +static void ebpf_obsolete_specific_shm_charts(char *type, int update_every) +{ + ebpf_write_chart_obsolete(type, NETDATA_SHMGET_CHART, + "", + "Calls to syscall shmget(2).", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_IPC_SHM_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_SHM_GET_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5800, update_every); + + ebpf_write_chart_obsolete(type, NETDATA_SHMAT_CHART, + "", + "Calls to syscall shmat(2).", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_IPC_SHM_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_SHM_AT_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5801, update_every); + + ebpf_write_chart_obsolete(type, NETDATA_SHMDT_CHART, + "", + "Calls to syscall shmdt(2).", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_IPC_SHM_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_SHM_DT_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5802, update_every); + + ebpf_write_chart_obsolete(type, NETDATA_SHMCTL_CHART, + "", + "Calls to syscall shmctl(2).", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_IPC_SHM_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_SHM_CTL_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5803, update_every); +} + +/** + * Create Systemd Swap Charts + * + * Create charts when systemd is enabled + * + * @param update_every value to overwrite the update frequency set by the server. + **/ +static void ebpf_create_systemd_shm_charts(int update_every) +{ + static ebpf_systemd_args_t data_shmget = { + .title = "Calls to syscall shmget(2).", + .units = EBPF_COMMON_UNITS_CALLS_PER_SEC, + .family = NETDATA_APPS_IPC_SHM_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20191, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SYSTEMD_SHM_GET_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_SHM, + .update_every = 0, + .suffix = NETDATA_SHMGET_CHART, + .dimension = "calls" + }; + + static ebpf_systemd_args_t data_shmat = { + .title = "Calls to syscall shmat(2).", + .units = EBPF_COMMON_UNITS_CALLS_PER_SEC, + .family = NETDATA_APPS_IPC_SHM_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20192, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SYSTEMD_SHM_AT_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_SHM, + .update_every = 0, + .suffix = NETDATA_SHMAT_CHART, + .dimension = "calls" + }; + + static ebpf_systemd_args_t data_shmdt = { + .title = "Calls to syscall shmdt(2).", + .units = EBPF_COMMON_UNITS_CALLS_PER_SEC, + .family = NETDATA_APPS_IPC_SHM_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20193, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SYSTEMD_SHM_DT_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_SHM, + .update_every = 0, + .suffix = NETDATA_SHMDT_CHART, + .dimension = "calls" + }; + + static ebpf_systemd_args_t data_shmctl = { + .title = "Calls to syscall shmctl(2).", + .units = EBPF_COMMON_UNITS_CALLS_PER_SEC, + .family = NETDATA_APPS_IPC_SHM_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20194, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SYSTEMD_SHM_CTL_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_SHM, + .update_every = 0, + .suffix = NETDATA_SHMCTL_CHART, + .dimension = "calls" + }; + + if (!data_shmget.update_every) + data_shmat.update_every = data_shmctl.update_every = + data_shmdt.update_every = data_shmget.update_every = update_every; + + ebpf_cgroup_target_t *w; + for (w = ebpf_cgroup_pids; w; w = w->next) { + if (unlikely(!w->systemd || w->flags & NETDATA_EBPF_SERVICES_HAS_SHM_CHART)) + continue; + + data_shmat.id = data_shmctl.id = data_shmdt.id = data_shmget.id = w->name; + ebpf_create_charts_on_systemd(&data_shmat); + + ebpf_create_charts_on_systemd(&data_shmctl); + + ebpf_create_charts_on_systemd(&data_shmdt); + + ebpf_create_charts_on_systemd(&data_shmget); + + w->flags |= NETDATA_EBPF_SERVICES_HAS_SHM_CHART; + } +} + +/** + * Send Systemd charts + * + * Send collected data to Netdata. + */ +static void ebpf_send_systemd_shm_charts() +{ + ebpf_cgroup_target_t *ect; + for (ect = ebpf_cgroup_pids; ect; ect = ect->next) { + if (unlikely(!(ect->flags & NETDATA_EBPF_SERVICES_HAS_SHM_CHART)) ) { + continue; + } + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_SHMGET_CHART); + write_chart_dimension("calls", (long long)ect->publish_shm.get); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_SHMAT_CHART); + write_chart_dimension("calls", (long long)ect->publish_shm.at); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_SHMDT_CHART); + write_chart_dimension("calls", (long long)ect->publish_shm.dt); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_SHMCTL_CHART); + write_chart_dimension("calls", (long long)ect->publish_shm.ctl); + ebpf_write_end_chart(); + } +} + +/* + * Send Specific Shared memory data + * + * Send data for specific cgroup/apps. + * + * @param type chart type + * @param values structure with values that will be sent to netdata + */ +static void ebpf_send_specific_shm_data(char *type, netdata_publish_shm_t *values) +{ + ebpf_write_begin_chart(type, NETDATA_SHMGET_CHART, ""); + write_chart_dimension(shm_publish_aggregated[NETDATA_KEY_SHMGET_CALL].name, (long long)values->get); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(type, NETDATA_SHMAT_CHART, ""); + write_chart_dimension(shm_publish_aggregated[NETDATA_KEY_SHMAT_CALL].name, (long long)values->at); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(type, NETDATA_SHMDT_CHART, ""); + write_chart_dimension(shm_publish_aggregated[NETDATA_KEY_SHMDT_CALL].name, (long long)values->dt); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(type, NETDATA_SHMCTL_CHART, ""); + write_chart_dimension(shm_publish_aggregated[NETDATA_KEY_SHMCTL_CALL].name, (long long)values->ctl); + ebpf_write_end_chart(); +} + +/** + * Send data to Netdata calling auxiliary functions. + * + * @param update_every value to overwrite the update frequency set by the server. +*/ +void ebpf_shm_send_cgroup_data(int update_every) +{ + pthread_mutex_lock(&mutex_cgroup_shm); + ebpf_cgroup_target_t *ect; + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + ebpf_shm_sum_cgroup_pids(&ect->publish_shm, ect->pids); + } + + if (shm_ebpf_cgroup.header->systemd_enabled) { + if (send_cgroup_chart) { + ebpf_create_systemd_shm_charts(update_every); + } + + ebpf_send_systemd_shm_charts(); + } + + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + if (ect->systemd) + continue; + + if (!(ect->flags & NETDATA_EBPF_CGROUP_HAS_SHM_CHART) && ect->updated) { + ebpf_create_specific_shm_charts(ect->name, update_every); + ect->flags |= NETDATA_EBPF_CGROUP_HAS_SHM_CHART; + } + + if (ect->flags & NETDATA_EBPF_CGROUP_HAS_SHM_CHART) { + if (ect->updated) { + ebpf_send_specific_shm_data(ect->name, &ect->publish_shm); + } else { + ebpf_obsolete_specific_shm_charts(ect->name, update_every); + ect->flags &= ~NETDATA_EBPF_CGROUP_HAS_SWAP_CHART; + } + } + } + + pthread_mutex_unlock(&mutex_cgroup_shm); +} + +/** + * Resume apps data + */ +void ebpf_shm_resume_apps_data() { + struct ebpf_target *w; + for (w = apps_groups_root_target; w; w = w->next) { + if (unlikely(!(w->charts_created & (1 << EBPF_MODULE_SHM_IDX)))) + continue; + + ebpf_shm_sum_pids(&w->shm, w->root_pid); + } +} + +/** + * DCstat thread + * + * Thread used to generate dcstat charts. + * + * @param ptr a pointer to `struct ebpf_module` + * + * @return It always return NULL + */ +void *ebpf_read_shm_thread(void *ptr) +{ + heartbeat_t hb; + heartbeat_init(&hb); + + ebpf_module_t *em = (ebpf_module_t *)ptr; + + int maps_per_core = em->maps_per_core; + int update_every = em->update_every; + + int counter = update_every - 1; + + uint32_t lifetime = em->lifetime; + uint32_t running_time = 0; + usec_t period = update_every * USEC_PER_SEC; + int max_period = update_every * EBPF_CLEANUP_FACTOR; + while (!ebpf_plugin_stop() && running_time < lifetime) { + (void)heartbeat_next(&hb, period); + if (ebpf_plugin_stop() || ++counter != update_every) + continue; + + pthread_mutex_lock(&collect_data_mutex); + ebpf_read_shm_apps_table(maps_per_core, max_period); + ebpf_shm_resume_apps_data(); + pthread_mutex_unlock(&collect_data_mutex); + + counter = 0; + + pthread_mutex_lock(&ebpf_exit_cleanup); + if (running_time && !em->running_time) + running_time = update_every; + else + running_time += update_every; + + em->running_time = running_time; + pthread_mutex_unlock(&ebpf_exit_cleanup); + } + + return NULL; +} + +/** +* Main loop for this collector. +*/ +static void shm_collector(ebpf_module_t *em) +{ + int cgroups = em->cgroup_charts; + int update_every = em->update_every; + heartbeat_t hb; + heartbeat_init(&hb); + int counter = update_every - 1; + int maps_per_core = em->maps_per_core; + uint32_t running_time = 0; + uint32_t lifetime = em->lifetime; + netdata_idx_t *stats = em->hash_table_stats; + memset(stats, 0, sizeof(em->hash_table_stats)); + while (!ebpf_plugin_stop() && running_time < lifetime) { + (void)heartbeat_next(&hb, USEC_PER_SEC); + if (ebpf_plugin_stop() || ++counter != update_every) + continue; + + counter = 0; + netdata_apps_integration_flags_t apps = em->apps_charts; + ebpf_shm_read_global_table(stats, maps_per_core); + pthread_mutex_lock(&lock); + + if (cgroups && shm_ebpf_cgroup.header) { + ebpf_update_shm_cgroup(); + } + + shm_send_global(); + + if (apps & NETDATA_EBPF_APPS_FLAG_CHART_CREATED) { + ebpf_shm_send_apps_data(apps_groups_root_target); + } + + if (cgroups && shm_ebpf_cgroup.header) { + ebpf_shm_send_cgroup_data(update_every); + } + + pthread_mutex_unlock(&lock); + + pthread_mutex_lock(&ebpf_exit_cleanup); + if (running_time && !em->running_time) + running_time = update_every; + else + running_time += update_every; + + em->running_time = running_time; + pthread_mutex_unlock(&ebpf_exit_cleanup); + } +} + +/***************************************************************** + * INITIALIZE THREAD + *****************************************************************/ + +/** + * Create apps charts + * + * Call ebpf_create_chart to create the charts on apps submenu. + * + * @param em a pointer to the structure with the default values. + */ +void ebpf_shm_create_apps_charts(struct ebpf_module *em, void *ptr) +{ + struct ebpf_target *root = ptr; + struct ebpf_target *w; + int update_every = em->update_every; + for (w = root; w; w = w->next) { + if (unlikely(!w->exposed)) + continue; + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_shmget_call", + "Calls to syscall shmget(2).", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_IPC_SHM_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_shmget_call", + 20191, + update_every, + NETDATA_EBPF_MODULE_NAME_SHM); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION calls '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_shmat_call", + "Calls to syscall shmat(2).", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_IPC_SHM_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_shmat_call", + 20192, + update_every, + NETDATA_EBPF_MODULE_NAME_SHM); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION calls '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_shmdt_call", + "Calls to syscall shmdt(2).", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_IPC_SHM_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_shmdt_call", + 20193, + update_every, + NETDATA_EBPF_MODULE_NAME_SHM); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION calls '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_shmctl_call", + "Calls to syscall shmctl(2).", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_IPC_SHM_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_shmctl_call", + 20194, + update_every, + NETDATA_EBPF_MODULE_NAME_SHM); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION calls '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + + w->charts_created |= 1<<EBPF_MODULE_SHM_IDX; + } + + em->apps_charts |= NETDATA_EBPF_APPS_FLAG_CHART_CREATED; +} + +/** + * 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 apps is apps enabled? + */ +static void ebpf_shm_allocate_global_vectors(int apps) +{ + UNUSED(apps); + shm_vector = callocz((size_t)ebpf_nprocs, sizeof(netdata_publish_shm_t)); + shm_values = callocz((size_t)ebpf_nprocs, sizeof(netdata_idx_t)); + + memset(shm_hash_values, 0, sizeof(shm_hash_values)); +} + +/***************************************************************** + * MAIN THREAD + *****************************************************************/ + +/** + * Create global charts + * + * Call ebpf_create_chart to create the charts for the collector. + * + * @param update_every value to overwrite the update frequency set by the server. + */ +static void ebpf_create_shm_charts(int update_every) +{ + ebpf_create_chart( + NETDATA_EBPF_SYSTEM_GROUP, + NETDATA_SHM_GLOBAL_CHART, + "Calls to shared memory system calls", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_SYSTEM_IPC_SHM_SUBMENU, + "system.shared_memory_calls", + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_SYSTEM_IPC_SHARED_MEM_CALLS, + ebpf_create_global_dimension, + shm_publish_aggregated, + NETDATA_SHM_END, + update_every, NETDATA_EBPF_MODULE_NAME_SHM + ); + + fflush(stdout); +} + +/* + * Load BPF + * + * Load BPF files. + * + * @param em the structure with configuration + */ +static int ebpf_shm_load_bpf(ebpf_module_t *em) +{ +#ifdef LIBBPF_MAJOR_VERSION + ebpf_define_map_type(em->maps, em->maps_per_core, running_on_kernel); +#endif + + int ret = 0; + + ebpf_adjust_apps_cgroup(em, em->targets[NETDATA_KEY_SHMGET_CALL].mode); + if (em->load & EBPF_LOAD_LEGACY) { + em->probe_links = ebpf_load_program(ebpf_plugin_dir, em, running_on_kernel, isrh, &em->objects); + if (!em->probe_links) { + ret = -1; + } + } +#ifdef LIBBPF_MAJOR_VERSION + else { + shm_bpf_obj = shm_bpf__open(); + if (!shm_bpf_obj) + ret = -1; + else + ret = ebpf_shm_load_and_attach(shm_bpf_obj, em); + } +#endif + + + if (ret) + netdata_log_error("%s %s", EBPF_DEFAULT_ERROR_MSG, em->info.thread_name); + + return ret; +} + +/** + * Shared memory thread. + * + * @param ptr a pointer to `struct ebpf_module` + * @return It always return NULL + */ +void *ebpf_shm_thread(void *ptr) +{ + ebpf_module_t *em = (ebpf_module_t *)ptr; + + CLEANUP_FUNCTION_REGISTER(ebpf_shm_exit) cleanup_ptr = em; + + em->maps = shm_maps; + + ebpf_update_pid_table(&shm_maps[NETDATA_PID_SHM_TABLE], em); + +#ifdef LIBBPF_MAJOR_VERSION + ebpf_adjust_thread_load(em, default_btf); +#endif + if (ebpf_shm_load_bpf(em)) { + goto endshm; + } + + ebpf_shm_allocate_global_vectors(em->apps_charts); + + int algorithms[NETDATA_SHM_END] = { + NETDATA_EBPF_INCREMENTAL_IDX, + NETDATA_EBPF_INCREMENTAL_IDX, + NETDATA_EBPF_INCREMENTAL_IDX, + NETDATA_EBPF_INCREMENTAL_IDX + }; + ebpf_global_labels( + shm_aggregated_data, + shm_publish_aggregated, + shm_dimension_name, + shm_dimension_name, + algorithms, + NETDATA_SHM_END + ); + + pthread_mutex_lock(&lock); + ebpf_create_shm_charts(em->update_every); + ebpf_update_stats(&plugin_statistics, em); + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps, EBPF_ACTION_STAT_ADD); + pthread_mutex_unlock(&lock); + + ebpf_read_shm.thread = nd_thread_create(ebpf_read_shm.name, NETDATA_THREAD_OPTION_DEFAULT, ebpf_read_shm_thread, em); + + shm_collector(em); + +endshm: + ebpf_update_disabled_plugin_stats(em); + + return NULL; +} diff --git a/src/collectors/ebpf.plugin/ebpf_shm.h b/src/collectors/ebpf.plugin/ebpf_shm.h new file mode 100644 index 000000000..5a670b1b5 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_shm.h @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_EBPF_SHM_H +#define NETDATA_EBPF_SHM_H 1 + +// Module name & description +#define NETDATA_EBPF_MODULE_NAME_SHM "shm" +#define NETDATA_EBPF_SHM_MODULE_DESC "Show calls to syscalls shmget(2), shmat(2), shmdt(2) and shmctl(2). This thread is integrated with apps and cgroup." + +// charts +#define NETDATA_SHM_GLOBAL_CHART "shared_memory_calls" +#define NETDATA_SHMGET_CHART "shmget_call" +#define NETDATA_SHMAT_CHART "shmat_call" +#define NETDATA_SHMDT_CHART "shmdt_call" +#define NETDATA_SHMCTL_CHART "shmctl_call" + +// configuration file +#define NETDATA_DIRECTORY_SHM_CONFIG_FILE "shm.conf" + +// Contexts +#define NETDATA_CGROUP_SHM_GET_CONTEXT "cgroup.shmget" +#define NETDATA_CGROUP_SHM_AT_CONTEXT "cgroup.shmat" +#define NETDATA_CGROUP_SHM_DT_CONTEXT "cgroup.shmdt" +#define NETDATA_CGROUP_SHM_CTL_CONTEXT "cgroup.shmctl" + +#define NETDATA_SYSTEMD_SHM_GET_CONTEXT "systemd.services.shmget" +#define NETDATA_SYSTEMD_SHM_AT_CONTEXT "systemd.services.shmat" +#define NETDATA_SYSTEMD_SHM_DT_CONTEXT "systemd.services.shmdt" +#define NETDATA_SYSTEMD_SHM_CTL_CONTEXT "systemd.services.shmctl" + +typedef struct netdata_publish_shm { + uint64_t ct; + char name[TASK_COMM_LEN]; + + uint64_t get; + uint64_t at; + uint64_t dt; + uint64_t ctl; +} netdata_publish_shm_t; + +enum shm_tables { + NETDATA_PID_SHM_TABLE, + NETDATA_SHM_CONTROLLER, + NETDATA_SHM_GLOBAL_TABLE +}; + +enum shm_counters { + NETDATA_KEY_SHMGET_CALL, + NETDATA_KEY_SHMAT_CALL, + NETDATA_KEY_SHMDT_CALL, + NETDATA_KEY_SHMCTL_CALL, + + // Keep this as last and don't skip numbers as it is used as element counter + NETDATA_SHM_END +}; + +void *ebpf_shm_thread(void *ptr); +void ebpf_shm_create_apps_charts(struct ebpf_module *em, void *ptr); +void ebpf_shm_release(netdata_publish_shm_t *stat); +extern netdata_ebpf_targets_t shm_targets[]; + +extern struct config shm_config; + +#endif diff --git a/src/collectors/ebpf.plugin/ebpf_socket.c b/src/collectors/ebpf.plugin/ebpf_socket.c new file mode 100644 index 000000000..9a55f7be4 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_socket.c @@ -0,0 +1,2950 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include <sys/resource.h> + +#include "ebpf.h" +#include "ebpf_socket.h" + +/***************************************************************** + * + * GLOBAL VARIABLES + * + *****************************************************************/ + +static char *socket_dimension_names[NETDATA_MAX_SOCKET_VECTOR] = { "received", "sent", "close", + "received", "sent", "retransmitted", + "connected_V4", "connected_V6", "connected_tcp", + "connected_udp"}; +static char *socket_id_names[NETDATA_MAX_SOCKET_VECTOR] = { "tcp_cleanup_rbuf", "tcp_sendmsg", "tcp_close", + "udp_recvmsg", "udp_sendmsg", "tcp_retransmit_skb", + "tcp_connect_v4", "tcp_connect_v6", "inet_csk_accept_tcp", + "inet_csk_accept_udp" }; + +static ebpf_local_maps_t socket_maps[] = {{.name = "tbl_global_sock", + .internal_input = NETDATA_SOCKET_COUNTER, + .user_input = 0, .type = NETDATA_EBPF_MAP_STATIC, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }, + {.name = "tbl_lports", + .internal_input = NETDATA_SOCKET_COUNTER, + .user_input = 0, .type = NETDATA_EBPF_MAP_STATIC, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_HASH +#endif + }, + {.name = "tbl_nd_socket", + .internal_input = NETDATA_COMPILED_CONNECTIONS_ALLOWED, + .user_input = NETDATA_MAXIMUM_CONNECTIONS_ALLOWED, + .type = NETDATA_EBPF_MAP_STATIC, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_HASH +#endif + }, + {.name = "tbl_nv_udp", + .internal_input = NETDATA_COMPILED_UDP_CONNECTIONS_ALLOWED, + .user_input = NETDATA_MAXIMUM_UDP_CONNECTIONS_ALLOWED, + .type = NETDATA_EBPF_MAP_STATIC, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_HASH +#endif + }, + {.name = "socket_ctrl", .internal_input = NETDATA_CONTROLLER_END, + .user_input = 0, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }, + {.name = NULL, .internal_input = 0, .user_input = 0, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }}; + +static netdata_idx_t *socket_hash_values = NULL; +static netdata_syscall_stat_t socket_aggregated_data[NETDATA_MAX_SOCKET_VECTOR]; +static netdata_publish_syscall_t socket_publish_aggregated[NETDATA_MAX_SOCKET_VECTOR]; + +netdata_socket_t *socket_values; + +ebpf_network_viewer_port_list_t *listen_ports = NULL; +ebpf_addresses_t tcp_v6_connect_address = {.function = "tcp_v6_connect", .hash = 0, .addr = 0, .type = 0}; + +struct config socket_config = { .first_section = NULL, + .last_section = NULL, + .mutex = NETDATA_MUTEX_INITIALIZER, + .index = { .avl_tree = { .root = NULL, .compar = appconfig_section_compare }, + .rwlock = AVL_LOCK_INITIALIZER } }; + +netdata_ebpf_targets_t socket_targets[] = { {.name = "inet_csk_accept", .mode = EBPF_LOAD_PROBE}, + {.name = "tcp_retransmit_skb", .mode = EBPF_LOAD_PROBE}, + {.name = "tcp_cleanup_rbuf", .mode = EBPF_LOAD_PROBE}, + {.name = "tcp_close", .mode = EBPF_LOAD_PROBE}, + {.name = "udp_recvmsg", .mode = EBPF_LOAD_PROBE}, + {.name = "tcp_sendmsg", .mode = EBPF_LOAD_PROBE}, + {.name = "udp_sendmsg", .mode = EBPF_LOAD_PROBE}, + {.name = "tcp_v4_connect", .mode = EBPF_LOAD_PROBE}, + {.name = "tcp_v6_connect", .mode = EBPF_LOAD_PROBE}, + {.name = NULL, .mode = EBPF_LOAD_TRAMPOLINE}}; + +struct netdata_static_thread ebpf_read_socket = { + .name = "EBPF_READ_SOCKET", + .config_section = NULL, + .config_name = NULL, + .env_name = NULL, + .enabled = 1, + .thread = NULL, + .init_routine = NULL, + .start_routine = NULL +}; + +ARAL *aral_socket_table = NULL; + +#ifdef LIBBPF_MAJOR_VERSION +/** + * Disable Probe + * + * Disable probes to use trampoline. + * + * @param obj is the main structure for bpf objects. + */ +static void ebpf_socket_disable_probes(struct socket_bpf *obj) +{ + bpf_program__set_autoload(obj->progs.netdata_inet_csk_accept_kretprobe, false); + bpf_program__set_autoload(obj->progs.netdata_tcp_v4_connect_kprobe, false); + bpf_program__set_autoload(obj->progs.netdata_tcp_v4_connect_kretprobe, false); + bpf_program__set_autoload(obj->progs.netdata_tcp_v6_connect_kprobe, false); + bpf_program__set_autoload(obj->progs.netdata_tcp_v6_connect_kretprobe, false); + bpf_program__set_autoload(obj->progs.netdata_tcp_retransmit_skb_kprobe, false); + bpf_program__set_autoload(obj->progs.netdata_tcp_cleanup_rbuf_kprobe, false); + bpf_program__set_autoload(obj->progs.netdata_tcp_close_kprobe, false); + bpf_program__set_autoload(obj->progs.netdata_udp_recvmsg_kprobe, false); + bpf_program__set_autoload(obj->progs.netdata_udp_recvmsg_kretprobe, false); + bpf_program__set_autoload(obj->progs.netdata_tcp_sendmsg_kretprobe, false); + bpf_program__set_autoload(obj->progs.netdata_tcp_sendmsg_kprobe, false); + bpf_program__set_autoload(obj->progs.netdata_udp_sendmsg_kretprobe, false); + bpf_program__set_autoload(obj->progs.netdata_udp_sendmsg_kprobe, false); +} + +/** + * Disable Trampoline + * + * Disable trampoline to use probes. + * + * @param obj is the main structure for bpf objects. + */ +static void ebpf_socket_disable_trampoline(struct socket_bpf *obj) +{ + bpf_program__set_autoload(obj->progs.netdata_inet_csk_accept_fexit, false); + bpf_program__set_autoload(obj->progs.netdata_tcp_v4_connect_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_tcp_v4_connect_fexit, false); + bpf_program__set_autoload(obj->progs.netdata_tcp_v6_connect_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_tcp_v6_connect_fexit, false); + bpf_program__set_autoload(obj->progs.netdata_tcp_retransmit_skb_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_tcp_cleanup_rbuf_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_tcp_close_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_udp_recvmsg_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_udp_recvmsg_fexit, false); + bpf_program__set_autoload(obj->progs.netdata_tcp_sendmsg_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_tcp_sendmsg_fexit, false); + bpf_program__set_autoload(obj->progs.netdata_udp_sendmsg_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_udp_sendmsg_fexit, false); +} + +/** + * Set trampoline target. + * + * @param obj is the main structure for bpf objects. + */ +static void ebpf_set_trampoline_target(struct socket_bpf *obj) +{ + bpf_program__set_attach_target(obj->progs.netdata_inet_csk_accept_fexit, 0, + socket_targets[NETDATA_FCNT_INET_CSK_ACCEPT].name); + + bpf_program__set_attach_target(obj->progs.netdata_tcp_v4_connect_fentry, 0, + socket_targets[NETDATA_FCNT_TCP_V4_CONNECT].name); + + bpf_program__set_attach_target(obj->progs.netdata_tcp_v4_connect_fexit, 0, + socket_targets[NETDATA_FCNT_TCP_V4_CONNECT].name); + + if (tcp_v6_connect_address.type == 'T') { + bpf_program__set_attach_target( + obj->progs.netdata_tcp_v6_connect_fentry, 0, socket_targets[NETDATA_FCNT_TCP_V6_CONNECT].name); + + bpf_program__set_attach_target(obj->progs.netdata_tcp_v6_connect_fexit, 0, + socket_targets[NETDATA_FCNT_TCP_V6_CONNECT].name); + } + + bpf_program__set_attach_target(obj->progs.netdata_tcp_retransmit_skb_fentry, 0, + socket_targets[NETDATA_FCNT_TCP_RETRANSMIT].name); + + bpf_program__set_attach_target(obj->progs.netdata_tcp_cleanup_rbuf_fentry, 0, + socket_targets[NETDATA_FCNT_CLEANUP_RBUF].name); + + bpf_program__set_attach_target(obj->progs.netdata_tcp_close_fentry, 0, + socket_targets[NETDATA_FCNT_TCP_CLOSE].name); + + bpf_program__set_attach_target(obj->progs.netdata_udp_recvmsg_fentry, 0, + socket_targets[NETDATA_FCNT_UDP_RECEVMSG].name); + + bpf_program__set_attach_target(obj->progs.netdata_udp_recvmsg_fexit, 0, + socket_targets[NETDATA_FCNT_UDP_RECEVMSG].name); + + bpf_program__set_attach_target(obj->progs.netdata_tcp_sendmsg_fentry, 0, + socket_targets[NETDATA_FCNT_TCP_SENDMSG].name); + + bpf_program__set_attach_target(obj->progs.netdata_tcp_sendmsg_fexit, 0, + socket_targets[NETDATA_FCNT_TCP_SENDMSG].name); + + bpf_program__set_attach_target(obj->progs.netdata_udp_sendmsg_fentry, 0, + socket_targets[NETDATA_FCNT_UDP_SENDMSG].name); + + bpf_program__set_attach_target(obj->progs.netdata_udp_sendmsg_fexit, 0, + socket_targets[NETDATA_FCNT_UDP_SENDMSG].name); +} + + +/** + * Disable specific trampoline + * + * Disable specific trampoline to match user selection. + * + * @param obj is the main structure for bpf objects. + * @param sel option selected by user. + */ +static inline void ebpf_socket_disable_specific_trampoline(struct socket_bpf *obj, netdata_run_mode_t sel) +{ + if (sel == MODE_RETURN) { + bpf_program__set_autoload(obj->progs.netdata_tcp_sendmsg_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_tcp_v4_connect_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_tcp_v6_connect_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_udp_sendmsg_fentry, false); + } else { + bpf_program__set_autoload(obj->progs.netdata_tcp_sendmsg_fexit, false); + bpf_program__set_autoload(obj->progs.netdata_tcp_v4_connect_fexit, false); + bpf_program__set_autoload(obj->progs.netdata_tcp_v6_connect_fexit, false); + bpf_program__set_autoload(obj->progs.netdata_udp_sendmsg_fexit, false); + } +} + +/** + * Disable specific probe + * + * Disable specific probe to match user selection. + * + * @param obj is the main structure for bpf objects. + * @param sel option selected by user. + */ +static inline void ebpf_socket_disable_specific_probe(struct socket_bpf *obj, netdata_run_mode_t sel) +{ + if (sel == MODE_RETURN) { + bpf_program__set_autoload(obj->progs.netdata_tcp_sendmsg_kprobe, false); + bpf_program__set_autoload(obj->progs.netdata_tcp_v4_connect_kprobe, false); + bpf_program__set_autoload(obj->progs.netdata_tcp_v6_connect_kprobe, false); + bpf_program__set_autoload(obj->progs.netdata_udp_sendmsg_kprobe, false); + } else { + bpf_program__set_autoload(obj->progs.netdata_tcp_sendmsg_kretprobe, false); + bpf_program__set_autoload(obj->progs.netdata_tcp_v4_connect_kretprobe, false); + bpf_program__set_autoload(obj->progs.netdata_tcp_v6_connect_kretprobe, false); + bpf_program__set_autoload(obj->progs.netdata_udp_sendmsg_kretprobe, false); + } +} + +/** + * Attach probes + * + * Attach probes to targets. + * + * @param obj is the main structure for bpf objects. + * @param sel option selected by user. + */ +static long ebpf_socket_attach_probes(struct socket_bpf *obj, netdata_run_mode_t sel) +{ + obj->links.netdata_inet_csk_accept_kretprobe = bpf_program__attach_kprobe(obj->progs.netdata_inet_csk_accept_kretprobe, + true, + socket_targets[NETDATA_FCNT_INET_CSK_ACCEPT].name); + long ret = libbpf_get_error(obj->links.netdata_inet_csk_accept_kretprobe); + if (ret) + return -1; + + obj->links.netdata_tcp_retransmit_skb_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_tcp_retransmit_skb_kprobe, + false, + socket_targets[NETDATA_FCNT_TCP_RETRANSMIT].name); + ret = libbpf_get_error(obj->links.netdata_tcp_retransmit_skb_kprobe); + if (ret) + return -1; + + obj->links.netdata_tcp_cleanup_rbuf_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_tcp_cleanup_rbuf_kprobe, + false, + socket_targets[NETDATA_FCNT_CLEANUP_RBUF].name); + ret = libbpf_get_error(obj->links.netdata_tcp_cleanup_rbuf_kprobe); + if (ret) + return -1; + + obj->links.netdata_tcp_close_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_tcp_close_kprobe, + false, + socket_targets[NETDATA_FCNT_TCP_CLOSE].name); + ret = libbpf_get_error(obj->links.netdata_tcp_close_kprobe); + if (ret) + return -1; + + obj->links.netdata_udp_recvmsg_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_udp_recvmsg_kprobe, + false, + socket_targets[NETDATA_FCNT_UDP_RECEVMSG].name); + ret = libbpf_get_error(obj->links.netdata_udp_recvmsg_kprobe); + if (ret) + return -1; + + obj->links.netdata_udp_recvmsg_kretprobe = bpf_program__attach_kprobe(obj->progs.netdata_udp_recvmsg_kretprobe, + true, + socket_targets[NETDATA_FCNT_UDP_RECEVMSG].name); + ret = libbpf_get_error(obj->links.netdata_udp_recvmsg_kretprobe); + if (ret) + return -1; + + if (sel == MODE_RETURN) { + obj->links.netdata_tcp_sendmsg_kretprobe = bpf_program__attach_kprobe(obj->progs.netdata_tcp_sendmsg_kretprobe, + true, + socket_targets[NETDATA_FCNT_TCP_SENDMSG].name); + ret = libbpf_get_error(obj->links.netdata_tcp_sendmsg_kretprobe); + if (ret) + return -1; + + obj->links.netdata_udp_sendmsg_kretprobe = bpf_program__attach_kprobe(obj->progs.netdata_udp_sendmsg_kretprobe, + true, + socket_targets[NETDATA_FCNT_UDP_SENDMSG].name); + ret = libbpf_get_error(obj->links.netdata_udp_sendmsg_kretprobe); + if (ret) + return -1; + + obj->links.netdata_tcp_v4_connect_kretprobe = bpf_program__attach_kprobe(obj->progs.netdata_tcp_v4_connect_kretprobe, + true, + socket_targets[NETDATA_FCNT_TCP_V4_CONNECT].name); + ret = libbpf_get_error(obj->links.netdata_tcp_v4_connect_kretprobe); + if (ret) + return -1; + + if (tcp_v6_connect_address.type == 'T') { + obj->links.netdata_tcp_v6_connect_kretprobe = bpf_program__attach_kprobe( + obj->progs.netdata_tcp_v6_connect_kretprobe, true, socket_targets[NETDATA_FCNT_TCP_V6_CONNECT].name); + ret = libbpf_get_error(obj->links.netdata_tcp_v6_connect_kretprobe); + if (ret) + return -1; + } + } else { + obj->links.netdata_tcp_sendmsg_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_tcp_sendmsg_kprobe, + false, + socket_targets[NETDATA_FCNT_TCP_SENDMSG].name); + ret = libbpf_get_error(obj->links.netdata_tcp_sendmsg_kprobe); + if (ret) + return -1; + + obj->links.netdata_udp_sendmsg_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_udp_sendmsg_kprobe, + false, + socket_targets[NETDATA_FCNT_UDP_SENDMSG].name); + ret = libbpf_get_error(obj->links.netdata_udp_sendmsg_kprobe); + if (ret) + return -1; + + obj->links.netdata_tcp_v4_connect_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_tcp_v4_connect_kprobe, + false, + socket_targets[NETDATA_FCNT_TCP_V4_CONNECT].name); + ret = libbpf_get_error(obj->links.netdata_tcp_v4_connect_kprobe); + if (ret) + return -1; + + if (tcp_v6_connect_address.type == 'T') { + obj->links.netdata_tcp_v6_connect_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_tcp_v6_connect_kprobe, + false, + socket_targets[NETDATA_FCNT_TCP_V6_CONNECT].name); + ret = libbpf_get_error(obj->links.netdata_tcp_v6_connect_kprobe); + if (ret) + return -1; + } + } + + return 0; +} + +/** + * Set hash tables + * + * Set the values for maps according the value given by kernel. + * + * @param obj is the main structure for bpf objects. + */ +static void ebpf_socket_set_hash_tables(struct socket_bpf *obj) +{ + socket_maps[NETDATA_SOCKET_GLOBAL].map_fd = bpf_map__fd(obj->maps.tbl_global_sock); + socket_maps[NETDATA_SOCKET_LPORTS].map_fd = bpf_map__fd(obj->maps.tbl_lports); + socket_maps[NETDATA_SOCKET_OPEN_SOCKET].map_fd = bpf_map__fd(obj->maps.tbl_nd_socket); + socket_maps[NETDATA_SOCKET_TABLE_UDP].map_fd = bpf_map__fd(obj->maps.tbl_nv_udp); + socket_maps[NETDATA_SOCKET_TABLE_CTRL].map_fd = bpf_map__fd(obj->maps.socket_ctrl); +} + +/** + * Adjust Map Size + * + * Resize maps according input from users. + * + * @param obj is the main structure for bpf objects. + * @param em structure with configuration + */ +static void ebpf_socket_adjust_map(struct socket_bpf *obj, ebpf_module_t *em) +{ + ebpf_update_map_size(obj->maps.tbl_nd_socket, &socket_maps[NETDATA_SOCKET_OPEN_SOCKET], + em, bpf_map__name(obj->maps.tbl_nd_socket)); + + ebpf_update_map_size(obj->maps.tbl_nv_udp, &socket_maps[NETDATA_SOCKET_TABLE_UDP], + em, bpf_map__name(obj->maps.tbl_nv_udp)); + + ebpf_update_map_type(obj->maps.tbl_nd_socket, &socket_maps[NETDATA_SOCKET_OPEN_SOCKET]); + ebpf_update_map_type(obj->maps.tbl_nv_udp, &socket_maps[NETDATA_SOCKET_TABLE_UDP]); + ebpf_update_map_type(obj->maps.socket_ctrl, &socket_maps[NETDATA_SOCKET_TABLE_CTRL]); + ebpf_update_map_type(obj->maps.tbl_global_sock, &socket_maps[NETDATA_SOCKET_GLOBAL]); + ebpf_update_map_type(obj->maps.tbl_lports, &socket_maps[NETDATA_SOCKET_LPORTS]); +} + +/** + * Disable TCP V6 connect + */ +static void ebpf_disable_tcp_v6_connect(struct socket_bpf *obj) +{ + bpf_program__set_autoload(obj->progs.netdata_tcp_v6_connect_kretprobe, false); + bpf_program__set_autoload(obj->progs.netdata_tcp_v6_connect_kprobe, false); + bpf_program__set_autoload(obj->progs.netdata_tcp_v6_connect_fexit, false); + bpf_program__set_autoload(obj->progs.netdata_tcp_v6_connect_fentry, false); +} + +/** + * Load and attach + * + * Load and attach the eBPF code in kernel. + * + * @param obj is the main structure for bpf objects. + * @param em structure with configuration + * + * @return it returns 0 on success and -1 otherwise + */ +static inline int ebpf_socket_load_and_attach(struct socket_bpf *obj, ebpf_module_t *em) +{ + netdata_ebpf_targets_t *mt = em->targets; + netdata_ebpf_program_loaded_t test = mt[NETDATA_FCNT_INET_CSK_ACCEPT].mode; + + if (test == EBPF_LOAD_TRAMPOLINE) { + ebpf_socket_disable_probes(obj); + + ebpf_set_trampoline_target(obj); + ebpf_socket_disable_specific_trampoline(obj, em->mode); + } else { // We are not using tracepoints for this thread. + ebpf_socket_disable_trampoline(obj); + + ebpf_socket_disable_specific_probe(obj, em->mode); + } + + ebpf_socket_adjust_map(obj, em); + + if (tcp_v6_connect_address.type != 'T') { + ebpf_disable_tcp_v6_connect(obj); + } + + int ret = socket_bpf__load(obj); + if (ret) { + fprintf(stderr, "failed to load BPF object: %d\n", ret); + return ret; + } + + if (test == EBPF_LOAD_TRAMPOLINE) { + ret = socket_bpf__attach(obj); + } else { + ret = (int)ebpf_socket_attach_probes(obj, em->mode); + } + + if (!ret) { + ebpf_socket_set_hash_tables(obj); + + ebpf_update_controller(socket_maps[NETDATA_SOCKET_TABLE_CTRL].map_fd, em); + } + + return ret; +} +#endif + +/***************************************************************** + * + * FUNCTIONS TO CLOSE THE THREAD + * + *****************************************************************/ + +/** + * Socket Free + * + * Cleanup variables after child threads to stop + * + * @param ptr thread data. + */ +static void ebpf_socket_free(ebpf_module_t *em ) +{ + pthread_mutex_lock(&ebpf_exit_cleanup); + em->enabled = NETDATA_THREAD_EBPF_STOPPED; + ebpf_update_stats(&plugin_statistics, em); + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps, EBPF_ACTION_STAT_REMOVE); + pthread_mutex_unlock(&ebpf_exit_cleanup); +} + +/** + * Obsolete Systemd Socket Charts + * + * Obsolete charts when systemd is enabled + * + * @param update_every value to overwrite the update frequency set by the server. + **/ +static void ebpf_obsolete_systemd_socket_charts(int update_every, char *id) +{ + int order = 20080; + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_NET_APPS_CONNECTION_TCP_V4, + "Calls to tcp_v4_connection", + EBPF_COMMON_UNITS_CONNECTIONS, + NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_SERVICES_SOCKET_TCP_V4_CONN_CONTEXT, + order++, + update_every); + + if (tcp_v6_connect_address.type == 'T') { + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_NET_APPS_CONNECTION_TCP_V6, + "Calls to tcp_v6_connection", + EBPF_COMMON_UNITS_CONNECTIONS, + NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_SERVICES_SOCKET_TCP_V6_CONN_CONTEXT, + order++, + update_every); + } + + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_NET_APPS_BANDWIDTH_RECV, + "Bits received", + EBPF_COMMON_UNITS_KILOBITS, + NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_SERVICES_SOCKET_BYTES_RECV_CONTEXT, + order++, + update_every); + + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_NET_APPS_BANDWIDTH_SENT, + "Bits sent", + EBPF_COMMON_UNITS_KILOBITS, + NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_SERVICES_SOCKET_BYTES_SEND_CONTEXT, + order++, + update_every); + + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_NET_APPS_BANDWIDTH_TCP_RECV_CALLS, + "Calls to tcp_cleanup_rbuf.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_SERVICES_SOCKET_TCP_RECV_CONTEXT, + order++, + update_every); + + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_NET_APPS_BANDWIDTH_TCP_SEND_CALLS, + "Calls to tcp_sendmsg.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_SERVICES_SOCKET_TCP_SEND_CONTEXT, + order++, + update_every); + + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_NET_APPS_BANDWIDTH_TCP_RETRANSMIT, + "Calls to tcp_retransmit", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_SERVICES_SOCKET_TCP_RETRANSMIT_CONTEXT, + order++, + update_every); + + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_NET_APPS_BANDWIDTH_UDP_SEND_CALLS, + "Calls to udp_sendmsg", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_SERVICES_SOCKET_UDP_SEND_CONTEXT, + order++, + update_every); + + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_NET_APPS_BANDWIDTH_UDP_RECV_CALLS, + "Calls to udp_recvmsg", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_SERVICES_SOCKET_UDP_RECV_CONTEXT, + order++, + update_every); +} + +static void ebpf_obsolete_specific_socket_charts(char *type, int update_every); +/** + * Obsolete cgroup chart + * + * Send obsolete for all charts created before to close. + * + * @param em a pointer to `struct ebpf_module` + */ +static inline void ebpf_obsolete_socket_cgroup_charts(ebpf_module_t *em) { + pthread_mutex_lock(&mutex_cgroup_shm); + + ebpf_cgroup_target_t *ect; + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + if (ect->systemd) { + ebpf_obsolete_systemd_socket_charts(em->update_every, ect->name); + + continue; + } + + ebpf_obsolete_specific_socket_charts(ect->name, em->update_every); + } + pthread_mutex_unlock(&mutex_cgroup_shm); +} + +/** + * Create apps charts + * + * Call ebpf_create_chart to create the charts on apps submenu. + * + * @param em a pointer to the structure with the default values. + */ +void ebpf_socket_obsolete_apps_charts(struct ebpf_module *em) +{ + int order = 20130; + struct ebpf_target *w; + int update_every = em->update_every; + pthread_mutex_lock(&collect_data_mutex); + for (w = apps_groups_root_target; w; w = w->next) { + if (unlikely(!(w->charts_created & (1<<EBPF_MODULE_SOCKET_IDX)))) + continue; + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_tcp_v4_connection", + "Calls to tcp_v4_connection.", + EBPF_COMMON_UNITS_CONNECTIONS, + NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_tcp_v4_connection", + order++, + update_every); + + if (tcp_v6_connect_address.type == 'T') { + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_tcp_v6_connection", + "Calls to tcp_v6_connection.", + EBPF_COMMON_UNITS_CONNECTIONS, + NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_tcp_v6_connection", + order++, + update_every); + } + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_sock_bytes_sent", + "Bits sent.", + EBPF_COMMON_UNITS_KILOBITS, + NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_sock_bytes_sent", + order++, + update_every); + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_sock_bytes_received", + "Bits received.", + EBPF_COMMON_UNITS_KILOBITS, + NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_sock_bytes_received", + order++, + update_every); + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_tcp_sendmsg", + "Calls to tcp_sendmsg.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_tcp_sendmsg", + order++, + update_every); + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_tcp_cleanup_rbuf", + "Calls to tcp_cleanup_rbuf.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_tcp_cleanup_rbuf", + order++, + update_every); + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_tcp_retransmit", + "Calls to tcp_retransmit.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_tcp_retransmit", + order++, + update_every); + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_udp_sendmsg", + "Calls to udp_sendmsg.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_udp_sendmsg", + order++, + update_every); + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_udp_recvmsg", + "Calls to udp_recvmsg.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_udp_recvmsg", + order++, + update_every); + + w->charts_created &= ~(1<<EBPF_MODULE_SOCKET_IDX); + } + pthread_mutex_unlock(&collect_data_mutex); +} + +/** + * Obsolete global charts + * + * Obsolete charts created. + * + * @param em a pointer to the structure with the default values. + */ +static void ebpf_socket_obsolete_global_charts(ebpf_module_t *em) +{ + int order = 21070; + ebpf_write_chart_obsolete(NETDATA_EBPF_IP_FAMILY, + NETDATA_INBOUND_CONNECTIONS, + "", + "Inbound connections.", + EBPF_COMMON_UNITS_CONNECTIONS, + NETDATA_SOCKET_KERNEL_FUNCTIONS, + NETDATA_EBPF_CHART_TYPE_LINE, + "ip.inbound_conn", + order++, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_EBPF_IP_FAMILY, + NETDATA_TCP_OUTBOUND_CONNECTIONS, + "", + "TCP outbound connections.", + EBPF_COMMON_UNITS_CONNECTIONS, + NETDATA_SOCKET_KERNEL_FUNCTIONS, + NETDATA_EBPF_CHART_TYPE_LINE, + "ip.tcp_outbound_conn", + order++, + em->update_every); + + + ebpf_write_chart_obsolete(NETDATA_EBPF_IP_FAMILY, + NETDATA_TCP_FUNCTION_COUNT, + "", + "Calls to internal functions", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_SOCKET_KERNEL_FUNCTIONS, + NETDATA_EBPF_CHART_TYPE_LINE, + "ip.tcp_functions", + order++, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_EBPF_IP_FAMILY, + NETDATA_TCP_FUNCTION_BITS, + "", + "TCP bandwidth", + EBPF_COMMON_UNITS_KILOBITS, + NETDATA_SOCKET_KERNEL_FUNCTIONS, + NETDATA_EBPF_CHART_TYPE_LINE, + "ip.total_tcp_bandwidth", + order++, + em->update_every); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_obsolete(NETDATA_EBPF_IP_FAMILY, + NETDATA_TCP_FUNCTION_ERROR, + "", + "TCP errors", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_SOCKET_KERNEL_FUNCTIONS, + NETDATA_EBPF_CHART_TYPE_LINE, + "ip.tcp_error", + order++, + em->update_every); + } + + ebpf_write_chart_obsolete(NETDATA_EBPF_IP_FAMILY, + NETDATA_TCP_RETRANSMIT, + "", + "Packages retransmitted", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_SOCKET_KERNEL_FUNCTIONS, + NETDATA_EBPF_CHART_TYPE_LINE, + "ip.tcp_retransmit", + order++, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_EBPF_IP_FAMILY, + NETDATA_UDP_FUNCTION_COUNT, + "", + "UDP calls", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_SOCKET_KERNEL_FUNCTIONS, + NETDATA_EBPF_CHART_TYPE_LINE, + "ip.udp_functions", + order++, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_EBPF_IP_FAMILY, + NETDATA_UDP_FUNCTION_BITS, + "", + "UDP bandwidth", + EBPF_COMMON_UNITS_KILOBITS, + NETDATA_SOCKET_KERNEL_FUNCTIONS, + NETDATA_EBPF_CHART_TYPE_LINE, + "ip.total_udp_bandwidth", + order++, + em->update_every); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_obsolete(NETDATA_EBPF_IP_FAMILY, + NETDATA_UDP_FUNCTION_ERROR, + "", + "UDP errors", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_SOCKET_KERNEL_FUNCTIONS, + NETDATA_EBPF_CHART_TYPE_LINE, + "ip.udp_error", + order++, + em->update_every); + } + + fflush(stdout); +} +/** + * Socket exit + * + * Clean up the main thread. + * + * @param ptr thread data. + */ +static void ebpf_socket_exit(void *pptr) +{ + ebpf_module_t *em = CLEANUP_FUNCTION_GET_PTR(pptr); + if(!em) return; + + if (ebpf_read_socket.thread) + nd_thread_signal_cancel(ebpf_read_socket.thread); + + if (em->enabled == NETDATA_THREAD_EBPF_FUNCTION_RUNNING) { + pthread_mutex_lock(&lock); + + if (em->cgroup_charts) { + ebpf_obsolete_socket_cgroup_charts(em); + fflush(stdout); + } + + if (em->apps_charts & NETDATA_EBPF_APPS_FLAG_CHART_CREATED) { + ebpf_socket_obsolete_apps_charts(em); + fflush(stdout); + } + + ebpf_socket_obsolete_global_charts(em); + + pthread_mutex_unlock(&lock); + } + + ebpf_socket_free(em); +} + +/***************************************************************** + * + * 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 tcp structure to store IO from tcp sockets + * @param udp structure to store IO from udp sockets + * @param input the structure with the input data. + */ +static void ebpf_update_global_publish( + netdata_publish_syscall_t *publish, netdata_publish_vfs_common_t *tcp, netdata_publish_vfs_common_t *udp, + 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; + } + + tcp->write = -(long)publish[0].nbyte; + tcp->read = (long)publish[1].nbyte; + + udp->write = -(long)publish[3].nbyte; + udp->read = (long)publish[4].nbyte; +} + +/** + * Socket Bytes 2 bits + * + * Convert data read from kernel ring to bits + * + * @param value data read from kernel ring + * + * @return + */ +static inline collected_number ebpf_socket_bytes2bits(uint64_t value) { + return (collected_number) (value* 8/BITS_IN_A_KILOBIT); +} + +/** + * Send Global Inbound connection + * + * Send number of connections read per protocol. + */ +static void ebpf_socket_send_global_inbound_conn() +{ + uint64_t udp_conn = 0; + uint64_t tcp_conn = 0; + ebpf_network_viewer_port_list_t *move = listen_ports; + while (move) { + if (move->protocol == IPPROTO_TCP) + tcp_conn += move->connections; + else + udp_conn += move->connections; + + move = move->next; + } + + ebpf_write_begin_chart(NETDATA_EBPF_IP_FAMILY, NETDATA_INBOUND_CONNECTIONS, ""); + write_chart_dimension(socket_publish_aggregated[NETDATA_IDX_INCOMING_CONNECTION_TCP].name, (long long) tcp_conn); + write_chart_dimension(socket_publish_aggregated[NETDATA_IDX_INCOMING_CONNECTION_UDP].name, (long long) udp_conn); + ebpf_write_end_chart(); +} + +/** + * Send data to Netdata calling auxiliary functions. + * + * @param em the structure with thread information + */ +static void ebpf_socket_send_data(ebpf_module_t *em) +{ + netdata_publish_vfs_common_t common_tcp; + netdata_publish_vfs_common_t common_udp; + ebpf_update_global_publish(socket_publish_aggregated, &common_tcp, &common_udp, socket_aggregated_data); + + ebpf_socket_send_global_inbound_conn(); + write_count_chart(NETDATA_TCP_OUTBOUND_CONNECTIONS, NETDATA_EBPF_IP_FAMILY, + &socket_publish_aggregated[NETDATA_IDX_TCP_CONNECTION_V4], 2); + + // We read bytes from function arguments, but bandwidth is given in bits, + // so we need to multiply by 8 to convert for the final value. + write_count_chart(NETDATA_TCP_FUNCTION_COUNT, NETDATA_EBPF_IP_FAMILY, socket_publish_aggregated, 3); + write_io_chart(NETDATA_TCP_FUNCTION_BITS, NETDATA_EBPF_IP_FAMILY, socket_id_names[0], + ebpf_socket_bytes2bits(common_tcp.read), socket_id_names[1], + ebpf_socket_bytes2bits(common_tcp.write)); + if (em->mode < MODE_ENTRY) { + write_err_chart(NETDATA_TCP_FUNCTION_ERROR, NETDATA_EBPF_IP_FAMILY, socket_publish_aggregated, 2); + } + write_count_chart(NETDATA_TCP_RETRANSMIT, NETDATA_EBPF_IP_FAMILY, + &socket_publish_aggregated[NETDATA_IDX_TCP_RETRANSMIT],1); + + write_count_chart(NETDATA_UDP_FUNCTION_COUNT, NETDATA_EBPF_IP_FAMILY, + &socket_publish_aggregated[NETDATA_IDX_UDP_RECVBUF],2); + write_io_chart(NETDATA_UDP_FUNCTION_BITS, NETDATA_EBPF_IP_FAMILY, + socket_id_names[3], ebpf_socket_bytes2bits(common_udp.read), + socket_id_names[4], ebpf_socket_bytes2bits(common_udp.write)); + if (em->mode < MODE_ENTRY) { + write_err_chart(NETDATA_UDP_FUNCTION_ERROR, NETDATA_EBPF_IP_FAMILY, + &socket_publish_aggregated[NETDATA_UDP_START], 2); + } +} + +/** + * Send data to Netdata calling auxiliary functions. + */ +void ebpf_socket_send_apps_data() +{ + struct ebpf_target *w; + pthread_mutex_lock(&collect_data_mutex); + for (w = apps_groups_root_target; w; w = w->next) { + if (unlikely(!(w->charts_created & (1<<EBPF_MODULE_SOCKET_IDX)))) + continue; + + ebpf_socket_publish_apps_t *values = &w->socket; + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_call_tcp_v4_connection"); + write_chart_dimension("connections", (collected_number) values->call_tcp_v4_connection); + ebpf_write_end_chart(); + + if (tcp_v6_connect_address.type == 'T') { + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_call_tcp_v6_connection"); + write_chart_dimension("calls", (collected_number) values->call_tcp_v6_connection); + ebpf_write_end_chart(); + } + + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_sock_bytes_sent"); + // We multiply by 0.008, because we read bytes, but we display bits + write_chart_dimension("bandwidth", ebpf_socket_bytes2bits(values->bytes_sent)); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_sock_bytes_received"); + // We multiply by 0.008, because we read bytes, but we display bits + write_chart_dimension("bandwidth", ebpf_socket_bytes2bits(values->bytes_received)); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_call_tcp_sendmsg"); + write_chart_dimension("calls", (collected_number) values->call_tcp_sent); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_call_tcp_cleanup_rbuf"); + write_chart_dimension("calls", (collected_number) values->call_tcp_received); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_call_tcp_retransmit"); + write_chart_dimension("calls", (collected_number) values->retransmit); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_call_udp_sendmsg"); + write_chart_dimension("calls", (collected_number) values->call_udp_sent); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_call_udp_recvmsg"); + write_chart_dimension("calls", (collected_number) values->call_udp_received); + ebpf_write_end_chart(); + } + pthread_mutex_unlock(&collect_data_mutex); +} + +/***************************************************************** + * + * FUNCTIONS TO CREATE CHARTS + * + *****************************************************************/ + +/** + * 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_socket_create_global_charts(ebpf_module_t *em) +{ + int order = 21070; + ebpf_create_chart(NETDATA_EBPF_IP_FAMILY, + NETDATA_INBOUND_CONNECTIONS, + "Inbound connections.", + EBPF_COMMON_UNITS_CONNECTIONS, + NETDATA_SOCKET_KERNEL_FUNCTIONS, + "ip.inbound_conn", + NETDATA_EBPF_CHART_TYPE_LINE, + order++, + ebpf_create_global_dimension, + &socket_publish_aggregated[NETDATA_IDX_INCOMING_CONNECTION_TCP], + 2, em->update_every, NETDATA_EBPF_MODULE_NAME_SOCKET); + + ebpf_create_chart(NETDATA_EBPF_IP_FAMILY, + NETDATA_TCP_OUTBOUND_CONNECTIONS, + "TCP outbound connections.", + EBPF_COMMON_UNITS_CONNECTIONS, + NETDATA_SOCKET_KERNEL_FUNCTIONS, + "ip.tcp_outbound_conn", + NETDATA_EBPF_CHART_TYPE_LINE, + order++, + ebpf_create_global_dimension, + &socket_publish_aggregated[NETDATA_IDX_TCP_CONNECTION_V4], + 2, em->update_every, NETDATA_EBPF_MODULE_NAME_SOCKET); + + + ebpf_create_chart(NETDATA_EBPF_IP_FAMILY, + NETDATA_TCP_FUNCTION_COUNT, + "Calls to internal functions", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_SOCKET_KERNEL_FUNCTIONS, + "ip.tcp_functions", + NETDATA_EBPF_CHART_TYPE_LINE, + order++, + ebpf_create_global_dimension, + socket_publish_aggregated, + 3, em->update_every, NETDATA_EBPF_MODULE_NAME_SOCKET); + + ebpf_create_chart(NETDATA_EBPF_IP_FAMILY, NETDATA_TCP_FUNCTION_BITS, + "TCP bandwidth", EBPF_COMMON_UNITS_KILOBITS, + NETDATA_SOCKET_KERNEL_FUNCTIONS, + "ip.total_tcp_bandwidth", + NETDATA_EBPF_CHART_TYPE_LINE, + order++, + ebpf_create_global_dimension, + socket_publish_aggregated, + 2, em->update_every, NETDATA_EBPF_MODULE_NAME_SOCKET); + + if (em->mode < MODE_ENTRY) { + ebpf_create_chart(NETDATA_EBPF_IP_FAMILY, + NETDATA_TCP_FUNCTION_ERROR, + "TCP errors", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_SOCKET_KERNEL_FUNCTIONS, + "ip.tcp_error", + NETDATA_EBPF_CHART_TYPE_LINE, + order++, + ebpf_create_global_dimension, + socket_publish_aggregated, + 2, em->update_every, NETDATA_EBPF_MODULE_NAME_SOCKET); + } + + ebpf_create_chart(NETDATA_EBPF_IP_FAMILY, + NETDATA_TCP_RETRANSMIT, + "Packages retransmitted", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_SOCKET_KERNEL_FUNCTIONS, + "ip.tcp_retransmit", + NETDATA_EBPF_CHART_TYPE_LINE, + order++, + ebpf_create_global_dimension, + &socket_publish_aggregated[NETDATA_IDX_TCP_RETRANSMIT], + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_SOCKET); + + ebpf_create_chart(NETDATA_EBPF_IP_FAMILY, + NETDATA_UDP_FUNCTION_COUNT, + "UDP calls", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_SOCKET_KERNEL_FUNCTIONS, + "ip.udp_functions", + NETDATA_EBPF_CHART_TYPE_LINE, + order++, + ebpf_create_global_dimension, + &socket_publish_aggregated[NETDATA_IDX_UDP_RECVBUF], + 2, em->update_every, NETDATA_EBPF_MODULE_NAME_SOCKET); + + ebpf_create_chart(NETDATA_EBPF_IP_FAMILY, NETDATA_UDP_FUNCTION_BITS, + "UDP bandwidth", EBPF_COMMON_UNITS_KILOBITS, + NETDATA_SOCKET_KERNEL_FUNCTIONS, + "ip.total_udp_bandwidth", + NETDATA_EBPF_CHART_TYPE_LINE, + order++, + ebpf_create_global_dimension, + &socket_publish_aggregated[NETDATA_IDX_UDP_RECVBUF], + 2, em->update_every, NETDATA_EBPF_MODULE_NAME_SOCKET); + + if (em->mode < MODE_ENTRY) { + ebpf_create_chart(NETDATA_EBPF_IP_FAMILY, + NETDATA_UDP_FUNCTION_ERROR, + "UDP errors", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_SOCKET_KERNEL_FUNCTIONS, + "ip.udp_error", + NETDATA_EBPF_CHART_TYPE_LINE, + order++, + ebpf_create_global_dimension, + &socket_publish_aggregated[NETDATA_IDX_UDP_RECVBUF], + 2, em->update_every, NETDATA_EBPF_MODULE_NAME_SOCKET); + } + + fflush(stdout); +} + +/** + * Create apps charts + * + * Call ebpf_create_chart to create the charts on apps submenu. + * + * @param em a pointer to the structure with the default values. + * @param ptr a pointer for targets + */ +void ebpf_socket_create_apps_charts(struct ebpf_module *em, void *ptr) +{ + struct ebpf_target *root = ptr; + struct ebpf_target *w; + int order = 20130; + int update_every = em->update_every; + for (w = root; w; w = w->next) { + if (unlikely(!w->exposed)) + continue; + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_tcp_v4_connection", + "Calls to tcp_v4_connection.", + EBPF_COMMON_UNITS_CONNECTIONS, + NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_tcp_v4_connection", + order++, + update_every, + NETDATA_EBPF_MODULE_NAME_SOCKET); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION connections '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + + if (tcp_v6_connect_address.type == 'T') { + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_tcp_v6_connection", + "Calls to tcp_v6_connection.", + EBPF_COMMON_UNITS_CONNECTIONS, + NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_tcp_v6_connection", + order++, + update_every, + NETDATA_EBPF_MODULE_NAME_SOCKET); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION connections '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + } + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_sock_bytes_sent", + "Bits sent.", + EBPF_COMMON_UNITS_KILOBITS, + NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_sock_bytes_sent", + order++, + update_every, + NETDATA_EBPF_MODULE_NAME_SOCKET); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION bandwidth '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_sock_bytes_received", + "Bits received.", + EBPF_COMMON_UNITS_KILOBITS, + NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_sock_bytes_received", + order++, + update_every, + NETDATA_EBPF_MODULE_NAME_SOCKET); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION bandwidth '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_tcp_sendmsg", + "Calls to tcp_sendmsg.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_tcp_sendmsg", + order++, + update_every, + NETDATA_EBPF_MODULE_NAME_SOCKET); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION calls '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_tcp_cleanup_rbuf", + "Calls to tcp_cleanup_rbuf.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_tcp_cleanup_rbuf", + order++, + update_every, + NETDATA_EBPF_MODULE_NAME_SOCKET); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION calls '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_tcp_retransmit", + "Calls to tcp_retransmit.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_tcp_retransmit", + order++, + update_every, + NETDATA_EBPF_MODULE_NAME_SOCKET); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION calls '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_udp_sendmsg", + "Calls to udp_sendmsg.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_udp_sendmsg", + order++, + update_every, + NETDATA_EBPF_MODULE_NAME_SOCKET); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION calls '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_udp_recvmsg", + "Calls to udp_recvmsg.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_udp_recvmsg", + order, + update_every, + NETDATA_EBPF_MODULE_NAME_SOCKET); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION calls '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + + w->charts_created |= 1<<EBPF_MODULE_SOCKET_IDX; + } + em->apps_charts |= NETDATA_EBPF_APPS_FLAG_CHART_CREATED; +} + +/***************************************************************** + * + * READ INFORMATION FROM KERNEL RING + * + *****************************************************************/ + +/** + * Is specific ip inside the range + * + * Check if the ip is inside a IP range previously defined + * + * @param cmp the IP to compare + * @param family the IP family + * + * @return It returns 1 if the IP is inside the range and 0 otherwise + */ +static int ebpf_is_specific_ip_inside_range(union netdata_ip_t *cmp, int family) +{ + if (!network_viewer_opt.excluded_ips && !network_viewer_opt.included_ips) + return 1; + + uint32_t ipv4_test = htonl(cmp->addr32[0]); + ebpf_network_viewer_ip_list_t *move = network_viewer_opt.excluded_ips; + while (move) { + if (family == AF_INET) { + if (move->first.addr32[0] <= ipv4_test && + ipv4_test <= move->last.addr32[0]) + return 0; + } else { + if (memcmp(move->first.addr8, cmp->addr8, sizeof(union netdata_ip_t)) <= 0 && + memcmp(move->last.addr8, cmp->addr8, sizeof(union netdata_ip_t)) >= 0) { + return 0; + } + } + move = move->next; + } + + move = network_viewer_opt.included_ips; + while (move) { + if (family == AF_INET && move->ver == AF_INET) { + if (move->first.addr32[0] <= ipv4_test && + move->last.addr32[0] >= ipv4_test) + return 1; + } else { + if (move->ver == AF_INET6 && + memcmp(move->first.addr8, cmp->addr8, sizeof(union netdata_ip_t)) <= 0 && + memcmp(move->last.addr8, cmp->addr8, sizeof(union netdata_ip_t)) >= 0) { + return 1; + } + } + move = move->next; + } + + return 0; +} + +/** + * Is port inside range + * + * Verify if the cmp port is inside the range [first, last]. + * This function expects only the last parameter as big endian. + * + * @param cmp the value to compare + * + * @return It returns 1 when cmp is inside and 0 otherwise. + */ +static int ebpf_is_port_inside_range(uint16_t cmp) +{ + // We do not have restrictions for ports. + if (!network_viewer_opt.excluded_port && !network_viewer_opt.included_port) + return 1; + + // Test if port is excluded + ebpf_network_viewer_port_list_t *move = network_viewer_opt.excluded_port; + while (move) { + if (move->cmp_first <= cmp && cmp <= move->cmp_last) + return 0; + + move = move->next; + } + + // Test if the port is inside allowed range + move = network_viewer_opt.included_port; + while (move) { + if (move->cmp_first <= cmp && cmp <= move->cmp_last) + return 1; + + move = move->next; + } + + return 0; +} + +/** + * Hostname matches pattern + * + * @param cmp the value to compare + * + * @return It returns 1 when the value matches and zero otherwise. + */ +int hostname_matches_pattern(char *cmp) +{ + if (!network_viewer_opt.included_hostnames && !network_viewer_opt.excluded_hostnames) + return 1; + + ebpf_network_viewer_hostname_list_t *move = network_viewer_opt.excluded_hostnames; + while (move) { + if (simple_pattern_matches(move->value_pattern, cmp)) + return 0; + + move = move->next; + } + + move = network_viewer_opt.included_hostnames; + while (move) { + if (simple_pattern_matches(move->value_pattern, cmp)) + return 1; + + move = move->next; + } + + + return 0; +} + +/** + * Is socket allowed? + * + * Compare destination addresses and destination ports to define next steps + * + * @param key the socket read from kernel ring + * @param data the socket data used also used to refuse some sockets. + * + * @return It returns 1 if this socket is inside the ranges and 0 otherwise. + */ +int ebpf_is_socket_allowed(netdata_socket_idx_t *key, netdata_socket_t *data) +{ + int ret = 0; + // If family is not AF_UNSPEC and it is different of specified + if (network_viewer_opt.family && network_viewer_opt.family != data->family) + goto endsocketallowed; + + if (!ebpf_is_port_inside_range(key->dport)) + goto endsocketallowed; + + ret = ebpf_is_specific_ip_inside_range(&key->daddr, data->family); + +endsocketallowed: + return ret; +} + +/** + * Hash accumulator + * + * @param values the values used to calculate the data. + * @param family the connection family + * @param end the values size. + */ +static void ebpf_hash_socket_accumulator(netdata_socket_t *values, int end) +{ + int i; + uint8_t protocol = values[0].protocol; + uint64_t ct = values[0].current_timestamp; + uint64_t ft = values[0].first_timestamp; + uint16_t family = AF_UNSPEC; + uint32_t external_origin = values[0].external_origin; + for (i = 1; i < end; i++) { + netdata_socket_t *w = &values[i]; + + values[0].tcp.call_tcp_sent += w->tcp.call_tcp_sent; + values[0].tcp.call_tcp_received += w->tcp.call_tcp_received; + values[0].tcp.tcp_bytes_received += w->tcp.tcp_bytes_received; + values[0].tcp.tcp_bytes_sent += w->tcp.tcp_bytes_sent; + values[0].tcp.close += w->tcp.close; + values[0].tcp.retransmit += w->tcp.retransmit; + values[0].tcp.ipv4_connect += w->tcp.ipv4_connect; + values[0].tcp.ipv6_connect += w->tcp.ipv6_connect; + + if (!protocol) + protocol = w->protocol; + + if (family == AF_UNSPEC) + family = w->family; + + if (w->current_timestamp > ct) + ct = w->current_timestamp; + + if (!ft) + ft = w->first_timestamp; + + if (w->external_origin) + external_origin = NETDATA_EBPF_SRC_IP_ORIGIN_EXTERNAL; + } + + values[0].protocol = (!protocol)?IPPROTO_TCP:protocol; + values[0].current_timestamp = ct; + values[0].first_timestamp = ft; + values[0].external_origin = external_origin; +} + +/** + * Translate socket + * + * Convert socket address to string + * + * @param dst structure where we will store + * @param key the socket address + */ +static void ebpf_socket_translate(netdata_socket_plus_t *dst, netdata_socket_idx_t *key) +{ + uint32_t resolve = network_viewer_opt.service_resolution_enabled; + char service[NI_MAXSERV]; + int ret; + if (dst->data.family == AF_INET) { + struct sockaddr_in ipv4_addr = { }; + ipv4_addr.sin_port = 0; + ipv4_addr.sin_addr.s_addr = key->saddr.addr32[0]; + ipv4_addr.sin_family = AF_INET; + if (resolve) { + // NI_NAMEREQD : It is too slow + ret = getnameinfo((struct sockaddr *) &ipv4_addr, sizeof(ipv4_addr), dst->socket_string.src_ip, + INET6_ADDRSTRLEN, service, NI_MAXSERV, NI_NUMERICHOST | NI_NUMERICSERV); + if (ret) { + collector_error("Cannot resolve name: %s", gai_strerror(ret)); + resolve = 0; + } else { + ipv4_addr.sin_addr.s_addr = key->daddr.addr32[0]; + + ipv4_addr.sin_port = key->dport; + ret = getnameinfo((struct sockaddr *) &ipv4_addr, sizeof(ipv4_addr), dst->socket_string.dst_ip, + INET6_ADDRSTRLEN, dst->socket_string.dst_port, NI_MAXSERV, + NI_NUMERICHOST); + if (ret) { + collector_error("Cannot resolve name: %s", gai_strerror(ret)); + resolve = 0; + } + } + } + + // When resolution fail, we should use addresses + if (!resolve) { + ipv4_addr.sin_addr.s_addr = key->saddr.addr32[0]; + + if(!inet_ntop(AF_INET, &ipv4_addr.sin_addr, dst->socket_string.src_ip, INET6_ADDRSTRLEN)) + netdata_log_info("Cannot convert IP %u .", ipv4_addr.sin_addr.s_addr); + + ipv4_addr.sin_addr.s_addr = key->daddr.addr32[0]; + + if(!inet_ntop(AF_INET, &ipv4_addr.sin_addr, dst->socket_string.dst_ip, INET6_ADDRSTRLEN)) + netdata_log_info("Cannot convert IP %u .", ipv4_addr.sin_addr.s_addr); + snprintfz(dst->socket_string.dst_port, NI_MAXSERV, "%u", ntohs(key->dport)); + } + } else { + struct sockaddr_in6 ipv6_addr = { }; + memcpy(&ipv6_addr.sin6_addr, key->saddr.addr8, sizeof(key->saddr.addr8)); + ipv6_addr.sin6_family = AF_INET6; + if (resolve) { + ret = getnameinfo((struct sockaddr *) &ipv6_addr, sizeof(ipv6_addr), dst->socket_string.src_ip, + INET6_ADDRSTRLEN, service, NI_MAXSERV, NI_NUMERICHOST | NI_NUMERICSERV); + if (ret) { + collector_error("Cannot resolve name: %s", gai_strerror(ret)); + resolve = 0; + } else { + memcpy(&ipv6_addr.sin6_addr, key->daddr.addr8, sizeof(key->daddr.addr8)); + ret = getnameinfo((struct sockaddr *) &ipv6_addr, sizeof(ipv6_addr), dst->socket_string.dst_ip, + INET6_ADDRSTRLEN, dst->socket_string.dst_port, NI_MAXSERV, + NI_NUMERICHOST); + if (ret) { + collector_error("Cannot resolve name: %s", gai_strerror(ret)); + resolve = 0; + } + } + } + + if (!resolve) { + memcpy(&ipv6_addr.sin6_addr, key->saddr.addr8, sizeof(key->saddr.addr8)); + if(!inet_ntop(AF_INET6, &ipv6_addr.sin6_addr, dst->socket_string.src_ip, INET6_ADDRSTRLEN)) + netdata_log_info("Cannot convert IPv6 Address."); + + memcpy(&ipv6_addr.sin6_addr, key->daddr.addr8, sizeof(key->daddr.addr8)); + if(!inet_ntop(AF_INET6, &ipv6_addr.sin6_addr, dst->socket_string.dst_ip, INET6_ADDRSTRLEN)) + netdata_log_info("Cannot convert IPv6 Address."); + snprintfz(dst->socket_string.dst_port, NI_MAXSERV, "%u", ntohs(key->dport)); + } + } + dst->pid = key->pid; + + if (!strcmp(dst->socket_string.dst_port, "0")) + snprintfz(dst->socket_string.dst_port, NI_MAXSERV, "%u", ntohs(key->dport)); +#ifdef NETDATA_DEV_MODE + collector_info("New socket: { ORIGIN IP: %s, ORIGIN : %u, DST IP:%s, DST PORT: %s, PID: %u, PROTO: %d, FAMILY: %d}", + dst->socket_string.src_ip, + dst->data.external_origin, + dst->socket_string.dst_ip, + dst->socket_string.dst_port, + dst->pid, + dst->data.protocol, + dst->data.family + ); +#endif +} + +/** + * Update array vectors + * + * Read data from hash table and update vectors. + * + * @param em the structure with configuration + */ +static void ebpf_update_array_vectors(ebpf_module_t *em) +{ + netdata_socket_idx_t key = {}; + netdata_socket_idx_t next_key = {}; + + int maps_per_core = em->maps_per_core; + int fd = em->maps[NETDATA_SOCKET_OPEN_SOCKET].map_fd; + + netdata_socket_t *values = socket_values; + size_t length = sizeof(netdata_socket_t); + int test, end; + if (maps_per_core) { + length *= ebpf_nprocs; + end = ebpf_nprocs; + } else + end = 1; + + // We need to reset the values when we are working on kernel 4.15 or newer, because kernel does not create + // values for specific processor unless it is used to store data. As result of this behavior one the next socket + // can have values from the previous one. + memset(values, 0, length); + time_t update_time = time(NULL); + while (bpf_map_get_next_key(fd, &key, &next_key) == 0) { + test = bpf_map_lookup_elem(fd, &key, values); + if (test < 0) { + goto end_socket_loop; + } + + if (key.pid > (uint32_t)pid_max) { + goto end_socket_loop; + } + + ebpf_hash_socket_accumulator(values, end); + ebpf_socket_fill_publish_apps(key.pid, values); + + // We update UDP to show info with charts, but we do not show them with functions + /* + if (key.dport == NETDATA_EBPF_UDP_PORT && values[0].protocol == IPPROTO_UDP) { + bpf_map_delete_elem(fd, &key); + goto end_socket_loop; + } + */ + + // Discard non-bind sockets + if (!key.daddr.addr64[0] && !key.daddr.addr64[1] && !key.saddr.addr64[0] && !key.saddr.addr64[1]) { + bpf_map_delete_elem(fd, &key); + goto end_socket_loop; + } + + // When socket is not allowed, we do not append it to table, but we are still keeping it to accumulate data. + if (!ebpf_is_socket_allowed(&key, values)) { + goto end_socket_loop; + } + + // Get PID structure + rw_spinlock_write_lock(&ebpf_judy_pid.index.rw_spinlock); + PPvoid_t judy_array = &ebpf_judy_pid.index.JudyLArray; + netdata_ebpf_judy_pid_stats_t *pid_ptr = ebpf_get_pid_from_judy_unsafe(judy_array, key.pid); + if (!pid_ptr) { + goto end_socket_loop; + } + + // Get Socket structure + rw_spinlock_write_lock(&pid_ptr->socket_stats.rw_spinlock); + netdata_socket_plus_t **socket_pptr = (netdata_socket_plus_t **)ebpf_judy_insert_unsafe( + &pid_ptr->socket_stats.JudyLArray, values[0].first_timestamp); + netdata_socket_plus_t *socket_ptr = *socket_pptr; + bool translate = false; + if (likely(*socket_pptr == NULL)) { + *socket_pptr = aral_mallocz(aral_socket_table); + + socket_ptr = *socket_pptr; + + translate = true; + } + uint64_t prev_period = socket_ptr->data.current_timestamp; + memcpy(&socket_ptr->data, &values[0], sizeof(netdata_socket_t)); + if (translate) + ebpf_socket_translate(socket_ptr, &key); + else { // Check socket was updated + if (prev_period) { + if (values[0].current_timestamp > prev_period) // Socket updated + socket_ptr->last_update = update_time; + else if ((update_time - socket_ptr->last_update) > em->update_every) { + // Socket was not updated since last read + JudyLDel(&pid_ptr->socket_stats.JudyLArray, values[0].first_timestamp, PJE0); + aral_freez(aral_socket_table, socket_ptr); + } + } else // First time + socket_ptr->last_update = update_time; + } + + rw_spinlock_write_unlock(&pid_ptr->socket_stats.rw_spinlock); + rw_spinlock_write_unlock(&ebpf_judy_pid.index.rw_spinlock); + +end_socket_loop: + memset(values, 0, length); + memcpy(&key, &next_key, sizeof(key)); + } +} +/** + * Resume apps data + */ +void ebpf_socket_resume_apps_data() +{ + struct ebpf_target *w; + + for (w = apps_groups_root_target; w; w = w->next) { + if (unlikely(!(w->charts_created & (1<<EBPF_MODULE_SOCKET_IDX)))) + continue; + + struct ebpf_pid_on_target *move = w->root_pid; + + ebpf_socket_publish_apps_t *values = &w->socket; + memset(&w->socket, 0, sizeof(ebpf_socket_publish_apps_t)); + while (move) { + int32_t pid = move->pid; + ebpf_pid_stat_t *local_pid = ebpf_get_pid_entry(pid, 0); + if (local_pid) { + ebpf_socket_publish_apps_t *ws = &local_pid->socket; + values->call_tcp_v4_connection = ws->call_tcp_v4_connection; + values->call_tcp_v6_connection = ws->call_tcp_v6_connection; + values->bytes_sent = ws->bytes_sent; + values->bytes_received = ws->bytes_received; + values->call_tcp_sent = ws->call_tcp_sent; + values->call_tcp_received = ws->call_tcp_received; + values->retransmit = ws->retransmit; + values->call_udp_sent = ws->call_udp_sent; + values->call_udp_received = ws->call_udp_received; + } + + move = move->next; + } + } +} + +/** + * Socket thread + * + * Thread used to generate socket charts. + * + * @param ptr a pointer to `struct ebpf_module` + * + * @return It always return NULL + */ +void *ebpf_read_socket_thread(void *ptr) +{ + heartbeat_t hb; + heartbeat_init(&hb); + + ebpf_module_t *em = (ebpf_module_t *)ptr; + + ebpf_update_array_vectors(em); + + int update_every = em->update_every; + int counter = update_every - 1; + + uint32_t running_time = 0; + uint32_t lifetime = em->lifetime; + usec_t period = update_every * USEC_PER_SEC; + while (!ebpf_plugin_stop() && running_time < lifetime) { + (void)heartbeat_next(&hb, period); + if (ebpf_plugin_stop() || ++counter != update_every) + continue; + + pthread_mutex_lock(&collect_data_mutex); + ebpf_update_array_vectors(em); + ebpf_socket_resume_apps_data(); + pthread_mutex_unlock(&collect_data_mutex); + + counter = 0; + } + + return NULL; +} + +/** + * Fill Network Viewer Port list + * + * Fill the structure with values read from /proc or hash table. + * + * @param out the structure where we will store data. + * @param value the ports we are listen to. + * @param proto the protocol used for this connection. + * @param in the structure with values read form different sources. + */ +static inline void fill_nv_port_list(ebpf_network_viewer_port_list_t *out, uint16_t value, uint16_t proto, + netdata_passive_connection_t *in) +{ + out->first = value; + out->protocol = proto; + out->pid = in->pid; + out->tgid = in->tgid; + out->connections = in->counter; +} + +/** + * Update listen table + * + * Update link list when it is necessary. + * + * @param value the ports we are listen to. + * @param proto the protocol used with port connection. + * @param in the structure with values read form different sources. + */ +void update_listen_table(uint16_t value, uint16_t proto, netdata_passive_connection_t *in) +{ + ebpf_network_viewer_port_list_t *w; + if (likely(listen_ports)) { + ebpf_network_viewer_port_list_t *move = listen_ports, *store = listen_ports; + while (move) { + if (move->protocol == proto && move->first == value) { + move->pid = in->pid; + move->tgid = in->tgid; + move->connections = in->counter; + return; + } + + store = move; + move = move->next; + } + + w = callocz(1, sizeof(ebpf_network_viewer_port_list_t)); + store->next = w; + } else { + w = callocz(1, sizeof(ebpf_network_viewer_port_list_t)); + + listen_ports = w; + } + fill_nv_port_list(w, value, proto, in); + +#ifdef NETDATA_INTERNAL_CHECKS + netdata_log_info("The network viewer is monitoring inbound connections for port %u", ntohs(value)); +#endif +} + +/** + * Read listen table + * + * Read the table with all ports that we are listen on host. + */ +static void read_listen_table() +{ + netdata_passive_connection_idx_t key = {}; + netdata_passive_connection_idx_t next_key = {}; + + int fd = socket_maps[NETDATA_SOCKET_LPORTS].map_fd; + netdata_passive_connection_t value = {}; + while (bpf_map_get_next_key(fd, &key, &next_key) == 0) { + int test = bpf_map_lookup_elem(fd, &key, &value); + if (test < 0) { + key = next_key; + continue; + } + + // The correct protocol must come from kernel + update_listen_table(key.port, key.protocol, &value); + + key = next_key; + memset(&value, 0, sizeof(value)); + } + + if (next_key.port && value.pid) { + // The correct protocol must come from kernel + update_listen_table(next_key.port, next_key.protocol, &value); + } +} + +/** + * Read the hash table and store data to allocated vectors. + * + * @param stats vector used to read data from control table. + * @param maps_per_core do I need to read all cores? + */ +static void ebpf_socket_read_hash_global_tables(netdata_idx_t *stats, int maps_per_core) +{ + netdata_idx_t res[NETDATA_SOCKET_COUNTER]; + ebpf_read_global_table_stats(res, + socket_hash_values, + socket_maps[NETDATA_SOCKET_GLOBAL].map_fd, + maps_per_core, + NETDATA_KEY_CALLS_TCP_SENDMSG, + NETDATA_SOCKET_COUNTER); + + ebpf_read_global_table_stats(stats, + socket_hash_values, + socket_maps[NETDATA_SOCKET_TABLE_CTRL].map_fd, + maps_per_core, + NETDATA_CONTROLLER_PID_TABLE_ADD, + NETDATA_CONTROLLER_END); + + socket_aggregated_data[NETDATA_IDX_TCP_SENDMSG].call = res[NETDATA_KEY_CALLS_TCP_SENDMSG]; + socket_aggregated_data[NETDATA_IDX_TCP_CLEANUP_RBUF].call = res[NETDATA_KEY_CALLS_TCP_CLEANUP_RBUF]; + socket_aggregated_data[NETDATA_IDX_TCP_CLOSE].call = res[NETDATA_KEY_CALLS_TCP_CLOSE]; + socket_aggregated_data[NETDATA_IDX_UDP_RECVBUF].call = res[NETDATA_KEY_CALLS_UDP_RECVMSG]; + socket_aggregated_data[NETDATA_IDX_UDP_SENDMSG].call = res[NETDATA_KEY_CALLS_UDP_SENDMSG]; + socket_aggregated_data[NETDATA_IDX_TCP_RETRANSMIT].call = res[NETDATA_KEY_TCP_RETRANSMIT]; + socket_aggregated_data[NETDATA_IDX_TCP_CONNECTION_V4].call = res[NETDATA_KEY_CALLS_TCP_CONNECT_IPV4]; + socket_aggregated_data[NETDATA_IDX_TCP_CONNECTION_V6].call = res[NETDATA_KEY_CALLS_TCP_CONNECT_IPV6]; + + socket_aggregated_data[NETDATA_IDX_TCP_SENDMSG].ecall = res[NETDATA_KEY_ERROR_TCP_SENDMSG]; + socket_aggregated_data[NETDATA_IDX_TCP_CLEANUP_RBUF].ecall = res[NETDATA_KEY_ERROR_TCP_CLEANUP_RBUF]; + socket_aggregated_data[NETDATA_IDX_UDP_RECVBUF].ecall = res[NETDATA_KEY_ERROR_UDP_RECVMSG]; + socket_aggregated_data[NETDATA_IDX_UDP_SENDMSG].ecall = res[NETDATA_KEY_ERROR_UDP_SENDMSG]; + socket_aggregated_data[NETDATA_IDX_TCP_CONNECTION_V4].ecall = res[NETDATA_KEY_ERROR_TCP_CONNECT_IPV4]; + socket_aggregated_data[NETDATA_IDX_TCP_CONNECTION_V6].ecall = res[NETDATA_KEY_ERROR_TCP_CONNECT_IPV6]; + + socket_aggregated_data[NETDATA_IDX_TCP_SENDMSG].bytes = res[NETDATA_KEY_BYTES_TCP_SENDMSG]; + socket_aggregated_data[NETDATA_IDX_TCP_CLEANUP_RBUF].bytes = res[NETDATA_KEY_BYTES_TCP_CLEANUP_RBUF]; + socket_aggregated_data[NETDATA_IDX_UDP_RECVBUF].bytes = res[NETDATA_KEY_BYTES_UDP_RECVMSG]; + socket_aggregated_data[NETDATA_IDX_UDP_SENDMSG].bytes = res[NETDATA_KEY_BYTES_UDP_SENDMSG]; +} + +/** + * Fill publish apps when necessary. + * + * @param current_pid the PID that I am updating + * @param ns the structure with data read from memory. + */ +void ebpf_socket_fill_publish_apps(uint32_t current_pid, netdata_socket_t *ns) +{ + ebpf_pid_stat_t *local_pid = ebpf_get_pid_entry(current_pid, 0); + if (!local_pid) + return; + + ebpf_socket_publish_apps_t *curr = &local_pid->socket; + + curr->bytes_sent = ns->tcp.tcp_bytes_sent; + curr->bytes_received = ns->tcp.tcp_bytes_received; + curr->call_tcp_sent = ns->tcp.call_tcp_sent; + curr->call_tcp_received = ns->tcp.call_tcp_received; + curr->retransmit = ns->tcp.retransmit; + curr->call_close = ns->tcp.close; + curr->call_tcp_v4_connection = ns->tcp.ipv4_connect; + curr->call_tcp_v6_connection = ns->tcp.ipv6_connect; + + curr->call_udp_sent = ns->udp.call_udp_sent; + curr->call_udp_received = ns->udp.call_udp_received; +} + +/** + * Update cgroup + * + * Update cgroup data based in PIDs. + */ +static void ebpf_update_socket_cgroup() +{ + ebpf_cgroup_target_t *ect ; + + pthread_mutex_lock(&mutex_cgroup_shm); + for (ect = ebpf_cgroup_pids; ect; ect = ect->next) { + struct pid_on_target2 *pids; + for (pids = ect->pids; pids; pids = pids->next) { + int pid = pids->pid; + ebpf_socket_publish_apps_t *publish = &ect->publish_socket; + ebpf_pid_stat_t *local_pid = ebpf_get_pid_entry(pid, 0); + if (local_pid) { + ebpf_socket_publish_apps_t *in = &local_pid->socket; + + publish->bytes_sent = in->bytes_sent; + publish->bytes_received = in->bytes_received; + publish->call_tcp_sent = in->call_tcp_sent; + publish->call_tcp_received = in->call_tcp_received; + publish->retransmit = in->retransmit; + publish->call_udp_sent = in->call_udp_sent; + publish->call_udp_received = in->call_udp_received; + publish->call_close = in->call_close; + publish->call_tcp_v4_connection = in->call_tcp_v4_connection; + publish->call_tcp_v6_connection = in->call_tcp_v6_connection; + } + } + } + pthread_mutex_unlock(&mutex_cgroup_shm); +} + +/** + * Sum PIDs + * + * Sum values for all targets. + * + * @param fd structure used to store data + * @param pids input data + */ +static void ebpf_socket_sum_cgroup_pids(ebpf_socket_publish_apps_t *socket, struct pid_on_target2 *pids) +{ + ebpf_socket_publish_apps_t accumulator; + memset(&accumulator, 0, sizeof(accumulator)); + + while (pids) { + netdata_socket_t *w = &pids->socket; + + accumulator.bytes_received += w->tcp.tcp_bytes_received; + accumulator.bytes_sent += w->tcp.tcp_bytes_sent; + accumulator.call_tcp_received += w->tcp.call_tcp_received; + accumulator.call_tcp_sent += w->tcp.call_tcp_sent; + accumulator.retransmit += w->tcp.retransmit; + accumulator.call_close += w->tcp.close; + accumulator.call_tcp_v4_connection += w->tcp.ipv4_connect; + accumulator.call_tcp_v6_connection += w->tcp.ipv6_connect; + accumulator.call_udp_received += w->udp.call_udp_received; + accumulator.call_udp_sent += w->udp.call_udp_sent; + + pids = pids->next; + } + + socket->bytes_sent = (accumulator.bytes_sent >= socket->bytes_sent) ? accumulator.bytes_sent : socket->bytes_sent; + socket->bytes_received = (accumulator.bytes_received >= socket->bytes_received) ? accumulator.bytes_received : socket->bytes_received; + socket->call_tcp_sent = (accumulator.call_tcp_sent >= socket->call_tcp_sent) ? accumulator.call_tcp_sent : socket->call_tcp_sent; + socket->call_tcp_received = (accumulator.call_tcp_received >= socket->call_tcp_received) ? accumulator.call_tcp_received : socket->call_tcp_received; + socket->retransmit = (accumulator.retransmit >= socket->retransmit) ? accumulator.retransmit : socket->retransmit; + socket->call_udp_sent = (accumulator.call_udp_sent >= socket->call_udp_sent) ? accumulator.call_udp_sent : socket->call_udp_sent; + socket->call_udp_received = (accumulator.call_udp_received >= socket->call_udp_received) ? accumulator.call_udp_received : socket->call_udp_received; + socket->call_close = (accumulator.call_close >= socket->call_close) ? accumulator.call_close : socket->call_close; + socket->call_tcp_v4_connection = (accumulator.call_tcp_v4_connection >= socket->call_tcp_v4_connection) ? + accumulator.call_tcp_v4_connection : socket->call_tcp_v4_connection; + socket->call_tcp_v6_connection = (accumulator.call_tcp_v6_connection >= socket->call_tcp_v6_connection) ? + accumulator.call_tcp_v6_connection : socket->call_tcp_v6_connection; +} + +/** + * Create specific socket charts + * + * Create charts for cgroup/application. + * + * @param type the chart type. + * @param update_every value to overwrite the update frequency set by the server. + */ +static void ebpf_create_specific_socket_charts(char *type, int update_every) +{ + int order_basis = 5300; + char *label = (!strncmp(type, "cgroup_", 7)) ? &type[7] : type; + ebpf_create_chart(type, NETDATA_NET_APPS_CONNECTION_TCP_V4, + "Calls to tcp_v4_connection", + EBPF_COMMON_UNITS_CONNECTIONS, NETDATA_CGROUP_NET_GROUP, + NETDATA_CGROUP_TCP_V4_CONN_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + order_basis++, + ebpf_create_global_dimension, + &socket_publish_aggregated[NETDATA_IDX_TCP_CONNECTION_V4], 1, + update_every, NETDATA_EBPF_MODULE_NAME_SOCKET); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + + if (tcp_v6_connect_address.type == 'T') { + ebpf_create_chart(type, + NETDATA_NET_APPS_CONNECTION_TCP_V6, + "Calls to tcp_v6_connection", + EBPF_COMMON_UNITS_CONNECTIONS, + NETDATA_CGROUP_NET_GROUP, + NETDATA_CGROUP_TCP_V6_CONN_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + order_basis++, + ebpf_create_global_dimension, + &socket_publish_aggregated[NETDATA_IDX_TCP_CONNECTION_V6], + 1, + update_every, + NETDATA_EBPF_MODULE_NAME_SOCKET); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + } + + ebpf_create_chart(type, NETDATA_NET_APPS_BANDWIDTH_RECV, + "Bits received", + EBPF_COMMON_UNITS_KILOBITS, NETDATA_CGROUP_NET_GROUP, + NETDATA_CGROUP_SOCKET_BYTES_RECV_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + order_basis++, + ebpf_create_global_dimension, + &socket_publish_aggregated[NETDATA_IDX_TCP_CLEANUP_RBUF], 1, + update_every, NETDATA_EBPF_MODULE_NAME_SOCKET); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + + ebpf_create_chart(type, NETDATA_NET_APPS_BANDWIDTH_SENT, + "Bits sent", + EBPF_COMMON_UNITS_KILOBITS, NETDATA_CGROUP_NET_GROUP, + NETDATA_CGROUP_SOCKET_BYTES_SEND_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + order_basis++, + ebpf_create_global_dimension, + socket_publish_aggregated, 1, + update_every, NETDATA_EBPF_MODULE_NAME_SOCKET); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + + ebpf_create_chart(type, NETDATA_NET_APPS_BANDWIDTH_TCP_RECV_CALLS, + "Calls to tcp_cleanup_rbuf.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_CGROUP_NET_GROUP, + NETDATA_CGROUP_SOCKET_TCP_RECV_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + order_basis++, + ebpf_create_global_dimension, + &socket_publish_aggregated[NETDATA_IDX_TCP_CLEANUP_RBUF], 1, + update_every, NETDATA_EBPF_MODULE_NAME_SOCKET); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + + ebpf_create_chart(type, NETDATA_NET_APPS_BANDWIDTH_TCP_SEND_CALLS, + "Calls to tcp_sendmsg.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_CGROUP_NET_GROUP, + NETDATA_CGROUP_SOCKET_TCP_SEND_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + order_basis++, + ebpf_create_global_dimension, + socket_publish_aggregated, 1, + update_every, NETDATA_EBPF_MODULE_NAME_SOCKET); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + + ebpf_create_chart(type, NETDATA_NET_APPS_BANDWIDTH_TCP_RETRANSMIT, + "Calls to tcp_retransmit.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_CGROUP_NET_GROUP, + NETDATA_CGROUP_SOCKET_TCP_RETRANSMIT_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + order_basis++, + ebpf_create_global_dimension, + &socket_publish_aggregated[NETDATA_IDX_TCP_RETRANSMIT], 1, + update_every, NETDATA_EBPF_MODULE_NAME_SOCKET); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + + ebpf_create_chart(type, NETDATA_NET_APPS_BANDWIDTH_UDP_SEND_CALLS, + "Calls to udp_sendmsg", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_CGROUP_NET_GROUP, + NETDATA_CGROUP_SOCKET_UDP_SEND_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + order_basis++, + ebpf_create_global_dimension, + &socket_publish_aggregated[NETDATA_IDX_UDP_SENDMSG], 1, + update_every, NETDATA_EBPF_MODULE_NAME_SOCKET); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + + ebpf_create_chart(type, NETDATA_NET_APPS_BANDWIDTH_UDP_RECV_CALLS, + "Calls to udp_recvmsg", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_CGROUP_NET_GROUP, + NETDATA_CGROUP_SOCKET_UDP_RECV_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + order_basis++, + ebpf_create_global_dimension, + &socket_publish_aggregated[NETDATA_IDX_UDP_RECVBUF], 1, + update_every, NETDATA_EBPF_MODULE_NAME_SOCKET); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); +} + +/** + * Obsolete specific socket charts + * + * Obsolete charts for cgroup/application. + * + * @param type the chart type. + * @param update_every value to overwrite the update frequency set by the server. + */ +static void ebpf_obsolete_specific_socket_charts(char *type, int update_every) +{ + int order_basis = 5300; + ebpf_write_chart_obsolete(type, NETDATA_NET_APPS_CONNECTION_TCP_V4, "", "Calls to tcp_v4_connection", + EBPF_COMMON_UNITS_CONNECTIONS, NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_SERVICES_SOCKET_TCP_V4_CONN_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + order_basis++, update_every); + + if (tcp_v6_connect_address.type == 'T') { + ebpf_write_chart_obsolete(type, + NETDATA_NET_APPS_CONNECTION_TCP_V6, + "", + "Calls to tcp_v6_connection", + EBPF_COMMON_UNITS_CONNECTIONS, + NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_SERVICES_SOCKET_TCP_V6_CONN_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + order_basis++, + update_every); + } + + ebpf_write_chart_obsolete(type, NETDATA_NET_APPS_BANDWIDTH_RECV, "", "Bits received", + EBPF_COMMON_UNITS_KILOBITS, NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_SERVICES_SOCKET_BYTES_RECV_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + order_basis++, update_every); + + ebpf_write_chart_obsolete(type, NETDATA_NET_APPS_BANDWIDTH_SENT, "","Bits sent", + EBPF_COMMON_UNITS_KILOBITS, NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_SERVICES_SOCKET_BYTES_SEND_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + order_basis++, update_every); + + ebpf_write_chart_obsolete(type, NETDATA_NET_APPS_BANDWIDTH_TCP_RECV_CALLS, "", "Calls to tcp_cleanup_rbuf.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_SERVICES_SOCKET_TCP_RECV_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + order_basis++, update_every); + + ebpf_write_chart_obsolete(type, NETDATA_NET_APPS_BANDWIDTH_TCP_SEND_CALLS, "", "Calls to tcp_sendmsg.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_SERVICES_SOCKET_TCP_SEND_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + order_basis++, update_every); + + ebpf_write_chart_obsolete(type, NETDATA_NET_APPS_BANDWIDTH_TCP_RETRANSMIT, "", "Calls to tcp_retransmit.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_SERVICES_SOCKET_TCP_RETRANSMIT_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + order_basis++, update_every); + + ebpf_write_chart_obsolete(type, NETDATA_NET_APPS_BANDWIDTH_UDP_SEND_CALLS, "", "Calls to udp_sendmsg", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_APPS_NET_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_SERVICES_SOCKET_UDP_SEND_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + order_basis++, update_every); + + ebpf_write_chart_obsolete(type, NETDATA_NET_APPS_BANDWIDTH_UDP_RECV_CALLS, "", "Calls to udp_recvmsg", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_APPS_NET_GROUP, NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_SERVICES_SOCKET_UDP_RECV_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + order_basis++, update_every); +} + +/* + * Send Specific Swap data + * + * Send data for specific cgroup/apps. + * + * @param type chart type + * @param values structure with values that will be sent to netdata + */ +static void ebpf_send_specific_socket_data(char *type, ebpf_socket_publish_apps_t *values) +{ + ebpf_write_begin_chart(type, NETDATA_NET_APPS_CONNECTION_TCP_V4, ""); + write_chart_dimension(socket_publish_aggregated[NETDATA_IDX_TCP_CONNECTION_V4].name, + (long long) values->call_tcp_v4_connection); + ebpf_write_end_chart(); + + if (tcp_v6_connect_address.type == 'T') { + ebpf_write_begin_chart(type, NETDATA_NET_APPS_CONNECTION_TCP_V6, ""); + write_chart_dimension( + socket_publish_aggregated[NETDATA_IDX_TCP_CONNECTION_V6].name, (long long)values->call_tcp_v6_connection); + ebpf_write_end_chart(); + } + + ebpf_write_begin_chart(type, NETDATA_NET_APPS_BANDWIDTH_SENT, ""); + write_chart_dimension(socket_publish_aggregated[NETDATA_IDX_TCP_SENDMSG].name, + (long long) ebpf_socket_bytes2bits(values->bytes_sent)); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(type, NETDATA_NET_APPS_BANDWIDTH_RECV, ""); + write_chart_dimension(socket_publish_aggregated[NETDATA_IDX_TCP_CLEANUP_RBUF].name, + (long long) ebpf_socket_bytes2bits(values->bytes_received)); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(type, NETDATA_NET_APPS_BANDWIDTH_TCP_SEND_CALLS, ""); + write_chart_dimension(socket_publish_aggregated[NETDATA_IDX_TCP_SENDMSG].name, + (long long) values->call_tcp_sent); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(type, NETDATA_NET_APPS_BANDWIDTH_TCP_RECV_CALLS, ""); + write_chart_dimension(socket_publish_aggregated[NETDATA_IDX_TCP_CLEANUP_RBUF].name, + (long long) values->call_tcp_received); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(type, NETDATA_NET_APPS_BANDWIDTH_TCP_RETRANSMIT, ""); + write_chart_dimension(socket_publish_aggregated[NETDATA_IDX_TCP_RETRANSMIT].name, + (long long) values->retransmit); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(type, NETDATA_NET_APPS_BANDWIDTH_UDP_SEND_CALLS, ""); + write_chart_dimension(socket_publish_aggregated[NETDATA_IDX_UDP_SENDMSG].name, + (long long) values->call_udp_sent); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(type, NETDATA_NET_APPS_BANDWIDTH_UDP_RECV_CALLS, ""); + write_chart_dimension(socket_publish_aggregated[NETDATA_IDX_UDP_RECVBUF].name, + (long long) values->call_udp_received); + ebpf_write_end_chart(); +} + +/** + * Create Systemd Socket Charts + * + * Create charts when systemd is enabled + * + * @param update_every value to overwrite the update frequency set by the server. + **/ +static void ebpf_create_systemd_socket_charts(int update_every) +{ + static ebpf_systemd_args_t data_tcp_v4 = { + .title = "Calls to tcp_v4_connection", + .units = EBPF_COMMON_UNITS_CONNECTIONS, + .family = NETDATA_APPS_NET_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20080, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SERVICES_SOCKET_TCP_V4_CONN_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_SOCKET, + .update_every = 0, + .suffix = NETDATA_NET_APPS_CONNECTION_TCP_V4, + .dimension = EBPF_COMMON_UNITS_CONNECTIONS + }; + + static ebpf_systemd_args_t data_tcp_v6 = { + .title = "Calls to tcp_v6_connection", + .units = EBPF_COMMON_UNITS_CONNECTIONS, + .family = NETDATA_APPS_NET_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20081, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SERVICES_SOCKET_TCP_V6_CONN_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_SOCKET, + .update_every = 0, + .suffix = NETDATA_NET_APPS_CONNECTION_TCP_V6, + .dimension = "connection" + }; + + static ebpf_systemd_args_t data_bandwith_recv = { + .title = "Bits received", + .units = EBPF_COMMON_UNITS_KILOBITS, + .family = NETDATA_APPS_NET_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20082, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SERVICES_SOCKET_BYTES_RECV_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_SOCKET, + .update_every = 0, + .suffix = NETDATA_NET_APPS_BANDWIDTH_RECV, + .dimension = "connection" + }; + + static ebpf_systemd_args_t data_bandwith_sent = { + .title = "Bits sent", + .units = EBPF_COMMON_UNITS_KILOBITS, + .family = NETDATA_APPS_NET_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20083, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SERVICES_SOCKET_BYTES_SEND_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_SOCKET, + .update_every = 0, + .suffix = NETDATA_NET_APPS_BANDWIDTH_SENT, + .dimension = EBPF_COMMON_UNITS_KILOBITS + }; + + static ebpf_systemd_args_t data_tcp_cleanup = { + .title = "Calls to tcp_cleanup_rbuf.", + .units = EBPF_COMMON_UNITS_CALLS_PER_SEC, + .family = NETDATA_APPS_NET_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20084, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SERVICES_SOCKET_TCP_RECV_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_SOCKET, + .update_every = 0, + .suffix = NETDATA_NET_APPS_BANDWIDTH_TCP_RECV_CALLS, + .dimension = "calls" + }; + + static ebpf_systemd_args_t data_tcp_sendmsg = { + .title = "Calls to tcp_sendmsg.", + .units = EBPF_COMMON_UNITS_CALLS_PER_SEC, + .family = NETDATA_APPS_NET_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20085, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SERVICES_SOCKET_TCP_SEND_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_SOCKET, + .update_every = 0, + .suffix = NETDATA_NET_APPS_BANDWIDTH_TCP_SEND_CALLS, + .dimension = "calls" + }; + + static ebpf_systemd_args_t data_tcp_retransmit = { + .title = "Calls to tcp_retransmit", + .units = EBPF_COMMON_UNITS_CALLS_PER_SEC, + .family = NETDATA_APPS_NET_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20086, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SERVICES_SOCKET_TCP_RETRANSMIT_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_SOCKET, + .update_every = 0, + .suffix = NETDATA_NET_APPS_BANDWIDTH_TCP_RETRANSMIT, + .dimension = "calls" + }; + + static ebpf_systemd_args_t data_udp_send = { + .title = "Calls to udp_sendmsg", + .units = EBPF_COMMON_UNITS_CALLS_PER_SEC, + .family = NETDATA_APPS_NET_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20087, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SERVICES_SOCKET_UDP_SEND_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_SOCKET, + .update_every = 0, + .suffix = NETDATA_NET_APPS_BANDWIDTH_UDP_SEND_CALLS, + .dimension = "calls" + }; + + static ebpf_systemd_args_t data_udp_recv = { + .title = "Calls to udp_recvmsg", + .units = EBPF_COMMON_UNITS_CALLS_PER_SEC, + .family = NETDATA_APPS_NET_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20088, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SERVICES_SOCKET_UDP_RECV_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_SOCKET, + .update_every = 0, + .suffix = NETDATA_NET_APPS_BANDWIDTH_UDP_RECV_CALLS, + .dimension = "calls" + }; + + if (!data_tcp_v4.update_every) + data_tcp_v4.update_every = data_tcp_v6.update_every = data_bandwith_recv.update_every = + data_bandwith_sent.update_every = data_tcp_cleanup.update_every = data_tcp_sendmsg.update_every = + data_tcp_retransmit.update_every = data_udp_send.update_every = data_udp_recv.update_every = update_every; + + ebpf_cgroup_target_t *w; + for (w = ebpf_cgroup_pids; w ; w = w->next) { + if (unlikely(!w->systemd || w->flags & NETDATA_EBPF_SERVICES_HAS_SOCKET_CHART)) + continue; + + data_tcp_v4.id = data_tcp_v6.id = data_bandwith_recv.id = + data_bandwith_sent.id = data_tcp_cleanup.id = data_tcp_sendmsg.id = + data_tcp_retransmit.id = data_udp_send.id = data_udp_recv.id = w->name; + + ebpf_create_charts_on_systemd(&data_tcp_v4); + if (tcp_v6_connect_address.type == 'T') { + ebpf_create_charts_on_systemd(&data_tcp_v6); + } + + ebpf_create_charts_on_systemd(&data_bandwith_recv); + ebpf_create_charts_on_systemd(&data_bandwith_sent); + + ebpf_create_charts_on_systemd(&data_tcp_cleanup); + + ebpf_create_charts_on_systemd(&data_tcp_sendmsg); + + ebpf_create_charts_on_systemd(&data_tcp_retransmit); + + ebpf_create_charts_on_systemd(&data_udp_recv); + + ebpf_create_charts_on_systemd(&data_udp_send); + + w->flags |= NETDATA_EBPF_SERVICES_HAS_SOCKET_CHART; + } +} + +/** + * Send Systemd charts + * + * Send collected data to Netdata. + */ +static void ebpf_send_systemd_socket_charts() +{ + ebpf_cgroup_target_t *ect; + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + if (unlikely(!(ect->flags & NETDATA_EBPF_SERVICES_HAS_SOCKET_CHART)) ) { + continue; + } + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_NET_APPS_CONNECTION_TCP_V4); + write_chart_dimension("connections", (long long)ect->publish_socket.call_tcp_v4_connection); + ebpf_write_end_chart(); + + if (tcp_v6_connect_address.type == 'T') { + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_NET_APPS_CONNECTION_TCP_V6); + write_chart_dimension("connections", (long long)ect->publish_socket.call_tcp_v6_connection); + ebpf_write_end_chart(); + } + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_NET_APPS_BANDWIDTH_SENT); + write_chart_dimension("bits", (long long)ect->publish_socket.bytes_sent); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_NET_APPS_BANDWIDTH_RECV); + write_chart_dimension("bits", (long long)ect->publish_socket.bytes_received); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_NET_APPS_BANDWIDTH_TCP_SEND_CALLS); + write_chart_dimension("calls", (long long)ect->publish_socket.call_tcp_sent); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_NET_APPS_BANDWIDTH_TCP_RECV_CALLS); + write_chart_dimension("calls", (long long)ect->publish_socket.call_tcp_received); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_NET_APPS_BANDWIDTH_TCP_RETRANSMIT); + write_chart_dimension("calls", (long long)ect->publish_socket.retransmit); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_NET_APPS_BANDWIDTH_UDP_SEND_CALLS); + write_chart_dimension("calls", (long long)ect->publish_socket.call_udp_sent); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_NET_APPS_BANDWIDTH_UDP_RECV_CALLS); + write_chart_dimension("calls", (long long)ect->publish_socket.call_udp_received); + ebpf_write_end_chart(); + } +} + +/** + * Update Cgroup algorithm + * + * Change algorithm from absolute to incremental + */ +void ebpf_socket_update_cgroup_algorithm() +{ + int i; + for (i = 0; i < NETDATA_MAX_SOCKET_VECTOR; i++) { + netdata_publish_syscall_t *ptr = &socket_publish_aggregated[i]; + ptr->algorithm = ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]; + } +} + +/** + * Send data to Netdata calling auxiliary functions. + * + * @param update_every value to overwrite the update frequency set by the server. +*/ +static void ebpf_socket_send_cgroup_data(int update_every) +{ + pthread_mutex_lock(&mutex_cgroup_shm); + ebpf_cgroup_target_t *ect; + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + ebpf_socket_sum_cgroup_pids(&ect->publish_socket, ect->pids); + } + + if (shm_ebpf_cgroup.header->systemd_enabled) { + if (send_cgroup_chart) { + ebpf_create_systemd_socket_charts(update_every); + } + ebpf_send_systemd_socket_charts(); + } + + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + if (ect->systemd) + continue; + + if (!(ect->flags & NETDATA_EBPF_CGROUP_HAS_SOCKET_CHART)) { + ebpf_create_specific_socket_charts(ect->name, update_every); + ect->flags |= NETDATA_EBPF_CGROUP_HAS_SOCKET_CHART; + } + + if (ect->flags & NETDATA_EBPF_CGROUP_HAS_SOCKET_CHART && ect->updated) { + ebpf_send_specific_socket_data(ect->name, &ect->publish_socket); + } else { + ebpf_obsolete_specific_socket_charts(ect->name, update_every); + ect->flags &= ~NETDATA_EBPF_CGROUP_HAS_SOCKET_CHART; + } + } + + pthread_mutex_unlock(&mutex_cgroup_shm); +} + +/***************************************************************** + * + * FUNCTIONS WITH THE MAIN LOOP + * + *****************************************************************/ + +/** + * Main loop for this collector. + * + * @param em the structure with thread information + */ +static void socket_collector(ebpf_module_t *em) +{ + heartbeat_t hb; + heartbeat_init(&hb); + + int cgroups = em->cgroup_charts; + if (cgroups) + ebpf_socket_update_cgroup_algorithm(); + + int socket_global_enabled = em->global_charts; + int update_every = em->update_every; + int maps_per_core = em->maps_per_core; + int counter = update_every - 1; + uint32_t running_time = 0; + uint32_t lifetime = em->lifetime; + netdata_idx_t *stats = em->hash_table_stats; + memset(stats, 0, sizeof(em->hash_table_stats)); + while (!ebpf_plugin_stop() && running_time < lifetime) { + (void)heartbeat_next(&hb, USEC_PER_SEC); + if (ebpf_plugin_stop() || ++counter != update_every) + continue; + + counter = 0; + netdata_apps_integration_flags_t socket_apps_enabled = em->apps_charts; + if (socket_global_enabled) { + read_listen_table(); + ebpf_socket_read_hash_global_tables(stats, maps_per_core); + } + + if (cgroups && shm_ebpf_cgroup.header) + ebpf_update_socket_cgroup(); + + pthread_mutex_lock(&lock); + if (socket_global_enabled) + ebpf_socket_send_data(em); + + if (socket_apps_enabled & NETDATA_EBPF_APPS_FLAG_CHART_CREATED) + ebpf_socket_send_apps_data(); + + if (cgroups && shm_ebpf_cgroup.header) + ebpf_socket_send_cgroup_data(update_every); + + fflush(stdout); + + pthread_mutex_unlock(&lock); + + pthread_mutex_lock(&ebpf_exit_cleanup); + if (running_time && !em->running_time) + running_time = update_every; + else + running_time += update_every; + + em->running_time = running_time; + pthread_mutex_unlock(&ebpf_exit_cleanup); + } +} + +/***************************************************************** + * + * FUNCTIONS TO START THREAD + * + *****************************************************************/ + +/** + * Initialize 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. + */ +static void ebpf_socket_initialize_global_vectors() +{ + memset(socket_aggregated_data, 0 ,NETDATA_MAX_SOCKET_VECTOR * sizeof(netdata_syscall_stat_t)); + memset(socket_publish_aggregated, 0 ,NETDATA_MAX_SOCKET_VECTOR * sizeof(netdata_publish_syscall_t)); + socket_hash_values = callocz(ebpf_nprocs, sizeof(netdata_idx_t)); + + aral_socket_table = ebpf_allocate_pid_aral(NETDATA_EBPF_SOCKET_ARAL_TABLE_NAME, + sizeof(netdata_socket_plus_t)); + + socket_values = callocz((size_t)ebpf_nprocs, sizeof(netdata_socket_t)); + + ebpf_load_addresses(&tcp_v6_connect_address, -1); +} + +/***************************************************************** + * + * EBPF SOCKET THREAD + * + *****************************************************************/ + +/** + * Link dimension name + * + * Link user specified names inside a link list. + * + * @param port the port number associated to the dimension name. + * @param hash the calculated hash for the dimension name. + * @param name the dimension name. + */ +static void ebpf_link_dimension_name(char *port, uint32_t hash, char *value) +{ + int test = str2i(port); + if (test < NETDATA_MINIMUM_PORT_VALUE || test > NETDATA_MAXIMUM_PORT_VALUE){ + netdata_log_error("The dimension given (%s = %s) has an invalid value and it will be ignored.", port, value); + return; + } + + ebpf_network_viewer_dim_name_t *w; + w = callocz(1, sizeof(ebpf_network_viewer_dim_name_t)); + + w->name = strdupz(value); + w->hash = hash; + + w->port = (uint16_t) htons(test); + + ebpf_network_viewer_dim_name_t *names = network_viewer_opt.names; + if (unlikely(!names)) { + network_viewer_opt.names = w; + } else { + for (; names->next; names = names->next) { + if (names->port == w->port) { + netdata_log_info("Duplicated definition for a service, the name %s will be ignored. ", names->name); + freez(names->name); + names->name = w->name; + names->hash = w->hash; + freez(w); + return; + } + } + names->next = w; + } + +#ifdef NETDATA_INTERNAL_CHECKS + netdata_log_info("Adding values %s( %u) to dimension name list used on network viewer", w->name, htons(w->port)); +#endif +} + +/** + * Parse service Name section. + * + * This function gets the values that will be used to overwrite dimensions. + * + * @param cfg the configuration structure + */ +void ebpf_parse_service_name_section(struct config *cfg) +{ + struct section *co = appconfig_get_section(cfg, EBPF_SERVICE_NAME_SECTION); + if (co) { + struct config_option *cv; + for (cv = co->values; cv ; cv = cv->next) { + ebpf_link_dimension_name(cv->name, cv->hash, cv->value); + } + } + + // Always associated the default port to Netdata + ebpf_network_viewer_dim_name_t *names = network_viewer_opt.names; + if (names) { + uint16_t default_port = htons(19999); + while (names) { + if (names->port == default_port) + return; + + names = names->next; + } + } + + char *port_string = getenv("NETDATA_LISTEN_PORT"); + if (port_string) { + // if variable has an invalid value, we assume netdata is using 19999 + int default_port = str2i(port_string); + if (default_port > 0 && default_port < 65536) + ebpf_link_dimension_name(port_string, simple_hash(port_string), "Netdata"); + } +} + +/** + * Parse table size options + * + * @param cfg configuration options read from user file. + */ +void parse_table_size_options(struct config *cfg) +{ + socket_maps[NETDATA_SOCKET_OPEN_SOCKET].user_input = (uint32_t) appconfig_get_number(cfg, + EBPF_GLOBAL_SECTION, + EBPF_CONFIG_SOCKET_MONITORING_SIZE, + NETDATA_MAXIMUM_CONNECTIONS_ALLOWED); + + socket_maps[NETDATA_SOCKET_TABLE_UDP].user_input = (uint32_t) appconfig_get_number(cfg, + EBPF_GLOBAL_SECTION, + EBPF_CONFIG_UDP_SIZE, NETDATA_MAXIMUM_UDP_CONNECTIONS_ALLOWED); +} + +/* + * Load BPF + * + * Load BPF files. + * + * @param em the structure with configuration + */ +static int ebpf_socket_load_bpf(ebpf_module_t *em) +{ +#ifdef LIBBPF_MAJOR_VERSION + ebpf_define_map_type(em->maps, em->maps_per_core, running_on_kernel); +#endif + + int ret = 0; + + if (em->load & EBPF_LOAD_LEGACY) { + em->probe_links = ebpf_load_program(ebpf_plugin_dir, em, running_on_kernel, isrh, &em->objects); + if (!em->probe_links) { + ret = -1; + } + } +#ifdef LIBBPF_MAJOR_VERSION + else { + socket_bpf_obj = socket_bpf__open(); + if (!socket_bpf_obj) + ret = -1; + else + ret = ebpf_socket_load_and_attach(socket_bpf_obj, em); + } +#endif + + if (ret) { + netdata_log_error("%s %s", EBPF_DEFAULT_ERROR_MSG, em->info.thread_name); + } + + return ret; +} + +/** + * Socket thread + * + * Thread used to generate socket charts. + * + * @param ptr a pointer to `struct ebpf_module` + * + * @return It always return NULL + */ +void *ebpf_socket_thread(void *ptr) +{ + ebpf_module_t *em = (ebpf_module_t *)ptr; + + CLEANUP_FUNCTION_REGISTER(ebpf_socket_exit) cleanup_ptr = em; + + if (em->enabled > NETDATA_THREAD_EBPF_FUNCTION_RUNNING) { + collector_error("There is already a thread %s running", em->info.thread_name); + return NULL; + } + + em->maps = socket_maps; + + rw_spinlock_write_lock(&network_viewer_opt.rw_spinlock); + // It was not enabled from main config file (ebpf.d.conf) + if (!network_viewer_opt.enabled) + network_viewer_opt.enabled = appconfig_get_boolean(&socket_config, EBPF_NETWORK_VIEWER_SECTION, "enabled", + CONFIG_BOOLEAN_YES); + rw_spinlock_write_unlock(&network_viewer_opt.rw_spinlock); + + parse_table_size_options(&socket_config); + + ebpf_socket_initialize_global_vectors(); + + if (running_on_kernel < NETDATA_EBPF_KERNEL_5_0) + em->mode = MODE_ENTRY; + +#ifdef LIBBPF_MAJOR_VERSION + ebpf_adjust_thread_load(em, default_btf); +#endif + if (ebpf_socket_load_bpf(em)) { + pthread_mutex_unlock(&lock); + goto endsocket; + } + + int algorithms[NETDATA_MAX_SOCKET_VECTOR] = { + NETDATA_EBPF_ABSOLUTE_IDX, NETDATA_EBPF_ABSOLUTE_IDX, NETDATA_EBPF_ABSOLUTE_IDX, + NETDATA_EBPF_ABSOLUTE_IDX, NETDATA_EBPF_ABSOLUTE_IDX, NETDATA_EBPF_ABSOLUTE_IDX, + NETDATA_EBPF_ABSOLUTE_IDX, NETDATA_EBPF_ABSOLUTE_IDX, NETDATA_EBPF_INCREMENTAL_IDX, + NETDATA_EBPF_INCREMENTAL_IDX + }; + ebpf_global_labels( + socket_aggregated_data, socket_publish_aggregated, socket_dimension_names, socket_id_names, + algorithms, NETDATA_MAX_SOCKET_VECTOR); + + ebpf_read_socket.thread = nd_thread_create(ebpf_read_socket.name, NETDATA_THREAD_OPTION_DEFAULT, + ebpf_read_socket_thread, em); + + pthread_mutex_lock(&lock); + ebpf_socket_create_global_charts(em); + + ebpf_update_stats(&plugin_statistics, em); + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps, EBPF_ACTION_STAT_ADD); + + pthread_mutex_unlock(&lock); + + socket_collector(em); + +endsocket: + ebpf_update_disabled_plugin_stats(em); + return NULL; +} diff --git a/src/collectors/ebpf.plugin/ebpf_socket.h b/src/collectors/ebpf.plugin/ebpf_socket.h new file mode 100644 index 000000000..b36ed064c --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_socket.h @@ -0,0 +1,354 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#ifndef NETDATA_EBPF_SOCKET_H +#define NETDATA_EBPF_SOCKET_H 1 +#include <stdint.h> +#include "libnetdata/avl/avl.h" + +#include <sys/socket.h> +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif + +// Module name & description +#define NETDATA_EBPF_MODULE_NAME_SOCKET "socket" +#define NETDATA_EBPF_SOCKET_MODULE_DESC "Monitors TCP and UDP bandwidth. This thread is integrated with apps and cgroup." + +// Vector indexes +#define NETDATA_UDP_START 3 + +// dimensions +#define EBPF_COMMON_UNITS_CONNECTIONS "connections/s" +#define EBPF_COMMON_UNITS_KILOBITS "kilobits/s" + +// config file +#define NETDATA_NETWORK_CONFIG_FILE "network.conf" +#define EBPF_NETWORK_VIEWER_SECTION "network connections" +#define EBPF_SERVICE_NAME_SECTION "service name" +#define EBPF_CONFIG_RESOLVE_HOSTNAME "resolve hostnames" +#define EBPF_CONFIG_RESOLVE_SERVICE "resolve service names" +#define EBPF_CONFIG_PORTS "ports" +#define EBPF_CONFIG_HOSTNAMES "hostnames" +#define EBPF_CONFIG_SOCKET_MONITORING_SIZE "socket monitoring table size" +#define EBPF_CONFIG_UDP_SIZE "udp connection table size" + +enum ebpf_socket_table_list { + NETDATA_SOCKET_GLOBAL, + NETDATA_SOCKET_LPORTS, + NETDATA_SOCKET_OPEN_SOCKET, + NETDATA_SOCKET_TABLE_UDP, + NETDATA_SOCKET_TABLE_CTRL +}; + +enum ebpf_socket_publish_index { + NETDATA_IDX_TCP_SENDMSG, + NETDATA_IDX_TCP_CLEANUP_RBUF, + NETDATA_IDX_TCP_CLOSE, + NETDATA_IDX_UDP_RECVBUF, + NETDATA_IDX_UDP_SENDMSG, + NETDATA_IDX_TCP_RETRANSMIT, + NETDATA_IDX_TCP_CONNECTION_V4, + NETDATA_IDX_TCP_CONNECTION_V6, + NETDATA_IDX_INCOMING_CONNECTION_TCP, + NETDATA_IDX_INCOMING_CONNECTION_UDP, + + // Keep this as last and don't skip numbers as it is used as element counter + NETDATA_MAX_SOCKET_VECTOR +}; + +enum socket_functions { + NETDATA_FCNT_INET_CSK_ACCEPT, + NETDATA_FCNT_TCP_RETRANSMIT, + NETDATA_FCNT_CLEANUP_RBUF, + NETDATA_FCNT_TCP_CLOSE, + NETDATA_FCNT_UDP_RECEVMSG, + NETDATA_FCNT_TCP_SENDMSG, + NETDATA_FCNT_UDP_SENDMSG, + NETDATA_FCNT_TCP_V4_CONNECT, + NETDATA_FCNT_TCP_V6_CONNECT +}; + +typedef enum ebpf_socket_idx { + NETDATA_KEY_CALLS_TCP_SENDMSG, + NETDATA_KEY_ERROR_TCP_SENDMSG, + NETDATA_KEY_BYTES_TCP_SENDMSG, + + NETDATA_KEY_CALLS_TCP_CLEANUP_RBUF, + NETDATA_KEY_ERROR_TCP_CLEANUP_RBUF, + NETDATA_KEY_BYTES_TCP_CLEANUP_RBUF, + + NETDATA_KEY_CALLS_TCP_CLOSE, + + NETDATA_KEY_CALLS_UDP_RECVMSG, + NETDATA_KEY_ERROR_UDP_RECVMSG, + NETDATA_KEY_BYTES_UDP_RECVMSG, + + NETDATA_KEY_CALLS_UDP_SENDMSG, + NETDATA_KEY_ERROR_UDP_SENDMSG, + NETDATA_KEY_BYTES_UDP_SENDMSG, + + NETDATA_KEY_TCP_RETRANSMIT, + + NETDATA_KEY_CALLS_TCP_CONNECT_IPV4, + NETDATA_KEY_ERROR_TCP_CONNECT_IPV4, + + NETDATA_KEY_CALLS_TCP_CONNECT_IPV6, + NETDATA_KEY_ERROR_TCP_CONNECT_IPV6, + + // Keep this as last and don't skip numbers as it is used as element counter + NETDATA_SOCKET_COUNTER +} ebpf_socket_index_t; + +#define NETDATA_SOCKET_KERNEL_FUNCTIONS "kernel" +#define NETDATA_CGROUP_NET_GROUP "network" + +// Global chart name +#define NETDATA_TCP_OUTBOUND_CONNECTIONS "tcp_outbound_conn" +#define NETDATA_INBOUND_CONNECTIONS "inbound_conn" +#define NETDATA_TCP_FUNCTION_COUNT "tcp_functions" +#define NETDATA_TCP_FUNCTION_BITS "total_tcp_bandwidth" +#define NETDATA_TCP_FUNCTION_ERROR "tcp_error" +#define NETDATA_TCP_RETRANSMIT "tcp_retransmit" +#define NETDATA_UDP_FUNCTION_COUNT "udp_functions" +#define NETDATA_UDP_FUNCTION_BITS "total_udp_bandwidth" +#define NETDATA_UDP_FUNCTION_ERROR "udp_error" + +// Charts created on Apps submenu +#define NETDATA_NET_APPS_CONNECTION_TCP_V4 "outbound_conn_v4" +#define NETDATA_NET_APPS_CONNECTION_TCP_V6 "outbound_conn_v6" +#define NETDATA_NET_APPS_BANDWIDTH_SENT "total_bandwidth_sent" +#define NETDATA_NET_APPS_BANDWIDTH_RECV "total_bandwidth_recv" +#define NETDATA_NET_APPS_BANDWIDTH_TCP_SEND_CALLS "bandwidth_tcp_send" +#define NETDATA_NET_APPS_BANDWIDTH_TCP_RECV_CALLS "bandwidth_tcp_recv" +#define NETDATA_NET_APPS_BANDWIDTH_TCP_RETRANSMIT "bandwidth_tcp_retransmit" +#define NETDATA_NET_APPS_BANDWIDTH_UDP_SEND_CALLS "bandwidth_udp_send" +#define NETDATA_NET_APPS_BANDWIDTH_UDP_RECV_CALLS "bandwidth_udp_recv" + +// Port range +#define NETDATA_MINIMUM_PORT_VALUE 1 +#define NETDATA_MAXIMUM_PORT_VALUE 65535 +#define NETDATA_COMPILED_CONNECTIONS_ALLOWED 65535U +#define NETDATA_MAXIMUM_CONNECTIONS_ALLOWED 16384U +#define NETDATA_COMPILED_UDP_CONNECTIONS_ALLOWED 8192U +#define NETDATA_MAXIMUM_UDP_CONNECTIONS_ALLOWED 4096U + +#define NETDATA_MINIMUM_IPV4_CIDR 0 +#define NETDATA_MAXIMUM_IPV4_CIDR 32 + +// Contexts +#define NETDATA_CGROUP_TCP_V4_CONN_CONTEXT "cgroup.net_conn_ipv4" +#define NETDATA_CGROUP_TCP_V6_CONN_CONTEXT "cgroup.net_conn_ipv6" +#define NETDATA_CGROUP_SOCKET_BYTES_RECV_CONTEXT "cgroup.net_bytes_recv" +#define NETDATA_CGROUP_SOCKET_BYTES_SEND_CONTEXT "cgroup.net_bytes_send" +#define NETDATA_CGROUP_SOCKET_TCP_RECV_CONTEXT "cgroup.net_tcp_recv" +#define NETDATA_CGROUP_SOCKET_TCP_SEND_CONTEXT "cgroup.net_tcp_send" +#define NETDATA_CGROUP_SOCKET_TCP_RETRANSMIT_CONTEXT "cgroup.net_retransmit" +#define NETDATA_CGROUP_SOCKET_UDP_RECV_CONTEXT "cgroup.net_udp_recv" +#define NETDATA_CGROUP_SOCKET_UDP_SEND_CONTEXT "cgroup.net_udp_send" + +#define NETDATA_SERVICES_SOCKET_TCP_V4_CONN_CONTEXT "systemd.services.net_conn_ipv4" +#define NETDATA_SERVICES_SOCKET_TCP_V6_CONN_CONTEXT "systemd.services.net_conn_ipv6" +#define NETDATA_SERVICES_SOCKET_BYTES_RECV_CONTEXT "systemd.services.net_bytes_recv" +#define NETDATA_SERVICES_SOCKET_BYTES_SEND_CONTEXT "systemd.services.net_bytes_send" +#define NETDATA_SERVICES_SOCKET_TCP_RECV_CONTEXT "systemd.services.net_tcp_recv" +#define NETDATA_SERVICES_SOCKET_TCP_SEND_CONTEXT "systemd.services.net_tcp_send" +#define NETDATA_SERVICES_SOCKET_TCP_RETRANSMIT_CONTEXT "systemd.services.net_retransmit" +#define NETDATA_SERVICES_SOCKET_UDP_RECV_CONTEXT "systemd.services.net_udp_recv" +#define NETDATA_SERVICES_SOCKET_UDP_SEND_CONTEXT "systemd.services.net_udp_send" + +// ARAL name +#define NETDATA_EBPF_SOCKET_ARAL_NAME "ebpf_socket" +#define NETDATA_EBPF_PID_SOCKET_ARAL_TABLE_NAME "ebpf_pid_socket" +#define NETDATA_EBPF_SOCKET_ARAL_TABLE_NAME "ebpf_socket_tbl" + +typedef struct ebpf_socket_publish_apps { + // Data read + uint64_t bytes_sent; // Bytes sent + uint64_t bytes_received; // Bytes received + uint64_t call_tcp_sent; // Number of times tcp_sendmsg was called + uint64_t call_tcp_received; // Number of times tcp_cleanup_rbuf was called + uint64_t retransmit; // Number of times tcp_retransmit was called + uint64_t call_udp_sent; // Number of times udp_sendmsg was called + uint64_t call_udp_received; // Number of times udp_recvmsg was called + uint64_t call_close; // Number of times tcp_close was called + uint64_t call_tcp_v4_connection;// Number of times tcp_v4_connect was called + uint64_t call_tcp_v6_connection;// Number of times tcp_v6_connect was called +} ebpf_socket_publish_apps_t; + +typedef struct ebpf_network_viewer_dimension_names { + char *name; + uint32_t hash; + + uint16_t port; + + struct ebpf_network_viewer_dimension_names *next; +} ebpf_network_viewer_dim_name_t ; + +typedef struct ebpf_network_viewer_port_list { + char *value; + uint32_t hash; + + uint16_t first; + uint16_t last; + + uint16_t cmp_first; + uint16_t cmp_last; + + uint16_t protocol; + uint32_t pid; + uint32_t tgid; + uint64_t connections; + struct ebpf_network_viewer_port_list *next; +} ebpf_network_viewer_port_list_t; + +typedef struct netdata_passive_connection { + uint32_t tgid; + uint32_t pid; + uint64_t counter; +} netdata_passive_connection_t; + +typedef struct netdata_passive_connection_idx { + uint16_t protocol; + uint16_t port; +} netdata_passive_connection_idx_t; + +/** + * Union used to store ip addresses + */ +union netdata_ip_t { + uint8_t addr8[16]; + uint16_t addr16[8]; + uint32_t addr32[4]; + uint64_t addr64[2]; +}; + +typedef struct ebpf_network_viewer_ip_list { + char *value; // IP value + uint32_t hash; // IP hash + + uint8_t ver; // IP version + + union netdata_ip_t first; // The IP address informed + union netdata_ip_t last; // The IP address informed + + struct ebpf_network_viewer_ip_list *next; +} ebpf_network_viewer_ip_list_t; + +typedef struct ebpf_network_viewer_hostname_list { + char *value; // IP value + uint32_t hash; // IP hash + + SIMPLE_PATTERN *value_pattern; + + struct ebpf_network_viewer_hostname_list *next; +} ebpf_network_viewer_hostname_list_t; + +typedef struct ebpf_network_viewer_options { + RW_SPINLOCK rw_spinlock; + + uint32_t enabled; + uint32_t family; // AF_INET, AF_INET6 or AF_UNSPEC (both) + + uint32_t hostname_resolution_enabled; + uint32_t service_resolution_enabled; + + ebpf_network_viewer_port_list_t *excluded_port; + ebpf_network_viewer_port_list_t *included_port; + + ebpf_network_viewer_dim_name_t *names; + + ebpf_network_viewer_ip_list_t *excluded_ips; + ebpf_network_viewer_ip_list_t *included_ips; + + ebpf_network_viewer_hostname_list_t *excluded_hostnames; + ebpf_network_viewer_hostname_list_t *included_hostnames; + + ebpf_network_viewer_ip_list_t *ipv4_local_ip; + ebpf_network_viewer_ip_list_t *ipv6_local_ip; +} ebpf_network_viewer_options_t; + +extern ebpf_network_viewer_options_t network_viewer_opt; + +/** + * Structure to store socket information + */ +typedef struct netdata_socket { + char name[TASK_COMM_LEN]; + + // Timestamp + uint64_t first_timestamp; + uint64_t current_timestamp; + // Socket additional info + uint16_t protocol; + uint16_t family; + uint32_t external_origin; + struct { + uint32_t call_tcp_sent; + uint32_t call_tcp_received; + uint64_t tcp_bytes_sent; + uint64_t tcp_bytes_received; + uint32_t close; //It is never used with UDP + uint32_t retransmit; //It is never used with UDP + uint32_t ipv4_connect; + uint32_t ipv6_connect; + uint32_t state; // We do not have charts for it, because we are using network viewer plugin + } tcp; + + struct { + uint32_t call_udp_sent; + uint32_t call_udp_received; + uint64_t udp_bytes_sent; + uint64_t udp_bytes_received; + } udp; +} netdata_socket_t; + +typedef enum netdata_socket_flags { + NETDATA_SOCKET_FLAGS_ALREADY_OPEN = (1<<0) +} netdata_socket_flags_t; + +typedef enum netdata_socket_src_ip_origin { + NETDATA_EBPF_SRC_IP_ORIGIN_LOCAL, + NETDATA_EBPF_SRC_IP_ORIGIN_EXTERNAL +} netdata_socket_src_ip_origin_t; + +typedef struct netata_socket_plus { + netdata_socket_t data; // Data read from database + uint32_t pid; + time_t last_update; + netdata_socket_flags_t flags; + + struct { + char src_ip[INET6_ADDRSTRLEN + 1]; + // uint16_t src_port; + char dst_ip[INET6_ADDRSTRLEN+ 1]; + char dst_port[NI_MAXSERV + 1]; + } socket_string; +} netdata_socket_plus_t; + +extern ARAL *aral_socket_table; + +/** + * Index used together previous structure + */ +typedef struct netdata_socket_idx { + union netdata_ip_t saddr; + //uint16_t sport; + union netdata_ip_t daddr; + uint16_t dport; + uint32_t pid; +} netdata_socket_idx_t; + +void ebpf_clean_port_structure(ebpf_network_viewer_port_list_t **clean); +extern ebpf_network_viewer_port_list_t *listen_ports; +void update_listen_table(uint16_t value, uint16_t proto, netdata_passive_connection_t *values); +void ebpf_fill_ip_list_unsafe(ebpf_network_viewer_ip_list_t **out, ebpf_network_viewer_ip_list_t *in, char *table); +void ebpf_parse_service_name_section(struct config *cfg); +void ebpf_parse_ips_unsafe(char *ptr); +void ebpf_parse_ports(char *ptr); +void ebpf_socket_read_open_connections(BUFFER *buf, struct ebpf_module *em); +void ebpf_socket_fill_publish_apps(uint32_t current_pid, netdata_socket_t *ns); + + +extern struct config socket_config; +extern netdata_ebpf_targets_t socket_targets[]; + +#endif diff --git a/src/collectors/ebpf.plugin/ebpf_softirq.c b/src/collectors/ebpf.plugin/ebpf_softirq.c new file mode 100644 index 000000000..21bd83a3e --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_softirq.c @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ebpf.h" +#include "ebpf_softirq.h" + +struct config softirq_config = { .first_section = NULL, + .last_section = NULL, + .mutex = NETDATA_MUTEX_INITIALIZER, + .index = { .avl_tree = { .root = NULL, .compar = appconfig_section_compare }, + .rwlock = AVL_LOCK_INITIALIZER } }; + +#define SOFTIRQ_MAP_LATENCY 0 +static ebpf_local_maps_t softirq_maps[] = { + { + .name = "tbl_softirq", + .internal_input = NETDATA_SOFTIRQ_MAX_IRQS, + .user_input = 0, + .type = NETDATA_EBPF_MAP_STATIC, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }, + /* end */ + { + .name = NULL, + .internal_input = 0, + .user_input = 0, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + } +}; + +#define SOFTIRQ_TP_CLASS_IRQ "irq" +static ebpf_tracepoint_t softirq_tracepoints[] = { + {.enabled = false, .class = SOFTIRQ_TP_CLASS_IRQ, .event = "softirq_entry"}, + {.enabled = false, .class = SOFTIRQ_TP_CLASS_IRQ, .event = "softirq_exit"}, + /* end */ + {.enabled = false, .class = NULL, .event = NULL} +}; + +// these must be in the order defined by the kernel: +// https://elixir.bootlin.com/linux/v5.12.19/source/include/trace/events/irq.h#L13 +static softirq_val_t softirq_vals[] = { + {.name = "HI", .latency = 0}, + {.name = "TIMER", .latency = 0}, + {.name = "NET_TX", .latency = 0}, + {.name = "NET_RX", .latency = 0}, + {.name = "BLOCK", .latency = 0}, + {.name = "IRQ_POLL", .latency = 0}, + {.name = "TASKLET", .latency = 0}, + {.name = "SCHED", .latency = 0}, + {.name = "HRTIMER", .latency = 0}, + {.name = "RCU", .latency = 0}, +}; + +// tmp store for soft IRQ values we get from a per-CPU eBPF map. +static softirq_ebpf_val_t *softirq_ebpf_vals = NULL; + +/** + * Obsolete global + * + * Obsolete global charts created by thread. + * + * @param em a pointer to `struct ebpf_module` + */ +static void ebpf_obsolete_softirq_global(ebpf_module_t *em) +{ + ebpf_write_chart_obsolete(NETDATA_EBPF_SYSTEM_GROUP, + "softirq_latency", + "", + "Software IRQ latency", + EBPF_COMMON_UNITS_MILLISECONDS, + "softirqs", + NETDATA_EBPF_CHART_TYPE_STACKED, + "system.softirq_latency", + NETDATA_CHART_PRIO_SYSTEM_SOFTIRQS+1, + em->update_every); +} + +/** + * Cleanup + * + * Clean up allocated memory. + * + * @param ptr thread data. + */ +static void softirq_cleanup(void *pptr) +{ + ebpf_module_t *em = CLEANUP_FUNCTION_GET_PTR(pptr); + if(!em) return; + + if (em->enabled == NETDATA_THREAD_EBPF_FUNCTION_RUNNING) { + pthread_mutex_lock(&lock); + + ebpf_obsolete_softirq_global(em); + + pthread_mutex_unlock(&lock); + fflush(stdout); + } + + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps, EBPF_ACTION_STAT_REMOVE); + + if (em->objects) { + ebpf_unload_legacy_code(em->objects, em->probe_links); + em->objects = NULL; + em->probe_links = NULL; + } + + for (int i = 0; softirq_tracepoints[i].class != NULL; i++) { + ebpf_disable_tracepoint(&softirq_tracepoints[i]); + } + freez(softirq_ebpf_vals); + softirq_ebpf_vals = NULL; + + pthread_mutex_lock(&ebpf_exit_cleanup); + em->enabled = NETDATA_THREAD_EBPF_STOPPED; + ebpf_update_stats(&plugin_statistics, em); + pthread_mutex_unlock(&ebpf_exit_cleanup); +} + +/***************************************************************** + * MAIN LOOP + *****************************************************************/ + +/** + * Read Latency Map + * + * Read data from kernel ring to plot for users. + * + * @param maps_per_core do I need to read all cores? + */ +static void softirq_read_latency_map(int maps_per_core) +{ + int fd = softirq_maps[SOFTIRQ_MAP_LATENCY].map_fd; + int i; + size_t length = sizeof(softirq_ebpf_val_t); + if (maps_per_core) + length *= ebpf_nprocs; + + for (i = 0; i < NETDATA_SOFTIRQ_MAX_IRQS; i++) { + int test = bpf_map_lookup_elem(fd, &i, softirq_ebpf_vals); + if (unlikely(test < 0)) { + continue; + } + + uint64_t total_latency = 0; + int cpu_i; + int end = (maps_per_core) ? ebpf_nprocs : 1; + for (cpu_i = 0; cpu_i < end; cpu_i++) { + total_latency += softirq_ebpf_vals[cpu_i].latency/1000; + } + + softirq_vals[i].latency = total_latency; + memset(softirq_ebpf_vals, 0, length); + } +} + +static void softirq_create_charts(int update_every) +{ + ebpf_create_chart( + NETDATA_EBPF_SYSTEM_GROUP, + "softirq_latency", + "Software IRQ latency", + EBPF_COMMON_UNITS_MILLISECONDS, + "softirqs", + "system.softirq_latency", + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_CHART_PRIO_SYSTEM_SOFTIRQS+1, + NULL, NULL, 0, update_every, + NETDATA_EBPF_MODULE_NAME_SOFTIRQ + ); + + fflush(stdout); +} + +static void softirq_create_dims() +{ + uint32_t i; + for (i = 0; i < NETDATA_SOFTIRQ_MAX_IRQS; i++) { + ebpf_write_global_dimension( + softirq_vals[i].name, softirq_vals[i].name, + ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX] + ); + } +} + +static inline void softirq_write_dims() +{ + uint32_t i; + for (i = 0; i < NETDATA_SOFTIRQ_MAX_IRQS; i++) { + write_chart_dimension(softirq_vals[i].name, softirq_vals[i].latency); + } +} + +/** +* Main loop for this collector. +*/ +static void softirq_collector(ebpf_module_t *em) +{ + softirq_ebpf_vals = callocz(ebpf_nprocs, sizeof(softirq_ebpf_val_t)); + + // create chart and static dims. + pthread_mutex_lock(&lock); + softirq_create_charts(em->update_every); + softirq_create_dims(); + ebpf_update_stats(&plugin_statistics, em); + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps, EBPF_ACTION_STAT_ADD); + pthread_mutex_unlock(&lock); + + // loop and read from published data until ebpf plugin is closed. + heartbeat_t hb; + heartbeat_init(&hb); + int update_every = em->update_every; + int counter = update_every - 1; + int maps_per_core = em->maps_per_core; + //This will be cancelled by its parent + uint32_t running_time = 0; + uint32_t lifetime = em->lifetime; + while (!ebpf_plugin_stop() && running_time < lifetime) { + (void)heartbeat_next(&hb, USEC_PER_SEC); + if (ebpf_plugin_stop() || ++counter != update_every) + continue; + + counter = 0; + softirq_read_latency_map(maps_per_core); + pthread_mutex_lock(&lock); + + // write dims now for all hitherto discovered IRQs. + ebpf_write_begin_chart(NETDATA_EBPF_SYSTEM_GROUP, "softirq_latency", ""); + softirq_write_dims(); + ebpf_write_end_chart(); + + pthread_mutex_unlock(&lock); + + pthread_mutex_lock(&ebpf_exit_cleanup); + if (running_time && !em->running_time) + running_time = update_every; + else + running_time += update_every; + + em->running_time = running_time; + pthread_mutex_unlock(&ebpf_exit_cleanup); + } +} + +/***************************************************************** + * EBPF SOFTIRQ THREAD + *****************************************************************/ + +/** + * Soft IRQ latency thread. + * + * @param ptr a `ebpf_module_t *`. + * @return always NULL. + */ +void *ebpf_softirq_thread(void *ptr) +{ + ebpf_module_t *em = ptr; + + CLEANUP_FUNCTION_REGISTER(softirq_cleanup) cleanup_ptr = em; + + em->maps = softirq_maps; + + if (ebpf_enable_tracepoints(softirq_tracepoints) == 0) { + goto endsoftirq; + } + +#ifdef LIBBPF_MAJOR_VERSION + ebpf_define_map_type(em->maps, em->maps_per_core, running_on_kernel); +#endif + em->probe_links = ebpf_load_program(ebpf_plugin_dir, em, running_on_kernel, isrh, &em->objects); + if (!em->probe_links) { + goto endsoftirq; + } + + softirq_collector(em); + +endsoftirq: + ebpf_update_disabled_plugin_stats(em); + + return NULL; +} diff --git a/src/collectors/ebpf.plugin/ebpf_softirq.h b/src/collectors/ebpf.plugin/ebpf_softirq.h new file mode 100644 index 000000000..4ef36775a --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_softirq.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_EBPF_SOFTIRQ_H +#define NETDATA_EBPF_SOFTIRQ_H 1 + +// Module observation +#define NETDATA_EBPF_SOFTIRQ_MODULE_DESC "Show time spent servicing individual software interrupt requests (soft IRQs)." + +/***************************************************************** + * copied from kernel-collectors repo, with modifications needed + * for inclusion here. + *****************************************************************/ + +#define NETDATA_SOFTIRQ_MAX_IRQS 10 + +typedef struct softirq_ebpf_val { + uint64_t latency; + uint64_t ts; +} softirq_ebpf_val_t; + +/***************************************************************** + * below this is eBPF plugin-specific code. + *****************************************************************/ + +#define NETDATA_EBPF_MODULE_NAME_SOFTIRQ "softirq" +#define NETDATA_SOFTIRQ_CONFIG_FILE "softirq.conf" + +typedef struct sofirq_val { + uint64_t latency; + char *name; +} softirq_val_t; + +extern struct config softirq_config; +void *ebpf_softirq_thread(void *ptr); + +#endif /* NETDATA_EBPF_SOFTIRQ_H */ diff --git a/src/collectors/ebpf.plugin/ebpf_swap.c b/src/collectors/ebpf.plugin/ebpf_swap.c new file mode 100644 index 000000000..1e2a7cc60 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_swap.c @@ -0,0 +1,1171 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ebpf.h" +#include "ebpf_swap.h" + +static char *swap_dimension_name[NETDATA_SWAP_END] = { "read", "write" }; +static netdata_syscall_stat_t swap_aggregated_data[NETDATA_SWAP_END]; +static netdata_publish_syscall_t swap_publish_aggregated[NETDATA_SWAP_END]; + +static netdata_idx_t swap_hash_values[NETDATA_SWAP_END]; +static netdata_idx_t *swap_values = NULL; + +netdata_publish_swap_t *swap_vector = NULL; + +struct config swap_config = { .first_section = NULL, + .last_section = NULL, + .mutex = NETDATA_MUTEX_INITIALIZER, + .index = { .avl_tree = { .root = NULL, .compar = appconfig_section_compare }, + .rwlock = AVL_LOCK_INITIALIZER } }; + +static ebpf_local_maps_t swap_maps[] = {{.name = "tbl_pid_swap", .internal_input = ND_EBPF_DEFAULT_PID_SIZE, + .user_input = 0, + .type = NETDATA_EBPF_MAP_RESIZABLE | NETDATA_EBPF_MAP_PID, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_HASH +#endif + }, + {.name = "swap_ctrl", .internal_input = NETDATA_CONTROLLER_END, + .user_input = 0, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }, + {.name = "tbl_swap", .internal_input = NETDATA_SWAP_END, + .user_input = 0, + .type = NETDATA_EBPF_MAP_STATIC, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }, + {.name = NULL, .internal_input = 0, .user_input = 0, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }}; + +netdata_ebpf_targets_t swap_targets[] = { {.name = NULL, .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = "swap_writepage", .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = NULL, .mode = EBPF_LOAD_TRAMPOLINE}}; + +static char *swap_read[] ={ "swap_readpage", "swap_read_folio", NULL }; + + +struct netdata_static_thread ebpf_read_swap = { + .name = "EBPF_READ_SWAP", + .config_section = NULL, + .config_name = NULL, + .env_name = NULL, + .enabled = 1, + .thread = NULL, + .init_routine = NULL, + .start_routine = NULL +}; + +#ifdef LIBBPF_MAJOR_VERSION +/** + * Disable probe + * + * Disable all probes to use exclusively another method. + * + * @param obj is the main structure for bpf objects + */ +static void ebpf_swap_disable_probe(struct swap_bpf *obj) +{ + bpf_program__set_autoload(obj->progs.netdata_swap_readpage_probe, false); + bpf_program__set_autoload(obj->progs.netdata_swap_read_folio_probe, false); + bpf_program__set_autoload(obj->progs.netdata_swap_writepage_probe, false); +} + +/** + * Disable specific probe + * + * Disable specific probes according to available functions + * + * @param obj is the main structure for bpf objects + */ +static inline void ebpf_swap_disable_specific_probe(struct swap_bpf *obj) +{ + if (!strcmp(swap_targets[NETDATA_KEY_SWAP_READPAGE_CALL].name, + swap_read[NETDATA_KEY_SWAP_READPAGE_CALL])) { + bpf_program__set_autoload(obj->progs.netdata_swap_read_folio_probe, false); + } else { + bpf_program__set_autoload(obj->progs.netdata_swap_readpage_probe, false); + } +} + +/* + * Disable trampoline + * + * Disable all trampoline to use exclusively another method. + * + * @param obj is the main structure for bpf objects. + */ +static void ebpf_swap_disable_trampoline(struct swap_bpf *obj) +{ + bpf_program__set_autoload(obj->progs.netdata_swap_readpage_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_swap_read_folio_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_swap_writepage_fentry, false); +} + +/** + * Disable specific trampoline + * + * Disable specific trampolines according to available functions + * + * @param obj is the main structure for bpf objects + */ +static inline void ebpf_swap_disable_specific_trampoline(struct swap_bpf *obj) +{ + if (!strcmp(swap_targets[NETDATA_KEY_SWAP_READPAGE_CALL].name, + swap_read[NETDATA_KEY_SWAP_READPAGE_CALL])) { + bpf_program__set_autoload(obj->progs.netdata_swap_read_folio_fentry, false); + } else { + bpf_program__set_autoload(obj->progs.netdata_swap_readpage_fentry, false); + } +} + +/** + * Set trampoline target + * + * Set the targets we will monitor. + * + * @param obj is the main structure for bpf objects. + */ +static void ebpf_swap_set_trampoline_target(struct swap_bpf *obj) +{ + bpf_program__set_attach_target(obj->progs.netdata_swap_readpage_fentry, 0, + swap_targets[NETDATA_KEY_SWAP_READPAGE_CALL].name); + + bpf_program__set_attach_target(obj->progs.netdata_swap_writepage_fentry, 0, + swap_targets[NETDATA_KEY_SWAP_WRITEPAGE_CALL].name); +} + +/** + * Mount Attach Probe + * + * Attach probes to target + * + * @param obj is the main structure for bpf objects. + * + * @return It returns 0 on success and -1 otherwise. + */ +static int ebpf_swap_attach_kprobe(struct swap_bpf *obj) +{ + int ret; + if (!strcmp(swap_targets[NETDATA_KEY_SWAP_READPAGE_CALL].name, + swap_read[NETDATA_KEY_SWAP_READPAGE_CALL])) { + obj->links.netdata_swap_readpage_probe = bpf_program__attach_kprobe(obj->progs.netdata_swap_readpage_probe, + false, + swap_targets[NETDATA_KEY_SWAP_READPAGE_CALL].name); + ret = libbpf_get_error(obj->links.netdata_swap_readpage_probe); + } else { + obj->links.netdata_swap_read_folio_probe = bpf_program__attach_kprobe(obj->progs.netdata_swap_read_folio_probe, + false, + swap_targets[NETDATA_KEY_SWAP_READPAGE_CALL].name); + ret = libbpf_get_error(obj->links.netdata_swap_read_folio_probe); + } + if (ret) + return -1; + + obj->links.netdata_swap_writepage_probe = bpf_program__attach_kprobe(obj->progs.netdata_swap_writepage_probe, + false, + swap_targets[NETDATA_KEY_SWAP_WRITEPAGE_CALL].name); + ret = libbpf_get_error(obj->links.netdata_swap_writepage_probe); + if (ret) + return -1; + + return 0; +} + +/** + * Set hash tables + * + * Set the values for maps according the value given by kernel. + * + * @param obj is the main structure for bpf objects. + */ +static void ebpf_swap_set_hash_tables(struct swap_bpf *obj) +{ + swap_maps[NETDATA_PID_SWAP_TABLE].map_fd = bpf_map__fd(obj->maps.tbl_pid_swap); + swap_maps[NETDATA_SWAP_CONTROLLER].map_fd = bpf_map__fd(obj->maps.swap_ctrl); + swap_maps[NETDATA_SWAP_GLOBAL_TABLE].map_fd = bpf_map__fd(obj->maps.tbl_swap); +} + +/** + * Adjust Map + * + * Resize maps according input from users. + * + * @param obj is the main structure for bpf objects. + * @param em structure with configuration + */ +static void ebpf_swap_adjust_map(struct swap_bpf *obj, ebpf_module_t *em) +{ + ebpf_update_map_size(obj->maps.tbl_pid_swap, &swap_maps[NETDATA_PID_SWAP_TABLE], + em, bpf_map__name(obj->maps.tbl_pid_swap)); + + ebpf_update_map_type(obj->maps.tbl_pid_swap, &swap_maps[NETDATA_PID_SWAP_TABLE]); + ebpf_update_map_type(obj->maps.tbl_swap, &swap_maps[NETDATA_SWAP_GLOBAL_TABLE]); + ebpf_update_map_type(obj->maps.swap_ctrl, &swap_maps[NETDATA_SWAP_CONTROLLER]); +} + +/** + * Load and attach + * + * Load and attach the eBPF code in kernel. + * + * @param obj is the main structure for bpf objects. + * @param em structure with configuration + * + * @return it returns 0 on success and -1 otherwise + */ +static inline int ebpf_swap_load_and_attach(struct swap_bpf *obj, ebpf_module_t *em) +{ + netdata_ebpf_targets_t *mt = em->targets; + netdata_ebpf_program_loaded_t test = mt[NETDATA_KEY_SWAP_READPAGE_CALL].mode; + + if (test == EBPF_LOAD_TRAMPOLINE) { + ebpf_swap_disable_probe(obj); + ebpf_swap_disable_specific_trampoline(obj); + + ebpf_swap_set_trampoline_target(obj); + } else { + ebpf_swap_disable_trampoline(obj); + ebpf_swap_disable_specific_probe(obj); + } + + ebpf_swap_adjust_map(obj, em); + + int ret = swap_bpf__load(obj); + if (ret) { + return ret; + } + + ret = (test == EBPF_LOAD_TRAMPOLINE) ? swap_bpf__attach(obj) : ebpf_swap_attach_kprobe(obj); + if (!ret) { + ebpf_swap_set_hash_tables(obj); + + ebpf_update_controller(swap_maps[NETDATA_SWAP_CONTROLLER].map_fd, em); + } + + return ret; +} +#endif + +/***************************************************************** + * + * FUNCTIONS TO CLOSE THE THREAD + * + *****************************************************************/ + +static void ebpf_obsolete_specific_swap_charts(char *type, int update_every); + +/** + * Obsolete services + * + * Obsolete all service charts created + * + * @param em a pointer to `struct ebpf_module` + */ +static void ebpf_obsolete_swap_services(ebpf_module_t *em, char *id) +{ + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_MEM_SWAP_READ_CHART, + "Calls to function swap_readpage.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_SYSTEM_SWAP_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_SYSTEMD_SWAP_READ_CONTEXT, + 20191, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_MEM_SWAP_WRITE_CHART, + "Calls to function swap_writepage.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_SYSTEM_SWAP_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CGROUP_SWAP_WRITE_CONTEXT, + 20192, + em->update_every); +} + +/** + * Obsolete cgroup chart + * + * Send obsolete for all charts created before to close. + * + * @param em a pointer to `struct ebpf_module` + */ +static inline void ebpf_obsolete_swap_cgroup_charts(ebpf_module_t *em) { + pthread_mutex_lock(&mutex_cgroup_shm); + + ebpf_cgroup_target_t *ect; + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + if (ect->systemd) { + ebpf_obsolete_swap_services(em, ect->name); + + continue; + } + + ebpf_obsolete_specific_swap_charts(ect->name, em->update_every); + } + pthread_mutex_unlock(&mutex_cgroup_shm); +} + +/** + * Obsolette apps charts + * + * Obsolete apps charts. + * + * @param em a pointer to the structure with the default values. + */ +void ebpf_obsolete_swap_apps_charts(struct ebpf_module *em) +{ + struct ebpf_target *w; + int update_every = em->update_every; + pthread_mutex_lock(&collect_data_mutex); + for (w = apps_groups_root_target; w; w = w->next) { + if (unlikely(!(w->charts_created & (1<<EBPF_MODULE_SWAP_IDX)))) + continue; + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_swap_readpage", + "Calls to function swap_readpage.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_EBPF_MEMORY_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_swap_readpage", + 20070, + update_every); + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_swap_writepage", + "Calls to function swap_writepage.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_EBPF_MEMORY_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_swap_writepage", + 20071, + update_every); + w->charts_created &= ~(1<<EBPF_MODULE_SWAP_IDX); + } + pthread_mutex_unlock(&collect_data_mutex); +} + +/** + * Obsolete global + * + * Obsolete global charts created by thread. + * + * @param em a pointer to `struct ebpf_module` + */ +static void ebpf_obsolete_swap_global(ebpf_module_t *em) +{ + ebpf_write_chart_obsolete(NETDATA_EBPF_MEMORY_GROUP, + NETDATA_MEM_SWAP_CHART, + "", + "Calls to access swap memory", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_SYSTEM_SWAP_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, + "mem.swapcalls", + NETDATA_CHART_PRIO_MEM_SWAP_CALLS, + em->update_every); +} + +/** + * Swap exit + * + * Cancel thread and exit. + * + * @param ptr thread data. + */ +static void ebpf_swap_exit(void *ptr) +{ + ebpf_module_t *em = (ebpf_module_t *)ptr; + + if (ebpf_read_swap.thread) + nd_thread_signal_cancel(ebpf_read_swap.thread); + + if (em->enabled == NETDATA_THREAD_EBPF_FUNCTION_RUNNING) { + pthread_mutex_lock(&lock); + if (em->cgroup_charts) { + ebpf_obsolete_swap_cgroup_charts(em); + fflush(stdout); + } + + if (em->apps_charts & NETDATA_EBPF_APPS_FLAG_CHART_CREATED) { + ebpf_obsolete_swap_apps_charts(em); + } + + ebpf_obsolete_swap_global(em); + + fflush(stdout); + pthread_mutex_unlock(&lock); + } + + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps, EBPF_ACTION_STAT_REMOVE); + +#ifdef LIBBPF_MAJOR_VERSION + if (bpf_obj) { + swap_bpf__destroy(bpf_obj); + bpf_obj = NULL; + } +#endif + if (em->objects) { + ebpf_unload_legacy_code(em->objects, em->probe_links); + em->objects = NULL; + em->probe_links = NULL; + } + + pthread_mutex_lock(&ebpf_exit_cleanup); + em->enabled = NETDATA_THREAD_EBPF_STOPPED; + ebpf_update_stats(&plugin_statistics, em); + pthread_mutex_unlock(&ebpf_exit_cleanup); +} + +/***************************************************************** + * + * COLLECTOR THREAD + * + *****************************************************************/ + +/** + * Apps Accumulator + * + * Sum all values read from kernel and store in the first address. + * + * @param out the vector with read values. + * @param maps_per_core do I need to read all cores? + */ +static void swap_apps_accumulator(netdata_publish_swap_t *out, int maps_per_core) +{ + int i, end = (maps_per_core) ? ebpf_nprocs : 1; + netdata_publish_swap_t *total = &out[0]; + for (i = 1; i < end; i++) { + netdata_publish_swap_t *w = &out[i]; + total->write += w->write; + total->read += w->read; + } +} + +/** + * Update cgroup + * + * Update cgroup data based in + */ +static void ebpf_update_swap_cgroup() +{ + ebpf_cgroup_target_t *ect ; + pthread_mutex_lock(&mutex_cgroup_shm); + for (ect = ebpf_cgroup_pids; ect; ect = ect->next) { + struct pid_on_target2 *pids; + for (pids = ect->pids; pids; pids = pids->next) { + int pid = pids->pid; + netdata_publish_swap_t *out = &pids->swap; + ebpf_pid_stat_t *local_pid = ebpf_get_pid_entry(pid, 0); + if (local_pid) { + netdata_publish_swap_t *in = &local_pid->swap; + + memcpy(out, in, sizeof(netdata_publish_swap_t)); + } + } + } + pthread_mutex_unlock(&mutex_cgroup_shm); +} + +/** + * Sum PIDs + * + * Sum values for all targets. + * + * @param swap + * @param root + */ +static void ebpf_swap_sum_pids(netdata_publish_swap_t *swap, struct ebpf_pid_on_target *root) +{ + uint64_t local_read = 0; + uint64_t local_write = 0; + + while (root) { + int32_t pid = root->pid; + ebpf_pid_stat_t *local_pid = ebpf_get_pid_entry(pid, 0); + if (local_pid) { + netdata_publish_swap_t *w = &local_pid->swap; + local_write += w->write; + local_read += w->read; + } + root = root->next; + } + + // These conditions were added, because we are using incremental algorithm + swap->write = (local_write >= swap->write) ? local_write : swap->write; + swap->read = (local_read >= swap->read) ? local_read : swap->read; + } + + +/** + * Resume apps data + */ +void ebpf_swap_resume_apps_data() { + struct ebpf_target *w; + for (w = apps_groups_root_target; w; w = w->next) { + if (unlikely(!(w->charts_created & (1 << EBPF_MODULE_SWAP_IDX)))) + continue; + + ebpf_swap_sum_pids(&w->swap, w->root_pid); + } +} + +/** + * Read APPS table + * + * Read the apps table and store data inside the structure. + * + * @param maps_per_core do I need to read all cores? + */ +static void ebpf_read_swap_apps_table(int maps_per_core, int max_period) +{ + netdata_publish_swap_t *cv = swap_vector; + int fd = swap_maps[NETDATA_PID_SWAP_TABLE].map_fd; + size_t length = sizeof(netdata_publish_swap_t); + if (maps_per_core) + length *= ebpf_nprocs; + + uint32_t key = 0, next_key = 0; + while (bpf_map_get_next_key(fd, &key, &next_key) == 0) { + if (bpf_map_lookup_elem(fd, &key, cv)) { + goto end_swap_loop; + } + + swap_apps_accumulator(cv, maps_per_core); + + ebpf_pid_stat_t *local_pid = ebpf_get_pid_entry(key, cv->tgid); + if (!local_pid) + goto end_swap_loop; + + netdata_publish_swap_t *publish = &local_pid->swap; + if (!publish->ct || publish->ct != cv->ct) { + memcpy(publish, cv, sizeof(netdata_publish_swap_t)); + local_pid->not_updated = 0; + } else if (++local_pid->not_updated >= max_period) { + bpf_map_delete_elem(fd, &key); + local_pid->not_updated = 0; + } + + // We are cleaning to avoid passing data read from one process to other. +end_swap_loop: + memset(cv, 0, length); + key = next_key; + } +} + +/** + * SWAP thread + * + * Thread used to generate swap charts. + * + * @param ptr a pointer to `struct ebpf_module` + * + * @return It always return NULL + */ +void *ebpf_read_swap_thread(void *ptr) +{ + heartbeat_t hb; + heartbeat_init(&hb); + + ebpf_module_t *em = (ebpf_module_t *)ptr; + + int maps_per_core = em->maps_per_core; + int update_every = em->update_every; + + int counter = update_every - 1; + + uint32_t lifetime = em->lifetime; + uint32_t running_time = 0; + usec_t period = update_every * USEC_PER_SEC; + int max_period = update_every * EBPF_CLEANUP_FACTOR; + + while (!ebpf_plugin_stop() && running_time < lifetime) { + (void)heartbeat_next(&hb, period); + if (ebpf_plugin_stop() || ++counter != update_every) + continue; + + pthread_mutex_lock(&collect_data_mutex); + ebpf_read_swap_apps_table(maps_per_core, max_period); + ebpf_swap_resume_apps_data(); + pthread_mutex_unlock(&collect_data_mutex); + + counter = 0; + + pthread_mutex_lock(&ebpf_exit_cleanup); + if (running_time && !em->running_time) + running_time = update_every; + else + running_time += update_every; + + em->running_time = running_time; + pthread_mutex_unlock(&ebpf_exit_cleanup); + } + + return NULL; +} + +/** +* Send global +* +* Send global charts to Netdata +*/ +static void swap_send_global() +{ + write_io_chart(NETDATA_MEM_SWAP_CHART, NETDATA_EBPF_MEMORY_GROUP, + swap_publish_aggregated[NETDATA_KEY_SWAP_WRITEPAGE_CALL].dimension, + (long long) swap_hash_values[NETDATA_KEY_SWAP_WRITEPAGE_CALL], + swap_publish_aggregated[NETDATA_KEY_SWAP_READPAGE_CALL].dimension, + (long long) swap_hash_values[NETDATA_KEY_SWAP_READPAGE_CALL]); +} + +/** + * Read global counter + * + * Read the table with number of calls to all functions + * + * @param stats vector used to read data from control table. + * @param maps_per_core do I need to read all cores? + */ +static void ebpf_swap_read_global_table(netdata_idx_t *stats, int maps_per_core) +{ + ebpf_read_global_table_stats(swap_hash_values, + swap_values, + swap_maps[NETDATA_SWAP_GLOBAL_TABLE].map_fd, + maps_per_core, + NETDATA_KEY_SWAP_READPAGE_CALL, + NETDATA_SWAP_END); + + ebpf_read_global_table_stats(stats, + swap_values, + swap_maps[NETDATA_SWAP_CONTROLLER].map_fd, + maps_per_core, + NETDATA_CONTROLLER_PID_TABLE_ADD, + NETDATA_CONTROLLER_END); +} + +/** + * Send data to Netdata calling auxiliary functions. + * + * @param root the target list. +*/ +void ebpf_swap_send_apps_data(struct ebpf_target *root) +{ + struct ebpf_target *w; + pthread_mutex_lock(&collect_data_mutex); + for (w = root; w; w = w->next) { + if (unlikely(!(w->charts_created & (1<<EBPF_MODULE_SWAP_IDX)))) + continue; + + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_call_swap_readpage"); + write_chart_dimension("calls", (long long) w->swap.read); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_call_swap_writepage"); + write_chart_dimension("calls", (long long) w->swap.write); + ebpf_write_end_chart(); + } + pthread_mutex_unlock(&collect_data_mutex); +} + +/** + * Sum PIDs + * + * Sum values for all targets. + * + * @param swap + * @param root + */ +static void ebpf_swap_sum_cgroup_pids(netdata_publish_swap_t *swap, struct pid_on_target2 *pids) +{ + uint64_t local_read = 0; + uint64_t local_write = 0; + + while (pids) { + netdata_publish_swap_t *w = &pids->swap; + local_write += w->write; + local_read += w->read; + + pids = pids->next; + } + + // These conditions were added, because we are using incremental algorithm + swap->write = (local_write >= swap->write) ? local_write : swap->write; + swap->read = (local_read >= swap->read) ? local_read : swap->read; +} + +/** + * Send Systemd charts + * + * Send collected data to Netdata. + */ +static void ebpf_send_systemd_swap_charts() +{ + ebpf_cgroup_target_t *ect; + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + if (unlikely(!(ect->flags & NETDATA_EBPF_SERVICES_HAS_SWAP_CHART)) ) { + continue; + } + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_MEM_SWAP_READ_CHART); + write_chart_dimension("calls", (long long) ect->publish_systemd_swap.read); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_MEM_SWAP_WRITE_CHART); + write_chart_dimension("calls", (long long) ect->publish_systemd_swap.write); + ebpf_write_end_chart(); + } +} + +/** + * Create specific swap charts + * + * Create charts for cgroup/application. + * + * @param type the chart type. + * @param update_every value to overwrite the update frequency set by the server. + */ +static void ebpf_create_specific_swap_charts(char *type, int update_every) +{ + char *label = (!strncmp(type, "cgroup_", 7)) ? &type[7] : type; + ebpf_create_chart(type, NETDATA_MEM_SWAP_READ_CHART, + "Calls to function swap_readpage.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_SYSTEM_SWAP_SUBMENU, + NETDATA_CGROUP_SWAP_READ_CONTEXT, NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5100, + ebpf_create_global_dimension, + swap_publish_aggregated, 1, update_every, NETDATA_EBPF_MODULE_NAME_SWAP); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + + ebpf_create_chart(type, NETDATA_MEM_SWAP_WRITE_CHART, + "Calls to function swap_writepage.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_SYSTEM_SWAP_SUBMENU, + NETDATA_CGROUP_SWAP_WRITE_CONTEXT, NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5101, + ebpf_create_global_dimension, + &swap_publish_aggregated[NETDATA_KEY_SWAP_WRITEPAGE_CALL], 1, + update_every, NETDATA_EBPF_MODULE_NAME_SWAP); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); +} + +/** + * Create specific swap charts + * + * Create charts for cgroup/application. + * + * @param type the chart type. + * @param update_every value to overwrite the update frequency set by the server. + */ +static void ebpf_obsolete_specific_swap_charts(char *type, int update_every) +{ + ebpf_write_chart_obsolete(type, NETDATA_MEM_SWAP_READ_CHART, "", "Calls to function swap_readpage.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_SYSTEM_SWAP_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_SWAP_READ_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5100, update_every); + + ebpf_write_chart_obsolete(type, NETDATA_MEM_SWAP_WRITE_CHART, "", "Calls to function swap_writepage.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_SYSTEM_SWAP_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_SWAP_WRITE_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5101, update_every); +} + +/* + * Send Specific Swap data + * + * Send data for specific cgroup/apps. + * + * @param type chart type + * @param values structure with values that will be sent to netdata + */ +static void ebpf_send_specific_swap_data(char *type, netdata_publish_swap_t *values) +{ + ebpf_write_begin_chart(type, NETDATA_MEM_SWAP_READ_CHART, ""); + write_chart_dimension(swap_publish_aggregated[NETDATA_KEY_SWAP_READPAGE_CALL].name, (long long) values->read); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(type, NETDATA_MEM_SWAP_WRITE_CHART, ""); + write_chart_dimension(swap_publish_aggregated[NETDATA_KEY_SWAP_WRITEPAGE_CALL].name, (long long) values->write); + ebpf_write_end_chart(); +} + +/** + * Create Systemd Swap Charts + * + * Create charts when systemd is enabled + * + * @param update_every value to overwrite the update frequency set by the server. + **/ +static void ebpf_create_systemd_swap_charts(int update_every) +{ + static ebpf_systemd_args_t data_read = { + .title = "Calls to function swap_readpage.", + .units = EBPF_COMMON_UNITS_CALLS_PER_SEC, + .family = NETDATA_SYSTEM_SWAP_SUBMENU, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20191, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SYSTEMD_SWAP_READ_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_SWAP, + .update_every = 0, + .suffix = NETDATA_MEM_SWAP_READ_CHART, + .dimension = "calls" + }; + + static ebpf_systemd_args_t data_write = { + .title = "Calls to function swap_writepage.", + .units = EBPF_COMMON_UNITS_CALLS_PER_SEC, + .family = NETDATA_SYSTEM_SWAP_SUBMENU, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20192, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SYSTEMD_SWAP_WRITE_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_SWAP, + .update_every = 0, + .suffix = NETDATA_MEM_SWAP_WRITE_CHART, + .dimension = "calls" + }; + + if (!data_write.update_every) + data_read.update_every = data_write.update_every = update_every; + + ebpf_cgroup_target_t *w; + for (w = ebpf_cgroup_pids; w ; w = w->next) { + if (unlikely(!w->systemd || w->flags & NETDATA_EBPF_SERVICES_HAS_SWAP_CHART)) + continue; + + data_read.id = data_write.id = w->name; + ebpf_create_charts_on_systemd(&data_read); + + ebpf_create_charts_on_systemd(&data_write); + + w->flags |= NETDATA_EBPF_SERVICES_HAS_SWAP_CHART; + } +} + +/** + * Send data to Netdata calling auxiliary functions. + * + * @param update_every value to overwrite the update frequency set by the server. +*/ +void ebpf_swap_send_cgroup_data(int update_every) +{ + pthread_mutex_lock(&mutex_cgroup_shm); + ebpf_cgroup_target_t *ect; + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + ebpf_swap_sum_cgroup_pids(&ect->publish_systemd_swap, ect->pids); + } + + if (shm_ebpf_cgroup.header->systemd_enabled) { + if (send_cgroup_chart) { + ebpf_create_systemd_swap_charts(update_every); + fflush(stdout); + } + ebpf_send_systemd_swap_charts(); + } + + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + if (ect->systemd) + continue; + + if (!(ect->flags & NETDATA_EBPF_CGROUP_HAS_SWAP_CHART) && ect->updated) { + ebpf_create_specific_swap_charts(ect->name, update_every); + ect->flags |= NETDATA_EBPF_CGROUP_HAS_SWAP_CHART; + } + + if (ect->flags & NETDATA_EBPF_CGROUP_HAS_SWAP_CHART) { + if (ect->updated) { + ebpf_send_specific_swap_data(ect->name, &ect->publish_systemd_swap); + } else { + ebpf_obsolete_specific_swap_charts(ect->name, update_every); + ect->flags &= ~NETDATA_EBPF_CGROUP_HAS_SWAP_CHART; + } + } + } + + pthread_mutex_unlock(&mutex_cgroup_shm); +} + +/** +* Main loop for this collector. +*/ +static void swap_collector(ebpf_module_t *em) +{ + int cgroup = em->cgroup_charts; + int update_every = em->update_every; + heartbeat_t hb; + heartbeat_init(&hb); + int counter = update_every - 1; + int maps_per_core = em->maps_per_core; + uint32_t running_time = 0; + uint32_t lifetime = em->lifetime; + netdata_idx_t *stats = em->hash_table_stats; + memset(stats, 0, sizeof(em->hash_table_stats)); + while (!ebpf_plugin_stop() && running_time < lifetime) { + (void)heartbeat_next(&hb, USEC_PER_SEC); + if (ebpf_plugin_stop() || ++counter != update_every) + continue; + + counter = 0; + netdata_apps_integration_flags_t apps = em->apps_charts; + ebpf_swap_read_global_table(stats, maps_per_core); + + if (cgroup && shm_ebpf_cgroup.header) + ebpf_update_swap_cgroup(); + + pthread_mutex_lock(&lock); + + swap_send_global(); + + if (apps & NETDATA_EBPF_APPS_FLAG_CHART_CREATED) + ebpf_swap_send_apps_data(apps_groups_root_target); + + if (cgroup && shm_ebpf_cgroup.header) + ebpf_swap_send_cgroup_data(update_every); + + pthread_mutex_unlock(&lock); + + pthread_mutex_lock(&ebpf_exit_cleanup); + if (running_time && !em->running_time) + running_time = update_every; + else + running_time += update_every; + + em->running_time = running_time; + pthread_mutex_unlock(&ebpf_exit_cleanup); + } +} + +/***************************************************************** + * + * INITIALIZE THREAD + * + *****************************************************************/ + +/** + * Create apps charts + * + * Call ebpf_create_chart to create the charts on apps submenu. + * + * @param em a pointer to the structure with the default values. + */ +void ebpf_swap_create_apps_charts(struct ebpf_module *em, void *ptr) +{ + struct ebpf_target *root = ptr; + struct ebpf_target *w; + int update_every = em->update_every; + for (w = root; w; w = w->next) { + if (unlikely(!w->exposed)) + continue; + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_swap_readpage", + "Calls to function swap_readpage.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_EBPF_MEMORY_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_swap_readpage", + 20070, + update_every, + NETDATA_EBPF_MODULE_NAME_SWAP); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION calls '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_swap_writepage", + "Calls to function swap_writepage.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_EBPF_MEMORY_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_swap_writepage", + 20071, + update_every, + NETDATA_EBPF_MODULE_NAME_SWAP); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION calls '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + + w->charts_created |= 1<<EBPF_MODULE_SWAP_IDX; + } + em->apps_charts |= NETDATA_EBPF_APPS_FLAG_CHART_CREATED; +} + +/** + * 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. + */ +static void ebpf_swap_allocate_global_vectors() +{ + swap_vector = callocz((size_t)ebpf_nprocs, sizeof(netdata_publish_swap_t)); + + swap_values = callocz((size_t)ebpf_nprocs, sizeof(netdata_idx_t)); + + memset(swap_hash_values, 0, sizeof(swap_hash_values)); +} + +/***************************************************************** + * + * MAIN THREAD + * + *****************************************************************/ + +/** + * Create global charts + * + * Call ebpf_create_chart to create the charts for the collector. + * + * @param update_every value to overwrite the update frequency set by the server. + */ +static void ebpf_create_swap_charts(int update_every) +{ + ebpf_create_chart(NETDATA_EBPF_MEMORY_GROUP, NETDATA_MEM_SWAP_CHART, + "Calls to access swap memory", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_SYSTEM_SWAP_SUBMENU, + "mem.swapcalls", + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_MEM_SWAP_CALLS, + ebpf_create_global_dimension, + swap_publish_aggregated, NETDATA_SWAP_END, + update_every, NETDATA_EBPF_MODULE_NAME_SWAP); + + fflush(stdout); +} + +/* + * Load BPF + * + * Load BPF files. + * + * @param em the structure with configuration + */ +static int ebpf_swap_load_bpf(ebpf_module_t *em) +{ +#ifdef LIBBPF_MAJOR_VERSION + ebpf_define_map_type(em->maps, em->maps_per_core, running_on_kernel); +#endif + + int ret = 0; + ebpf_adjust_apps_cgroup(em, em->targets[NETDATA_KEY_SWAP_READPAGE_CALL].mode); + if (em->load & EBPF_LOAD_LEGACY) { + em->probe_links = ebpf_load_program(ebpf_plugin_dir, em, running_on_kernel, isrh, &em->objects); + if (!em->probe_links) { + ret = -1; + } + } +#ifdef LIBBPF_MAJOR_VERSION + else { + bpf_obj = swap_bpf__open(); + if (!bpf_obj) + ret = -1; + else + ret = ebpf_swap_load_and_attach(bpf_obj, em); + } +#endif + + if (ret) + netdata_log_error("%s %s", EBPF_DEFAULT_ERROR_MSG, em->info.thread_name); + + return ret; +} + +/** + * Update Internal value + * + * Update values used during runtime. + * + * @return It returns 0 when one of the functions is present and -1 otherwise. + */ +static int ebpf_swap_set_internal_value() +{ + ebpf_addresses_t address = {.function = NULL, .hash = 0, .addr = 0}; + int i; + for (i = 0; swap_read[i] ; i++) { + address.function = swap_read[i]; + ebpf_load_addresses(&address, -1); + if (address.addr) + break; + } + + if (!address.addr) { + netdata_log_error("%s swap.", NETDATA_EBPF_DEFAULT_FNT_NOT_FOUND); + return -1; + } + + swap_targets[NETDATA_KEY_SWAP_READPAGE_CALL].name = address.function; + + return 0; +} + +/** + * SWAP thread + * + * Thread used to make swap thread + * + * @param ptr a pointer to `struct ebpf_module` + * + * @return It always return NULL + */ +void *ebpf_swap_thread(void *ptr) +{ + ebpf_module_t *em = (ebpf_module_t *)ptr; + + CLEANUP_FUNCTION_REGISTER(ebpf_swap_exit) cleanup_ptr = em; + + em->maps = swap_maps; + + ebpf_update_pid_table(&swap_maps[NETDATA_PID_SWAP_TABLE], em); + + if (ebpf_swap_set_internal_value()) { + goto endswap; + } + +#ifdef LIBBPF_MAJOR_VERSION + ebpf_adjust_thread_load(em, default_btf); +#endif + if (ebpf_swap_load_bpf(em)) { + goto endswap; + } + + ebpf_swap_allocate_global_vectors(); + + int algorithms[NETDATA_SWAP_END] = { NETDATA_EBPF_INCREMENTAL_IDX, NETDATA_EBPF_INCREMENTAL_IDX }; + ebpf_global_labels(swap_aggregated_data, swap_publish_aggregated, swap_dimension_name, swap_dimension_name, + algorithms, NETDATA_SWAP_END); + + pthread_mutex_lock(&lock); + ebpf_create_swap_charts(em->update_every); + ebpf_update_stats(&plugin_statistics, em); + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps, EBPF_ACTION_STAT_ADD); + pthread_mutex_unlock(&lock); + + ebpf_read_swap.thread = nd_thread_create(ebpf_read_swap.name, NETDATA_THREAD_OPTION_DEFAULT, + ebpf_read_swap_thread, em); + + swap_collector(em); + +endswap: + ebpf_update_disabled_plugin_stats(em); + + return NULL; +} diff --git a/src/collectors/ebpf.plugin/ebpf_swap.h b/src/collectors/ebpf.plugin/ebpf_swap.h new file mode 100644 index 000000000..92aecd29b --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_swap.h @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_EBPF_SWAP_H +#define NETDATA_EBPF_SWAP_H 1 + +// Module name & description +#define NETDATA_EBPF_MODULE_NAME_SWAP "swap" +#define NETDATA_EBPF_SWAP_MODULE_DESC "Monitor swap space usage. This thread is integrated with apps and cgroup." + +#define NETDATA_SWAP_SLEEP_MS 850000ULL + +// charts +#define NETDATA_MEM_SWAP_CHART "swapcalls" +#define NETDATA_MEM_SWAP_READ_CHART "swap_read_call" +#define NETDATA_MEM_SWAP_WRITE_CHART "swap_write_call" +#define NETDATA_SWAP_SUBMENU "swap" + +// configuration file +#define NETDATA_DIRECTORY_SWAP_CONFIG_FILE "swap.conf" + +// Contexts +#define NETDATA_CGROUP_SWAP_READ_CONTEXT "cgroup.swap_read" +#define NETDATA_CGROUP_SWAP_WRITE_CONTEXT "cgroup.swap_write" +#define NETDATA_SYSTEMD_SWAP_READ_CONTEXT "systemd.services.swap_read" +#define NETDATA_SYSTEMD_SWAP_WRITE_CONTEXT "systemd.services.swap_write" + +typedef struct netdata_publish_swap { + uint64_t ct; + uint32_t tgid; + uint32_t uid; + uint32_t gid; + char name[TASK_COMM_LEN]; + + uint64_t read; + uint64_t write; +} netdata_publish_swap_t; + +enum swap_tables { + NETDATA_PID_SWAP_TABLE, + NETDATA_SWAP_CONTROLLER, + NETDATA_SWAP_GLOBAL_TABLE +}; + +enum swap_counters { + NETDATA_KEY_SWAP_READPAGE_CALL, + NETDATA_KEY_SWAP_WRITEPAGE_CALL, + + // Keep this as last and don't skip numbers as it is used as element counter + NETDATA_SWAP_END +}; + +void *ebpf_swap_thread(void *ptr); +void ebpf_swap_create_apps_charts(struct ebpf_module *em, void *ptr); + +extern struct config swap_config; +extern netdata_ebpf_targets_t swap_targets[]; + +#endif diff --git a/src/collectors/ebpf.plugin/ebpf_sync.c b/src/collectors/ebpf.plugin/ebpf_sync.c new file mode 100644 index 000000000..2be9192c5 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_sync.c @@ -0,0 +1,745 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ebpf.h" +#include "ebpf_sync.h" + +static char *sync_counter_dimension_name[NETDATA_SYNC_IDX_END] = { "sync", "syncfs", "msync", "fsync", "fdatasync", + "sync_file_range" }; +static netdata_syscall_stat_t sync_counter_aggregated_data[NETDATA_SYNC_IDX_END]; +static netdata_publish_syscall_t sync_counter_publish_aggregated[NETDATA_SYNC_IDX_END]; + +static netdata_idx_t sync_hash_values[NETDATA_SYNC_IDX_END]; + +ebpf_local_maps_t sync_maps[] = {{.name = "tbl_sync", .internal_input = NETDATA_SYNC_END, + .user_input = 0, .type = NETDATA_EBPF_MAP_STATIC, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }, + {.name = NULL, .internal_input = 0, .user_input = 0, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }}; + +ebpf_local_maps_t syncfs_maps[] = {{.name = "tbl_syncfs", .internal_input = NETDATA_SYNC_END, + .user_input = 0, .type = NETDATA_EBPF_MAP_STATIC, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }, + {.name = NULL, .internal_input = 0, .user_input = 0, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }}; + +ebpf_local_maps_t msync_maps[] = {{.name = "tbl_msync", .internal_input = NETDATA_SYNC_END, + .user_input = 0, .type = NETDATA_EBPF_MAP_STATIC, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }, + {.name = NULL, .internal_input = 0, .user_input = 0, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }}; + +ebpf_local_maps_t fsync_maps[] = {{.name = "tbl_fsync", .internal_input = NETDATA_SYNC_END, + .user_input = 0, .type = NETDATA_EBPF_MAP_STATIC, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }, + {.name = NULL, .internal_input = 0, .user_input = 0, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }}; + +ebpf_local_maps_t fdatasync_maps[] = {{.name = "tbl_fdatasync", .internal_input = NETDATA_SYNC_END, + .user_input = 0, .type = NETDATA_EBPF_MAP_STATIC, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }, + {.name = NULL, .internal_input = 0, .user_input = 0, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }}; + +ebpf_local_maps_t sync_file_range_maps[] = {{.name = "tbl_syncfr", .internal_input = NETDATA_SYNC_END, + .user_input = 0, .type = NETDATA_EBPF_MAP_STATIC, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }, + {.name = NULL, .internal_input = 0, .user_input = 0, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }}; + +struct config sync_config = { .first_section = NULL, + .last_section = NULL, + .mutex = NETDATA_MUTEX_INITIALIZER, + .index = { .avl_tree = { .root = NULL, .compar = appconfig_section_compare }, + .rwlock = AVL_LOCK_INITIALIZER } }; + +netdata_ebpf_targets_t sync_targets[] = { {.name = NETDATA_SYSCALLS_SYNC, .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = NETDATA_SYSCALLS_SYNCFS, .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = NETDATA_SYSCALLS_MSYNC, .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = NETDATA_SYSCALLS_FSYNC, .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = NETDATA_SYSCALLS_FDATASYNC, .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = NETDATA_SYSCALLS_SYNC_FILE_RANGE, .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = NULL, .mode = EBPF_LOAD_TRAMPOLINE}}; + +#ifdef LIBBPF_MAJOR_VERSION +/***************************************************************** + * + * BTF FUNCTIONS + * + *****************************************************************/ + +/** + * Disable probe + * + * Disable kprobe to use another method. + * + * @param obj is the main structure for bpf objects. + */ +static inline void ebpf_sync_disable_probe(struct sync_bpf *obj) +{ + bpf_program__set_autoload(obj->progs.netdata_sync_kprobe, false); +} + +/** + * Disable trampoline + * + * Disable trampoline to use another method. + * + * @param obj is the main structure for bpf objects. + */ +static inline void ebpf_sync_disable_trampoline(struct sync_bpf *obj) +{ + bpf_program__set_autoload(obj->progs.netdata_sync_fentry, false); +} + +/** + * Disable tracepoint + * + * Disable tracepoints according information given. + * + * @param obj object loaded + * @param idx Which syscall will not be disabled + */ +void ebpf_sync_disable_tracepoints(struct sync_bpf *obj, sync_syscalls_index_t idx) +{ + if (idx != NETDATA_SYNC_SYNC_IDX) + bpf_program__set_autoload(obj->progs.netdata_sync_entry, false); + + if (idx != NETDATA_SYNC_SYNCFS_IDX) + bpf_program__set_autoload(obj->progs.netdata_syncfs_entry, false); + + if (idx != NETDATA_SYNC_MSYNC_IDX) + bpf_program__set_autoload(obj->progs.netdata_msync_entry, false); + + if (idx != NETDATA_SYNC_FSYNC_IDX) + bpf_program__set_autoload(obj->progs.netdata_fsync_entry, false); + + if (idx != NETDATA_SYNC_FDATASYNC_IDX) + bpf_program__set_autoload(obj->progs.netdata_fdatasync_entry, false); + + if (idx != NETDATA_SYNC_SYNC_FILE_RANGE_IDX) + bpf_program__set_autoload(obj->progs.netdata_sync_file_range_entry, false); +} + +/** + * Set hash tables + * + * Set the values for maps according the value given by kernel. + * + * @param map the map loaded. + * @param obj the main structure for bpf objects. + */ +static void ebpf_sync_set_hash_tables(ebpf_local_maps_t *map, struct sync_bpf *obj) +{ + map->map_fd = bpf_map__fd(obj->maps.tbl_sync); +} + +/** + * Load and attach + * + * Load and attach the eBPF code in kernel. + * + * @param obj is the main structure for bpf objects. + * @param em the structure with configuration + * @param target the syscall that we are attaching a tracer. + * @param idx the index for the main structure + * + * @return it returns 0 on success and -1 otherwise + */ +static inline int ebpf_sync_load_and_attach(struct sync_bpf *obj, ebpf_module_t *em, char *target, + sync_syscalls_index_t idx) +{ + netdata_ebpf_targets_t *synct = em->targets; + netdata_ebpf_program_loaded_t test = synct[NETDATA_SYNC_SYNC_IDX].mode; + + if (test == EBPF_LOAD_TRAMPOLINE) { + ebpf_sync_disable_probe(obj); + ebpf_sync_disable_tracepoints(obj, NETDATA_SYNC_IDX_END); + + bpf_program__set_attach_target(obj->progs.netdata_sync_fentry, 0, + target); + } else if (test == EBPF_LOAD_PROBE || + test == EBPF_LOAD_RETPROBE) { + ebpf_sync_disable_tracepoints(obj, NETDATA_SYNC_IDX_END); + ebpf_sync_disable_trampoline(obj); + } else { + ebpf_sync_disable_probe(obj); + ebpf_sync_disable_trampoline(obj); + + ebpf_sync_disable_tracepoints(obj, idx); + } + + ebpf_update_map_type(obj->maps.tbl_sync, &em->maps[NETDATA_SYNC_GLOBAL_TABLE]); + + int ret = sync_bpf__load(obj); + if (!ret) { + if (test != EBPF_LOAD_PROBE && test != EBPF_LOAD_RETPROBE) { + ret = sync_bpf__attach(obj); + } else { + obj->links.netdata_sync_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_sync_kprobe, + false, target); + ret = (int)libbpf_get_error(obj->links.netdata_sync_kprobe); + } + + if (!ret) + ebpf_sync_set_hash_tables(&em->maps[NETDATA_SYNC_GLOBAL_TABLE], obj); + } + + return ret; +} +#endif + +/***************************************************************** + * + * CLEANUP THREAD + * + *****************************************************************/ + +/** + * Cleanup Objects + * + * Cleanup loaded objects when thread was initialized. + */ +void ebpf_sync_cleanup_objects() +{ + int i; + for (i = 0; local_syscalls[i].syscall; i++) { + ebpf_sync_syscalls_t *w = &local_syscalls[i]; +#ifdef LIBBPF_MAJOR_VERSION + if (w->sync_obj) { + sync_bpf__destroy(w->sync_obj); + w->sync_obj = NULL; + } +#endif + if (w->probe_links) { + ebpf_unload_legacy_code(w->objects, w->probe_links); + w->objects = NULL; + w->probe_links = NULL; + } + } +} + +/* + static void ebpf_create_sync_chart(char *id, + char *title, + int order, + int idx, + int end, + int update_every) + { + ebpf_write_chart_cmd(NETDATA_EBPF_MEMORY_GROUP, id, title, EBPF_COMMON_UNITS_CALL, + NETDATA_EBPF_SYNC_SUBMENU, NETDATA_EBPF_CHART_TYPE_LINE, NULL, order, + update_every, + NETDATA_EBPF_MODULE_NAME_SYNC); + */ + +/** + * Obsolete global + * + * Obsolete global charts created by thread. + * + * @param em a pointer to `struct ebpf_module` + */ +static void ebpf_obsolete_sync_global(ebpf_module_t *em) +{ + if (local_syscalls[NETDATA_SYNC_FSYNC_IDX].enabled && local_syscalls[NETDATA_SYNC_FDATASYNC_IDX].enabled) + ebpf_write_chart_obsolete(NETDATA_EBPF_MEMORY_GROUP, + NETDATA_EBPF_FILE_SYNC_CHART, + "", + "Monitor calls to fsync(2) and fdatasync(2).", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_EBPF_SYNC_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, + "mem.file_sync", + 21300, + em->update_every); + + if (local_syscalls[NETDATA_SYNC_MSYNC_IDX].enabled) + ebpf_write_chart_obsolete(NETDATA_EBPF_MEMORY_GROUP, + NETDATA_EBPF_MSYNC_CHART, + "", + "Monitor calls to msync(2).", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_EBPF_SYNC_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, + "mem.memory_map", + 21301, + em->update_every); + + if (local_syscalls[NETDATA_SYNC_SYNC_IDX].enabled && local_syscalls[NETDATA_SYNC_SYNCFS_IDX].enabled) + ebpf_write_chart_obsolete(NETDATA_EBPF_MEMORY_GROUP, + NETDATA_EBPF_SYNC_CHART, + "", + "Monitor calls to sync(2) and syncfs(2).", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_EBPF_SYNC_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, + "mem.sync", + 21302, + em->update_every); + + if (local_syscalls[NETDATA_SYNC_SYNC_FILE_RANGE_IDX].enabled) + ebpf_write_chart_obsolete(NETDATA_EBPF_MEMORY_GROUP, + NETDATA_EBPF_FILE_SEGMENT_CHART, + "", + "Monitor calls to sync_file_range(2).", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_EBPF_SYNC_SUBMENU, + NETDATA_EBPF_CHART_TYPE_LINE, + "mem.file_segment", + 21303, + em->update_every); +} + +/** + * Exit + * + * Clean up the main thread. + * + * @param ptr thread data. + */ +static void ebpf_sync_exit(void *pptr) +{ + ebpf_module_t *em = CLEANUP_FUNCTION_GET_PTR(pptr); + if(!em) return; + + if (em->enabled == NETDATA_THREAD_EBPF_FUNCTION_RUNNING) { + pthread_mutex_lock(&lock); + ebpf_obsolete_sync_global(em); + pthread_mutex_unlock(&lock); + } + + ebpf_sync_cleanup_objects(); + + pthread_mutex_lock(&ebpf_exit_cleanup); + em->enabled = NETDATA_THREAD_EBPF_STOPPED; + ebpf_update_stats(&plugin_statistics, em); + pthread_mutex_unlock(&ebpf_exit_cleanup); +} + +/***************************************************************** + * + * INITIALIZE THREAD + * + *****************************************************************/ + +/** + * Load Legacy + * + * Load legacy code. + * + * @param w is the sync output structure with pointers to objects loaded. + * @param em is structure with configuration + * + * @return 0 on success and -1 otherwise. + */ +static int ebpf_sync_load_legacy(ebpf_sync_syscalls_t *w, ebpf_module_t *em) +{ + em->info.thread_name = w->syscall; + if (!w->probe_links) { + w->probe_links = ebpf_load_program(ebpf_plugin_dir, em, running_on_kernel, isrh, &w->objects); + if (!w->probe_links) { + return -1; + } + } + + return 0; +} + +/* + * Initialize Syscalls + * + * Load the eBPF programs to monitor syscalls + * + * @return 0 on success and -1 otherwise. + */ +static int ebpf_sync_initialize_syscall(ebpf_module_t *em) +{ +#ifdef LIBBPF_MAJOR_VERSION + ebpf_define_map_type(sync_maps, em->maps_per_core, running_on_kernel); + ebpf_define_map_type(syncfs_maps, em->maps_per_core, running_on_kernel); + ebpf_define_map_type(msync_maps, em->maps_per_core, running_on_kernel); + ebpf_define_map_type(fsync_maps, em->maps_per_core, running_on_kernel); + ebpf_define_map_type(fdatasync_maps, em->maps_per_core, running_on_kernel); + ebpf_define_map_type(sync_file_range_maps, em->maps_per_core, running_on_kernel); +#endif + + int i; + const char *saved_name = em->info.thread_name; + int errors = 0; + for (i = 0; local_syscalls[i].syscall; i++) { + ebpf_sync_syscalls_t *w = &local_syscalls[i]; + w->sync_maps = local_syscalls[i].sync_maps; + em->maps = local_syscalls[i].sync_maps; + if (w->enabled) { + if (em->load & EBPF_LOAD_LEGACY) { + if (ebpf_sync_load_legacy(w, em)) + errors++; + + em->info.thread_name = saved_name; + } +#ifdef LIBBPF_MAJOR_VERSION + else { + char syscall[NETDATA_EBPF_MAX_SYSCALL_LENGTH]; + ebpf_select_host_prefix(syscall, NETDATA_EBPF_MAX_SYSCALL_LENGTH, w->syscall, running_on_kernel); + if (ebpf_is_function_inside_btf(default_btf, syscall)) { + w->sync_obj = sync_bpf__open(); + if (!w->sync_obj) { + w->enabled = false; + errors++; + } else { + if (ebpf_sync_load_and_attach(w->sync_obj, em, syscall, i)) { + w->enabled = false; + errors++; + } + } + } else { + netdata_log_info("Cannot find syscall %s we are not going to monitor it.", syscall); + w->enabled = false; + } + + em->info.thread_name = saved_name; + } +#endif + } + } + em->info.thread_name = saved_name; + + memset(sync_counter_aggregated_data, 0 , NETDATA_SYNC_IDX_END * sizeof(netdata_syscall_stat_t)); + memset(sync_counter_publish_aggregated, 0 , NETDATA_SYNC_IDX_END * sizeof(netdata_publish_syscall_t)); + memset(sync_hash_values, 0 , NETDATA_SYNC_IDX_END * sizeof(netdata_idx_t)); + + return (errors) ? -1 : 0; +} + +/***************************************************************** + * + * DATA THREAD + * + *****************************************************************/ + +/** + * Read global table + * + * Read the table with number of calls for all functions + * + * @param maps_per_core do I need to read all cores? + */ +static void ebpf_sync_read_global_table(int maps_per_core) +{ + netdata_idx_t stored[NETDATA_MAX_PROCESSOR]; + uint32_t idx = NETDATA_SYNC_CALL; + int i; + for (i = 0; local_syscalls[i].syscall; i++) { + ebpf_sync_syscalls_t *w = &local_syscalls[i]; + if (w->enabled) { + int fd = w->sync_maps[NETDATA_SYNC_GLOBAL_TABLE].map_fd; + if (!bpf_map_lookup_elem(fd, &idx, &stored)) { + int j, end = (maps_per_core) ? ebpf_nprocs : 1; + netdata_idx_t total = 0; + for (j = 0; j < end ;j++ ) + total += stored[j]; + + sync_hash_values[i] = total; + } + } + } +} + +/** + * Create Sync charts + * + * Create charts and dimensions according user input. + * + * @param id chart id + * @param idx the first index with data. + * @param end the last index with data. + */ +static void ebpf_send_sync_chart(char *id, + int idx, + int end) +{ + ebpf_write_begin_chart(NETDATA_EBPF_MEMORY_GROUP, id, ""); + + netdata_publish_syscall_t *move = &sync_counter_publish_aggregated[idx]; + + while (move && idx <= end) { + if (local_syscalls[idx].enabled) + write_chart_dimension(move->name, (long long)sync_hash_values[idx]); + + move = move->next; + idx++; + } + + ebpf_write_end_chart(); +} + +/** + * Send data + * + * Send global charts to Netdata + */ +static void sync_send_data() +{ + if (local_syscalls[NETDATA_SYNC_FSYNC_IDX].enabled && local_syscalls[NETDATA_SYNC_FDATASYNC_IDX].enabled) { + ebpf_send_sync_chart(NETDATA_EBPF_FILE_SYNC_CHART, NETDATA_SYNC_FSYNC_IDX, NETDATA_SYNC_FDATASYNC_IDX); + } + + if (local_syscalls[NETDATA_SYNC_MSYNC_IDX].enabled) + ebpf_one_dimension_write_charts(NETDATA_EBPF_MEMORY_GROUP, NETDATA_EBPF_MSYNC_CHART, + sync_counter_publish_aggregated[NETDATA_SYNC_MSYNC_IDX].dimension, + sync_hash_values[NETDATA_SYNC_MSYNC_IDX]); + + if (local_syscalls[NETDATA_SYNC_SYNC_IDX].enabled && local_syscalls[NETDATA_SYNC_SYNCFS_IDX].enabled) { + ebpf_send_sync_chart(NETDATA_EBPF_SYNC_CHART, NETDATA_SYNC_SYNC_IDX, NETDATA_SYNC_SYNCFS_IDX); + } + + if (local_syscalls[NETDATA_SYNC_SYNC_FILE_RANGE_IDX].enabled) + ebpf_one_dimension_write_charts(NETDATA_EBPF_MEMORY_GROUP, NETDATA_EBPF_FILE_SEGMENT_CHART, + sync_counter_publish_aggregated[NETDATA_SYNC_SYNC_FILE_RANGE_IDX].dimension, + sync_hash_values[NETDATA_SYNC_SYNC_FILE_RANGE_IDX]); +} + +/** +* Main loop for this collector. +*/ +static void sync_collector(ebpf_module_t *em) +{ + heartbeat_t hb; + heartbeat_init(&hb); + int update_every = em->update_every; + int counter = update_every - 1; + int maps_per_core = em->maps_per_core; + uint32_t running_time = 0; + uint32_t lifetime = em->lifetime; + while (!ebpf_plugin_stop() && running_time < lifetime) { + (void)heartbeat_next(&hb, USEC_PER_SEC); + if (ebpf_plugin_stop() || ++counter != update_every) + continue; + + counter = 0; + ebpf_sync_read_global_table(maps_per_core); + pthread_mutex_lock(&lock); + + sync_send_data(); + + pthread_mutex_unlock(&lock); + + pthread_mutex_lock(&ebpf_exit_cleanup); + if (running_time && !em->running_time) + running_time = update_every; + else + running_time += update_every; + + em->running_time = running_time; + pthread_mutex_unlock(&ebpf_exit_cleanup); + } +} + + +/***************************************************************** + * + * MAIN THREAD + * + *****************************************************************/ + +/** + * Create Sync charts + * + * Create charts and dimensions according user input. + * + * @param id chart id + * @param title chart title + * @param order order number of the specified chart + * @param idx the first index with data. + * @param end the last index with data. + * @param update_every value to overwrite the update frequency set by the server. + * @param context the chart context + */ +static void ebpf_create_sync_chart(char *id, + char *title, + int order, + int idx, + int end, + int update_every, + char *context) +{ + ebpf_write_chart_cmd(NETDATA_EBPF_MEMORY_GROUP, id, "", title, EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_EBPF_SYNC_SUBMENU, NETDATA_EBPF_CHART_TYPE_LINE, context, order, + update_every, + NETDATA_EBPF_MODULE_NAME_SYNC); + + netdata_publish_syscall_t *move = &sync_counter_publish_aggregated[idx]; + + while (move && idx <= end) { + if (local_syscalls[idx].enabled) + ebpf_write_global_dimension(move->name, move->dimension, move->algorithm); + + move = move->next; + idx++; + } +} + +/** + * Create global charts + * + * Call ebpf_create_chart to create the charts for the collector. + * + * @param update_every value to overwrite the update frequency set by the server. + */ +static void ebpf_create_sync_charts(int update_every) +{ + if (local_syscalls[NETDATA_SYNC_FSYNC_IDX].enabled && local_syscalls[NETDATA_SYNC_FDATASYNC_IDX].enabled) + ebpf_create_sync_chart(NETDATA_EBPF_FILE_SYNC_CHART, + "Monitor calls to fsync(2) and fdatasync(2).", 21300, + NETDATA_SYNC_FSYNC_IDX, NETDATA_SYNC_FDATASYNC_IDX, update_every, + "mem.file_sync"); + + if (local_syscalls[NETDATA_SYNC_MSYNC_IDX].enabled) + ebpf_create_sync_chart(NETDATA_EBPF_MSYNC_CHART, + "Monitor calls to msync(2).", 21301, + NETDATA_SYNC_MSYNC_IDX, NETDATA_SYNC_MSYNC_IDX, update_every, + "mem.memory_map"); + + if (local_syscalls[NETDATA_SYNC_SYNC_IDX].enabled && local_syscalls[NETDATA_SYNC_SYNCFS_IDX].enabled) + ebpf_create_sync_chart(NETDATA_EBPF_SYNC_CHART, + "Monitor calls to sync(2) and syncfs(2).", 21302, + NETDATA_SYNC_SYNC_IDX, NETDATA_SYNC_SYNCFS_IDX, update_every, + "mem.sync"); + + if (local_syscalls[NETDATA_SYNC_SYNC_FILE_RANGE_IDX].enabled) + ebpf_create_sync_chart(NETDATA_EBPF_FILE_SEGMENT_CHART, + "Monitor calls to sync_file_range(2).", 21303, + NETDATA_SYNC_SYNC_FILE_RANGE_IDX, NETDATA_SYNC_SYNC_FILE_RANGE_IDX, update_every, + "mem.file_segment"); + + fflush(stdout); +} + +/** + * Parse Syscalls + * + * Parse syscall options available inside ebpf.d/sync.conf + */ +static void ebpf_sync_parse_syscalls() +{ + int i; + for (i = 0; local_syscalls[i].syscall; i++) { + local_syscalls[i].enabled = appconfig_get_boolean(&sync_config, NETDATA_SYNC_CONFIG_NAME, + local_syscalls[i].syscall, CONFIG_BOOLEAN_YES); + } +} + +/** + * Set sync maps + * + * When thread is initialized the variable sync_maps is set as null, + * this function fills the variable before to use. + */ +static void ebpf_set_sync_maps() +{ + local_syscalls[NETDATA_SYNC_SYNC_IDX].sync_maps = sync_maps; + local_syscalls[NETDATA_SYNC_SYNCFS_IDX].sync_maps = syncfs_maps; + local_syscalls[NETDATA_SYNC_MSYNC_IDX].sync_maps = msync_maps; + local_syscalls[NETDATA_SYNC_FSYNC_IDX].sync_maps = fsync_maps; + local_syscalls[NETDATA_SYNC_FDATASYNC_IDX].sync_maps = fdatasync_maps; + local_syscalls[NETDATA_SYNC_SYNC_FILE_RANGE_IDX].sync_maps = sync_file_range_maps; +} + +/** + * Sync thread + * + * Thread used to make sync thread + * + * @param ptr a pointer to `struct ebpf_module` + * + * @return It always return NULL + */ +void *ebpf_sync_thread(void *ptr) +{ + ebpf_module_t *em = (ebpf_module_t *)ptr; + + CLEANUP_FUNCTION_REGISTER(ebpf_sync_exit) cleanup_ptr = em; + + ebpf_set_sync_maps(); + ebpf_sync_parse_syscalls(); + +#ifdef LIBBPF_MAJOR_VERSION + ebpf_adjust_thread_load(em, default_btf); +#endif + if (ebpf_sync_initialize_syscall(em)) { + goto endsync; + } + + int algorithms[NETDATA_SYNC_IDX_END] = { NETDATA_EBPF_INCREMENTAL_IDX, NETDATA_EBPF_INCREMENTAL_IDX, + NETDATA_EBPF_INCREMENTAL_IDX, NETDATA_EBPF_INCREMENTAL_IDX, + NETDATA_EBPF_INCREMENTAL_IDX, NETDATA_EBPF_INCREMENTAL_IDX }; + ebpf_global_labels(sync_counter_aggregated_data, sync_counter_publish_aggregated, + sync_counter_dimension_name, sync_counter_dimension_name, + algorithms, NETDATA_SYNC_IDX_END); + + pthread_mutex_lock(&lock); + ebpf_create_sync_charts(em->update_every); + ebpf_update_stats(&plugin_statistics, em); + pthread_mutex_unlock(&lock); + + sync_collector(em); + +endsync: + ebpf_update_disabled_plugin_stats(em); + + return NULL; +} diff --git a/src/collectors/ebpf.plugin/ebpf_sync.h b/src/collectors/ebpf.plugin/ebpf_sync.h new file mode 100644 index 000000000..373695565 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_sync.h @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_EBPF_SYNC_H +#define NETDATA_EBPF_SYNC_H 1 + +// Module name & description +#define NETDATA_EBPF_MODULE_NAME_SYNC "sync" +#define NETDATA_EBPF_SYNC_MODULE_DESC "Monitor calls to syscalls sync(2), fsync(2), fdatasync(2), syncfs(2), msync(2), and sync_file_range(2)." + +// charts +#define NETDATA_EBPF_SYNC_CHART "sync" +#define NETDATA_EBPF_MSYNC_CHART "memory_map" +#define NETDATA_EBPF_FILE_SYNC_CHART "file_sync" +#define NETDATA_EBPF_FILE_SEGMENT_CHART "file_segment" +#define NETDATA_EBPF_SYNC_SUBMENU "synchronization (eBPF)" + +#define NETDATA_SYSCALLS_SYNC "sync" +#define NETDATA_SYSCALLS_SYNCFS "syncfs" +#define NETDATA_SYSCALLS_MSYNC "msync" +#define NETDATA_SYSCALLS_FSYNC "fsync" +#define NETDATA_SYSCALLS_FDATASYNC "fdatasync" +#define NETDATA_SYSCALLS_SYNC_FILE_RANGE "sync_file_range" + +#define NETDATA_EBPF_SYNC_SLEEP_MS 800000ULL + +// configuration file +#define NETDATA_SYNC_CONFIG_FILE "sync.conf" +#define NETDATA_SYNC_CONFIG_NAME "syscalls" + +typedef enum sync_syscalls_index { + NETDATA_SYNC_SYNC_IDX, + NETDATA_SYNC_SYNCFS_IDX, + NETDATA_SYNC_MSYNC_IDX, + NETDATA_SYNC_FSYNC_IDX, + NETDATA_SYNC_FDATASYNC_IDX, + NETDATA_SYNC_SYNC_FILE_RANGE_IDX, + + NETDATA_SYNC_IDX_END +} sync_syscalls_index_t; + +enum netdata_sync_charts { + NETDATA_SYNC_CALL, + + // Keep this as last and don't skip numbers as it is used as element counter + NETDATA_SYNC_END +}; + +enum netdata_sync_table { + NETDATA_SYNC_GLOBAL_TABLE +}; + +void *ebpf_sync_thread(void *ptr); +extern struct config sync_config; +extern netdata_ebpf_targets_t sync_targets[]; + +#endif /* NETDATA_EBPF_SYNC_H */ diff --git a/src/collectors/ebpf.plugin/ebpf_unittest.c b/src/collectors/ebpf.plugin/ebpf_unittest.c new file mode 100644 index 000000000..11b449e03 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_unittest.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ebpf_unittest.h" + +ebpf_module_t test_em; + +/** + * Initialize structure + * + * Initialize structure used to run unittests + */ +void ebpf_ut_initialize_structure(netdata_run_mode_t mode) +{ + memset(&test_em, 0, sizeof(ebpf_module_t)); + test_em.info.thread_name = strdupz("process"); + test_em.info.config_name = test_em.info.thread_name; + test_em.kernels = NETDATA_V3_10 | NETDATA_V4_14 | NETDATA_V4_16 | NETDATA_V4_18 | NETDATA_V5_4 | NETDATA_V5_10 | + NETDATA_V5_14; + test_em.pid_map_size = ND_EBPF_DEFAULT_PID_SIZE; + test_em.apps_level = NETDATA_APPS_LEVEL_REAL_PARENT; + test_em.mode = mode; +} + +/** + * Clean UP Memory + * + * Clean up allocated data during unit test; + */ +void ebpf_ut_cleanup_memory() +{ + freez((void *)test_em.info.thread_name); +} + +/** + * Load Binary + * + * Test load of legacy eBPF programs. + * + * @return It returns 0 on success and -1 otherwise. + */ +static int ebpf_ut_load_binary() +{ + test_em.probe_links = ebpf_load_program(ebpf_plugin_dir, &test_em, running_on_kernel, isrh, &test_em.objects); + if (!test_em.probe_links) + return -1; + + ebpf_unload_legacy_code(test_em.objects, test_em.probe_links); + + return 0; +} + +/** + * Load Real Binary + * + * Load an existent binary inside plugin directory. + * + * @return It returns 0 on success and -1 otherwise. + */ +int ebpf_ut_load_real_binary() +{ + return ebpf_ut_load_binary(); +} +/** + * Load fake Binary + * + * Try to load a binary not generated by netdata. + * + * @return It returns 0 on success and -1 otherwise. The success for this function means we could work properly with + * expected fails. + */ +int ebpf_ut_load_fake_binary() +{ + const char *original = test_em.info.thread_name; + + test_em.info.thread_name = strdupz("I_am_not_here"); + int ret = ebpf_ut_load_binary(); + + ebpf_ut_cleanup_memory(); + + test_em.info.thread_name = original; + + return !ret; +} diff --git a/src/collectors/ebpf.plugin/ebpf_unittest.h b/src/collectors/ebpf.plugin/ebpf_unittest.h new file mode 100644 index 000000000..429cbe628 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_unittest.h @@ -0,0 +1,10 @@ +#ifndef NETDATA_EBPF_PLUGIN_UNITTEST_H_ +# define NETDATA_EBPF_PLUGIN_UNITTEST_H_ 1 + +#include "ebpf.h" + +void ebpf_ut_initialize_structure(netdata_run_mode_t mode); +int ebpf_ut_load_real_binary(); +int ebpf_ut_load_fake_binary(); +void ebpf_ut_cleanup_memory(); +#endif diff --git a/src/collectors/ebpf.plugin/ebpf_vfs.c b/src/collectors/ebpf.plugin/ebpf_vfs.c new file mode 100644 index 000000000..eea27192e --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_vfs.c @@ -0,0 +1,2629 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "ebpf.h" +#include "ebpf_vfs.h" + +static char *vfs_dimension_names[NETDATA_KEY_PUBLISH_VFS_END] = { "delete", "read", "write", + "fsync", "open", "create" }; +static char *vfs_id_names[NETDATA_KEY_PUBLISH_VFS_END] = { "vfs_unlink", "vfs_read", "vfs_write", + "vfs_fsync", "vfs_open", "vfs_create"}; + +static netdata_idx_t *vfs_hash_values = NULL; +static netdata_syscall_stat_t vfs_aggregated_data[NETDATA_KEY_PUBLISH_VFS_END]; +static netdata_publish_syscall_t vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_END]; +netdata_publish_vfs_t *vfs_vector = NULL; + +static ebpf_local_maps_t vfs_maps[] = {{.name = "tbl_vfs_pid", .internal_input = ND_EBPF_DEFAULT_PID_SIZE, + .user_input = 0, .type = NETDATA_EBPF_MAP_RESIZABLE | NETDATA_EBPF_MAP_PID, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_HASH +#endif + }, + {.name = "tbl_vfs_stats", .internal_input = NETDATA_VFS_COUNTER, + .user_input = 0, .type = NETDATA_EBPF_MAP_STATIC, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }, + {.name = "vfs_ctrl", .internal_input = NETDATA_CONTROLLER_END, + .user_input = 0, + .type = NETDATA_EBPF_MAP_CONTROLLER, + .map_fd = ND_EBPF_MAP_FD_NOT_INITIALIZED, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }, + {.name = NULL, .internal_input = 0, .user_input = 0, +#ifdef LIBBPF_MAJOR_VERSION + .map_type = BPF_MAP_TYPE_PERCPU_ARRAY +#endif + }}; + +struct netdata_static_thread ebpf_read_vfs = { + .name = "EBPF_READ_VFS", + .config_section = NULL, + .config_name = NULL, + .env_name = NULL, + .enabled = 1, + .thread = NULL, + .init_routine = NULL, + .start_routine = NULL +}; + +struct config vfs_config = { .first_section = NULL, + .last_section = NULL, + .mutex = NETDATA_MUTEX_INITIALIZER, + .index = { .avl_tree = { .root = NULL, .compar = appconfig_section_compare }, + .rwlock = AVL_LOCK_INITIALIZER } }; + +netdata_ebpf_targets_t vfs_targets[] = { {.name = "vfs_write", .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = "vfs_writev", .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = "vfs_read", .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = "vfs_readv", .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = "vfs_unlink", .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = "vfs_fsync", .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = "vfs_open", .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = "vfs_create", .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = "release_task", .mode = EBPF_LOAD_TRAMPOLINE}, + {.name = NULL, .mode = EBPF_LOAD_TRAMPOLINE}}; + +#ifdef LIBBPF_MAJOR_VERSION +/** + * Disable probe + * + * Disable all probes to use exclusively another method. + * + * @param obj is the main structure for bpf objects + */ +static void ebpf_vfs_disable_probes(struct vfs_bpf *obj) +{ + bpf_program__set_autoload(obj->progs.netdata_vfs_write_kprobe, false); + bpf_program__set_autoload(obj->progs.netdata_vfs_write_kretprobe, false); + bpf_program__set_autoload(obj->progs.netdata_vfs_writev_kprobe, false); + bpf_program__set_autoload(obj->progs.netdata_vfs_writev_kretprobe, false); + bpf_program__set_autoload(obj->progs.netdata_vfs_read_kprobe, false); + bpf_program__set_autoload(obj->progs.netdata_vfs_read_kretprobe, false); + bpf_program__set_autoload(obj->progs.netdata_vfs_readv_kprobe, false); + bpf_program__set_autoload(obj->progs.netdata_vfs_readv_kretprobe, false); + bpf_program__set_autoload(obj->progs.netdata_vfs_unlink_kprobe, false); + bpf_program__set_autoload(obj->progs.netdata_vfs_unlink_kretprobe, false); + bpf_program__set_autoload(obj->progs.netdata_vfs_fsync_kprobe, false); + bpf_program__set_autoload(obj->progs.netdata_vfs_fsync_kretprobe, false); + bpf_program__set_autoload(obj->progs.netdata_vfs_open_kprobe, false); + bpf_program__set_autoload(obj->progs.netdata_vfs_open_kretprobe, false); + bpf_program__set_autoload(obj->progs.netdata_vfs_create_kprobe, false); + bpf_program__set_autoload(obj->progs.netdata_vfs_create_kretprobe, false); +} + +/* + * Disable trampoline + * + * Disable all trampoline to use exclusively another method. + * + * @param obj is the main structure for bpf objects. + */ +static void ebpf_vfs_disable_trampoline(struct vfs_bpf *obj) +{ + bpf_program__set_autoload(obj->progs.netdata_vfs_write_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_vfs_write_fexit, false); + bpf_program__set_autoload(obj->progs.netdata_vfs_writev_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_vfs_writev_fexit, false); + bpf_program__set_autoload(obj->progs.netdata_vfs_read_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_vfs_read_fexit, false); + bpf_program__set_autoload(obj->progs.netdata_vfs_readv_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_vfs_readv_fexit, false); + bpf_program__set_autoload(obj->progs.netdata_vfs_unlink_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_vfs_fsync_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_vfs_fsync_fexit, false); + bpf_program__set_autoload(obj->progs.netdata_vfs_open_fentry, false); + bpf_program__set_autoload(obj->progs.netdata_vfs_open_fexit, false); + bpf_program__set_autoload(obj->progs.netdata_vfs_create_fentry, false); +} + +/** + * Set trampoline target + * + * Set the targets we will monitor. + * + * @param obj is the main structure for bpf objects. + */ +static void ebpf_vfs_set_trampoline_target(struct vfs_bpf *obj) +{ + bpf_program__set_attach_target(obj->progs.netdata_vfs_write_fentry, 0, vfs_targets[NETDATA_EBPF_VFS_WRITE].name); + + bpf_program__set_attach_target(obj->progs.netdata_vfs_write_fexit, 0, vfs_targets[NETDATA_EBPF_VFS_WRITE].name); + + bpf_program__set_attach_target(obj->progs.netdata_vfs_writev_fentry, 0, vfs_targets[NETDATA_EBPF_VFS_WRITEV].name); + + bpf_program__set_attach_target(obj->progs.netdata_vfs_writev_fexit, 0, vfs_targets[NETDATA_EBPF_VFS_WRITEV].name); + + bpf_program__set_attach_target(obj->progs.netdata_vfs_read_fentry, 0, vfs_targets[NETDATA_EBPF_VFS_READ].name); + + bpf_program__set_attach_target(obj->progs.netdata_vfs_read_fexit, 0, vfs_targets[NETDATA_EBPF_VFS_READ].name); + + bpf_program__set_attach_target(obj->progs.netdata_vfs_readv_fentry, 0, vfs_targets[NETDATA_EBPF_VFS_READV].name); + + bpf_program__set_attach_target(obj->progs.netdata_vfs_readv_fexit, 0, vfs_targets[NETDATA_EBPF_VFS_READV].name); + + bpf_program__set_attach_target(obj->progs.netdata_vfs_unlink_fentry, 0, vfs_targets[NETDATA_EBPF_VFS_UNLINK].name); + + bpf_program__set_attach_target(obj->progs.netdata_vfs_fsync_fentry, 0, vfs_targets[NETDATA_EBPF_VFS_FSYNC].name); + + bpf_program__set_attach_target(obj->progs.netdata_vfs_fsync_fexit, 0, vfs_targets[NETDATA_EBPF_VFS_FSYNC].name); + + bpf_program__set_attach_target(obj->progs.netdata_vfs_open_fentry, 0, vfs_targets[NETDATA_EBPF_VFS_OPEN].name); + + bpf_program__set_attach_target(obj->progs.netdata_vfs_open_fexit, 0, vfs_targets[NETDATA_EBPF_VFS_OPEN].name); + + bpf_program__set_attach_target(obj->progs.netdata_vfs_create_fentry, 0, vfs_targets[NETDATA_EBPF_VFS_CREATE].name); +} + +/** + * Attach Probe + * + * Attach probes to target + * + * @param obj is the main structure for bpf objects. + * + * @return It returns 0 on success and -1 otherwise. + */ +static int ebpf_vfs_attach_probe(struct vfs_bpf *obj) +{ + obj->links.netdata_vfs_write_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_vfs_write_kprobe, false, + vfs_targets[NETDATA_EBPF_VFS_WRITE].name); + long ret = libbpf_get_error(obj->links.netdata_vfs_write_kprobe); + if (ret) + return -1; + + obj->links.netdata_vfs_write_kretprobe = bpf_program__attach_kprobe(obj->progs.netdata_vfs_write_kretprobe, true, + vfs_targets[NETDATA_EBPF_VFS_WRITE].name); + ret = libbpf_get_error(obj->links.netdata_vfs_write_kretprobe); + if (ret) + return -1; + + obj->links.netdata_vfs_writev_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_vfs_writev_kprobe, false, + vfs_targets[NETDATA_EBPF_VFS_WRITEV].name); + ret = libbpf_get_error(obj->links.netdata_vfs_writev_kprobe); + if (ret) + return -1; + + obj->links.netdata_vfs_writev_kretprobe = bpf_program__attach_kprobe(obj->progs.netdata_vfs_writev_kretprobe, true, + vfs_targets[NETDATA_EBPF_VFS_WRITEV].name); + ret = libbpf_get_error(obj->links.netdata_vfs_writev_kretprobe); + if (ret) + return -1; + + obj->links.netdata_vfs_read_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_vfs_read_kprobe, false, + vfs_targets[NETDATA_EBPF_VFS_READ].name); + ret = libbpf_get_error(obj->links.netdata_vfs_read_kprobe); + if (ret) + return -1; + + obj->links.netdata_vfs_read_kretprobe = bpf_program__attach_kprobe(obj->progs.netdata_vfs_read_kretprobe, true, + vfs_targets[NETDATA_EBPF_VFS_READ].name); + ret = libbpf_get_error(obj->links.netdata_vfs_read_kretprobe); + if (ret) + return -1; + + obj->links.netdata_vfs_readv_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_vfs_readv_kprobe, false, + vfs_targets[NETDATA_EBPF_VFS_READV].name); + ret = libbpf_get_error(obj->links.netdata_vfs_readv_kprobe); + if (ret) + return -1; + + obj->links.netdata_vfs_readv_kretprobe = bpf_program__attach_kprobe(obj->progs.netdata_vfs_readv_kretprobe, true, + vfs_targets[NETDATA_EBPF_VFS_READV].name); + ret = libbpf_get_error(obj->links.netdata_vfs_readv_kretprobe); + if (ret) + return -1; + + obj->links.netdata_vfs_unlink_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_vfs_unlink_kprobe, false, + vfs_targets[NETDATA_EBPF_VFS_UNLINK].name); + ret = libbpf_get_error(obj->links.netdata_vfs_unlink_kprobe); + if (ret) + return -1; + + obj->links.netdata_vfs_unlink_kretprobe = bpf_program__attach_kprobe(obj->progs.netdata_vfs_unlink_kretprobe, true, + vfs_targets[NETDATA_EBPF_VFS_UNLINK].name); + ret = libbpf_get_error(obj->links.netdata_vfs_unlink_kretprobe); + if (ret) + return -1; + + obj->links.netdata_vfs_fsync_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_vfs_fsync_kprobe, false, + vfs_targets[NETDATA_EBPF_VFS_FSYNC].name); + ret = libbpf_get_error(obj->links.netdata_vfs_fsync_kprobe); + if (ret) + return -1; + + obj->links.netdata_vfs_fsync_kretprobe = bpf_program__attach_kprobe(obj->progs.netdata_vfs_fsync_kretprobe, true, + vfs_targets[NETDATA_EBPF_VFS_FSYNC].name); + ret = libbpf_get_error(obj->links.netdata_vfs_fsync_kretprobe); + if (ret) + return -1; + + obj->links.netdata_vfs_open_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_vfs_open_kprobe, false, + vfs_targets[NETDATA_EBPF_VFS_OPEN].name); + ret = libbpf_get_error(obj->links.netdata_vfs_open_kprobe); + if (ret) + return -1; + + obj->links.netdata_vfs_open_kretprobe = bpf_program__attach_kprobe(obj->progs.netdata_vfs_open_kretprobe, true, + vfs_targets[NETDATA_EBPF_VFS_OPEN].name); + ret = libbpf_get_error(obj->links.netdata_vfs_open_kretprobe); + if (ret) + return -1; + + obj->links.netdata_vfs_create_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_vfs_create_kprobe, false, + vfs_targets[NETDATA_EBPF_VFS_CREATE].name); + ret = libbpf_get_error(obj->links.netdata_vfs_create_kprobe); + if (ret) + return -1; + + obj->links.netdata_vfs_create_kretprobe = bpf_program__attach_kprobe(obj->progs.netdata_vfs_create_kretprobe, true, + vfs_targets[NETDATA_EBPF_VFS_CREATE].name); + ret = libbpf_get_error(obj->links.netdata_vfs_create_kretprobe); + if (ret) + return -1; + + obj->links.netdata_vfs_fsync_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_vfs_fsync_kprobe, false, + vfs_targets[NETDATA_EBPF_VFS_FSYNC].name); + ret = libbpf_get_error(obj->links.netdata_vfs_fsync_kprobe); + if (ret) + return -1; + + obj->links.netdata_vfs_fsync_kretprobe = bpf_program__attach_kprobe(obj->progs.netdata_vfs_fsync_kretprobe, true, + vfs_targets[NETDATA_EBPF_VFS_FSYNC].name); + ret = libbpf_get_error(obj->links.netdata_vfs_fsync_kretprobe); + if (ret) + return -1; + + obj->links.netdata_vfs_open_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_vfs_open_kprobe, false, + vfs_targets[NETDATA_EBPF_VFS_OPEN].name); + ret = libbpf_get_error(obj->links.netdata_vfs_open_kprobe); + if (ret) + return -1; + + obj->links.netdata_vfs_open_kretprobe = bpf_program__attach_kprobe(obj->progs.netdata_vfs_open_kretprobe, true, + vfs_targets[NETDATA_EBPF_VFS_OPEN].name); + ret = libbpf_get_error(obj->links.netdata_vfs_open_kretprobe); + if (ret) + return -1; + + obj->links.netdata_vfs_create_kprobe = bpf_program__attach_kprobe(obj->progs.netdata_vfs_create_kprobe, false, + vfs_targets[NETDATA_EBPF_VFS_CREATE].name); + ret = libbpf_get_error(obj->links.netdata_vfs_create_kprobe); + if (ret) + return -1; + + obj->links.netdata_vfs_create_kretprobe = bpf_program__attach_kprobe(obj->progs.netdata_vfs_create_kretprobe, true, + vfs_targets[NETDATA_EBPF_VFS_CREATE].name); + ret = libbpf_get_error(obj->links.netdata_vfs_create_kretprobe); + if (ret) + return -1; + + return 0; +} + +/** + * Adjust Size + * + * Resize maps according input from users. + * + * @param obj is the main structure for bpf objects. + * @param em structure with configuration + */ +static void ebpf_vfs_adjust_map(struct vfs_bpf *obj, ebpf_module_t *em) +{ + ebpf_update_map_size(obj->maps.tbl_vfs_pid, &vfs_maps[NETDATA_VFS_PID], + em, bpf_map__name(obj->maps.tbl_vfs_pid)); + + ebpf_update_map_type(obj->maps.tbl_vfs_pid, &vfs_maps[NETDATA_VFS_PID]); + ebpf_update_map_type(obj->maps.tbl_vfs_stats, &vfs_maps[NETDATA_VFS_ALL]); + ebpf_update_map_type(obj->maps.vfs_ctrl, &vfs_maps[NETDATA_VFS_CTRL]); +} + +/** + * Set hash tables + * + * Set the values for maps according the value given by kernel. + * + * @param obj is the main structure for bpf objects. + */ +static void ebpf_vfs_set_hash_tables(struct vfs_bpf *obj) +{ + vfs_maps[NETDATA_VFS_ALL].map_fd = bpf_map__fd(obj->maps.tbl_vfs_stats); + vfs_maps[NETDATA_VFS_PID].map_fd = bpf_map__fd(obj->maps.tbl_vfs_pid); + vfs_maps[NETDATA_VFS_CTRL].map_fd = bpf_map__fd(obj->maps.vfs_ctrl); +} + +/** + * Load and attach + * + * Load and attach the eBPF code in kernel. + * + * @param obj is the main structure for bpf objects. + * @param em structure with configuration + * + * @return it returns 0 on success and -1 otherwise + */ +static inline int ebpf_vfs_load_and_attach(struct vfs_bpf *obj, ebpf_module_t *em) +{ + netdata_ebpf_targets_t *mt = em->targets; + netdata_ebpf_program_loaded_t test = mt[NETDATA_EBPF_VFS_WRITE].mode; + + if (test == EBPF_LOAD_TRAMPOLINE) { + ebpf_vfs_disable_probes(obj); + + ebpf_vfs_set_trampoline_target(obj); + } else { + ebpf_vfs_disable_trampoline(obj); + } + + ebpf_vfs_adjust_map(obj, em); + + int ret = vfs_bpf__load(obj); + if (ret) { + return ret; + } + + ret = (test == EBPF_LOAD_TRAMPOLINE) ? vfs_bpf__attach(obj) : ebpf_vfs_attach_probe(obj); + if (!ret) { + ebpf_vfs_set_hash_tables(obj); + + ebpf_update_controller(vfs_maps[NETDATA_VFS_CTRL].map_fd, em); + } + + return ret; +} +#endif + +/***************************************************************** + * + * FUNCTIONS TO CLOSE THE THREAD + * + *****************************************************************/ + +static void ebpf_obsolete_specific_vfs_charts(char *type, ebpf_module_t *em); + +/** + * Obsolete services + * + * Obsolete all service charts created + * + * @param em a pointer to `struct ebpf_module` + */ +static void ebpf_obsolete_vfs_services(ebpf_module_t *em, char *id) +{ + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_SYSCALL_APPS_FILE_DELETED, + "Files deleted", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_SYSTEMD_VFS_UNLINK_CONTEXT, + 20065, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_SYSCALL_APPS_VFS_WRITE_CALLS, + "Write to disk", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_SYSTEMD_VFS_WRITE_CONTEXT, + 20066, + em->update_every); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_SYSCALL_APPS_VFS_WRITE_CALLS_ERROR, + "Fails to write", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_SYSTEMD_VFS_WRITE_ERROR_CONTEXT, + 20067, + em->update_every); + } + + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_SYSCALL_APPS_VFS_READ_CALLS, + "Read from disk", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_SYSTEMD_VFS_READ_CONTEXT, + 20068, + em->update_every); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_SYSCALL_APPS_VFS_READ_CALLS_ERROR, + "Fails to read", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_SYSTEMD_VFS_READ_ERROR_CONTEXT, + 20069, + em->update_every); + } + + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_SYSCALL_APPS_VFS_WRITE_BYTES, + "Bytes written on disk", + EBPF_COMMON_UNITS_BYTES, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_SYSTEMD_VFS_WRITE_BYTES_CONTEXT, + 20070, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_SYSCALL_APPS_VFS_READ_BYTES, + "Bytes read from disk", + EBPF_COMMON_UNITS_BYTES, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_SYSTEMD_VFS_READ_BYTES_CONTEXT, + 20071, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_SYSCALL_APPS_VFS_FSYNC, + "Calls to vfs_fsync.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_SYSTEMD_VFS_FSYNC_CONTEXT, + 20072, + em->update_every); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_SYSCALL_APPS_VFS_FSYNC_CALLS_ERROR, + "Sync error", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_SYSTEMD_VFS_FSYNC_ERROR_CONTEXT, + 20073, + em->update_every); + } + + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_SYSCALL_APPS_VFS_OPEN, + "Calls to vfs_open.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_SYSTEMD_VFS_OPEN_CONTEXT, + 20074, + em->update_every); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_SYSCALL_APPS_VFS_OPEN_CALLS_ERROR, + "Open error", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_SYSTEMD_VFS_OPEN_ERROR_CONTEXT, + 20075, + em->update_every); + } + + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_SYSCALL_APPS_VFS_CREATE, + "Calls to vfs_create.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_SYSTEMD_VFS_OPEN_ERROR_CONTEXT, + 20076, + em->update_every); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_obsolete(NETDATA_SERVICE_FAMILY, + id, + NETDATA_SYSCALL_APPS_VFS_CREATE_CALLS_ERROR, + "Create error", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + NETDATA_SYSTEMD_VFS_CREATE_ERROR_CONTEXT, + 20077, + em->update_every); + } +} + +/** + * Obsolete cgroup chart + * + * Send obsolete for all charts created before to close. + * + * @param em a pointer to `struct ebpf_module` + */ +static inline void ebpf_obsolete_vfs_cgroup_charts(ebpf_module_t *em) { + pthread_mutex_lock(&mutex_cgroup_shm); + + ebpf_cgroup_target_t *ect; + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + if (ect->systemd) { + ebpf_obsolete_vfs_services(em, ect->name); + + continue; + } + + ebpf_obsolete_specific_vfs_charts(ect->name, em); + } + pthread_mutex_unlock(&mutex_cgroup_shm); +} + +/** + * Obsolette apps charts + * + * Obsolete apps charts. + * + * @param em a pointer to the structure with the default values. + */ +void ebpf_obsolete_vfs_apps_charts(struct ebpf_module *em) +{ + int order = 20275; + struct ebpf_target *w; + int update_every = em->update_every; + pthread_mutex_lock(&collect_data_mutex); + for (w = apps_groups_root_target; w; w = w->next) { + if (unlikely(!(w->charts_created & (1<<EBPF_MODULE_VFS_IDX)))) + continue; + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_vfs_unlink", + "Files deleted.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_vfs_unlink", + order++, + update_every); + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_vfs_write", + "Write to disk.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_vfs_write", + order++, + update_every); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_vfs_write_error", + "Fails to write.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_vfs_write_error", + order++, + update_every); + } + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_vfs_read", + "Read from disk.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_vfs_read", + order++, + update_every); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_vfs_read_error", + "Fails to read.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_vfs_read_error", + order++, + update_every); + } + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_vfs_write_bytes", + "Bytes written on disk.", + EBPF_COMMON_UNITS_BYTES, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_vfs_write_bytes", + order++, + update_every); + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_vfs_read_bytes", + "Bytes read from disk.", + EBPF_COMMON_UNITS_BYTES, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_vfs_read_bytes", + order++, + update_every); + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_vfs_fsync", + "Calls to vfs_fsync.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_vfs_fsync", + order++, + update_every); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_vfs_fsync_error", + "Fails to sync.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_vfs_fsync_error", + order++, + update_every); + } + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_vfs_open", + "Calls to vfs_open.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_vfs_open", + order++, + update_every); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_vfs_open_error", + "Fails to open.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_vfs_open_error", + order++, + update_every); + } + + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_vfs_create", + "Calls to vfs_create.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_vfs_create", + order++, + update_every); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_obsolete(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_vfs_create_error", + "Fails to create.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_vfs_create_error", + order++, + update_every); + } + w->charts_created &= ~(1<<EBPF_MODULE_VFS_IDX); + } + pthread_mutex_unlock(&collect_data_mutex); +} + +/** + * Obsolete global + * + * Obsolete global charts created by thread. + * + * @param em a pointer to `struct ebpf_module` + */ +static void ebpf_obsolete_vfs_global(ebpf_module_t *em) +{ + ebpf_write_chart_obsolete(NETDATA_FILESYSTEM_FAMILY, + NETDATA_VFS_FILE_CLEAN_COUNT, + "", + "Remove files", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, + "filesystem.vfs_deleted_objects", + NETDATA_CHART_PRIO_FILESYSTEM_VFS_CLEAN, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_FILESYSTEM_FAMILY, + NETDATA_VFS_FILE_IO_COUNT, + "", + "Calls to IO", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, + "filesystem.vfs_io", + NETDATA_CHART_PRIO_FILESYSTEM_VFS_IO_COUNT, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_FILESYSTEM_FAMILY, + NETDATA_VFS_IO_FILE_BYTES, + "", + "Bytes written and read", + EBPF_COMMON_UNITS_BYTES, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, + "filesystem.vfs_io_bytes", + NETDATA_CHART_PRIO_FILESYSTEM_VFS_IO_BYTES, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_FILESYSTEM_FAMILY, + NETDATA_VFS_FSYNC, + "", + "Calls to vfs_fsync.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, + "filesystem.vfs_fsync", + NETDATA_CHART_PRIO_FILESYSTEM_VFS_IO_FSYNC, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_FILESYSTEM_FAMILY, + NETDATA_VFS_OPEN, + "", + "Calls to vfs_open.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, + "filesystem.vfs_open", + NETDATA_CHART_PRIO_FILESYSTEM_VFS_IO_OPEN, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_FILESYSTEM_FAMILY, + NETDATA_VFS_CREATE, + "", + "Calls to vfs_create.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, + "filesystem.vfs_create", + NETDATA_CHART_PRIO_FILESYSTEM_VFS_IO_CREATE, + em->update_every); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_obsolete(NETDATA_FILESYSTEM_FAMILY, + NETDATA_VFS_FILE_ERR_COUNT, + "", + "Fails to write or read", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, + "filesystem.vfs_io_error", + NETDATA_CHART_PRIO_FILESYSTEM_VFS_IO_EBYTES, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_FILESYSTEM_FAMILY, + NETDATA_VFS_OPEN_ERR, + "", + "Fails to open a file", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, + "filesystem.vfs_open_error", + NETDATA_CHART_PRIO_FILESYSTEM_VFS_IO_EOPEN, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_FILESYSTEM_FAMILY, + NETDATA_VFS_FSYNC_ERR, + "", + "Fails to synchronize", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, + "filesystem.vfs_fsync_error", + NETDATA_CHART_PRIO_FILESYSTEM_VFS_IO_EFSYNC, + em->update_every); + + ebpf_write_chart_obsolete(NETDATA_FILESYSTEM_FAMILY, + NETDATA_VFS_CREATE_ERR, + "", + "Fails to create a file.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, + "filesystem.vfs_create_error", + NETDATA_CHART_PRIO_FILESYSTEM_VFS_IO_ECREATE, + em->update_every); + } +} + +/** + * Exit + * + * Cancel thread and exit. + * + * @param ptr thread data. +**/ +static void ebpf_vfs_exit(void *pptr) +{ + ebpf_module_t *em = CLEANUP_FUNCTION_GET_PTR(pptr); + if(!em) return; + + if (ebpf_read_vfs.thread) + nd_thread_signal_cancel(ebpf_read_vfs.thread); + + if (em->enabled == NETDATA_THREAD_EBPF_FUNCTION_RUNNING) { + pthread_mutex_lock(&lock); + if (em->cgroup_charts) { + ebpf_obsolete_vfs_cgroup_charts(em); + fflush(stdout); + } + + if (em->apps_charts & NETDATA_EBPF_APPS_FLAG_CHART_CREATED) { + ebpf_obsolete_vfs_apps_charts(em); + } + + ebpf_obsolete_vfs_global(em); + + fflush(stdout); + pthread_mutex_unlock(&lock); + } + + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps, EBPF_ACTION_STAT_REMOVE); + +#ifdef LIBBPF_MAJOR_VERSION + if (vfs_bpf_obj) { + vfs_bpf__destroy(vfs_bpf_obj); + vfs_bpf_obj = NULL; + } +#endif + if (em->objects) { + ebpf_unload_legacy_code(em->objects, em->probe_links); + em->objects = NULL; + em->probe_links = NULL; + } + + pthread_mutex_lock(&ebpf_exit_cleanup); + em->enabled = NETDATA_THREAD_EBPF_STOPPED; + ebpf_update_stats(&plugin_statistics, em); + pthread_mutex_unlock(&ebpf_exit_cleanup); +} + +/***************************************************************** + * + * FUNCTIONS WITH THE MAIN LOOP + * + *****************************************************************/ + +/** + * Send data to Netdata calling auxiliary functions. + * + * @param em the structure with thread information +*/ +static void ebpf_vfs_send_data(ebpf_module_t *em) +{ + netdata_publish_vfs_common_t pvc; + + pvc.write = (long)vfs_aggregated_data[NETDATA_KEY_PUBLISH_VFS_WRITE].bytes; + pvc.read = (long)vfs_aggregated_data[NETDATA_KEY_PUBLISH_VFS_READ].bytes; + + write_count_chart(NETDATA_VFS_FILE_CLEAN_COUNT, NETDATA_FILESYSTEM_FAMILY, + &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_UNLINK], 1); + + write_count_chart(NETDATA_VFS_FILE_IO_COUNT, NETDATA_FILESYSTEM_FAMILY, + &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_READ], 2); + + if (em->mode < MODE_ENTRY) { + write_err_chart(NETDATA_VFS_FILE_ERR_COUNT, NETDATA_FILESYSTEM_FAMILY, + &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_READ], 2); + } + + write_io_chart(NETDATA_VFS_IO_FILE_BYTES, NETDATA_FILESYSTEM_FAMILY, vfs_id_names[NETDATA_KEY_PUBLISH_VFS_WRITE], + (long long)pvc.write, vfs_id_names[NETDATA_KEY_PUBLISH_VFS_READ], (long long)pvc.read); + + write_count_chart(NETDATA_VFS_FSYNC, NETDATA_FILESYSTEM_FAMILY, + &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_FSYNC], 1); + + if (em->mode < MODE_ENTRY) { + write_err_chart(NETDATA_VFS_FSYNC_ERR, NETDATA_FILESYSTEM_FAMILY, + &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_FSYNC], 1); + } + + write_count_chart(NETDATA_VFS_OPEN, NETDATA_FILESYSTEM_FAMILY, + &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_OPEN], 1); + + if (em->mode < MODE_ENTRY) { + write_err_chart(NETDATA_VFS_OPEN_ERR, NETDATA_FILESYSTEM_FAMILY, + &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_OPEN], 1); + } + + write_count_chart(NETDATA_VFS_CREATE, NETDATA_FILESYSTEM_FAMILY, + &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_CREATE], 1); + + if (em->mode < MODE_ENTRY) { + write_err_chart( + NETDATA_VFS_CREATE_ERR, + NETDATA_FILESYSTEM_FAMILY, + &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_CREATE], + 1); + } +} + +/** + * Read the hash table and store data to allocated vectors. + * + * @param stats vector used to read data from control table. + * @param maps_per_core do I need to read all cores? + */ +static void ebpf_vfs_read_global_table(netdata_idx_t *stats, int maps_per_core) +{ + netdata_idx_t res[NETDATA_VFS_COUNTER]; + ebpf_read_global_table_stats(res, + vfs_hash_values, + vfs_maps[NETDATA_VFS_ALL].map_fd, + maps_per_core, + NETDATA_KEY_CALLS_VFS_WRITE, + NETDATA_VFS_COUNTER); + + ebpf_read_global_table_stats(stats, + vfs_hash_values, + vfs_maps[NETDATA_VFS_CTRL].map_fd, + maps_per_core, + NETDATA_CONTROLLER_PID_TABLE_ADD, + NETDATA_CONTROLLER_END); + + vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_UNLINK].ncall = res[NETDATA_KEY_CALLS_VFS_UNLINK]; + vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_READ].ncall = res[NETDATA_KEY_CALLS_VFS_READ] + + res[NETDATA_KEY_CALLS_VFS_READV]; + vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_WRITE].ncall = res[NETDATA_KEY_CALLS_VFS_WRITE] + + res[NETDATA_KEY_CALLS_VFS_WRITEV]; + vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_FSYNC].ncall = res[NETDATA_KEY_CALLS_VFS_FSYNC]; + vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_OPEN].ncall = res[NETDATA_KEY_CALLS_VFS_OPEN]; + vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_CREATE].ncall = res[NETDATA_KEY_CALLS_VFS_CREATE]; + + vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_UNLINK].nerr = res[NETDATA_KEY_ERROR_VFS_UNLINK]; + vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_READ].nerr = res[NETDATA_KEY_ERROR_VFS_READ] + + res[NETDATA_KEY_ERROR_VFS_READV]; + vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_WRITE].nerr = res[NETDATA_KEY_ERROR_VFS_WRITE] + + res[NETDATA_KEY_ERROR_VFS_WRITEV]; + vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_FSYNC].nerr = res[NETDATA_KEY_ERROR_VFS_FSYNC]; + vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_OPEN].nerr = res[NETDATA_KEY_ERROR_VFS_OPEN]; + vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_CREATE].nerr = res[NETDATA_KEY_ERROR_VFS_CREATE]; + + vfs_aggregated_data[NETDATA_KEY_PUBLISH_VFS_WRITE].bytes = (uint64_t)res[NETDATA_KEY_BYTES_VFS_WRITE] + + (uint64_t)res[NETDATA_KEY_BYTES_VFS_WRITEV]; + vfs_aggregated_data[NETDATA_KEY_PUBLISH_VFS_READ].bytes = (uint64_t)res[NETDATA_KEY_BYTES_VFS_READ] + + (uint64_t)res[NETDATA_KEY_BYTES_VFS_READV]; +} + +/** + * Sum PIDs + * + * Sum values for all targets. + * + * @param swap output structure + * @param root link list with structure to be used + */ +static void ebpf_vfs_sum_pids(netdata_publish_vfs_t *vfs, struct ebpf_pid_on_target *root) +{ + netdata_publish_vfs_t accumulator; + memset(&accumulator, 0, sizeof(accumulator)); + + while (root) { + int32_t pid = root->pid; + ebpf_pid_stat_t *local_pid = ebpf_get_pid_entry(pid, 0); + if (local_pid) { + netdata_publish_vfs_t *w = &local_pid->vfs; + accumulator.write_call += w->write_call; + accumulator.writev_call += w->writev_call; + accumulator.read_call += w->read_call; + accumulator.readv_call += w->readv_call; + accumulator.unlink_call += w->unlink_call; + accumulator.fsync_call += w->fsync_call; + accumulator.open_call += w->open_call; + accumulator.create_call += w->create_call; + + accumulator.write_bytes += w->write_bytes; + accumulator.writev_bytes += w->writev_bytes; + accumulator.read_bytes += w->read_bytes; + accumulator.readv_bytes += w->readv_bytes; + + accumulator.write_err += w->write_err; + accumulator.writev_err += w->writev_err; + accumulator.read_err += w->read_err; + accumulator.readv_err += w->readv_err; + accumulator.unlink_err += w->unlink_err; + accumulator.fsync_err += w->fsync_err; + accumulator.open_err += w->open_err; + accumulator.create_err += w->create_err; + } + root = root->next; + } + + // These conditions were added, because we are using incremental algorithm + vfs->write_call = (accumulator.write_call >= vfs->write_call) ? accumulator.write_call : vfs->write_call; + vfs->writev_call = (accumulator.writev_call >= vfs->writev_call) ? accumulator.writev_call : vfs->writev_call; + vfs->read_call = (accumulator.read_call >= vfs->read_call) ? accumulator.read_call : vfs->read_call; + vfs->readv_call = (accumulator.readv_call >= vfs->readv_call) ? accumulator.readv_call : vfs->readv_call; + vfs->unlink_call = (accumulator.unlink_call >= vfs->unlink_call) ? accumulator.unlink_call : vfs->unlink_call; + vfs->fsync_call = (accumulator.fsync_call >= vfs->fsync_call) ? accumulator.fsync_call : vfs->fsync_call; + vfs->open_call = (accumulator.open_call >= vfs->open_call) ? accumulator.open_call : vfs->open_call; + vfs->create_call = (accumulator.create_call >= vfs->create_call) ? accumulator.create_call : vfs->create_call; + + vfs->write_bytes = (accumulator.write_bytes >= vfs->write_bytes) ? accumulator.write_bytes : vfs->write_bytes; + vfs->writev_bytes = (accumulator.writev_bytes >= vfs->writev_bytes) ? accumulator.writev_bytes : vfs->writev_bytes; + vfs->read_bytes = (accumulator.read_bytes >= vfs->read_bytes) ? accumulator.read_bytes : vfs->read_bytes; + vfs->readv_bytes = (accumulator.readv_bytes >= vfs->readv_bytes) ? accumulator.readv_bytes : vfs->readv_bytes; + + vfs->write_err = (accumulator.write_err >= vfs->write_err) ? accumulator.write_err : vfs->write_err; + vfs->writev_err = (accumulator.writev_err >= vfs->writev_err) ? accumulator.writev_err : vfs->writev_err; + vfs->read_err = (accumulator.read_err >= vfs->read_err) ? accumulator.read_err : vfs->read_err; + vfs->readv_err = (accumulator.readv_err >= vfs->readv_err) ? accumulator.readv_err : vfs->readv_err; + vfs->unlink_err = (accumulator.unlink_err >= vfs->unlink_err) ? accumulator.unlink_err : vfs->unlink_err; + vfs->fsync_err = (accumulator.fsync_err >= vfs->fsync_err) ? accumulator.fsync_err : vfs->fsync_err; + vfs->open_err = (accumulator.open_err >= vfs->open_err) ? accumulator.open_err : vfs->open_err; + vfs->create_err = (accumulator.create_err >= vfs->create_err) ? accumulator.create_err : vfs->create_err; +} + +/** + * Send data to Netdata calling auxiliary functions. + * + * @param em the structure with thread information + * @param root the target list. + */ +void ebpf_vfs_send_apps_data(ebpf_module_t *em, struct ebpf_target *root) +{ + struct ebpf_target *w; + pthread_mutex_lock(&collect_data_mutex); + for (w = root; w; w = w->next) { + if (unlikely(!(w->charts_created & (1<<EBPF_MODULE_VFS_IDX)))) + continue; + + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_call_vfs_unlink"); + write_chart_dimension("calls", w->vfs.unlink_call); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_call_vfs_write"); + write_chart_dimension("calls", w->vfs.write_call + w->vfs.writev_call); + ebpf_write_end_chart(); + + if (em->mode < MODE_ENTRY) { + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_call_vfs_write_error"); + write_chart_dimension("calls", w->vfs.write_err + w->vfs.writev_err); + ebpf_write_end_chart(); + } + + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_call_vfs_read"); + write_chart_dimension("calls", w->vfs.read_call + w->vfs.readv_call); + ebpf_write_end_chart(); + + if (em->mode < MODE_ENTRY) { + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_call_vfs_read_error"); + write_chart_dimension("calls", w->vfs.read_err + w->vfs.readv_err); + ebpf_write_end_chart(); + } + + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_call_vfs_write_bytes"); + write_chart_dimension("writes", w->vfs.write_bytes + w->vfs.writev_bytes); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_call_vfs_read_bytes"); + write_chart_dimension("reads", w->vfs.read_bytes + w->vfs.readv_bytes); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_call_vfs_fsync"); + write_chart_dimension("calls", w->vfs.fsync_call); + ebpf_write_end_chart(); + + if (em->mode < MODE_ENTRY) { + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_call_vfs_fsync_error"); + write_chart_dimension("calls", w->vfs.fsync_err); + ebpf_write_end_chart(); + } + + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_call_vfs_open"); + write_chart_dimension("calls", w->vfs.open_call); + ebpf_write_end_chart(); + + if (em->mode < MODE_ENTRY) { + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_call_vfs_open_error"); + write_chart_dimension("calls", w->vfs.open_err); + ebpf_write_end_chart(); + } + + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_call_vfs_create"); + write_chart_dimension("calls", w->vfs.create_call); + ebpf_write_end_chart(); + + if (em->mode < MODE_ENTRY) { + ebpf_write_begin_chart(NETDATA_APP_FAMILY, w->clean_name, "_ebpf_call_vfs_create_error"); + write_chart_dimension("calls", w->vfs.create_err); + ebpf_write_end_chart(); + } + } + pthread_mutex_unlock(&collect_data_mutex); +} + +/** + * Apps Accumulator + * + * Sum all values read from kernel and store in the first address. + * + * @param out the vector with read values. + */ +static void vfs_apps_accumulator(netdata_publish_vfs_t *out, int maps_per_core) +{ + int i, end = (maps_per_core) ? ebpf_nprocs : 1; + netdata_publish_vfs_t *total = &out[0]; + for (i = 1; i < end; i++) { + netdata_publish_vfs_t *w = &out[i]; + + total->write_call += w->write_call; + total->writev_call += w->writev_call; + total->read_call += w->read_call; + total->readv_call += w->readv_call; + total->unlink_call += w->unlink_call; + + total->write_bytes += w->write_bytes; + total->writev_bytes += w->writev_bytes; + total->read_bytes += w->read_bytes; + total->readv_bytes += w->readv_bytes; + + total->write_err += w->write_err; + total->writev_err += w->writev_err; + total->read_err += w->read_err; + total->readv_err += w->readv_err; + total->unlink_err += w->unlink_err; + } +} + +/** + * Read the hash table and store data to allocated vectors. + */ +static void ebpf_vfs_read_apps(int maps_per_core, int max_period) +{ + netdata_publish_vfs_t *vv = vfs_vector; + int fd = vfs_maps[NETDATA_VFS_PID].map_fd; + size_t length = sizeof(netdata_publish_vfs_t); + if (maps_per_core) + length *= ebpf_nprocs; + + uint32_t key = 0, next_key = 0; + while (bpf_map_get_next_key(fd, &key, &next_key) == 0) { + if (bpf_map_lookup_elem(fd, &key, vv)) { + goto end_vfs_loop; + } + + vfs_apps_accumulator(vv, maps_per_core); + + ebpf_pid_stat_t *local_pid = ebpf_get_pid_entry(key, vv->tgid); + if (!local_pid) + goto end_vfs_loop; + + netdata_publish_vfs_t *publish = &local_pid->vfs; + if (!publish->ct || publish->ct != vv->ct) { + memcpy(publish, vv, sizeof(netdata_publish_vfs_t)); + local_pid->not_updated = 0; + } else if (++local_pid->not_updated >= max_period){ + bpf_map_delete_elem(fd, &key); + local_pid->not_updated = 0; + } + +end_vfs_loop: + // We are cleaning to avoid passing data read from one process to other. + memset(vv, 0, length); + key = next_key; + } +} + +/** + * Update cgroup + * + * Update cgroup data based in PID. + * + * @param maps_per_core do I need to read all cores? + */ +static void read_update_vfs_cgroup() +{ + ebpf_cgroup_target_t *ect ; + pthread_mutex_lock(&mutex_cgroup_shm); + for (ect = ebpf_cgroup_pids; ect; ect = ect->next) { + struct pid_on_target2 *pids; + for (pids = ect->pids; pids; pids = pids->next) { + int pid = pids->pid; + netdata_publish_vfs_t *out = &pids->vfs; + ebpf_pid_stat_t *local_pid = ebpf_get_pid_entry(pid, 0); + if (local_pid) { + netdata_publish_vfs_t *in = &local_pid->vfs; + + memcpy(out, in, sizeof(netdata_publish_vfs_t)); + } + } + } + pthread_mutex_unlock(&mutex_cgroup_shm); +} + +/** + * Sum PIDs + * + * Sum values for all targets. + * + * @param vfs structure used to store data + * @param pids input data + */ +static void ebpf_vfs_sum_cgroup_pids(netdata_publish_vfs_t *vfs, struct pid_on_target2 *pids) + { + netdata_publish_vfs_t accumulator; + memset(&accumulator, 0, sizeof(accumulator)); + + while (pids) { + netdata_publish_vfs_t *w = &pids->vfs; + + accumulator.write_call += w->write_call; + accumulator.writev_call += w->writev_call; + accumulator.read_call += w->read_call; + accumulator.readv_call += w->readv_call; + accumulator.unlink_call += w->unlink_call; + accumulator.fsync_call += w->fsync_call; + accumulator.open_call += w->open_call; + accumulator.create_call += w->create_call; + + accumulator.write_bytes += w->write_bytes; + accumulator.writev_bytes += w->writev_bytes; + accumulator.read_bytes += w->read_bytes; + accumulator.readv_bytes += w->readv_bytes; + + accumulator.write_err += w->write_err; + accumulator.writev_err += w->writev_err; + accumulator.read_err += w->read_err; + accumulator.readv_err += w->readv_err; + accumulator.unlink_err += w->unlink_err; + accumulator.fsync_err += w->fsync_err; + accumulator.open_err += w->open_err; + accumulator.create_err += w->create_err; + + pids = pids->next; + } + + // These conditions were added, because we are using incremental algorithm + vfs->write_call = (accumulator.write_call >= vfs->write_call) ? accumulator.write_call : vfs->write_call; + vfs->writev_call = (accumulator.writev_call >= vfs->writev_call) ? accumulator.writev_call : vfs->writev_call; + vfs->read_call = (accumulator.read_call >= vfs->read_call) ? accumulator.read_call : vfs->read_call; + vfs->readv_call = (accumulator.readv_call >= vfs->readv_call) ? accumulator.readv_call : vfs->readv_call; + vfs->unlink_call = (accumulator.unlink_call >= vfs->unlink_call) ? accumulator.unlink_call : vfs->unlink_call; + vfs->fsync_call = (accumulator.fsync_call >= vfs->fsync_call) ? accumulator.fsync_call : vfs->fsync_call; + vfs->open_call = (accumulator.open_call >= vfs->open_call) ? accumulator.open_call : vfs->open_call; + vfs->create_call = (accumulator.create_call >= vfs->create_call) ? accumulator.create_call : vfs->create_call; + + vfs->write_bytes = (accumulator.write_bytes >= vfs->write_bytes) ? accumulator.write_bytes : vfs->write_bytes; + vfs->writev_bytes = (accumulator.writev_bytes >= vfs->writev_bytes) ? accumulator.writev_bytes : vfs->writev_bytes; + vfs->read_bytes = (accumulator.read_bytes >= vfs->read_bytes) ? accumulator.read_bytes : vfs->read_bytes; + vfs->readv_bytes = (accumulator.readv_bytes >= vfs->readv_bytes) ? accumulator.readv_bytes : vfs->readv_bytes; + + vfs->write_err = (accumulator.write_err >= vfs->write_err) ? accumulator.write_err : vfs->write_err; + vfs->writev_err = (accumulator.writev_err >= vfs->writev_err) ? accumulator.writev_err : vfs->writev_err; + vfs->read_err = (accumulator.read_err >= vfs->read_err) ? accumulator.read_err : vfs->read_err; + vfs->readv_err = (accumulator.readv_err >= vfs->readv_err) ? accumulator.readv_err : vfs->readv_err; + vfs->unlink_err = (accumulator.unlink_err >= vfs->unlink_err) ? accumulator.unlink_err : vfs->unlink_err; + vfs->fsync_err = (accumulator.fsync_err >= vfs->fsync_err) ? accumulator.fsync_err : vfs->fsync_err; + vfs->open_err = (accumulator.open_err >= vfs->open_err) ? accumulator.open_err : vfs->open_err; + vfs->create_err = (accumulator.create_err >= vfs->create_err) ? accumulator.create_err : vfs->create_err; +} + +/** + * Create specific VFS charts + * + * Create charts for cgroup/application. + * + * @param type the chart type. + * @param em the main thread structure. + */ +static void ebpf_create_specific_vfs_charts(char *type, ebpf_module_t *em) +{ + char *label = (!strncmp(type, "cgroup_", 7)) ? &type[7] : type; + ebpf_create_chart(type, NETDATA_SYSCALL_APPS_FILE_DELETED,"Files deleted", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_VFS_GROUP, NETDATA_CGROUP_VFS_UNLINK_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5500, + ebpf_create_global_dimension, &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_UNLINK], + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + + ebpf_create_chart(type, NETDATA_SYSCALL_APPS_VFS_WRITE_CALLS, "Write to disk", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_VFS_GROUP, NETDATA_CGROUP_VFS_WRITE_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5501, + ebpf_create_global_dimension, &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_WRITE], + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + + if (em->mode < MODE_ENTRY) { + ebpf_create_chart(type, NETDATA_SYSCALL_APPS_VFS_WRITE_CALLS_ERROR, "Fails to write", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_VFS_GROUP, NETDATA_CGROUP_VFS_WRITE_ERROR_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5502, + ebpf_create_global_dimension, &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_WRITE], + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + } + + ebpf_create_chart(type, NETDATA_SYSCALL_APPS_VFS_READ_CALLS, "Read from disk", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_VFS_GROUP, NETDATA_CGROUP_VFS_READ_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5503, + ebpf_create_global_dimension, &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_READ], + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + + if (em->mode < MODE_ENTRY) { + ebpf_create_chart(type, NETDATA_SYSCALL_APPS_VFS_READ_CALLS_ERROR, "Fails to read", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_VFS_GROUP, NETDATA_CGROUP_VFS_READ_ERROR_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5504, + ebpf_create_global_dimension, &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_READ], + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + } + + ebpf_create_chart(type, NETDATA_SYSCALL_APPS_VFS_WRITE_BYTES, "Bytes written on disk", + EBPF_COMMON_UNITS_BYTES, NETDATA_VFS_GROUP, NETDATA_CGROUP_VFS_WRITE_BYTES_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5505, + ebpf_create_global_dimension, &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_WRITE], + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + + ebpf_create_chart(type, NETDATA_SYSCALL_APPS_VFS_READ_BYTES, "Bytes read from disk", + EBPF_COMMON_UNITS_BYTES, NETDATA_VFS_GROUP, NETDATA_CGROUP_VFS_READ_BYTES_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5506, + ebpf_create_global_dimension, &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_READ], + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + + ebpf_create_chart(type, NETDATA_SYSCALL_APPS_VFS_FSYNC, "Calls to vfs_fsync.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_VFS_GROUP, NETDATA_CGROUP_VFS_FSYNC_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5507, + ebpf_create_global_dimension, &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_FSYNC], + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + + if (em->mode < MODE_ENTRY) { + ebpf_create_chart(type, NETDATA_SYSCALL_APPS_VFS_FSYNC_CALLS_ERROR, "Sync error", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_VFS_GROUP, NETDATA_CGROUP_VFS_FSYNC_ERROR_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5508, + ebpf_create_global_dimension, &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_FSYNC], + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + } + + ebpf_create_chart(type, NETDATA_SYSCALL_APPS_VFS_OPEN, "Calls to vfs_open.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_VFS_GROUP, NETDATA_CGROUP_VFS_OPEN_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5509, + ebpf_create_global_dimension, &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_OPEN], + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + + if (em->mode < MODE_ENTRY) { + ebpf_create_chart(type, NETDATA_SYSCALL_APPS_VFS_OPEN_CALLS_ERROR, "Open error", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_VFS_GROUP, NETDATA_CGROUP_VFS_OPEN_ERROR_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5510, + ebpf_create_global_dimension, &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_OPEN], + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + } + + ebpf_create_chart(type, NETDATA_SYSCALL_APPS_VFS_CREATE, "Calls to vfs_create.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_VFS_GROUP, NETDATA_CGROUP_VFS_CREATE_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5511, + ebpf_create_global_dimension, &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_CREATE], + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + + if (em->mode < MODE_ENTRY) { + ebpf_create_chart(type, NETDATA_SYSCALL_APPS_VFS_CREATE_CALLS_ERROR, "Create error", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_VFS_GROUP, NETDATA_CGROUP_VFS_CREATE_ERROR_CONTEXT, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5512, + ebpf_create_global_dimension, &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_CREATE], + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); + ebpf_create_chart_labels("cgroup_name", label, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + } +} + +/** + * Obsolete specific VFS charts + * + * Obsolete charts for cgroup/application. + * + * @param type the chart type. + * @param em the main thread structure. + */ +static void ebpf_obsolete_specific_vfs_charts(char *type, ebpf_module_t *em) +{ + ebpf_write_chart_obsolete(type, NETDATA_SYSCALL_APPS_FILE_DELETED, "", "Files deleted", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_VFS_UNLINK_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5500, em->update_every); + + ebpf_write_chart_obsolete(type, NETDATA_SYSCALL_APPS_VFS_WRITE_CALLS, "", "Write to disk", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_VFS_WRITE_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5501, em->update_every); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_obsolete(type, NETDATA_SYSCALL_APPS_VFS_WRITE_CALLS_ERROR, "", "Fails to write", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_VFS_WRITE_ERROR_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5502, em->update_every); + } + + ebpf_write_chart_obsolete(type, NETDATA_SYSCALL_APPS_VFS_READ_CALLS, "", "Read from disk", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_VFS_READ_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5503, em->update_every); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_obsolete(type, NETDATA_SYSCALL_APPS_VFS_READ_CALLS_ERROR, "", "Fails to read", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_VFS_READ_ERROR_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5504, em->update_every); + } + + ebpf_write_chart_obsolete(type, NETDATA_SYSCALL_APPS_VFS_WRITE_BYTES, "", "Bytes written on disk", + EBPF_COMMON_UNITS_BYTES, NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_VFS_WRITE_BYTES_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5505, em->update_every); + + ebpf_write_chart_obsolete(type, NETDATA_SYSCALL_APPS_VFS_READ_BYTES, "", "Bytes read from disk", + EBPF_COMMON_UNITS_BYTES, NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_VFS_READ_BYTES_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5506, em->update_every); + + ebpf_write_chart_obsolete(type, NETDATA_SYSCALL_APPS_VFS_FSYNC, "", "Calls to vfs_fsync.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_VFS_FSYNC_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5507, em->update_every); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_obsolete(type, NETDATA_SYSCALL_APPS_VFS_FSYNC_CALLS_ERROR, "", "Sync error", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_VFS_FSYNC_ERROR_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5508, em->update_every); + } + + ebpf_write_chart_obsolete(type, NETDATA_SYSCALL_APPS_VFS_OPEN, "", "Calls to vfs_open.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_VFS_OPEN_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5509, em->update_every); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_obsolete(type, NETDATA_SYSCALL_APPS_VFS_OPEN_CALLS_ERROR, "", "Open error", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_VFS_OPEN_ERROR_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5510, em->update_every); + } + + ebpf_write_chart_obsolete(type, NETDATA_SYSCALL_APPS_VFS_CREATE, "", "Calls to vfs_create.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_VFS_CREATE_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5511, em->update_every); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_obsolete(type, NETDATA_SYSCALL_APPS_VFS_CREATE_CALLS_ERROR, "", "Create error", + EBPF_COMMON_UNITS_CALLS_PER_SEC, NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_LINE, NETDATA_CGROUP_VFS_CREATE_ERROR_CONTEXT, + NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 5512, em->update_every); + } +} + +/* + * Send specific VFS data + * + * Send data for specific cgroup/apps. + * + * @param type chart type + * @param values structure with values that will be sent to netdata + */ +static void ebpf_send_specific_vfs_data(char *type, netdata_publish_vfs_t *values, ebpf_module_t *em) +{ + ebpf_write_begin_chart(type, NETDATA_SYSCALL_APPS_FILE_DELETED, ""); + write_chart_dimension(vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_UNLINK].name, (long long)values->unlink_call); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(type, NETDATA_SYSCALL_APPS_VFS_WRITE_CALLS, ""); + write_chart_dimension(vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_WRITE].name, + (long long)values->write_call + (long long)values->writev_call); + ebpf_write_end_chart(); + + if (em->mode < MODE_ENTRY) { + ebpf_write_begin_chart(type, NETDATA_SYSCALL_APPS_VFS_WRITE_CALLS_ERROR, ""); + write_chart_dimension(vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_WRITE].name, + (long long)values->write_err + (long long)values->writev_err); + ebpf_write_end_chart(); + } + + ebpf_write_begin_chart(type, NETDATA_SYSCALL_APPS_VFS_READ_CALLS, ""); + write_chart_dimension(vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_READ].name, + (long long)values->read_call + (long long)values->readv_call); + ebpf_write_end_chart(); + + if (em->mode < MODE_ENTRY) { + ebpf_write_begin_chart(type, NETDATA_SYSCALL_APPS_VFS_READ_CALLS_ERROR, ""); + write_chart_dimension(vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_READ].name, + (long long)values->read_err + (long long)values->readv_err); + ebpf_write_end_chart(); + } + + ebpf_write_begin_chart(type, NETDATA_SYSCALL_APPS_VFS_WRITE_BYTES, ""); + write_chart_dimension(vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_WRITE].name, + (long long)values->write_bytes + (long long)values->writev_bytes); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(type, NETDATA_SYSCALL_APPS_VFS_READ_BYTES, ""); + write_chart_dimension(vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_READ].name, + (long long)values->read_bytes + (long long)values->readv_bytes); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(type, NETDATA_SYSCALL_APPS_VFS_FSYNC, ""); + write_chart_dimension(vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_FSYNC].name, + (long long)values->fsync_call); + ebpf_write_end_chart(); + + if (em->mode < MODE_ENTRY) { + ebpf_write_begin_chart(type, NETDATA_SYSCALL_APPS_VFS_FSYNC_CALLS_ERROR, ""); + write_chart_dimension(vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_FSYNC].name, + (long long)values->fsync_err); + ebpf_write_end_chart(); + } + + ebpf_write_begin_chart(type, NETDATA_SYSCALL_APPS_VFS_OPEN, ""); + write_chart_dimension(vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_OPEN].name, + (long long)values->open_call); + ebpf_write_end_chart(); + + if (em->mode < MODE_ENTRY) { + ebpf_write_begin_chart(type, NETDATA_SYSCALL_APPS_VFS_OPEN_CALLS_ERROR, ""); + write_chart_dimension(vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_OPEN].name, + (long long)values->open_err); + ebpf_write_end_chart(); + } + + ebpf_write_begin_chart(type, NETDATA_SYSCALL_APPS_VFS_CREATE, ""); + write_chart_dimension(vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_CREATE].name, + (long long)values->create_call); + ebpf_write_end_chart(); + + if (em->mode < MODE_ENTRY) { + ebpf_write_begin_chart(type, NETDATA_SYSCALL_APPS_VFS_CREATE_CALLS_ERROR, ""); + write_chart_dimension(vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_CREATE].name, + (long long)values->create_err); + ebpf_write_end_chart(); + } +} + +/** + * Create Systemd Socket Charts + * + * Create charts when systemd is enabled + * + * @param em the main collector structure + **/ +static void ebpf_create_systemd_vfs_charts(ebpf_module_t *em) +{ + static ebpf_systemd_args_t data_vfs_unlink = { + .title = "Files deleted", + .units = EBPF_COMMON_UNITS_CALLS_PER_SEC, + .family = NETDATA_VFS_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20065, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SYSTEMD_VFS_UNLINK_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_VFS, + .update_every = 0, + .suffix = NETDATA_SYSCALL_APPS_FILE_DELETED, + .dimension = "calls" + }; + + static ebpf_systemd_args_t data_vfs_write = { + .title = "Write to disk", + .units = EBPF_COMMON_UNITS_CALLS_PER_SEC, + .family = NETDATA_VFS_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20066, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SYSTEMD_VFS_WRITE_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_VFS, + .update_every = 0, + .suffix = NETDATA_SYSCALL_APPS_VFS_WRITE_CALLS, + .dimension = "calls" + }; + + static ebpf_systemd_args_t data_vfs_write_err = { + .title = "Fails to write", + .units = EBPF_COMMON_UNITS_CALLS_PER_SEC, + .family = NETDATA_VFS_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20067, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SYSTEMD_VFS_WRITE_ERROR_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_VFS, + .update_every = 0, + .suffix = NETDATA_SYSCALL_APPS_VFS_WRITE_CALLS_ERROR, + .dimension = "calls" + }; + + static ebpf_systemd_args_t data_vfs_read = { + .title = "Read from disk", + .units = EBPF_COMMON_UNITS_CALLS_PER_SEC, + .family = NETDATA_VFS_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20068, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SYSTEMD_VFS_READ_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_VFS, + .update_every = 0, + .suffix = NETDATA_SYSCALL_APPS_VFS_READ_CALLS, + .dimension = "calls" + }; + + static ebpf_systemd_args_t data_vfs_read_err = { + .title = "Fails to read", + .units = EBPF_COMMON_UNITS_CALLS_PER_SEC, + .family = NETDATA_VFS_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20069, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SYSTEMD_VFS_READ_ERROR_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_VFS, + .update_every = 0, + .suffix = NETDATA_SYSCALL_APPS_VFS_READ_CALLS_ERROR, + .dimension = "calls" + }; + + static ebpf_systemd_args_t data_vfs_write_bytes = { + .title = "Bytes written on disk", + .units = EBPF_COMMON_UNITS_BYTES, + .family = NETDATA_VFS_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20070, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SYSTEMD_VFS_WRITE_BYTES_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_VFS, + .update_every = 0, + .suffix = NETDATA_SYSCALL_APPS_VFS_WRITE_BYTES, + .dimension = "bytes" + }; + + static ebpf_systemd_args_t data_vfs_read_bytes = { + .title = "Bytes read from disk", + .units = EBPF_COMMON_UNITS_BYTES, + .family = NETDATA_VFS_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20071, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SYSTEMD_VFS_READ_BYTES_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_VFS, + .update_every = 0, + .suffix = NETDATA_SYSCALL_APPS_VFS_READ_BYTES, + .dimension = "bytes" + }; + + static ebpf_systemd_args_t data_vfs_fsync = { + .title = "Calls to vfs_fsync.", + .units = EBPF_COMMON_UNITS_CALLS_PER_SEC, + .family = NETDATA_VFS_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20072, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SYSTEMD_VFS_FSYNC_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_VFS, + .update_every = 0, + .suffix = NETDATA_SYSCALL_APPS_VFS_FSYNC, + .dimension = "calls" + }; + + static ebpf_systemd_args_t data_vfs_fsync_err = { + .title = "Sync error", + .units = EBPF_COMMON_UNITS_CALLS_PER_SEC, + .family = NETDATA_VFS_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20073, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SYSTEMD_VFS_FSYNC_ERROR_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_VFS, + .update_every = 0, + .suffix = NETDATA_SYSCALL_APPS_VFS_FSYNC_CALLS_ERROR, + .dimension = "calls" + }; + + static ebpf_systemd_args_t data_vfs_open = { + .title = "Calls to vfs_open.", + .units = EBPF_COMMON_UNITS_CALLS_PER_SEC, + .family = NETDATA_VFS_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20074, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SYSTEMD_VFS_OPEN_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_VFS, + .update_every = 0, + .suffix = NETDATA_SYSCALL_APPS_VFS_OPEN, + .dimension = "calls" + }; + + static ebpf_systemd_args_t data_vfs_open_err = { + .title = "Open error", + .units = EBPF_COMMON_UNITS_CALLS_PER_SEC, + .family = NETDATA_VFS_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20075, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SYSTEMD_VFS_OPEN_ERROR_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_VFS, + .update_every = 0, + .suffix = NETDATA_SYSCALL_APPS_VFS_OPEN_CALLS_ERROR, + .dimension = "calls" + }; + + static ebpf_systemd_args_t data_vfs_create = { + .title = "Calls to vfs_create.", + .units = EBPF_COMMON_UNITS_CALLS_PER_SEC, + .family = NETDATA_VFS_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20076, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SYSTEMD_VFS_CREATE_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_VFS, + .update_every = 0, + .suffix = NETDATA_SYSCALL_APPS_VFS_CREATE, + .dimension = "calls" + }; + + static ebpf_systemd_args_t data_vfs_create_err = { + .title = "Create error", + .units = EBPF_COMMON_UNITS_CALLS_PER_SEC, + .family = NETDATA_VFS_GROUP, + .charttype = NETDATA_EBPF_CHART_TYPE_STACKED, + .order = 20077, + .algorithm = EBPF_CHART_ALGORITHM_INCREMENTAL, + .context = NETDATA_SYSTEMD_VFS_CREATE_ERROR_CONTEXT, + .module = NETDATA_EBPF_MODULE_NAME_VFS, + .update_every = 0, + .suffix = NETDATA_SYSCALL_APPS_VFS_CREATE_CALLS_ERROR, + .dimension = "calls" + }; + + if (!data_vfs_create.update_every) + data_vfs_unlink.update_every = data_vfs_write.update_every = data_vfs_write_err.update_every = + data_vfs_read.update_every = data_vfs_read_err.update_every = data_vfs_write_bytes.update_every = + data_vfs_read_bytes.update_every = data_vfs_fsync.update_every = data_vfs_fsync_err.update_every = + data_vfs_open.update_every = data_vfs_open_err.update_every = data_vfs_create.update_every = + data_vfs_create_err.update_every = em->update_every; + + ebpf_cgroup_target_t *w; + for (w = ebpf_cgroup_pids; w ; w = w->next) { + if (unlikely(!w->systemd || w->flags & NETDATA_EBPF_SERVICES_HAS_VFS_CHART)) + continue; + + data_vfs_unlink.id = data_vfs_write.id = data_vfs_write_err.id = + data_vfs_read.id = data_vfs_read_err.id = data_vfs_write_bytes.id = data_vfs_read_bytes.id = + data_vfs_fsync.id = data_vfs_fsync_err.id = data_vfs_open.id = + data_vfs_open_err.id = data_vfs_create.id = data_vfs_create_err.id = w->name; + ebpf_create_charts_on_systemd(&data_vfs_unlink); + + ebpf_create_charts_on_systemd(&data_vfs_write); + + ebpf_create_charts_on_systemd(&data_vfs_read); + + ebpf_create_charts_on_systemd(&data_vfs_write_bytes); + + ebpf_create_charts_on_systemd(&data_vfs_read_bytes); + + ebpf_create_charts_on_systemd(&data_vfs_fsync); + + ebpf_create_charts_on_systemd(&data_vfs_open); + + ebpf_create_charts_on_systemd(&data_vfs_create); + if (em->mode < MODE_ENTRY) { + ebpf_create_charts_on_systemd(&data_vfs_write_err); + ebpf_create_charts_on_systemd(&data_vfs_read_err); + ebpf_create_charts_on_systemd(&data_vfs_fsync_err); + ebpf_create_charts_on_systemd(&data_vfs_open_err); + ebpf_create_charts_on_systemd(&data_vfs_create_err); + } + + w->flags |= NETDATA_EBPF_SERVICES_HAS_VFS_CHART; + } +} + +/** + * Send Systemd charts + * + * Send collected data to Netdata. + * + * @param em the main collector structure + */ +static void ebpf_send_systemd_vfs_charts(ebpf_module_t *em) +{ + ebpf_cgroup_target_t *ect; + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + if (unlikely(!(ect->flags & NETDATA_EBPF_SERVICES_HAS_VFS_CHART)) ) { + continue; + } + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_SYSCALL_APPS_FILE_DELETED); + write_chart_dimension("calls", ect->publish_systemd_vfs.unlink_call); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_SYSCALL_APPS_VFS_WRITE_CALLS); + write_chart_dimension("calls", ect->publish_systemd_vfs.write_call + + ect->publish_systemd_vfs.writev_call); + ebpf_write_end_chart(); + + if (em->mode < MODE_ENTRY) { + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_SYSCALL_APPS_VFS_WRITE_CALLS_ERROR); + write_chart_dimension("calls", ect->publish_systemd_vfs.write_err + + ect->publish_systemd_vfs.writev_err); + ebpf_write_end_chart(); + } + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_SYSCALL_APPS_VFS_READ_CALLS); + write_chart_dimension("calls", ect->publish_systemd_vfs.read_call + + ect->publish_systemd_vfs.readv_call); + ebpf_write_end_chart(); + + if (em->mode < MODE_ENTRY) { + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_SYSCALL_APPS_VFS_READ_CALLS_ERROR); + write_chart_dimension("calls", ect->publish_systemd_vfs.read_err + + ect->publish_systemd_vfs.readv_err); + ebpf_write_end_chart(); + } + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_SYSCALL_APPS_VFS_WRITE_BYTES); + write_chart_dimension("bytes", ect->publish_systemd_vfs.write_bytes + + ect->publish_systemd_vfs.writev_bytes); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_SYSCALL_APPS_VFS_READ_BYTES); + write_chart_dimension("bytes", ect->publish_systemd_vfs.read_bytes + + ect->publish_systemd_vfs.readv_bytes); + ebpf_write_end_chart(); + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_SYSCALL_APPS_VFS_FSYNC); + write_chart_dimension("calls", ect->publish_systemd_vfs.fsync_call); + ebpf_write_end_chart(); + + if (em->mode < MODE_ENTRY) { + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_SYSCALL_APPS_VFS_FSYNC_CALLS_ERROR); + write_chart_dimension("calls", ect->publish_systemd_vfs.fsync_err); + ebpf_write_end_chart(); + } + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_SYSCALL_APPS_VFS_OPEN); + write_chart_dimension("calls", ect->publish_systemd_vfs.open_call); + ebpf_write_end_chart(); + + if (em->mode < MODE_ENTRY) { + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_SYSCALL_APPS_VFS_OPEN_CALLS_ERROR); + write_chart_dimension("calls", ect->publish_systemd_vfs.open_err); + ebpf_write_end_chart(); + } + + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_SYSCALL_APPS_VFS_CREATE); + write_chart_dimension("calls", ect->publish_systemd_vfs.create_call); + ebpf_write_end_chart(); + + if (em->mode < MODE_ENTRY) { + ebpf_write_begin_chart(NETDATA_SERVICE_FAMILY, ect->name, NETDATA_SYSCALL_APPS_VFS_CREATE_CALLS_ERROR); + write_chart_dimension("calls", ect->publish_systemd_vfs.create_err); + ebpf_write_end_chart(); + } + } +} + +/** + * Send data to Netdata calling auxiliary functions. + * + * @param em the main collector structure +*/ +static void ebpf_vfs_send_cgroup_data(ebpf_module_t *em) +{ + pthread_mutex_lock(&mutex_cgroup_shm); + ebpf_cgroup_target_t *ect; + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + ebpf_vfs_sum_cgroup_pids(&ect->publish_systemd_vfs, ect->pids); + } + + if (shm_ebpf_cgroup.header->systemd_enabled) { + if (send_cgroup_chart) { + ebpf_create_systemd_vfs_charts(em); + } + ebpf_send_systemd_vfs_charts(em); + } + + for (ect = ebpf_cgroup_pids; ect ; ect = ect->next) { + if (ect->systemd) + continue; + + if (!(ect->flags & NETDATA_EBPF_CGROUP_HAS_VFS_CHART) && ect->updated) { + ebpf_create_specific_vfs_charts(ect->name, em); + ect->flags |= NETDATA_EBPF_CGROUP_HAS_VFS_CHART; + } + + if (ect->flags & NETDATA_EBPF_CGROUP_HAS_VFS_CHART) { + if (ect->updated) { + ebpf_send_specific_vfs_data(ect->name, &ect->publish_systemd_vfs, em); + } else { + ebpf_obsolete_specific_vfs_charts(ect->name, em); + ect->flags &= ~NETDATA_EBPF_CGROUP_HAS_VFS_CHART; + } + } + } + + pthread_mutex_unlock(&mutex_cgroup_shm); +} + +/** + * Resume apps data + */ +void ebpf_vfs_resume_apps_data() { + struct ebpf_target *w; + for (w = apps_groups_root_target; w; w = w->next) { + if (unlikely(!(w->charts_created & (1 << EBPF_MODULE_VFS_IDX)))) + continue; + + ebpf_vfs_sum_pids(&w->vfs, w->root_pid); + } +} + +/** + * VFS thread + * + * Thread used to generate charts. + * + * @param ptr a pointer to `struct ebpf_module` + * + * @return It always return NULL + */ +void *ebpf_read_vfs_thread(void *ptr) +{ + heartbeat_t hb; + heartbeat_init(&hb); + + ebpf_module_t *em = (ebpf_module_t *)ptr; + + int maps_per_core = em->maps_per_core; + int update_every = em->update_every; + + int counter = update_every - 1; + + uint32_t lifetime = em->lifetime; + uint32_t running_time = 0; + usec_t period = update_every * USEC_PER_SEC; + int max_period = update_every * EBPF_CLEANUP_FACTOR; + while (!ebpf_plugin_stop() && running_time < lifetime) { + (void)heartbeat_next(&hb, period); + if (ebpf_plugin_stop() || ++counter != update_every) + continue; + + pthread_mutex_lock(&collect_data_mutex); + ebpf_vfs_read_apps(maps_per_core, max_period); + ebpf_vfs_resume_apps_data(); + pthread_mutex_unlock(&collect_data_mutex); + + counter = 0; + + pthread_mutex_lock(&ebpf_exit_cleanup); + if (running_time && !em->running_time) + running_time = update_every; + else + running_time += update_every; + + em->running_time = running_time; + pthread_mutex_unlock(&ebpf_exit_cleanup); + } + + return NULL; +} + +/** + * Main loop for this collector. + * + * @param step the number of microseconds used with heart beat + * @param em the structure with thread information + */ +static void vfs_collector(ebpf_module_t *em) +{ + int cgroups = em->cgroup_charts; + heartbeat_t hb; + heartbeat_init(&hb); + int update_every = em->update_every; + int counter = update_every - 1; + int maps_per_core = em->maps_per_core; + uint32_t running_time = 0; + uint32_t lifetime = em->lifetime; + netdata_idx_t *stats = em->hash_table_stats; + memset(stats, 0, sizeof(em->hash_table_stats)); + while (!ebpf_plugin_stop() && running_time < lifetime) { + (void)heartbeat_next(&hb, USEC_PER_SEC); + if (ebpf_plugin_stop() || ++counter != update_every) + continue; + + counter = 0; + netdata_apps_integration_flags_t apps = em->apps_charts; + ebpf_vfs_read_global_table(stats, maps_per_core); + + if (cgroups && shm_ebpf_cgroup.header) + read_update_vfs_cgroup(); + + pthread_mutex_lock(&lock); + + ebpf_vfs_send_data(em); + fflush(stdout); + + if (apps & NETDATA_EBPF_APPS_FLAG_CHART_CREATED) + ebpf_vfs_send_apps_data(em, apps_groups_root_target); + + if (cgroups && shm_ebpf_cgroup.header) + ebpf_vfs_send_cgroup_data(em); + + pthread_mutex_unlock(&lock); + + pthread_mutex_lock(&ebpf_exit_cleanup); + if (running_time && !em->running_time) + running_time = update_every; + else + running_time += update_every; + + em->running_time = running_time; + pthread_mutex_unlock(&ebpf_exit_cleanup); + } +} + +/***************************************************************** + * + * 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 dashboard + * @param order the order number of the specified chart + * @param algorithm the algorithm used to make the charts. + * @param update_every value to overwrite the update frequency set by the server. + */ +static void ebpf_create_io_chart(char *family, char *name, char *axis, char *web, + int order, int algorithm, int update_every) +{ + printf("CHART %s.%s '' 'Bytes written and read' '%s' '%s' 'filesystem.vfs_io_bytes' line %d %d '' 'ebpf.plugin' 'filesystem'\n", + family, + name, + axis, + web, + order, + update_every); + + printf("DIMENSION %s %s %s 1 1\n", + vfs_id_names[NETDATA_KEY_PUBLISH_VFS_READ], + vfs_dimension_names[NETDATA_KEY_PUBLISH_VFS_READ], + ebpf_algorithms[algorithm]); + printf("DIMENSION %s %s %s -1 1\n", + vfs_id_names[NETDATA_KEY_PUBLISH_VFS_WRITE], + vfs_dimension_names[NETDATA_KEY_PUBLISH_VFS_WRITE], + ebpf_algorithms[algorithm]); +} + +/** + * 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_FILESYSTEM_FAMILY, + NETDATA_VFS_FILE_CLEAN_COUNT, + "Remove files", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + "filesystem.vfs_deleted_objects", + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_FILESYSTEM_VFS_CLEAN, + ebpf_create_global_dimension, + &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_UNLINK], + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); + + ebpf_create_chart(NETDATA_FILESYSTEM_FAMILY, + NETDATA_VFS_FILE_IO_COUNT, + "Calls to IO", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + "filesystem.vfs_io", + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_FILESYSTEM_VFS_IO_COUNT, + ebpf_create_global_dimension, + &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_READ], + 2, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); + + ebpf_create_io_chart(NETDATA_FILESYSTEM_FAMILY, + NETDATA_VFS_IO_FILE_BYTES, EBPF_COMMON_UNITS_BYTES, + NETDATA_VFS_GROUP, + NETDATA_CHART_PRIO_FILESYSTEM_VFS_IO_BYTES, + NETDATA_EBPF_INCREMENTAL_IDX, em->update_every); + + ebpf_create_chart(NETDATA_FILESYSTEM_FAMILY, + NETDATA_VFS_FSYNC, + "Calls to vfs_fsync.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + "filesystem.vfs_fsync", + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_FILESYSTEM_VFS_IO_FSYNC, + ebpf_create_global_dimension, + &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_FSYNC], + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); + + ebpf_create_chart(NETDATA_FILESYSTEM_FAMILY, + NETDATA_VFS_OPEN, + "Calls to vfs_open.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + "filesystem.vfs_open", + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_FILESYSTEM_VFS_IO_OPEN, + ebpf_create_global_dimension, + &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_OPEN], + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); + + ebpf_create_chart(NETDATA_FILESYSTEM_FAMILY, + NETDATA_VFS_CREATE, + "Calls to vfs_create.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + "filesystem.vfs_create", + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_FILESYSTEM_VFS_IO_CREATE, + ebpf_create_global_dimension, + &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_CREATE], + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); + + if (em->mode < MODE_ENTRY) { + ebpf_create_chart(NETDATA_FILESYSTEM_FAMILY, + NETDATA_VFS_FILE_ERR_COUNT, + "Fails to write or read", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + "filesystem.vfs_io_error", + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_FILESYSTEM_VFS_IO_EBYTES, + ebpf_create_global_dimension, + &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_READ], + 2, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); + + ebpf_create_chart(NETDATA_FILESYSTEM_FAMILY, + NETDATA_VFS_FSYNC_ERR, + "Fails to synchronize", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + "filesystem.vfs_fsync_error", + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_FILESYSTEM_VFS_IO_EFSYNC, + ebpf_create_global_dimension, + &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_FSYNC], + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); + + ebpf_create_chart(NETDATA_FILESYSTEM_FAMILY, + NETDATA_VFS_OPEN_ERR, + "Fails to open a file", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + "filesystem.vfs_open_error", + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_FILESYSTEM_VFS_IO_EOPEN, + ebpf_create_global_dimension, + &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_OPEN], + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); + + ebpf_create_chart(NETDATA_FILESYSTEM_FAMILY, + NETDATA_VFS_CREATE_ERR, + "Fails to create a file.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + "filesystem.vfs_create_error", + NETDATA_EBPF_CHART_TYPE_LINE, + NETDATA_CHART_PRIO_FILESYSTEM_VFS_IO_ECREATE, + ebpf_create_global_dimension, + &vfs_publish_aggregated[NETDATA_KEY_PUBLISH_VFS_CREATE], + 1, em->update_every, NETDATA_EBPF_MODULE_NAME_VFS); + } + + fflush(stdout); +} + +/** + * Create process apps charts + * + * Call ebpf_create_chart to create the charts on apps submenu. + * + * @param em a pointer to the structure with the default values. + * @param ptr a pointer for the targets. + **/ +void ebpf_vfs_create_apps_charts(struct ebpf_module *em, void *ptr) +{ + struct ebpf_target *root = ptr; + struct ebpf_target *w; + int order = 20275; + int update_every = em->update_every; + for (w = root; w; w = w->next) { + if (unlikely(!w->exposed)) + continue; + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_vfs_unlink", + "Files deleted.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_vfs_unlink", + order++, + update_every, + NETDATA_EBPF_MODULE_NAME_VFS); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION calls '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_vfs_write", + "Write to disk.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_vfs_write", + order++, + update_every, + NETDATA_EBPF_MODULE_NAME_VFS); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION calls '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_vfs_write_error", + "Fails to write.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_vfs_write_error", + order++, + update_every, + NETDATA_EBPF_MODULE_NAME_VFS); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION calls '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + } + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_vfs_read", + "Read from disk.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_vfs_read", + order++, + update_every, + NETDATA_EBPF_MODULE_NAME_VFS); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION calls '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_vfs_read_error", + "Fails to read.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_vfs_read_error", + order++, + update_every, + NETDATA_EBPF_MODULE_NAME_VFS); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION calls '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + } + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_vfs_write_bytes", + "Bytes written on disk.", + EBPF_COMMON_UNITS_BYTES, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_vfs_write_bytes", + order++, + update_every, + NETDATA_EBPF_MODULE_NAME_VFS); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION writes '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_vfs_read_bytes", + "Bytes read from disk.", + EBPF_COMMON_UNITS_BYTES, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_vfs_read_bytes", + order++, + update_every, + NETDATA_EBPF_MODULE_NAME_VFS); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION reads '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_vfs_fsync", + "Calls to vfs_fsync.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_vfs_fsync", + order++, + update_every, + NETDATA_EBPF_MODULE_NAME_VFS); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION calls '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_vfs_fsync_error", + "Fails to sync.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_vfs_fsync_error", + order++, + update_every, + NETDATA_EBPF_MODULE_NAME_VFS); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION calls '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + } + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_vfs_open", + "Calls to vfs_open.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_vfs_open", + order++, + update_every, + NETDATA_EBPF_MODULE_NAME_VFS); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION calls '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_vfs_open_error", + "Fails to open.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_vfs_open_error", + order++, + update_every, + NETDATA_EBPF_MODULE_NAME_VFS); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION calls '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + } + + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_vfs_create", + "Calls to vfs_create.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_vfs_create", + order++, + update_every, + NETDATA_EBPF_MODULE_NAME_VFS); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION calls '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + + if (em->mode < MODE_ENTRY) { + ebpf_write_chart_cmd(NETDATA_APP_FAMILY, + w->clean_name, + "_ebpf_call_vfs_create_error", + "Fails to create a file.", + EBPF_COMMON_UNITS_CALLS_PER_SEC, + NETDATA_VFS_GROUP, + NETDATA_EBPF_CHART_TYPE_STACKED, + "app.ebpf_call_vfs_create_error", + order++, + update_every, + NETDATA_EBPF_MODULE_NAME_VFS); + ebpf_create_chart_labels("app_group", w->name, RRDLABEL_SRC_AUTO); + ebpf_commit_label(); + fprintf(stdout, "DIMENSION calls '' %s 1 1\n", ebpf_algorithms[NETDATA_EBPF_INCREMENTAL_IDX]); + } + + w->charts_created |= 1<<EBPF_MODULE_VFS_IDX; + } + + em->apps_charts |= NETDATA_EBPF_APPS_FLAG_CHART_CREATED; +} + +/***************************************************************** + * + * 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 apps is apps enabled? + */ +static void ebpf_vfs_allocate_global_vectors() +{ + vfs_vector = callocz(ebpf_nprocs, sizeof(netdata_publish_vfs_t)); + + memset(vfs_aggregated_data, 0, sizeof(vfs_aggregated_data)); + memset(vfs_publish_aggregated, 0, sizeof(vfs_publish_aggregated)); + + vfs_hash_values = callocz(ebpf_nprocs, sizeof(netdata_idx_t)); +} + +/***************************************************************** + * + * EBPF VFS THREAD + * + *****************************************************************/ + +/* + * Load BPF + * + * Load BPF files. + * + * @param em the structure with configuration + */ +static int ebpf_vfs_load_bpf(ebpf_module_t *em) +{ +#ifdef LIBBPF_MAJOR_VERSION + ebpf_define_map_type(em->maps, em->maps_per_core, running_on_kernel); +#endif + + int ret = 0; + ebpf_adjust_apps_cgroup(em, em->targets[NETDATA_EBPF_VFS_WRITE].mode); + if (em->load & EBPF_LOAD_LEGACY) { + em->probe_links = ebpf_load_program(ebpf_plugin_dir, em, running_on_kernel, isrh, &em->objects); + if (!em->probe_links) { + ret = -1; + } + } +#ifdef LIBBPF_MAJOR_VERSION + else { + vfs_bpf_obj = vfs_bpf__open(); + if (!vfs_bpf_obj) + ret = -1; + else + ret = ebpf_vfs_load_and_attach(vfs_bpf_obj, em); + } +#endif + + return ret; +} + +/** + * Process thread + * + * Thread used to generate process charts. + * + * @param ptr a pointer to `struct ebpf_module` + * + * @return It always return NULL + */ +void *ebpf_vfs_thread(void *ptr) +{ + ebpf_module_t *em = (ebpf_module_t *)ptr; + + CLEANUP_FUNCTION_REGISTER(ebpf_vfs_exit) cleanup_ptr = em; + + em->maps = vfs_maps; + + ebpf_update_pid_table(&vfs_maps[NETDATA_VFS_PID], em); + + ebpf_vfs_allocate_global_vectors(); + +#ifdef LIBBPF_MAJOR_VERSION + ebpf_adjust_thread_load(em, default_btf); +#endif + if (ebpf_vfs_load_bpf(em)) { + goto endvfs; + } + + int algorithms[NETDATA_KEY_PUBLISH_VFS_END] = { + NETDATA_EBPF_INCREMENTAL_IDX, NETDATA_EBPF_INCREMENTAL_IDX,NETDATA_EBPF_INCREMENTAL_IDX, + NETDATA_EBPF_INCREMENTAL_IDX, NETDATA_EBPF_INCREMENTAL_IDX,NETDATA_EBPF_INCREMENTAL_IDX + }; + + ebpf_global_labels(vfs_aggregated_data, vfs_publish_aggregated, vfs_dimension_names, + vfs_id_names, algorithms, NETDATA_KEY_PUBLISH_VFS_END); + + pthread_mutex_lock(&lock); + ebpf_create_global_charts(em); + ebpf_update_stats(&plugin_statistics, em); + ebpf_update_kernel_memory_with_vector(&plugin_statistics, em->maps, EBPF_ACTION_STAT_ADD); + + pthread_mutex_unlock(&lock); + + ebpf_read_vfs.thread = nd_thread_create(ebpf_read_vfs.name, NETDATA_THREAD_OPTION_DEFAULT, ebpf_read_vfs_thread, em); + + vfs_collector(em); + +endvfs: + ebpf_update_disabled_plugin_stats(em); + + return NULL; +} diff --git a/src/collectors/ebpf.plugin/ebpf_vfs.h b/src/collectors/ebpf.plugin/ebpf_vfs.h new file mode 100644 index 000000000..398e28317 --- /dev/null +++ b/src/collectors/ebpf.plugin/ebpf_vfs.h @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_EBPF_VFS_H +#define NETDATA_EBPF_VFS_H 1 + +// Module name & description +#define NETDATA_EBPF_MODULE_NAME_VFS "vfs" +#define NETDATA_EBPF_VFS_MODULE_DESC "Monitor VFS (Virtual File System) functions. This thread is integrated with apps and cgroup." + +#define NETDATA_DIRECTORY_VFS_CONFIG_FILE "vfs.conf" + +// Global chart name +#define NETDATA_VFS_FILE_CLEAN_COUNT "vfs_deleted_objects" +#define NETDATA_VFS_FILE_IO_COUNT "vfs_io" +#define NETDATA_VFS_FILE_ERR_COUNT "vfs_io_error" +#define NETDATA_VFS_IO_FILE_BYTES "vfs_io_bytes" +#define NETDATA_VFS_FSYNC "vfs_fsync" +#define NETDATA_VFS_FSYNC_ERR "vfs_fsync_error" +#define NETDATA_VFS_OPEN "vfs_open" +#define NETDATA_VFS_OPEN_ERR "vfs_open_error" +#define NETDATA_VFS_CREATE "vfs_create" +#define NETDATA_VFS_CREATE_ERR "vfs_create_error" + +// Charts created on Apps submenu +#define NETDATA_SYSCALL_APPS_FILE_DELETED "file_deleted" +#define NETDATA_SYSCALL_APPS_VFS_WRITE_CALLS "vfs_write_call" +#define NETDATA_SYSCALL_APPS_VFS_READ_CALLS "vfs_read_call" +#define NETDATA_SYSCALL_APPS_VFS_WRITE_BYTES "vfs_write_bytes" +#define NETDATA_SYSCALL_APPS_VFS_READ_BYTES "vfs_read_bytes" +#define NETDATA_SYSCALL_APPS_VFS_FSYNC "vfs_fsync" +#define NETDATA_SYSCALL_APPS_VFS_OPEN "vfs_open" +#define NETDATA_SYSCALL_APPS_VFS_CREATE "vfs_create" + +#define NETDATA_SYSCALL_APPS_VFS_WRITE_CALLS_ERROR "vfs_write_error" +#define NETDATA_SYSCALL_APPS_VFS_READ_CALLS_ERROR "vfs_read_error" +#define NETDATA_SYSCALL_APPS_VFS_FSYNC_CALLS_ERROR "vfs_fsync_error" +#define NETDATA_SYSCALL_APPS_VFS_OPEN_CALLS_ERROR "vfs_open_error" +#define NETDATA_SYSCALL_APPS_VFS_CREATE_CALLS_ERROR "vfs_create_error" + +// Group used on Dashboard +#define NETDATA_VFS_GROUP "vfs" + +// Contexts +#define NETDATA_CGROUP_VFS_UNLINK_CONTEXT "cgroup.vfs_unlink" +#define NETDATA_CGROUP_VFS_WRITE_CONTEXT "cgroup.vfs_write" +#define NETDATA_CGROUP_VFS_WRITE_ERROR_CONTEXT "cgroup.vfs_write_error" +#define NETDATA_CGROUP_VFS_READ_CONTEXT "cgroup.vfs_read" +#define NETDATA_CGROUP_VFS_READ_ERROR_CONTEXT "cgroup.vfs_read_error" +#define NETDATA_CGROUP_VFS_WRITE_BYTES_CONTEXT "cgroup.vfs_write_bytes" +#define NETDATA_CGROUP_VFS_READ_BYTES_CONTEXT "cgroup.vfs_read_bytes" +#define NETDATA_CGROUP_VFS_CREATE_CONTEXT "cgroup.vfs_create" +#define NETDATA_CGROUP_VFS_CREATE_ERROR_CONTEXT "cgroup.vfs_create_error" +#define NETDATA_CGROUP_VFS_OPEN_CONTEXT "cgroup.vfs_open" +#define NETDATA_CGROUP_VFS_OPEN_ERROR_CONTEXT "cgroup.vfs_open_error" +#define NETDATA_CGROUP_VFS_FSYNC_CONTEXT "cgroup.vfs_fsync" +#define NETDATA_CGROUP_VFS_FSYNC_ERROR_CONTEXT "cgroup.vfs_fsync_error" + +#define NETDATA_SYSTEMD_VFS_UNLINK_CONTEXT "systemd.services.vfs_unlink" +#define NETDATA_SYSTEMD_VFS_WRITE_CONTEXT "systemd.services.vfs_write" +#define NETDATA_SYSTEMD_VFS_WRITE_ERROR_CONTEXT "systemd.services.vfs_write_error" +#define NETDATA_SYSTEMD_VFS_READ_CONTEXT "systemd.services.vfs_read" +#define NETDATA_SYSTEMD_VFS_READ_ERROR_CONTEXT "systemd.services.vfs_read_error" +#define NETDATA_SYSTEMD_VFS_WRITE_BYTES_CONTEXT "systemd.services.vfs_write_bytes" +#define NETDATA_SYSTEMD_VFS_READ_BYTES_CONTEXT "systemd.services.vfs_read_bytes" +#define NETDATA_SYSTEMD_VFS_CREATE_CONTEXT "systemd.services.vfs_create" +#define NETDATA_SYSTEMD_VFS_CREATE_ERROR_CONTEXT "systemd.services.vfs_create_error" +#define NETDATA_SYSTEMD_VFS_OPEN_CONTEXT "systemd.services.vfs_open" +#define NETDATA_SYSTEMD_VFS_OPEN_ERROR_CONTEXT "systemd.services.vfs_open_error" +#define NETDATA_SYSTEMD_VFS_FSYNC_CONTEXT "systemd.services.vfs_fsync" +#define NETDATA_SYSTEMD_VFS_FSYNC_ERROR_CONTEXT "systemd.services.vfs_fsync_error" + +// ARAL name +#define NETDATA_EBPF_VFS_ARAL_NAME "ebpf_vfs" + +// dimension +#define EBPF_COMMON_UNITS_BYTES "bytes/s" + +typedef struct netdata_publish_vfs { + uint64_t ct; + uint32_t tgid; + uint32_t uid; + uint32_t gid; + char name[TASK_COMM_LEN]; + + //Counter + uint32_t write_call; + uint32_t writev_call; + uint32_t read_call; + uint32_t readv_call; + uint32_t unlink_call; + uint32_t fsync_call; + uint32_t open_call; + uint32_t create_call; + + //Accumulator + uint64_t write_bytes; + uint64_t writev_bytes; + uint64_t readv_bytes; + uint64_t read_bytes; + + //Counter + uint32_t write_err; + uint32_t writev_err; + uint32_t read_err; + uint32_t readv_err; + uint32_t unlink_err; + uint32_t fsync_err; + uint32_t open_err; + uint32_t create_err; +} netdata_publish_vfs_t; + +enum netdata_publish_vfs_list { + NETDATA_KEY_PUBLISH_VFS_UNLINK, + NETDATA_KEY_PUBLISH_VFS_READ, + NETDATA_KEY_PUBLISH_VFS_WRITE, + NETDATA_KEY_PUBLISH_VFS_FSYNC, + NETDATA_KEY_PUBLISH_VFS_OPEN, + NETDATA_KEY_PUBLISH_VFS_CREATE, + + NETDATA_KEY_PUBLISH_VFS_END +}; + +enum vfs_counters { + NETDATA_KEY_CALLS_VFS_WRITE, + NETDATA_KEY_ERROR_VFS_WRITE, + NETDATA_KEY_BYTES_VFS_WRITE, + + NETDATA_KEY_CALLS_VFS_WRITEV, + NETDATA_KEY_ERROR_VFS_WRITEV, + NETDATA_KEY_BYTES_VFS_WRITEV, + + NETDATA_KEY_CALLS_VFS_READ, + NETDATA_KEY_ERROR_VFS_READ, + NETDATA_KEY_BYTES_VFS_READ, + + NETDATA_KEY_CALLS_VFS_READV, + NETDATA_KEY_ERROR_VFS_READV, + NETDATA_KEY_BYTES_VFS_READV, + + NETDATA_KEY_CALLS_VFS_UNLINK, + NETDATA_KEY_ERROR_VFS_UNLINK, + + NETDATA_KEY_CALLS_VFS_FSYNC, + NETDATA_KEY_ERROR_VFS_FSYNC, + + NETDATA_KEY_CALLS_VFS_OPEN, + NETDATA_KEY_ERROR_VFS_OPEN, + + NETDATA_KEY_CALLS_VFS_CREATE, + NETDATA_KEY_ERROR_VFS_CREATE, + + // Keep this as last and don't skip numbers as it is used as element counter + NETDATA_VFS_COUNTER +}; + +enum netdata_vfs_tables { + NETDATA_VFS_PID, + NETDATA_VFS_ALL, + NETDATA_VFS_CTRL +}; + +enum netdata_vfs_calls_name { + NETDATA_EBPF_VFS_WRITE, + NETDATA_EBPF_VFS_WRITEV, + NETDATA_EBPF_VFS_READ, + NETDATA_EBPF_VFS_READV, + NETDATA_EBPF_VFS_UNLINK, + NETDATA_EBPF_VFS_FSYNC, + NETDATA_EBPF_VFS_OPEN, + NETDATA_EBPF_VFS_CREATE, + + NETDATA_VFS_END_LIST +}; + +void *ebpf_vfs_thread(void *ptr); +void ebpf_vfs_create_apps_charts(struct ebpf_module *em, void *ptr); +void ebpf_vfs_release(netdata_publish_vfs_t *stat); +extern netdata_ebpf_targets_t vfs_targets[]; + +extern struct config vfs_config; + +#endif /* NETDATA_EBPF_VFS_H */ diff --git a/src/collectors/ebpf.plugin/integrations/ebpf_cachestat.md b/src/collectors/ebpf.plugin/integrations/ebpf_cachestat.md new file mode 100644 index 000000000..352bc0721 --- /dev/null +++ b/src/collectors/ebpf.plugin/integrations/ebpf_cachestat.md @@ -0,0 +1,179 @@ +<!--startmeta +custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/integrations/ebpf_cachestat.md" +meta_yaml: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/metadata.yaml" +sidebar_label: "eBPF Cachestat" +learn_status: "Published" +learn_rel_path: "Collecting Metrics/eBPF" +most_popular: False +message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE" +endmeta--> + +# eBPF Cachestat + + +<img src="https://netdata.cloud/img/ebpf.jpg" width="150"/> + + +Plugin: ebpf.plugin +Module: cachestat + +<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" /> + +## Overview + +Monitor Linux page cache events giving for users a general vision about how his kernel is manipulating files. + +Attach tracing (kprobe, trampoline) to internal kernel functions according options used to compile kernel. + +This collector is only supported on the following platforms: + +- Linux + +This collector supports collecting metrics from multiple instances of this integration, including remote instances. + +The plugin needs setuid because it loads data inside kernel. Netada sets necessary permission during installation time. + +### Default Behavior + +#### Auto-Detection + +The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) and presence of BTF files to decide which eBPF program will be attached. + +#### Limits + +The default configuration for this integration does not impose any limits on data collection. + +#### Performance Impact + +This thread will add overhead every time that an internal kernel function monitored by this thread is called. The estimated additional period of time is between 90-200ms per call on kernels that do not have BTF technology. + + +## Metrics + +Metrics grouped by *scope*. + +The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels. + + + +### Per eBPF Cachestat instance + +These metrics show total number of calls to functions inside kernel. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| mem.cachestat_ratio | ratio | % | +| mem.cachestat_dirties | dirty | page/s | +| mem.cachestat_hits | hit | hits/s | +| mem.cachestat_misses | miss | misses/s | + +### Per apps + +These Metrics show grouped information per apps group. + +Labels: + +| Label | Description | +|:-----------|:----------------| +| app_group | The name of the group defined in the configuration. | + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| app.ebpf_cachestat_hit_ratio | ratio | % | +| app.ebpf_cachestat_dirty_pages | pages | page/s | +| app.ebpf_cachestat_access | hits | hits/s | +| app.ebpf_cachestat_misses | misses | misses/s | + +### Per cgroup + + + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| cgroup.cachestat_ratio | ratio | % | +| cgroup.cachestat_dirties | dirty | page/s | +| cgroup.cachestat_hits | hit | hits/s | +| cgroup.cachestat_misses | miss | misses/s | +| services.cachestat_ratio | a dimension per systemd service | % | +| services.cachestat_dirties | a dimension per systemd service | page/s | +| services.cachestat_hits | a dimension per systemd service | hits/s | +| services.cachestat_misses | a dimension per systemd service | misses/s | + + + +## Alerts + +There are no alerts configured by default for this integration. + + +## Setup + +### Prerequisites + +#### Compile kernel + +Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. +When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files +with different names. + +Now follow steps: +1. Copy the configuration file to /usr/src/linux/.config. +2. Select the necessary options: make oldconfig +3. Compile your kernel image: make bzImage +4. Compile your modules: make modules +5. Copy your new kernel image for boot loader directory +6. Install the new modules: make modules_install +7. Generate an initial ramdisk image (`initrd`) if it is necessary. +8. Update your boot loader + + + +### Configuration + +#### File + +The configuration file name for this integration is `ebpf.d/cachestat.conf`. + + +You can edit the configuration file using the `edit-config` script from the +Netdata [config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory). + +```bash +cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata +sudo ./edit-config ebpf.d/cachestat.conf +``` +#### Options + +All options are defined inside section `[global]`. + + +<details open><summary>Config options</summary> + +| Name | Description | Default | Required | +|:----|:-----------|:-------|:--------:| +| update every | Data collection frequency. | 5 | no | +| ebpf load mode | Define whether plugin will monitor the call (`entry`) for the functions or it will also monitor the return (`return`). | entry | no | +| apps | Enable or disable integration with apps.plugin | no | no | +| cgroups | Enable or disable integration with cgroup.plugin | no | no | +| pid table size | Number of elements stored inside hash tables used to monitor calls per PID. | 32768 | no | +| ebpf type format | Define the file type to load an eBPF program. Three options are available: `legacy` (Attach only `kprobe`), `co-re` (Plugin tries to use `trampoline` when available), and `auto` (plugin check OS configuration before to load). | auto | no | +| ebpf co-re tracing | Select the attach method used by plugin when `co-re` is defined in previous option. Two options are available: `trampoline` (Option with lowest overhead), and `probe` (the same of legacy code). | trampoline | no | +| maps per core | Define how plugin will load their hash maps. When enabled (`yes`) plugin will load one hash table per core, instead to have centralized information. | yes | no | +| lifetime | Set default lifetime for thread when enabled by cloud. | 300 | no | + +</details> + +#### Examples +There are no configuration examples. + + diff --git a/src/collectors/ebpf.plugin/integrations/ebpf_dcstat.md b/src/collectors/ebpf.plugin/integrations/ebpf_dcstat.md new file mode 100644 index 000000000..5ca7a6a68 --- /dev/null +++ b/src/collectors/ebpf.plugin/integrations/ebpf_dcstat.md @@ -0,0 +1,177 @@ +<!--startmeta +custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/integrations/ebpf_dcstat.md" +meta_yaml: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/metadata.yaml" +sidebar_label: "eBPF DCstat" +learn_status: "Published" +learn_rel_path: "Collecting Metrics/eBPF" +most_popular: False +message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE" +endmeta--> + +# eBPF DCstat + + +<img src="https://netdata.cloud/img/ebpf.jpg" width="150"/> + + +Plugin: ebpf.plugin +Module: dcstat + +<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" /> + +## Overview + +Monitor directory cache events per application given an overall vision about files on memory or storage device. + +Attach tracing (kprobe, trampoline) to internal kernel functions according options used to compile kernel. + +This collector is only supported on the following platforms: + +- Linux + +This collector supports collecting metrics from multiple instances of this integration, including remote instances. + +The plugin needs setuid because it loads data inside kernel. Netada sets necessary permission during installation time. + +### Default Behavior + +#### Auto-Detection + +The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) and presence of BTF files to decide which eBPF program will be attached. + +#### Limits + +The default configuration for this integration does not impose any limits on data collection. + +#### Performance Impact + +This thread will add overhead every time that an internal kernel function monitored by this thread is called. The estimated additional period of time is between 90-200ms per call on kernels that do not have BTF technology. + + +## Metrics + +Metrics grouped by *scope*. + +The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels. + + + +### Per apps + +These Metrics show grouped information per apps group. + +Labels: + +| Label | Description | +|:-----------|:----------------| +| app_group | The name of the group defined in the configuration. | + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| app.ebpf_dc_ratio | ratio | % | +| app.ebpf_dc_reference | files | files | +| app.ebpf_dc_not_cache | files | files | +| app.ebpf_dc_not_found | files | files | + +### Per filesystem + +These metrics show total number of calls to functions inside kernel. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| filesystem.dc_reference | reference, slow, miss | files | +| filesystem.dc_hit_ratio | ratio | % | + +### Per cgroup + + + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| cgroup.dc_ratio | ratio | % | +| cgroup.dc_reference | reference | files | +| cgroup.dc_not_cache | slow | files | +| cgroup.dc_not_found | miss | files | +| services.dc_ratio | a dimension per systemd service | % | +| services.dc_reference | a dimension per systemd service | files | +| services.dc_not_cache | a dimension per systemd service | files | +| services.dc_not_found | a dimension per systemd service | files | + + + +## Alerts + +There are no alerts configured by default for this integration. + + +## Setup + +### Prerequisites + +#### Compile kernel + +Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. +When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files +with different names. + +Now follow steps: +1. Copy the configuration file to /usr/src/linux/.config. +2. Select the necessary options: make oldconfig +3. Compile your kernel image: make bzImage +4. Compile your modules: make modules +5. Copy your new kernel image for boot loader directory +6. Install the new modules: make modules_install +7. Generate an initial ramdisk image (`initrd`) if it is necessary. +8. Update your boot loader + + + +### Configuration + +#### File + +The configuration file name for this integration is `ebpf.d/dcstat.conf`. + + +You can edit the configuration file using the `edit-config` script from the +Netdata [config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory). + +```bash +cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata +sudo ./edit-config ebpf.d/dcstat.conf +``` +#### Options + +All options are defined inside section `[global]`. + + +<details open><summary>Config option</summary> + +| Name | Description | Default | Required | +|:----|:-----------|:-------|:--------:| +| update every | Data collection frequency. | 5 | no | +| ebpf load mode | Define whether plugin will monitor the call (`entry`) for the functions or it will also monitor the return (`return`). | entry | no | +| apps | Enable or disable integration with apps.plugin | no | no | +| cgroups | Enable or disable integration with cgroup.plugin | no | no | +| pid table size | Number of elements stored inside hash tables used to monitor calls per PID. | 32768 | no | +| ebpf type format | Define the file type to load an eBPF program. Three options are available: `legacy` (Attach only `kprobe`), `co-re` (Plugin tries to use `trampoline` when available), and `auto` (plugin check OS configuration before to load). | auto | no | +| ebpf co-re tracing | Select the attach method used by plugin when `co-re` is defined in previous option. Two options are available: `trampoline` (Option with lowest overhead), and `probe` (the same of legacy code). | trampoline | no | +| maps per core | Define how plugin will load their hash maps. When enabled (`yes`) plugin will load one hash table per core, instead to have centralized information. | yes | no | +| lifetime | Set default lifetime for thread when enabled by cloud. | 300 | no | + +</details> + +#### Examples +There are no configuration examples. + + diff --git a/src/collectors/ebpf.plugin/integrations/ebpf_disk.md b/src/collectors/ebpf.plugin/integrations/ebpf_disk.md new file mode 100644 index 000000000..4fc3dc700 --- /dev/null +++ b/src/collectors/ebpf.plugin/integrations/ebpf_disk.md @@ -0,0 +1,137 @@ +<!--startmeta +custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/integrations/ebpf_disk.md" +meta_yaml: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/metadata.yaml" +sidebar_label: "eBPF Disk" +learn_status: "Published" +learn_rel_path: "Collecting Metrics/eBPF" +most_popular: False +message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE" +endmeta--> + +# eBPF Disk + + +<img src="https://netdata.cloud/img/ebpf.jpg" width="150"/> + + +Plugin: ebpf.plugin +Module: disk + +<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" /> + +## Overview + +Measure latency for I/O events on disk. + +Attach tracepoints to internal kernel functions. + +This collector is only supported on the following platforms: + +- Linux + +This collector supports collecting metrics from multiple instances of this integration, including remote instances. + +The plugin needs setuid because it loads data inside kernel. Netada sets necessary permission during installation time. + +### Default Behavior + +#### Auto-Detection + +The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT), files inside debugfs, and presence of BTF files to decide which eBPF program will be attached. + +#### Limits + +The default configuration for this integration does not impose any limits on data collection. + +#### Performance Impact + +This thread will add overhead every time that an internal kernel function monitored by this thread is called. + + +## Metrics + +Metrics grouped by *scope*. + +The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels. + + + +### Per disk + +These metrics measure latency for I/O events on every hard disk present on host. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| disk.latency_io | latency | calls/s | + + + +## Alerts + +There are no alerts configured by default for this integration. + + +## Setup + +### Prerequisites + +#### Compile kernel + +Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. +When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files +with different names. + +Now follow steps: +1. Copy the configuration file to /usr/src/linux/.config. +2. Select the necessary options: make oldconfig +3. Compile your kernel image: make bzImage +4. Compile your modules: make modules +5. Copy your new kernel image for boot loader directory +6. Install the new modules: make modules_install +7. Generate an initial ramdisk image (`initrd`) if it is necessary. +8. Update your boot loader + + +#### Debug Filesystem + +This thread needs to attach a tracepoint to monitor when a process schedule an exit event. To allow this specific feaure, it is necessary to mount `debugfs` (`mount -t debugfs none /sys/kernel/debug/`).` + + + +### Configuration + +#### File + +The configuration file name for this integration is `ebpf.d/disk.conf`. + + +You can edit the configuration file using the `edit-config` script from the +Netdata [config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory). + +```bash +cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata +sudo ./edit-config ebpf.d/disk.conf +``` +#### Options + +All options are defined inside section `[global]`. + + +<details open><summary>Config options</summary> + +| Name | Description | Default | Required | +|:----|:-----------|:-------|:--------:| +| update every | Data collection frequency. | 5 | no | +| ebpf load mode | Define whether plugin will monitor the call (`entry`) for the functions or it will also monitor the return (`return`). | entry | no | +| lifetime | Set default lifetime for thread when enabled by cloud. | 300 | no | + +</details> + +#### Examples +There are no configuration examples. + + diff --git a/src/collectors/ebpf.plugin/integrations/ebpf_filedescriptor.md b/src/collectors/ebpf.plugin/integrations/ebpf_filedescriptor.md new file mode 100644 index 000000000..2f917d183 --- /dev/null +++ b/src/collectors/ebpf.plugin/integrations/ebpf_filedescriptor.md @@ -0,0 +1,177 @@ +<!--startmeta +custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/integrations/ebpf_filedescriptor.md" +meta_yaml: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/metadata.yaml" +sidebar_label: "eBPF Filedescriptor" +learn_status: "Published" +learn_rel_path: "Collecting Metrics/eBPF" +most_popular: False +message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE" +endmeta--> + +# eBPF Filedescriptor + + +<img src="https://netdata.cloud/img/ebpf.jpg" width="150"/> + + +Plugin: ebpf.plugin +Module: filedescriptor + +<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" /> + +## Overview + +Monitor calls for functions responsible to open or close a file descriptor and possible errors. + +Attach tracing (kprobe and trampoline) to internal kernel functions according options used to compile kernel. + +This collector is only supported on the following platforms: + +- Linux + +This collector supports collecting metrics from multiple instances of this integration, including remote instances. + +The plugin needs setuid because it loads data inside kernel. Netdata sets necessary permissions during installation time. + +### Default Behavior + +#### Auto-Detection + +The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) and presence of BTF files to decide which eBPF program will be attached. + +#### Limits + +The default configuration for this integration does not impose any limits on data collection. + +#### Performance Impact + +Depending of kernel version and frequency that files are open and close, this thread will add overhead every time that an internal kernel function monitored by this thread is called. The estimated additional period of time is between 90-200ms per call on kernels that do not have BTF technology. + + +## Metrics + +Metrics grouped by *scope*. + +The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels. + + + +### Per cgroup + +These Metrics show grouped information per cgroup/service. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| cgroup.fd_open | open | calls/s | +| cgroup.fd_open_error | open | calls/s | +| cgroup.fd_closed | close | calls/s | +| cgroup.fd_close_error | close | calls/s | +| services.file_open | a dimension per systemd service | calls/s | +| services.file_open_error | a dimension per systemd service | calls/s | +| services.file_closed | a dimension per systemd service | calls/s | +| services.file_close_error | a dimension per systemd service | calls/s | + +### Per eBPF Filedescriptor instance + +These metrics show total number of calls to functions inside kernel. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| filesystem.file_descriptor | open, close | calls/s | +| filesystem.file_error | open, close | calls/s | + +### Per apps + +These Metrics show grouped information per apps group. + +Labels: + +| Label | Description | +|:-----------|:----------------| +| app_group | The name of the group defined in the configuration. | + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| app.ebpf_file_open | calls | calls/s | +| app.ebpf_file_open_error | calls | calls/s | +| app.ebpf_file_closed | calls | calls/s | +| app.ebpf_file_close_error | calls | calls/s | + + + +## Alerts + +There are no alerts configured by default for this integration. + + +## Setup + +### Prerequisites + +#### Compile kernel + +Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. +When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files +with different names. + +Now follow steps: +1. Copy the configuration file to /usr/src/linux/.config. +2. Select the necessary options: make oldconfig +3. Compile your kernel image: make bzImage +4. Compile your modules: make modules +5. Copy your new kernel image for boot loader directory +6. Install the new modules: make modules_install +7. Generate an initial ramdisk image (`initrd`) if it is necessary. +8. Update your boot loader + + + +### Configuration + +#### File + +The configuration file name for this integration is `ebpf.d/fd.conf`. + + +You can edit the configuration file using the `edit-config` script from the +Netdata [config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory). + +```bash +cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata +sudo ./edit-config ebpf.d/fd.conf +``` +#### Options + +All options are defined inside section `[global]`. + + +<details open><summary>Config options</summary> + +| Name | Description | Default | Required | +|:----|:-----------|:-------|:--------:| +| update every | Data collection frequency. | 5 | no | +| ebpf load mode | Define whether plugin will monitor the call (`entry`) for the functions or it will also monitor the return (`return`). | entry | no | +| apps | Enable or disable integration with apps.plugin | no | no | +| cgroups | Enable or disable integration with cgroup.plugin | no | no | +| pid table size | Number of elements stored inside hash tables used to monitor calls per PID. | 32768 | no | +| ebpf type format | Define the file type to load an eBPF program. Three options are available: `legacy` (Attach only `kprobe`), `co-re` (Plugin tries to use `trampoline` when available), and `auto` (plugin check OS configuration before to load). | auto | no | +| ebpf co-re tracing | Select the attach method used by plugin when `co-re` is defined in previous option. Two options are available: `trampoline` (Option with lowest overhead), and `probe` (the same of legacy code). | trampoline | no | +| maps per core | Define how plugin will load their hash maps. When enabled (`yes`) plugin will load one hash table per core, instead to have centralized information. | yes | no | +| lifetime | Set default lifetime for thread when enabled by cloud. | 300 | no | + +</details> + +#### Examples +There are no configuration examples. + + diff --git a/src/collectors/ebpf.plugin/integrations/ebpf_filesystem.md b/src/collectors/ebpf.plugin/integrations/ebpf_filesystem.md new file mode 100644 index 000000000..ea55a6c04 --- /dev/null +++ b/src/collectors/ebpf.plugin/integrations/ebpf_filesystem.md @@ -0,0 +1,163 @@ +<!--startmeta +custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/integrations/ebpf_filesystem.md" +meta_yaml: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/metadata.yaml" +sidebar_label: "eBPF Filesystem" +learn_status: "Published" +learn_rel_path: "Collecting Metrics/eBPF" +most_popular: False +message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE" +endmeta--> + +# eBPF Filesystem + + +<img src="https://netdata.cloud/img/ebpf.jpg" width="150"/> + + +Plugin: ebpf.plugin +Module: filesystem + +<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" /> + +## Overview + +Monitor latency for main actions on filesystem like I/O events. + +Attach tracing (kprobe, trampoline) to internal kernel functions according options used to compile kernel. + +This collector is only supported on the following platforms: + +- Linux + +This collector supports collecting metrics from multiple instances of this integration, including remote instances. + +The plugin needs setuid because it loads data inside kernel. Netada sets necessary permission during installation time. + +### Default Behavior + +#### Auto-Detection + +The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT), files inside debugfs, and presence of BTF files to decide which eBPF program will be attached. + +#### Limits + +The default configuration for this integration does not impose any limits on data collection. + +#### Performance Impact + +The default configuration for this integration is not expected to impose a significant performance impact on the system. + + +## Metrics + +Metrics grouped by *scope*. + +The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels. + + + +### Per filesystem + +Latency charts associate with filesystem actions. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| filesystem.read_latency | latency period | calls/s | +| filesystem.open_latency | latency period | calls/s | +| filesystem.sync_latency | latency period | calls/s | + +### Per iilesystem + + + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| filesystem.write_latency | latency period | calls/s | + +### Per eBPF Filesystem instance + + + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| filesystem.attributte_latency | latency period | calls/s | + + + +## Alerts + +There are no alerts configured by default for this integration. + + +## Setup + +### Prerequisites + +#### Compile kernel + +Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. +When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files +with different names. + +Now follow steps: +1. Copy the configuration file to /usr/src/linux/.config. +2. Select the necessary options: make oldconfig +3. Compile your kernel image: make bzImage +4. Compile your modules: make modules +5. Copy your new kernel image for boot loader directory +6. Install the new modules: make modules_install +7. Generate an initial ramdisk image (`initrd`) if it is necessary. +8. Update your boot loader + + + +### Configuration + +#### File + +The configuration file name for this integration is `ebpf.d/filesystem.conf`. + + +You can edit the configuration file using the `edit-config` script from the +Netdata [config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory). + +```bash +cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata +sudo ./edit-config ebpf.d/filesystem.conf +``` +#### Options + +This configuration file have two different sections. The `[global]` overwrites default options, while `[filesystem]` allow user to select the filesystems to monitor. + + +<details open><summary>Config options</summary> + +| Name | Description | Default | Required | +|:----|:-----------|:-------|:--------:| +| update every | Data collection frequency. | 5 | no | +| ebpf load mode | Define whether plugin will monitor the call (`entry`) for the functions or it will also monitor the return (`return`). | entry | no | +| lifetime | Set default lifetime for thread when enabled by cloud. | 300 | no | +| btrfsdist | Enable or disable latency monitoring for functions associated with btrfs filesystem. | yes | no | +| ext4dist | Enable or disable latency monitoring for functions associated with ext4 filesystem. | yes | no | +| nfsdist | Enable or disable latency monitoring for functions associated with nfs filesystem. | yes | no | +| xfsdist | Enable or disable latency monitoring for functions associated with xfs filesystem. | yes | no | +| zfsdist | Enable or disable latency monitoring for functions associated with zfs filesystem. | yes | no | + +</details> + +#### Examples +There are no configuration examples. + + diff --git a/src/collectors/ebpf.plugin/integrations/ebpf_hardirq.md b/src/collectors/ebpf.plugin/integrations/ebpf_hardirq.md new file mode 100644 index 000000000..d5f79353f --- /dev/null +++ b/src/collectors/ebpf.plugin/integrations/ebpf_hardirq.md @@ -0,0 +1,137 @@ +<!--startmeta +custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/integrations/ebpf_hardirq.md" +meta_yaml: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/metadata.yaml" +sidebar_label: "eBPF Hardirq" +learn_status: "Published" +learn_rel_path: "Collecting Metrics/eBPF" +most_popular: False +message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE" +endmeta--> + +# eBPF Hardirq + + +<img src="https://netdata.cloud/img/ebpf.jpg" width="150"/> + + +Plugin: ebpf.plugin +Module: hardirq + +<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" /> + +## Overview + +Monitor latency for each HardIRQ available. + +Attach tracepoints to internal kernel functions. + +This collector is only supported on the following platforms: + +- Linux + +This collector supports collecting metrics from multiple instances of this integration, including remote instances. + +The plugin needs setuid because it loads data inside kernel. Netada sets necessary permission during installation time. + +### Default Behavior + +#### Auto-Detection + +The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT), files inside debugfs, and presence of BTF files to decide which eBPF program will be attached. + +#### Limits + +The default configuration for this integration does not impose any limits on data collection. + +#### Performance Impact + +This thread will add overhead every time that an internal kernel function monitored by this thread is called. + + +## Metrics + +Metrics grouped by *scope*. + +The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels. + + + +### Per eBPF Hardirq instance + +These metrics show latest timestamp for each hardIRQ available on host. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| system.hardirq_latency | hardirq names | milliseconds | + + + +## Alerts + +There are no alerts configured by default for this integration. + + +## Setup + +### Prerequisites + +#### Compile kernel + +Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. +When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files +with different names. + +Now follow steps: +1. Copy the configuration file to /usr/src/linux/.config. +2. Select the necessary options: make oldconfig +3. Compile your kernel image: make bzImage +4. Compile your modules: make modules +5. Copy your new kernel image for boot loader directory +6. Install the new modules: make modules_install +7. Generate an initial ramdisk image (`initrd`) if it is necessary. +8. Update your boot loader + + +#### Debug Filesystem + +This thread needs to attach a tracepoint to monitor when a process schedule an exit event. To allow this specific feaure, it is necessary to mount `debugfs` (`mount -t debugfs none /sys/kernel/debug/`). + + + +### Configuration + +#### File + +The configuration file name for this integration is `ebpf.d/hardirq.conf`. + + +You can edit the configuration file using the `edit-config` script from the +Netdata [config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory). + +```bash +cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata +sudo ./edit-config ebpf.d/hardirq.conf +``` +#### Options + +All options are defined inside section `[global]`. + + +<details open><summary>Config options</summary> + +| Name | Description | Default | Required | +|:----|:-----------|:-------|:--------:| +| update every | Data collection frequency. | 5 | no | +| ebpf load mode | Define whether plugin will monitor the call (`entry`) for the functions or it will also monitor the return (`return`). | entry | no | +| lifetime | Set default lifetime for thread when enabled by cloud. | 300 | no | + +</details> + +#### Examples +There are no configuration examples. + + diff --git a/src/collectors/ebpf.plugin/integrations/ebpf_mdflush.md b/src/collectors/ebpf.plugin/integrations/ebpf_mdflush.md new file mode 100644 index 000000000..369e8958f --- /dev/null +++ b/src/collectors/ebpf.plugin/integrations/ebpf_mdflush.md @@ -0,0 +1,132 @@ +<!--startmeta +custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/integrations/ebpf_mdflush.md" +meta_yaml: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/metadata.yaml" +sidebar_label: "eBPF MDflush" +learn_status: "Published" +learn_rel_path: "Collecting Metrics/eBPF" +most_popular: False +message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE" +endmeta--> + +# eBPF MDflush + + +<img src="https://netdata.cloud/img/ebpf.jpg" width="150"/> + + +Plugin: ebpf.plugin +Module: mdflush + +<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" /> + +## Overview + +Monitor when flush events happen between disks. + +Attach tracing (kprobe, trampoline) to internal kernel functions according options used to compile kernel. + +This collector is only supported on the following platforms: + +- Linux + +This collector supports collecting metrics from multiple instances of this integration, including remote instances. + +The plugin needs setuid because it loads data inside kernel. Netada sets necessary permission during installation time. + +### Default Behavior + +#### Auto-Detection + +The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) and presence of BTF files to decide which eBPF program will be attached. + +#### Limits + +The default configuration for this integration does not impose any limits on data collection. + +#### Performance Impact + +This thread will add overhead every time that `md_flush_request` is called. The estimated additional period of time is between 90-200ms per call on kernels that do not have BTF technology. + + +## Metrics + +Metrics grouped by *scope*. + +The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels. + + + +### Per eBPF MDflush instance + +Number of times md_flush_request was called since last time. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| mdstat.mdstat_flush | disk | flushes | + + + +## Alerts + +There are no alerts configured by default for this integration. + + +## Setup + +### Prerequisites + +#### Compile kernel + +Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. +When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files +with different names. + +Now follow steps: +1. Copy the configuration file to /usr/src/linux/.config. +2. Select the necessary options: make oldconfig +3. Compile your kernel image: make bzImage +4. Compile your modules: make modules +5. Copy your new kernel image for boot loader directory +6. Install the new modules: make modules_install +7. Generate an initial ramdisk image (`initrd`) if it is necessary. +8. Update your boot loader + + + +### Configuration + +#### File + +The configuration file name for this integration is `ebpf.d/mdflush.conf`. + + +You can edit the configuration file using the `edit-config` script from the +Netdata [config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory). + +```bash +cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata +sudo ./edit-config ebpf.d/mdflush.conf +``` +#### Options + +All options are defined inside section `[global]`. + + +<details open><summary>Config options</summary> + +| Name | Description | Default | Required | +|:----|:-----------|:-------|:--------:| +| update every | Data collection frequency. | 5 | no | +| ebpf load mode | Define whether plugin will monitor the call (`entry`) for the functions or it will also monitor the return (`return`). | entry | no | +| lifetime | Set default lifetime for thread when enabled by cloud. | 300 | no | + +</details> + +#### Examples +There are no configuration examples. + + diff --git a/src/collectors/ebpf.plugin/integrations/ebpf_mount.md b/src/collectors/ebpf.plugin/integrations/ebpf_mount.md new file mode 100644 index 000000000..5e6738e2c --- /dev/null +++ b/src/collectors/ebpf.plugin/integrations/ebpf_mount.md @@ -0,0 +1,140 @@ +<!--startmeta +custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/integrations/ebpf_mount.md" +meta_yaml: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/metadata.yaml" +sidebar_label: "eBPF Mount" +learn_status: "Published" +learn_rel_path: "Collecting Metrics/eBPF" +most_popular: False +message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE" +endmeta--> + +# eBPF Mount + + +<img src="https://netdata.cloud/img/ebpf.jpg" width="150"/> + + +Plugin: ebpf.plugin +Module: mount + +<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" /> + +## Overview + +Monitor calls for mount and umount syscall. + +Attach tracing (kprobe, trampoline) to internal kernel functions according options used to compile kernel. + +This collector is only supported on the following platforms: + +- Linux + +This collector supports collecting metrics from multiple instances of this integration, including remote instances. + +The plugin needs setuid because it loads data inside kernel. Netada sets necessary permission during installation time. + +### Default Behavior + +#### Auto-Detection + +The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT, CONFIG_HAVE_SYSCALL_TRACEPOINTS), files inside debugfs, and presence of BTF files to decide which eBPF program will be attached. + +#### Limits + +The default configuration for this integration does not impose any limits on data collection. + +#### Performance Impact + +This thread will add overhead every time that an internal kernel function monitored by this thread is called. The estimated additional period of time is between 90-200ms per call on kernels that do not have BTF technology. + + +## Metrics + +Metrics grouped by *scope*. + +The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels. + + + +### Per eBPF Mount instance + +Calls for syscalls mount an umount. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| mount_points.call | mount, umount | calls/s | +| mount_points.error | mount, umount | calls/s | + + + +## Alerts + +There are no alerts configured by default for this integration. + + +## Setup + +### Prerequisites + +#### Compile kernel + +Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. +When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files +with different names. + +Now follow steps: +1. Copy the configuration file to /usr/src/linux/.config. +2. Select the necessary options: make oldconfig +3. Compile your kernel image: make bzImage +4. Compile your modules: make modules +5. Copy your new kernel image for boot loader directory +6. Install the new modules: make modules_install +7. Generate an initial ramdisk image (`initrd`) if it is necessary. +8. Update your boot loader + + +#### Debug Filesystem + +This thread needs to attach a tracepoint to monitor when a process schedule an exit event. To allow this specific feaure, it is necessary to mount `debugfs` (`mount -t debugfs none /sys/kernel/debug/`).` + + + +### Configuration + +#### File + +The configuration file name for this integration is `ebpf.d/mount.conf`. + + +You can edit the configuration file using the `edit-config` script from the +Netdata [config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory). + +```bash +cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata +sudo ./edit-config ebpf.d/mount.conf +``` +#### Options + +All options are defined inside section `[global]`. + + +<details open><summary>Config options</summary> + +| Name | Description | Default | Required | +|:----|:-----------|:-------|:--------:| +| update every | Data collection frequency. | 5 | no | +| ebpf load mode | Define whether plugin will monitor the call (`entry`) for the functions or it will also monitor the return (`return`). | entry | no | +| ebpf type format | Define the file type to load an eBPF program. Three options are available: `legacy` (Attach only `kprobe`), `co-re` (Plugin tries to use `trampoline` when available), and `auto` (plugin check OS configuration before to load). | auto | no | +| ebpf co-re tracing | Select the attach method used by plugin when `co-re` is defined in previous option. Two options are available: `trampoline` (Option with lowest overhead), and `probe` (the same of legacy code). | trampoline | no | +| lifetime | Set default lifetime for thread when enabled by cloud. | 300 | no | + +</details> + +#### Examples +There are no configuration examples. + + diff --git a/src/collectors/ebpf.plugin/integrations/ebpf_oomkill.md b/src/collectors/ebpf.plugin/integrations/ebpf_oomkill.md new file mode 100644 index 000000000..d9e14f4fb --- /dev/null +++ b/src/collectors/ebpf.plugin/integrations/ebpf_oomkill.md @@ -0,0 +1,160 @@ +<!--startmeta +custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/integrations/ebpf_oomkill.md" +meta_yaml: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/metadata.yaml" +sidebar_label: "eBPF OOMkill" +learn_status: "Published" +learn_rel_path: "Collecting Metrics/eBPF" +most_popular: False +message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE" +endmeta--> + +# eBPF OOMkill + + +<img src="https://netdata.cloud/img/ebpf.jpg" width="150"/> + + +Plugin: ebpf.plugin +Module: oomkill + +<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" /> + +## Overview + +Monitor applications that reach out of memory. + +Attach tracepoint to internal kernel functions. + +This collector is only supported on the following platforms: + +- Linux + +This collector supports collecting metrics from multiple instances of this integration, including remote instances. + +The plugin needs setuid because it loads data inside kernel. Netada sets necessary permission during installation time. + +### Default Behavior + +#### Auto-Detection + +The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT), files inside debugfs, and presence of BTF files to decide which eBPF program will be attached. + +#### Limits + +The default configuration for this integration does not impose any limits on data collection. + +#### Performance Impact + +This thread will add overhead every time that an internal kernel function monitored by this thread is called. + + +## Metrics + +Metrics grouped by *scope*. + +The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels. + + + +### Per cgroup + +These metrics show cgroup/service that reached OOM. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| cgroup.oomkills | cgroup name | kills | +| services.oomkills | a dimension per systemd service | kills | + +### Per apps + +These metrics show cgroup/service that reached OOM. + +Labels: + +| Label | Description | +|:-----------|:----------------| +| app_group | The name of the group defined in the configuration. | + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| app.oomkill | kills | kills | + + + +## Alerts + +There are no alerts configured by default for this integration. + + +## Setup + +### Prerequisites + +#### Compile kernel + +Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. +When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files +with different names. + +Now follow steps: +1. Copy the configuration file to /usr/src/linux/.config. +2. Select the necessary options: make oldconfig +3. Compile your kernel image: make bzImage +4. Compile your modules: make modules +5. Copy your new kernel image for boot loader directory +6. Install the new modules: make modules_install +7. Generate an initial ramdisk image (`initrd`) if it is necessary. +8. Update your boot loader + + +#### Debug Filesystem + +This thread needs to attach a tracepoint to monitor when a process schedule an exit event. To allow this specific feaure, it is necessary to mount `debugfs` (`mount -t debugfs none /sys/kernel/debug/`). + + + +### Configuration + +#### File + +The configuration file name for this integration is `ebpf.d/oomkill.conf`. + + +You can edit the configuration file using the `edit-config` script from the +Netdata [config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory). + +```bash +cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata +sudo ./edit-config ebpf.d/oomkill.conf +``` +#### Options + +Overwrite default configuration reducing number of I/O events + + +#### Examples +There are no configuration examples. + + + +## Troubleshooting + +### update every + + + +### ebpf load mode + + + +### lifetime + + + + diff --git a/src/collectors/ebpf.plugin/integrations/ebpf_process.md b/src/collectors/ebpf.plugin/integrations/ebpf_process.md new file mode 100644 index 000000000..d6da09031 --- /dev/null +++ b/src/collectors/ebpf.plugin/integrations/ebpf_process.md @@ -0,0 +1,107 @@ +<!--startmeta +custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/integrations/ebpf_process.md" +meta_yaml: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/metadata.yaml" +sidebar_label: "eBPF Process" +learn_status: "Published" +learn_rel_path: "Collecting Metrics/eBPF" +most_popular: False +message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE" +endmeta--> + +# eBPF Process + + +<img src="https://netdata.cloud/img/ebpf.jpg" width="150"/> + + +Plugin: ebpf.plugin +Module: process + +<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" /> + +## Overview + +Monitor internal memory usage. + +Uses netdata internal statistic to monitor memory management by plugin. + +This collector is only supported on the following platforms: + +- Linux + +This collector supports collecting metrics from multiple instances of this integration, including remote instances. + + +### Default Behavior + +#### Auto-Detection + +This integration doesn't support auto-detection. + +#### Limits + +The default configuration for this integration does not impose any limits on data collection. + +#### Performance Impact + +The default configuration for this integration is not expected to impose a significant performance impact on the system. + + +## Metrics + +Metrics grouped by *scope*. + +The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels. + + + +### Per eBPF Process instance + +How plugin is allocating memory. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| netdata.ebpf_aral_stat_size | memory | bytes | +| netdata.ebpf_aral_stat_alloc | aral | calls | +| netdata.ebpf_threads | total, running | threads | +| netdata.ebpf_load_methods | legacy, co-re | methods | +| netdata.ebpf_kernel_memory | memory_locked | bytes | +| netdata.ebpf_hash_tables_count | hash_table | hash tables | +| netdata.ebpf_hash_tables_insert_pid_elements | thread | rows | +| netdata.ebpf_hash_tables_remove_pid_elements | thread | rows | + + + +## Alerts + +There are no alerts configured by default for this integration. + + +## Setup + +### Prerequisites + +#### Netdata flags. + +To have these charts you need to compile netdata with flag `NETDATA_DEV_MODE`. + + +### Configuration + +#### File + +There is no configuration file. +#### Options + + + +There are no configuration options. + +#### Examples +There are no configuration examples. + + diff --git a/src/collectors/ebpf.plugin/integrations/ebpf_processes.md b/src/collectors/ebpf.plugin/integrations/ebpf_processes.md new file mode 100644 index 000000000..8ff091da0 --- /dev/null +++ b/src/collectors/ebpf.plugin/integrations/ebpf_processes.md @@ -0,0 +1,187 @@ +<!--startmeta +custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/integrations/ebpf_processes.md" +meta_yaml: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/metadata.yaml" +sidebar_label: "eBPF Processes" +learn_status: "Published" +learn_rel_path: "Collecting Metrics/eBPF" +most_popular: False +message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE" +endmeta--> + +# eBPF Processes + + +<img src="https://netdata.cloud/img/ebpf.jpg" width="150"/> + + +Plugin: ebpf.plugin +Module: processes + +<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" /> + +## Overview + +Monitor calls for function creating tasks (threads and processes) inside Linux kernel. + +Attach tracing (kprobe or tracepoint, and trampoline) to internal kernel functions. + +This collector is only supported on the following platforms: + +- Linux + +This collector supports collecting metrics from multiple instances of this integration, including remote instances. + +The plugin needs setuid because it loads data inside kernel. Netada sets necessary permission during installation time. + +### Default Behavior + +#### Auto-Detection + +The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT), files inside debugfs, and presence of BTF files to decide which eBPF program will be attached. + +#### Limits + +The default configuration for this integration does not impose any limits on data collection. + +#### Performance Impact + +This thread will add overhead every time that an internal kernel function monitored by this thread is called. + + +## Metrics + +Metrics grouped by *scope*. + +The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels. + + + +### Per eBPF Processes instance + +These metrics show total number of calls to functions inside kernel. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| system.process_thread | process | calls/s | +| system.process_status | process, zombie | difference | +| system.exit | process | calls/s | +| system.task_error | task | calls/s | + +### Per apps + +These Metrics show grouped information per apps group. + +Labels: + +| Label | Description | +|:-----------|:----------------| +| app_group | The name of the group defined in the configuration. | + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| app.process_create | calls | calls/s | +| app.thread_create | call | calls/s | +| app.task_exit | call | calls/s | +| app.task_close | call | calls/s | +| app.task_error | app | calls/s | + +### Per cgroup + +These Metrics show grouped information per cgroup/service. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| cgroup.process_create | process | calls/s | +| cgroup.thread_create | thread | calls/s | +| cgroup.task_exit | exit | calls/s | +| cgroup.task_close | process | calls/s | +| cgroup.task_error | process | calls/s | +| services.process_create | a dimension per systemd service | calls/s | +| services.thread_create | a dimension per systemd service | calls/s | +| services.task_close | a dimension per systemd service | calls/s | +| services.task_exit | a dimension per systemd service | calls/s | +| services.task_error | a dimension per systemd service | calls/s | + + + +## Alerts + +There are no alerts configured by default for this integration. + + +## Setup + +### Prerequisites + +#### Compile kernel + +Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. +When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files +with different names. + +Now follow steps: +1. Copy the configuration file to /usr/src/linux/.config. +2. Select the necessary options: make oldconfig +3. Compile your kernel image: make bzImage +4. Compile your modules: make modules +5. Copy your new kernel image for boot loader directory +6. Install the new modules: make modules_install +7. Generate an initial ramdisk image (`initrd`) if it is necessary. +8. Update your boot loader + + +#### Debug Filesystem + +This thread needs to attach a tracepoint to monitor when a process schedule an exit event. To allow this specific feaure, it is necessary to mount `debugfs` (`mount -t debugfs none /sys/kernel/debug/`). + + + +### Configuration + +#### File + +The configuration file name for this integration is `ebpf.d/process.conf`. + + +You can edit the configuration file using the `edit-config` script from the +Netdata [config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory). + +```bash +cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata +sudo ./edit-config ebpf.d/process.conf +``` +#### Options + +All options are defined inside section `[global]`. + + +<details open><summary>Config options</summary> + +| Name | Description | Default | Required | +|:----|:-----------|:-------|:--------:| +| update every | Data collection frequency. | 5 | no | +| ebpf load mode | Define whether plugin will monitor the call (`entry`) for the functions or it will also monitor the return (`return`). | entry | no | +| apps | Enable or disable integration with apps.plugin | no | no | +| cgroups | Enable or disable integration with cgroup.plugin | no | no | +| pid table size | Number of elements stored inside hash tables used to monitor calls per PID. | 32768 | no | +| ebpf type format | Define the file type to load an eBPF program. Three options are available: `legacy` (Attach only `kprobe`), `co-re` (Plugin tries to use `trampoline` when available), and `auto` (plugin check OS configuration before to load). | auto | no | +| ebpf co-re tracing | Select the attach method used by plugin when `co-re` is defined in previous option. Two options are available: `trampoline` (Option with lowest overhead), and `probe` (the same of legacy code). This plugin will always try to attach a tracepoint, so option here will impact only function used to monitor task (thread and process) creation. | trampoline | no | +| maps per core | Define how plugin will load their hash maps. When enabled (`yes`) plugin will load one hash table per core, instead to have centralized information. | yes | no | +| lifetime | Set default lifetime for thread when enabled by cloud. | 300 | no | + +</details> + +#### Examples +There are no configuration examples. + + diff --git a/src/collectors/ebpf.plugin/integrations/ebpf_shm.md b/src/collectors/ebpf.plugin/integrations/ebpf_shm.md new file mode 100644 index 000000000..c65d3a85e --- /dev/null +++ b/src/collectors/ebpf.plugin/integrations/ebpf_shm.md @@ -0,0 +1,185 @@ +<!--startmeta +custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/integrations/ebpf_shm.md" +meta_yaml: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/metadata.yaml" +sidebar_label: "eBPF SHM" +learn_status: "Published" +learn_rel_path: "Collecting Metrics/eBPF" +most_popular: False +message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE" +endmeta--> + +# eBPF SHM + + +<img src="https://netdata.cloud/img/ebpf.jpg" width="150"/> + + +Plugin: ebpf.plugin +Module: shm + +<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" /> + +## Overview + +Monitor syscall responsible to manipulate shared memory. + +Attach tracing (kprobe, trampoline) to internal kernel functions according options used to compile kernel. + +This collector is only supported on the following platforms: + +- Linux + +This collector supports collecting metrics from multiple instances of this integration, including remote instances. + +The plugin needs setuid because it loads data inside kernel. Netada sets necessary permission during installation time. + +### Default Behavior + +#### Auto-Detection + +The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) and presence of BTF files to decide which eBPF program will be attached. + +#### Limits + +The default configuration for this integration does not impose any limits on data collection. + +#### Performance Impact + +This thread will add overhead every time that an internal kernel function monitored by this thread is called. The estimated additional period of time is between 90-200ms per call on kernels that do not have BTF technology. + + +## Metrics + +Metrics grouped by *scope*. + +The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels. + + + +### Per cgroup + +These Metrics show grouped information per cgroup/service. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| cgroup.shmget | get | calls/s | +| cgroup.shmat | at | calls/s | +| cgroup.shmdt | dt | calls/s | +| cgroup.shmctl | ctl | calls/s | +| services.shmget | a dimension per systemd service | calls/s | +| services.shmat | a dimension per systemd service | calls/s | +| services.shmdt | a dimension per systemd service | calls/s | +| services.shmctl | a dimension per systemd service | calls/s | + +### Per apps + +These Metrics show grouped information per apps group. + +Labels: + +| Label | Description | +|:-----------|:----------------| +| app_group | The name of the group defined in the configuration. | + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| app.ebpf_shmget_call | calls | calls/s | +| app.ebpf_shmat_call | calls | calls/s | +| app.ebpf_shmdt_call | calls | calls/s | +| app.ebpf_shmctl_call | calls | calls/s | + +### Per eBPF SHM instance + +These Metrics show number of calls for specified syscall. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| system.shared_memory_calls | get, at, dt, ctl | calls/s | + + + +## Alerts + +There are no alerts configured by default for this integration. + + +## Setup + +### Prerequisites + +#### Compile kernel + +Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. +When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files +with different names. + +Now follow steps: +1. Copy the configuration file to /usr/src/linux/.config. +2. Select the necessary options: make oldconfig +3. Compile your kernel image: make bzImage +4. Compile your modules: make modules +5. Copy your new kernel image for boot loader directory +6. Install the new modules: make modules_install +7. Generate an initial ramdisk image (`initrd`) if it is necessary. +8. Update your boot loader + + +#### Debug Filesystem + +This thread needs to attach a tracepoint to monitor when a process schedule an exit event. To allow this specific feaure, it is necessary to mount `debugfs` (`mount -t debugfs none /sys/kernel/debug/`).` + + + +### Configuration + +#### File + +The configuration file name for this integration is `ebpf.d/shm.conf`. + + +You can edit the configuration file using the `edit-config` script from the +Netdata [config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory). + +```bash +cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata +sudo ./edit-config ebpf.d/shm.conf +``` +#### Options + +This configuration file have two different sections. The `[global]` overwrites all default options, while `[syscalls]` allow user to select the syscall to monitor. + + +<details open><summary>Config options</summary> + +| Name | Description | Default | Required | +|:----|:-----------|:-------|:--------:| +| update every | Data collection frequency. | 5 | no | +| ebpf load mode | Define whether plugin will monitor the call (`entry`) for the functions or it will also monitor the return (`return`). | entry | no | +| apps | Enable or disable integration with apps.plugin | no | no | +| cgroups | Enable or disable integration with cgroup.plugin | no | no | +| pid table size | Number of elements stored inside hash tables used to monitor calls per PID. | 32768 | no | +| ebpf type format | Define the file type to load an eBPF program. Three options are available: `legacy` (Attach only `kprobe`), `co-re` (Plugin tries to use `trampoline` when available), and `auto` (plugin check OS configuration before to load). | auto | no | +| ebpf co-re tracing | Select the attach method used by plugin when `co-re` is defined in previous option. Two options are available: `trampoline` (Option with lowest overhead), and `probe` (the same of legacy code). | trampoline | no | +| maps per core | Define how plugin will load their hash maps. When enabled (`yes`) plugin will load one hash table per core, instead to have centralized information. | yes | no | +| lifetime | Set default lifetime for thread when enabled by cloud. | 300 | no | +| shmget | Enable or disable monitoring for syscall `shmget` | yes | no | +| shmat | Enable or disable monitoring for syscall `shmat` | yes | no | +| shmdt | Enable or disable monitoring for syscall `shmdt` | yes | no | +| shmctl | Enable or disable monitoring for syscall `shmctl` | yes | no | + +</details> + +#### Examples +There are no configuration examples. + + diff --git a/src/collectors/ebpf.plugin/integrations/ebpf_socket.md b/src/collectors/ebpf.plugin/integrations/ebpf_socket.md new file mode 100644 index 000000000..c5b613315 --- /dev/null +++ b/src/collectors/ebpf.plugin/integrations/ebpf_socket.md @@ -0,0 +1,201 @@ +<!--startmeta +custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/integrations/ebpf_socket.md" +meta_yaml: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/metadata.yaml" +sidebar_label: "eBPF Socket" +learn_status: "Published" +learn_rel_path: "Collecting Metrics/eBPF" +most_popular: False +message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE" +endmeta--> + +# eBPF Socket + + +<img src="https://netdata.cloud/img/ebpf.jpg" width="150"/> + + +Plugin: ebpf.plugin +Module: socket + +<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" /> + +## Overview + +Monitor bandwidth consumption per application for protocols TCP and UDP. + +Attach tracing (kprobe, trampoline) to internal kernel functions according options used to compile kernel. + +This collector is only supported on the following platforms: + +- Linux + +This collector supports collecting metrics from multiple instances of this integration, including remote instances. + +The plugin needs setuid because it loads data inside kernel. Netada sets necessary permission during installation time. + +### Default Behavior + +#### Auto-Detection + +The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) and presence of BTF files to decide which eBPF program will be attached. + +#### Limits + +The default configuration for this integration does not impose any limits on data collection. + +#### Performance Impact + +This thread will add overhead every time that an internal kernel function monitored by this thread is called. The estimated additional period of time is between 90-200ms per call on kernels that do not have BTF technology. + + +## Metrics + +Metrics grouped by *scope*. + +The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels. + + + +### Per eBPF Socket instance + +These metrics show total number of calls to functions inside kernel. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| ip.inbound_conn | connection_tcp | connections/s | +| ip.tcp_outbound_conn | received | connections/s | +| ip.tcp_functions | received, send, closed | calls/s | +| ip.total_tcp_bandwidth | received, send | kilobits/s | +| ip.tcp_error | received, send | calls/s | +| ip.tcp_retransmit | retransmited | calls/s | +| ip.udp_functions | received, send | calls/s | +| ip.total_udp_bandwidth | received, send | kilobits/s | +| ip.udp_error | received, send | calls/s | + +### Per apps + +These metrics show grouped information per apps group. + +Labels: + +| Label | Description | +|:-----------|:----------------| +| app_group | The name of the group defined in the configuration. | + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| app.ebpf_call_tcp_v4_connection | connections | connections/s | +| app.ebpf_call_tcp_v6_connection | connections | connections/s | +| app.ebpf_sock_bytes_sent | bandwidth | kilobits/s | +| app.ebpf_sock_bytes_received | bandwidth | kilobits/s | +| app.ebpf_call_tcp_sendmsg | calls | calls/s | +| app.ebpf_call_tcp_cleanup_rbuf | calls | calls/s | +| app.ebpf_call_tcp_retransmit | calls | calls/s | +| app.ebpf_call_udp_sendmsg | calls | calls/s | +| app.ebpf_call_udp_recvmsg | calls | calls/s | + +### Per cgroup + + + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| cgroup.net_conn_ipv4 | connected_v4 | connections/s | +| cgroup.net_conn_ipv6 | connected_v6 | connections/s | +| cgroup.net_bytes_recv | received | calls/s | +| cgroup.net_bytes_sent | sent | calls/s | +| cgroup.net_tcp_recv | received | calls/s | +| cgroup.net_tcp_send | sent | calls/s | +| cgroup.net_retransmit | retransmitted | calls/s | +| cgroup.net_udp_send | sent | calls/s | +| cgroup.net_udp_recv | received | calls/s | +| services.net_conn_ipv6 | a dimension per systemd service | connections/s | +| services.net_bytes_recv | a dimension per systemd service | kilobits/s | +| services.net_bytes_sent | a dimension per systemd service | kilobits/s | +| services.net_tcp_recv | a dimension per systemd service | calls/s | +| services.net_tcp_send | a dimension per systemd service | calls/s | +| services.net_tcp_retransmit | a dimension per systemd service | calls/s | +| services.net_udp_send | a dimension per systemd service | calls/s | +| services.net_udp_recv | a dimension per systemd service | calls/s | + + + +## Alerts + +There are no alerts configured by default for this integration. + + +## Setup + +### Prerequisites + +#### Compile kernel + +Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. +When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files +with different names. + +Now follow steps: +1. Copy the configuration file to /usr/src/linux/.config. +2. Select the necessary options: make oldconfig +3. Compile your kernel image: make bzImage +4. Compile your modules: make modules +5. Copy your new kernel image for boot loader directory +6. Install the new modules: make modules_install +7. Generate an initial ramdisk image (`initrd`) if it is necessary. +8. Update your boot loader + + + +### Configuration + +#### File + +The configuration file name for this integration is `ebpf.d/network.conf`. + + +You can edit the configuration file using the `edit-config` script from the +Netdata [config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory). + +```bash +cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata +sudo ./edit-config ebpf.d/network.conf +``` +#### Options + +All options are defined inside section `[global]`. Options inside `network connections` are ignored for while. + + +<details open><summary>Config options</summary> + +| Name | Description | Default | Required | +|:----|:-----------|:-------|:--------:| +| update every | Data collection frequency. | 5 | no | +| ebpf load mode | Define whether plugin will monitor the call (`entry`) for the functions or it will also monitor the return (`return`). | entry | no | +| apps | Enable or disable integration with apps.plugin | no | no | +| cgroups | Enable or disable integration with cgroup.plugin | no | no | +| bandwidth table size | Number of elements stored inside hash tables used to monitor calls per PID. | 16384 | no | +| ipv4 connection table size | Number of elements stored inside hash tables used to monitor calls per IPV4 connections. | 16384 | no | +| ipv6 connection table size | Number of elements stored inside hash tables used to monitor calls per IPV6 connections. | 16384 | no | +| udp connection table size | Number of temporary elements stored inside hash tables used to monitor UDP connections. | 4096 | no | +| ebpf type format | Define the file type to load an eBPF program. Three options are available: `legacy` (Attach only `kprobe`), `co-re` (Plugin tries to use `trampoline` when available), and `auto` (plugin check OS configuration before to load). | auto | no | +| ebpf co-re tracing | Select the attach method used by plugin when `co-re` is defined in previous option. Two options are available: `trampoline` (Option with lowest overhead), and `probe` (the same of legacy code). | trampoline | no | +| maps per core | Define how plugin will load their hash maps. When enabled (`yes`) plugin will load one hash table per core, instead to have centralized information. | yes | no | +| lifetime | Set default lifetime for thread when enabled by cloud. | 300 | no | + +</details> + +#### Examples +There are no configuration examples. + + diff --git a/src/collectors/ebpf.plugin/integrations/ebpf_softirq.md b/src/collectors/ebpf.plugin/integrations/ebpf_softirq.md new file mode 100644 index 000000000..1571dd4b5 --- /dev/null +++ b/src/collectors/ebpf.plugin/integrations/ebpf_softirq.md @@ -0,0 +1,137 @@ +<!--startmeta +custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/integrations/ebpf_softirq.md" +meta_yaml: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/metadata.yaml" +sidebar_label: "eBPF SoftIRQ" +learn_status: "Published" +learn_rel_path: "Collecting Metrics/eBPF" +most_popular: False +message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE" +endmeta--> + +# eBPF SoftIRQ + + +<img src="https://netdata.cloud/img/ebpf.jpg" width="150"/> + + +Plugin: ebpf.plugin +Module: softirq + +<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" /> + +## Overview + +Monitor latency for each SoftIRQ available. + +Attach kprobe to internal kernel functions. + +This collector is only supported on the following platforms: + +- Linux + +This collector supports collecting metrics from multiple instances of this integration, including remote instances. + +The plugin needs setuid because it loads data inside kernel. Netada sets necessary permission during installation time. + +### Default Behavior + +#### Auto-Detection + +The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT), files inside debugfs, and presence of BTF files to decide which eBPF program will be attached. + +#### Limits + +The default configuration for this integration does not impose any limits on data collection. + +#### Performance Impact + +This thread will add overhead every time that an internal kernel function monitored by this thread is called. + + +## Metrics + +Metrics grouped by *scope*. + +The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels. + + + +### Per eBPF SoftIRQ instance + +These metrics show latest timestamp for each softIRQ available on host. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| system.softirq_latency | soft IRQs | milliseconds | + + + +## Alerts + +There are no alerts configured by default for this integration. + + +## Setup + +### Prerequisites + +#### Compile kernel + +Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. +When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files +with different names. + +Now follow steps: +1. Copy the configuration file to /usr/src/linux/.config. +2. Select the necessary options: make oldconfig +3. Compile your kernel image: make bzImage +4. Compile your modules: make modules +5. Copy your new kernel image for boot loader directory +6. Install the new modules: make modules_install +7. Generate an initial ramdisk image (`initrd`) if it is necessary. +8. Update your boot loader + + +#### Debug Filesystem + +This thread needs to attach a tracepoint to monitor when a process schedule an exit event. To allow this specific feaure, it is necessary to mount `debugfs` (`mount -t debugfs none /sys/kernel/debug/`).` + + + +### Configuration + +#### File + +The configuration file name for this integration is `ebpf.d/softirq.conf`. + + +You can edit the configuration file using the `edit-config` script from the +Netdata [config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory). + +```bash +cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata +sudo ./edit-config ebpf.d/softirq.conf +``` +#### Options + +All options are defined inside section `[global]`. + + +<details open><summary>Config options</summary> + +| Name | Description | Default | Required | +|:----|:-----------|:-------|:--------:| +| update every | Data collection frequency. | 5 | no | +| ebpf load mode | Define whether plugin will monitor the call (`entry`) for the functions or it will also monitor the return (`return`). | entry | no | +| lifetime | Set default lifetime for thread when enabled by cloud. | 300 | no | + +</details> + +#### Examples +There are no configuration examples. + + diff --git a/src/collectors/ebpf.plugin/integrations/ebpf_swap.md b/src/collectors/ebpf.plugin/integrations/ebpf_swap.md new file mode 100644 index 000000000..4358ac71b --- /dev/null +++ b/src/collectors/ebpf.plugin/integrations/ebpf_swap.md @@ -0,0 +1,170 @@ +<!--startmeta +custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/integrations/ebpf_swap.md" +meta_yaml: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/metadata.yaml" +sidebar_label: "eBPF SWAP" +learn_status: "Published" +learn_rel_path: "Collecting Metrics/eBPF" +most_popular: False +message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE" +endmeta--> + +# eBPF SWAP + + +<img src="https://netdata.cloud/img/ebpf.jpg" width="150"/> + + +Plugin: ebpf.plugin +Module: swap + +<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" /> + +## Overview + +Monitors when swap has I/O events and applications executing events. + +Attach tracing (kprobe, trampoline) to internal kernel functions according options used to compile kernel. + +This collector is only supported on the following platforms: + +- Linux + +This collector supports collecting metrics from multiple instances of this integration, including remote instances. + +The plugin needs setuid because it loads data inside kernel. Netada sets necessary permission during installation time. + +### Default Behavior + +#### Auto-Detection + +The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) and presence of BTF files to decide which eBPF program will be attached. + +#### Limits + +The default configuration for this integration does not impose any limits on data collection. + +#### Performance Impact + +This thread will add overhead every time that an internal kernel function monitored by this thread is called. The estimated additional period of time is between 90-200ms per call on kernels that do not have BTF technology. + + +## Metrics + +Metrics grouped by *scope*. + +The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels. + + + +### Per cgroup + +These Metrics show grouped information per cgroup/service. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| cgroup.swap_read | read | calls/s | +| cgroup.swap_write | write | calls/s | +| services.swap_read | a dimension per systemd service | calls/s | +| services.swap_write | a dimension per systemd service | calls/s | + +### Per apps + +These Metrics show grouped information per apps group. + +Labels: + +| Label | Description | +|:-----------|:----------------| +| app_group | The name of the group defined in the configuration. | + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| app.ebpf_call_swap_readpage | a dimension per app group | calls/s | +| app.ebpf_call_swap_writepage | a dimension per app group | calls/s | + +### Per eBPF SWAP instance + +These metrics show total number of calls to functions inside kernel. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| mem.swapcalls | write, read | calls/s | + + + +## Alerts + +There are no alerts configured by default for this integration. + + +## Setup + +### Prerequisites + +#### Compile kernel + +Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. +When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files +with different names. + +Now follow steps: +1. Copy the configuration file to /usr/src/linux/.config. +2. Select the necessary options: make oldconfig +3. Compile your kernel image: make bzImage +4. Compile your modules: make modules +5. Copy your new kernel image for boot loader directory +6. Install the new modules: make modules_install +7. Generate an initial ramdisk image (`initrd`) if it is necessary. +8. Update your boot loader + + + +### Configuration + +#### File + +The configuration file name for this integration is `ebpf.d/swap.conf`. + + +You can edit the configuration file using the `edit-config` script from the +Netdata [config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory). + +```bash +cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata +sudo ./edit-config ebpf.d/swap.conf +``` +#### Options + +All options are defined inside section `[global]`. + + +<details open><summary>Config options</summary> + +| Name | Description | Default | Required | +|:----|:-----------|:-------|:--------:| +| update every | Data collection frequency. | 5 | no | +| ebpf load mode | Define whether plugin will monitor the call (`entry`) for the functions or it will also monitor the return (`return`). | entry | no | +| apps | Enable or disable integration with apps.plugin | no | no | +| cgroups | Enable or disable integration with cgroup.plugin | no | no | +| pid table size | Number of elements stored inside hash tables used to monitor calls per PID. | 32768 | no | +| ebpf type format | Define the file type to load an eBPF program. Three options are available: `legacy` (Attach only `kprobe`), `co-re` (Plugin tries to use `trampoline` when available), and `auto` (plugin check OS configuration before to load). | auto | no | +| ebpf co-re tracing | Select the attach method used by plugin when `co-re` is defined in previous option. Two options are available: `trampoline` (Option with lowest overhead), and `probe` (the same of legacy code). | trampoline | no | +| maps per core | Define how plugin will load their hash maps. When enabled (`yes`) plugin will load one hash table per core, instead to have centralized information. | yes | no | +| lifetime | Set default lifetime for thread when enabled by cloud. | 300 | no | + +</details> + +#### Examples +There are no configuration examples. + + diff --git a/src/collectors/ebpf.plugin/integrations/ebpf_sync.md b/src/collectors/ebpf.plugin/integrations/ebpf_sync.md new file mode 100644 index 000000000..08d69fada --- /dev/null +++ b/src/collectors/ebpf.plugin/integrations/ebpf_sync.md @@ -0,0 +1,157 @@ +<!--startmeta +custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/integrations/ebpf_sync.md" +meta_yaml: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/metadata.yaml" +sidebar_label: "eBPF Sync" +learn_status: "Published" +learn_rel_path: "Collecting Metrics/eBPF" +most_popular: False +message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE" +endmeta--> + +# eBPF Sync + + +<img src="https://netdata.cloud/img/ebpf.jpg" width="150"/> + + +Plugin: ebpf.plugin +Module: sync + +<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" /> + +## Overview + +Monitor syscall responsible to move data from memory to storage device. + +Attach tracing (kprobe, trampoline) to internal kernel functions according options used to compile kernel. + +This collector is only supported on the following platforms: + +- Linux + +This collector supports collecting metrics from multiple instances of this integration, including remote instances. + +The plugin needs setuid because it loads data inside kernel. Netada sets necessary permission during installation time. + +### Default Behavior + +#### Auto-Detection + +The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT, CONFIG_HAVE_SYSCALL_TRACEPOINTS), files inside debugfs, and presence of BTF files to decide which eBPF program will be attached. + +#### Limits + +The default configuration for this integration does not impose any limits on data collection. + +#### Performance Impact + +This thread will add overhead every time that an internal kernel function monitored by this thread is called. The estimated additional period of time is between 90-200ms per call on kernels that do not have BTF technology. + + +## Metrics + +Metrics grouped by *scope*. + +The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels. + + + +### Per eBPF Sync instance + +These metrics show total number of calls to functions inside kernel. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| mem.file_sync | fsync, fdatasync | calls/s | +| mem.memory_map | msync | calls/s | +| mem.sync | sync, syncfs | calls/s | +| mem.file_segment | sync_file_range | calls/s | + + + +## Alerts + + +The following alerts are available: + +| Alert name | On metric | Description | +|:------------|:----------|:------------| +| [ sync_freq ](https://github.com/netdata/netdata/blob/master/src/health/health.d/synchronization.conf) | mem.sync | number of sync() system calls. Every call causes all pending modifications to filesystem metadata and cached file data to be written to the underlying filesystems. | + + +## Setup + +### Prerequisites + +#### Compile kernel + +Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. +When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files +with different names. + +Now follow steps: +1. Copy the configuration file to /usr/src/linux/.config. +2. Select the necessary options: make oldconfig +3. Compile your kernel image: make bzImage +4. Compile your modules: make modules +5. Copy your new kernel image for boot loader directory +6. Install the new modules: make modules_install +7. Generate an initial ramdisk image (`initrd`) if it is necessary. +8. Update your boot loader + + +#### Debug Filesystem + +This thread needs to attach a tracepoint to monitor when a process schedule an exit event. To allow this specific feaure, it is necessary to mount `debugfs` (`mount -t debugfs none /sys/kernel/debug`). + + + +### Configuration + +#### File + +The configuration file name for this integration is `ebpf.d/sync.conf`. + + +You can edit the configuration file using the `edit-config` script from the +Netdata [config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory). + +```bash +cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata +sudo ./edit-config ebpf.d/sync.conf +``` +#### Options + +This configuration file have two different sections. The `[global]` overwrites all default options, while `[syscalls]` allow user to select the syscall to monitor. + + +<details open><summary>Config options</summary> + +| Name | Description | Default | Required | +|:----|:-----------|:-------|:--------:| +| update every | Data collection frequency. | 5 | no | +| ebpf load mode | Define whether plugin will monitor the call (`entry`) for the functions or it will also monitor the return (`return`). | entry | no | +| apps | Enable or disable integration with apps.plugin | no | no | +| cgroups | Enable or disable integration with cgroup.plugin | no | no | +| pid table size | Number of elements stored inside hash tables used to monitor calls per PID. | 32768 | no | +| ebpf type format | Define the file type to load an eBPF program. Three options are available: `legacy` (Attach only `kprobe`), `co-re` (Plugin tries to use `trampoline` when available), and `auto` (plugin check OS configuration before to load). | auto | no | +| ebpf co-re tracing | Select the attach method used by plugin when `co-re` is defined in previous option. Two options are available: `trampoline` (Option with lowest overhead), and `probe` (the same of legacy code). | trampoline | no | +| maps per core | Define how plugin will load their hash maps. When enabled (`yes`) plugin will load one hash table per core, instead to have centralized information. | yes | no | +| lifetime | Set default lifetime for thread when enabled by cloud. | 300 | no | +| sync | Enable or disable monitoring for syscall `sync` | yes | no | +| msync | Enable or disable monitoring for syscall `msync` | yes | no | +| fsync | Enable or disable monitoring for syscall `fsync` | yes | no | +| fdatasync | Enable or disable monitoring for syscall `fdatasync` | yes | no | +| syncfs | Enable or disable monitoring for syscall `syncfs` | yes | no | +| sync_file_range | Enable or disable monitoring for syscall `sync_file_range` | yes | no | + +</details> + +#### Examples +There are no configuration examples. + + diff --git a/src/collectors/ebpf.plugin/integrations/ebpf_vfs.md b/src/collectors/ebpf.plugin/integrations/ebpf_vfs.md new file mode 100644 index 000000000..3adb00e9b --- /dev/null +++ b/src/collectors/ebpf.plugin/integrations/ebpf_vfs.md @@ -0,0 +1,212 @@ +<!--startmeta +custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/integrations/ebpf_vfs.md" +meta_yaml: "https://github.com/netdata/netdata/edit/master/src/collectors/ebpf.plugin/metadata.yaml" +sidebar_label: "eBPF VFS" +learn_status: "Published" +learn_rel_path: "Collecting Metrics/eBPF" +most_popular: False +message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE" +endmeta--> + +# eBPF VFS + + +<img src="https://netdata.cloud/img/ebpf.jpg" width="150"/> + + +Plugin: ebpf.plugin +Module: vfs + +<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" /> + +## Overview + +Monitor I/O events on Linux Virtual Filesystem. + +Attach tracing (kprobe, trampoline) to internal kernel functions according options used to compile kernel. + +This collector is only supported on the following platforms: + +- Linux + +This collector supports collecting metrics from multiple instances of this integration, including remote instances. + +The plugin needs setuid because it loads data inside kernel. Netada sets necessary permission during installation time. + +### Default Behavior + +#### Auto-Detection + +The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) and presence of BTF files to decide which eBPF program will be attached. + +#### Limits + +The default configuration for this integration does not impose any limits on data collection. + +#### Performance Impact + +This thread will add overhead every time that an internal kernel function monitored by this thread is called. The estimated additional period of time is between 90-200ms per call on kernels that do not have BTF technology. + + +## Metrics + +Metrics grouped by *scope*. + +The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels. + + + +### Per cgroup + +These Metrics show grouped information per cgroup/service. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| cgroup.vfs_unlink | delete | calls/s | +| cgroup.vfs_write | write | calls/s | +| cgroup.vfs_write_error | write | calls/s | +| cgroup.vfs_read | read | calls/s | +| cgroup.vfs_read_error | read | calls/s | +| cgroup.vfs_write_bytes | write | bytes/s | +| cgroup.vfs_read_bytes | read | bytes/s | +| cgroup.vfs_fsync | fsync | calls/s | +| cgroup.vfs_fsync_error | fsync | calls/s | +| cgroup.vfs_open | open | calls/s | +| cgroup.vfs_open_error | open | calls/s | +| cgroup.vfs_create | create | calls/s | +| cgroup.vfs_create_error | create | calls/s | +| services.vfs_unlink | a dimension per systemd service | calls/s | +| services.vfs_write | a dimension per systemd service | calls/s | +| services.vfs_write_error | a dimension per systemd service | calls/s | +| services.vfs_read | a dimension per systemd service | calls/s | +| services.vfs_read_error | a dimension per systemd service | calls/s | +| services.vfs_write_bytes | a dimension per systemd service | bytes/s | +| services.vfs_read_bytes | a dimension per systemd service | bytes/s | +| services.vfs_fsync | a dimension per systemd service | calls/s | +| services.vfs_fsync_error | a dimension per systemd service | calls/s | +| services.vfs_open | a dimension per systemd service | calls/s | +| services.vfs_open_error | a dimension per systemd service | calls/s | +| services.vfs_create | a dimension per systemd service | calls/s | +| services.vfs_create_error | a dimension per systemd service | calls/s | + +### Per eBPF VFS instance + +These Metrics show grouped information per cgroup/service. + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| filesystem.vfs_deleted_objects | delete | calls/s | +| filesystem.vfs_io | read, write | calls/s | +| filesystem.vfs_io_bytes | read, write | bytes/s | +| filesystem.vfs_io_error | read, write | calls/s | +| filesystem.vfs_fsync | fsync | calls/s | +| filesystem.vfs_fsync_error | fsync | calls/s | +| filesystem.vfs_open | open | calls/s | +| filesystem.vfs_open_error | open | calls/s | +| filesystem.vfs_create | create | calls/s | +| filesystem.vfs_create_error | create | calls/s | + +### Per apps + +These Metrics show grouped information per apps group. + +Labels: + +| Label | Description | +|:-----------|:----------------| +| app_group | The name of the group defined in the configuration. | + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| app.ebpf_call_vfs_unlink | calls | calls/s | +| app.ebpf_call_vfs_write | calls | calls/s | +| app.ebpf_call_vfs_write_error | calls | calls/s | +| app.ebpf_call_vfs_read | calls | calls/s | +| app.ebpf_call_vfs_read_error | calls | calls/s | +| app.ebpf_call_vfs_write_bytes | writes | bytes/s | +| app.ebpf_call_vfs_read_bytes | reads | bytes/s | +| app.ebpf_call_vfs_fsync | calls | calls/s | +| app.ebpf_call_vfs_fsync_error | calls | calls/s | +| app.ebpf_call_vfs_open | calls | calls/s | +| app.ebpf_call_vfs_open_error | calls | calls/s | +| app.ebpf_call_vfs_create | calls | calls/s | +| app.ebpf_call_vfs_create_error | calls | calls/s | + + + +## Alerts + +There are no alerts configured by default for this integration. + + +## Setup + +### Prerequisites + +#### Compile kernel + +Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. +When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files +with different names. + +Now follow steps: +1. Copy the configuration file to /usr/src/linux/.config. +2. Select the necessary options: make oldconfig +3. Compile your kernel image: make bzImage +4. Compile your modules: make modules +5. Copy your new kernel image for boot loader directory +6. Install the new modules: make modules_install +7. Generate an initial ramdisk image (`initrd`) if it is necessary. +8. Update your boot loader + + + +### Configuration + +#### File + +The configuration file name for this integration is `ebpf.d/vfs.conf`. + + +You can edit the configuration file using the `edit-config` script from the +Netdata [config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory). + +```bash +cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata +sudo ./edit-config ebpf.d/vfs.conf +``` +#### Options + +All options are defined inside section `[global]`. + + +<details open><summary>Config options</summary> + +| Name | Description | Default | Required | +|:----|:-----------|:-------|:--------:| +| update every | Data collection frequency. | 5 | no | +| ebpf load mode | Define whether plugin will monitor the call (`entry`) for the functions or it will also monitor the return (`return`). | entry | no | +| apps | Enable or disable integration with apps.plugin | no | no | +| cgroups | Enable or disable integration with cgroup.plugin | no | no | +| pid table size | Number of elements stored inside hash tables used to monitor calls per PID. | 32768 | no | +| ebpf type format | Define the file type to load an eBPF program. Three options are available: `legacy` (Attach only `kprobe`), `co-re` (Plugin tries to use `trampoline` when available), and `auto` (plugin check OS configuration before to load). | auto | no | +| ebpf co-re tracing | Select the attach method used by plugin when `co-re` is defined in previous option. Two options are available: `trampoline` (Option with lowest overhead), and `probe` (the same of legacy code). | trampoline | no | +| maps per core | Define how plugin will load their hash maps. When enabled (`yes`) plugin will load one hash table per core, instead to have centralized information. | yes | no | +| lifetime | Set default lifetime for thread when enabled by cloud. | 300 | no | + +</details> + +#### Examples +There are no configuration examples. + + diff --git a/src/collectors/ebpf.plugin/metadata.yaml b/src/collectors/ebpf.plugin/metadata.yaml new file mode 100644 index 000000000..4921e44f0 --- /dev/null +++ b/src/collectors/ebpf.plugin/metadata.yaml @@ -0,0 +1,3296 @@ +plugin_name: ebpf.plugin +modules: + - meta: + plugin_name: ebpf.plugin + module_name: filedescriptor + monitored_instance: + name: eBPF Filedescriptor + link: "https://kernel.org/" + categories: + - data-collection.ebpf + icon_filename: "ebpf.jpg" + related_resources: + integrations: + list: + - plugin_name: apps.plugin + module_name: apps + - plugin_name: cgroups.plugin + module_name: cgroups + info_provided_to_referring_integrations: + description: "" + keywords: + - file + - eBPF + - fd + - open + - close + most_popular: false + overview: + data_collection: + metrics_description: "Monitor calls for functions responsible to open or close a file descriptor and possible errors." + method_description: "Attach tracing (kprobe and trampoline) to internal kernel functions according options used to compile kernel." + supported_platforms: + include: + - Linux + exclude: [] + multi_instance: true + additional_permissions: + description: "The plugin needs setuid because it loads data inside kernel. Netdata sets necessary permissions during installation time." + default_behavior: + auto_detection: + description: "The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) and presence of BTF files to decide which eBPF program will be attached." + limits: + description: "" + performance_impact: + description: "Depending of kernel version and frequency that files are open and close, this thread will add overhead every time that an internal kernel function monitored by this thread is called. The estimated additional period of time is between 90-200ms per call on kernels that do not have BTF technology." + setup: + prerequisites: + list: + - title: Compile kernel + description: | + Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. + When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files + with different names. + + Now follow steps: + 1. Copy the configuration file to /usr/src/linux/.config. + 2. Select the necessary options: make oldconfig + 3. Compile your kernel image: make bzImage + 4. Compile your modules: make modules + 5. Copy your new kernel image for boot loader directory + 6. Install the new modules: make modules_install + 7. Generate an initial ramdisk image (`initrd`) if it is necessary. + 8. Update your boot loader + configuration: + file: + name: "ebpf.d/fd.conf" + description: "Overwrite default configuration helping to reduce memory usage. You can also select charts visible on dashboard." + options: + description: | + All options are defined inside section `[global]`. + folding: + title: "Config options" + enabled: true + list: + - name: update every + description: Data collection frequency. + default_value: 5 + required: false + - name: ebpf load mode + description: Define whether plugin will monitor the call (`entry`) for the functions or it will also monitor the return (`return`). + default_value: entry + required: false + - name: apps + description: Enable or disable integration with apps.plugin + default_value: no + required: false + - name: cgroups + description: Enable or disable integration with cgroup.plugin + default_value: no + required: false + - name: pid table size + description: Number of elements stored inside hash tables used to monitor calls per PID. + default_value: 32768 + required: false + - name: ebpf type format + description: "Define the file type to load an eBPF program. Three options are available: `legacy` (Attach only `kprobe`), `co-re` (Plugin tries to use `trampoline` when available), and `auto` (plugin check OS configuration before to load)." + default_value: auto + required: false + - name: ebpf co-re tracing + description: "Select the attach method used by plugin when `co-re` is defined in previous option. Two options are available: `trampoline` (Option with lowest overhead), and `probe` (the same of legacy code)." + default_value: trampoline + required: false + - name: maps per core + description: Define how plugin will load their hash maps. When enabled (`yes`) plugin will load one hash table per core, instead to have centralized information. + default_value: yes + required: false + - name: lifetime + description: Set default lifetime for thread when enabled by cloud. + default_value: 300 + required: false + examples: + folding: + enabled: true + title: "" + list: [] + troubleshooting: + problems: + list: [] + alerts: [] + metrics: + folding: + title: Metrics + enabled: false + description: "" + availability: [] + scopes: + - name: cgroup + description: "These Metrics show grouped information per cgroup/service." + labels: [] + metrics: + - name: cgroup.fd_open + description: Number of open files + unit: "calls/s" + chart_type: line + dimensions: + - name: open + - name: cgroup.fd_open_error + description: Fails to open files + unit: "calls/s" + chart_type: line + dimensions: + - name: open + - name: cgroup.fd_closed + description: Files closed + unit: "calls/s" + chart_type: line + dimensions: + - name: close + - name: cgroup.fd_close_error + description: Fails to close files + unit: "calls/s" + chart_type: line + dimensions: + - name: close + - name: services.file_open + description: Number of open files + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: services.file_open_error + description: Fails to open files + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: services.file_closed + description: Files closed + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: services.file_close_error + description: Fails to close files + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: global + description: "These metrics show total number of calls to functions inside kernel." + labels: [] + metrics: + - name: filesystem.file_descriptor + description: Open and close calls + unit: "calls/s" + chart_type: line + dimensions: + - name: open + - name: close + - name: filesystem.file_error + description: Open fails + unit: "calls/s" + chart_type: line + dimensions: + - name: open + - name: close + - name: apps + description: "These Metrics show grouped information per apps group." + labels: + - name: app_group + description: The name of the group defined in the configuration. + metrics: + - name: app.ebpf_file_open + description: Number of open files + unit: "calls/s" + chart_type: stacked + dimensions: + - name: calls + - name: app.ebpf_file_open_error + description: Fails to open files + unit: "calls/s" + chart_type: stacked + dimensions: + - name: calls + - name: app.ebpf_file_closed + description: Files closed + unit: "calls/s" + chart_type: stacked + dimensions: + - name: calls + - name: app.ebpf_file_close_error + description: Fails to close files + unit: "calls/s" + chart_type: stacked + dimensions: + - name: calls + - meta: + plugin_name: ebpf.plugin + module_name: processes + monitored_instance: + name: eBPF Processes + link: "https://kernel.org/" + categories: + - data-collection.ebpf + icon_filename: "ebpf.jpg" + related_resources: + integrations: + list: + - plugin_name: apps.plugin + module_name: apps + - plugin_name: cgroups.plugin + module_name: cgroups + info_provided_to_referring_integrations: + description: "" + keywords: + - thread + - fork + - process + - eBPF + most_popular: false + overview: + data_collection: + metrics_description: "Monitor calls for function creating tasks (threads and processes) inside Linux kernel." + method_description: "Attach tracing (kprobe or tracepoint, and trampoline) to internal kernel functions." + supported_platforms: + include: + - Linux + exclude: [] + multi_instance: true + additional_permissions: + description: "The plugin needs setuid because it loads data inside kernel. Netada sets necessary permission during installation time." + default_behavior: + auto_detection: + description: "The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT), files inside debugfs, and presence of BTF files to decide which eBPF program will be attached." + limits: + description: "" + performance_impact: + description: "This thread will add overhead every time that an internal kernel function monitored by this thread is called." + setup: + prerequisites: + list: + - title: Compile kernel + description: | + Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. + When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files + with different names. + + Now follow steps: + 1. Copy the configuration file to /usr/src/linux/.config. + 2. Select the necessary options: make oldconfig + 3. Compile your kernel image: make bzImage + 4. Compile your modules: make modules + 5. Copy your new kernel image for boot loader directory + 6. Install the new modules: make modules_install + 7. Generate an initial ramdisk image (`initrd`) if it is necessary. + 8. Update your boot loader + - title: Debug Filesystem + description: | + This thread needs to attach a tracepoint to monitor when a process schedule an exit event. To allow this specific feaure, it is necessary to mount `debugfs` (`mount -t debugfs none /sys/kernel/debug/`). + configuration: + file: + name: "ebpf.d/process.conf" + description: "Overwrite default configuration helping to reduce memory usage. You can also select charts visible on dashboard." + options: + description: | + All options are defined inside section `[global]`. + folding: + title: "Config options" + enabled: true + list: + - name: update every + description: Data collection frequency. + default_value: 5 + required: false + - name: ebpf load mode + description: Define whether plugin will monitor the call (`entry`) for the functions or it will also monitor the return (`return`). + default_value: entry + required: false + - name: apps + description: Enable or disable integration with apps.plugin + default_value: no + required: false + - name: cgroups + description: Enable or disable integration with cgroup.plugin + default_value: no + required: false + - name: pid table size + description: Number of elements stored inside hash tables used to monitor calls per PID. + default_value: 32768 + required: false + - name: ebpf type format + description: "Define the file type to load an eBPF program. Three options are available: `legacy` (Attach only `kprobe`), `co-re` (Plugin tries to use `trampoline` when available), and `auto` (plugin check OS configuration before to load)." + default_value: auto + required: false + - name: ebpf co-re tracing + description: "Select the attach method used by plugin when `co-re` is defined in previous option. Two options are available: `trampoline` (Option with lowest overhead), and `probe` (the same of legacy code). This plugin will always try to attach a tracepoint, so option here will impact only function used to monitor task (thread and process) creation." + default_value: trampoline + required: false + - name: maps per core + description: Define how plugin will load their hash maps. When enabled (`yes`) plugin will load one hash table per core, instead to have centralized information. + default_value: yes + required: false + - name: lifetime + description: Set default lifetime for thread when enabled by cloud. + default_value: 300 + required: false + examples: + folding: + enabled: true + title: "" + list: [] + troubleshooting: + problems: + list: [] + alerts: [] + metrics: + folding: + title: Metrics + enabled: false + description: "" + availability: [] + scopes: + - name: global + description: "These metrics show total number of calls to functions inside kernel." + labels: [] + metrics: + - name: system.process_thread + description: Start process + unit: "calls/s" + chart_type: line + dimensions: + - name: process + - name: system.process_status + description: Process not closed + unit: "difference" + chart_type: line + dimensions: + - name: process + - name: zombie + - name: system.exit + description: Exit process + unit: "calls/s" + chart_type: line + dimensions: + - name: process + - name: system.task_error + description: Fails to create process + unit: "calls/s" + chart_type: line + dimensions: + - name: task + - name: apps + description: "These Metrics show grouped information per apps group." + labels: + - name: app_group + description: The name of the group defined in the configuration. + metrics: + - name: app.process_create + description: Process started + unit: "calls/s" + chart_type: stacked + dimensions: + - name: calls + - name: app.thread_create + description: Threads started + unit: "calls/s" + chart_type: stacked + dimensions: + - name: call + - name: app.task_exit + description: Tasks starts exit process + unit: "calls/s" + chart_type: stacked + dimensions: + - name: call + - name: app.task_close + description: Tasks closed + unit: "calls/s" + chart_type: stacked + dimensions: + - name: call + - name: app.task_error + description: Errors to create process or threads + unit: "calls/s" + chart_type: stacked + dimensions: + - name: app + - name: cgroup + description: "These Metrics show grouped information per cgroup/service." + labels: [] + metrics: + - name: cgroup.process_create + description: Process started + unit: "calls/s" + chart_type: line + dimensions: + - name: process + - name: cgroup.thread_create + description: Threads started + unit: "calls/s" + chart_type: line + dimensions: + - name: thread + - name: cgroup.task_exit + description: Tasks starts exit process + unit: "calls/s" + chart_type: line + dimensions: + - name: exit + - name: cgroup.task_close + description: Tasks closed + unit: "calls/s" + chart_type: line + dimensions: + - name: process + - name: cgroup.task_error + description: Errors to create process or threads + unit: "calls/s" + chart_type: line + dimensions: + - name: process + - name: services.process_create + description: Process started + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: services.thread_create + description: Threads started + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: services.task_close + description: Tasks starts exit process + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: services.task_exit + description: Tasks closed + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: services.task_error + description: Errors to create process or threads + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - meta: + plugin_name: ebpf.plugin + module_name: disk + monitored_instance: + name: eBPF Disk + link: "https://kernel.org/" + categories: + - data-collection.ebpf + icon_filename: "ebpf.jpg" + related_resources: + integrations: + list: [] + info_provided_to_referring_integrations: + description: "" + keywords: + - hard Disk + - eBPF + - latency + - partition + most_popular: false + overview: + data_collection: + metrics_description: "Measure latency for I/O events on disk." + method_description: "Attach tracepoints to internal kernel functions." + supported_platforms: + include: + - Linux + exclude: [] + multi_instance: true + additional_permissions: + description: "The plugin needs setuid because it loads data inside kernel. Netada sets necessary permission during installation time." + default_behavior: + auto_detection: + description: "The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT), files inside debugfs, and presence of BTF files to decide which eBPF program will be attached." + limits: + description: "" + performance_impact: + description: "This thread will add overhead every time that an internal kernel function monitored by this thread is called." + setup: + prerequisites: + list: + - title: Compile kernel + description: | + Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. + When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files + with different names. + + Now follow steps: + 1. Copy the configuration file to /usr/src/linux/.config. + 2. Select the necessary options: make oldconfig + 3. Compile your kernel image: make bzImage + 4. Compile your modules: make modules + 5. Copy your new kernel image for boot loader directory + 6. Install the new modules: make modules_install + 7. Generate an initial ramdisk image (`initrd`) if it is necessary. + 8. Update your boot loader + - title: Debug Filesystem + description: | + This thread needs to attach a tracepoint to monitor when a process schedule an exit event. To allow this specific feaure, it is necessary to mount `debugfs` (`mount -t debugfs none /sys/kernel/debug/`).` + configuration: + file: + name: "ebpf.d/disk.conf" + description: "Overwrite default configuration reducing number of I/O events." + options: + description: | + All options are defined inside section `[global]`. + folding: + title: "Config options" + enabled: true + list: + - name: update every + description: Data collection frequency. + default_value: 5 + required: false + - name: ebpf load mode + description: Define whether plugin will monitor the call (`entry`) for the functions or it will also monitor the return (`return`). + default_value: entry + required: false + - name: lifetime + description: Set default lifetime for thread when enabled by cloud. + default_value: 300 + required: false + examples: + folding: + enabled: true + title: "" + list: [] + troubleshooting: + problems: + list: [] + alerts: [] + metrics: + folding: + title: Metrics + enabled: false + description: "" + availability: [] + scopes: + - name: disk + description: "These metrics measure latency for I/O events on every hard disk present on host." + labels: [] + metrics: + - name: disk.latency_io + description: Disk latency + unit: "calls/s" + chart_type: stacked + dimensions: + - name: latency + - meta: + plugin_name: ebpf.plugin + module_name: hardirq + monitored_instance: + name: eBPF Hardirq + link: "https://kernel.org/" + categories: + - data-collection.ebpf + icon_filename: "ebpf.jpg" + related_resources: + integrations: + list: [] + info_provided_to_referring_integrations: + description: "" + keywords: + - HardIRQ + - eBPF + most_popular: false + overview: + data_collection: + metrics_description: "Monitor latency for each HardIRQ available." + method_description: "Attach tracepoints to internal kernel functions." + supported_platforms: + include: + - Linux + exclude: [] + multi_instance: true + additional_permissions: + description: "The plugin needs setuid because it loads data inside kernel. Netada sets necessary permission during installation time." + default_behavior: + auto_detection: + description: "The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT), files inside debugfs, and presence of BTF files to decide which eBPF program will be attached." + limits: + description: "" + performance_impact: + description: "This thread will add overhead every time that an internal kernel function monitored by this thread is called." + setup: + prerequisites: + list: + - title: Compile kernel + description: | + Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. + When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files + with different names. + + Now follow steps: + 1. Copy the configuration file to /usr/src/linux/.config. + 2. Select the necessary options: make oldconfig + 3. Compile your kernel image: make bzImage + 4. Compile your modules: make modules + 5. Copy your new kernel image for boot loader directory + 6. Install the new modules: make modules_install + 7. Generate an initial ramdisk image (`initrd`) if it is necessary. + 8. Update your boot loader + - title: Debug Filesystem + description: | + This thread needs to attach a tracepoint to monitor when a process schedule an exit event. To allow this specific feaure, it is necessary to mount `debugfs` (`mount -t debugfs none /sys/kernel/debug/`). + configuration: + file: + name: "ebpf.d/hardirq.conf" + description: "Overwrite default configuration reducing number of I/O events." + options: + description: | + All options are defined inside section `[global]`. + folding: + title: "Config options" + enabled: true + list: + - name: update every + description: Data collection frequency. + default_value: 5 + required: false + - name: ebpf load mode + description: Define whether plugin will monitor the call (`entry`) for the functions or it will also monitor the return (`return`). + default_value: entry + required: false + - name: lifetime + description: Set default lifetime for thread when enabled by cloud. + default_value: 300 + required: false + examples: + folding: + enabled: true + title: "" + list: [] + troubleshooting: + problems: + list: [] + alerts: [] + metrics: + folding: + title: Metrics + enabled: false + description: "" + availability: [] + scopes: + - name: global + description: "These metrics show latest timestamp for each hardIRQ available on host." + labels: [] + metrics: + - name: system.hardirq_latency + description: Hard IRQ latency + unit: "milliseconds" + chart_type: stacked + dimensions: + - name: hardirq names + - meta: + plugin_name: ebpf.plugin + module_name: cachestat + monitored_instance: + name: eBPF Cachestat + link: "https://kernel.org/" + categories: + - data-collection.ebpf + icon_filename: "ebpf.jpg" + related_resources: + integrations: + list: + - plugin_name: apps.plugin + module_name: apps + - plugin_name: cgroups.plugin + module_name: cgroups + info_provided_to_referring_integrations: + description: "" + keywords: + - Page cache + - Hit ratio + - eBPF + most_popular: false + overview: + data_collection: + metrics_description: "Monitor Linux page cache events giving for users a general vision about how his kernel is manipulating files." + method_description: "Attach tracing (kprobe, trampoline) to internal kernel functions according options used to compile kernel." + supported_platforms: + include: + - Linux + exclude: [] + multi_instance: true + additional_permissions: + description: "The plugin needs setuid because it loads data inside kernel. Netada sets necessary permission during installation time." + default_behavior: + auto_detection: + description: "The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) and presence of BTF files to decide which eBPF program will be attached." + limits: + description: "" + performance_impact: + description: "This thread will add overhead every time that an internal kernel function monitored by this thread is called. The estimated additional period of time is between 90-200ms per call on kernels that do not have BTF technology." + setup: + prerequisites: + list: + - title: Compile kernel + description: | + Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. + When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files + with different names. + + Now follow steps: + 1. Copy the configuration file to /usr/src/linux/.config. + 2. Select the necessary options: make oldconfig + 3. Compile your kernel image: make bzImage + 4. Compile your modules: make modules + 5. Copy your new kernel image for boot loader directory + 6. Install the new modules: make modules_install + 7. Generate an initial ramdisk image (`initrd`) if it is necessary. + 8. Update your boot loader + configuration: + file: + name: "ebpf.d/cachestat.conf" + description: "Overwrite default configuration helping to reduce memory usage. You can also select charts visible on dashboard." + options: + description: | + All options are defined inside section `[global]`. + folding: + title: "Config options" + enabled: true + list: + - name: update every + description: Data collection frequency. + default_value: 5 + required: false + - name: ebpf load mode + description: Define whether plugin will monitor the call (`entry`) for the functions or it will also monitor the return (`return`). + default_value: entry + required: false + - name: apps + description: Enable or disable integration with apps.plugin + default_value: no + required: false + - name: cgroups + description: Enable or disable integration with cgroup.plugin + default_value: no + required: false + - name: pid table size + description: Number of elements stored inside hash tables used to monitor calls per PID. + default_value: 32768 + required: false + - name: ebpf type format + description: "Define the file type to load an eBPF program. Three options are available: `legacy` (Attach only `kprobe`), `co-re` (Plugin tries to use `trampoline` when available), and `auto` (plugin check OS configuration before to load)." + default_value: auto + required: false + - name: ebpf co-re tracing + description: "Select the attach method used by plugin when `co-re` is defined in previous option. Two options are available: `trampoline` (Option with lowest overhead), and `probe` (the same of legacy code)." + default_value: trampoline + required: false + - name: maps per core + description: Define how plugin will load their hash maps. When enabled (`yes`) plugin will load one hash table per core, instead to have centralized information. + default_value: yes + required: false + - name: lifetime + description: Set default lifetime for thread when enabled by cloud. + default_value: 300 + required: false + examples: + folding: + enabled: true + title: "" + list: [] + troubleshooting: + problems: + list: [] + alerts: [] + metrics: + folding: + title: Metrics + enabled: false + description: "" + availability: [] + scopes: + - name: global + description: "These metrics show total number of calls to functions inside kernel." + labels: [] + metrics: + - name: mem.cachestat_ratio + description: Hit ratio + unit: "%" + chart_type: line + dimensions: + - name: ratio + - name: mem.cachestat_dirties + description: Number of dirty pages + unit: "page/s" + chart_type: line + dimensions: + - name: dirty + - name: mem.cachestat_hits + description: Number of accessed files + unit: "hits/s" + chart_type: line + dimensions: + - name: hit + - name: mem.cachestat_misses + description: Files out of page cache + unit: "misses/s" + chart_type: line + dimensions: + - name: miss + - name: apps + description: "These Metrics show grouped information per apps group." + labels: + - name: app_group + description: The name of the group defined in the configuration. + metrics: + - name: app.ebpf_cachestat_hit_ratio + description: Hit ratio + unit: "%" + chart_type: line + dimensions: + - name: ratio + - name: app.ebpf_cachestat_dirty_pages + description: Number of dirty pages + unit: "page/s" + chart_type: stacked + dimensions: + - name: pages + - name: app.ebpf_cachestat_access + description: Number of accessed files + unit: "hits/s" + chart_type: stacked + dimensions: + - name: hits + - name: app.ebpf_cachestat_misses + description: Files out of page cache + unit: "misses/s" + chart_type: stacked + dimensions: + - name: misses + - name: cgroup + description: "" + labels: [] + metrics: + - name: cgroup.cachestat_ratio + description: Hit ratio + unit: "%" + chart_type: line + dimensions: + - name: ratio + - name: cgroup.cachestat_dirties + description: Number of dirty pages + unit: "page/s" + chart_type: line + dimensions: + - name: dirty + - name: cgroup.cachestat_hits + description: Number of accessed files + unit: "hits/s" + chart_type: line + dimensions: + - name: hit + - name: cgroup.cachestat_misses + description: Files out of page cache + unit: "misses/s" + chart_type: line + dimensions: + - name: miss + - name: services.cachestat_ratio + description: Hit ratio + unit: "%" + chart_type: line + dimensions: + - name: a dimension per systemd service + - name: services.cachestat_dirties + description: Number of dirty pages + unit: "page/s" + chart_type: line + dimensions: + - name: a dimension per systemd service + - name: services.cachestat_hits + description: Number of accessed files + unit: "hits/s" + chart_type: line + dimensions: + - name: a dimension per systemd service + - name: services.cachestat_misses + description: Files out of page cache + unit: "misses/s" + chart_type: line + dimensions: + - name: a dimension per systemd service + - meta: + plugin_name: ebpf.plugin + module_name: sync + monitored_instance: + name: eBPF Sync + link: "https://kernel.org/" + categories: + - data-collection.ebpf + icon_filename: "ebpf.jpg" + related_resources: + integrations: + list: [] + info_provided_to_referring_integrations: + description: "" + keywords: + - syscall + - eBPF + - hard disk + - memory + most_popular: false + overview: + data_collection: + metrics_description: "Monitor syscall responsible to move data from memory to storage device." + method_description: "Attach tracing (kprobe, trampoline) to internal kernel functions according options used to compile kernel." + supported_platforms: + include: + - Linux + exclude: [] + multi_instance: true + additional_permissions: + description: "The plugin needs setuid because it loads data inside kernel. Netada sets necessary permission during installation time." + default_behavior: + auto_detection: + description: "The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT, CONFIG_HAVE_SYSCALL_TRACEPOINTS), files inside debugfs, and presence of BTF files to decide which eBPF program will be attached." + limits: + description: "" + performance_impact: + description: "This thread will add overhead every time that an internal kernel function monitored by this thread is called. The estimated additional period of time is between 90-200ms per call on kernels that do not have BTF technology." + setup: + prerequisites: + list: + - title: Compile kernel + description: | + Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. + When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files + with different names. + + Now follow steps: + 1. Copy the configuration file to /usr/src/linux/.config. + 2. Select the necessary options: make oldconfig + 3. Compile your kernel image: make bzImage + 4. Compile your modules: make modules + 5. Copy your new kernel image for boot loader directory + 6. Install the new modules: make modules_install + 7. Generate an initial ramdisk image (`initrd`) if it is necessary. + 8. Update your boot loader + - title: Debug Filesystem + description: | + This thread needs to attach a tracepoint to monitor when a process schedule an exit event. To allow this specific feaure, it is necessary to mount `debugfs` (`mount -t debugfs none /sys/kernel/debug`). + configuration: + file: + name: "ebpf.d/sync.conf" + description: "Overwrite default configuration and allows user to select charts visible on dashboard." + options: + description: | + This configuration file have two different sections. The `[global]` overwrites all default options, while `[syscalls]` allow user to select the syscall to monitor. + folding: + title: "Config options" + enabled: true + list: + - name: update every + description: Data collection frequency. + default_value: 5 + required: false + - name: ebpf load mode + description: Define whether plugin will monitor the call (`entry`) for the functions or it will also monitor the return (`return`). + default_value: entry + required: false + - name: apps + description: Enable or disable integration with apps.plugin + default_value: no + required: false + - name: cgroups + description: Enable or disable integration with cgroup.plugin + default_value: no + required: false + - name: pid table size + description: Number of elements stored inside hash tables used to monitor calls per PID. + default_value: 32768 + required: false + - name: ebpf type format + description: "Define the file type to load an eBPF program. Three options are available: `legacy` (Attach only `kprobe`), `co-re` (Plugin tries to use `trampoline` when available), and `auto` (plugin check OS configuration before to load)." + default_value: auto + required: false + - name: ebpf co-re tracing + description: "Select the attach method used by plugin when `co-re` is defined in previous option. Two options are available: `trampoline` (Option with lowest overhead), and `probe` (the same of legacy code)." + default_value: trampoline + required: false + - name: maps per core + description: Define how plugin will load their hash maps. When enabled (`yes`) plugin will load one hash table per core, instead to have centralized information. + default_value: yes + required: false + - name: lifetime + description: Set default lifetime for thread when enabled by cloud. + default_value: 300 + required: false + - name: sync + description: Enable or disable monitoring for syscall `sync` + default_value: yes + required: false + - name: msync + description: Enable or disable monitoring for syscall `msync` + default_value: yes + required: false + - name: fsync + description: Enable or disable monitoring for syscall `fsync` + default_value: yes + required: false + - name: fdatasync + description: Enable or disable monitoring for syscall `fdatasync` + default_value: yes + required: false + - name: syncfs + description: Enable or disable monitoring for syscall `syncfs` + default_value: yes + required: false + - name: sync_file_range + description: Enable or disable monitoring for syscall `sync_file_range` + default_value: yes + required: false + examples: + folding: + enabled: true + title: "" + list: [] + troubleshooting: + problems: + list: [] + alerts: + - name: sync_freq + link: https://github.com/netdata/netdata/blob/master/src/health/health.d/synchronization.conf + metric: mem.sync + info: + number of sync() system calls. Every call causes all pending modifications to filesystem metadata and cached file data to be written to the + underlying filesystems. + metrics: + folding: + title: Metrics + enabled: false + description: "" + availability: [] + scopes: + - name: global + description: "These metrics show total number of calls to functions inside kernel." + labels: [] + metrics: + - name: mem.file_sync + description: Monitor calls to fsync(2) and fdatasync(2). + unit: "calls/s" + chart_type: stacked + dimensions: + - name: fsync + - name: fdatasync + - name: mem.memory_map + description: Monitor calls to msync(2). + unit: "calls/s" + chart_type: line + dimensions: + - name: msync + - name: mem.sync + description: Monitor calls to sync(2) and syncfs(2). + unit: "calls/s" + chart_type: line + dimensions: + - name: sync + - name: syncfs + - name: mem.file_segment + description: Monitor calls to sync_file_range(2). + unit: "calls/s" + chart_type: line + dimensions: + - name: sync_file_range + - meta: + plugin_name: ebpf.plugin + module_name: mdflush + monitored_instance: + name: eBPF MDflush + link: "https://kernel.org/" + categories: + - data-collection.ebpf + icon_filename: "ebpf.jpg" + related_resources: + integrations: + list: [] + info_provided_to_referring_integrations: + description: "" + keywords: + - MD + - RAID + - eBPF + most_popular: false + overview: + data_collection: + metrics_description: "Monitor when flush events happen between disks." + method_description: "Attach tracing (kprobe, trampoline) to internal kernel functions according options used to compile kernel." + supported_platforms: + include: + - Linux + exclude: [] + multi_instance: true + additional_permissions: + description: "The plugin needs setuid because it loads data inside kernel. Netada sets necessary permission during installation time." + default_behavior: + auto_detection: + description: "The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) and presence of BTF files to decide which eBPF program will be attached." + limits: + description: "" + performance_impact: + description: "This thread will add overhead every time that `md_flush_request` is called. The estimated additional period of time is between 90-200ms per call on kernels that do not have BTF technology." + setup: + prerequisites: + list: + - title: Compile kernel + description: | + Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. + When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files + with different names. + + Now follow steps: + 1. Copy the configuration file to /usr/src/linux/.config. + 2. Select the necessary options: make oldconfig + 3. Compile your kernel image: make bzImage + 4. Compile your modules: make modules + 5. Copy your new kernel image for boot loader directory + 6. Install the new modules: make modules_install + 7. Generate an initial ramdisk image (`initrd`) if it is necessary. + 8. Update your boot loader + configuration: + file: + name: "ebpf.d/mdflush.conf" + description: "Overwrite default configuration reducing I/O events." + options: + description: | + All options are defined inside section `[global]`. + folding: + title: "Config options" + enabled: true + list: + - name: update every + description: Data collection frequency. + default_value: 5 + required: false + - name: ebpf load mode + description: Define whether plugin will monitor the call (`entry`) for the functions or it will also monitor the return (`return`). + default_value: entry + required: false + - name: lifetime + description: Set default lifetime for thread when enabled by cloud. + default_value: 300 + required: false + examples: + folding: + enabled: true + title: "" + list: [] + troubleshooting: + problems: + list: [] + alerts: [] + metrics: + folding: + title: Metrics + enabled: false + description: "" + availability: [] + scopes: + - name: global + description: "Number of times md_flush_request was called since last time." + labels: [] + metrics: + - name: mdstat.mdstat_flush + description: MD flushes + unit: "flushes" + chart_type: stacked + dimensions: + - name: disk + - meta: + plugin_name: ebpf.plugin + module_name: swap + monitored_instance: + name: eBPF SWAP + link: "https://kernel.org/" + categories: + - data-collection.ebpf + icon_filename: "ebpf.jpg" + related_resources: + integrations: + list: + - plugin_name: apps.plugin + module_name: apps + - plugin_name: cgroups.plugin + module_name: cgroups + info_provided_to_referring_integrations: + description: "" + keywords: + - SWAP + - memory + - eBPF + - Hard Disk + most_popular: false + overview: + data_collection: + metrics_description: "Monitors when swap has I/O events and applications executing events." + method_description: "Attach tracing (kprobe, trampoline) to internal kernel functions according options used to compile kernel." + supported_platforms: + include: + - Linux + exclude: [] + multi_instance: true + additional_permissions: + description: "The plugin needs setuid because it loads data inside kernel. Netada sets necessary permission during installation time." + default_behavior: + auto_detection: + description: "The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) and presence of BTF files to decide which eBPF program will be attached." + limits: + description: "" + performance_impact: + description: "This thread will add overhead every time that an internal kernel function monitored by this thread is called. The estimated additional period of time is between 90-200ms per call on kernels that do not have BTF technology." + setup: + prerequisites: + list: + - title: Compile kernel + description: | + Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. + When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files + with different names. + + Now follow steps: + 1. Copy the configuration file to /usr/src/linux/.config. + 2. Select the necessary options: make oldconfig + 3. Compile your kernel image: make bzImage + 4. Compile your modules: make modules + 5. Copy your new kernel image for boot loader directory + 6. Install the new modules: make modules_install + 7. Generate an initial ramdisk image (`initrd`) if it is necessary. + 8. Update your boot loader + configuration: + file: + name: "ebpf.d/swap.conf" + description: "Overwrite default configuration helping to reduce memory usage. You can also select charts visible on dashboard." + options: + description: | + All options are defined inside section `[global]`. + folding: + title: "Config options" + enabled: true + list: + - name: update every + description: Data collection frequency. + default_value: 5 + required: false + - name: ebpf load mode + description: Define whether plugin will monitor the call (`entry`) for the functions or it will also monitor the return (`return`). + default_value: entry + required: false + - name: apps + description: Enable or disable integration with apps.plugin + default_value: no + required: false + - name: cgroups + description: Enable or disable integration with cgroup.plugin + default_value: no + required: false + - name: pid table size + description: Number of elements stored inside hash tables used to monitor calls per PID. + default_value: 32768 + required: false + - name: ebpf type format + description: "Define the file type to load an eBPF program. Three options are available: `legacy` (Attach only `kprobe`), `co-re` (Plugin tries to use `trampoline` when available), and `auto` (plugin check OS configuration before to load)." + default_value: auto + required: false + - name: ebpf co-re tracing + description: "Select the attach method used by plugin when `co-re` is defined in previous option. Two options are available: `trampoline` (Option with lowest overhead), and `probe` (the same of legacy code)." + default_value: trampoline + required: false + - name: maps per core + description: Define how plugin will load their hash maps. When enabled (`yes`) plugin will load one hash table per core, instead to have centralized information. + default_value: yes + required: false + - name: lifetime + description: Set default lifetime for thread when enabled by cloud. + default_value: 300 + required: false + examples: + folding: + enabled: true + title: "" + list: [] + troubleshooting: + problems: + list: [] + alerts: [] + metrics: + folding: + title: Metrics + enabled: false + description: "" + availability: [] + scopes: + - name: cgroup + description: "These Metrics show grouped information per cgroup/service." + labels: [] + metrics: + - name: cgroup.swap_read + description: Calls to function swap_readpage. + unit: "calls/s" + chart_type: line + dimensions: + - name: read + - name: cgroup.swap_write + description: Calls to function swap_writepage. + unit: "calls/s" + chart_type: line + dimensions: + - name: write + - name: services.swap_read + description: Calls to swap_readpage. + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: services.swap_write + description: Calls to function swap_writepage. + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: apps + description: "These Metrics show grouped information per apps group." + labels: + - name: app_group + description: The name of the group defined in the configuration. + metrics: + - name: app.ebpf_call_swap_readpage + description: Calls to function swap_readpage. + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per app group + - name: app.ebpf_call_swap_writepage + description: Calls to function swap_writepage. + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per app group + - name: global + description: "These metrics show total number of calls to functions inside kernel." + labels: [] + metrics: + - name: mem.swapcalls + description: Calls to access swap memory + unit: "calls/s" + chart_type: line + dimensions: + - name: write + - name: read + - meta: + plugin_name: ebpf.plugin + module_name: oomkill + monitored_instance: + name: eBPF OOMkill + link: "https://kernel.org/" + categories: + - data-collection.ebpf + icon_filename: "ebpf.jpg" + related_resources: + integrations: + list: + - plugin_name: apps.plugin + module_name: apps + - plugin_name: cgroups.plugin + module_name: cgroups + info_provided_to_referring_integrations: + description: "" + keywords: + - application + - memory + most_popular: false + overview: + data_collection: + metrics_description: "Monitor applications that reach out of memory." + method_description: "Attach tracepoint to internal kernel functions." + supported_platforms: + include: + - Linux + exclude: [] + multi_instance: true + additional_permissions: + description: "The plugin needs setuid because it loads data inside kernel. Netada sets necessary permission during installation time." + default_behavior: + auto_detection: + description: "The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT), files inside debugfs, and presence of BTF files to decide which eBPF program will be attached." + limits: + description: "" + performance_impact: + description: "This thread will add overhead every time that an internal kernel function monitored by this thread is called." + setup: + prerequisites: + list: + - title: Compile kernel + description: | + Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. + When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files + with different names. + + Now follow steps: + 1. Copy the configuration file to /usr/src/linux/.config. + 2. Select the necessary options: make oldconfig + 3. Compile your kernel image: make bzImage + 4. Compile your modules: make modules + 5. Copy your new kernel image for boot loader directory + 6. Install the new modules: make modules_install + 7. Generate an initial ramdisk image (`initrd`) if it is necessary. + 8. Update your boot loader + - title: Debug Filesystem + description: | + This thread needs to attach a tracepoint to monitor when a process schedule an exit event. To allow this specific feaure, it is necessary to mount `debugfs` (`mount -t debugfs none /sys/kernel/debug/`). + configuration: + file: + name: "ebpf.d/oomkill.conf" + description: "Overwrite default configuration reducing number of I/O events." + options: + description: | + Overwrite default configuration reducing number of I/O events + folding: + title: "Config options" + enabled: true + list: [] + examples: + folding: + enabled: true + title: "" + list: [] + troubleshooting: + problems: + list: + - name: update every + description: Data collection frequency. + default_value: 5 + required: false + - name: ebpf load mode + description: Define whether plugin will monitor the call (`entry`) for the functions or it will also monitor the return (`return`). + default_value: entry + required: false + - name: lifetime + description: Set default lifetime for thread when enabled by cloud. + default_value: 300 + required: false + alerts: [] + metrics: + folding: + title: Metrics + enabled: false + description: "" + availability: [] + scopes: + - name: cgroup + description: "These metrics show cgroup/service that reached OOM." + labels: [] + metrics: + - name: cgroup.oomkills + description: OOM kills. This chart is provided by eBPF plugin. + unit: "kills" + chart_type: line + dimensions: + - name: cgroup name + - name: services.oomkills + description: OOM kills. This chart is provided by eBPF plugin. + unit: "kills" + chart_type: line + dimensions: + - name: a dimension per systemd service + - name: apps + description: "These metrics show cgroup/service that reached OOM." + labels: + - name: app_group + description: The name of the group defined in the configuration. + metrics: + - name: app.oomkill + description: OOM kills + unit: "kills" + chart_type: stacked + dimensions: + - name: kills + - meta: + plugin_name: ebpf.plugin + module_name: socket + monitored_instance: + name: eBPF Socket + link: "https://kernel.org/" + categories: + - data-collection.ebpf + icon_filename: "ebpf.jpg" + related_resources: + integrations: + list: + - plugin_name: apps.plugin + module_name: apps + - plugin_name: cgroups.plugin + module_name: cgroups + info_provided_to_referring_integrations: + description: "" + keywords: + - TCP + - UDP + - bandwidth + - server + - connection + - socket + most_popular: false + overview: + data_collection: + metrics_description: "Monitor bandwidth consumption per application for protocols TCP and UDP." + method_description: "Attach tracing (kprobe, trampoline) to internal kernel functions according options used to compile kernel." + supported_platforms: + include: + - Linux + exclude: [] + multi_instance: true + additional_permissions: + description: "The plugin needs setuid because it loads data inside kernel. Netada sets necessary permission during installation time." + default_behavior: + auto_detection: + description: "The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) and presence of BTF files to decide which eBPF program will be attached." + limits: + description: "" + performance_impact: + description: "This thread will add overhead every time that an internal kernel function monitored by this thread is called. The estimated additional period of time is between 90-200ms per call on kernels that do not have BTF technology." + setup: + prerequisites: + list: + - title: Compile kernel + description: | + Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. + When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files + with different names. + + Now follow steps: + 1. Copy the configuration file to /usr/src/linux/.config. + 2. Select the necessary options: make oldconfig + 3. Compile your kernel image: make bzImage + 4. Compile your modules: make modules + 5. Copy your new kernel image for boot loader directory + 6. Install the new modules: make modules_install + 7. Generate an initial ramdisk image (`initrd`) if it is necessary. + 8. Update your boot loader + configuration: + file: + name: "ebpf.d/network.conf" + description: "Overwrite default configuration helping to reduce memory usage. You can also select charts visible on dashboard." + options: + description: | + All options are defined inside section `[global]`. Options inside `network connections` are ignored for while. + folding: + title: "Config options" + enabled: true + list: + - name: update every + description: Data collection frequency. + default_value: 5 + required: false + - name: ebpf load mode + description: Define whether plugin will monitor the call (`entry`) for the functions or it will also monitor the return (`return`). + default_value: entry + required: false + - name: apps + description: Enable or disable integration with apps.plugin + default_value: no + required: false + - name: cgroups + description: Enable or disable integration with cgroup.plugin + default_value: no + required: false + - name: bandwidth table size + description: Number of elements stored inside hash tables used to monitor calls per PID. + default_value: 16384 + required: false + - name: ipv4 connection table size + description: Number of elements stored inside hash tables used to monitor calls per IPV4 connections. + default_value: 16384 + required: false + - name: ipv6 connection table size + description: Number of elements stored inside hash tables used to monitor calls per IPV6 connections. + default_value: 16384 + required: false + - name: udp connection table size + description: Number of temporary elements stored inside hash tables used to monitor UDP connections. + default_value: 4096 + required: false + - name: ebpf type format + description: "Define the file type to load an eBPF program. Three options are available: `legacy` (Attach only `kprobe`), `co-re` (Plugin tries to use `trampoline` when available), and `auto` (plugin check OS configuration before to load)." + default_value: auto + required: false + - name: ebpf co-re tracing + description: "Select the attach method used by plugin when `co-re` is defined in previous option. Two options are available: `trampoline` (Option with lowest overhead), and `probe` (the same of legacy code)." + default_value: trampoline + required: false + - name: maps per core + description: Define how plugin will load their hash maps. When enabled (`yes`) plugin will load one hash table per core, instead to have centralized information. + default_value: yes + required: false + - name: lifetime + description: Set default lifetime for thread when enabled by cloud. + default_value: 300 + required: false + examples: + folding: + enabled: true + title: "" + list: [] + troubleshooting: + problems: + list: [] + alerts: [] + metrics: + folding: + title: Metrics + enabled: false + description: "" + availability: [] + scopes: + - name: global + description: "These metrics show total number of calls to functions inside kernel." + labels: [] + metrics: + - name: ip.inbound_conn + description: Inbound connections. + unit: "connections/s" + chart_type: line + dimensions: + - name: connection_tcp + - name: ip.tcp_outbound_conn + description: TCP outbound connections. + unit: "connections/s" + chart_type: line + dimensions: + - name: received + - name: ip.tcp_functions + description: Calls to internal functions + unit: "calls/s" + chart_type: line + dimensions: + - name: received + - name: send + - name: closed + - name: ip.total_tcp_bandwidth + description: TCP bandwidth + unit: "kilobits/s" + chart_type: line + dimensions: + - name: received + - name: send + - name: ip.tcp_error + description: TCP errors + unit: "calls/s" + chart_type: line + dimensions: + - name: received + - name: send + - name: ip.tcp_retransmit + description: Packages retransmitted + unit: "calls/s" + chart_type: line + dimensions: + - name: retransmited + - name: ip.udp_functions + description: UDP calls + unit: "calls/s" + chart_type: line + dimensions: + - name: received + - name: send + - name: ip.total_udp_bandwidth + description: UDP bandwidth + unit: "kilobits/s" + chart_type: line + dimensions: + - name: received + - name: send + - name: ip.udp_error + description: UDP errors + unit: "calls/s" + chart_type: line + dimensions: + - name: received + - name: send + - name: apps + description: "These metrics show grouped information per apps group." + labels: + - name: app_group + description: The name of the group defined in the configuration. + metrics: + - name: app.ebpf_call_tcp_v4_connection + description: Calls to tcp_v4_connection + unit: "connections/s" + chart_type: stacked + dimensions: + - name: connections + - name: app.ebpf_call_tcp_v6_connection + description: Calls to tcp_v6_connection + unit: "connections/s" + chart_type: stacked + dimensions: + - name: connections + - name: app.ebpf_sock_bytes_sent + description: Bytes sent + unit: "kilobits/s" + chart_type: stacked + dimensions: + - name: bandwidth + - name: app.ebpf_sock_bytes_received + description: bytes received + unit: "kilobits/s" + chart_type: stacked + dimensions: + - name: bandwidth + - name: app.ebpf_call_tcp_sendmsg + description: Calls for tcp_sendmsg + unit: "calls/s" + chart_type: stacked + dimensions: + - name: calls + - name: app.ebpf_call_tcp_cleanup_rbuf + description: Calls for tcp_cleanup_rbuf + unit: "calls/s" + chart_type: stacked + dimensions: + - name: calls + - name: app.ebpf_call_tcp_retransmit + description: Calls for tcp_retransmit + unit: "calls/s" + chart_type: stacked + dimensions: + - name: calls + - name: app.ebpf_call_udp_sendmsg + description: Calls for udp_sendmsg + unit: "calls/s" + chart_type: stacked + dimensions: + - name: calls + - name: app.ebpf_call_udp_recvmsg + description: Calls for udp_recvmsg + unit: "calls/s" + chart_type: stacked + dimensions: + - name: calls + - name: cgroup + description: "" + labels: [] + metrics: + - name: cgroup.net_conn_ipv4 + description: Calls to tcp_v4_connection + unit: "connections/s" + chart_type: line + dimensions: + - name: connected_v4 + - name: cgroup.net_conn_ipv6 + description: Calls to tcp_v6_connection + unit: "connections/s" + chart_type: line + dimensions: + - name: connected_v6 + - name: cgroup.net_bytes_recv + description: Bytes received + unit: "calls/s" + chart_type: line + dimensions: + - name: received + - name: cgroup.net_bytes_sent + description: Bytes sent + unit: "calls/s" + chart_type: line + dimensions: + - name: sent + - name: cgroup.net_tcp_recv + description: Calls to tcp_cleanup_rbuf. + unit: "calls/s" + chart_type: line + dimensions: + - name: received + - name: cgroup.net_tcp_send + description: Calls to tcp_sendmsg. + unit: "calls/s" + chart_type: line + dimensions: + - name: sent + - name: cgroup.net_retransmit + description: Calls to tcp_retransmit. + unit: "calls/s" + chart_type: line + dimensions: + - name: retransmitted + - name: cgroup.net_udp_send + description: Calls to udp_sendmsg + unit: "calls/s" + chart_type: line + dimensions: + - name: sent + - name: cgroup.net_udp_recv + description: Calls to udp_recvmsg + unit: "calls/s" + chart_type: line + dimensions: + - name: received + - name: services.net_conn_ipv6 + description: Calls to tcp_v6_connection + unit: "connections/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: services.net_bytes_recv + description: Bytes received + unit: "kilobits/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: services.net_bytes_sent + description: Bytes sent + unit: "kilobits/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: services.net_tcp_recv + description: Calls to tcp_cleanup_rbuf. + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: services.net_tcp_send + description: Calls to tcp_sendmsg. + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: services.net_tcp_retransmit + description: Calls to tcp_retransmit + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: services.net_udp_send + description: Calls to udp_sendmsg + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: services.net_udp_recv + description: Calls to udp_recvmsg + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - meta: + plugin_name: ebpf.plugin + module_name: dcstat + monitored_instance: + name: eBPF DCstat + link: "https://kernel.org/" + categories: + - data-collection.ebpf + icon_filename: "ebpf.jpg" + related_resources: + integrations: + list: + - plugin_name: apps.plugin + module_name: apps + - plugin_name: cgroups.plugin + module_name: cgroups + info_provided_to_referring_integrations: + description: "" + keywords: + - Directory Cache + - File system + - eBPF + most_popular: false + overview: + data_collection: + metrics_description: "Monitor directory cache events per application given an overall vision about files on memory or storage device." + method_description: "Attach tracing (kprobe, trampoline) to internal kernel functions according options used to compile kernel." + supported_platforms: + include: + - Linux + exclude: [] + multi_instance: true + additional_permissions: + description: "The plugin needs setuid because it loads data inside kernel. Netada sets necessary permission during installation time." + default_behavior: + auto_detection: + description: "The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) and presence of BTF files to decide which eBPF program will be attached." + limits: + description: "" + performance_impact: + description: "This thread will add overhead every time that an internal kernel function monitored by this thread is called. The estimated additional period of time is between 90-200ms per call on kernels that do not have BTF technology." + setup: + prerequisites: + list: + - title: Compile kernel + description: | + Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. + When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files + with different names. + + Now follow steps: + 1. Copy the configuration file to /usr/src/linux/.config. + 2. Select the necessary options: make oldconfig + 3. Compile your kernel image: make bzImage + 4. Compile your modules: make modules + 5. Copy your new kernel image for boot loader directory + 6. Install the new modules: make modules_install + 7. Generate an initial ramdisk image (`initrd`) if it is necessary. + 8. Update your boot loader + configuration: + file: + name: "ebpf.d/dcstat.conf" + description: "Overwrite default configuration helping to reduce memory usage. You can also select charts visible on dashboard." + options: + description: | + All options are defined inside section `[global]`. + folding: + title: "Config option" + enabled: true + list: + - name: update every + description: Data collection frequency. + default_value: 5 + required: false + - name: ebpf load mode + description: Define whether plugin will monitor the call (`entry`) for the functions or it will also monitor the return (`return`). + default_value: entry + required: false + - name: apps + description: Enable or disable integration with apps.plugin + default_value: no + required: false + - name: cgroups + description: Enable or disable integration with cgroup.plugin + default_value: no + required: false + - name: pid table size + description: Number of elements stored inside hash tables used to monitor calls per PID. + default_value: 32768 + required: false + - name: ebpf type format + description: "Define the file type to load an eBPF program. Three options are available: `legacy` (Attach only `kprobe`), `co-re` (Plugin tries to use `trampoline` when available), and `auto` (plugin check OS configuration before to load)." + default_value: auto + required: false + - name: ebpf co-re tracing + description: "Select the attach method used by plugin when `co-re` is defined in previous option. Two options are available: `trampoline` (Option with lowest overhead), and `probe` (the same of legacy code)." + default_value: trampoline + required: false + - name: maps per core + description: Define how plugin will load their hash maps. When enabled (`yes`) plugin will load one hash table per core, instead to have centralized information. + default_value: yes + required: false + - name: lifetime + description: Set default lifetime for thread when enabled by cloud. + default_value: 300 + required: false + examples: + folding: + enabled: true + title: "" + list: [] + troubleshooting: + problems: + list: [] + alerts: [] + metrics: + folding: + title: Metrics + enabled: false + description: "" + availability: [] + scopes: + - name: apps + description: "These Metrics show grouped information per apps group." + labels: + - name: app_group + description: The name of the group defined in the configuration. + metrics: + - name: app.ebpf_dc_ratio + description: Percentage of files inside directory cache + unit: "%" + chart_type: line + dimensions: + - name: ratio + - name: app.ebpf_dc_reference + description: Count file access + unit: "files" + chart_type: stacked + dimensions: + - name: files + - name: app.ebpf_dc_not_cache + description: Files not present inside directory cache + unit: "files" + chart_type: stacked + dimensions: + - name: files + - name: app.ebpf_dc_not_found + description: Files not found + unit: "files" + chart_type: stacked + dimensions: + - name: files + - name: filesystem + description: "These metrics show total number of calls to functions inside kernel." + labels: [] + metrics: + - name: filesystem.dc_reference + description: Variables used to calculate hit ratio. + unit: "files" + chart_type: line + dimensions: + - name: reference + - name: slow + - name: miss + - name: filesystem.dc_hit_ratio + description: Percentage of files inside directory cache + unit: "%" + chart_type: line + dimensions: + - name: ratio + - name: cgroup + description: "" + labels: [] + metrics: + - name: cgroup.dc_ratio + description: Percentage of files inside directory cache + unit: "%" + chart_type: line + dimensions: + - name: ratio + - name: cgroup.dc_reference + description: Count file access + unit: "files" + chart_type: line + dimensions: + - name: reference + - name: cgroup.dc_not_cache + description: Files not present inside directory cache + unit: "files" + chart_type: line + dimensions: + - name: slow + - name: cgroup.dc_not_found + description: Files not found + unit: "files" + chart_type: line + dimensions: + - name: miss + - name: services.dc_ratio + description: Percentage of files inside directory cache + unit: "%" + chart_type: line + dimensions: + - name: a dimension per systemd service + - name: services.dc_reference + description: Count file access + unit: "files" + chart_type: line + dimensions: + - name: a dimension per systemd service + - name: services.dc_not_cache + description: Files not present inside directory cache + unit: "files" + chart_type: line + dimensions: + - name: a dimension per systemd service + - name: services.dc_not_found + description: Files not found + unit: "files" + chart_type: line + dimensions: + - name: a dimension per systemd service + - meta: + plugin_name: ebpf.plugin + module_name: filesystem + monitored_instance: + name: eBPF Filesystem + link: "https://kernel.org/" + categories: + - data-collection.ebpf + icon_filename: "ebpf.jpg" + related_resources: + integrations: + list: [] + info_provided_to_referring_integrations: + description: "" + keywords: + - Filesystem + - ext4 + - btrfs + - nfs + - xfs + - zfs + - eBPF + - latency + - I/O + most_popular: false + overview: + data_collection: + metrics_description: "Monitor latency for main actions on filesystem like I/O events." + method_description: "Attach tracing (kprobe, trampoline) to internal kernel functions according options used to compile kernel." + supported_platforms: + include: + - Linux + exclude: [] + multi_instance: true + additional_permissions: + description: "The plugin needs setuid because it loads data inside kernel. Netada sets necessary permission during installation time." + default_behavior: + auto_detection: + description: "The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT), files inside debugfs, and presence of BTF files to decide which eBPF program will be attached." + limits: + description: "" + performance_impact: + description: "" + setup: + prerequisites: + list: + - title: Compile kernel + description: | + Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. + When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files + with different names. + + Now follow steps: + 1. Copy the configuration file to /usr/src/linux/.config. + 2. Select the necessary options: make oldconfig + 3. Compile your kernel image: make bzImage + 4. Compile your modules: make modules + 5. Copy your new kernel image for boot loader directory + 6. Install the new modules: make modules_install + 7. Generate an initial ramdisk image (`initrd`) if it is necessary. + 8. Update your boot loader + configuration: + file: + name: "ebpf.d/filesystem.conf" + description: "Overwrite default configuration and allows user to select charts visible on dashboard." + options: + description: | + This configuration file have two different sections. The `[global]` overwrites default options, while `[filesystem]` allow user to select the filesystems to monitor. + folding: + title: "Config options" + enabled: true + list: + - name: update every + description: Data collection frequency. + default_value: 5 + required: false + - name: ebpf load mode + description: Define whether plugin will monitor the call (`entry`) for the functions or it will also monitor the return (`return`). + default_value: entry + required: false + - name: lifetime + description: Set default lifetime for thread when enabled by cloud. + default_value: 300 + required: false + - name: btrfsdist + description: Enable or disable latency monitoring for functions associated with btrfs filesystem. + default_value: yes + required: false + - name: ext4dist + description: Enable or disable latency monitoring for functions associated with ext4 filesystem. + default_value: yes + required: false + - name: nfsdist + description: Enable or disable latency monitoring for functions associated with nfs filesystem. + default_value: yes + required: false + - name: xfsdist + description: Enable or disable latency monitoring for functions associated with xfs filesystem. + default_value: yes + required: false + - name: zfsdist + description: Enable or disable latency monitoring for functions associated with zfs filesystem. + default_value: yes + required: false + examples: + folding: + enabled: true + title: "" + list: [] + troubleshooting: + problems: + list: [] + alerts: [] + metrics: + folding: + title: Metrics + enabled: false + description: "" + availability: [] + scopes: + - name: filesystem + description: "Latency charts associate with filesystem actions." + labels: [] + metrics: + - name: filesystem.read_latency + description: ext4 latency for each read request. + unit: "calls/s" + chart_type: stacked + dimensions: + - name: latency period + - name: filesystem.open_latency + description: ext4 latency for each open request. + unit: "calls/s" + chart_type: stacked + dimensions: + - name: latency period + - name: filesystem.sync_latency + description: ext4 latency for each sync request. + unit: "calls/s" + chart_type: stacked + dimensions: + - name: latency period + - name: iilesystem + description: "" + labels: [] + metrics: + - name: filesystem.write_latency + description: ext4 latency for each write request. + unit: "calls/s" + chart_type: stacked + dimensions: + - name: latency period + - name: global + description: "" + labels: [] + metrics: + - name: filesystem.attributte_latency + description: nfs latency for each attribute request. + unit: "calls/s" + chart_type: stacked + dimensions: + - name: latency period + - meta: + plugin_name: ebpf.plugin + module_name: shm + monitored_instance: + name: eBPF SHM + link: "https://kernel.org/" + categories: + - data-collection.ebpf + icon_filename: "ebpf.jpg" + related_resources: + integrations: + list: + - plugin_name: apps.plugin + module_name: apps + - plugin_name: cgroups.plugin + module_name: cgroups + info_provided_to_referring_integrations: + description: "" + keywords: + - syscall + - shared memory + - eBPF + most_popular: false + overview: + data_collection: + metrics_description: "Monitor syscall responsible to manipulate shared memory." + method_description: "Attach tracing (kprobe, trampoline) to internal kernel functions according options used to compile kernel." + supported_platforms: + include: + - Linux + exclude: [] + multi_instance: true + additional_permissions: + description: "The plugin needs setuid because it loads data inside kernel. Netada sets necessary permission during installation time." + default_behavior: + auto_detection: + description: "The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) and presence of BTF files to decide which eBPF program will be attached." + limits: + description: "" + performance_impact: + description: "This thread will add overhead every time that an internal kernel function monitored by this thread is called. The estimated additional period of time is between 90-200ms per call on kernels that do not have BTF technology." + setup: + prerequisites: + list: + - title: Compile kernel + description: | + Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. + When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files + with different names. + + Now follow steps: + 1. Copy the configuration file to /usr/src/linux/.config. + 2. Select the necessary options: make oldconfig + 3. Compile your kernel image: make bzImage + 4. Compile your modules: make modules + 5. Copy your new kernel image for boot loader directory + 6. Install the new modules: make modules_install + 7. Generate an initial ramdisk image (`initrd`) if it is necessary. + 8. Update your boot loader + - title: Debug Filesystem + description: | + This thread needs to attach a tracepoint to monitor when a process schedule an exit event. To allow this specific feaure, it is necessary to mount `debugfs` (`mount -t debugfs none /sys/kernel/debug/`).` + configuration: + file: + name: "ebpf.d/shm.conf" + description: "Overwrite default configuration and allows user to select charts visible on dashboard." + options: + description: | + This configuration file have two different sections. The `[global]` overwrites all default options, while `[syscalls]` allow user to select the syscall to monitor. + folding: + title: "Config options" + enabled: true + list: + - name: update every + description: Data collection frequency. + default_value: 5 + required: false + - name: ebpf load mode + description: Define whether plugin will monitor the call (`entry`) for the functions or it will also monitor the return (`return`). + default_value: entry + required: false + - name: apps + description: Enable or disable integration with apps.plugin + default_value: no + required: false + - name: cgroups + description: Enable or disable integration with cgroup.plugin + default_value: no + required: false + - name: pid table size + description: Number of elements stored inside hash tables used to monitor calls per PID. + default_value: 32768 + required: false + - name: ebpf type format + description: "Define the file type to load an eBPF program. Three options are available: `legacy` (Attach only `kprobe`), `co-re` (Plugin tries to use `trampoline` when available), and `auto` (plugin check OS configuration before to load)." + default_value: auto + required: false + - name: ebpf co-re tracing + description: "Select the attach method used by plugin when `co-re` is defined in previous option. Two options are available: `trampoline` (Option with lowest overhead), and `probe` (the same of legacy code)." + default_value: trampoline + required: false + - name: maps per core + description: Define how plugin will load their hash maps. When enabled (`yes`) plugin will load one hash table per core, instead to have centralized information. + default_value: yes + required: false + - name: lifetime + description: Set default lifetime for thread when enabled by cloud. + default_value: 300 + required: false + - name: shmget + description: Enable or disable monitoring for syscall `shmget` + default_value: yes + required: false + - name: shmat + description: Enable or disable monitoring for syscall `shmat` + default_value: yes + required: false + - name: shmdt + description: Enable or disable monitoring for syscall `shmdt` + default_value: yes + required: false + - name: shmctl + description: Enable or disable monitoring for syscall `shmctl` + default_value: yes + required: false + examples: + folding: + enabled: true + title: "" + list: [] + troubleshooting: + problems: + list: [] + alerts: [] + metrics: + folding: + title: Metrics + enabled: false + description: "" + availability: [] + scopes: + - name: cgroup + description: "These Metrics show grouped information per cgroup/service." + labels: [] + metrics: + - name: cgroup.shmget + description: Calls to syscall shmget(2). + unit: "calls/s" + chart_type: line + dimensions: + - name: get + - name: cgroup.shmat + description: Calls to syscall shmat(2). + unit: "calls/s" + chart_type: line + dimensions: + - name: at + - name: cgroup.shmdt + description: Calls to syscall shmdt(2). + unit: "calls/s" + chart_type: line + dimensions: + - name: dt + - name: cgroup.shmctl + description: Calls to syscall shmctl(2). + unit: "calls/s" + chart_type: line + dimensions: + - name: ctl + - name: services.shmget + description: Calls to syscall shmget(2). + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: services.shmat + description: Calls to syscall shmat(2). + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: services.shmdt + description: Calls to syscall shmdt(2). + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: services.shmctl + description: Calls to syscall shmctl(2). + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: apps + description: "These Metrics show grouped information per apps group." + labels: + - name: app_group + description: The name of the group defined in the configuration. + metrics: + - name: app.ebpf_shmget_call + description: Calls to syscall shmget(2). + unit: "calls/s" + chart_type: stacked + dimensions: + - name: calls + - name: app.ebpf_shmat_call + description: Calls to syscall shmat(2). + unit: "calls/s" + chart_type: stacked + dimensions: + - name: calls + - name: app.ebpf_shmdt_call + description: Calls to syscall shmdt(2). + unit: "calls/s" + chart_type: stacked + dimensions: + - name: calls + - name: app.ebpf_shmctl_call + description: Calls to syscall shmctl(2). + unit: "calls/s" + chart_type: stacked + dimensions: + - name: calls + - name: global + description: "These Metrics show number of calls for specified syscall." + labels: [] + metrics: + - name: system.shared_memory_calls + description: Calls to shared memory system calls + unit: "calls/s" + chart_type: line + dimensions: + - name: get + - name: at + - name: dt + - name: ctl + - meta: + plugin_name: ebpf.plugin + module_name: softirq + monitored_instance: + name: eBPF SoftIRQ + link: "https://kernel.org/" + categories: + - data-collection.ebpf + icon_filename: "ebpf.jpg" + related_resources: + integrations: + list: [] + info_provided_to_referring_integrations: + description: "" + keywords: + - SoftIRQ + - eBPF + most_popular: false + overview: + data_collection: + metrics_description: "Monitor latency for each SoftIRQ available." + method_description: "Attach kprobe to internal kernel functions." + supported_platforms: + include: + - Linux + exclude: [] + multi_instance: true + additional_permissions: + description: "The plugin needs setuid because it loads data inside kernel. Netada sets necessary permission during installation time." + default_behavior: + auto_detection: + description: "The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT), files inside debugfs, and presence of BTF files to decide which eBPF program will be attached." + limits: + description: "" + performance_impact: + description: "This thread will add overhead every time that an internal kernel function monitored by this thread is called." + setup: + prerequisites: + list: + - title: Compile kernel + description: | + Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. + When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files + with different names. + + Now follow steps: + 1. Copy the configuration file to /usr/src/linux/.config. + 2. Select the necessary options: make oldconfig + 3. Compile your kernel image: make bzImage + 4. Compile your modules: make modules + 5. Copy your new kernel image for boot loader directory + 6. Install the new modules: make modules_install + 7. Generate an initial ramdisk image (`initrd`) if it is necessary. + 8. Update your boot loader + - title: Debug Filesystem + description: | + This thread needs to attach a tracepoint to monitor when a process schedule an exit event. To allow this specific feaure, it is necessary to mount `debugfs` (`mount -t debugfs none /sys/kernel/debug/`).` + configuration: + file: + name: "ebpf.d/softirq.conf" + description: "Overwrite default configuration reducing number of I/O events." + options: + description: | + All options are defined inside section `[global]`. + folding: + title: "Config options" + enabled: true + list: + - name: update every + description: Data collection frequency. + default_value: 5 + required: false + - name: ebpf load mode + description: Define whether plugin will monitor the call (`entry`) for the functions or it will also monitor the return (`return`). + default_value: entry + required: false + - name: lifetime + description: Set default lifetime for thread when enabled by cloud. + default_value: 300 + required: false + examples: + folding: + enabled: true + title: "" + list: [] + troubleshooting: + problems: + list: [] + alerts: [] + metrics: + folding: + title: Metrics + enabled: false + description: "" + availability: [] + scopes: + - name: global + description: "These metrics show latest timestamp for each softIRQ available on host." + labels: [] + metrics: + - name: system.softirq_latency + description: Soft IRQ latency + unit: "milliseconds" + chart_type: stacked + dimensions: + - name: soft IRQs + - meta: + plugin_name: ebpf.plugin + module_name: mount + monitored_instance: + name: eBPF Mount + link: "https://kernel.org/" + categories: + - data-collection.ebpf + icon_filename: "ebpf.jpg" + related_resources: + integrations: + list: [] + info_provided_to_referring_integrations: + description: "" + keywords: + - mount + - umount + - device + - eBPF + most_popular: false + overview: + data_collection: + metrics_description: "Monitor calls for mount and umount syscall." + method_description: "Attach tracing (kprobe, trampoline) to internal kernel functions according options used to compile kernel." + supported_platforms: + include: + - Linux + exclude: [] + multi_instance: true + additional_permissions: + description: "The plugin needs setuid because it loads data inside kernel. Netada sets necessary permission during installation time." + default_behavior: + auto_detection: + description: "The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT, CONFIG_HAVE_SYSCALL_TRACEPOINTS), files inside debugfs, and presence of BTF files to decide which eBPF program will be attached." + limits: + description: "" + performance_impact: + description: "This thread will add overhead every time that an internal kernel function monitored by this thread is called. The estimated additional period of time is between 90-200ms per call on kernels that do not have BTF technology." + setup: + prerequisites: + list: + - title: Compile kernel + description: | + Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. + When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files + with different names. + + Now follow steps: + 1. Copy the configuration file to /usr/src/linux/.config. + 2. Select the necessary options: make oldconfig + 3. Compile your kernel image: make bzImage + 4. Compile your modules: make modules + 5. Copy your new kernel image for boot loader directory + 6. Install the new modules: make modules_install + 7. Generate an initial ramdisk image (`initrd`) if it is necessary. + 8. Update your boot loader + - title: Debug Filesystem + description: | + This thread needs to attach a tracepoint to monitor when a process schedule an exit event. To allow this specific feaure, it is necessary to mount `debugfs` (`mount -t debugfs none /sys/kernel/debug/`).` + configuration: + file: + name: "ebpf.d/mount.conf" + description: "Overwrite default configuration." + options: + description: | + All options are defined inside section `[global]`. + folding: + title: "Config options" + enabled: true + list: + - name: update every + description: Data collection frequency. + default_value: 5 + required: false + - name: ebpf load mode + description: Define whether plugin will monitor the call (`entry`) for the functions or it will also monitor the return (`return`). + default_value: entry + required: false + - name: ebpf type format + description: "Define the file type to load an eBPF program. Three options are available: `legacy` (Attach only `kprobe`), `co-re` (Plugin tries to use `trampoline` when available), and `auto` (plugin check OS configuration before to load)." + default_value: auto + required: false + - name: ebpf co-re tracing + description: "Select the attach method used by plugin when `co-re` is defined in previous option. Two options are available: `trampoline` (Option with lowest overhead), and `probe` (the same of legacy code)." + default_value: trampoline + required: false + - name: lifetime + description: Set default lifetime for thread when enabled by cloud. + default_value: 300 + required: false + examples: + folding: + enabled: true + title: "" + list: [] + troubleshooting: + problems: + list: [] + alerts: [] + metrics: + folding: + title: Metrics + enabled: false + description: "" + availability: [] + scopes: + - name: global + description: "Calls for syscalls mount an umount." + labels: [] + metrics: + - name: mount_points.call + description: Calls to mount and umount syscalls + unit: "calls/s" + chart_type: line + dimensions: + - name: mount + - name: umount + - name: mount_points.error + description: Errors to mount and umount file systems + unit: "calls/s" + chart_type: line + dimensions: + - name: mount + - name: umount + - meta: + plugin_name: ebpf.plugin + module_name: vfs + monitored_instance: + name: eBPF VFS + link: "https://kernel.org/" + categories: + - data-collection.ebpf + icon_filename: "ebpf.jpg" + related_resources: + integrations: + list: + - plugin_name: apps.plugin + module_name: apps + - plugin_name: cgroups.plugin + module_name: cgroups + info_provided_to_referring_integrations: + description: "" + keywords: + - virtual + - filesystem + - eBPF + - I/O + - files + most_popular: false + overview: + data_collection: + metrics_description: "Monitor I/O events on Linux Virtual Filesystem." + method_description: "Attach tracing (kprobe, trampoline) to internal kernel functions according options used to compile kernel." + supported_platforms: + include: + - Linux + exclude: [] + multi_instance: true + additional_permissions: + description: "The plugin needs setuid because it loads data inside kernel. Netada sets necessary permission during installation time." + default_behavior: + auto_detection: + description: "The plugin checks kernel compilation flags (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) and presence of BTF files to decide which eBPF program will be attached." + limits: + description: "" + performance_impact: + description: "This thread will add overhead every time that an internal kernel function monitored by this thread is called. The estimated additional period of time is between 90-200ms per call on kernels that do not have BTF technology." + setup: + prerequisites: + list: + - title: Compile kernel + description: | + Check if your kernel was compiled with necessary options (CONFIG_KPROBES, CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_BPF_JIT) in `/proc/config.gz` or inside /boot/config file. Some cited names can be different accoring preferences of Linux distributions. + When you do not have options set, it is necessary to get the kernel source code from https://kernel.org or a kernel package from your distribution, this last is preferred. The kernel compilation has a well definedd pattern, but distributions can deliver their configuration files + with different names. + + Now follow steps: + 1. Copy the configuration file to /usr/src/linux/.config. + 2. Select the necessary options: make oldconfig + 3. Compile your kernel image: make bzImage + 4. Compile your modules: make modules + 5. Copy your new kernel image for boot loader directory + 6. Install the new modules: make modules_install + 7. Generate an initial ramdisk image (`initrd`) if it is necessary. + 8. Update your boot loader + configuration: + file: + name: "ebpf.d/vfs.conf" + description: "Overwrite default configuration helping to reduce memory usage." + options: + description: | + All options are defined inside section `[global]`. + folding: + title: "Config options" + enabled: true + list: + - name: update every + description: Data collection frequency. + default_value: 5 + required: false + - name: ebpf load mode + description: Define whether plugin will monitor the call (`entry`) for the functions or it will also monitor the return (`return`). + default_value: entry + required: false + - name: apps + description: Enable or disable integration with apps.plugin + default_value: no + required: false + - name: cgroups + description: Enable or disable integration with cgroup.plugin + default_value: no + required: false + - name: pid table size + description: Number of elements stored inside hash tables used to monitor calls per PID. + default_value: 32768 + required: false + - name: ebpf type format + description: "Define the file type to load an eBPF program. Three options are available: `legacy` (Attach only `kprobe`), `co-re` (Plugin tries to use `trampoline` when available), and `auto` (plugin check OS configuration before to load)." + default_value: auto + required: false + - name: ebpf co-re tracing + description: "Select the attach method used by plugin when `co-re` is defined in previous option. Two options are available: `trampoline` (Option with lowest overhead), and `probe` (the same of legacy code)." + default_value: trampoline + required: false + - name: maps per core + description: Define how plugin will load their hash maps. When enabled (`yes`) plugin will load one hash table per core, instead to have centralized information. + default_value: yes + required: false + - name: lifetime + description: Set default lifetime for thread when enabled by cloud. + default_value: 300 + required: false + examples: + folding: + enabled: true + title: "" + list: [] + troubleshooting: + problems: + list: [] + alerts: [] + metrics: + folding: + title: Metrics + enabled: false + description: "" + availability: [] + scopes: + - name: cgroup + description: "These Metrics show grouped information per cgroup/service." + labels: [] + metrics: + - name: cgroup.vfs_unlink + description: Files deleted + unit: "calls/s" + chart_type: line + dimensions: + - name: delete + - name: cgroup.vfs_write + description: Write to disk + unit: "calls/s" + chart_type: line + dimensions: + - name: write + - name: cgroup.vfs_write_error + description: Fails to write + unit: "calls/s" + chart_type: line + dimensions: + - name: write + - name: cgroup.vfs_read + description: Read from disk + unit: "calls/s" + chart_type: line + dimensions: + - name: read + - name: cgroup.vfs_read_error + description: Fails to read + unit: "calls/s" + chart_type: line + dimensions: + - name: read + - name: cgroup.vfs_write_bytes + description: Bytes written on disk + unit: "bytes/s" + chart_type: line + dimensions: + - name: write + - name: cgroup.vfs_read_bytes + description: Bytes read from disk + unit: "bytes/s" + chart_type: line + dimensions: + - name: read + - name: cgroup.vfs_fsync + description: Calls to vfs_fsync. + unit: "calls/s" + chart_type: line + dimensions: + - name: fsync + - name: cgroup.vfs_fsync_error + description: Sync error + unit: "calls/s" + chart_type: line + dimensions: + - name: fsync + - name: cgroup.vfs_open + description: Calls to vfs_open. + unit: "calls/s" + chart_type: line + dimensions: + - name: open + - name: cgroup.vfs_open_error + description: Open error + unit: "calls/s" + chart_type: line + dimensions: + - name: open + - name: cgroup.vfs_create + description: Calls to vfs_create. + unit: "calls/s" + chart_type: line + dimensions: + - name: create + - name: cgroup.vfs_create_error + description: Create error + unit: "calls/s" + chart_type: line + dimensions: + - name: create + - name: services.vfs_unlink + description: Files deleted + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: services.vfs_write + description: Write to disk + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: services.vfs_write_error + description: Fails to write + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: services.vfs_read + description: Read from disk + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: services.vfs_read_error + description: Fails to read + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: services.vfs_write_bytes + description: Bytes written on disk + unit: "bytes/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: services.vfs_read_bytes + description: Bytes read from disk + unit: "bytes/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: services.vfs_fsync + description: Calls to vfs_fsync. + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: services.vfs_fsync_error + description: Sync error + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: services.vfs_open + description: Calls to vfs_open. + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: services.vfs_open_error + description: Open error + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: services.vfs_create + description: Calls to vfs_create. + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: services.vfs_create_error + description: Create error + unit: "calls/s" + chart_type: stacked + dimensions: + - name: a dimension per systemd service + - name: global + description: "These Metrics show grouped information per cgroup/service." + labels: [] + metrics: + - name: filesystem.vfs_deleted_objects + description: Remove files + unit: "calls/s" + chart_type: line + dimensions: + - name: delete + - name: filesystem.vfs_io + description: Calls to IO + unit: "calls/s" + chart_type: line + dimensions: + - name: read + - name: write + - name: filesystem.vfs_io_bytes + description: Bytes written and read + unit: "bytes/s" + chart_type: line + dimensions: + - name: read + - name: write + - name: filesystem.vfs_io_error + description: Fails to write or read + unit: "calls/s" + chart_type: line + dimensions: + - name: read + - name: write + - name: filesystem.vfs_fsync + description: Calls to vfs_fsync. + unit: "calls/s" + chart_type: line + dimensions: + - name: fsync + - name: filesystem.vfs_fsync_error + description: Fails to synchronize + unit: "calls/s" + chart_type: line + dimensions: + - name: fsync + - name: filesystem.vfs_open + description: Calls to vfs_open. + unit: "calls/s" + chart_type: line + dimensions: + - name: open + - name: filesystem.vfs_open_error + description: Fails to open a file + unit: "calls/s" + chart_type: line + dimensions: + - name: open + - name: filesystem.vfs_create + description: Calls to vfs_create. + unit: "calls/s" + chart_type: line + dimensions: + - name: create + - name: filesystem.vfs_create_error + description: Fails to create a file. + unit: "calls/s" + chart_type: line + dimensions: + - name: create + - name: apps + description: "These Metrics show grouped information per apps group." + labels: + - name: app_group + description: The name of the group defined in the configuration. + metrics: + - name: app.ebpf_call_vfs_unlink + description: Files deleted + unit: "calls/s" + chart_type: stacked + dimensions: + - name: calls + - name: app.ebpf_call_vfs_write + description: Write to disk + unit: "calls/s" + chart_type: stacked + dimensions: + - name: calls + - name: app.ebpf_call_vfs_write_error + description: Fails to write + unit: "calls/s" + chart_type: stacked + dimensions: + - name: calls + - name: app.ebpf_call_vfs_read + description: Read from disk + unit: "calls/s" + chart_type: stacked + dimensions: + - name: calls + - name: app.ebpf_call_vfs_read_error + description: Fails to read + unit: "calls/s" + chart_type: stacked + dimensions: + - name: calls + - name: app.ebpf_call_vfs_write_bytes + description: Bytes written on disk + unit: "bytes/s" + chart_type: stacked + dimensions: + - name: writes + - name: app.ebpf_call_vfs_read_bytes + description: Bytes read on disk + unit: "bytes/s" + chart_type: stacked + dimensions: + - name: reads + - name: app.ebpf_call_vfs_fsync + description: Calls to vfs_fsync. + unit: "calls/s" + chart_type: stacked + dimensions: + - name: calls + - name: app.ebpf_call_vfs_fsync_error + description: Sync error + unit: "calls/s" + chart_type: stacked + dimensions: + - name: calls + - name: app.ebpf_call_vfs_open + description: Calls to vfs_open. + unit: "calls/s" + chart_type: stacked + dimensions: + - name: calls + - name: app.ebpf_call_vfs_open_error + description: Open error + unit: "calls/s" + chart_type: stacked + dimensions: + - name: calls + - name: app.ebpf_call_vfs_create + description: Calls to vfs_create. + unit: "calls/s" + chart_type: stacked + dimensions: + - name: calls + - name: app.ebpf_call_vfs_create_error + description: Create error + unit: "calls/s" + chart_type: stacked + dimensions: + - name: calls + - meta: + plugin_name: ebpf.plugin + module_name: process + monitored_instance: + name: eBPF Process + link: "https://github.com/netdata/netdata/" + categories: + - data-collection.ebpf + icon_filename: "ebpf.jpg" + related_resources: + integrations: + list: [] + info_provided_to_referring_integrations: + description: "" + keywords: + - Memory + - plugin + - eBPF + most_popular: false + overview: + data_collection: + metrics_description: "Monitor internal memory usage." + method_description: "Uses netdata internal statistic to monitor memory management by plugin." + supported_platforms: + include: + - Linux + exclude: [] + multi_instance: true + additional_permissions: + description: "" + default_behavior: + auto_detection: + description: "" + limits: + description: "" + performance_impact: + description: "" + setup: + prerequisites: + list: + - title: Netdata flags. + description: "To have these charts you need to compile netdata with flag `NETDATA_DEV_MODE`." + configuration: + file: + name: "" + description: "" + options: + description: "" + folding: + title: "" + enabled: true + list: [] + examples: + folding: + enabled: true + title: "" + list: [] + troubleshooting: + problems: + list: [] + alerts: [] + metrics: + folding: + title: Metrics + enabled: false + description: "" + availability: [] + scopes: + - name: global + description: "How plugin is allocating memory." + labels: [] + metrics: + - name: netdata.ebpf_aral_stat_size + description: Bytes allocated for ARAL. + unit: "bytes" + chart_type: stacked + dimensions: + - name: memory + - name: netdata.ebpf_aral_stat_alloc + description: Calls to allocate memory. + unit: "calls" + chart_type: stacked + dimensions: + - name: aral + - name: netdata.ebpf_threads + description: Threads info + unit: "threads" + chart_type: line + dimensions: + - name: total + - name: running + - name: netdata.ebpf_load_methods + description: Load info + unit: "methods" + chart_type: line + dimensions: + - name: legacy + - name: co-re + - name: netdata.ebpf_kernel_memory + description: Memory allocated for hash tables. + unit: "bytes" + chart_type: line + dimensions: + - name: memory_locked + - name: netdata.ebpf_hash_tables_count + description: Number of hash tables loaded + unit: "hash tables" + chart_type: line + dimensions: + - name: hash_table + - name: netdata.ebpf_hash_tables_insert_pid_elements + description: Number of times an element was inserted in a hash table. + unit: "rows" + chart_type: line + dimensions: + - name: thread + - name: netdata.ebpf_hash_tables_remove_pid_elements + description: Number of times an element was removed in a hash table. + unit: "rows" + chart_type: line + dimensions: + - name: thread |