summaryrefslogtreecommitdiffstats
path: root/collectors/ebpf.plugin
diff options
context:
space:
mode:
Diffstat (limited to 'collectors/ebpf.plugin')
-rw-r--r--collectors/ebpf.plugin/Makefile.am25
-rw-r--r--collectors/ebpf.plugin/README.md400
-rw-r--r--collectors/ebpf.plugin/ebpf.c1953
-rw-r--r--collectors/ebpf.plugin/ebpf.conf45
-rw-r--r--collectors/ebpf.plugin/ebpf.h194
-rw-r--r--collectors/ebpf.plugin/ebpf_apps.c1086
-rw-r--r--collectors/ebpf.plugin/ebpf_apps.h431
-rw-r--r--collectors/ebpf.plugin/ebpf_kernel_reject_list.txt1
-rw-r--r--collectors/ebpf.plugin/ebpf_process.c1071
-rw-r--r--collectors/ebpf.plugin/ebpf_process.h138
-rw-r--r--collectors/ebpf.plugin/ebpf_socket.c1938
-rw-r--r--collectors/ebpf.plugin/ebpf_socket.h276
-rw-r--r--collectors/ebpf.plugin/reset_netdata_trace.sh.in9
13 files changed, 7567 insertions, 0 deletions
diff --git a/collectors/ebpf.plugin/Makefile.am b/collectors/ebpf.plugin/Makefile.am
new file mode 100644
index 00000000..1327d47a
--- /dev/null
+++ b/collectors/ebpf.plugin/Makefile.am
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+AUTOMAKE_OPTIONS = subdir-objects
+MAINTAINERCLEANFILES = $(srcdir)/Makefile.in
+
+CLEANFILES = \
+ reset_netdata_trace.sh \
+ $(NULL)
+
+include $(top_srcdir)/build/subst.inc
+SUFFIXES = .in
+
+dist_plugins_SCRIPTS = \
+ reset_netdata_trace.sh \
+ $(NULL)
+
+dist_noinst_DATA = \
+ reset_netdata_trace.sh.in \
+ README.md \
+ $(NULL)
+
+dist_libconfig_DATA = \
+ ebpf.conf \
+ ebpf_kernel_reject_list.txt \
+ $(NULL)
diff --git a/collectors/ebpf.plugin/README.md b/collectors/ebpf.plugin/README.md
new file mode 100644
index 00000000..5ea3b495
--- /dev/null
+++ b/collectors/ebpf.plugin/README.md
@@ -0,0 +1,400 @@
+<!--
+title: "eBPF monitoring with Netdata"
+description: "Use Netdata's extended Berkeley Packet Filter (eBPF) collector to monitor kernel-level metrics about your complex applications with per-second granularity."
+custom_edit_url: https://github.com/netdata/netdata/edit/master/collectors/ebpf.plugin/README.md
+sidebar_label: "eBPF"
+-->
+
+# eBPF monitoring with Netdata
+
+Netdata's extended Berkeley Packet Filter (eBPF) collector monitors kernel-level metrics for file descriptors, virtual
+filesystem IO, and process management on Linux systems. You can use our eBPF collector to analyze how and when a process
+accesses files, when it makes system calls, whether it leaks memory or creating zombie processes, and more.
+
+Netdata's eBPF monitoring toolkit uses two custom eBPF programs. The default, called `entry`, monitors calls to a
+variety of kernel functions, such as `do_sys_open`, `__close_fd`, `vfs_read`, `vfs_write`, `_do_fork`, and more. The
+`return` program also monitors the return of each kernel functions to deliver more granular metrics about how your
+system and its applications interact with the Linux kernel.
+
+eBPF monitoring can help you troubleshoot and debug how applications interact with the Linux kernel. See our [guide on
+troubleshooting apps with eBPF metrics](/docs/guides/troubleshoot/monitor-debug-applications-ebpf.md) for configuration
+and troubleshooting tips.
+
+<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 VFS charts made possible by the eBPF collector plugin.</figcaption>
+</figure>
+
+## Enable the collector on Linux
+
+**The eBPF collector is installed and enabled by default on most new installations of the Agent**. The eBPF collector
+does not currently work with [static build installations](/packaging/installer/methods/kickstart-64.md), but improved
+support is in active development.
+
+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.
+
+If your Agent is v1.22 or older, you may to enable the collector yourself. See the [configuration](#configuration)
+section for details.
+
+## Charts
+
+The eBPF collector creates an **eBPF** menu in the Agent's dashboard along with three sub-menus: **File**, **VFS**, and
+**Process**. All the charts in this section update every second. 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.
+
+### File
+
+This group has two charts demonstrating how software interacts with the Linux kernel to open and close file descriptors.
+
+#### File descriptor
+
+This chart contains two dimensions that show the number of calls to the functions `do_sys_open` and `__close_fd`. Most
+software do not commonly call these functions directly, but they are behind the system calls `open(2)`, `openat(2)`,
+and `close(2)`.
+
+#### File error
+
+This chart shows the number of times some software tried and failed to open or close a file descriptor.
+
+### VFS
+
+A [virtual file system](https://en.wikipedia.org/wiki/Virtual_file_system) (VFS) is a layer on top of regular
+filesystems. The functions present inside this API are used for all filesystems, so it's possible the charts in this
+group won't show _all_ the actions that occurred on your system.
+
+#### Deleted objects
+
+This chart monitors calls for `vfs_unlink`. This function is responsible for removing objects from the file system.
+
+#### IO
+
+This chart shows the number of calls to the functions `vfs_read` and `vfs_write`.
+
+#### IO bytes
+
+This chart also monitors `vfs_read` and `vfs_write`, but instead shows the total 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.
+
+#### IO errors
+
+The Agent counts and shows the number of instances where a running program experiences a read or write error.
+
+### Process
+
+For this group, the eBPF collector monitors process/thread creation and process end, and then displays any errors in the
+following charts.
+
+#### Process thread
+
+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)`. In turn, each of these system calls use the function `_do_fork`. To
+generate this chart, the eBPF collector monitors `_do_fork` to populate the `process` dimension, and monitors
+`sys_clone` to identify threads.
+
+#### 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).
+
+#### Task error
+
+The functions responsible for ending tasks do not return values, so this chart contains information about failures on
+process and thread creation.
+
+## Configuration
+
+Enable or disable the entire eBPF collector by editing `netdata.conf`.
+
+```bash
+cd /etc/netdata/ # Replace with your Netdata configuration directory, if not /etc/netdata/
+./edit-config netdata.conf
+```
+
+To enable the collector, scroll down to the `[plugins]` section ensure the relevant line references `ebpf` (not
+`ebpf_process`), is uncommented, and is set to `yes`.
+
+```conf
+[plugins]
+ ebpf = yes
+```
+
+You can also configure the eBPF collector's behavior by editing `ebpf.conf`.
+
+```bash
+cd /etc/netdata/ # Replace with your Netdata configuration directory, if not /etc/netdata/
+./edit-config ebpf.conf
+```
+
+### `[global]`
+
+The `[global]` section defines settings for the whole eBPF collector.
+
+#### ebpf load mode
+
+The collector has two different eBPF programs. These programs monitor 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`](/collectors/apps.plugin/README.md). This integration helps you understand how specific applications
+interact with the Linux kernel.
+
+When the integration is enabled, your dashboard will also show the following charts using low-level Linux metrics:
+
+- eBPF file
+ - Number of calls to open files. (`apps.file_open`)
+ - Number of files closed. (`apps.file_closed`)
+ - Number of calls to open files that returned errors.
+ - Number of calls to close files that returned errors.
+- eBPF syscall
+ - Number of calls to delete files. (`apps.file_deleted`)
+ - Number of calls to `vfs_write`. (`apps.vfs_write_call`)
+ - Number of calls to `vfs_read`. (`apps.vfs_read_call`)
+ - Number of bytes written with `vfs_write`. (`apps.vfs_write_bytes`)
+ - Number of bytes read with `vfs_read`. (`apps.vfs_read_bytes`)
+ - Number of calls to write a file that returned errors.
+ - Number of calls to read a file that returned errors.
+- eBPF process
+ - Number of process created with `do_fork`. (`apps.process_create`)
+ - Number of threads created with `do_fork` or `__x86_64_sys_clone`, depending on your system's kernel version. (`apps.thread_create`)
+ - Number of times that a process called `do_exit`. (`apps.task_close`)
+- eBPF net
+ - Number of bytes sent. (`apps.bandwidth_sent`)
+ - Number of bytes received. (`apps.bandwidth_recv`)
+
+If you want to _disable_ the integration with `apps.plugin` along with the above charts, change the setting `apps` to
+`no`.
+
+```conf
+[global]
+ apps = yes
+```
+
+### `[ebpf programs]`
+
+The eBPF collector enables and runs the following eBPF programs by default:
+
+- `process`: This eBPF program creates charts that show information about process creation, VFS IO, and files removed.
+ When in `return` mode, it also creates charts showing errors when these operations are executed.
+- `network viewer`: This eBPF program creates charts with information about `TCP` and `UDP` functions, including the
+ bandwidth consumed by each.
+
+### `[network connections]`
+
+You can configure the information shown on `outbound` and `inbound` charts with the settings in this section.
+
+```conf
+[network connections]
+ maximum dimensions = 500
+ resolve hostname ips = no
+ 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](/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 443, with the exception of 53 (domain)
+and 145.
+
+The following options are available:
+
+- `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. The default behavior is to only collect data for private IP addresses, but this
+ can be changed with the `ips` setting.
+
+By default, Netdata displays up to 500 dimensions on network connection charts. If there are more possible dimensions,
+they will be bundled into the `other` dimension. You can increase the number of shown dimensions by changing the `maximum
+dimensions` setting.
+
+The dimensions for the traffic charts are created using the destination IPs of the sockets by default. This can be
+changed setting `resolve hostname ips = yes` and restarting Netdata, after this Netdata will create dimensions using
+the `hostnames` every time that is possible to resolve IPs to their hostnames.
+
+### `[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
+```
+
+## 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 netdata
+./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.
+
+```bash
+curl -sSL https://raw.githubusercontent.com/netdata/kernel-collector/master/tools/check-kernel-config.sh | sudo bash
+```
+
+If this script returns no output, your system is ready to compile and run the eBPF collector.
+
+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).
+
+## Performance
+
+Because eBPF monitoring is complex, we are evaluating the performance of this new collector in various real-world
+conditions, across various system loads, and when monitoring complex applications.
+
+Our [initial testing](https://github.com/netdata/netdata/issues/8195) shows the performance of the eBPF collector is
+nearly identical to our [apps.plugin collector](/collectors/apps.plugin/README.md), despite collecting and displaying
+much more sophisticated metrics. You can now use the eBPF to gather deeper insights without affecting the performance of
+your complex applications at any load.
+
+## 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
+```
+
+## 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`.
+
+## Cleaning `kprobe_events`
+The eBPF collector adds entries to the file `/sys/kernel/debug/tracing/kprobe_events`, and cleans them on exit, unless
+another process prevents it. If you need to clean the eBPF entries safely, you can manually run the script
+`/usr/libexec/netdata/plugins.d/reset_netdata_trace.sh`.
+
+[![analytics](https://www.google-analytics.com/collect?v=1&aip=1&t=pageview&_s=1&ds=github&dr=https%3A%2F%2Fgithub.com%2Fnetdata%2Fnetdata&dl=https%3A%2F%2Fmy-netdata.io%2Fgithub%2Fcollectors%2Febpf.plugin%2FREADME&_u=MAC~&cid=5792dfd7-8dc4-476b-af31-da2fdb9f93d2&tid=UA-64295674-3)](<>)
diff --git a/collectors/ebpf.plugin/ebpf.c b/collectors/ebpf.plugin/ebpf.c
new file mode 100644
index 00000000..56e084e9
--- /dev/null
+++ b/collectors/ebpf.plugin/ebpf.c
@@ -0,0 +1,1953 @@
+// 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"
+
+/*****************************************************************
+ *
+ * FUNCTIONS USED BY NETDATA
+ *
+ *****************************************************************/
+
+// callback required by eval()
+int health_variable_lookup(const char *variable, uint32_t hash, struct rrdcalc *rc, calculated_number *result)
+{
+ UNUSED(variable);
+ UNUSED(hash);
+ UNUSED(rc);
+ UNUSED(result);
+ return 0;
+};
+
+void send_statistics(const char *action, const char *action_result, const char *action_data)
+{
+ UNUSED(action);
+ UNUSED(action_result);
+ UNUSED(action_data);
+}
+
+// callbacks required by popen()
+void signals_block(void){};
+void signals_unblock(void){};
+void signals_reset(void){};
+
+// required by get_system_cpus()
+char *netdata_configured_host_prefix = "";
+
+// callback required by fatal()
+void netdata_cleanup_and_exit(int ret)
+{
+ exit(ret);
+}
+
+// ----------------------------------------------------------------------
+/*****************************************************************
+ *
+ * GLOBAL VARIABLES
+ *
+ *****************************************************************/
+
+char *ebpf_plugin_dir = PLUGINS_DIR;
+char *ebpf_user_config_dir = CONFIG_DIR;
+char *ebpf_stock_config_dir = LIBCONFIG_DIR;
+static char *ebpf_configured_log_dir = LOG_DIR;
+
+int update_every = 1;
+static int thread_finished = 0;
+int close_ebpf_plugin = 0;
+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;
+char kernel_string[64];
+int ebpf_nprocs;
+static int isrh;
+uint32_t finalized_threads = 1;
+
+pthread_mutex_t lock;
+pthread_mutex_t collect_data_mutex;
+pthread_cond_t collect_data_cond_var;
+
+ebpf_module_t ebpf_modules[] = {
+ { .thread_name = "process", .config_name = "process", .enabled = 0, .start_routine = ebpf_process_thread,
+ .update_time = 1, .global_charts = 1, .apps_charts = 1, .mode = MODE_ENTRY,
+ .optional = 0 },
+ { .thread_name = "socket", .config_name = "socket", .enabled = 0, .start_routine = ebpf_socket_thread,
+ .update_time = 1, .global_charts = 1, .apps_charts = 1, .mode = MODE_ENTRY,
+ .optional = 0 },
+ { .thread_name = NULL, .enabled = 0, .start_routine = NULL, .update_time = 1,
+ .global_charts = 0, .apps_charts = 1, .mode = MODE_ENTRY,
+ .optional = 0 },
+};
+
+// Link with apps.plugin
+ebpf_process_stat_t *global_process_stat = NULL;
+
+//Network viewer
+ebpf_network_viewer_options_t network_viewer_opt;
+
+/*****************************************************************
+ *
+ * FUNCTIONS USED TO CLEAN MEMORY AND OPERATE SYSTEM FILES
+ *
+ *****************************************************************/
+
+/**
+ * Clean port Structure
+ *
+ * Clean the allocated list.
+ *
+ * @param clean the list that will be cleaned
+ */
+void 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
+ */
+static void 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);
+
+ move = next;
+ }
+ *clean = NULL;
+}
+
+/**
+ * Clean Loaded Events
+ *
+ * This function cleans the events previous loaded on Linux.
+void clean_loaded_events()
+{
+ int event_pid;
+ for (event_pid = 0; ebpf_modules[event_pid].probes; event_pid++)
+ clean_kprobe_events(NULL, (int)ebpf_modules[event_pid].thread_id, ebpf_modules[event_pid].probes);
+}
+ */
+
+/**
+ * Close the collector gracefully
+ *
+ * @param sig is the signal number used to close the collector
+ */
+static void ebpf_exit(int sig)
+{
+ close_ebpf_plugin = 1;
+
+ // When both threads were not finished case I try to go in front this address, the collector will crash
+ if (!thread_finished) {
+ return;
+ }
+
+ freez(global_process_stat);
+
+ /*
+ int ret = fork();
+ if (ret < 0) // error
+ error("Cannot fork(), so I won't be able to clean %skprobe_events", NETDATA_DEBUGFS);
+ else if (!ret) { // child
+ int i;
+ for (i = getdtablesize(); i >= 0; --i)
+ close(i);
+
+ int fd = open("/dev/null", O_RDWR, 0);
+ if (fd != -1) {
+ dup2(fd, STDIN_FILENO);
+ dup2(fd, STDOUT_FILENO);
+ dup2(fd, STDERR_FILENO);
+ }
+
+ if (fd > 2)
+ close(fd);
+
+ int sid = setsid();
+ if (sid >= 0) {
+ debug(D_EXIT, "Wait for father %d die", getpid());
+ sleep_usec(200000); // Sleep 200 miliseconds to father dies.
+ clean_loaded_events();
+ } else {
+ error("Cannot become session id leader, so I won't try to clean kprobe_events.\n");
+ }
+ } else { // parent
+ exit(0);
+ }
+ */
+
+ exit(sig);
+}
+
+/*****************************************************************
+ *
+ * FUNCTIONS TO CREATE CHARTS
+ *
+ *****************************************************************/
+
+/**
+ * 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 begin command on standard output
+ *
+ * @param family the chart family name
+ * @param name the chart name
+ */
+void write_begin_chart(char *family, char *name)
+{
+ printf("BEGIN %s.%s\n", family, name);
+}
+
+/**
+ * Write END command on stdout.
+ */
+inline void write_end_chart()
+{
+ printf("END\n");
+}
+
+/**
+ * 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)
+{
+ int ret = printf("SET %s = %lld\n", dim, value);
+ UNUSED(ret);
+}
+
+/**
+ * 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 tha 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)
+{
+ write_begin_chart(family, name);
+
+ uint32_t i = 0;
+ while (move && i < end) {
+ write_chart_dimension(move->name, move->ncall);
+
+ move = move->next;
+ i++;
+ }
+
+ 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)
+{
+ write_begin_chart(family, name);
+
+ int i = 0;
+ while (move && i < end) {
+ write_chart_dimension(move->name, move->nerr);
+
+ move = move->next;
+ i++;
+ }
+
+ write_end_chart();
+}
+
+/**
+ * Call the necessary functions to create a chart.
+ *
+ * @param family the chart family
+ * @param move the pointer with the values that will be published
+ *
+ * @return It returns a variable tha maps the charts that did not have zero values.
+ */
+void write_io_chart(char *chart, char *family, char *dwrite, char *dread, netdata_publish_vfs_common_t *pvc)
+{
+ write_begin_chart(family, chart);
+
+ write_chart_dimension(dwrite, (long long)pvc->write);
+ write_chart_dimension(dread, (long long)pvc->read);
+
+ write_end_chart();
+}
+
+/**
+ * Write chart cmd on standard output
+ *
+ * @param type the chart type
+ * @param id the chart id
+ * @param title the chart title
+ * @param units the units label
+ * @param family the group name used to attach the chart on dashaboard
+ * @param charttype the chart type
+ * @param order the chart order
+ */
+void ebpf_write_chart_cmd(char *type, char *id, char *title, char *units, char *family, char *charttype, int order)
+{
+ printf("CHART %s.%s '' '%s' '%s' '%s' '' %s %d %d\n",
+ type,
+ id,
+ title,
+ units,
+ family,
+ charttype,
+ order,
+ update_every);
+}
+
+/**
+ * Write the dimension command on standard output
+ *
+ * @param n the dimension name
+ * @param d the dimension information
+ */
+void ebpf_write_global_dimension(char *n, char *d)
+{
+ printf("DIMENSION %s %s absolute 1 1\n", n, d);
+}
+
+/**
+ * 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 = move->next;
+ i++;
+ }
+}
+
+/**
+ * Call write_chart_cmd to create the charts
+ *
+ * @param type the chart type
+ * @param id the chart id
+ * @param units the axis label
+ * @param family the group name used to attach the chart on dashaboard
+ * @param order the 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
+ */
+void ebpf_create_chart(char *type,
+ char *id,
+ char *title,
+ char *units,
+ char *family,
+ int order,
+ void (*ncd)(void *, int),
+ void *move,
+ int end)
+{
+ ebpf_write_chart_cmd(type, id, title, units, family, "line", order);
+
+ ncd(move, end);
+}
+
+/**
+ * Create charts on apps 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 order the chart order
+ * @param root structure used to create the dimensions.
+ */
+void ebpf_create_charts_on_apps(char *id, char *title, char *units, char *family, int order, struct target *root)
+{
+ struct target *w;
+ ebpf_write_chart_cmd(NETDATA_APPS_FAMILY, id, title, units, family, "stacked", order);
+
+ for (w = root; w; w = w->next) {
+ if (unlikely(w->exposed))
+ fprintf(stdout, "DIMENSION %s '' absolute 1 1\n", w->name);
+ }
+}
+
+/*****************************************************************
+ *
+ * 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 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 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];
+ 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; ebpf_modules[i].thread_name; i++) {
+ ebpf_modules[i].mode = lmode;
+ }
+}
+
+/**
+ * Enable specific charts selected by user.
+ *
+ * @param em the structure that will be changed
+ * @param enable the status about the apps charts.
+ */
+static inline void ebpf_enable_specific_chart(struct ebpf_module *em, int enable)
+{
+ em->enabled = 1;
+ if (!enable) {
+ em->apps_charts = 1;
+ }
+ em->global_charts = 1;
+}
+
+/**
+ * Enable all charts
+ *
+ * @param apps what is the current status of apps
+ */
+static inline void ebpf_enable_all_charts(int apps)
+{
+ int i;
+ for (i = 0; ebpf_modules[i].thread_name; i++) {
+ ebpf_enable_specific_chart(&ebpf_modules[i], apps);
+ }
+}
+
+/**
+ * Enable the specified chart group
+ *
+ * @param idx the index of ebpf_modules that I am enabling
+ * @param disable_apps should I keep apps charts?
+ */
+static inline void ebpf_enable_chart(int idx, int disable_apps)
+{
+ int i;
+ for (i = 0; ebpf_modules[i].thread_name; i++) {
+ if (i == idx) {
+ ebpf_enable_specific_chart(&ebpf_modules[i], disable_apps);
+ break;
+ }
+ }
+}
+
+/**
+ * Disable APPs
+ *
+ * Disable charts for apps loading only global charts.
+ */
+static inline void ebpf_disable_apps()
+{
+ int i;
+ for (i = 0; ebpf_modules[i].thread_name; i++) {
+ ebpf_modules[i].apps_charts = 0;
+ }
+}
+
+/**
+ * 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 program is a data collector plugin for netdata.\n"
+ "\n"
+ " Available command line options:\n"
+ "\n"
+ " SECONDS set the data collection frequency.\n"
+ "\n"
+ " --help or -h show this help.\n"
+ "\n"
+ " --version or -v show software version.\n"
+ "\n"
+ " --global or -g disable charts per application.\n"
+ "\n"
+ " --all or -a Enable all chart groups (global and apps), unless -g is also given.\n"
+ "\n"
+ " --net or -n Enable network viewer charts.\n"
+ "\n"
+ " --process or -p Enable charts related to process run time.\n"
+ "\n"
+ " --return or -r Run the collector in return mode.\n"
+ "\n",
+ VERSION,
+ (year >= 116) ? year + 1900 : 2020);
+}
+
+/*****************************************************************
+ *
+ * AUXILIAR 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 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 (ntohl(rfirst->addr32[0]) <= ntohl(cmpfirst->addr32[0]) &&
+ ntohl(rlast->addr32[0]) >= ntohl(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.
+ */
+static inline void fill_ip_list(ebpf_network_viewer_ip_list_t **out, ebpf_network_viewer_ip_list_t *in, char *table)
+{
+#ifndef NETDATA_INTERNAL_CHECKS
+ UNUSED(table);
+#endif
+ if (likely(*out)) {
+ ebpf_network_viewer_ip_list_t *move = *out, *store = *out;
+ while (move) {
+ if (in->ver == move->ver && is_ip_inside_range(&move->first, &move->last, &in->first, &in->last, in->ver)) {
+ info("The range/value (%s) is inside the range/value (%s) already inserted, it will be ignored.",
+ in->value, move->value);
+ freez(in->value);
+ freez(in);
+ return;
+ }
+ store = move;
+ move = move->next;
+ }
+
+ store->next = in;
+ } else {
+ *out = in;
+ }
+
+#ifdef NETDATA_INTERNAL_CHECKS
+ char first[512], last[512];
+ if (in->ver == AF_INET) {
+ if (inet_ntop(AF_INET, in->first.addr8, first, INET_ADDRSTRLEN) &&
+ inet_ntop(AF_INET, in->last.addr8, last, INET_ADDRSTRLEN))
+ 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);
+ } else {
+ if (inet_ntop(AF_INET6, in->first.addr8, first, INET6_ADDRSTRLEN) &&
+ inet_ntop(AF_INET6, in->last.addr8, last, INET6_ADDRSTRLEN))
+ 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
+}
+
+/**
+ * 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;
+ 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);
+ }
+
+ procfile_close(ff);
+}
+
+/**
+ * Read Local addresseses
+ *
+ * Read the local address from the interfaces.
+ */
+static void read_local_addresses()
+{
+ struct ifaddrs *ifaddr, *ifa;
+ if (getifaddrs(&ifaddr) == -1) {
+ 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);
+ }
+ }
+
+ fill_ip_list((family == AF_INET)?&network_viewer_opt.ipv4_local_ip:&network_viewer_opt.ipv6_local_ip,
+ w,
+ "selector");
+ }
+
+ freeifaddrs(ifaddr);
+}
+
+/**
+ * Start Ptherad Variable
+ *
+ * This function starts all pthread variables.
+ *
+ * @return It returns 0 on success and -1.
+ */
+int ebpf_start_pthread_variables()
+{
+ pthread_mutex_init(&lock, NULL);
+ pthread_mutex_init(&collect_data_mutex, NULL);
+
+ if (pthread_cond_init(&collect_data_cond_var, NULL)) {
+ thread_finished++;
+ error("Cannot start conditional variable to control Apps charts.");
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * Allocate the vectors used for all threads.
+ */
+static void ebpf_allocate_common_vectors()
+{
+ all_pids = callocz((size_t)pid_max, sizeof(struct pid_stat *));
+ global_process_stat = callocz((size_t)ebpf_nprocs, sizeof(ebpf_process_stat_t));
+}
+
+/**
+ * Fill the ebpf_data structure with default values
+ *
+ * @param ef the pointer to set default values
+ */
+void fill_ebpf_data(ebpf_data_t *ef)
+{
+ memset(ef, 0, sizeof(ebpf_data_t));
+ ef->kernel_string = kernel_string;
+ ef->running_on_kernel = running_on_kernel;
+ ef->map_fd = callocz(EBPF_MAX_MAPS, sizeof(int));
+ ef->isrh = isrh;
+}
+
+/**
+ * Define how to load the ebpf programs
+ *
+ * @param ptr the option given by users
+ */
+static inline void how_to_load(char *ptr)
+{
+ if (!strcasecmp(ptr, "return"))
+ ebpf_set_thread_mode(MODE_RETURN);
+ else if (!strcasecmp(ptr, "entry"))
+ ebpf_set_thread_mode(MODE_ENTRY);
+ else
+ error("the option %s for \"ebpf load mode\" is not a valid option.", ptr);
+}
+
+/**
+ * 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 ) {
+ 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) {
+ 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
+ info("Adding values %s( %u, %u) to %s port list used on network viewer",
+ in->value, ntohs(in->first), ntohs(in->last),
+ (*out == network_viewer_opt.included_port)?"included":"excluded");
+#endif
+}
+
+/**
+ * Fill port list
+ *
+ * Fill 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 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;
+
+ 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 == '!') {
+ 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) {
+ 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) {
+ 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) {
+ 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)htons((uint16_t)first);
+ w->last = (uint16_t)htons((uint16_t)last);
+ w->cmp_first = (uint16_t)first;
+ w->cmp_last = (uint16_t)last;
+
+ fill_port_list(list, w);
+}
+
+/**
+ * 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 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) {
+ info("Cannot resolv 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);
+}
+
+/**
+ * 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 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 broadcast(in_addr_t addr, int prefix)
+{
+ return (addr | ~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 ipv4_network(in_addr_t addr, int prefix)
+{
+ return (addr & netmask(prefix));
+}
+
+/**
+ * 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 ip2nl(uint8_t *dst, char *ip, int domain, char *source)
+{
+ if (inet_pton(domain, ip, dst) <= 0) {
+ error("The address specified (%s) is invalid ", source);
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * 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]);
+ if (prefix > 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));
+}
+
+/**
+ * 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]);
+ if (prefix > 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));
+}
+
+/**
+ * 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 parse_ip_list(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;
+
+ 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 == '!') {
+ info("The exclusion cannot be in the second part of the range %s, it will be ignored.", ipdup);
+ goto cleanipdup;
+ }
+
+ if (!select) { // CIDR
+ select = 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) {
+ info("The specified CIDR %s is not valid, the IP %s will be ignored.", end, ip);
+ goto cleanipdup;
+ }
+
+ last.addr32[0] = htonl(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(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))
+ info("The network value of CIDR %s was updated for %s .", ipdup, ipv4_msg);
+ }
+ } else { // Range
+ select = ip2nl(first.addr8, ip, AF_INET, ipdup);
+ if (select)
+ goto cleanipdup;
+
+ select = ip2nl(last.addr8, end, AF_INET, ipdup);
+ if (select)
+ goto cleanipdup;
+ }
+
+ if (htonl(first.addr32[0]) > htonl(last.addr32[0])) {
+ 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 = 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 == '!') {
+ info("The exclusion cannot be in the second part of the range %s, it will be ignored.", ipdup);
+ goto cleanipdup;
+ }
+
+ select = ip2nl(first.addr8, ip, AF_INET6, ipdup);
+ if (select)
+ goto cleanipdup;
+
+ select = ip2nl(last.addr8, end, AF_INET6, ipdup);
+ if (select)
+ goto cleanipdup;
+ } else { // CIDR
+ *end++ = 0x00;
+ if (*end == '!') {
+ 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) {
+ info("The CIDR %s is not valid, the address %s will be ignored.", end, ip);
+ goto cleanipdup;
+ }
+
+ uint64_t prefix = (uint64_t)select;
+ select = 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))
+ info("The network value of CIDR %s was updated for %s .", ipdup, ipv6_msg);
+ }
+ }
+
+ if ((be64toh(*(uint64_t *)&first.addr32[2]) > be64toh(*(uint64_t *)&last.addr32[2]) &&
+ !memcmp(first.addr32, last.addr32, 2*sizeof(uint32_t))) ||
+ (be64toh(*(uint64_t *)&first.addr32) > be64toh(*(uint64_t *)&last.addr32)) ) {
+ 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 = 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));
+
+ fill_ip_list(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.
+ */
+static void parse_ips(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
+ parse_ip_list((!neg)?(void **)&network_viewer_opt.included_ips:(void **)&network_viewer_opt.excluded_ips,
+ ptr);
+ }
+
+ ptr = end;
+ }
+}
+
+
+/**
+ * Parse Port Range
+ *
+ * Parse the port ranges given and create Network Viewer Port Structure
+ *
+ * @param ptr is a pointer with the text to parse.
+ */
+static void 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
+ parse_port_list((!neg)?(void **)&network_viewer_opt.included_port:(void **)&network_viewer_opt.excluded_port,
+ ptr);
+ } else if (isalpha(*ptr)) { // Parse service
+ parse_service_list((!neg)?(void **)&network_viewer_opt.included_port:(void **)&network_viewer_opt.excluded_port,
+ ptr);
+ } else if (*ptr == '*') { // All
+ parse_port_list((!neg)?(void **)&network_viewer_opt.included_port:(void **)&network_viewer_opt.excluded_port,
+ ptr);
+ }
+
+ ptr = end;
+ }
+}
+
+/**
+ * Link hostname
+ *
+ * @param out is the output link list
+ * @param in the hostname to add to list.
+ */
+static void 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)) {
+ 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
+ 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 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);
+
+ link_hostname((!neg)?&network_viewer_opt.included_hostnames:&network_viewer_opt.excluded_hostnames,
+ hostname);
+
+ parse = end;
+ }
+}
+
+/**
+ * Read max dimension.
+ *
+ * Netdata plot two dimensions per connection, so it is necessary to adjust the values.
+ */
+static void read_max_dimension()
+{
+ int maxdim ;
+ maxdim = (int) appconfig_get_number(&collector_config,
+ EBPF_NETWORK_VIEWER_SECTION,
+ "maximum dimensions",
+ NETDATA_NV_CAP_VALUE);
+ if (maxdim < 0) {
+ error("'maximum dimensions = %d' must be a positive number, Netdata will change for default value %ld.",
+ maxdim, NETDATA_NV_CAP_VALUE);
+ maxdim = NETDATA_NV_CAP_VALUE;
+ }
+
+ maxdim /= 2;
+ if (!maxdim) {
+ info("The number of dimensions is too small (%u), we are setting it to minimum 2", network_viewer_opt.max_dim);
+ network_viewer_opt.max_dim = 1;
+ }
+
+ network_viewer_opt.max_dim = (uint32_t)maxdim;
+}
+
+/**
+ * Parse network viewer section
+ */
+static void parse_network_viewer_section()
+{
+ read_max_dimension();
+
+ network_viewer_opt.hostname_resolution_enabled = appconfig_get_boolean(&collector_config,
+ EBPF_NETWORK_VIEWER_SECTION,
+ "resolve hostnames",
+ CONFIG_BOOLEAN_NO);
+
+ network_viewer_opt.service_resolution_enabled = appconfig_get_boolean(&collector_config,
+ EBPF_NETWORK_VIEWER_SECTION,
+ "resolve service names",
+ CONFIG_BOOLEAN_NO);
+
+ char *value = appconfig_get(&collector_config, EBPF_NETWORK_VIEWER_SECTION,
+ "ports", NULL);
+ parse_ports(value);
+
+ if (network_viewer_opt.hostname_resolution_enabled) {
+ value = appconfig_get(&collector_config, EBPF_NETWORK_VIEWER_SECTION, "hostnames", NULL);
+ link_hostnames(value);
+ } else {
+ info("Name resolution is disabled, collector will not parser \"hostnames\" list.");
+ }
+
+ value = appconfig_get(&collector_config, EBPF_NETWORK_VIEWER_SECTION,
+ "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");
+ parse_ips(value);
+}
+
+/**
+ * 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 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){
+ 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) {
+ info("Dupplicated 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
+ 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.
+ */
+static void parse_service_name_section()
+{
+ struct section *co = appconfig_get_section(&collector_config, EBPF_SERVICE_NAME_SECTION);
+ if (co) {
+ struct config_option *cv;
+ for (cv = co->values; cv ; cv = cv->next) {
+ 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)
+ link_dimension_name(port_string, simple_hash(port_string), "Netdata");
+}
+
+/**
+ * Read collector values
+ *
+ * @param disable_apps variable to store information related to apps.
+ */
+static void read_collector_values(int *disable_apps)
+{
+ // 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", "entry");
+ else
+ value = appconfig_get(&collector_config, EBPF_GLOBAL_SECTION, "ebpf load mode", "entry");
+
+ how_to_load(value);
+
+ // 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, "apps",
+ CONFIG_BOOLEAN_YES);
+ enabled = (enabled == CONFIG_BOOLEAN_NO)?CONFIG_BOOLEAN_YES:CONFIG_BOOLEAN_NO;
+ }
+ *disable_apps = (int)enabled;
+
+ // Read ebpf programs section
+ enabled = appconfig_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION,
+ ebpf_modules[0].config_name, CONFIG_BOOLEAN_YES);
+ int started = 0;
+ if (enabled) {
+ ebpf_enable_chart(EBPF_MODULE_PROCESS_IDX, *disable_apps);
+ started++;
+ }
+
+ // 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[1].config_name,
+ CONFIG_BOOLEAN_NO);
+
+ if (enabled) {
+ ebpf_enable_chart(EBPF_MODULE_SOCKET_IDX, *disable_apps);
+ // Read network viewer section if network viewer is enabled
+ parse_network_viewer_section();
+ parse_service_name_section();
+ started++;
+ }
+
+ // This is kept to keep compatibility
+ enabled = appconfig_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "network connection monitoring",
+ CONFIG_BOOLEAN_NO);
+ if (!enabled)
+ enabled = appconfig_get_boolean(&collector_config, EBPF_PROGRAMS_SECTION, "network connections",
+ CONFIG_BOOLEAN_NO);
+ ebpf_modules[1].optional = enabled;
+
+ if (!started){
+ ebpf_enable_all_charts(*disable_apps);
+ // Read network viewer section
+ parse_network_viewer_section();
+ parse_service_name_section();
+ }
+}
+
+/**
+ * Load collector config
+ *
+ * @param path the path where the file ebpf.conf is stored.
+ * @param disable_apps variable to store the information about apps plugin status.
+ *
+ * @return 0 on success and -1 otherwise.
+ */
+static int load_collector_config(char *path, int *disable_apps)
+{
+ char lpath[4096];
+
+ snprintf(lpath, 4095, "%s/%s", path, "ebpf.conf");
+
+ if (!appconfig_load(&collector_config, lpath, 0, NULL))
+ return -1;
+
+ read_collector_values(disable_apps);
+
+ 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 > NETDATA_MAX_PROCESSOR) {
+ ebpf_nprocs = NETDATA_MAX_PROCESSOR;
+ }
+
+ isrh = get_redhat_release();
+ pid_max = get_system_pid_max();
+}
+
+/**
+ * Parse arguments given from user.
+ *
+ * @param argc the number of arguments
+ * @param argv the pointer to the arguments
+ */
+static void parse_args(int argc, char **argv)
+{
+ int enabled = 0;
+ int disable_apps = 0;
+ int freq = 0;
+ int option_index = 0;
+ static struct option long_options[] = {
+ {"help", no_argument, 0, 'h' },
+ {"version", no_argument, 0, 'v' },
+ {"global", no_argument, 0, 'g' },
+ {"all", no_argument, 0, 'a' },
+ {"net", no_argument, 0, 'n' },
+ {"process", no_argument, 0, 'p' },
+ {"return", no_argument, 0, 'r' },
+ {0, 0, 0, 0}
+ };
+
+ memset(&network_viewer_opt, 0, sizeof(network_viewer_opt));
+ network_viewer_opt.max_dim = NETDATA_NV_CAP_VALUE;
+
+ if (argc > 1) {
+ int n = (int)str2l(argv[1]);
+ if (n > 0) {
+ freq = n;
+ }
+ }
+
+ while (1) {
+ int c = getopt_long(argc, argv, "hvganpr", long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h': {
+ ebpf_print_help();
+ exit(0);
+ }
+ case 'v': {
+ printf("ebpf.plugin %s\n", VERSION);
+ exit(0);
+ }
+ case 'g': {
+ disable_apps = 1;
+ ebpf_disable_apps();
+#ifdef NETDATA_INTERNAL_CHECKS
+ info(
+ "EBPF running with global chart group, because it was started with the option \"--global\" or \"-g\".");
+#endif
+ break;
+ }
+ case 'a': {
+ ebpf_enable_all_charts(disable_apps);
+#ifdef NETDATA_INTERNAL_CHECKS
+ info("EBPF running with all chart groups, because it was started with the option \"--all\" or \"-a\".");
+#endif
+ break;
+ }
+ case 'n': {
+ enabled = 1;
+ ebpf_enable_chart(EBPF_MODULE_SOCKET_IDX, disable_apps);
+#ifdef NETDATA_INTERNAL_CHECKS
+ info("EBPF enabling \"NET\" charts, because it was started with the option \"--net\" or \"-n\".");
+#endif
+ break;
+ }
+ case 'p': {
+ enabled = 1;
+ ebpf_enable_chart(EBPF_MODULE_PROCESS_IDX, disable_apps);
+#ifdef NETDATA_INTERNAL_CHECKS
+ info(
+ "EBPF enabling \"PROCESS\" charts, because it was started with the option \"--process\" or \"-p\".");
+#endif
+ break;
+ }
+ case 'r': {
+ ebpf_set_thread_mode(MODE_RETURN);
+#ifdef NETDATA_INTERNAL_CHECKS
+ info("EBPF running in \"return\" mode, because it was started with the option \"--return\" or \"-r\".");
+#endif
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+ }
+
+ if (freq > 0) {
+ update_every = freq;
+ }
+
+ if (load_collector_config(ebpf_user_config_dir, &disable_apps)) {
+ info(
+ "Does not have a configuration file inside `%s/ebpf.conf. It will try to load stock file.",
+ ebpf_user_config_dir);
+ if (load_collector_config(ebpf_stock_config_dir, &disable_apps)) {
+ info("Does not have a stock file. It is starting with default options.");
+ } else {
+ enabled = 1;
+ }
+ } else {
+ enabled = 1;
+ }
+
+ if (!enabled) {
+ ebpf_enable_all_charts(disable_apps);
+#ifdef NETDATA_INTERNAL_CHECKS
+ info("EBPF running with all charts, because neither \"-n\" or \"-p\" was given.");
+#endif
+ }
+
+ if (disable_apps)
+ return;
+
+ // Load apps_groups.conf
+ if (ebpf_read_apps_groups_conf(
+ &apps_groups_default_target, &apps_groups_root_target, ebpf_user_config_dir, "groups")) {
+ 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")) {
+ error(
+ "Cannot read process groups '%s/apps_groups.conf'. There are no internal defaults. Failing.",
+ ebpf_stock_config_dir);
+ thread_finished++;
+ ebpf_exit(1);
+ }
+ } else
+ info("Loaded config file '%s/apps_groups.conf'", ebpf_user_config_dir);
+}
+
+/*****************************************************************
+ *
+ * COLLECTOR ENTRY POINT
+ *
+ *****************************************************************/
+
+/**
+ * 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)
+{
+ set_global_variables();
+ parse_args(argc, argv);
+
+ running_on_kernel = get_kernel_version(kernel_string, 63);
+ if (!has_condition_to_run(running_on_kernel)) {
+ error("The current collector cannot run on this kernel.");
+ return 2;
+ }
+
+ if (!am_i_running_as_root()) {
+ error(
+ "ebpf.plugin should either run as root (now running with uid %u, euid %u) or have special capabilities..",
+ (unsigned int)getuid(), (unsigned int)geteuid());
+ return 3;
+ }
+
+ // set name
+ program_name = "ebpf.plugin";
+
+ // disable syslog
+ error_log_syslog = 0;
+
+ // set errors flood protection to 100 logs per hour
+ error_log_errors_per_period = 100;
+ error_log_throttle_period = 3600;
+
+ struct rlimit r = { RLIM_INFINITY, RLIM_INFINITY };
+ if (setrlimit(RLIMIT_MEMLOCK, &r)) {
+ error("Setrlimit(RLIMIT_MEMLOCK)");
+ return 4;
+ }
+
+ signal(SIGINT, ebpf_exit);
+ signal(SIGTERM, ebpf_exit);
+ signal(SIGPIPE, ebpf_exit);
+
+ if (ebpf_start_pthread_variables()) {
+ thread_finished++;
+ error("Cannot start mutex to control overall charts.");
+ ebpf_exit(5);
+ }
+
+ ebpf_allocate_common_vectors();
+
+ read_local_addresses();
+ 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);
+
+ struct netdata_static_thread ebpf_threads[] = {
+ {"EBPF PROCESS", NULL, NULL, 1, NULL, NULL, ebpf_modules[0].start_routine},
+ {"EBPF SOCKET" , NULL, NULL, 1, NULL, NULL, ebpf_modules[1].start_routine},
+ {NULL , NULL, NULL, 0, NULL, NULL, NULL}
+ };
+
+ //clean_loaded_events();
+
+ int i;
+ for (i = 0; ebpf_threads[i].name != NULL; i++) {
+ struct netdata_static_thread *st = &ebpf_threads[i];
+ st->thread = mallocz(sizeof(netdata_thread_t));
+
+ ebpf_module_t *em = &ebpf_modules[i];
+ em->thread_id = i;
+ netdata_thread_create(st->thread, st->name, NETDATA_THREAD_OPTION_JOINABLE, st->start_routine, em);
+ }
+
+ for (i = 0; ebpf_threads[i].name != NULL; i++) {
+ struct netdata_static_thread *st = &ebpf_threads[i];
+ netdata_thread_join(*st->thread, NULL);
+ }
+
+ thread_finished++;
+ ebpf_exit(0);
+
+ return 0;
+}
diff --git a/collectors/ebpf.plugin/ebpf.conf b/collectors/ebpf.plugin/ebpf.conf
new file mode 100644
index 00000000..3a5b7739
--- /dev/null
+++ b/collectors/ebpf.plugin/ebpf.conf
@@ -0,0 +1,45 @@
+#
+# 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`.
+# 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
+ apps = yes
+
+#
+# eBPF Programs
+#
+# The eBPF collector enables and runs the following eBPF programs by default:
+#
+# `process` : This eBPF program creates charts that show information about process creation, VFS IO, and
+# files removed.
+# `socket` : This eBPF program creates charts with information about `TCP` and `UDP` functions, including the
+# bandwidth consumed by each.
+[ebpf programs]
+ process = yes
+ socket = yes
+ network connections = no
+
+#
+# Network Connection
+#
+# This is a feature with status WIP(Work in Progress)
+#
+[network connections]
+ maximum dimensions = 50
+ resolve hostnames = no
+ resolve service names = no
+ 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
+ hostnames = *
+
+[service name]
+ 19999 = Netdata
diff --git a/collectors/ebpf.plugin/ebpf.h b/collectors/ebpf.plugin/ebpf.h
new file mode 100644
index 00000000..1f582295
--- /dev/null
+++ b/collectors/ebpf.plugin/ebpf.h
@@ -0,0 +1,194 @@
+// 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 "daemon/main.h"
+
+#include "ebpf_apps.h"
+
+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 uint64_t netdata_idx_t;
+
+typedef struct netdata_publish_syscall {
+ char *dimension;
+ char *name;
+ 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;
+
+extern ebpf_module_t ebpf_modules[];
+#define EBPF_MODULE_PROCESS_IDX 0
+#define EBPF_MODULE_SOCKET_IDX 1
+
+// 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
+
+// Chart defintions
+#define NETDATA_EBPF_FAMILY "ebpf"
+
+// 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
+
+// Threads
+extern void *ebpf_process_thread(void *ptr);
+extern void *ebpf_socket_thread(void *ptr);
+
+// Common variables
+extern pthread_mutex_t lock;
+extern int close_ebpf_plugin;
+extern int ebpf_nprocs;
+extern int running_on_kernel;
+extern char *ebpf_plugin_dir;
+extern char kernel_string[64];
+
+extern pthread_mutex_t collect_data_mutex;
+extern pthread_cond_t collect_data_cond_var;
+
+// Common functions
+extern void ebpf_global_labels(netdata_syscall_stat_t *is,
+ netdata_publish_syscall_t *pio,
+ char **dim,
+ char **name,
+ int end);
+
+extern void ebpf_write_chart_cmd(char *type,
+ char *id,
+ char *title,
+ char *units,
+ char *family,
+ char *charttype,
+ int order);
+
+extern void ebpf_write_global_dimension(char *n, char *d);
+
+extern void ebpf_create_global_dimension(void *ptr, int end);
+
+extern void ebpf_create_chart(char *type,
+ char *id,
+ char *title,
+ char *units,
+ char *family,
+ int order,
+ void (*ncd)(void *, int),
+ void *move,
+ int end);
+
+extern void write_begin_chart(char *family, char *name);
+
+extern void write_chart_dimension(char *dim, long long value);
+
+extern void write_count_chart(char *name, char *family, netdata_publish_syscall_t *move, uint32_t end);
+
+extern void write_err_chart(char *name, char *family, netdata_publish_syscall_t *move, int end);
+
+extern void write_io_chart(char *chart, char *family, char *dwrite, char *dread, netdata_publish_vfs_common_t *pvc);
+
+extern void fill_ebpf_data(ebpf_data_t *ef);
+
+extern void ebpf_create_charts_on_apps(char *name,
+ char *title,
+ char *units,
+ char *family,
+ int order,
+ struct target *root);
+
+extern void write_end_chart();
+
+#define EBPF_GLOBAL_SECTION "global"
+#define EBPF_PROGRAMS_SECTION "ebpf programs"
+#define EBPF_NETWORK_VIEWER_SECTION "network connections"
+#define EBPF_SERVICE_NAME_SECTION "service name"
+
+#define EBPF_COMMON_DIMENSION_CALL "calls/s"
+#define EBPF_COMMON_DIMENSION_BYTESS "bytes/s"
+#define EBPF_COMMON_DIMENSION_DIFFERENCE "difference"
+#define EBPF_COMMON_DIMENSION_PACKETS "packets"
+
+// Common variables
+extern char *ebpf_user_config_dir;
+extern char *ebpf_stock_config_dir;
+extern int debug_enabled;
+extern struct pid_stat *root_of_pids;
+
+// Socket functions and variables
+// Common functions
+extern void ebpf_socket_create_apps_charts(ebpf_module_t *em, struct target *root);
+extern collected_number get_value_from_structure(char *basis, size_t offset);
+extern struct pid_stat *root_of_pids;
+extern ebpf_process_stat_t *global_process_stat;
+extern size_t all_pids_count;
+extern int update_every;
+extern uint32_t finalized_threads;
+
+#define EBPF_MAX_SYNCHRONIZATION_TIME 300
+
+#endif /* NETDATA_COLLECTOR_EBPF_H */
diff --git a/collectors/ebpf.plugin/ebpf_apps.c b/collectors/ebpf.plugin/ebpf_apps.c
new file mode 100644
index 00000000..062c9a4e
--- /dev/null
+++ b/collectors/ebpf.plugin/ebpf_apps.c
@@ -0,0 +1,1086 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "ebpf.h"
+#include "ebpf_socket.h"
+#include "ebpf_apps.h"
+
+// ----------------------------------------------------------------------------
+// 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;
+}
+
+/**
+ * Read socket statistic
+ *
+ * Read information from kernel ring to user ring.
+ *
+ * @param ep the table with all process stats values.
+ * @param fd the file descriptor mapped from kernel
+ * @param ef a pointer for the functions mapped from dynamic library
+ * @param pids the list of pids associated to a target.
+ *
+ * @return
+ */
+size_t read_bandwidth_statistic_using_pid_on_target(ebpf_bandwidth_t **ep, int fd, struct pid_on_target *pids)
+{
+ size_t count = 0;
+ while (pids) {
+ uint32_t current_pid = pids->pid;
+ if (!ebpf_read_hash_table(ep[current_pid], fd, current_pid))
+ count++;
+
+ pids = pids->next;
+ }
+
+ return count;
+}
+
+/**
+ * Read bandwidth statistic using hash table
+ *
+ * @param out the output tensor that will receive the information.
+ * @param fd the file descriptor that has the data
+ * @param bpf_map_lookup_elem a pointer for the function to read the data
+ * @param bpf_map_get_next_key a pointer fo the function to read the index.
+ */
+size_t read_bandwidth_statistic_using_hash_table(ebpf_bandwidth_t **out, int fd)
+{
+ size_t count = 0;
+ uint32_t key = 0;
+ uint32_t next_key = 0;
+
+ while (bpf_map_get_next_key(fd, &key, &next_key) == 0) {
+ ebpf_bandwidth_t *eps = out[next_key];
+ if (!eps) {
+ eps = callocz(1, sizeof(ebpf_process_stat_t));
+ out[next_key] = eps;
+ }
+ ebpf_read_hash_table(eps, fd, next_key);
+ }
+
+ return count;
+}
+
+/*****************************************************************
+ *
+ * FUNCTIONS CALLED FROM COLLECTORS
+ *
+ *****************************************************************/
+
+/**
+ * Am I running as Root
+ *
+ * Verify the user that is running the collector.
+ *
+ * @return It returns 1 for root and 0 otherwise.
+ */
+int am_i_running_as_root()
+{
+ uid_t uid = getuid(), euid = geteuid();
+
+ if (uid == 0 || euid == 0) {
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * Reset the target values
+ *
+ * @param root the pointer to the chain that will be reseted.
+ *
+ * @return it returns the number of structures that was reseted.
+ */
+size_t zero_all_targets(struct target *root)
+{
+ struct target *w;
+ size_t count = 0;
+
+ for (w = root; w; w = w->next) {
+ count++;
+
+ if (unlikely(w->root_pid)) {
+ struct pid_on_target *pid_on_target = w->root_pid;
+
+ while (pid_on_target) {
+ struct pid_on_target *pid_on_target_to_free = pid_on_target;
+ pid_on_target = pid_on_target->next;
+ free(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 target *agrt)
+{
+ struct 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 target *get_apps_groups_target(struct target **agrt, const char *id, struct 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 target *w, *last = *agrt;
+ for (w = *agrt; w; w = w->next) {
+ if (w->idhash == hash && strncmp(nid, w->id, 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 target));
+ strncpyz(w->id, nid, MAX_NAME);
+ w->idhash = simple_hash(w->id);
+
+ if (unlikely(!target))
+ // copy the name
+ strncpyz(w->name, name, MAX_NAME);
+ else
+ // copy the id
+ strncpyz(w->name, nid, MAX_NAME);
+
+ strncpyz(w->compare, nid, 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 succcess and -1 otherwise
+ */
+int ebpf_read_apps_groups_conf(struct target **agdt, struct 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(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 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 target *n = get_apps_groups_target(agrt, s, w, name);
+ if (!n) {
+ 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 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_COMPARE_NAME 100
+#define MAX_NAME 100
+#define MAX_CMDLINE 16384
+
+struct pid_stat **all_pids = NULL; // to avoid allocations, we pre-allocate the
+ // the entire pid space.
+struct pid_stat *root_of_pids = NULL; // global list of all processes running
+
+size_t all_pids_count = 0; // the number of processes running
+
+struct 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 pid_stat *p, uint32_t log, int status)
+{
+ if (unlikely(!status)) {
+ // 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:
+ error(
+ "Cannot process %s/proc/%d/io (command '%s')", netdata_configured_host_prefix, p->pid,
+ p->comm);
+ break;
+
+ case PID_LOG_STATUS:
+ error(
+ "Cannot process %s/proc/%d/status (command '%s')", netdata_configured_host_prefix, p->pid,
+ p->comm);
+ break;
+
+ case PID_LOG_CMDLINE:
+ error(
+ "Cannot process %s/proc/%d/cmdline (command '%s')", netdata_configured_host_prefix, p->pid,
+ p->comm);
+ break;
+
+ case PID_LOG_FDS:
+ 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:
+ error("unhandled error for pid %d, command '%s'", p->pid, p->comm);
+ break;
+ }
+ }
+ }
+ errno = 0;
+ } else if (unlikely(p->log_thrown & 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 specifid pid.
+ *
+ * @param pid the pid to search the data.
+ *
+ * @return It returns the pid entry structure
+ */
+static inline struct pid_stat *get_pid_entry(pid_t pid)
+{
+ if (unlikely(all_pids[pid]))
+ return all_pids[pid];
+
+ struct pid_stat *p = callocz(1, sizeof(struct pid_stat));
+
+ if (likely(root_of_pids))
+ root_of_pids->prev = p;
+
+ p->next = root_of_pids;
+ root_of_pids = p;
+
+ p->pid = pid;
+
+ all_pids[pid] = p;
+ 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 pid_stat *p)
+{
+ targets_assignment_counter++;
+
+ uint32_t hash = simple_hash(p->comm);
+ size_t pclen = strlen(p->comm);
+
+ struct 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 pid_stat_structure.
+ *
+ * @return It returns 1 on success and 0 otherwise.
+ */
+static inline int read_proc_pid_cmdline(struct pid_stat *p)
+{
+ static char cmdline[MAX_CMDLINE + 1];
+
+ 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] = ' ';
+ }
+
+ if (p->cmdline)
+ freez(p->cmdline);
+ p->cmdline = strdupz(cmdline);
+
+ debug_log("Read file '%s' contents: %s", p->cmdline_filename, p->cmdline);
+
+ return 1;
+
+cleanup:
+ // copy the command to the command line
+ if (p->cmdline)
+ freez(p->cmdline);
+ p->cmdline = strdupz(p->comm);
+ return 0;
+}
+
+/**
+ * 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 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, 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 succcess and 0 otherwise
+ */
+static inline int collect_data_for_pid(pid_t pid, void *ptr)
+{
+ if (unlikely(pid < 0 || pid > pid_max)) {
+ error("Invalid pid %d read (expected %d to %d). Ignoring process.", pid, 0, pid_max);
+ return 0;
+ }
+
+ struct pid_stat *p = get_pid_entry(pid);
+ 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)) {
+ 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 pid_stat *p, *pp;
+
+ // link all children to their parents
+ // and update children count on parents
+ for (p = 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 = 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 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 = 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 = 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 (all_pids[INIT_PID])
+ all_pids[INIT_PID]->target = apps_groups_default_target;
+
+ // pid 0 goes always to default target
+ if (all_pids[0])
+ all_pids[0]->target = apps_groups_default_target;
+
+ // give a default target on all top level processes
+ if (unlikely(debug_enabled))
+ loops++;
+ for (p = 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 (all_pids[1])
+ 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 = 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 target *root)
+{
+ struct 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 del_pid_entry(pid_t pid)
+{
+ struct pid_stat *p = all_pids[pid];
+
+ if (unlikely(!p)) {
+ error("attempted to free pid %d that is not allocated.", pid);
+ return;
+ }
+
+ debug_log("process %d %s exited, deleting it.", pid, p->comm);
+
+ if (root_of_pids == p)
+ 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);
+ freez(p->cmdline);
+ freez(p);
+
+ all_pids[pid] = NULL;
+ all_pids_count--;
+}
+
+/**
+ * Remove PIDs when they are not running more.
+ */
+void cleanup_exited_pids()
+{
+ struct pid_stat *p = NULL;
+
+ for (p = root_of_pids; p;) {
+ if (!p->updated && (!p->keep || p->keeploops > 0)) {
+ 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;
+ del_pid_entry(r);
+
+ // Clean process structure
+ freez(global_process_stats[r]);
+ global_process_stats[r] = NULL;
+
+ freez(current_apps_data[r]);
+ current_apps_data[r] = NULL;
+ prev_apps_data[r] = NULL;
+
+ // Clean socket structures
+ if (socket_bandwidth_curr) {
+ freez(socket_bandwidth_curr[r]);
+ socket_bandwidth_curr[r] = NULL;
+ socket_bandwidth_prev[r] = NULL;
+ }
+ } else {
+ if (unlikely(p->keep))
+ p->keeploops++;
+ p->keep = 0;
+ 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;
+
+ 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 target *w, struct pid_stat *p, struct target *o)
+{
+ UNUSED(o);
+
+ if (unlikely(!p->updated)) {
+ // the process is not running
+ return;
+ }
+
+ if (unlikely(!w)) {
+ error("pid %d %s was left without a target!", p->pid, p->comm);
+ return;
+ }
+
+ w->processes++;
+ struct pid_on_target *pid_on_target = mallocz(sizeof(struct pid_on_target));
+ pid_on_target->pid = p->pid;
+ pid_on_target->next = w->root_pid;
+ w->root_pid = pid_on_target;
+}
+
+/**
+ * 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.
+ */
+void collect_data_for_all_processes(int tbl_pid_stats_fd)
+{
+ struct pid_stat *pids = 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();
+
+ uint32_t key;
+ pids = root_of_pids; // global list of all processes running
+ // while (bpf_map_get_next_key(tbl_pid_stats_fd, &key, &next_key) == 0) {
+ while (pids) {
+ key = pids->pid;
+ ebpf_process_stat_t *w = global_process_stats[key];
+ if (!w) {
+ w = mallocz(sizeof(ebpf_process_stat_t));
+ global_process_stats[key] = w;
+ }
+
+ if (bpf_map_lookup_elem(tbl_pid_stats_fd, &key, w)) {
+ // Clean Process structures
+ freez(w);
+ global_process_stats[key] = NULL;
+
+ freez(current_apps_data[key]);
+ current_apps_data[key] = NULL;
+ prev_apps_data[key] = NULL;
+
+ // Clean socket structures
+ if (socket_bandwidth_curr) {
+ freez(socket_bandwidth_curr[key]);
+ socket_bandwidth_curr[key] = NULL;
+ socket_bandwidth_prev[key] = NULL;
+ }
+
+ pids = pids->next;
+ continue;
+ }
+
+ pids = pids->next;
+ }
+
+ 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 = root_of_pids; pids; pids = pids->next)
+ aggregate_pid_on_target(pids->target, pids, NULL);
+
+ post_aggregate_targets(apps_groups_root_target);
+}
diff --git a/collectors/ebpf.plugin/ebpf_apps.h b/collectors/ebpf.plugin/ebpf_apps.h
new file mode 100644
index 00000000..46d36966
--- /dev/null
+++ b/collectors/ebpf.plugin/ebpf_apps.h
@@ -0,0 +1,431 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_EBPF_APPS_H
+#define NETDATA_EBPF_APPS_H 1
+
+#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"
+
+#define NETDATA_APPS_FAMILY "apps"
+#define NETDATA_APPS_FILE_GROUP "ebpf file"
+#define NETDATA_APPS_VFS_GROUP "ebpf vfs"
+#define NETDATA_APPS_PROCESS_GROUP "ebpf process"
+#define NETDATA_APPS_NET_GROUP "ebpf net"
+
+#include "ebpf_process.h"
+
+#define MAX_COMPARE_NAME 100
+#define MAX_NAME 100
+
+// ----------------------------------------------------------------------------
+// process_pid_stat
+//
+// Fields read from the kernel ring for a specific PID
+//
+typedef struct process_pid_stat {
+ uint64_t pid_tgid; // Unique identifier
+ uint32_t pid; // process id
+
+ // Count number of calls done for specific function
+ uint32_t open_call;
+ uint32_t write_call;
+ uint32_t writev_call;
+ uint32_t read_call;
+ uint32_t readv_call;
+ uint32_t unlink_call;
+ uint32_t exit_call;
+ uint32_t release_call;
+ uint32_t fork_call;
+ uint32_t clone_call;
+ uint32_t close_call;
+
+ // Count number of bytes written or read
+ uint64_t write_bytes;
+ uint64_t writev_bytes;
+ uint64_t readv_bytes;
+ uint64_t read_bytes;
+
+ // Count number of errors for the specified function
+ uint32_t open_err;
+ uint32_t write_err;
+ uint32_t writev_err;
+ uint32_t read_err;
+ uint32_t readv_err;
+ uint32_t unlink_err;
+ uint32_t fork_err;
+ uint32_t clone_err;
+ uint32_t close_err;
+} process_pid_stat_t;
+
+// ----------------------------------------------------------------------------
+// socket_bandwidth
+//
+// Fields read from the kernel ring for a specific PID
+//
+typedef struct socket_bandwidth {
+ uint64_t first;
+ uint64_t ct;
+ uint64_t sent;
+ uint64_t received;
+ unsigned char removed;
+} socket_bandwidth_t;
+
+// ----------------------------------------------------------------------------
+// pid_stat
+//
+// structure to store data for each process running
+// see: man proc for the description of the fields
+
+struct pid_fd {
+ int fd;
+
+#ifndef __FreeBSD__
+ ino_t inode;
+ char *filename;
+ uint32_t link_hash;
+ size_t cache_iterations_counter;
+ size_t cache_iterations_reset;
+#endif
+};
+
+struct target {
+ char compare[MAX_COMPARE_NAME + 1];
+ uint32_t comparehash;
+ size_t comparelen;
+
+ char id[MAX_NAME + 1];
+ uint32_t idhash;
+
+ char name[MAX_NAME + 1];
+
+ uid_t uid;
+ gid_t gid;
+
+ /* These variables are not necessary for eBPF collector
+ kernel_uint_t minflt;
+ kernel_uint_t cminflt;
+ kernel_uint_t majflt;
+ kernel_uint_t cmajflt;
+ kernel_uint_t utime;
+ kernel_uint_t stime;
+ kernel_uint_t gtime;
+ kernel_uint_t cutime;
+ kernel_uint_t cstime;
+ kernel_uint_t cgtime;
+ kernel_uint_t num_threads;
+ // kernel_uint_t rss;
+
+ kernel_uint_t status_vmsize;
+ kernel_uint_t status_vmrss;
+ kernel_uint_t status_vmshared;
+ kernel_uint_t status_rssfile;
+ kernel_uint_t status_rssshmem;
+ kernel_uint_t status_vmswap;
+
+ kernel_uint_t io_logical_bytes_read;
+ kernel_uint_t io_logical_bytes_written;
+ // kernel_uint_t io_read_calls;
+ // kernel_uint_t io_write_calls;
+ kernel_uint_t io_storage_bytes_read;
+ kernel_uint_t io_storage_bytes_written;
+ // kernel_uint_t io_cancelled_write_bytes;
+
+ int *target_fds;
+ int target_fds_size;
+
+ kernel_uint_t openfiles;
+ kernel_uint_t openpipes;
+ kernel_uint_t opensockets;
+ kernel_uint_t openinotifies;
+ kernel_uint_t openeventfds;
+ kernel_uint_t opentimerfds;
+ kernel_uint_t opensignalfds;
+ kernel_uint_t openeventpolls;
+ kernel_uint_t openother;
+ */
+
+ kernel_uint_t starttime;
+ kernel_uint_t collected_starttime;
+
+ /*
+ kernel_uint_t uptime_min;
+ kernel_uint_t uptime_sum;
+ kernel_uint_t uptime_max;
+ */
+
+ 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 pid_on_target *root_pid; // list of aggregated pids for target debugging
+
+ struct target *target; // the one that will be reported to netdata
+ struct target *next;
+};
+
+extern struct target *apps_groups_default_target;
+extern struct target *apps_groups_root_target;
+extern struct target *users_root_target;
+extern struct target *groups_root_target;
+
+struct pid_stat {
+ int32_t pid;
+ char comm[MAX_COMPARE_NAME + 1];
+ char *cmdline;
+
+ uint32_t log_thrown;
+
+ // char state;
+ int32_t ppid;
+
+ // int32_t pgrp;
+ // int32_t session;
+ // int32_t tty_nr;
+ // int32_t tpgid;
+ // uint64_t flags;
+
+ /*
+ // these are raw values collected
+ kernel_uint_t minflt_raw;
+ kernel_uint_t cminflt_raw;
+ kernel_uint_t majflt_raw;
+ kernel_uint_t cmajflt_raw;
+ kernel_uint_t utime_raw;
+ kernel_uint_t stime_raw;
+ kernel_uint_t gtime_raw; // guest_time
+ kernel_uint_t cutime_raw;
+ kernel_uint_t cstime_raw;
+ kernel_uint_t cgtime_raw; // cguest_time
+
+ // these are rates
+ kernel_uint_t minflt;
+ kernel_uint_t cminflt;
+ kernel_uint_t majflt;
+ kernel_uint_t cmajflt;
+ kernel_uint_t utime;
+ kernel_uint_t stime;
+ kernel_uint_t gtime;
+ kernel_uint_t cutime;
+ kernel_uint_t cstime;
+ kernel_uint_t cgtime;
+
+ // int64_t priority;
+ // int64_t nice;
+ int32_t num_threads;
+ // int64_t itrealvalue;
+ kernel_uint_t collected_starttime;
+ // kernel_uint_t vsize;
+ // kernel_uint_t rss;
+ // kernel_uint_t rsslim;
+ // kernel_uint_t starcode;
+ // kernel_uint_t endcode;
+ // kernel_uint_t startstack;
+ // kernel_uint_t kstkesp;
+ // kernel_uint_t kstkeip;
+ // uint64_t signal;
+ // uint64_t blocked;
+ // uint64_t sigignore;
+ // uint64_t sigcatch;
+ // uint64_t wchan;
+ // uint64_t nswap;
+ // uint64_t cnswap;
+ // int32_t exit_signal;
+ // int32_t processor;
+ // uint32_t rt_priority;
+ // uint32_t policy;
+ // kernel_uint_t delayacct_blkio_ticks;
+
+ uid_t uid;
+ gid_t gid;
+
+ kernel_uint_t status_vmsize;
+ kernel_uint_t status_vmrss;
+ kernel_uint_t status_vmshared;
+ kernel_uint_t status_rssfile;
+ kernel_uint_t status_rssshmem;
+ kernel_uint_t status_vmswap;
+#ifndef __FreeBSD__
+ ARL_BASE *status_arl;
+#endif
+
+ kernel_uint_t io_logical_bytes_read_raw;
+ kernel_uint_t io_logical_bytes_written_raw;
+ // kernel_uint_t io_read_calls_raw;
+ // kernel_uint_t io_write_calls_raw;
+ kernel_uint_t io_storage_bytes_read_raw;
+ kernel_uint_t io_storage_bytes_written_raw;
+ // kernel_uint_t io_cancelled_write_bytes_raw;
+
+ kernel_uint_t io_logical_bytes_read;
+ kernel_uint_t io_logical_bytes_written;
+ // kernel_uint_t io_read_calls;
+ // kernel_uint_t io_write_calls;
+ kernel_uint_t io_storage_bytes_read;
+ kernel_uint_t io_storage_bytes_written;
+ // kernel_uint_t io_cancelled_write_bytes;
+ */
+
+ struct pid_fd *fds; // array of fds it uses
+ size_t fds_size; // the size of the fds array
+
+ 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
+
+ struct target *target; // app_groups.conf targets
+ struct target *user_target; // uid based targets
+ struct target *group_target; // gid based targets
+
+ usec_t stat_collected_usec;
+ usec_t last_stat_collected_usec;
+
+ usec_t io_collected_usec;
+ usec_t last_io_collected_usec;
+
+ kernel_uint_t uptime;
+
+ char *fds_dirname; // the full directory name in /proc/PID/fd
+
+ char *stat_filename;
+ char *status_filename;
+ char *io_filename;
+ char *cmdline_filename;
+
+ struct pid_stat *parent;
+ struct pid_stat *prev;
+ struct pid_stat *next;
+};
+
+// ----------------------------------------------------------------------------
+// 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 pid_on_target {
+ int32_t pid;
+ struct pid_on_target *next;
+};
+
+// ----------------------------------------------------------------------------
+// Structures used to read information from kernel ring
+typedef struct ebpf_process_stat {
+ uint64_t pid_tgid;
+ uint32_t pid;
+
+ //Counter
+ uint32_t open_call;
+ uint32_t write_call;
+ uint32_t writev_call;
+ uint32_t read_call;
+ uint32_t readv_call;
+ uint32_t unlink_call;
+ uint32_t exit_call;
+ uint32_t release_call;
+ uint32_t fork_call;
+ uint32_t clone_call;
+ uint32_t close_call;
+
+ //Accumulator
+ uint64_t write_bytes;
+ uint64_t writev_bytes;
+ uint64_t readv_bytes;
+ uint64_t read_bytes;
+
+ //Counter
+ uint32_t open_err;
+ uint32_t write_err;
+ uint32_t writev_err;
+ uint32_t read_err;
+ uint32_t readv_err;
+ uint32_t unlink_err;
+ uint32_t fork_err;
+ uint32_t clone_err;
+ uint32_t close_err;
+
+ uint8_t removeme;
+} ebpf_process_stat_t;
+
+typedef struct ebpf_bandwidth {
+ uint32_t pid;
+
+ uint64_t first; // First timestamp
+ uint64_t ct; // Last timestamp
+ 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
+} ebpf_bandwidth_t;
+
+/**
+ * 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 pid_stat **all_pids;
+
+extern int ebpf_read_apps_groups_conf(struct target **apps_groups_default_target,
+ struct target **apps_groups_root_target,
+ const char *path,
+ const char *file);
+
+extern void clean_apps_groups_target(struct target *apps_groups_root_target);
+
+extern size_t zero_all_targets(struct target *root);
+
+extern int am_i_running_as_root();
+
+extern void cleanup_exited_pids();
+
+extern int ebpf_read_hash_table(void *ep, int fd, uint32_t pid);
+
+extern size_t read_processes_statistic_using_pid_on_target(ebpf_process_stat_t **ep,
+ int fd,
+ struct pid_on_target *pids);
+
+extern size_t read_bandwidth_statistic_using_pid_on_target(ebpf_bandwidth_t **ep, int fd, struct pid_on_target *pids);
+
+extern void collect_data_for_all_processes(int tbl_pid_stats_fd);
+
+extern ebpf_process_stat_t **global_process_stats;
+extern ebpf_process_publish_apps_t **current_apps_data;
+extern ebpf_process_publish_apps_t **prev_apps_data;
+
+#endif /* NETDATA_EBPF_APPS_H */
diff --git a/collectors/ebpf.plugin/ebpf_kernel_reject_list.txt b/collectors/ebpf.plugin/ebpf_kernel_reject_list.txt
new file mode 100644
index 00000000..d56b216a
--- /dev/null
+++ b/collectors/ebpf.plugin/ebpf_kernel_reject_list.txt
@@ -0,0 +1 @@
+Ubuntu 4.18.0-13.
diff --git a/collectors/ebpf.plugin/ebpf_process.c b/collectors/ebpf.plugin/ebpf_process.c
new file mode 100644
index 00000000..9a1d69c0
--- /dev/null
+++ b/collectors/ebpf.plugin/ebpf_process.c
@@ -0,0 +1,1071 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include <sys/resource.h>
+
+#include "ebpf.h"
+#include "ebpf_process.h"
+
+/*****************************************************************
+ *
+ * GLOBAL VARIABLES
+ *
+ *****************************************************************/
+
+static char *process_dimension_names[NETDATA_MAX_MONITOR_VECTOR] = { "open", "close", "delete", "read", "write",
+ "process", "task", "process", "thread" };
+static char *process_id_names[NETDATA_MAX_MONITOR_VECTOR] = { "do_sys_open", "__close_fd", "vfs_unlink",
+ "vfs_read", "vfs_write", "do_exit",
+ "release_task", "_do_fork", "sys_clone" };
+static char *status[] = { "process", "zombie" };
+
+static netdata_idx_t *process_hash_values = NULL;
+static netdata_syscall_stat_t *process_aggregated_data = NULL;
+static netdata_publish_syscall_t *process_publish_aggregated = NULL;
+
+static ebpf_data_t process_data;
+
+ebpf_process_stat_t **global_process_stats = NULL;
+ebpf_process_publish_apps_t **current_apps_data = NULL;
+ebpf_process_publish_apps_t **prev_apps_data = NULL;
+
+int process_enabled = 0;
+
+static int *map_fd = NULL;
+static struct bpf_object *objects = NULL;
+static struct bpf_link **probe_links = NULL;
+
+/*****************************************************************
+ *
+ * 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;
+ while (move) {
+ if (input->call != move->pcall) {
+ //This condition happens to avoid initial values with dimensions higher than normal values.
+ if (move->pcall) {
+ move->ncall = (input->call > move->pcall) ? input->call - move->pcall : move->pcall - input->call;
+ move->nbyte = (input->bytes > move->pbyte) ? input->bytes - move->pbyte : move->pbyte - input->bytes;
+ move->nerr = (input->ecall > move->nerr) ? input->ecall - move->perr : move->perr - input->ecall;
+ } else {
+ move->ncall = 0;
+ move->nbyte = 0;
+ move->nerr = 0;
+ }
+
+ move->pcall = input->call;
+ move->pbyte = input->bytes;
+ move->perr = input->ecall;
+ } else {
+ move->ncall = 0;
+ move->nbyte = 0;
+ move->nerr = 0;
+ }
+
+ input = input->next;
+ move = move->next;
+ }
+
+ pvc->write = -((long)publish[2].nbyte);
+ pvc->read = (long)publish[3].nbyte;
+
+ pvc->running = (long)publish[7].ncall - (long)publish[8].ncall;
+ publish[6].ncall = -publish[6].ncall; // release
+ pvc->zombie = (long)publish[5].ncall + (long)publish[6].ncall;
+}
+
+/**
+ * Update apps dimension to publish.
+ *
+ * @param curr Last values read from memory.
+ * @param prev Previous values read from memory.
+ * @param first was it allocated now?
+ */
+static void
+ebpf_process_update_apps_publish(ebpf_process_publish_apps_t *curr, ebpf_process_publish_apps_t *prev, int first)
+{
+ if (first)
+ return;
+
+ curr->publish_open = curr->call_sys_open - prev->call_sys_open;
+ curr->publish_closed = curr->call_close_fd - prev->call_close_fd;
+ curr->publish_deleted = curr->call_vfs_unlink - prev->call_vfs_unlink;
+ curr->publish_write_call = curr->call_write - prev->call_write;
+ curr->publish_write_bytes = curr->bytes_written - prev->bytes_written;
+ curr->publish_read_call = curr->call_read - prev->call_read;
+ curr->publish_read_bytes = curr->bytes_read - prev->bytes_read;
+ curr->publish_process = curr->call_do_fork - prev->call_do_fork;
+ curr->publish_thread = curr->call_sys_clone - prev->call_sys_clone;
+ curr->publish_task = curr->call_release_task - prev->call_release_task;
+ curr->publish_open_error = curr->ecall_sys_open - prev->ecall_sys_open;
+ curr->publish_close_error = curr->ecall_close_fd - prev->ecall_close_fd;
+ curr->publish_write_error = curr->ecall_write - prev->ecall_write;
+ curr->publish_read_error = curr->ecall_read - prev->ecall_read;
+}
+
+/**
+ * Call the necessary functions to create a chart.
+ *
+ * @param family the chart family
+ * @param move the pointer with the values that will be published
+ */
+static void write_status_chart(char *family, netdata_publish_vfs_common_t *pvc)
+{
+ write_begin_chart(family, NETDATA_PROCESS_STATUS_NAME);
+
+ write_chart_dimension(status[0], (long long)pvc->running);
+ write_chart_dimension(status[1], (long long)pvc->zombie);
+
+ write_end_chart();
+}
+
+/**
+ * Send data to Netdata calling auxiliar functions.
+ *
+ * @param em the structure with thread information
+ */
+static void ebpf_process_send_data(ebpf_module_t *em)
+{
+ netdata_publish_vfs_common_t pvc;
+ ebpf_update_global_publish(process_publish_aggregated, &pvc, process_aggregated_data);
+
+ write_count_chart(
+ NETDATA_FILE_OPEN_CLOSE_COUNT, NETDATA_EBPF_FAMILY, process_publish_aggregated, 2);
+
+ write_count_chart(
+ NETDATA_VFS_FILE_CLEAN_COUNT, NETDATA_EBPF_FAMILY, &process_publish_aggregated[NETDATA_DEL_START], 1);
+
+ write_count_chart(
+ NETDATA_VFS_FILE_IO_COUNT, NETDATA_EBPF_FAMILY, &process_publish_aggregated[NETDATA_IN_START_BYTE], 2);
+
+ write_count_chart(
+ NETDATA_EXIT_SYSCALL, NETDATA_EBPF_FAMILY, &process_publish_aggregated[NETDATA_EXIT_START], 2);
+ write_count_chart(
+ NETDATA_PROCESS_SYSCALL, NETDATA_EBPF_FAMILY, &process_publish_aggregated[NETDATA_PROCESS_START], 2);
+
+ write_status_chart(NETDATA_EBPF_FAMILY, &pvc);
+ if (em->mode < MODE_ENTRY) {
+ write_err_chart(
+ NETDATA_FILE_OPEN_ERR_COUNT, NETDATA_EBPF_FAMILY, process_publish_aggregated, 2);
+ write_err_chart(
+ NETDATA_VFS_FILE_ERR_COUNT, NETDATA_EBPF_FAMILY, &process_publish_aggregated[2], NETDATA_VFS_ERRORS);
+ write_err_chart(
+ NETDATA_PROCESS_ERROR_NAME, NETDATA_EBPF_FAMILY, &process_publish_aggregated[NETDATA_PROCESS_START], 2);
+ }
+
+ write_io_chart(NETDATA_VFS_IO_FILE_BYTES, NETDATA_EBPF_FAMILY, process_id_names[3], process_id_names[4], &pvc);
+}
+
+/**
+ * Sum values for pid
+ *
+ * @param root the structure with all available PIDs
+ *
+ * @param offset the address that we are reading
+ *
+ * @return it returns the sum of all PIDs
+ */
+long long ebpf_process_sum_values_for_pids(struct pid_on_target *root, size_t offset)
+{
+ long long ret = 0;
+ while (root) {
+ int32_t pid = root->pid;
+ ebpf_process_publish_apps_t *w = current_apps_data[pid];
+ if (w) {
+ ret += get_value_from_structure((char *)w, offset);
+ }
+
+ root = root->next;
+ }
+
+ return ret;
+}
+
+/**
+ * Remove process pid
+ *
+ * Remove from PID task table when task_release was called.
+ */
+void ebpf_process_remove_pids()
+{
+ struct pid_stat *pids = root_of_pids;
+ int pid_fd = map_fd[0];
+ while (pids) {
+ uint32_t pid = pids->pid;
+ ebpf_process_stat_t *w = global_process_stats[pid];
+ if (w) {
+ if (w->removeme) {
+ freez(w);
+ global_process_stats[pid] = NULL;
+ bpf_map_delete_elem(pid_fd, &pid);
+ }
+ }
+
+ pids = pids->next;
+ }
+}
+
+/**
+ * Send data to Netdata calling auxiliar functions.
+ *
+ * @param em the structure with thread information
+ * @param root the target list.
+ */
+void ebpf_process_send_apps_data(ebpf_module_t *em, struct target *root)
+{
+ struct target *w;
+ collected_number value;
+
+ write_begin_chart(NETDATA_APPS_FAMILY, NETDATA_SYSCALL_APPS_FILE_OPEN);
+ for (w = root; w; w = w->next) {
+ if (unlikely(w->exposed && w->processes)) {
+ value = ebpf_process_sum_values_for_pids(w->root_pid, offsetof(ebpf_process_publish_apps_t, publish_open));
+ write_chart_dimension(w->name, value);
+ }
+ }
+ write_end_chart();
+
+ if (em->mode < MODE_ENTRY) {
+ write_begin_chart(NETDATA_APPS_FAMILY, NETDATA_SYSCALL_APPS_FILE_OPEN_ERROR);
+ for (w = root; w; w = w->next) {
+ if (unlikely(w->exposed && w->processes)) {
+ value = ebpf_process_sum_values_for_pids(
+ w->root_pid, offsetof(ebpf_process_publish_apps_t, publish_open_error));
+ write_chart_dimension(w->name, value);
+ }
+ }
+ write_end_chart();
+ }
+
+ write_begin_chart(NETDATA_APPS_FAMILY, NETDATA_SYSCALL_APPS_FILE_CLOSED);
+ for (w = root; w; w = w->next) {
+ if (unlikely(w->exposed && w->processes)) {
+ value =
+ ebpf_process_sum_values_for_pids(w->root_pid, offsetof(ebpf_process_publish_apps_t, publish_closed));
+ write_chart_dimension(w->name, value);
+ }
+ }
+ write_end_chart();
+
+ if (em->mode < MODE_ENTRY) {
+ write_begin_chart(NETDATA_APPS_FAMILY, NETDATA_SYSCALL_APPS_FILE_CLOSE_ERROR);
+ for (w = root; w; w = w->next) {
+ if (unlikely(w->exposed && w->processes)) {
+ value = ebpf_process_sum_values_for_pids(
+ w->root_pid, offsetof(ebpf_process_publish_apps_t, publish_close_error));
+ write_chart_dimension(w->name, value);
+ }
+ }
+ write_end_chart();
+ }
+
+ write_begin_chart(NETDATA_APPS_FAMILY, NETDATA_SYSCALL_APPS_FILE_DELETED);
+ for (w = root; w; w = w->next) {
+ if (unlikely(w->exposed && w->processes)) {
+ value =
+ ebpf_process_sum_values_for_pids(w->root_pid, offsetof(ebpf_process_publish_apps_t, publish_deleted));
+ write_chart_dimension(w->name, value);
+ }
+ }
+ write_end_chart();
+
+ write_begin_chart(NETDATA_APPS_FAMILY, NETDATA_SYSCALL_APPS_VFS_WRITE_CALLS);
+ for (w = root; w; w = w->next) {
+ if (unlikely(w->exposed && w->processes)) {
+ value = ebpf_process_sum_values_for_pids(
+ w->root_pid, offsetof(ebpf_process_publish_apps_t, publish_write_call));
+ write_chart_dimension(w->name, value);
+ }
+ }
+ write_end_chart();
+
+ if (em->mode < MODE_ENTRY) {
+ write_begin_chart(NETDATA_APPS_FAMILY, NETDATA_SYSCALL_APPS_VFS_WRITE_CALLS_ERROR);
+ for (w = root; w; w = w->next) {
+ if (unlikely(w->exposed && w->processes)) {
+ value = ebpf_process_sum_values_for_pids(
+ w->root_pid, offsetof(ebpf_process_publish_apps_t, publish_write_error));
+ write_chart_dimension(w->name, value);
+ }
+ }
+ write_end_chart();
+ }
+
+ write_begin_chart(NETDATA_APPS_FAMILY, NETDATA_SYSCALL_APPS_VFS_READ_CALLS);
+ for (w = root; w; w = w->next) {
+ if (unlikely(w->exposed && w->processes)) {
+ value =
+ ebpf_process_sum_values_for_pids(w->root_pid, offsetof(ebpf_process_publish_apps_t, publish_read_call));
+ write_chart_dimension(w->name, value);
+ }
+ }
+ write_end_chart();
+
+ if (em->mode < MODE_ENTRY) {
+ write_begin_chart(NETDATA_APPS_FAMILY, NETDATA_SYSCALL_APPS_VFS_READ_CALLS_ERROR);
+ for (w = root; w; w = w->next) {
+ if (unlikely(w->exposed && w->processes)) {
+ value = ebpf_process_sum_values_for_pids(
+ w->root_pid, offsetof(ebpf_process_publish_apps_t, publish_read_error));
+ write_chart_dimension(w->name, value);
+ }
+ }
+ write_end_chart();
+ }
+
+ write_begin_chart(NETDATA_APPS_FAMILY, NETDATA_SYSCALL_APPS_VFS_WRITE_BYTES);
+ for (w = root; w; w = w->next) {
+ if (unlikely(w->exposed && w->processes)) {
+ value = ebpf_process_sum_values_for_pids(
+ w->root_pid, offsetof(ebpf_process_publish_apps_t, publish_write_bytes));
+ write_chart_dimension(w->name, value);
+ }
+ }
+ write_end_chart();
+
+ write_begin_chart(NETDATA_APPS_FAMILY, NETDATA_SYSCALL_APPS_VFS_READ_BYTES);
+ for (w = root; w; w = w->next) {
+ if (unlikely(w->exposed && w->processes)) {
+ value = ebpf_process_sum_values_for_pids(
+ w->root_pid, offsetof(ebpf_process_publish_apps_t, publish_read_bytes));
+ write_chart_dimension(w->name, value);
+ }
+ }
+ write_end_chart();
+
+ write_begin_chart(NETDATA_APPS_FAMILY, NETDATA_SYSCALL_APPS_TASK_PROCESS);
+ for (w = root; w; w = w->next) {
+ if (unlikely(w->exposed && w->processes)) {
+ value =
+ ebpf_process_sum_values_for_pids(w->root_pid, offsetof(ebpf_process_publish_apps_t, publish_process));
+ write_chart_dimension(w->name, value);
+ }
+ }
+ write_end_chart();
+
+ write_begin_chart(NETDATA_APPS_FAMILY, NETDATA_SYSCALL_APPS_TASK_THREAD);
+ for (w = root; w; w = w->next) {
+ if (unlikely(w->exposed && w->processes)) {
+ value =
+ ebpf_process_sum_values_for_pids(w->root_pid, offsetof(ebpf_process_publish_apps_t, publish_thread));
+ write_chart_dimension(w->name, value);
+ }
+ }
+ write_end_chart();
+
+ write_begin_chart(NETDATA_APPS_FAMILY, NETDATA_SYSCALL_APPS_TASK_CLOSE);
+ for (w = root; w; w = w->next) {
+ if (unlikely(w->exposed && w->processes)) {
+ value = ebpf_process_sum_values_for_pids(w->root_pid, offsetof(ebpf_process_publish_apps_t, publish_task));
+ write_chart_dimension(w->name, value);
+ }
+ }
+ write_end_chart();
+
+ ebpf_process_remove_pids();
+}
+
+/*****************************************************************
+ *
+ * READ INFORMATION FROM KERNEL RING
+ *
+ *****************************************************************/
+
+/**
+ * Read the hash table and store data to allocated vectors.
+ */
+static void read_hash_global_tables()
+{
+ uint64_t idx;
+ netdata_idx_t res[NETDATA_GLOBAL_VECTOR];
+
+ netdata_idx_t *val = process_hash_values;
+ for (idx = 0; idx < NETDATA_GLOBAL_VECTOR; idx++) {
+ if (!bpf_map_lookup_elem(map_fd[1], &idx, val)) {
+ uint64_t total = 0;
+ int i;
+ int end = (running_on_kernel < NETDATA_KERNEL_V4_15) ? 1 : ebpf_nprocs;
+ for (i = 0; i < end; i++)
+ total += val[i];
+
+ res[idx] = total;
+ } else {
+ res[idx] = 0;
+ }
+ }
+
+ process_aggregated_data[0].call = res[NETDATA_KEY_CALLS_DO_SYS_OPEN];
+ process_aggregated_data[1].call = res[NETDATA_KEY_CALLS_CLOSE_FD];
+ process_aggregated_data[2].call = res[NETDATA_KEY_CALLS_VFS_UNLINK];
+ process_aggregated_data[3].call = res[NETDATA_KEY_CALLS_VFS_READ] + res[NETDATA_KEY_CALLS_VFS_READV];
+ process_aggregated_data[4].call = res[NETDATA_KEY_CALLS_VFS_WRITE] + res[NETDATA_KEY_CALLS_VFS_WRITEV];
+ process_aggregated_data[5].call = res[NETDATA_KEY_CALLS_DO_EXIT];
+ process_aggregated_data[6].call = res[NETDATA_KEY_CALLS_RELEASE_TASK];
+ process_aggregated_data[7].call = res[NETDATA_KEY_CALLS_DO_FORK];
+ process_aggregated_data[8].call = res[NETDATA_KEY_CALLS_SYS_CLONE];
+
+ process_aggregated_data[0].ecall = res[NETDATA_KEY_ERROR_DO_SYS_OPEN];
+ process_aggregated_data[1].ecall = res[NETDATA_KEY_ERROR_CLOSE_FD];
+ process_aggregated_data[2].ecall = res[NETDATA_KEY_ERROR_VFS_UNLINK];
+ process_aggregated_data[3].ecall = res[NETDATA_KEY_ERROR_VFS_READ] + res[NETDATA_KEY_ERROR_VFS_READV];
+ process_aggregated_data[4].ecall = res[NETDATA_KEY_ERROR_VFS_WRITE] + res[NETDATA_KEY_ERROR_VFS_WRITEV];
+ process_aggregated_data[7].ecall = res[NETDATA_KEY_ERROR_DO_FORK];
+ process_aggregated_data[8].ecall = res[NETDATA_KEY_ERROR_SYS_CLONE];
+
+ process_aggregated_data[2].bytes = (uint64_t)res[NETDATA_KEY_BYTES_VFS_WRITE] +
+ (uint64_t)res[NETDATA_KEY_BYTES_VFS_WRITEV];
+ process_aggregated_data[3].bytes = (uint64_t)res[NETDATA_KEY_BYTES_VFS_READ] +
+ (uint64_t)res[NETDATA_KEY_BYTES_VFS_READV];
+}
+
+/**
+ * Read the hash table and store data to allocated vectors.
+ */
+static void ebpf_process_update_apps_data()
+{
+ struct pid_stat *pids = root_of_pids;
+ while (pids) {
+ uint32_t current_pid = pids->pid;
+ ebpf_process_stat_t *ps = global_process_stats[current_pid];
+ if (!ps) {
+ pids = pids->next;
+ continue;
+ }
+
+ ebpf_process_publish_apps_t *cad = current_apps_data[current_pid];
+ ebpf_process_publish_apps_t *pad = prev_apps_data[current_pid];
+ int lstatus;
+ if (!cad) {
+ ebpf_process_publish_apps_t *ptr = callocz(2, sizeof(ebpf_process_publish_apps_t));
+ cad = &ptr[0];
+ current_apps_data[current_pid] = cad;
+ pad = &ptr[1];
+ prev_apps_data[current_pid] = pad;
+ lstatus = 1;
+ } else {
+ memcpy(pad, cad, sizeof(ebpf_process_publish_apps_t));
+ lstatus = 0;
+ }
+
+ //Read data
+ cad->call_sys_open = ps->open_call;
+ cad->call_close_fd = ps->close_call;
+ cad->call_vfs_unlink = ps->unlink_call;
+ cad->call_read = ps->read_call + ps->readv_call;
+ cad->call_write = ps->write_call + ps->writev_call;
+ cad->call_do_exit = ps->exit_call;
+ cad->call_release_task = ps->release_call;
+ cad->call_do_fork = ps->fork_call;
+ cad->call_sys_clone = ps->clone_call;
+
+ cad->ecall_sys_open = ps->open_err;
+ cad->ecall_close_fd = ps->close_err;
+ cad->ecall_vfs_unlink = ps->unlink_err;
+ cad->ecall_read = ps->read_err + ps->readv_err;
+ cad->ecall_write = ps->write_err + ps->writev_err;
+ cad->ecall_do_fork = ps->fork_err;
+ cad->ecall_sys_clone = ps->clone_err;
+
+ cad->bytes_written = (uint64_t)ps->write_bytes + (uint64_t)ps->write_bytes;
+ cad->bytes_read = (uint64_t)ps->read_bytes + (uint64_t)ps->readv_bytes;
+
+ ebpf_process_update_apps_publish(cad, pad, lstatus);
+
+ pids = pids->next;
+ }
+}
+
+/*****************************************************************
+ *
+ * FUNCTIONS TO CREATE CHARTS
+ *
+ *****************************************************************/
+
+/**
+ * Create IO chart
+ *
+ * @param family the chart family
+ * @param name the chart name
+ * @param axis the axis label
+ * @param web the group name used to attach the chart on dashaboard
+ * @param order the order number of the specified chart
+ */
+static void ebpf_create_io_chart(char *family, char *name, char *axis, char *web, int order)
+{
+ printf("CHART %s.%s '' 'Bytes written and read' '%s' '%s' '' line %d %d\n",
+ family,
+ name,
+ axis,
+ web,
+ order,
+ update_every);
+
+ printf("DIMENSION %s %s absolute 1 1\n", process_id_names[3], NETDATA_VFS_DIM_OUT_FILE_BYTES);
+ printf("DIMENSION %s %s absolute 1 1\n", process_id_names[4], NETDATA_VFS_DIM_IN_FILE_BYTES);
+}
+
+/**
+ * Create process status chart
+ *
+ * @param family the chart family
+ * @param name the chart name
+ * @param axis the axis label
+ * @param web the group name used to attach the chart on dashaboard
+ * @param order the order number of the specified chart
+ */
+static void ebpf_process_status_chart(char *family, char *name, char *axis, char *web, int order)
+{
+ printf("CHART %s.%s '' 'Process not closed' '%s' '%s' '' line %d %d ''\n",
+ family,
+ name,
+ axis,
+ web,
+ order,
+ update_every);
+
+ printf("DIMENSION %s '' absolute 1 1\n", status[0]);
+ printf("DIMENSION %s '' absolute 1 1\n", status[1]);
+}
+
+/**
+ * Create global charts
+ *
+ * Call ebpf_create_chart to create the charts for the collector.
+ *
+ * @param em a pointer to the structure with the default values.
+ */
+static void ebpf_create_global_charts(ebpf_module_t *em)
+{
+ ebpf_create_chart(NETDATA_EBPF_FAMILY,
+ NETDATA_FILE_OPEN_CLOSE_COUNT,
+ "Open and close calls",
+ EBPF_COMMON_DIMENSION_CALL,
+ NETDATA_FILE_GROUP,
+ 21000,
+ ebpf_create_global_dimension,
+ process_publish_aggregated,
+ 2);
+
+ if (em->mode < MODE_ENTRY) {
+ ebpf_create_chart(NETDATA_EBPF_FAMILY,
+ NETDATA_FILE_OPEN_ERR_COUNT,
+ "Open fails",
+ EBPF_COMMON_DIMENSION_CALL,
+ NETDATA_FILE_GROUP,
+ 21001,
+ ebpf_create_global_dimension,
+ process_publish_aggregated,
+ 2);
+ }
+
+ ebpf_create_chart(NETDATA_EBPF_FAMILY,
+ NETDATA_VFS_FILE_CLEAN_COUNT,
+ "Remove files",
+ EBPF_COMMON_DIMENSION_CALL,
+ NETDATA_VFS_GROUP,
+ 21002,
+ ebpf_create_global_dimension,
+ &process_publish_aggregated[NETDATA_DEL_START],
+ 1);
+
+ ebpf_create_chart(NETDATA_EBPF_FAMILY,
+ NETDATA_VFS_FILE_IO_COUNT,
+ "Calls to IO",
+ EBPF_COMMON_DIMENSION_CALL,
+ NETDATA_VFS_GROUP,
+ 21003,
+ ebpf_create_global_dimension,
+ &process_publish_aggregated[NETDATA_IN_START_BYTE],
+ 2);
+
+ ebpf_create_io_chart(NETDATA_EBPF_FAMILY,
+ NETDATA_VFS_IO_FILE_BYTES,
+ EBPF_COMMON_DIMENSION_BYTESS,
+ NETDATA_VFS_GROUP,
+ 21004);
+
+ if (em->mode < MODE_ENTRY) {
+ ebpf_create_chart(NETDATA_EBPF_FAMILY,
+ NETDATA_VFS_FILE_ERR_COUNT,
+ "Fails to write or read",
+ EBPF_COMMON_DIMENSION_CALL,
+ NETDATA_VFS_GROUP,
+ 21005,
+ ebpf_create_global_dimension,
+ &process_publish_aggregated[2],
+ NETDATA_VFS_ERRORS);
+ }
+
+ ebpf_create_chart(NETDATA_EBPF_FAMILY,
+ NETDATA_PROCESS_SYSCALL,
+ "Start process",
+ EBPF_COMMON_DIMENSION_CALL,
+ NETDATA_PROCESS_GROUP,
+ 21006,
+ ebpf_create_global_dimension,
+ &process_publish_aggregated[NETDATA_PROCESS_START],
+ 2);
+
+ ebpf_create_chart(NETDATA_EBPF_FAMILY,
+ NETDATA_EXIT_SYSCALL,
+ "Exit process",
+ EBPF_COMMON_DIMENSION_CALL,
+ NETDATA_PROCESS_GROUP,
+ 21007,
+ ebpf_create_global_dimension,
+ &process_publish_aggregated[NETDATA_EXIT_START],
+ 2);
+
+ ebpf_process_status_chart(NETDATA_EBPF_FAMILY,
+ NETDATA_PROCESS_STATUS_NAME,
+ EBPF_COMMON_DIMENSION_DIFFERENCE,
+ NETDATA_PROCESS_GROUP,
+ 21008);
+
+ if (em->mode < MODE_ENTRY) {
+ ebpf_create_chart(NETDATA_EBPF_FAMILY,
+ NETDATA_PROCESS_ERROR_NAME,
+ "Fails to create process",
+ EBPF_COMMON_DIMENSION_CALL,
+ NETDATA_PROCESS_GROUP,
+ 21009,
+ ebpf_create_global_dimension,
+ &process_publish_aggregated[NETDATA_PROCESS_START],
+ 2);
+ }
+}
+
+/**
+ * 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 root a pointer for the targets.
+ */
+static void ebpf_process_create_apps_charts(ebpf_module_t *em, struct target *root)
+{
+ ebpf_create_charts_on_apps(NETDATA_SYSCALL_APPS_FILE_OPEN,
+ "Number of open files",
+ EBPF_COMMON_DIMENSION_CALL,
+ NETDATA_APPS_FILE_GROUP,
+ 20061,
+ root);
+
+ if (em->mode < MODE_ENTRY) {
+ ebpf_create_charts_on_apps(NETDATA_SYSCALL_APPS_FILE_OPEN_ERROR,
+ "Fails to open files",
+ EBPF_COMMON_DIMENSION_CALL,
+ NETDATA_APPS_FILE_GROUP,
+ 20062,
+ root);
+ }
+
+ ebpf_create_charts_on_apps(NETDATA_SYSCALL_APPS_FILE_CLOSED,
+ "Files closed",
+ EBPF_COMMON_DIMENSION_CALL,
+ NETDATA_APPS_FILE_GROUP,
+ 20063,
+ root);
+
+ if (em->mode < MODE_ENTRY) {
+ ebpf_create_charts_on_apps(NETDATA_SYSCALL_APPS_FILE_CLOSE_ERROR,
+ "Fails to close files",
+ EBPF_COMMON_DIMENSION_CALL,
+ NETDATA_APPS_FILE_GROUP,
+ 20064,
+ root);
+ }
+
+ ebpf_create_charts_on_apps(NETDATA_SYSCALL_APPS_FILE_DELETED,
+ "Files deleted",
+ EBPF_COMMON_DIMENSION_CALL,
+ NETDATA_APPS_VFS_GROUP,
+ 20065,
+ root);
+
+ ebpf_create_charts_on_apps(NETDATA_SYSCALL_APPS_VFS_WRITE_CALLS,
+ "Write to disk",
+ EBPF_COMMON_DIMENSION_CALL,
+ NETDATA_APPS_VFS_GROUP,
+ 20066,
+ apps_groups_root_target);
+
+ if (em->mode < MODE_ENTRY) {
+ ebpf_create_charts_on_apps(NETDATA_SYSCALL_APPS_VFS_WRITE_CALLS_ERROR,
+ "Fails to write",
+ EBPF_COMMON_DIMENSION_CALL,
+ NETDATA_APPS_VFS_GROUP,
+ 20067,
+ root);
+ }
+
+ ebpf_create_charts_on_apps(NETDATA_SYSCALL_APPS_VFS_READ_CALLS,
+ "Read from disk",
+ EBPF_COMMON_DIMENSION_CALL,
+ NETDATA_APPS_VFS_GROUP,
+ 20068,
+ root);
+
+ if (em->mode < MODE_ENTRY) {
+ ebpf_create_charts_on_apps(NETDATA_SYSCALL_APPS_VFS_READ_CALLS_ERROR,
+ "Fails to read",
+ EBPF_COMMON_DIMENSION_CALL,
+ NETDATA_APPS_VFS_GROUP,
+ 20069,
+ root);
+ }
+
+ ebpf_create_charts_on_apps(NETDATA_SYSCALL_APPS_VFS_WRITE_BYTES,
+ "Bytes written on disk",
+ EBPF_COMMON_DIMENSION_BYTESS,
+ NETDATA_APPS_VFS_GROUP,
+ 20070,
+ root);
+
+ ebpf_create_charts_on_apps(NETDATA_SYSCALL_APPS_VFS_READ_BYTES,
+ "Bytes read from disk",
+ EBPF_COMMON_DIMENSION_BYTESS,
+ NETDATA_APPS_VFS_GROUP,
+ 20071,
+ root);
+
+ ebpf_create_charts_on_apps(NETDATA_SYSCALL_APPS_TASK_PROCESS,
+ "Process started",
+ EBPF_COMMON_DIMENSION_CALL,
+ NETDATA_APPS_PROCESS_GROUP,
+ 20072,
+ root);
+
+ ebpf_create_charts_on_apps(NETDATA_SYSCALL_APPS_TASK_THREAD,
+ "Threads started",
+ EBPF_COMMON_DIMENSION_CALL,
+ NETDATA_APPS_PROCESS_GROUP,
+ 20073,
+ root);
+
+ ebpf_create_charts_on_apps(NETDATA_SYSCALL_APPS_TASK_CLOSE,
+ "Tasks closed",
+ EBPF_COMMON_DIMENSION_CALL,
+ NETDATA_APPS_PROCESS_GROUP,
+ 20074,
+ root);
+}
+
+/**
+ * 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 root a pointer for the targets.
+ */
+static void ebpf_create_apps_charts(ebpf_module_t *em, struct target *root)
+{
+ struct 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 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);
+ }
+ }
+
+ if (!newly_added)
+ return;
+
+ if (ebpf_modules[EBPF_MODULE_PROCESS_IDX].apps_charts)
+ ebpf_process_create_apps_charts(em, root);
+
+ if (ebpf_modules[EBPF_MODULE_SOCKET_IDX].apps_charts)
+ ebpf_socket_create_apps_charts(NULL, root);
+}
+
+/*****************************************************************
+ *
+ * FUNCTIONS WITH THE MAIN LOOP
+ *
+ *****************************************************************/
+
+/**
+ * Main loop for this collector.
+ *
+ * @param step the number of microseconds used with heart beat
+ * @param em the structure with thread information
+ */
+static void process_collector(usec_t step, ebpf_module_t *em)
+{
+ heartbeat_t hb;
+ heartbeat_init(&hb);
+ int publish_global = em->global_charts;
+ int apps_enabled = em->apps_charts;
+ int pid_fd = map_fd[0];
+ while (!close_ebpf_plugin) {
+ usec_t dt = heartbeat_next(&hb, step);
+ (void)dt;
+
+ read_hash_global_tables();
+
+ pthread_mutex_lock(&collect_data_mutex);
+ cleanup_exited_pids();
+ collect_data_for_all_processes(pid_fd);
+
+ ebpf_create_apps_charts(em, apps_groups_root_target);
+
+ pthread_cond_broadcast(&collect_data_cond_var);
+ pthread_mutex_unlock(&collect_data_mutex);
+
+ int publish_apps = 0;
+ if (apps_enabled && all_pids_count > 0) {
+ publish_apps = 1;
+ ebpf_process_update_apps_data();
+ }
+
+ pthread_mutex_lock(&lock);
+ if (publish_global) {
+ ebpf_process_send_data(em);
+ }
+
+ if (publish_apps) {
+ ebpf_process_send_apps_data(em, apps_groups_root_target);
+ }
+ pthread_mutex_unlock(&lock);
+
+ fflush(stdout);
+ }
+}
+
+/*****************************************************************
+ *
+ * FUNCTIONS TO CLOSE THE THREAD
+ *
+ *****************************************************************/
+
+void clean_global_memory() {
+ int pid_fd = map_fd[0];
+ struct pid_stat *pids = root_of_pids;
+ while (pids) {
+ uint32_t pid = pids->pid;
+ freez(global_process_stats[pid]);
+
+ bpf_map_delete_elem(pid_fd, &pid);
+ freez(current_apps_data[pid]);
+
+ pids = pids->next;
+ }
+}
+
+void clean_pid_on_target(struct pid_on_target *ptr) {
+ while (ptr) {
+ struct pid_on_target *next = ptr->next;
+ freez(ptr);
+
+ ptr = next;
+ }
+}
+
+void clean_apps_structures(struct target *ptr) {
+ struct target *agdt = ptr;
+ while (agdt) {
+ struct target *next = agdt->next;
+ clean_pid_on_target(agdt->root_pid);
+ freez(agdt);
+
+ agdt = next;
+ }
+}
+
+/**
+ * Clean up the main thread.
+ *
+ * @param ptr thread data.
+ */
+static void ebpf_process_cleanup(void *ptr)
+{
+ UNUSED(ptr);
+
+ heartbeat_t hb;
+ heartbeat_init(&hb);
+ uint32_t tick = 200*USEC_PER_MS;
+ while (!finalized_threads) {
+ usec_t dt = heartbeat_next(&hb, tick);
+ UNUSED(dt);
+ }
+
+ freez(process_aggregated_data);
+ freez(process_publish_aggregated);
+ freez(process_hash_values);
+
+ clean_global_memory();
+ freez(global_process_stats);
+ freez(current_apps_data);
+ freez(prev_apps_data);
+
+ clean_apps_structures(apps_groups_root_target);
+ freez(process_data.map_fd);
+
+ struct bpf_program *prog;
+ size_t i = 0 ;
+ bpf_object__for_each_program(prog, objects) {
+ bpf_link__destroy(probe_links[i]);
+ i++;
+ }
+ bpf_object__close(objects);
+}
+
+/*****************************************************************
+ *
+ * FUNCTIONS TO START THREAD
+ *
+ *****************************************************************/
+
+/**
+ * Allocate vectors used with this thread.
+ * We are not testing the return, because callocz does this and shutdown the software
+ * case it was not possible to allocate.
+ *
+ * @param length is the length for the vectors used inside the collector.
+ */
+static void ebpf_process_allocate_global_vectors(size_t length)
+{
+ process_aggregated_data = callocz(length, sizeof(netdata_syscall_stat_t));
+ process_publish_aggregated = callocz(length, sizeof(netdata_publish_syscall_t));
+ process_hash_values = callocz(ebpf_nprocs, sizeof(netdata_idx_t));
+
+ global_process_stats = callocz((size_t)pid_max, sizeof(ebpf_process_stat_t *));
+ current_apps_data = callocz((size_t)pid_max, sizeof(ebpf_process_publish_apps_t *));
+ prev_apps_data = callocz((size_t)pid_max, sizeof(ebpf_process_publish_apps_t *));
+}
+
+static void change_syscalls()
+{
+ static char *lfork = { "do_fork" };
+ process_id_names[7] = lfork;
+}
+
+/**
+ * Set local variables
+ *
+ */
+static void set_local_pointers()
+{
+ map_fd = process_data.map_fd;
+
+ if (process_data.isrh >= NETDATA_MINIMUM_RH_VERSION && process_data.isrh < NETDATA_RH_8)
+ change_syscalls();
+}
+
+/*****************************************************************
+ *
+ * EBPF PROCESS THREAD
+ *
+ *****************************************************************/
+
+/**
+ *
+ */
+static void wait_for_all_threads_die()
+{
+ ebpf_modules[EBPF_MODULE_PROCESS_IDX].enabled = 0;
+
+ heartbeat_t hb;
+ heartbeat_init(&hb);
+
+ int max = 10;
+ int i;
+ for (i = 0; i < max; i++) {
+ heartbeat_next(&hb, 200000);
+
+ size_t j, counter = 0, compare = 0;
+ for (j = 0; ebpf_modules[j].thread_name; j++) {
+ if (!ebpf_modules[j].enabled)
+ counter++;
+
+ compare++;
+ }
+
+ if (counter == compare)
+ break;
+ }
+}
+
+/**
+ * Process thread
+ *
+ * Thread used to generate process charts.
+ *
+ * @param ptr a pointer to `struct ebpf_module`
+ *
+ * @return It always return NULL
+ */
+void *ebpf_process_thread(void *ptr)
+{
+ netdata_thread_cleanup_push(ebpf_process_cleanup, ptr);
+
+ ebpf_module_t *em = (ebpf_module_t *)ptr;
+ process_enabled = em->enabled;
+ fill_ebpf_data(&process_data);
+
+ pthread_mutex_lock(&lock);
+ ebpf_process_allocate_global_vectors(NETDATA_MAX_MONITOR_VECTOR);
+
+ if (ebpf_update_kernel(&process_data)) {
+ pthread_mutex_unlock(&lock);
+ goto endprocess;
+ }
+
+ set_local_pointers();
+ probe_links = ebpf_load_program(ebpf_plugin_dir, em, kernel_string, &objects, process_data.map_fd);
+ if (!probe_links) {
+ pthread_mutex_unlock(&lock);
+ goto endprocess;
+ }
+
+ ebpf_global_labels(
+ process_aggregated_data, process_publish_aggregated, process_dimension_names, process_id_names,
+ NETDATA_MAX_MONITOR_VECTOR);
+
+ if (process_enabled) {
+ ebpf_create_global_charts(em);
+ }
+
+ pthread_mutex_unlock(&lock);
+
+ process_collector((usec_t)(em->update_time * USEC_PER_SEC), em);
+
+endprocess:
+ wait_for_all_threads_die();
+ netdata_thread_cleanup_pop(1);
+ return NULL;
+}
diff --git a/collectors/ebpf.plugin/ebpf_process.h b/collectors/ebpf.plugin/ebpf_process.h
new file mode 100644
index 00000000..9553434b
--- /dev/null
+++ b/collectors/ebpf.plugin/ebpf_process.h
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_EBPF_PROCESS_H
+#define NETDATA_EBPF_PROCESS_H 1
+
+// Groups used on Dashboard
+#define NETDATA_FILE_GROUP "File"
+#define NETDATA_VFS_GROUP "VFS"
+#define NETDATA_PROCESS_GROUP "Process"
+
+// Internal constants
+#define NETDATA_GLOBAL_VECTOR 24
+#define NETDATA_MAX_MONITOR_VECTOR 9
+#define NETDATA_VFS_ERRORS 3
+
+// Map index
+#define NETDATA_DEL_START 2
+#define NETDATA_IN_START_BYTE 3
+#define NETDATA_EXIT_START 5
+#define NETDATA_PROCESS_START 7
+
+// Global chart name
+#define NETDATA_FILE_OPEN_CLOSE_COUNT "file_descriptor"
+#define NETDATA_FILE_OPEN_ERR_COUNT "file_error"
+#define NETDATA_VFS_FILE_CLEAN_COUNT "deleted_objects"
+#define NETDATA_VFS_FILE_IO_COUNT "io"
+#define NETDATA_VFS_FILE_ERR_COUNT "io_error"
+
+#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"
+
+#define NETDATA_VFS_IO_FILE_BYTES "io_bytes"
+#define NETDATA_VFS_DIM_IN_FILE_BYTES "write"
+#define NETDATA_VFS_DIM_OUT_FILE_BYTES "read"
+
+// 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_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_TASK_PROCESS "process_create"
+#define NETDATA_SYSCALL_APPS_TASK_THREAD "thread_create"
+#define NETDATA_SYSCALL_APPS_TASK_CLOSE "task_close"
+
+// Charts created on Apps submenu, if and only if, the return mode is active
+
+#define NETDATA_SYSCALL_APPS_FILE_OPEN_ERROR "file_open_error"
+#define NETDATA_SYSCALL_APPS_FILE_CLOSE_ERROR "file_close_error"
+#define NETDATA_SYSCALL_APPS_VFS_WRITE_CALLS_ERROR "vfs_write_error"
+#define NETDATA_SYSCALL_APPS_VFS_READ_CALLS_ERROR "vfs_read_error"
+
+// Index from kernel
+typedef enum ebpf_process_index {
+ NETDATA_KEY_CALLS_DO_SYS_OPEN,
+ NETDATA_KEY_ERROR_DO_SYS_OPEN,
+
+ NETDATA_KEY_CALLS_VFS_WRITE,
+ NETDATA_KEY_ERROR_VFS_WRITE,
+ NETDATA_KEY_BYTES_VFS_WRITE,
+
+ NETDATA_KEY_CALLS_VFS_READ,
+ NETDATA_KEY_ERROR_VFS_READ,
+ NETDATA_KEY_BYTES_VFS_READ,
+
+ NETDATA_KEY_CALLS_VFS_UNLINK,
+ NETDATA_KEY_ERROR_VFS_UNLINK,
+
+ NETDATA_KEY_CALLS_DO_EXIT,
+
+ NETDATA_KEY_CALLS_RELEASE_TASK,
+
+ NETDATA_KEY_CALLS_DO_FORK,
+ NETDATA_KEY_ERROR_DO_FORK,
+
+ NETDATA_KEY_CALLS_CLOSE_FD,
+ NETDATA_KEY_ERROR_CLOSE_FD,
+
+ NETDATA_KEY_CALLS_SYS_CLONE,
+ NETDATA_KEY_ERROR_SYS_CLONE,
+
+ NETDATA_KEY_CALLS_VFS_WRITEV,
+ NETDATA_KEY_ERROR_VFS_WRITEV,
+ NETDATA_KEY_BYTES_VFS_WRITEV,
+
+ NETDATA_KEY_CALLS_VFS_READV,
+ NETDATA_KEY_ERROR_VFS_READV,
+ NETDATA_KEY_BYTES_VFS_READV
+
+} ebpf_process_index_t;
+
+typedef struct ebpf_process_publish_apps {
+ // Number of calls during the last read
+ uint64_t call_sys_open;
+ uint64_t call_close_fd;
+ uint64_t call_vfs_unlink;
+ uint64_t call_read;
+ uint64_t call_write;
+ uint64_t call_do_exit;
+ uint64_t call_release_task;
+ uint64_t call_do_fork;
+ uint64_t call_sys_clone;
+
+ // Number of errors during the last read
+ uint64_t ecall_sys_open;
+ uint64_t ecall_close_fd;
+ uint64_t ecall_vfs_unlink;
+ uint64_t ecall_read;
+ uint64_t ecall_write;
+ uint64_t ecall_do_fork;
+ uint64_t ecall_sys_clone;
+
+ // Number of bytes during the last read
+ uint64_t bytes_written;
+ uint64_t bytes_read;
+
+ // Dimensions sent to chart
+ uint64_t publish_open;
+ uint64_t publish_closed;
+ uint64_t publish_deleted;
+ uint64_t publish_write_call;
+ uint64_t publish_write_bytes;
+ uint64_t publish_read_call;
+ uint64_t publish_read_bytes;
+ uint64_t publish_process;
+ uint64_t publish_thread;
+ uint64_t publish_task;
+ uint64_t publish_open_error;
+ uint64_t publish_close_error;
+ uint64_t publish_write_error;
+ uint64_t publish_read_error;
+} ebpf_process_publish_apps_t;
+
+#endif /* NETDATA_EBPF_PROCESS_H */
diff --git a/collectors/ebpf.plugin/ebpf_socket.c b/collectors/ebpf.plugin/ebpf_socket.c
new file mode 100644
index 00000000..2f73cf4d
--- /dev/null
+++ b/collectors/ebpf.plugin/ebpf_socket.c
@@ -0,0 +1,1938 @@
+// 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] = { "sent", "received", "close", "sent",
+ "received", "retransmitted" };
+static char *socket_id_names[NETDATA_MAX_SOCKET_VECTOR] = { "tcp_sendmsg", "tcp_cleanup_rbuf", "tcp_close",
+ "udp_sendmsg", "udp_recvmsg", "tcp_retransmit_skb" };
+
+static netdata_idx_t *socket_hash_values = NULL;
+static netdata_syscall_stat_t *socket_aggregated_data = NULL;
+static netdata_publish_syscall_t *socket_publish_aggregated = NULL;
+
+static ebpf_data_t socket_data;
+
+ebpf_socket_publish_apps_t **socket_bandwidth_curr = NULL;
+ebpf_socket_publish_apps_t **socket_bandwidth_prev = NULL;
+static ebpf_bandwidth_t *bandwidth_vector = NULL;
+
+static int socket_apps_created = 0;
+pthread_mutex_t nv_mutex;
+int wait_to_plot = 0;
+int read_thread_closed = 1;
+
+netdata_vector_plot_t inbound_vectors = { .plot = NULL, .next = 0, .last = 0 };
+netdata_vector_plot_t outbound_vectors = { .plot = NULL, .next = 0, .last = 0 };
+netdata_socket_t *socket_values;
+
+ebpf_network_viewer_port_list_t *listen_ports = NULL;
+
+static int *map_fd = NULL;
+static struct bpf_object *objects = NULL;
+static struct bpf_link **probe_links = NULL;
+
+/*****************************************************************
+ *
+ * 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;
+}
+
+/**
+ * Update Network Viewer plot data
+ *
+ * @param plot the structure where the data will be stored
+ * @param sock the last update from the socket
+ */
+static inline void update_nv_plot_data(netdata_plot_values_t *plot, netdata_socket_t *sock)
+{
+ if (sock->ct > plot->last_time) {
+ plot->last_time = sock->ct;
+ plot->plot_recv_packets = sock->recv_packets;
+ plot->plot_sent_packets = sock->sent_packets;
+ plot->plot_recv_bytes = sock->recv_bytes;
+ plot->plot_sent_bytes = sock->sent_bytes;
+ plot->plot_retransmit = sock->retransmit;
+ }
+
+ sock->recv_packets = 0;
+ sock->sent_packets = 0;
+ sock->recv_bytes = 0;
+ sock->sent_bytes = 0;
+ sock->retransmit = 0;
+}
+
+/**
+ * Calculate Network Viewer Plot
+ *
+ * Do math with collected values before to plot data.
+ */
+static inline void calculate_nv_plot()
+{
+ uint32_t i;
+ uint32_t end = inbound_vectors.next;
+ for (i = 0; i < end; i++) {
+ update_nv_plot_data(&inbound_vectors.plot[i].plot, &inbound_vectors.plot[i].sock);
+ }
+ inbound_vectors.max_plot = end;
+
+ // The 'Other' dimension is always calculated for the chart to have at least one dimension
+ update_nv_plot_data(&inbound_vectors.plot[inbound_vectors.last].plot,
+ &inbound_vectors.plot[inbound_vectors.last].sock);
+
+ end = outbound_vectors.next;
+ for (i = 0; i < end; i++) {
+ update_nv_plot_data(&outbound_vectors.plot[i].plot, &outbound_vectors.plot[i].sock);
+ }
+ outbound_vectors.max_plot = end;
+
+ // The 'Other' dimension is always calculated for the chart to have at least one dimension
+ update_nv_plot_data(&outbound_vectors.plot[outbound_vectors.last].plot,
+ &outbound_vectors.plot[outbound_vectors.last].sock);
+}
+
+/**
+ * Network viewer send bytes
+ *
+ * @param ptr the structure with values to plot
+ * @param chart the chart name.
+ */
+static inline void ebpf_socket_nv_send_bytes(netdata_vector_plot_t *ptr, char *chart)
+{
+ uint32_t i;
+ uint32_t end = ptr->last_plot;
+ netdata_socket_plot_t *w = ptr->plot;
+ collected_number value;
+
+ write_begin_chart(NETDATA_EBPF_FAMILY, chart);
+ for (i = 0; i < end; i++) {
+ value = ((collected_number) w[i].plot.plot_sent_bytes);
+ write_chart_dimension(w[i].dimension_sent, value);
+ value = (collected_number) w[i].plot.plot_recv_bytes;
+ write_chart_dimension(w[i].dimension_recv, value);
+ }
+
+ i = ptr->last;
+ value = ((collected_number) w[i].plot.plot_sent_bytes);
+ write_chart_dimension(w[i].dimension_sent, value);
+ value = (collected_number) w[i].plot.plot_recv_bytes;
+ write_chart_dimension(w[i].dimension_recv, value);
+ write_end_chart();
+}
+
+/**
+ * Network Viewer Send packets
+ *
+ * @param ptr the structure with values to plot
+ * @param chart the chart name.
+ */
+static inline void ebpf_socket_nv_send_packets(netdata_vector_plot_t *ptr, char *chart)
+{
+ uint32_t i;
+ uint32_t end = ptr->last_plot;
+ netdata_socket_plot_t *w = ptr->plot;
+ collected_number value;
+
+ write_begin_chart(NETDATA_EBPF_FAMILY, chart);
+ for (i = 0; i < end; i++) {
+ value = ((collected_number)w[i].plot.plot_sent_packets);
+ write_chart_dimension(w[i].dimension_sent, value);
+ value = (collected_number) w[i].plot.plot_recv_packets;
+ write_chart_dimension(w[i].dimension_recv, value);
+ }
+
+ i = ptr->last;
+ value = ((collected_number)w[i].plot.plot_sent_packets);
+ write_chart_dimension(w[i].dimension_sent, value);
+ value = (collected_number)w[i].plot.plot_recv_packets;
+ write_chart_dimension(w[i].dimension_recv, value);
+ write_end_chart();
+}
+
+/**
+ * Network Viewer Send Retransmit
+ *
+ * @param ptr the structure with values to plot
+ * @param chart the chart name.
+ */
+static inline void ebpf_socket_nv_send_retransmit(netdata_vector_plot_t *ptr, char *chart)
+{
+ uint32_t i;
+ uint32_t end = ptr->last_plot;
+ netdata_socket_plot_t *w = ptr->plot;
+ collected_number value;
+
+ write_begin_chart(NETDATA_EBPF_FAMILY, chart);
+ for (i = 0; i < end; i++) {
+ value = (collected_number) w[i].plot.plot_retransmit;
+ write_chart_dimension(w[i].dimension_retransmit, value);
+ }
+
+ i = ptr->last;
+ value = (collected_number)w[i].plot.plot_retransmit;
+ write_chart_dimension(w[i].dimension_retransmit, value);
+ write_end_chart();
+}
+
+/**
+ * Send network viewer data
+ *
+ * @param ptr the pointer to plot data
+ */
+static void ebpf_socket_send_nv_data(netdata_vector_plot_t *ptr)
+{
+ if (!ptr->flags)
+ return;
+
+ if (ptr == (netdata_vector_plot_t *)&outbound_vectors) {
+ ebpf_socket_nv_send_bytes(ptr, NETDATA_NV_OUTBOUND_BYTES);
+ fflush(stdout);
+
+ ebpf_socket_nv_send_packets(ptr, NETDATA_NV_OUTBOUND_PACKETS);
+ fflush(stdout);
+
+ ebpf_socket_nv_send_retransmit(ptr, NETDATA_NV_OUTBOUND_RETRANSMIT);
+ fflush(stdout);
+ } else {
+ ebpf_socket_nv_send_bytes(ptr, NETDATA_NV_INBOUND_BYTES);
+ fflush(stdout);
+
+ ebpf_socket_nv_send_packets(ptr, NETDATA_NV_INBOUND_PACKETS);
+ fflush(stdout);
+ }
+}
+
+
+/**
+ * Update the publish strctures to create the dimenssions
+ *
+ * @param curr Last values read from memory.
+ * @param prev Previous values read from memory.
+ */
+static void ebpf_socket_update_apps_publish(ebpf_socket_publish_apps_t *curr, ebpf_socket_publish_apps_t *prev)
+{
+ curr->publish_received_bytes = curr->bytes_received - prev->bytes_received;
+ curr->publish_sent_bytes = curr->bytes_sent - prev->bytes_sent;
+ curr->publish_tcp_sent = curr->call_tcp_sent - prev->call_tcp_sent;
+ curr->publish_tcp_received = curr->call_tcp_received - prev->call_tcp_received;
+ curr->publish_retransmit = curr->retransmit - prev->retransmit;
+ curr->publish_udp_sent = curr->call_udp_sent - prev->call_udp_sent;
+ curr->publish_udp_received = curr->call_udp_received - prev->call_udp_received;
+}
+
+/**
+ * Send data to Netdata calling auxiliar 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);
+
+ write_count_chart(
+ NETDATA_TCP_FUNCTION_COUNT, NETDATA_EBPF_FAMILY, socket_publish_aggregated, 3);
+ write_io_chart(
+ NETDATA_TCP_FUNCTION_BYTES, NETDATA_EBPF_FAMILY, socket_id_names[0], socket_id_names[1], &common_tcp);
+ if (em->mode < MODE_ENTRY) {
+ write_err_chart(
+ NETDATA_TCP_FUNCTION_ERROR, NETDATA_EBPF_FAMILY, socket_publish_aggregated, 2);
+ }
+ write_count_chart(
+ NETDATA_TCP_RETRANSMIT, NETDATA_EBPF_FAMILY, &socket_publish_aggregated[NETDATA_RETRANSMIT_START], 1);
+
+ write_count_chart(
+ NETDATA_UDP_FUNCTION_COUNT, NETDATA_EBPF_FAMILY, &socket_publish_aggregated[NETDATA_UDP_START], 2);
+ write_io_chart(
+ NETDATA_UDP_FUNCTION_BYTES, NETDATA_EBPF_FAMILY, socket_id_names[3], socket_id_names[4], &common_udp);
+ if (em->mode < MODE_ENTRY) {
+ write_err_chart(
+ NETDATA_UDP_FUNCTION_ERROR, NETDATA_EBPF_FAMILY, &socket_publish_aggregated[NETDATA_UDP_START], 2);
+ }
+}
+
+/**
+ * Sum values for pid
+ *
+ * @param root the structure with all available PIDs
+ *
+ * @param offset the address that we are reading
+ *
+ * @return it returns the sum of all PIDs
+ */
+long long ebpf_socket_sum_values_for_pids(struct pid_on_target *root, size_t offset)
+{
+ long long ret = 0;
+ while (root) {
+ int32_t pid = root->pid;
+ ebpf_socket_publish_apps_t *w = socket_bandwidth_curr[pid];
+ if (w) {
+ ret += get_value_from_structure((char *)w, offset);
+ }
+
+ root = root->next;
+ }
+
+ return ret;
+}
+
+/**
+ * Send data to Netdata calling auxiliar functions.
+ *
+ * @param em the structure with thread information
+ * @param root the target list.
+ */
+void ebpf_socket_send_apps_data(ebpf_module_t *em, struct target *root)
+{
+ UNUSED(em);
+ if (!socket_apps_created)
+ return;
+
+ struct target *w;
+ collected_number value;
+
+ write_begin_chart(NETDATA_APPS_FAMILY, NETDATA_NET_APPS_BANDWIDTH_SENT);
+ for (w = root; w; w = w->next) {
+ if (unlikely(w->exposed && w->processes)) {
+ value = ebpf_socket_sum_values_for_pids(w->root_pid, offsetof(ebpf_socket_publish_apps_t,
+ publish_sent_bytes));
+ write_chart_dimension(w->name, value);
+ }
+ }
+ write_end_chart();
+
+ write_begin_chart(NETDATA_APPS_FAMILY, NETDATA_NET_APPS_BANDWIDTH_RECV);
+ for (w = root; w; w = w->next) {
+ if (unlikely(w->exposed && w->processes)) {
+ value = ebpf_socket_sum_values_for_pids(w->root_pid, offsetof(ebpf_socket_publish_apps_t,
+ publish_received_bytes));
+ write_chart_dimension(w->name, value);
+ }
+ }
+ write_end_chart();
+
+ write_begin_chart(NETDATA_APPS_FAMILY, NETDATA_NET_APPS_BANDWIDTH_TCP_SEND_CALLS);
+ for (w = root; w; w = w->next) {
+ if (unlikely(w->exposed && w->processes)) {
+ value = ebpf_socket_sum_values_for_pids(w->root_pid, offsetof(ebpf_socket_publish_apps_t,
+ publish_tcp_sent));
+ write_chart_dimension(w->name, value);
+ }
+ }
+ write_end_chart();
+
+ write_begin_chart(NETDATA_APPS_FAMILY, NETDATA_NET_APPS_BANDWIDTH_TCP_RECV_CALLS);
+ for (w = root; w; w = w->next) {
+ if (unlikely(w->exposed && w->processes)) {
+ value = ebpf_socket_sum_values_for_pids(w->root_pid, offsetof(ebpf_socket_publish_apps_t,
+ publish_tcp_received));
+ write_chart_dimension(w->name, value);
+ }
+ }
+ write_end_chart();
+
+ write_begin_chart(NETDATA_APPS_FAMILY, NETDATA_NET_APPS_BANDWIDTH_TCP_RETRANSMIT);
+ for (w = root; w; w = w->next) {
+ if (unlikely(w->exposed && w->processes)) {
+ value = ebpf_socket_sum_values_for_pids(w->root_pid, offsetof(ebpf_socket_publish_apps_t,
+ publish_retransmit));
+ write_chart_dimension(w->name, value);
+ }
+ }
+ write_end_chart();
+
+ write_begin_chart(NETDATA_APPS_FAMILY, NETDATA_NET_APPS_BANDWIDTH_UDP_SEND_CALLS);
+ for (w = root; w; w = w->next) {
+ if (unlikely(w->exposed && w->processes)) {
+ value = ebpf_socket_sum_values_for_pids(w->root_pid, offsetof(ebpf_socket_publish_apps_t,
+ publish_udp_sent));
+ write_chart_dimension(w->name, value);
+ }
+ }
+ write_end_chart();
+
+ write_begin_chart(NETDATA_APPS_FAMILY, NETDATA_NET_APPS_BANDWIDTH_UDP_RECV_CALLS);
+ for (w = root; w; w = w->next) {
+ if (unlikely(w->exposed && w->processes)) {
+ value = ebpf_socket_sum_values_for_pids(w->root_pid, offsetof(ebpf_socket_publish_apps_t,
+ publish_udp_received));
+ write_chart_dimension(w->name, value);
+ }
+ }
+ write_end_chart();
+
+}
+
+/*****************************************************************
+ *
+ * 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_create_global_charts(ebpf_module_t *em)
+{
+ ebpf_create_chart(NETDATA_EBPF_FAMILY,
+ NETDATA_TCP_FUNCTION_COUNT,
+ "Calls to internal functions",
+ EBPF_COMMON_DIMENSION_CALL,
+ NETDATA_SOCKET_GROUP,
+ 21070,
+ ebpf_create_global_dimension,
+ socket_publish_aggregated,
+ 3);
+
+ ebpf_create_chart(NETDATA_EBPF_FAMILY,
+ NETDATA_TCP_FUNCTION_BYTES,
+ "TCP bandwidth",
+ EBPF_COMMON_DIMENSION_BYTESS,
+ NETDATA_SOCKET_GROUP,
+ 21071,
+ ebpf_create_global_dimension,
+ socket_publish_aggregated,
+ 3);
+
+ if (em->mode < MODE_ENTRY) {
+ ebpf_create_chart(NETDATA_EBPF_FAMILY,
+ NETDATA_TCP_FUNCTION_ERROR,
+ "TCP errors",
+ EBPF_COMMON_DIMENSION_CALL,
+ NETDATA_SOCKET_GROUP,
+ 21072,
+ ebpf_create_global_dimension,
+ socket_publish_aggregated,
+ 2);
+ }
+
+ ebpf_create_chart(NETDATA_EBPF_FAMILY,
+ NETDATA_TCP_RETRANSMIT,
+ "Packages retransmitted",
+ EBPF_COMMON_DIMENSION_CALL,
+ NETDATA_SOCKET_GROUP,
+ 21073,
+ ebpf_create_global_dimension,
+ &socket_publish_aggregated[NETDATA_RETRANSMIT_START],
+ 1);
+
+ ebpf_create_chart(NETDATA_EBPF_FAMILY,
+ NETDATA_UDP_FUNCTION_COUNT,
+ "UDP calls",
+ EBPF_COMMON_DIMENSION_CALL,
+ NETDATA_SOCKET_GROUP,
+ 21074,
+ ebpf_create_global_dimension,
+ &socket_publish_aggregated[NETDATA_UDP_START],
+ 2);
+
+ ebpf_create_chart(NETDATA_EBPF_FAMILY,
+ NETDATA_UDP_FUNCTION_BYTES,
+ "UDP bandwidth",
+ EBPF_COMMON_DIMENSION_BYTESS,
+ NETDATA_SOCKET_GROUP,
+ 21075,
+ ebpf_create_global_dimension,
+ &socket_publish_aggregated[NETDATA_UDP_START],
+ 2);
+
+ if (em->mode < MODE_ENTRY) {
+ ebpf_create_chart(NETDATA_EBPF_FAMILY,
+ NETDATA_UDP_FUNCTION_ERROR,
+ "UDP errors",
+ EBPF_COMMON_DIMENSION_CALL,
+ NETDATA_SOCKET_GROUP,
+ 21076,
+ ebpf_create_global_dimension,
+ &socket_publish_aggregated[NETDATA_UDP_START],
+ 2);
+ }
+}
+
+/**
+ * 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_create_apps_charts(ebpf_module_t *em, struct target *root)
+{
+ UNUSED(em);
+ ebpf_create_charts_on_apps(NETDATA_NET_APPS_BANDWIDTH_SENT,
+ "Bytes sent",
+ EBPF_COMMON_DIMENSION_BYTESS,
+ NETDATA_APPS_NET_GROUP,
+ 20080,
+ root);
+
+ ebpf_create_charts_on_apps(NETDATA_NET_APPS_BANDWIDTH_RECV,
+ "bytes received",
+ EBPF_COMMON_DIMENSION_BYTESS,
+ NETDATA_APPS_NET_GROUP,
+ 20081,
+ root);
+
+ ebpf_create_charts_on_apps(NETDATA_NET_APPS_BANDWIDTH_TCP_SEND_CALLS,
+ "Calls for tcp_sendmsg",
+ EBPF_COMMON_DIMENSION_CALL,
+ NETDATA_APPS_NET_GROUP,
+ 20082,
+ root);
+
+ ebpf_create_charts_on_apps(NETDATA_NET_APPS_BANDWIDTH_TCP_RECV_CALLS,
+ "Calls for tcp_cleanup_rbuf",
+ EBPF_COMMON_DIMENSION_CALL,
+ NETDATA_APPS_NET_GROUP,
+ 20083,
+ root);
+
+ ebpf_create_charts_on_apps(NETDATA_NET_APPS_BANDWIDTH_TCP_RETRANSMIT,
+ "Calls for tcp_retransmit",
+ EBPF_COMMON_DIMENSION_CALL,
+ NETDATA_APPS_NET_GROUP,
+ 20084,
+ root);
+
+ ebpf_create_charts_on_apps(NETDATA_NET_APPS_BANDWIDTH_UDP_SEND_CALLS,
+ "Calls for udp_sendmsg",
+ EBPF_COMMON_DIMENSION_CALL,
+ NETDATA_APPS_NET_GROUP,
+ 20085,
+ root);
+
+ ebpf_create_charts_on_apps(NETDATA_NET_APPS_BANDWIDTH_UDP_RECV_CALLS,
+ "Calls for udp_recvmsg",
+ EBPF_COMMON_DIMENSION_CALL,
+ NETDATA_APPS_NET_GROUP,
+ 20086,
+ root);
+
+ socket_apps_created = 1;
+}
+
+/**
+ * Create network viewer chart
+ *
+ * Create common charts.
+ *
+ * @param id the chart id
+ * @param title the chart title
+ * @param units the units label
+ * @param family the group name used to attach the chart on dashaboard
+ * @param order the chart order
+ * @param ptr the plot structure with values.
+ */
+static void ebpf_socket_create_nv_chart(char *id, char *title, char *units,
+ char *family, int order, netdata_vector_plot_t *ptr)
+{
+ ebpf_write_chart_cmd(NETDATA_EBPF_FAMILY,
+ id,
+ title,
+ units,
+ family,
+ "stacked",
+ order);
+
+ uint32_t i;
+ uint32_t end = ptr->last_plot;
+ netdata_socket_plot_t *w = ptr->plot;
+ for (i = 0; i < end; i++) {
+ fprintf(stdout, "DIMENSION %s '' incremental -1 1\n", w[i].dimension_sent);
+ fprintf(stdout, "DIMENSION %s '' incremental 1 1\n", w[i].dimension_recv);
+ }
+
+ end = ptr->last;
+ fprintf(stdout, "DIMENSION %s '' incremental -1 1\n", w[end].dimension_sent);
+ fprintf(stdout, "DIMENSION %s '' incremental 1 1\n", w[end].dimension_recv);
+}
+
+/**
+ * Create network viewer retransmit
+ *
+ * Create a specific chart.
+ *
+ * @param id the chart id
+ * @param title the chart title
+ * @param units the units label
+ * @param family the group name used to attach the chart on dashaboard
+ * @param order the chart order
+ * @param ptr the plot structure with values.
+ */
+static void ebpf_socket_create_nv_retransmit(char *id, char *title, char *units,
+ char *family, int order, netdata_vector_plot_t *ptr)
+{
+ ebpf_write_chart_cmd(NETDATA_EBPF_FAMILY,
+ id,
+ title,
+ units,
+ family,
+ "stacked",
+ order);
+
+ uint32_t i;
+ uint32_t end = ptr->last_plot;
+ netdata_socket_plot_t *w = ptr->plot;
+ for (i = 0; i < end; i++) {
+ fprintf(stdout, "DIMENSION %s '' incremental 1 1\n", w[i].dimension_retransmit);
+ }
+
+ end = ptr->last;
+ fprintf(stdout, "DIMENSION %s '' incremental 1 1\n", w[end].dimension_retransmit);
+}
+
+/**
+ * Create Network Viewer charts
+ *
+ * Recreate the charts when new sockets are created.
+ *
+ * @param ptr a pointer for inbound or outbound vectors.
+ */
+static void ebpf_socket_create_nv_charts(netdata_vector_plot_t *ptr)
+{
+ // We do not have new sockets, so we do not need move forward
+ if (ptr->max_plot == ptr->last_plot)
+ return;
+
+ ptr->last_plot = ptr->max_plot;
+
+ if (ptr == (netdata_vector_plot_t *)&outbound_vectors) {
+ ebpf_socket_create_nv_chart(NETDATA_NV_OUTBOUND_BYTES,
+ "Outbound connections (bytes).",
+ EBPF_COMMON_DIMENSION_BYTESS,
+ NETDATA_NETWORK_CONNECTIONS_GROUP,
+ 21080,
+ ptr);
+
+ ebpf_socket_create_nv_chart(NETDATA_NV_OUTBOUND_PACKETS,
+ "Outbound connections (packets)",
+ EBPF_COMMON_DIMENSION_PACKETS,
+ NETDATA_NETWORK_CONNECTIONS_GROUP,
+ 21082,
+ ptr);
+
+ ebpf_socket_create_nv_retransmit(NETDATA_NV_OUTBOUND_RETRANSMIT,
+ "Retransmitted packets",
+ EBPF_COMMON_DIMENSION_CALL,
+ NETDATA_NETWORK_CONNECTIONS_GROUP,
+ 21083,
+ ptr);
+ } else {
+ ebpf_socket_create_nv_chart(NETDATA_NV_INBOUND_BYTES,
+ "Inbound connections (bytes)",
+ EBPF_COMMON_DIMENSION_BYTESS,
+ NETDATA_NETWORK_CONNECTIONS_GROUP,
+ 21084,
+ ptr);
+
+ ebpf_socket_create_nv_chart(NETDATA_NV_INBOUND_PACKETS,
+ "Inbound connections (packets)",
+ EBPF_COMMON_DIMENSION_PACKETS,
+ NETDATA_NETWORK_CONNECTIONS_GROUP,
+ 21085,
+ ptr);
+ }
+
+ ptr->flags |= NETWORK_VIEWER_CHARTS_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 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 = ntohl(cmp->addr32[0]);
+ ebpf_network_viewer_ip_list_t *move = network_viewer_opt.excluded_ips;
+ while (move) {
+ if (family == AF_INET) {
+ if (ntohl(move->first.addr32[0]) <= ipv4_test &&
+ ipv4_test <= ntohl(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) {
+ if (ntohl(move->first.addr32[0]) <= ipv4_test &&
+ ntohl(move->last.addr32[0]) >= ipv4_test)
+ return 1;
+ } 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 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 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;
+ cmp = htons(cmp);
+ 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 family the family used to compare IPs (AF_INET and AF_INET6)
+ *
+ * @return It returns 1 if this socket is inside the ranges and 0 otherwise.
+ */
+int is_socket_allowed(netdata_socket_idx_t *key, int family)
+{
+ if (!is_port_inside_range(key->dport))
+ return 0;
+
+ return is_specific_ip_inside_range(&key->daddr, family);
+}
+
+/**
+ * Compare sockets
+ *
+ * Compare destination address and destination port.
+ * We do not compare source port, because it is random.
+ * We also do not compare source address, because inbound and outbound connections are stored in separated AVL trees.
+ *
+ * @param a pointer to netdata_socket_plot
+ * @param b pointer to netdata_socket_plot
+ *
+ * @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 compare_sockets(void *a, void *b)
+{
+ struct netdata_socket_plot *val1 = a;
+ struct netdata_socket_plot *val2 = b;
+ int cmp;
+
+ // We do not need to compare val2 family, because data inside hash table is always from the same family
+ if (val1->family == AF_INET) { //IPV4
+ if (val1->flags & NETDATA_INBOUND_DIRECTION) {
+ if (val1->index.sport == val2->index.sport)
+ cmp = 0;
+ else {
+ cmp = (val1->index.sport > val2->index.sport)?1:-1;
+ }
+ } else {
+ cmp = memcmp(&val1->index.dport, &val2->index.dport, sizeof(uint16_t));
+ if (!cmp) {
+ cmp = memcmp(&val1->index.daddr.addr32[0], &val2->index.daddr.addr32[0], sizeof(uint32_t));
+ }
+ }
+ } else {
+ if (val1->flags & NETDATA_INBOUND_DIRECTION) {
+ if (val1->index.sport == val2->index.sport)
+ cmp = 0;
+ else {
+ cmp = (val1->index.sport > val2->index.sport)?1:-1;
+ }
+ } else {
+ cmp = memcmp(&val1->index.dport, &val2->index.dport, sizeof(uint16_t));
+ if (!cmp) {
+ cmp = memcmp(&val1->index.daddr.addr32, &val2->index.daddr.addr32, 4*sizeof(uint32_t));
+ }
+ }
+ }
+
+ return cmp;
+}
+
+/**
+ * Build dimension name
+ *
+ * Fill dimension name vector with values given
+ *
+ * @param dimname the output vector
+ * @param hostname the hostname for the socket.
+ * @param service_name the service used to connect.
+ * @param proto the protocol used in this connection
+ * @param family is this IPV4(AF_INET) or IPV6(AF_INET6)
+ *
+ * @return it returns the size of the data copied on success and -1 otherwise.
+ */
+static inline int build_outbound_dimension_name(char *dimname, char *hostname, char *service_name,
+ char *proto, int family)
+{
+ return snprintf(dimname, CONFIG_MAX_NAME - 7, (family == AF_INET)?"%s:%s:%s_":"%s:%s:[%s]_",
+ service_name, proto,
+ hostname);
+}
+
+/**
+ * Fill inbound dimension name
+ *
+ * Mount the dimension name with the input given
+ *
+ * @param dimname the output vector
+ * @param service_name the service used to connect.
+ * @param proto the protocol used in this connection
+ *
+ * @return it returns the size of the data copied on success and -1 otherwise.
+ */
+static inline int build_inbound_dimension_name(char *dimname, char *service_name, char *proto)
+{
+ return snprintf(dimname, CONFIG_MAX_NAME - 7, "%s:%s_", service_name,
+ proto);
+}
+
+/**
+ * Fill Resolved Name
+ *
+ * Fill the resolved name structure with the value given.
+ * The hostname is the largest value possible, if it is necessary to cut some value, it must be cut.
+ *
+ * @param ptr the output vector
+ * @param hostname the hostname resolved or IP.
+ * @param length the length for the hostname.
+ * @param service_name the service name associated to the connection
+ * @param is_outbound the is this an outbound connection
+ */
+static inline void fill_resolved_name(netdata_socket_plot_t *ptr, char *hostname, size_t length,
+ char *service_name, int is_outbound)
+{
+ if (length < NETDATA_MAX_NETWORK_COMBINED_LENGTH)
+ ptr->resolved_name = strdupz(hostname);
+ else {
+ length = NETDATA_MAX_NETWORK_COMBINED_LENGTH;
+ ptr->resolved_name = mallocz( NETDATA_MAX_NETWORK_COMBINED_LENGTH + 1);
+ memcpy(ptr->resolved_name, hostname, length);
+ ptr->resolved_name[length] = '\0';
+ }
+
+ char dimname[CONFIG_MAX_NAME];
+ int size;
+ char *protocol;
+ if (ptr->sock.protocol == IPPROTO_UDP) {
+ protocol = "UDP";
+ } else if (ptr->sock.protocol == IPPROTO_TCP) {
+ protocol = "TCP";
+ } else {
+ protocol = "ALL";
+ }
+
+ if (is_outbound)
+ size = build_outbound_dimension_name(dimname, hostname, service_name, protocol, ptr->family);
+ else
+ size = build_inbound_dimension_name(dimname,service_name, protocol);
+
+ if (size > 0) {
+ strcpy(&dimname[size], "sent");
+ dimname[size + 4] = '\0';
+ ptr->dimension_sent = strdupz(dimname);
+
+ strcpy(&dimname[size], "recv");
+ ptr->dimension_recv = strdupz(dimname);
+
+ dimname[size - 1] = '\0';
+ ptr->dimension_retransmit = strdupz(dimname);
+ }
+}
+
+/**
+ * Mount dimension names
+ *
+ * Fill the vector names after to resolve the addresses
+ *
+ * @param ptr a pointer to the structure where the values are stored.
+ * @param is_outbound is a outbound ptr value?
+ *
+ * @return It returns 1 if the name is valid and 0 otherwise.
+ */
+int fill_names(netdata_socket_plot_t *ptr, int is_outbound)
+{
+ char hostname[NI_MAXHOST], service_name[NI_MAXSERV];
+ if (ptr->resolved)
+ return 1;
+
+ int ret;
+ static int resolve_name = -1;
+ static int resolve_service = -1;
+ if (resolve_name == -1)
+ resolve_name = network_viewer_opt.hostname_resolution_enabled;
+
+ if (resolve_service == -1)
+ resolve_service = network_viewer_opt.service_resolution_enabled;
+
+ netdata_socket_idx_t *idx = &ptr->index;
+
+ char *errname = { "Not resolved" };
+ // Resolve Name
+ if (ptr->family == AF_INET) { //IPV4
+ struct sockaddr_in myaddr;
+ memset(&myaddr, 0 , sizeof(myaddr));
+
+ myaddr.sin_family = ptr->family;
+ if (is_outbound) {
+ myaddr.sin_port = idx->dport;
+ myaddr.sin_addr.s_addr = idx->daddr.addr32[0];
+ } else {
+ myaddr.sin_port = idx->sport;
+ myaddr.sin_addr.s_addr = idx->saddr.addr32[0];
+ }
+
+ ret = (!resolve_name)?-1:getnameinfo((struct sockaddr *)&myaddr, sizeof(myaddr), hostname,
+ sizeof(hostname), service_name, sizeof(service_name), NI_NAMEREQD);
+
+ if (!ret && !resolve_service) {
+ snprintf(service_name, sizeof(service_name), "%u", ntohs(myaddr.sin_port));
+ }
+
+ if (ret) {
+ // I cannot resolve the name, I will use the IP
+ if (!inet_ntop(AF_INET, &myaddr.sin_addr.s_addr, hostname, NI_MAXHOST)) {
+ strncpy(hostname, errname, 13);
+ }
+
+ snprintf(service_name, sizeof(service_name), "%u", ntohs(myaddr.sin_port));
+ ret = 1;
+ }
+ } else { // IPV6
+ struct sockaddr_in6 myaddr6;
+ memset(&myaddr6, 0 , sizeof(myaddr6));
+
+ myaddr6.sin6_family = AF_INET6;
+ if (is_outbound) {
+ myaddr6.sin6_port = idx->dport;
+ memcpy(myaddr6.sin6_addr.s6_addr, idx->daddr.addr8, sizeof(union netdata_ip_t));
+ } else {
+ myaddr6.sin6_port = idx->sport;
+ memcpy(myaddr6.sin6_addr.s6_addr, idx->saddr.addr8, sizeof(union netdata_ip_t));
+ }
+
+ ret = (!resolve_name)?-1:getnameinfo((struct sockaddr *)&myaddr6, sizeof(myaddr6), hostname,
+ sizeof(hostname), service_name, sizeof(service_name), NI_NAMEREQD);
+
+ if (!ret && !resolve_service) {
+ snprintf(service_name, sizeof(service_name), "%u", ntohs(myaddr6.sin6_port));
+ }
+
+ if (ret) {
+ // I cannot resolve the name, I will use the IP
+ if (!inet_ntop(AF_INET6, myaddr6.sin6_addr.s6_addr, hostname, NI_MAXHOST)) {
+ strncpy(hostname, errname, 13);
+ }
+
+ snprintf(service_name, sizeof(service_name), "%u", ntohs(myaddr6.sin6_port));
+
+ ret = 1;
+ }
+ }
+
+ fill_resolved_name(ptr, hostname,
+ strlen(hostname) + strlen(service_name)+ NETDATA_DOTS_PROTOCOL_COMBINED_LENGTH,
+ service_name, is_outbound);
+
+ if (resolve_name && !ret)
+ ret = hostname_matches_pattern(hostname);
+
+ ptr->resolved++;
+
+ return ret;
+}
+
+/**
+ * Fill last Network Viewer Dimension
+ *
+ * Fill the unique dimension that is always plotted.
+ *
+ * @param ptr the pointer for the last dimension
+ * @param is_outbound is this an inbound structure?
+ */
+static void fill_last_nv_dimension(netdata_socket_plot_t *ptr, int is_outbound)
+{
+ char hostname[NI_MAXHOST], service_name[NI_MAXSERV];
+ char *other = { "other" };
+ // We are also copying the NULL bytes to avoid warnings in new compilers
+ strncpy(hostname, other, 6);
+ strncpy(service_name, other, 6);
+
+ ptr->family = AF_INET;
+ ptr->sock.protocol = 255;
+ ptr->flags = (!is_outbound)?NETDATA_INBOUND_DIRECTION:NETDATA_OUTBOUND_DIRECTION;
+
+ fill_resolved_name(ptr, hostname, 10 + NETDATA_DOTS_PROTOCOL_COMBINED_LENGTH, service_name, is_outbound);
+
+#ifdef NETDATA_INTERNAL_CHECKS
+ info("Last %s dimension added: ID = %u, IP = OTHER, NAME = %s, DIM1 = %s, DIM2 = %s, DIM3 = %s",
+ (is_outbound)?"outbound":"inbound", network_viewer_opt.max_dim - 1, ptr->resolved_name,
+ ptr->dimension_recv, ptr->dimension_sent, ptr->dimension_retransmit);
+#endif
+}
+
+/**
+ * Update Socket Data
+ *
+ * Update the socket information with last collected data
+ *
+ * @param sock
+ * @param lvalues
+ */
+static inline void update_socket_data(netdata_socket_t *sock, netdata_socket_t *lvalues)
+{
+ sock->recv_packets += lvalues->recv_packets;
+ sock->sent_packets += lvalues->sent_packets;
+ sock->recv_bytes += lvalues->recv_bytes;
+ sock->sent_bytes += lvalues->sent_bytes;
+ sock->retransmit += lvalues->retransmit;
+
+ if (lvalues->ct > sock->ct)
+ sock->ct = lvalues->ct;
+}
+
+/**
+ * Store socket inside avl
+ *
+ * Store the socket values inside the avl tree.
+ *
+ * @param out the structure with information used to plot charts.
+ * @param lvalues Values read from socket ring.
+ * @param lindex the index information, the real socket.
+ * @param family the family associated to the socket
+ * @param flags the connection flags
+ */
+static void store_socket_inside_avl(netdata_vector_plot_t *out, netdata_socket_t *lvalues,
+ netdata_socket_idx_t *lindex, int family, uint32_t flags)
+{
+ netdata_socket_plot_t test, *ret ;
+
+ memcpy(&test.index, lindex, sizeof(netdata_socket_idx_t));
+ test.flags = flags;
+
+ ret = (netdata_socket_plot_t *) avl_search_lock(&out->tree, (avl *)&test);
+ if (ret) {
+ if (lvalues->ct > ret->plot.last_time) {
+ update_socket_data(&ret->sock, lvalues);
+ }
+ } else {
+ uint32_t curr = out->next;
+ uint32_t last = out->last;
+
+ netdata_socket_plot_t *w = &out->plot[curr];
+
+ int resolved;
+ if (curr == last) {
+ if (lvalues->ct > w->plot.last_time) {
+ update_socket_data(&w->sock, lvalues);
+ }
+ return;
+ } else {
+ memcpy(&w->sock, lvalues, sizeof(netdata_socket_t));
+ memcpy(&w->index, lindex, sizeof(netdata_socket_idx_t));
+ w->family = family;
+
+ resolved = fill_names(w, out != (netdata_vector_plot_t *)&inbound_vectors);
+ }
+
+ if (!resolved) {
+ freez(w->resolved_name);
+ freez(w->dimension_sent);
+ freez(w->dimension_recv);
+ freez(w->dimension_retransmit);
+
+ memset(w, 0, sizeof(netdata_socket_plot_t));
+
+ return;
+ }
+
+ w->flags = flags;
+ netdata_socket_plot_t *check ;
+ check = (netdata_socket_plot_t *) avl_insert_lock(&out->tree, (avl *)w);
+ if (check != w)
+ error("Internal error, cannot insert the AVL tree.");
+
+#ifdef NETDATA_INTERNAL_CHECKS
+ char iptext[INET6_ADDRSTRLEN];
+ if (inet_ntop(family, &w->index.daddr.addr8, iptext, sizeof(iptext)))
+ info("New %s dimension added: ID = %u, IP = %s, NAME = %s, DIM1 = %s, DIM2 = %s, DIM3 = %s",
+ (out == &inbound_vectors)?"inbound":"outbound", curr, iptext, w->resolved_name,
+ w->dimension_recv, w->dimension_sent, w->dimension_retransmit);
+#endif
+ curr++;
+ if (curr > last)
+ curr = last;
+ out->next = curr;
+ }
+}
+
+/**
+ * Compare Vector to store
+ *
+ * Compare input values with local address to select table to store.
+ *
+ * @param direction store inbound and outbound direction.
+ * @param cmp index read from hash table.
+ * @param proto the protocol read.
+ *
+ * @return It returns the structure with address to compare.
+ */
+netdata_vector_plot_t * select_vector_to_store(uint32_t *direction, netdata_socket_idx_t *cmp, uint8_t proto)
+{
+ if (!listen_ports) {
+ *direction = NETDATA_OUTBOUND_DIRECTION;
+ return &outbound_vectors;
+ }
+
+ ebpf_network_viewer_port_list_t *move_ports = listen_ports;
+ while (move_ports) {
+ if (move_ports->protocol == proto && move_ports->first == cmp->sport) {
+ *direction = NETDATA_INBOUND_DIRECTION;
+ return &inbound_vectors;
+ }
+
+ move_ports = move_ports->next;
+ }
+
+ *direction = NETDATA_OUTBOUND_DIRECTION;
+ return &outbound_vectors;
+}
+
+/**
+ * Hash accumulator
+ *
+ * @param values the values used to calculate the data.
+ * @param key the key to store data.
+ * @param removesock check if this socket must be removed .
+ * @param family the connection family
+ * @param end the values size.
+ */
+static void hash_accumulator(netdata_socket_t *values, netdata_socket_idx_t *key, int *removesock, int family, int end)
+{
+ uint64_t bsent = 0, brecv = 0, psent = 0, precv = 0;
+ uint16_t retransmit = 0;
+ int i;
+ uint8_t protocol = values[0].protocol;
+ uint64_t ct = values[0].ct;
+ for (i = 1; i < end; i++) {
+ netdata_socket_t *w = &values[i];
+
+ precv += w->recv_packets;
+ psent += w->sent_packets;
+ brecv += w->recv_bytes;
+ bsent += w->sent_bytes;
+ retransmit += w->retransmit;
+
+ if (!protocol)
+ protocol = w->protocol;
+
+ if (w->ct > ct)
+ ct = w->ct;
+
+ *removesock += (int)w->removeme;
+ }
+
+ values[0].recv_packets += precv;
+ values[0].sent_packets += psent;
+ values[0].recv_bytes += brecv;
+ values[0].sent_bytes += bsent;
+ values[0].retransmit += retransmit;
+ values[0].removeme += (uint8_t)*removesock;
+ values[0].protocol = (!protocol)?IPPROTO_TCP:protocol;
+ values[0].ct = ct;
+
+ if (is_socket_allowed(key, family)) {
+ uint32_t dir;
+ netdata_vector_plot_t *table = select_vector_to_store(&dir, key, protocol);
+ store_socket_inside_avl(table, &values[0], key, family, dir);
+ }
+}
+
+/**
+ * Read socket hash table
+ *
+ * Read data from hash tables created on kernel ring.
+ *
+ * @param fd the hash table with data.
+ * @param family the family associated to the hash table
+ *
+ * @return it returns 0 on success and -1 otherwise.
+ */
+static void read_socket_hash_table(int fd, int family, int network_connection)
+{
+ if (wait_to_plot)
+ return;
+
+ netdata_socket_idx_t key = {};
+ netdata_socket_idx_t next_key;
+ netdata_socket_idx_t removeme;
+ int removesock = 0;
+
+ netdata_socket_t *values = socket_values;
+ size_t length = ebpf_nprocs*sizeof(netdata_socket_t);
+ int test, end = (running_on_kernel < NETDATA_KERNEL_V4_15) ? 1 : ebpf_nprocs;
+
+ while (bpf_map_get_next_key(fd, &key, &next_key) == 0) {
+ // 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);
+ test = bpf_map_lookup_elem(fd, &key, values);
+ if (test < 0) {
+ key = next_key;
+ continue;
+ }
+
+ if (removesock)
+ bpf_map_delete_elem(fd, &removeme);
+
+ if (network_connection) {
+ removesock = 0;
+ hash_accumulator(values, &key, &removesock, family, end);
+ }
+
+ if (removesock)
+ removeme = key;
+
+ key = next_key;
+ }
+
+ if (removesock)
+ bpf_map_delete_elem(fd, &removeme);
+
+ test = bpf_map_lookup_elem(fd, &next_key, values);
+ if (test < 0) {
+ return;
+ }
+
+ if (network_connection) {
+ removesock = 0;
+ hash_accumulator(values, &next_key, &removesock, family, end);
+ }
+
+ if (removesock)
+ bpf_map_delete_elem(fd, &next_key);
+}
+
+/**
+ * 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.
+ */
+void update_listen_table(uint16_t value, uint8_t proto)
+{
+ 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)
+ return;
+
+ store = move;
+ move = move->next;
+ }
+
+ w = callocz(1, sizeof(ebpf_network_viewer_port_list_t));
+ w->first = value;
+ w->protocol = proto;
+ store->next = w;
+ } else {
+ w = callocz(1, sizeof(ebpf_network_viewer_port_list_t));
+ w->first = value;
+ w->protocol = proto;
+
+ listen_ports = w;
+ }
+
+#ifdef NETDATA_INTERNAL_CHECKS
+ 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()
+{
+ uint16_t key = 0;
+ uint16_t next_key;
+
+ int fd = map_fd[NETDATA_SOCKET_LISTEN_TABLE];
+ uint8_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(htons(key), (key == 53)?IPPROTO_UDP:IPPROTO_TCP);
+
+ key = next_key;
+ }
+
+ if (next_key) {
+ // The correct protocol must come from kernel
+ update_listen_table(htons(next_key), (key == 53)?IPPROTO_UDP:IPPROTO_TCP);
+ }
+}
+
+/**
+ * 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_socket_read_hash(void *ptr)
+{
+ ebpf_module_t *em = (ebpf_module_t *)ptr;
+
+ read_thread_closed = 0;
+ heartbeat_t hb;
+ heartbeat_init(&hb);
+ usec_t step = NETDATA_SOCKET_READ_SLEEP_MS;
+ int fd_ipv4 = map_fd[NETDATA_SOCKET_IPV4_HASH_TABLE];
+ int fd_ipv6 = map_fd[NETDATA_SOCKET_IPV6_HASH_TABLE];
+ int network_connection = em->optional;
+ while (!close_ebpf_plugin) {
+ usec_t dt = heartbeat_next(&hb, step);
+ (void)dt;
+
+ pthread_mutex_lock(&nv_mutex);
+ read_listen_table();
+ read_socket_hash_table(fd_ipv4, AF_INET, network_connection);
+ read_socket_hash_table(fd_ipv6, AF_INET6, network_connection);
+ wait_to_plot = 1;
+ pthread_mutex_unlock(&nv_mutex);
+ }
+
+ read_thread_closed = 1;
+ return NULL;
+}
+
+/**
+ * Read the hash table and store data to allocated vectors.
+ */
+static void read_hash_global_tables()
+{
+ uint64_t idx;
+ netdata_idx_t res[NETDATA_SOCKET_COUNTER];
+
+ netdata_idx_t *val = socket_hash_values;
+ int fd = map_fd[NETDATA_SOCKET_GLOBAL_HASH_TABLE];
+ for (idx = 0; idx < NETDATA_SOCKET_COUNTER; idx++) {
+ if (!bpf_map_lookup_elem(fd, &idx, val)) {
+ uint64_t total = 0;
+ int i;
+ int end = (running_on_kernel < NETDATA_KERNEL_V4_15) ? 1 : ebpf_nprocs;
+ for (i = 0; i < end; i++)
+ total += val[i];
+
+ res[idx] = total;
+ } else {
+ res[idx] = 0;
+ }
+ }
+
+ socket_aggregated_data[0].call = res[NETDATA_KEY_CALLS_TCP_SENDMSG];
+ socket_aggregated_data[1].call = res[NETDATA_KEY_CALLS_TCP_CLEANUP_RBUF];
+ socket_aggregated_data[2].call = res[NETDATA_KEY_CALLS_TCP_CLOSE];
+ socket_aggregated_data[3].call = res[NETDATA_KEY_CALLS_UDP_RECVMSG];
+ socket_aggregated_data[4].call = res[NETDATA_KEY_CALLS_UDP_SENDMSG];
+ socket_aggregated_data[5].call = res[NETDATA_KEY_TCP_RETRANSMIT];
+
+ socket_aggregated_data[0].ecall = res[NETDATA_KEY_ERROR_TCP_SENDMSG];
+ socket_aggregated_data[1].ecall = res[NETDATA_KEY_ERROR_TCP_CLEANUP_RBUF];
+ socket_aggregated_data[3].ecall = res[NETDATA_KEY_ERROR_UDP_RECVMSG];
+ socket_aggregated_data[4].ecall = res[NETDATA_KEY_ERROR_UDP_SENDMSG];
+
+ socket_aggregated_data[0].bytes = res[NETDATA_KEY_BYTES_TCP_SENDMSG];
+ socket_aggregated_data[1].bytes = res[NETDATA_KEY_BYTES_TCP_CLEANUP_RBUF];
+ socket_aggregated_data[3].bytes = res[NETDATA_KEY_BYTES_UDP_RECVMSG];
+ socket_aggregated_data[4].bytes = res[NETDATA_KEY_BYTES_UDP_SENDMSG];
+}
+
+/**
+ * Fill publish apps when necessary.
+ *
+ * @param current_pid the PID that I am updating
+ * @param eb the structure with data read from memory.
+ */
+void ebpf_socket_fill_publish_apps(uint32_t current_pid, ebpf_bandwidth_t *eb)
+{
+ ebpf_socket_publish_apps_t *curr = socket_bandwidth_curr[current_pid];
+ ebpf_socket_publish_apps_t *prev = socket_bandwidth_prev[current_pid];
+ if (!curr) {
+ ebpf_socket_publish_apps_t *ptr = callocz(2, sizeof(ebpf_socket_publish_apps_t));
+ curr = &ptr[0];
+ socket_bandwidth_curr[current_pid] = curr;
+ prev = &ptr[1];
+ socket_bandwidth_prev[current_pid] = prev;
+ } else {
+ memcpy(prev, curr, sizeof(ebpf_socket_publish_apps_t));
+ }
+
+ curr->bytes_sent = eb->bytes_sent;
+ curr->bytes_received = eb->bytes_received;
+ curr->call_tcp_sent = eb->call_tcp_sent;
+ curr->call_tcp_received = eb->call_tcp_received;
+ curr->retransmit = eb->retransmit;
+ curr->call_udp_sent = eb->call_udp_sent;
+ curr->call_udp_received = eb->call_udp_received;
+
+ ebpf_socket_update_apps_publish(curr, prev);
+}
+
+/**
+ * Bandwidth accumulator.
+ *
+ * @param out the vector with the values to sum
+ */
+void ebpf_socket_bandwidth_accumulator(ebpf_bandwidth_t *out)
+{
+ int i, end = (running_on_kernel >= NETDATA_KERNEL_V4_15) ? ebpf_nprocs : 1;
+ ebpf_bandwidth_t *total = &out[0];
+ for (i = 1; i < end; i++) {
+ ebpf_bandwidth_t *move = &out[i];
+ total->bytes_sent += move->bytes_sent;
+ total->bytes_received += move->bytes_received;
+ total->call_tcp_sent += move->call_tcp_sent;
+ total->call_tcp_received += move->call_tcp_received;
+ total->retransmit += move->retransmit;
+ total->call_udp_sent += move->call_udp_sent;
+ total->call_udp_received += move->call_udp_received;
+ }
+}
+
+/**
+ * Update the apps data reading information from the hash table
+ */
+static void ebpf_socket_update_apps_data()
+{
+ int fd = map_fd[NETDATA_SOCKET_APPS_HASH_TABLE];
+ ebpf_bandwidth_t *eb = bandwidth_vector;
+ uint32_t key;
+ struct pid_stat *pids = root_of_pids;
+ while (pids) {
+ key = pids->pid;
+
+ if (bpf_map_lookup_elem(fd, &key, eb)) {
+ pids = pids->next;
+ continue;
+ }
+
+ ebpf_socket_bandwidth_accumulator(eb);
+
+ ebpf_socket_fill_publish_apps(key, eb);
+
+ pids = pids->next;
+ }
+}
+
+/*****************************************************************
+ *
+ * FUNCTIONS WITH THE MAIN LOOP
+ *
+ *****************************************************************/
+
+struct netdata_static_thread socket_threads = {"EBPF SOCKET READ",
+ NULL, NULL, 1, NULL,
+ NULL, ebpf_socket_read_hash };
+
+/**
+ * Main loop for this collector.
+ *
+ * @param step the number of microseconds used with heart beat
+ * @param em the structure with thread information
+ */
+static void socket_collector(usec_t step, ebpf_module_t *em)
+{
+ UNUSED(em);
+ UNUSED(step);
+ heartbeat_t hb;
+ heartbeat_init(&hb);
+
+ socket_threads.thread = mallocz(sizeof(netdata_thread_t));
+
+ netdata_thread_create(socket_threads.thread, socket_threads.name,
+ NETDATA_THREAD_OPTION_JOINABLE, ebpf_socket_read_hash, em);
+
+ int socket_apps_enabled = ebpf_modules[EBPF_MODULE_SOCKET_IDX].apps_charts;
+ int socket_global_enabled = ebpf_modules[EBPF_MODULE_SOCKET_IDX].global_charts;
+ int network_connection = em->optional;
+ while (!close_ebpf_plugin) {
+ pthread_mutex_lock(&collect_data_mutex);
+ pthread_cond_wait(&collect_data_cond_var, &collect_data_mutex);
+
+ if (socket_global_enabled)
+ read_hash_global_tables();
+
+ if (socket_apps_enabled)
+ ebpf_socket_update_apps_data();
+
+ calculate_nv_plot();
+
+ pthread_mutex_lock(&lock);
+ if (socket_global_enabled)
+ ebpf_socket_send_data(em);
+
+ if (socket_apps_enabled)
+ ebpf_socket_send_apps_data(em, apps_groups_root_target);
+
+ fflush(stdout);
+
+ if (network_connection) {
+ // We are calling fflush many times, because when we have a lot of dimensions
+ // we began to have not expected outputs and Netdata closed the plugin.
+ pthread_mutex_lock(&nv_mutex);
+ ebpf_socket_create_nv_charts(&inbound_vectors);
+ fflush(stdout);
+ ebpf_socket_send_nv_data(&inbound_vectors);
+
+ ebpf_socket_create_nv_charts(&outbound_vectors);
+ fflush(stdout);
+ ebpf_socket_send_nv_data(&outbound_vectors);
+ wait_to_plot = 0;
+ pthread_mutex_unlock(&nv_mutex);
+
+ }
+
+ pthread_mutex_unlock(&collect_data_mutex);
+ pthread_mutex_unlock(&lock);
+
+ }
+}
+
+/*****************************************************************
+ *
+ * FUNCTIONS TO CLOSE THE THREAD
+ *
+ *****************************************************************/
+
+
+/**
+ * Clean internal socket plot
+ *
+ * Clean all structures allocated with strdupz.
+ *
+ * @param ptr the pointer with addresses to clean.
+ */
+static inline void clean_internal_socket_plot(netdata_socket_plot_t *ptr)
+{
+ freez(ptr->dimension_recv);
+ freez(ptr->dimension_sent);
+ freez(ptr->resolved_name);
+ freez(ptr->dimension_retransmit);
+}
+
+/**
+ * Clean socket plot
+ *
+ * Clean the allocated data for inbound and outbound vectors.
+ */
+static void clean_allocated_socket_plot()
+{
+ uint32_t i;
+ uint32_t end = inbound_vectors.last;
+ netdata_socket_plot_t *plot = inbound_vectors.plot;
+ for (i = 0; i < end; i++) {
+ clean_internal_socket_plot(&plot[i]);
+ }
+
+ clean_internal_socket_plot(&plot[inbound_vectors.last]);
+
+ end = outbound_vectors.last;
+ plot = outbound_vectors.plot;
+ for (i = 0; i < end; i++) {
+ clean_internal_socket_plot(&plot[i]);
+ }
+ clean_internal_socket_plot(&plot[outbound_vectors.last]);
+}
+
+/**
+ * Clean netowrk ports allocated during initializaion.
+ *
+ * @param ptr a pointer to the link list.
+ */
+static void clean_network_ports(ebpf_network_viewer_port_list_t *ptr)
+{
+ if (unlikely(!ptr))
+ return;
+
+ while (ptr) {
+ ebpf_network_viewer_port_list_t *next = ptr->next;
+ freez(ptr->value);
+ freez(ptr);
+ ptr = next;
+ }
+}
+
+/**
+ * Clean service names
+ *
+ * Clean the allocated link list that stores names.
+ *
+ * @param names the link list.
+ */
+static void clean_service_names(ebpf_network_viewer_dim_name_t *names)
+{
+ if (unlikely(!names))
+ return;
+
+ while (names) {
+ ebpf_network_viewer_dim_name_t *next = names->next;
+ freez(names->name);
+ freez(names);
+ names = next;
+ }
+}
+
+/**
+ * Clean hostnames
+ *
+ * @param hostnames the hostnames to clean
+ */
+static void clean_hostnames(ebpf_network_viewer_hostname_list_t *hostnames)
+{
+ if (unlikely(!hostnames))
+ return;
+
+ while (hostnames) {
+ ebpf_network_viewer_hostname_list_t *next = hostnames->next;
+ freez(hostnames->value);
+ simple_pattern_free(hostnames->value_pattern);
+ freez(hostnames);
+ hostnames = next;
+ }
+}
+
+void clean_thread_structures() {
+ struct pid_stat *pids = root_of_pids;
+ while (pids) {
+ freez(socket_bandwidth_curr[pids->pid]);
+
+ pids = pids->next;
+ }
+}
+
+/**
+ * Clean up the main thread.
+ *
+ * @param ptr thread data.
+ */
+static void ebpf_socket_cleanup(void *ptr)
+{
+ ebpf_module_t *em = (ebpf_module_t *)ptr;
+ if (!em->enabled)
+ return;
+
+ heartbeat_t hb;
+ heartbeat_init(&hb);
+ uint32_t tick = 200*USEC_PER_MS;
+ while (!read_thread_closed) {
+ usec_t dt = heartbeat_next(&hb, tick);
+ UNUSED(dt);
+ }
+
+ freez(socket_aggregated_data);
+ freez(socket_publish_aggregated);
+ freez(socket_hash_values);
+
+ clean_thread_structures();
+ freez(socket_bandwidth_curr);
+ freez(socket_bandwidth_prev);
+ freez(bandwidth_vector);
+
+ freez(socket_values);
+ clean_allocated_socket_plot();
+ freez(inbound_vectors.plot);
+ freez(outbound_vectors.plot);
+
+ clean_port_structure(&listen_ports);
+
+ ebpf_modules[EBPF_MODULE_SOCKET_IDX].enabled = 0;
+
+ clean_network_ports(network_viewer_opt.included_port);
+ clean_network_ports(network_viewer_opt.excluded_port);
+ clean_service_names(network_viewer_opt.names);
+ clean_hostnames(network_viewer_opt.included_hostnames);
+ clean_hostnames(network_viewer_opt.excluded_hostnames);
+
+ pthread_mutex_destroy(&nv_mutex);
+ freez(socket_data.map_fd);
+
+ freez(socket_threads.thread);
+
+ struct bpf_program *prog;
+ size_t i = 0 ;
+ bpf_object__for_each_program(prog, objects) {
+ bpf_link__destroy(probe_links[i]);
+ i++;
+ }
+ bpf_object__close(objects);
+ finalized_threads = 1;
+}
+
+/*****************************************************************
+ *
+ * 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_socket_allocate_global_vectors(size_t length)
+{
+ socket_aggregated_data = callocz(length, sizeof(netdata_syscall_stat_t));
+ socket_publish_aggregated = callocz(length, sizeof(netdata_publish_syscall_t));
+ socket_hash_values = callocz(ebpf_nprocs, sizeof(netdata_idx_t));
+
+ socket_bandwidth_curr = callocz((size_t)pid_max, sizeof(ebpf_socket_publish_apps_t *));
+ socket_bandwidth_prev = callocz((size_t)pid_max, sizeof(ebpf_socket_publish_apps_t *));
+ bandwidth_vector = callocz((size_t)ebpf_nprocs, sizeof(ebpf_bandwidth_t));
+
+ socket_values = callocz((size_t)ebpf_nprocs, sizeof(netdata_socket_t));
+ inbound_vectors.plot = callocz(network_viewer_opt.max_dim, sizeof(netdata_socket_plot_t));
+ outbound_vectors.plot = callocz(network_viewer_opt.max_dim, sizeof(netdata_socket_plot_t));
+}
+
+/**
+ * Set local function pointers, this function will never be compiled with static libraries
+ */
+static void set_local_pointers()
+{
+ map_fd = socket_data.map_fd;
+}
+
+/**
+ * Initialize Inbound and Outbound
+ *
+ * Initialize the common outbound and inbound sockets.
+ */
+static void initialize_inbound_outbound()
+{
+ inbound_vectors.last = network_viewer_opt.max_dim - 1;
+ outbound_vectors.last = inbound_vectors.last;
+ fill_last_nv_dimension(&inbound_vectors.plot[inbound_vectors.last], 0);
+ fill_last_nv_dimension(&outbound_vectors.plot[outbound_vectors.last], 1);
+}
+
+/*****************************************************************
+ *
+ * EBPF SOCKET THREAD
+ *
+ *****************************************************************/
+
+/**
+ * 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)
+{
+ netdata_thread_cleanup_push(ebpf_socket_cleanup, ptr);
+
+ avl_init_lock(&inbound_vectors.tree, compare_sockets);
+ avl_init_lock(&outbound_vectors.tree, compare_sockets);
+
+ ebpf_module_t *em = (ebpf_module_t *)ptr;
+ fill_ebpf_data(&socket_data);
+
+ if (!em->enabled)
+ goto endsocket;
+
+ if (pthread_mutex_init(&nv_mutex, NULL)) {
+ error("Cannot initialize local mutex");
+ goto endsocket;
+ }
+ pthread_mutex_lock(&lock);
+
+ ebpf_socket_allocate_global_vectors(NETDATA_MAX_SOCKET_VECTOR);
+ initialize_inbound_outbound();
+
+ if (ebpf_update_kernel(&socket_data)) {
+ pthread_mutex_unlock(&lock);
+ goto endsocket;
+ }
+
+ set_local_pointers();
+ probe_links = ebpf_load_program(ebpf_plugin_dir, em, kernel_string, &objects, socket_data.map_fd);
+ if (!probe_links) {
+ pthread_mutex_unlock(&lock);
+ goto endsocket;
+ }
+
+ ebpf_global_labels(
+ socket_aggregated_data, socket_publish_aggregated, socket_dimension_names, socket_id_names,
+ NETDATA_MAX_SOCKET_VECTOR);
+
+ ebpf_create_global_charts(em);
+
+ finalized_threads = 0;
+ pthread_mutex_unlock(&lock);
+
+ socket_collector((usec_t)(em->update_time * USEC_PER_SEC), em);
+
+endsocket:
+ netdata_thread_cleanup_pop(1);
+ return NULL;
+}
diff --git a/collectors/ebpf.plugin/ebpf_socket.h b/collectors/ebpf.plugin/ebpf_socket.h
new file mode 100644
index 00000000..0e19f80e
--- /dev/null
+++ b/collectors/ebpf.plugin/ebpf_socket.h
@@ -0,0 +1,276 @@
+// 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"
+
+// Vector indexes
+#define NETDATA_MAX_SOCKET_VECTOR 6
+#define NETDATA_UDP_START 3
+#define NETDATA_RETRANSMIT_START 5
+
+#define NETDATA_SOCKET_APPS_HASH_TABLE 0
+#define NETDATA_SOCKET_IPV4_HASH_TABLE 1
+#define NETDATA_SOCKET_IPV6_HASH_TABLE 2
+#define NETDATA_SOCKET_GLOBAL_HASH_TABLE 4
+#define NETDATA_SOCKET_LISTEN_TABLE 5
+
+#define NETDATA_SOCKET_READ_SLEEP_MS 800000ULL
+
+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_SOCKET_COUNTER
+} ebpf_socket_index_t;
+
+#define NETDATA_SOCKET_GROUP "Socket"
+#define NETDATA_NETWORK_CONNECTIONS_GROUP "Network connections"
+
+// Global chart name
+#define NETDATA_TCP_FUNCTION_COUNT "tcp_functions"
+#define NETDATA_TCP_FUNCTION_BYTES "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_BYTES "udp_bandwidth"
+#define NETDATA_UDP_FUNCTION_ERROR "udp_error"
+
+// Charts created on Apps submenu
+#define NETDATA_NET_APPS_BANDWIDTH_SENT "bandwidth_sent"
+#define NETDATA_NET_APPS_BANDWIDTH_RECV "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"
+
+// Network viewer charts
+#define NETDATA_NV_OUTBOUND_BYTES "outbound_bytes"
+#define NETDATA_NV_OUTBOUND_PACKETS "outbound_packets"
+#define NETDATA_NV_OUTBOUND_RETRANSMIT "outbound_retransmit"
+#define NETDATA_NV_INBOUND_BYTES "inbound_bytes"
+#define NETDATA_NV_INBOUND_PACKETS "inbound_packets"
+
+// Port range
+#define NETDATA_MINIMUM_PORT_VALUE 1
+#define NETDATA_MAXIMUM_PORT_VALUE 65535
+
+#define NETDATA_MINIMUM_IPV4_CIDR 0
+#define NETDATA_MAXIMUM_IPV4_CIDR 32
+
+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
+
+ // Publish information.
+ uint64_t publish_sent_bytes;
+ uint64_t publish_received_bytes;
+ uint64_t publish_tcp_sent;
+ uint64_t publish_tcp_received;
+ uint64_t publish_retransmit;
+ uint64_t publish_udp_sent;
+ uint64_t publish_udp_received;
+} 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;
+
+ uint8_t protocol;
+ struct ebpf_network_viewer_port_list *next;
+} ebpf_network_viewer_port_list_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;
+
+#define NETDATA_NV_CAP_VALUE 50L
+typedef struct ebpf_network_viewer_options {
+ uint32_t max_dim; // Store value read from 'maximum dimensions'
+
+ 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 {
+ uint64_t recv_packets;
+ uint64_t sent_packets;
+ uint64_t recv_bytes;
+ uint64_t sent_bytes;
+ uint64_t first; // First timestamp
+ uint64_t ct; // Current timestamp
+ uint16_t retransmit; // It is never used with UDP
+ uint8_t protocol;
+ uint8_t removeme;
+ uint32_t reserved;
+} netdata_socket_t __attribute__((__aligned__(8)));
+
+
+typedef struct netdata_plot_values {
+ // Values used in the previous iteration
+ uint64_t recv_packets;
+ uint64_t sent_packets;
+ uint64_t recv_bytes;
+ uint64_t sent_bytes;
+ uint16_t retransmit;
+
+ uint64_t last_time;
+
+ // Values used to plot
+ uint64_t plot_recv_packets;
+ uint64_t plot_sent_packets;
+ uint64_t plot_recv_bytes;
+ uint64_t plot_sent_bytes;
+ uint16_t plot_retransmit;
+} netdata_plot_values_t;
+
+/**
+ * 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;
+} netdata_socket_idx_t __attribute__((__aligned__(8)));
+
+// Next values were defined according getnameinfo(3)
+#define NETDATA_MAX_NETWORK_COMBINED_LENGTH 1018
+#define NETDATA_DOTS_PROTOCOL_COMBINED_LENGTH 5 // :TCP:
+#define NETDATA_DIM_LENGTH_WITHOUT_SERVICE_PROTOCOL 979
+
+#define NETDATA_INBOUND_DIRECTION (uint32_t)1
+#define NETDATA_OUTBOUND_DIRECTION (uint32_t)2
+/**
+ * Allocate the maximum number of structures in the beginning, this can force the collector to use more memory
+ * in the long term, on the other had it is faster.
+ */
+typedef struct netdata_socket_plot {
+ // Search
+ avl avl;
+ netdata_socket_idx_t index;
+
+ // Current data
+ netdata_socket_t sock;
+
+ // Previous values and values used to write on chart.
+ netdata_plot_values_t plot;
+
+ int family; // AF_INET or AF_INET6
+ char *resolved_name; // Resolve only in the first call
+ unsigned char resolved;
+
+ char *dimension_sent;
+ char *dimension_recv;
+ char *dimension_retransmit;
+
+ uint32_t flags;
+} netdata_socket_plot_t;
+
+#define NETWORK_VIEWER_CHARTS_CREATED (uint32_t)1
+typedef struct netdata_vector_plot {
+ netdata_socket_plot_t *plot; // Vector used to plot charts
+
+ avl_tree_lock tree; // AVL tree to speed up search
+ uint32_t last; // The 'other' dimension, the last chart accepted.
+ uint32_t next; // The next position to store in the vector.
+ uint32_t max_plot; // Max number of elements to plot.
+ uint32_t last_plot; // Last element plot
+
+ uint32_t flags; // Flags
+
+} netdata_vector_plot_t;
+
+extern void clean_port_structure(ebpf_network_viewer_port_list_t **clean);
+extern ebpf_network_viewer_port_list_t *listen_ports;
+extern void update_listen_table(uint16_t value, uint8_t proto);
+
+extern ebpf_socket_publish_apps_t **socket_bandwidth_curr;
+extern ebpf_socket_publish_apps_t **socket_bandwidth_prev;
+
+#endif
diff --git a/collectors/ebpf.plugin/reset_netdata_trace.sh.in b/collectors/ebpf.plugin/reset_netdata_trace.sh.in
new file mode 100644
index 00000000..51d981ee
--- /dev/null
+++ b/collectors/ebpf.plugin/reset_netdata_trace.sh.in
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+KPROBE_FILE="/sys/kernel/debug/tracing/kprobe_events"
+
+DATA="$(grep _netdata_ $KPROBE_FILE| cut -d' ' -f1 | cut -d: -f2)"
+
+for I in $DATA; do
+ echo "-:$I" > $KPROBE_FILE 2>/dev/null;
+done