Browse Source

apps.plugin for windows (#18594)

* apps.plugin uses a hashtable for pids; apps.plugin pids sortlist cleanup

* struct pid_stat now uses aral

* structures cleanup

* remove limitation on command name length

* fix log

* process tree grouping which automatically groups the processes based on the process tree

* cleanup

* revert accidental changes

* fix debug logs for STRING pointers

* moved perflib to libnetdata; fixed apps.plugin to accept windows specific functions; not yet working on windows

* fix for linux

* basic structure for perflib collection

* control features per O/S

* split aggregations

* isolate user and group targets per O/S

* gather all O/S functions together

* fix for windows

* virtualized all process variables

* fixed macro; process name extracted from cmdline in a better way

* finished modularizing the whole code

* fix compilation on windows

* fix compilation on macos

* fix format in debug statement

* windows collector for apps.plugin

* windows processes CPU

* fix process name

* fix macos

* fix freebsd

* make it run under clion windows

* cpu utilization in NANOSECONDCORES

* windows cpu utilization in nanosecondcores

* memory utilization is internally in bytes

* exclude pid 0 on windows

* remove the updated flag too

* reset the processing flags at the beginning

* fixed exited processes processing

* fixes for exited children

* fix for macos

* fix for freebsd

* handles are now a type of fd

* fix fds on windows

* macos and freebsd have logical I/O, not physical I/O

* freebsd now reports i/o bytes, not blocks

* I/O calls are now I/O ops

* fix uptime in windows

* get more friendly windows process names

* added parents to function; added orchestrators and aggregators; added mutex to processes function

* added pid name, when it is available

* documentation

* more code cleanup

* fixes for windows

* fix infinite pool

* add name to processes function, when available

* break infinite loop when processes are linked in a loop

* parent-child loop detection earlier

* debug loops

* debug loops

* debug loops

* debug loops

* debug loops

* debug loops

* debug loops

* debug loops

* debug loops

* fixed parents loops

* do not errno in loops

* cosmetic changes

* fixed exited pids processing

* simplified exited pids processing

* simplified exited pids processing again

* code rearrange on users and groups

* fix freebsd; new tree process chart name and label

* pid 0 is an aggregator for all operating systems

* System becomes kernel

* Update src/collectors/apps.plugin/README.md

Co-authored-by: Fotis Voutsas <fotis@netdata.cloud>

* fixed typo

* fixed bug in procfile parsing when multiple opening and closing brackets appear

* removed trailing spaces from cmdline

* fixed orchestrators

* merged tree and app_groups groupings

* updated app_groups.conf

* added docker-init

---------

Co-authored-by: Costa Tsaousis <costa@Costas-Macbook-Pro.local>
Co-authored-by: Costa Tsaousis <costa@MacBookPro.plaka>
Co-authored-by: Fotis Voutsas <fotis@netdata.cloud>
Costa Tsaousis 5 months ago
parent
commit
06bf6631a0

+ 21 - 15
CMakeLists.txt

@@ -125,6 +125,8 @@ elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "CYGWIN" OR "${CMAKE_SYSTEM_NAME}" STREQU
         add_definitions(-D_GNU_SOURCE)
 
         if($ENV{CLION_IDE})
+                set(RUN_UNDER_CLION True)
+
                 # clion needs these to find the includes
                 if("${CMAKE_SYSTEM_NAME}" STREQUAL "MSYS" OR "${CMAKE_SYSTEM_NAME}" STREQUAL "Windows")
                         if("$ENV{MSYSTEM}" STREQUAL "MSYS")
@@ -158,7 +160,7 @@ option(ENABLE_DBENGINE "Enable dbengine metrics storage" True)
 option(ENABLE_PLUGIN_GO "Enable metric collectors written in Go" ${DEFAULT_FEATURE_STATE})
 option(ENABLE_PLUGIN_PYTHON "Enable metric collectors written in Python" ${DEFAULT_FEATURE_STATE})
 
-cmake_dependent_option(ENABLE_PLUGIN_APPS "Enable per-process resource usage monitoring" ${DEFAULT_FEATURE_STATE} "NOT OS_WINDOWS" False)
+cmake_dependent_option(ENABLE_PLUGIN_APPS "Enable per-process resource usage monitoring" ${DEFAULT_FEATURE_STATE} "OS_LINUX OR OS_FREEBSD OR OS_MACOS OR OS_WINDOWS" False)
 cmake_dependent_option(ENABLE_PLUGIN_CHARTS "Enable metric collectors written in Bash" ${DEFAULT_FEATURE_STATE} "NOT OS_WINDOWS" False)
 cmake_dependent_option(ENABLE_PLUGIN_CUPS "Enable CUPS monitoring" ${DEFAULT_FEATURE_STATE} "NOT OS_WINDOWS" False)
 
@@ -763,6 +765,10 @@ set(LIBNETDATA_FILES
         src/libnetdata/os/close_range.h
         src/libnetdata/os/setproctitle.c
         src/libnetdata/os/setproctitle.h
+        src/libnetdata/os/windows-perflib/perflib.c
+        src/libnetdata/os/windows-perflib/perflib.h
+        src/libnetdata/os/windows-perflib/perflib-names.c
+        src/libnetdata/os/windows-perflib/perflib-dump.c
         src/libnetdata/paths/paths.c
         src/libnetdata/paths/paths.h
         src/libnetdata/json/json-c-parser-inline.c
@@ -796,6 +802,8 @@ set(LIBNETDATA_FILES
         src/libnetdata/os/timestamps.h
         src/libnetdata/parsers/entries.c
         src/libnetdata/parsers/entries.h
+        src/libnetdata/sanitizers/chart_id_and_name.c
+        src/libnetdata/sanitizers/chart_id_and_name.h
 )
 
 if(ENABLE_PLUGIN_EBPF)
@@ -1427,12 +1435,8 @@ set(WINDOWS_PLUGIN_FILES
         src/collectors/windows.plugin/GetSystemUptime.c
         src/collectors/windows.plugin/GetSystemRAM.c
         src/collectors/windows.plugin/GetSystemCPU.c
-        src/collectors/windows.plugin/perflib.c
-        src/collectors/windows.plugin/perflib.h
         src/collectors/windows.plugin/perflib-rrd.c
         src/collectors/windows.plugin/perflib-rrd.h
-        src/collectors/windows.plugin/perflib-names.c
-        src/collectors/windows.plugin/perflib-dump.c
         src/collectors/windows.plugin/perflib-storage.c
         src/collectors/windows.plugin/perflib-processor.c
         src/collectors/windows.plugin/perflib-thermalzone.c
@@ -1883,19 +1887,21 @@ if(ENABLE_PLUGIN_APPS)
             src/collectors/apps.plugin/apps_targets.c
             src/collectors/apps.plugin/apps_users_and_groups.c
             src/collectors/apps.plugin/apps_output.c
-            src/collectors/apps.plugin/apps_proc_pid_status.c
-            src/collectors/apps.plugin/apps_proc_pid_limits.c
-            src/collectors/apps.plugin/apps_proc_pid_stat.c
-            src/collectors/apps.plugin/apps_proc_pid_cmdline.c
-            src/collectors/apps.plugin/apps_proc_pid_io.c
-            src/collectors/apps.plugin/apps_proc_stat.c
-            src/collectors/apps.plugin/apps_proc_pid_fd.c
-            src/collectors/apps.plugin/apps_proc_pids.c
-            src/collectors/apps.plugin/apps_proc_meminfo.c
+            src/collectors/apps.plugin/apps_pid_files.c
+            src/collectors/apps.plugin/apps_pid.c
+            src/collectors/apps.plugin/apps_aggregations.c
+            src/collectors/apps.plugin/apps_os_linux.c
+            src/collectors/apps.plugin/apps_os_freebsd.c
+            src/collectors/apps.plugin/apps_os_macos.c
+            src/collectors/apps.plugin/apps_os_windows.c
+            src/collectors/apps.plugin/apps_incremental_collection.c
     )
 
     add_executable(apps.plugin ${APPS_PLUGIN_FILES})
-    target_link_libraries(apps.plugin libnetdata ${CAP_LIBRARIES})
+
+    target_link_libraries(apps.plugin libnetdata ${CAP_LIBRARIES}
+            "$<$<BOOL:${OS_WINDOWS}>:Version>")
+
     target_include_directories(apps.plugin PRIVATE ${CAP_INCLUDE_DIRS})
     target_compile_options(apps.plugin PRIVATE ${CAP_CFLAGS_OTHER})
 

+ 2 - 0
packaging/cmake/config.cmake.h.in

@@ -169,6 +169,8 @@
 #cmakedefine HAVE_LIBYAML
 #cmakedefine HAVE_LIBMNL
 
+#cmakedefine RUN_UNDER_CLION
+
 // /* Enable GNU extensions on systems that have them.  */
 // #ifndef _GNU_SOURCE
 // # define _GNU_SOURCE 1

+ 1 - 0
packaging/utils/compile-on-windows.sh

@@ -66,6 +66,7 @@ then
       -DENABLE_ML=On \
       -DENABLE_BUNDLED_JSONC=On \
       -DENABLE_BUNDLED_PROTOBUF=Off \
+      -DENABLE_PLUGIN_APPS=On \
       ${NULL}
 fi
 

+ 49 - 24
src/collectors/apps.plugin/README.md

@@ -7,30 +7,57 @@ learn_topic_type: "References"
 learn_rel_path: "Integrations/Monitor/System metrics"
 -->
 
-# Application monitoring (apps.plugin)
+# Applications monitoring (apps.plugin)
 
-`apps.plugin` breaks down system resource usage to **processes**, **users** and **user groups**.  
-It is enabled by default on every Netdata installation.
+`apps.plugin` monitors the resources utilization of all processes running.
 
-To achieve this task, it iterates through the whole process tree, collecting resource usage information
-for every process found running.
+## Process Aggregation and Grouping
 
-Since Netdata needs to present this information in charts and track them through time,
-instead of presenting a `top` like list, `apps.plugin` uses a pre-defined list of **process groups**
-to which it assigns all running processes. This list is customizable via `apps_groups.conf`, and Netdata
-ships with a good default for most cases (to edit it on your system run `/etc/netdata/edit-config apps_groups.conf`).
+`apps.plugin` aggregates processes in three distinct ways to provide a more insightful
+breakdown of resource utilization:
 
-So, `apps.plugin` builds a process tree (much like `ps fax` does in Linux), and groups
+ - **Tree** or **Category**: Grouped by their position in the process tree.
+   This is customizable and allows aggregation by process managers and individual
+   processes of interest. Allows also renaming the processes for presentation purposes.
+ 
+ - **User**: Grouped by the effective user (UID) under which the processes run.
+ 
+ - **Group**: Grouped by the effective group (GID) under which the processes run.
+
+ ## Short-Lived Process Handling
+
+`apps.plugin` accounts for resource utilization of both running and exited processes,
+capturing the impact of processes that spawn short-lived subprocesses, such as shell
+scripts that fork hundreds or thousands of times per second. So, although processes
+may spawn short lived sub-processes, `apps.plugin` will aggregate their resources
+utilization providing a holistic view of how resources are shared among the processes. 
+
+## Charts sections
+
+To provide more valuable insights, apps.plugin aggregates individual processes in several ways.
+Each type of aggregation is presented as a different section on the dashboard.
+
+### Custom Process Groups (Apps)
+
+In this section, apps.plugin summarizes the resources consumed by all processes, grouped based
+on the groups provided in `/etc/netdata/apps_groups.conf`. You can edit this file using our [`edit-config`](docs/netdata-agent/configuration/README.md) script.
+
+For this section, `apps.plugin` builds a process tree (much like `ps fax` does in Linux), and groups
 processes together (evaluating both child and parent processes) so that the result is always a list with
 a predefined set of members (of course, only process groups found running are reported).
 
 > If you find that `apps.plugin` categorizes standard applications as `other`, we would be
 > glad to accept pull requests improving the defaults shipped with Netdata in `apps_groups.conf`.
 
-Unlike traditional process monitoring tools (like `top`), `apps.plugin` is able to account the resource
-utilization of exit processes. Their utilization is accounted at their currently running parents.
-So, `apps.plugin` is perfectly able to measure the resources used by shell scripts and other processes
-that fork/spawn other short-lived processes hundreds of times per second.
+### By User (Users)
+
+In this section, apps.plugin summarizes the resources consumed by all processes, grouped by the
+effective user under which each process runs.
+
+### By User Group (Groups)
+
+In this section, apps.plugin summarizes the resources consumed by all processes, grouped by the
+effective user group under which each process runs.
 
 ## Charts
 
@@ -82,7 +109,7 @@ The above are reported:
 `apps.plugin` is a complex piece of software and has a lot of work to do
 We are proud that `apps.plugin` is a lot faster compared to any other similar tool,
 while collecting a lot more information for the processes, however the fact is that
-this plugin requires more CPU resources than the `netdata` daemon itself.
+this plugin may require more CPU resources than the `netdata` daemon itself.
 
 Under Linux, for each process running, `apps.plugin` reads several `/proc` files
 per process. Doing this work per-second, especially on hosts with several thousands
@@ -103,7 +130,7 @@ its CPU resources will be cut in half, and data collection will be once every 2
 
 ## Configuration
 
-The configuration file is `/etc/netdata/apps_groups.conf`. To edit it on your system, run `/etc/netdata/edit-config apps_groups.conf`.
+The configuration file is `/etc/netdata/apps_groups.conf`. You can edit this file using our [`edit-config`](docs/netdata-agent/configuration/README.md) script.
 
 The configuration file works accepts multiple lines, each having this format:
 
@@ -381,14 +408,14 @@ the process tree of `sshd`, **including the exited children**.
 > `apps.plugin` does not use these mechanisms. The process grouping made by `apps.plugin` works
 > on any Linux, `systemd` based or not.
 
-#### a more technical description of how Netdata works
+#### a more technical description of how apps.plugin works
 
-Netdata reads `/proc/<pid>/stat` for all processes, once per second and extracts `utime` and
+Apps.plugin reads `/proc/<pid>/stat` for all processes, once per second and extracts `utime` and
 `stime` (user and system cpu utilization), much like all the console tools do.
 
-But it also extracts `cutime` and `cstime` that account the user and system time of the exit children of each process.
-By keeping a map in memory of the whole process tree, it is capable of assigning the right time to every process, taking
-into account all its exited children.
+But it also extracts `cutime` and `cstime` that account the user and system time of the exit
+children of each process. By keeping a map in memory of the whole process tree, it is capable of
+assigning the right time to every process, taking into account all its exited children.
 
 It is tricky, since a process may be running for 1 hour and once it exits, its parent should not
 receive the whole 1 hour of cpu time in just 1 second - you have to subtract the cpu time that has
@@ -397,6 +424,4 @@ been reported for it prior to this iteration.
 It is even trickier, because walking through the entire process tree takes some time itself. So,
 if you sum the CPU utilization of all processes, you might have more CPU time than the reported
 total cpu time of the system. Netdata solves this, by adapting the per process cpu utilization to
-the total of the system. [Netdata adds charts that document this normalization](https://london.my-netdata.io/default.html#menu_netdata_submenu_apps_plugin).
-
-
+the total of the system. [Apps.plugin adds charts that document this normalization](https://london.my-netdata.io/default.html#menu_netdata_submenu_apps_plugin).

+ 237 - 0
src/collectors/apps.plugin/apps_aggregations.c

@@ -0,0 +1,237 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "apps_plugin.h"
+
+// ----------------------------------------------------------------------------
+// update statistics on the targets
+
+static size_t zero_all_targets(struct target *root) {
+    struct target *w;
+    size_t count = 0;
+
+    for (w = root; w ; w = w->next) {
+        count++;
+
+        for(size_t f = 0; f < PDF_MAX ;f++)
+            w->values[f] = 0;
+
+        w->uptime_min = 0;
+        w->uptime_max = 0;
+
+#if (PROCESSES_HAVE_FDS == 1)
+        // zero file counters
+        if(w->target_fds) {
+            memset(w->target_fds, 0, sizeof(int) * w->target_fds_size);
+            w->openfds.files = 0;
+            w->openfds.pipes = 0;
+            w->openfds.sockets = 0;
+            w->openfds.inotifies = 0;
+            w->openfds.eventfds = 0;
+            w->openfds.timerfds = 0;
+            w->openfds.signalfds = 0;
+            w->openfds.eventpolls = 0;
+            w->openfds.other = 0;
+
+            w->max_open_files_percent = 0.0;
+        }
+#endif
+
+        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;
+                freez(pid_on_target_to_free);
+            }
+
+            w->root_pid = NULL;
+        }
+    }
+
+    return count;
+}
+
+static inline void aggregate_pid_on_target(struct target *w, struct pid_stat *p, struct target *o __maybe_unused) {
+    if(unlikely(!p->updated)) {
+        // the process is not running
+        return;
+    }
+
+    if(unlikely(!w)) {
+        netdata_log_error("pid %d %s was left without a target!", p->pid, pid_stat_comm(p));
+        return;
+    }
+
+#if (PROCESSES_HAVE_FDS == 1) && (PROCESSES_HAVE_PID_LIMITS == 1)
+    if(p->openfds_limits_percent > w->max_open_files_percent)
+        w->max_open_files_percent = p->openfds_limits_percent;
+#endif
+
+    for(size_t f = 0; f < PDF_MAX ;f++)
+        w->values[f] += p->values[f];
+
+    if(!w->uptime_min || p->values[PDF_UPTIME] < w->uptime_min) w->uptime_min = p->values[PDF_UPTIME];
+    if(!w->uptime_max || w->uptime_max < p->values[PDF_UPTIME]) w->uptime_max = p->values[PDF_UPTIME];
+
+    if(unlikely(debug_enabled || w->debug_enabled)) {
+        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;
+    }
+}
+
+static inline void cleanup_exited_pids(void) {
+    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, pid_stat_comm(p));
+
+#if (PROCESSES_HAVE_FDS == 1)
+            for(size_t c = 0; c < p->fds_size; c++)
+                if(p->fds[c].fd > 0) {
+                    file_descriptor_not_used(p->fds[c].fd);
+                    clear_pid_fd(&p->fds[c]);
+                }
+#endif
+
+            const pid_t r = p->pid;
+            p = p->next;
+            del_pid_entry(r);
+        }
+        else {
+            if(unlikely(p->keep)) p->keeploops++;
+            p->keep = false;
+            p = p->next;
+        }
+    }
+}
+
+static struct target *get_app_group_target_for_pid(struct pid_stat *p) {
+    targets_assignment_counter++;
+
+    for(struct target *w = apps_groups_root_target; w ; w = w->next) {
+        if(w->type != TARGET_TYPE_APP_GROUP) continue;
+
+        // 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->compare == p->comm)
+                      || (w->starts_with && !w->ends_with && string_starts_with_string(p->comm, w->compare))
+                      || (!w->starts_with && w->ends_with && string_ends_with_string(p->comm, w->compare))
+                      || (proc_pid_cmdline_is_needed && w->starts_with && w->ends_with && strstr(pid_stat_cmdline(p), string2str(w->compare)))
+                          ))) {
+
+            p->matched_by_config = true;
+            if(w->target) return w->target;
+            else return w;
+        }
+    }
+
+    return NULL;
+}
+
+static void assign_a_target_to_all_processes(void) {
+    // assign targets from app_groups.conf
+    for(struct pid_stat *p = root_of_pids(); p ; p = p->next) {
+        if(!p->target)
+            p->target = get_app_group_target_for_pid(p);
+    }
+
+    // assign targets from their parents, if they have
+    for(struct pid_stat *p = root_of_pids(); p ; p = p->next) {
+        if(!p->target) {
+            for(struct pid_stat *pp = p->parent ; pp ; pp = pp->parent) {
+                if(pp->target) {
+                    if(pp->matched_by_config) {
+                        // we are only interested about app_groups.conf matches
+                        p->target = pp->target;
+                    }
+                    break;
+                }
+            }
+
+            if(!p->target) {
+                // there is no target, get it from the tree
+                p->target = get_tree_target(p);
+            }
+        }
+
+        fatal_assert(p->target != NULL);
+    }
+}
+
+void aggregate_processes_to_targets(void) {
+    assign_a_target_to_all_processes();
+    apps_groups_targets_count = zero_all_targets(apps_groups_root_target);
+
+#if (PROCESSES_HAVE_UID == 1)
+    zero_all_targets(users_root_target);
+#endif
+#if (PROCESSES_HAVE_GID == 1)
+    zero_all_targets(groups_root_target);
+#endif
+
+    // this has to be done, before the cleanup
+    struct target *w = NULL, *o = NULL;
+
+    // concentrate everything on the targets
+    for(struct pid_stat *p = root_of_pids(); p ; p = p->next) {
+
+        // --------------------------------------------------------------------
+        // apps_groups and tree target
+
+        aggregate_pid_on_target(p->target, p, NULL);
+
+
+        // --------------------------------------------------------------------
+        // user target
+
+#if (PROCESSES_HAVE_UID == 1)
+        o = p->uid_target;
+        if(likely(p->uid_target && p->uid_target->uid == p->uid))
+            w = p->uid_target;
+        else {
+            if(unlikely(debug_enabled && p->uid_target))
+                debug_log("pid %d (%s) switched user from %u (%s) to %u.", p->pid, pid_stat_comm(p), p->uid_target->uid, p->uid_target->name, p->uid);
+
+            w = p->uid_target = get_uid_target(p->uid);
+        }
+
+        aggregate_pid_on_target(w, p, o);
+#endif
+
+        // --------------------------------------------------------------------
+        // user group target
+
+#if (PROCESSES_HAVE_GID == 1)
+        o = p->gid_target;
+        if(likely(p->gid_target && p->gid_target->gid == p->gid))
+            w = p->gid_target;
+        else {
+            if(unlikely(debug_enabled && p->gid_target))
+                debug_log("pid %d (%s) switched group from %u (%s) to %u.", p->pid, pid_stat_comm(p), p->gid_target->gid, p->gid_target->name, p->gid);
+
+            w = p->gid_target = get_gid_target(p->gid);
+        }
+
+        aggregate_pid_on_target(w, p, o);
+#endif
+
+        // --------------------------------------------------------------------
+        // aggregate all file descriptors
+
+#if (PROCESSES_HAVE_FDS == 1)
+        if(enable_file_charts)
+            aggregate_pid_fds_on_targets(p);
+#endif
+    }
+
+    cleanup_exited_pids();
+}

+ 278 - 86
src/collectors/apps.plugin/apps_functions.c

@@ -24,24 +24,35 @@ static void apps_plugin_function_processes_help(const char *transaction) {
                    "   category:NAME\n"
                    "      Shows only processes that are assigned the category `NAME` in apps_groups.conf\n"
                    "\n"
+                   "   parent:NAME\n"
+                   "      Shows only processes that are aggregated under parent `NAME`\n"
+                   "\n"
+#if (PROCESSES_HAVE_UID == 1)
                    "   user:NAME\n"
                    "      Shows only processes that are running as user name `NAME`.\n"
                    "\n"
+#endif
+#if (PROCESSES_HAVE_GID == 1)
                    "   group:NAME\n"
                    "      Shows only processes that are running as group name `NAME`.\n"
                    "\n"
+#endif
                    "   process:NAME\n"
                    "      Shows only processes that their Command is `NAME` or their parent's Command is `NAME`.\n"
                    "\n"
                    "   pid:NUMBER\n"
                    "      Shows only processes that their PID is `NUMBER` or their parent's PID is `NUMBER`\n"
                    "\n"
+#if (PROCESSES_HAVE_UID == 1)
                    "   uid:NUMBER\n"
                    "      Shows only processes that their UID is `NUMBER`\n"
                    "\n"
+#endif
+#if (PROCESSES_HAVE_GID == 1)
                    "   gid:NUMBER\n"
                    "      Shows only processes that their GID is `NUMBER`\n"
                    "\n"
+#endif
                    "Filters can be combined. Each filter can be given only one time.\n"
     );
 
@@ -72,21 +83,20 @@ void function_processes(const char *transaction, char *function,
     struct pid_stat *p;
 
     bool show_cmdline = http_access_user_has_enough_access_level_for_endpoint(
-                            access,
-                            HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_SENSITIVE_DATA |
-                                HTTP_ACCESS_VIEW_AGENT_CONFIG) || enable_function_cmdline;
+            access, HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_SENSITIVE_DATA | HTTP_ACCESS_VIEW_AGENT_CONFIG) || enable_function_cmdline;
 
     char *words[PLUGINSD_MAX_WORDS] = { NULL };
     size_t num_words = quoted_strings_splitter_pluginsd(function, words, PLUGINSD_MAX_WORDS);
 
-    struct target *category = NULL, *user = NULL, *group = NULL;
+    struct target *category = NULL, *user = NULL, *group = NULL; (void)category; (void)user; (void)group;
     const char *process_name = NULL;
     pid_t pid = 0;
-    uid_t uid = 0;
-    gid_t gid = 0;
+    uid_t uid = 0; (void)uid;
+    gid_t gid = 0; (void)gid;
     bool info = false;
 
     bool filter_pid = false, filter_uid = false, filter_gid = false;
+    (void)filter_uid; (void)filter_gid;
 
     for(int i = 1; i < PLUGINSD_MAX_WORDS ;i++) {
         const char *keyword = get_word(words, num_words, i);
@@ -100,6 +110,7 @@ void function_processes(const char *transaction, char *function,
                 return;
             }
         }
+#if (PROCESSES_HAVE_UID == 1)
         else if(!user && strncmp(keyword, PROCESS_FILTER_USER, strlen(PROCESS_FILTER_USER)) == 0) {
             user = find_target_by_name(users_root_target, &keyword[strlen(PROCESS_FILTER_USER)]);
             if(!user) {
@@ -108,6 +119,8 @@ void function_processes(const char *transaction, char *function,
                 return;
             }
         }
+#endif
+#if (PROCESSES_HAVE_GID == 1)
         else if(strncmp(keyword, PROCESS_FILTER_GROUP, strlen(PROCESS_FILTER_GROUP)) == 0) {
             group = find_target_by_name(groups_root_target, &keyword[strlen(PROCESS_FILTER_GROUP)]);
             if(!group) {
@@ -116,6 +129,7 @@ void function_processes(const char *transaction, char *function,
                 return;
             }
         }
+#endif
         else if(!process_name && strncmp(keyword, PROCESS_FILTER_PROCESS, strlen(PROCESS_FILTER_PROCESS)) == 0) {
             process_name = &keyword[strlen(PROCESS_FILTER_PROCESS)];
         }
@@ -123,14 +137,18 @@ void function_processes(const char *transaction, char *function,
             pid = str2i(&keyword[strlen(PROCESS_FILTER_PID)]);
             filter_pid = true;
         }
+#if (PROCESSES_HAVE_UID == 1)
         else if(!uid && strncmp(keyword, PROCESS_FILTER_UID, strlen(PROCESS_FILTER_UID)) == 0) {
             uid = str2i(&keyword[strlen(PROCESS_FILTER_UID)]);
             filter_uid = true;
         }
+#endif
+#if (PROCESSES_HAVE_GID == 1)
         else if(!gid && strncmp(keyword, PROCESS_FILTER_GID, strlen(PROCESS_FILTER_GID)) == 0) {
             gid = str2i(&keyword[strlen(PROCESS_FILTER_GID)]);
             filter_gid = true;
         }
+#endif
         else if(strcmp(keyword, "help") == 0) {
             apps_plugin_function_processes_help(transaction);
             return;
@@ -140,10 +158,6 @@ void function_processes(const char *transaction, char *function,
         }
     }
 
-    unsigned int cpu_divisor = time_factor * RATES_DETAIL / 100;
-    unsigned int memory_divisor = 1024;
-    unsigned int io_divisor = 1024 * RATES_DETAIL;
-
     BUFFER *wb = buffer_create(4096, NULL);
     buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);
     buffer_json_member_add_uint64(wb, "status", HTTP_RESP_OK);
@@ -156,38 +170,71 @@ void function_processes(const char *transaction, char *function,
     if(info)
         goto close_and_send;
 
+    uint64_t cpu_divisor = NSEC_PER_SEC / 100;
+    unsigned int memory_divisor = 1024 * 1024;
+    unsigned int io_divisor = 1024 * RATES_DETAIL;
+
+    uint64_t total_memory_bytes = OS_FUNCTION(apps_os_get_total_memory)();
+
     NETDATA_DOUBLE
-    UserCPU_max = 0.0
+          UserCPU_max = 0.0
         , SysCPU_max = 0.0
+#if (PROCESSES_HAVE_CPU_GUEST_TIME == 1)
         , GuestCPU_max = 0.0
+#endif
+#if (PROCESSES_HAVE_CPU_CHILDREN_TIME == 1)
         , CUserCPU_max = 0.0
         , CSysCPU_max = 0.0
+#if (PROCESSES_HAVE_CPU_GUEST_TIME == 1)
         , CGuestCPU_max = 0.0
+#endif
+#endif
         , CPU_max = 0.0
         , VMSize_max = 0.0
         , RSS_max = 0.0
+#if (PROCESSES_HAVE_VMSHARED == 1)
         , Shared_max = 0.0
+#endif
         , Swap_max = 0.0
         , Memory_max = 0.0
+#if (PROCESSES_HAVE_FDS == 1) && (PROCESSES_HAVE_PID_LIMITS == 1)
         , FDsLimitPercent_max = 0.0
+#endif
         ;
 
     unsigned long long
         Processes_max = 0
         , Threads_max = 0
+#if (PROCESSES_HAVE_VOLCTX == 1)
         , VoluntaryCtxtSwitches_max = 0
+#endif
+#if (PROCESSES_HAVE_NVOLCTX == 1)
         , NonVoluntaryCtxtSwitches_max = 0
+#endif
         , Uptime_max = 0
         , MinFlt_max = 0
-        , CMinFlt_max = 0
-        , TMinFlt_max = 0
+#if (PROCESSES_HAVE_MAJFLT == 1)
         , MajFlt_max = 0
+#endif
+#if (PROCESSES_HAVE_CHILDREN_FLTS == 1)
+        , CMinFlt_max = 0
         , CMajFlt_max = 0
+        , TMinFlt_max = 0
         , TMajFlt_max = 0
+#endif
+#if (PROCESSES_HAVE_LOGICAL_IO == 1)
+        , LReads_max = 0
+        , LWrites_max = 0
+#endif
+#if (PROCESSES_HAVE_PHYSICAL_IO == 1)
         , PReads_max = 0
         , PWrites_max = 0
-        , RCalls_max = 0
-        , WCalls_max = 0
+#endif
+#if (PROCESSES_HAVE_IO_CALLS == 1)
+        , ROps_max = 0
+        , WOps_max = 0
+#endif
+#if (PROCESSES_HAVE_FDS == 1)
         , Files_max = 0
         , Pipes_max = 0
         , Sockets_max = 0
@@ -198,40 +245,47 @@ void function_processes(const char *transaction, char *function,
         , EvPollFDs_max = 0
         , OtherFDs_max = 0
         , FDs_max = 0
+#endif
+#if (PROCESSES_HAVE_HANDLES == 1)
+        , Handles_max = 0
+#endif
         ;
 
-#if !defined(__FreeBSD__) && !defined(__APPLE__)
-    unsigned long long
-        LReads_max = 0
-        , LWrites_max = 0
-        ;
-#endif // !__FreeBSD__ !__APPLE_
+    netdata_mutex_lock(&apps_and_stdout_mutex);
 
     int rows= 0;
-    for(p = root_of_pids; p ; p = p->next) {
+    for(p = root_of_pids(); p ; p = p->next) {
         if(!p->updated)
             continue;
 
         if(category && p->target != category)
             continue;
 
-        if(user && p->user_target != user)
+#if (PROCESSES_HAVE_UID == 1)
+        if(user && p->uid_target != user)
             continue;
+#endif
 
-        if(group && p->group_target != group)
+#if (PROCESSES_HAVE_GID == 1)
+        if(group && p->gid_target != group)
             continue;
+#endif
 
-        if(process_name && ((strcmp(p->comm, process_name) != 0 && !p->parent) || (p->parent && strcmp(p->comm, process_name) != 0 && strcmp(p->parent->comm, process_name) != 0)))
+        if(process_name && ((strcmp(pid_stat_comm(p), process_name) != 0 && !p->parent) || (p->parent && strcmp(pid_stat_comm(p), process_name) != 0 && strcmp(pid_stat_comm(p->parent), process_name) != 0)))
             continue;
 
         if(filter_pid && p->pid != pid && p->ppid != pid)
             continue;
 
+#if (PROCESSES_HAVE_UID == 1)
         if(filter_uid && p->uid != uid)
             continue;
+#endif
 
+#if (PROCESSES_HAVE_GID == 1)
         if(filter_gid && p->gid != gid)
             continue;
+#endif
 
         rows++;
 
@@ -244,80 +298,126 @@ void function_processes(const char *transaction, char *function,
         buffer_json_add_array_item_uint64(wb, p->pid);
 
         // cmd
-        buffer_json_add_array_item_string(wb, p->comm);
+        buffer_json_add_array_item_string(wb, string2str(p->comm));
+
+#if (PROCESSES_HAVE_COMM_AND_NAME == 1)
+        // name
+        buffer_json_add_array_item_string(wb, string2str(p->name ? p->name : p->comm));
+#endif
 
         // cmdline
         if (show_cmdline) {
-            buffer_json_add_array_item_string(wb, (p->cmdline && *p->cmdline) ? p->cmdline : p->comm);
+            buffer_json_add_array_item_string(wb, (string_strlen(p->cmdline)) ? pid_stat_cmdline(p) : pid_stat_comm(p));
         }
 
         // ppid
         buffer_json_add_array_item_uint64(wb, p->ppid);
 
         // category
-        buffer_json_add_array_item_string(wb, p->target ? p->target->name : "-");
+        buffer_json_add_array_item_string(wb, p->target ? string2str(p->target->name) : "-");
 
+#if (PROCESSES_HAVE_UID == 1)
         // user
-        buffer_json_add_array_item_string(wb, p->user_target ? p->user_target->name : "-");
+        buffer_json_add_array_item_string(wb, p->uid_target ? string2str(p->uid_target->name) : "-");
 
         // uid
         buffer_json_add_array_item_uint64(wb, p->uid);
+#endif
 
+#if (PROCESSES_HAVE_GID == 1)
         // group
-        buffer_json_add_array_item_string(wb, p->group_target ? p->group_target->name : "-");
+        buffer_json_add_array_item_string(wb, p->gid_target ? string2str(p->gid_target->name) : "-");
 
         // gid
         buffer_json_add_array_item_uint64(wb, p->gid);
+#endif
 
         // CPU utilization %
-        add_value_field_ndd_with_max(wb, CPU, (NETDATA_DOUBLE)(p->utime + p->stime + p->gtime + p->cutime + p->cstime + p->cgtime) / cpu_divisor);
-        add_value_field_ndd_with_max(wb, UserCPU, (NETDATA_DOUBLE)(p->utime) / cpu_divisor);
-        add_value_field_ndd_with_max(wb, SysCPU, (NETDATA_DOUBLE)(p->stime) / cpu_divisor);
-        add_value_field_ndd_with_max(wb, GuestCPU, (NETDATA_DOUBLE)(p->gtime) / cpu_divisor);
-        add_value_field_ndd_with_max(wb, CUserCPU, (NETDATA_DOUBLE)(p->cutime) / cpu_divisor);
-        add_value_field_ndd_with_max(wb, CSysCPU, (NETDATA_DOUBLE)(p->cstime) / cpu_divisor);
-        add_value_field_ndd_with_max(wb, CGuestCPU, (NETDATA_DOUBLE)(p->cgtime) / cpu_divisor);
+        kernel_uint_t total_cpu = p->values[PDF_UTIME] + p->values[PDF_STIME];
 
-        add_value_field_llu_with_max(wb, VoluntaryCtxtSwitches, p->status_voluntary_ctxt_switches / RATES_DETAIL);
-        add_value_field_llu_with_max(wb, NonVoluntaryCtxtSwitches, p->status_nonvoluntary_ctxt_switches / RATES_DETAIL);
+#if (PROCESSES_HAVE_CPU_GUEST_TIME)
+        total_cpu += p->values[PDF_GTIME];
+#endif
+#if (PROCESSES_HAVE_CPU_CHILDREN_TIME)
+        total_cpu += p->values[PDF_CUTIME] + p->values[PDF_CSTIME];
+#if (PROCESSES_HAVE_CPU_GUEST_TIME)
+        total_cpu += p->values[PDF_CGTIME];
+#endif
+#endif
+        add_value_field_ndd_with_max(wb, CPU, (NETDATA_DOUBLE)(total_cpu) / cpu_divisor);
+        add_value_field_ndd_with_max(wb, UserCPU, (NETDATA_DOUBLE)(p->values[PDF_UTIME]) / cpu_divisor);
+        add_value_field_ndd_with_max(wb, SysCPU, (NETDATA_DOUBLE)(p->values[PDF_STIME]) / cpu_divisor);
+#if (PROCESSES_HAVE_CPU_GUEST_TIME)
+        add_value_field_ndd_with_max(wb, GuestCPU, (NETDATA_DOUBLE)(p->values[PDF_GTIME]) / cpu_divisor);
+#endif
+#if (PROCESSES_HAVE_CPU_CHILDREN_TIME)
+        add_value_field_ndd_with_max(wb, CUserCPU, (NETDATA_DOUBLE)(p->values[PDF_CUTIME]) / cpu_divisor);
+        add_value_field_ndd_with_max(wb, CSysCPU, (NETDATA_DOUBLE)(p->values[PDF_CSTIME]) / cpu_divisor);
+#if (PROCESSES_HAVE_CPU_GUEST_TIME)
+        add_value_field_ndd_with_max(wb, CGuestCPU, (NETDATA_DOUBLE)(p->values[PDF_CGTIME]) / cpu_divisor);
+#endif
+#endif
+
+#if (PROCESSES_HAVE_VOLCTX == 1)
+        add_value_field_llu_with_max(wb, VoluntaryCtxtSwitches, p->values[PDF_VOLCTX] / RATES_DETAIL);
+#endif
+#if (PROCESSES_HAVE_NVOLCTX == 1)
+        add_value_field_llu_with_max(wb, NonVoluntaryCtxtSwitches, p->values[PDF_NVOLCTX] / RATES_DETAIL);
+#endif
 
         // memory MiB
-        if(MemTotal)
-            add_value_field_ndd_with_max(wb, Memory, (NETDATA_DOUBLE)p->status_vmrss * 100.0 / (NETDATA_DOUBLE)MemTotal);
+        if(total_memory_bytes)
+            add_value_field_ndd_with_max(wb, Memory, (NETDATA_DOUBLE)p->values[PDF_VMRSS] * 100.0 / (NETDATA_DOUBLE)total_memory_bytes);
+
+        add_value_field_ndd_with_max(wb, RSS, (NETDATA_DOUBLE)p->values[PDF_VMRSS] / memory_divisor);
 
-        add_value_field_ndd_with_max(wb, RSS, (NETDATA_DOUBLE)p->status_vmrss / memory_divisor);
-        add_value_field_ndd_with_max(wb, Shared, (NETDATA_DOUBLE)p->status_vmshared / memory_divisor);
-#if !defined(__APPLE__)
-        add_value_field_ndd_with_max(wb, VMSize, (NETDATA_DOUBLE)p->status_vmsize / memory_divisor);
+#if (PROCESSES_HAVE_VMSHARED == 1)
+        add_value_field_ndd_with_max(wb, Shared, (NETDATA_DOUBLE)p->values[PDF_VMSHARED] / memory_divisor);
 #endif
-        add_value_field_ndd_with_max(wb, Swap, (NETDATA_DOUBLE)p->status_vmswap / memory_divisor);
 
+        add_value_field_ndd_with_max(wb, VMSize, (NETDATA_DOUBLE)p->values[PDF_VMSIZE] / memory_divisor);
+#if (PROCESSES_HAVE_VMSWAP == 1)
+        add_value_field_ndd_with_max(wb, Swap, (NETDATA_DOUBLE)p->values[PDF_VMSWAP] / memory_divisor);
+#endif
+
+#if (PROCESSES_HAVE_PHYSICAL_IO == 1)
         // Physical I/O
-        add_value_field_llu_with_max(wb, PReads, p->io_storage_bytes_read / io_divisor);
-        add_value_field_llu_with_max(wb, PWrites, p->io_storage_bytes_written / io_divisor);
+        add_value_field_llu_with_max(wb, PReads, p->values[PDF_PREAD] / io_divisor);
+        add_value_field_llu_with_max(wb, PWrites, p->values[PDF_PWRITE] / io_divisor);
+#endif
 
+#if (PROCESSES_HAVE_LOGICAL_IO == 1)
         // Logical I/O
-#if !defined(__FreeBSD__) && !defined(__APPLE__)
-        add_value_field_llu_with_max(wb, LReads, p->io_logical_bytes_read / io_divisor);
-        add_value_field_llu_with_max(wb, LWrites, p->io_logical_bytes_written / io_divisor);
+        add_value_field_llu_with_max(wb, LReads, p->values[PDF_LREAD] / io_divisor);
+        add_value_field_llu_with_max(wb, LWrites, p->values[PDF_LWRITE] / io_divisor);
 #endif
 
+#if (PROCESSES_HAVE_IO_CALLS == 1)
         // I/O calls
-        add_value_field_llu_with_max(wb, RCalls, p->io_read_calls / RATES_DETAIL);
-        add_value_field_llu_with_max(wb, WCalls, p->io_write_calls / RATES_DETAIL);
+        add_value_field_llu_with_max(wb, ROps, p->values[PDF_OREAD] / RATES_DETAIL);
+        add_value_field_llu_with_max(wb, WOps, p->values[PDF_OWRITE] / RATES_DETAIL);
+#endif
 
         // minor page faults
-        add_value_field_llu_with_max(wb, MinFlt, p->minflt / RATES_DETAIL);
-        add_value_field_llu_with_max(wb, CMinFlt, p->cminflt / RATES_DETAIL);
-        add_value_field_llu_with_max(wb, TMinFlt, (p->minflt + p->cminflt) / RATES_DETAIL);
+        add_value_field_llu_with_max(wb, MinFlt, p->values[PDF_MINFLT] / RATES_DETAIL);
 
+#if (PROCESSES_HAVE_MAJFLT == 1)
         // major page faults
-        add_value_field_llu_with_max(wb, MajFlt, p->majflt / RATES_DETAIL);
-        add_value_field_llu_with_max(wb, CMajFlt, p->cmajflt / RATES_DETAIL);
-        add_value_field_llu_with_max(wb, TMajFlt, (p->majflt + p->cmajflt) / RATES_DETAIL);
+        add_value_field_llu_with_max(wb, MajFlt, p->values[PDF_MAJFLT] / RATES_DETAIL);
+#endif
+
+#if (PROCESSES_HAVE_CHILDREN_FLTS == 1)
+        add_value_field_llu_with_max(wb, CMinFlt, p->values[PDF_CMINFLT] / RATES_DETAIL);
+        add_value_field_llu_with_max(wb, CMajFlt, p->values[PDF_CMAJFLT] / RATES_DETAIL);
+        add_value_field_llu_with_max(wb, TMinFlt, (p->values[PDF_MINFLT] + p->values[PDF_CMINFLT]) / RATES_DETAIL);
+        add_value_field_llu_with_max(wb, TMajFlt, (p->values[PDF_MAJFLT] + p->values[PDF_CMAJFLT]) / RATES_DETAIL);
+#endif
 
+#if (PROCESSES_HAVE_FDS == 1)
         // open file descriptors
+#if (PROCESSES_HAVE_PID_LIMITS == 1)
         add_value_field_ndd_with_max(wb, FDsLimitPercent, p->openfds_limits_percent);
+#endif
         add_value_field_llu_with_max(wb, FDs, pid_openfds_sum(p));
         add_value_field_llu_with_max(wb, Files, p->openfds.files);
         add_value_field_llu_with_max(wb, Pipes, p->openfds.pipes);
@@ -328,12 +428,16 @@ void function_processes(const char *transaction, char *function,
         add_value_field_llu_with_max(wb, SigFDs, p->openfds.signalfds);
         add_value_field_llu_with_max(wb, EvPollFDs, p->openfds.eventpolls);
         add_value_field_llu_with_max(wb, OtherFDs, p->openfds.other);
+#endif
 
+#if (PROCESSES_HAVE_HANDLES == 1)
+        add_value_field_llu_with_max(wb, Handles, p->values[PDF_HANDLES]);
+#endif
 
         // processes, threads, uptime
-        add_value_field_llu_with_max(wb, Processes, p->children_count);
-        add_value_field_llu_with_max(wb, Threads, p->num_threads);
-        add_value_field_llu_with_max(wb, Uptime, p->uptime);
+        add_value_field_llu_with_max(wb, Processes, p->values[PDF_PROCESSES]);
+        add_value_field_llu_with_max(wb, Threads, p->values[PDF_THREADS]);
+        add_value_field_llu_with_max(wb, Uptime, p->values[PDF_UPTIME]);
 
         buffer_json_array_close(wb); // for each pid
     }
@@ -360,6 +464,14 @@ void function_processes(const char *transaction, char *function,
                                     RRDF_FIELD_FILTER_MULTISELECT,
                                     RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_STICKY, NULL);
 
+#if (PROCESSES_HAVE_COMM_AND_NAME == 1)
+        buffer_rrdf_table_add_field(wb, field_id++, "Name", "Process Friendly Name", RRDF_FIELD_TYPE_STRING,
+                                    RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, 0, NULL, NAN,
+                                    RRDF_FIELD_SORT_ASCENDING, NULL, RRDF_FIELD_SUMMARY_COUNT,
+                                    RRDF_FIELD_FILTER_MULTISELECT,
+                                    RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_STICKY, NULL);
+#endif
+
         if (show_cmdline) {
             buffer_rrdf_table_add_field(wb, field_id++, "CmdLine", "Command Line", RRDF_FIELD_TYPE_STRING,
                                         RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, 0,
@@ -373,12 +485,15 @@ void function_processes(const char *transaction, char *function,
                                     NAN, RRDF_FIELD_SORT_ASCENDING, "PID", RRDF_FIELD_SUMMARY_COUNT,
                                     RRDF_FIELD_FILTER_MULTISELECT,
                                     RRDF_FIELD_OPTS_NONE, NULL);
+
         buffer_rrdf_table_add_field(wb, field_id++, "Category", "Category (apps_groups.conf)", RRDF_FIELD_TYPE_STRING,
                                     RRDF_FIELD_VISUAL_VALUE,
                                     RRDF_FIELD_TRANSFORM_NONE,
                                     0, NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL, RRDF_FIELD_SUMMARY_COUNT,
                                     RRDF_FIELD_FILTER_MULTISELECT,
                                     RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_STICKY, NULL);
+
+#if (PROCESSES_HAVE_UID == 1)
         buffer_rrdf_table_add_field(wb, field_id++, "User", "User Owner", RRDF_FIELD_TYPE_STRING,
                                     RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, 0, NULL, NAN,
                                     RRDF_FIELD_SORT_ASCENDING, NULL, RRDF_FIELD_SUMMARY_COUNT,
@@ -389,6 +504,9 @@ void function_processes(const char *transaction, char *function,
                                     RRDF_FIELD_SORT_ASCENDING, NULL, RRDF_FIELD_SUMMARY_COUNT,
                                     RRDF_FIELD_FILTER_MULTISELECT,
                                     RRDF_FIELD_OPTS_NONE, NULL);
+#endif
+
+#if (PROCESSES_HAVE_GID == 1)
         buffer_rrdf_table_add_field(wb, field_id++, "Group", "Group Owner", RRDF_FIELD_TYPE_STRING,
                                     RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, 0, NULL, NAN,
                                     RRDF_FIELD_SORT_ASCENDING, NULL, RRDF_FIELD_SUMMARY_COUNT,
@@ -399,6 +517,7 @@ void function_processes(const char *transaction, char *function,
                                     RRDF_FIELD_SORT_ASCENDING, NULL, RRDF_FIELD_SUMMARY_COUNT,
                                     RRDF_FIELD_FILTER_MULTISELECT,
                                     RRDF_FIELD_OPTS_NONE, NULL);
+#endif
 
         // CPU utilization
         buffer_rrdf_table_add_field(wb, field_id++, "CPU", "Total CPU Time (100% = 1 core)",
@@ -416,11 +535,14 @@ void function_processes(const char *transaction, char *function,
                                     RRDF_FIELD_VISUAL_BAR, RRDF_FIELD_TRANSFORM_NUMBER, 2, "%", SysCPU_max,
                                     RRDF_FIELD_SORT_DESCENDING, NULL, RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
                                     RRDF_FIELD_OPTS_NONE, NULL);
+#if (PROCESSES_HAVE_CPU_GUEST_TIME == 1)
         buffer_rrdf_table_add_field(wb, field_id++, "GuestCPU", "Guest CPU Time (100% = 1 core)",
                                     RRDF_FIELD_TYPE_BAR_WITH_INTEGER,
                                     RRDF_FIELD_VISUAL_BAR, RRDF_FIELD_TRANSFORM_NUMBER, 2, "%", GuestCPU_max,
                                     RRDF_FIELD_SORT_DESCENDING, NULL, RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
                                     RRDF_FIELD_OPTS_NONE, NULL);
+#endif
+#if (PROCESSES_HAVE_CPU_CHILDREN_TIME == 1)
         buffer_rrdf_table_add_field(wb, field_id++, "CUserCPU", "Children User CPU Time (100% = 1 core)",
                                     RRDF_FIELD_TYPE_BAR_WITH_INTEGER, RRDF_FIELD_VISUAL_BAR,
                                     RRDF_FIELD_TRANSFORM_NUMBER, 2, "%", CUserCPU_max, RRDF_FIELD_SORT_DESCENDING, NULL,
@@ -431,26 +553,33 @@ void function_processes(const char *transaction, char *function,
                                     RRDF_FIELD_TRANSFORM_NUMBER, 2, "%", CSysCPU_max, RRDF_FIELD_SORT_DESCENDING, NULL,
                                     RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
                                     RRDF_FIELD_OPTS_NONE, NULL);
+#if (PROCESSES_HAVE_CPU_GUEST_TIME == 1)
         buffer_rrdf_table_add_field(wb, field_id++, "CGuestCPU", "Children Guest CPU Time (100% = 1 core)",
                                     RRDF_FIELD_TYPE_BAR_WITH_INTEGER, RRDF_FIELD_VISUAL_BAR,
                                     RRDF_FIELD_TRANSFORM_NUMBER, 2, "%", CGuestCPU_max, RRDF_FIELD_SORT_DESCENDING,
                                     NULL,
                                     RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE, RRDF_FIELD_OPTS_NONE, NULL);
+#endif
+#endif
 
+#if (PROCESSES_HAVE_VOLCTX == 1)
         // CPU context switches
         buffer_rrdf_table_add_field(wb, field_id++, "vCtxSwitch", "Voluntary Context Switches",
                                     RRDF_FIELD_TYPE_BAR_WITH_INTEGER,
                                     RRDF_FIELD_VISUAL_BAR, RRDF_FIELD_TRANSFORM_NUMBER, 2, "switches/s",
                                     VoluntaryCtxtSwitches_max, RRDF_FIELD_SORT_DESCENDING, NULL,
                                     RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE, RRDF_FIELD_OPTS_NONE, NULL);
+#endif
+#if (PROCESSES_HAVE_NVOLCTX == 1)
         buffer_rrdf_table_add_field(wb, field_id++, "iCtxSwitch", "Involuntary Context Switches",
                                     RRDF_FIELD_TYPE_BAR_WITH_INTEGER,
                                     RRDF_FIELD_VISUAL_BAR, RRDF_FIELD_TRANSFORM_NUMBER, 2, "switches/s",
                                     NonVoluntaryCtxtSwitches_max, RRDF_FIELD_SORT_DESCENDING, NULL,
                                     RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE, RRDF_FIELD_OPTS_NONE, NULL);
+#endif
 
         // memory
-        if (MemTotal)
+        if (total_memory_bytes)
             buffer_rrdf_table_add_field(wb, field_id++, "Memory", "Memory Percentage", RRDF_FIELD_TYPE_BAR_WITH_INTEGER,
                                         RRDF_FIELD_VISUAL_BAR,
                                         RRDF_FIELD_TRANSFORM_NUMBER, 2, "%", 100.0, RRDF_FIELD_SORT_DESCENDING, NULL,
@@ -463,25 +592,30 @@ void function_processes(const char *transaction, char *function,
                                     2, "MiB", RSS_max, RRDF_FIELD_SORT_DESCENDING, NULL, RRDF_FIELD_SUMMARY_SUM,
                                     RRDF_FIELD_FILTER_RANGE,
                                     RRDF_FIELD_OPTS_VISIBLE, NULL);
+#if (PROCESSES_HAVE_VMSHARED == 1)
         buffer_rrdf_table_add_field(wb, field_id++, "Shared", "Shared Pages", RRDF_FIELD_TYPE_BAR_WITH_INTEGER,
                                     RRDF_FIELD_VISUAL_BAR, RRDF_FIELD_TRANSFORM_NUMBER, 2,
                                     "MiB", Shared_max, RRDF_FIELD_SORT_DESCENDING, NULL, RRDF_FIELD_SUMMARY_SUM,
                                     RRDF_FIELD_FILTER_RANGE,
                                     RRDF_FIELD_OPTS_VISIBLE, NULL);
-#if !defined(__APPLE__)
+#endif
+
         buffer_rrdf_table_add_field(wb, field_id++, "Virtual", "Virtual Memory Size", RRDF_FIELD_TYPE_BAR_WITH_INTEGER,
                                     RRDF_FIELD_VISUAL_BAR,
                                     RRDF_FIELD_TRANSFORM_NUMBER, 2, "MiB", VMSize_max, RRDF_FIELD_SORT_DESCENDING, NULL,
                                     RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
                                     RRDF_FIELD_OPTS_VISIBLE, NULL);
-#endif
+
+#if (PROCESSES_HAVE_VMSWAP == 1)
         buffer_rrdf_table_add_field(wb, field_id++, "Swap", "Swap Memory", RRDF_FIELD_TYPE_BAR_WITH_INTEGER,
                                     RRDF_FIELD_VISUAL_BAR, RRDF_FIELD_TRANSFORM_NUMBER, 2,
                                     "MiB",
                                     Swap_max, RRDF_FIELD_SORT_DESCENDING, NULL, RRDF_FIELD_SUMMARY_SUM,
                                     RRDF_FIELD_FILTER_RANGE,
                                     RRDF_FIELD_OPTS_NONE, NULL);
+#endif
 
+#if (PROCESSES_HAVE_PHYSICAL_IO == 1)
         // Physical I/O
         buffer_rrdf_table_add_field(wb, field_id++, "PReads", "Physical I/O Reads", RRDF_FIELD_TYPE_BAR_WITH_INTEGER,
                                     RRDF_FIELD_VISUAL_BAR, RRDF_FIELD_TRANSFORM_NUMBER,
@@ -493,33 +627,41 @@ void function_processes(const char *transaction, char *function,
                                     RRDF_FIELD_TRANSFORM_NUMBER, 2, "KiB/s", PWrites_max, RRDF_FIELD_SORT_DESCENDING,
                                     NULL, RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
                                     RRDF_FIELD_OPTS_VISIBLE, NULL);
+#endif
 
+#if (PROCESSES_HAVE_LOGICAL_IO == 1)
+#if (PROCESSES_HAVE_PHYSICAL_IO == 1)
+        RRDF_FIELD_OPTIONS logical_io_options = RRDF_FIELD_OPTS_NONE;
+#else
+        RRDF_FIELD_OPTIONS logical_io_options = RRDF_FIELD_OPTS_VISIBLE;
+#endif
         // Logical I/O
-#if !defined(__FreeBSD__) && !defined(__APPLE__)
         buffer_rrdf_table_add_field(wb, field_id++, "LReads", "Logical I/O Reads", RRDF_FIELD_TYPE_BAR_WITH_INTEGER,
                                     RRDF_FIELD_VISUAL_BAR, RRDF_FIELD_TRANSFORM_NUMBER,
                                     2, "KiB/s", LReads_max, RRDF_FIELD_SORT_DESCENDING, NULL, RRDF_FIELD_SUMMARY_SUM,
                                     RRDF_FIELD_FILTER_RANGE,
-                                    RRDF_FIELD_OPTS_NONE, NULL);
+                                    logical_io_options, NULL);
         buffer_rrdf_table_add_field(wb, field_id++, "LWrites", "Logical I/O Writes", RRDF_FIELD_TYPE_BAR_WITH_INTEGER,
                                     RRDF_FIELD_VISUAL_BAR,
                                     RRDF_FIELD_TRANSFORM_NUMBER,
                                     2, "KiB/s", LWrites_max, RRDF_FIELD_SORT_DESCENDING, NULL, RRDF_FIELD_SUMMARY_SUM,
                                     RRDF_FIELD_FILTER_RANGE,
-                                    RRDF_FIELD_OPTS_NONE, NULL);
+                                    logical_io_options, NULL);
 #endif
 
+#if (PROCESSES_HAVE_IO_CALLS == 1)
         // I/O calls
-        buffer_rrdf_table_add_field(wb, field_id++, "RCalls", "I/O Read Calls", RRDF_FIELD_TYPE_BAR_WITH_INTEGER,
+        buffer_rrdf_table_add_field(wb, field_id++, "ROps", "I/O Read Operations", RRDF_FIELD_TYPE_BAR_WITH_INTEGER,
                                     RRDF_FIELD_VISUAL_BAR, RRDF_FIELD_TRANSFORM_NUMBER, 2,
-                                    "calls/s", RCalls_max, RRDF_FIELD_SORT_DESCENDING, NULL, RRDF_FIELD_SUMMARY_SUM,
+                                    "ops/s", ROps_max, RRDF_FIELD_SORT_DESCENDING, NULL, RRDF_FIELD_SUMMARY_SUM,
                                     RRDF_FIELD_FILTER_RANGE,
                                     RRDF_FIELD_OPTS_NONE, NULL);
-        buffer_rrdf_table_add_field(wb, field_id++, "WCalls", "I/O Write Calls", RRDF_FIELD_TYPE_BAR_WITH_INTEGER,
+        buffer_rrdf_table_add_field(wb, field_id++, "WOps", "I/O Write Operations", RRDF_FIELD_TYPE_BAR_WITH_INTEGER,
                                     RRDF_FIELD_VISUAL_BAR, RRDF_FIELD_TRANSFORM_NUMBER, 2,
-                                    "calls/s", WCalls_max, RRDF_FIELD_SORT_DESCENDING, NULL, RRDF_FIELD_SUMMARY_SUM,
+                                    "ops/s", WOps_max, RRDF_FIELD_SORT_DESCENDING, NULL, RRDF_FIELD_SUMMARY_SUM,
                                     RRDF_FIELD_FILTER_RANGE,
                                     RRDF_FIELD_OPTS_NONE, NULL);
+#endif
 
         // minor page faults
         buffer_rrdf_table_add_field(wb, field_id++, "MinFlt", "Minor Page Faults/s", RRDF_FIELD_TYPE_BAR_WITH_INTEGER,
@@ -528,18 +670,8 @@ void function_processes(const char *transaction, char *function,
                                     2, "pgflts/s", MinFlt_max, RRDF_FIELD_SORT_DESCENDING, NULL, RRDF_FIELD_SUMMARY_SUM,
                                     RRDF_FIELD_FILTER_RANGE,
                                     RRDF_FIELD_OPTS_NONE, NULL);
-        buffer_rrdf_table_add_field(wb, field_id++, "CMinFlt", "Children Minor Page Faults/s",
-                                    RRDF_FIELD_TYPE_BAR_WITH_INTEGER,
-                                    RRDF_FIELD_VISUAL_BAR,
-                                    RRDF_FIELD_TRANSFORM_NUMBER, 2, "pgflts/s", CMinFlt_max, RRDF_FIELD_SORT_DESCENDING,
-                                    NULL, RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
-                                    RRDF_FIELD_OPTS_NONE, NULL);
-        buffer_rrdf_table_add_field(wb, field_id++, "TMinFlt", "Total Minor Page Faults/s",
-                                    RRDF_FIELD_TYPE_BAR_WITH_INTEGER, RRDF_FIELD_VISUAL_BAR,
-                                    RRDF_FIELD_TRANSFORM_NUMBER, 2, "pgflts/s", TMinFlt_max, RRDF_FIELD_SORT_DESCENDING,
-                                    NULL, RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
-                                    RRDF_FIELD_OPTS_NONE, NULL);
 
+#if (PROCESSES_HAVE_MAJFLT == 1)
         // major page faults
         buffer_rrdf_table_add_field(wb, field_id++, "MajFlt", "Major Page Faults/s", RRDF_FIELD_TYPE_BAR_WITH_INTEGER,
                                     RRDF_FIELD_VISUAL_BAR,
@@ -547,24 +679,42 @@ void function_processes(const char *transaction, char *function,
                                     2, "pgflts/s", MajFlt_max, RRDF_FIELD_SORT_DESCENDING, NULL, RRDF_FIELD_SUMMARY_SUM,
                                     RRDF_FIELD_FILTER_RANGE,
                                     RRDF_FIELD_OPTS_NONE, NULL);
+#endif
+
+#if (PROCESSES_HAVE_CHILDREN_FLTS == 1)
+        buffer_rrdf_table_add_field(wb, field_id++, "CMinFlt", "Children Minor Page Faults/s",
+                                    RRDF_FIELD_TYPE_BAR_WITH_INTEGER,
+                                    RRDF_FIELD_VISUAL_BAR,
+                                    RRDF_FIELD_TRANSFORM_NUMBER, 2, "pgflts/s", CMinFlt_max, RRDF_FIELD_SORT_DESCENDING,
+                                    NULL, RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
+                                    RRDF_FIELD_OPTS_NONE, NULL);
         buffer_rrdf_table_add_field(wb, field_id++, "CMajFlt", "Children Major Page Faults/s",
                                     RRDF_FIELD_TYPE_BAR_WITH_INTEGER,
                                     RRDF_FIELD_VISUAL_BAR,
                                     RRDF_FIELD_TRANSFORM_NUMBER, 2, "pgflts/s", CMajFlt_max, RRDF_FIELD_SORT_DESCENDING,
                                     NULL, RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
                                     RRDF_FIELD_OPTS_NONE, NULL);
+        buffer_rrdf_table_add_field(wb, field_id++, "TMinFlt", "Total Minor Page Faults/s",
+                                    RRDF_FIELD_TYPE_BAR_WITH_INTEGER, RRDF_FIELD_VISUAL_BAR,
+                                    RRDF_FIELD_TRANSFORM_NUMBER, 2, "pgflts/s", TMinFlt_max, RRDF_FIELD_SORT_DESCENDING,
+                                    NULL, RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
+                                    RRDF_FIELD_OPTS_NONE, NULL);
         buffer_rrdf_table_add_field(wb, field_id++, "TMajFlt", "Total Major Page Faults/s",
                                     RRDF_FIELD_TYPE_BAR_WITH_INTEGER, RRDF_FIELD_VISUAL_BAR,
                                     RRDF_FIELD_TRANSFORM_NUMBER, 2, "pgflts/s", TMajFlt_max, RRDF_FIELD_SORT_DESCENDING,
                                     NULL, RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
                                     RRDF_FIELD_OPTS_NONE, NULL);
+#endif
 
+#if (PROCESSES_HAVE_FDS == 1)
         // open file descriptors
+#if (PROCESSES_HAVE_PID_LIMITS == 1)
         buffer_rrdf_table_add_field(wb, field_id++, "FDsLimitPercent", "Percentage of Open Descriptors vs Limits",
                                     RRDF_FIELD_TYPE_BAR_WITH_INTEGER, RRDF_FIELD_VISUAL_BAR,
                                     RRDF_FIELD_TRANSFORM_NUMBER, 2, "%", FDsLimitPercent_max, RRDF_FIELD_SORT_DESCENDING, NULL,
                                     RRDF_FIELD_SUMMARY_MAX, RRDF_FIELD_FILTER_RANGE,
                                     RRDF_FIELD_OPTS_NONE, NULL);
+#endif
         buffer_rrdf_table_add_field(wb, field_id++, "FDs", "All Open File Descriptors",
                                     RRDF_FIELD_TYPE_BAR_WITH_INTEGER, RRDF_FIELD_VISUAL_BAR,
                                     RRDF_FIELD_TRANSFORM_NUMBER, 0, "fds", FDs_max, RRDF_FIELD_SORT_DESCENDING, NULL,
@@ -617,6 +767,16 @@ void function_processes(const char *transaction, char *function,
                                     RRDF_FIELD_TRANSFORM_NUMBER, 0, "fds", OtherFDs_max, RRDF_FIELD_SORT_DESCENDING,
                                     NULL, RRDF_FIELD_SUMMARY_SUM, RRDF_FIELD_FILTER_RANGE,
                                     RRDF_FIELD_OPTS_NONE, NULL);
+#endif
+
+#if (PROCESSES_HAVE_HANDLES == 1)
+        buffer_rrdf_table_add_field(wb, field_id++, "Handles", "Open Handles", RRDF_FIELD_TYPE_BAR_WITH_INTEGER,
+                                    RRDF_FIELD_VISUAL_BAR, RRDF_FIELD_TRANSFORM_NUMBER, 0,
+                                    "handles",
+                                    Handles_max, RRDF_FIELD_SORT_DESCENDING, NULL, RRDF_FIELD_SUMMARY_SUM,
+                                    RRDF_FIELD_FILTER_RANGE,
+                                    RRDF_FIELD_OPTS_VISIBLE, NULL);
+#endif
 
         // processes, threads, uptime
         buffer_rrdf_table_add_field(wb, field_id++, "Processes", "Processes", RRDF_FIELD_TYPE_BAR_WITH_INTEGER,
@@ -650,27 +810,39 @@ void function_processes(const char *transaction, char *function,
             {
                 buffer_json_add_array_item_string(wb, "UserCPU");
                 buffer_json_add_array_item_string(wb, "SysCPU");
+#if (PROCESSES_HAVE_CPU_GUEST_TIME == 1)
                 buffer_json_add_array_item_string(wb, "GuestCPU");
+#endif
+#if (PROCESSES_HAVE_CPU_CHILDREN_TIME == 1)
                 buffer_json_add_array_item_string(wb, "CUserCPU");
                 buffer_json_add_array_item_string(wb, "CSysCPU");
+#if (PROCESSES_HAVE_CPU_GUEST_TIME == 1)
                 buffer_json_add_array_item_string(wb, "CGuestCPU");
+#endif
+#endif
             }
             buffer_json_array_close(wb);
         }
         buffer_json_object_close(wb);
 
+#if (PROCESSES_HAVE_VOLCTX == 1) || (PROCESSES_HAVE_NVOLCTX == 1)
         buffer_json_member_add_object(wb, "CPUCtxSwitches");
         {
             buffer_json_member_add_string(wb, "name", "CPU Context Switches");
             buffer_json_member_add_string(wb, "type", "stacked-bar");
             buffer_json_member_add_array(wb, "columns");
             {
+#if (PROCESSES_HAVE_VOLCTX == 1)
                 buffer_json_add_array_item_string(wb, "vCtxSwitch");
+#endif
+#if (PROCESSES_HAVE_NVOLCTX == 1)
                 buffer_json_add_array_item_string(wb, "iCtxSwitch");
+#endif
             }
             buffer_json_array_close(wb);
         }
         buffer_json_object_close(wb);
+#endif
 
         // Memory chart
         buffer_json_member_add_object(wb, "Memory");
@@ -688,7 +860,7 @@ void function_processes(const char *transaction, char *function,
         }
         buffer_json_object_close(wb);
 
-        if(MemTotal) {
+        if(total_memory_bytes) {
             // Memory chart
             buffer_json_member_add_object(wb, "MemoryPercent");
             {
@@ -703,7 +875,7 @@ void function_processes(const char *transaction, char *function,
             buffer_json_object_close(wb);
         }
 
-#if !defined(__FreeBSD__) && !defined(__APPLE__)
+#if (PROCESSES_HAVE_LOGICAL_IO == 1) || (PROCESSES_HAVE_PHYSICAL_IO == 1)
         // I/O Reads chart
         buffer_json_member_add_object(wb, "Reads");
         {
@@ -711,8 +883,12 @@ void function_processes(const char *transaction, char *function,
             buffer_json_member_add_string(wb, "type", "stacked-bar");
             buffer_json_member_add_array(wb, "columns");
             {
+#if (PROCESSES_HAVE_LOGICAL_IO == 1)
                 buffer_json_add_array_item_string(wb, "LReads");
+#endif
+#if (PROCESSES_HAVE_PHYSICAL_IO == 1)
                 buffer_json_add_array_item_string(wb, "PReads");
+#endif
             }
             buffer_json_array_close(wb);
         }
@@ -725,13 +901,19 @@ void function_processes(const char *transaction, char *function,
             buffer_json_member_add_string(wb, "type", "stacked-bar");
             buffer_json_member_add_array(wb, "columns");
             {
+#if (PROCESSES_HAVE_LOGICAL_IO == 1)
                 buffer_json_add_array_item_string(wb, "LWrites");
+#endif
+#if (PROCESSES_HAVE_PHYSICAL_IO == 1)
                 buffer_json_add_array_item_string(wb, "PWrites");
+#endif
             }
             buffer_json_array_close(wb);
         }
         buffer_json_object_close(wb);
+#endif
 
+#if (PROCESSES_HAVE_LOGICAL_IO == 1)
         // Logical I/O chart
         buffer_json_member_add_object(wb, "LogicalIO");
         {
@@ -747,6 +929,7 @@ void function_processes(const char *transaction, char *function,
         buffer_json_object_close(wb);
 #endif
 
+#if (PROCESSES_HAVE_PHYSICAL_IO == 1)
         // Physical I/O chart
         buffer_json_member_add_object(wb, "PhysicalIO");
         {
@@ -760,7 +943,9 @@ void function_processes(const char *transaction, char *function,
             buffer_json_array_close(wb);
         }
         buffer_json_object_close(wb);
+#endif
 
+#if (PROCESSES_HAVE_IO_CALLS == 1)
         // I/O Calls chart
         buffer_json_member_add_object(wb, "IOCalls");
         {
@@ -768,12 +953,13 @@ void function_processes(const char *transaction, char *function,
             buffer_json_member_add_string(wb, "type", "stacked-bar");
             buffer_json_member_add_array(wb, "columns");
             {
-                buffer_json_add_array_item_string(wb, "RCalls");
+                buffer_json_add_array_item_string(wb, "ROps");
                 buffer_json_add_array_item_string(wb, "WCalls");
             }
             buffer_json_array_close(wb);
         }
         buffer_json_object_close(wb);
+#endif
 
         // Minor Page Faults chart
         buffer_json_member_add_object(wb, "MinFlt");
@@ -893,6 +1079,7 @@ void function_processes(const char *transaction, char *function,
         }
         buffer_json_object_close(wb);
 
+#if (PROCESSES_HAVE_UID == 1)
         // group by User
         buffer_json_member_add_object(wb, "User");
         {
@@ -905,7 +1092,9 @@ void function_processes(const char *transaction, char *function,
             buffer_json_array_close(wb);
         }
         buffer_json_object_close(wb);
+#endif
 
+#if (PROCESSES_HAVE_GID == 1)
         // group by Group
         buffer_json_member_add_object(wb, "Group");
         {
@@ -918,9 +1107,12 @@ void function_processes(const char *transaction, char *function,
             buffer_json_array_close(wb);
         }
         buffer_json_object_close(wb);
+#endif
     }
     buffer_json_object_close(wb); // group_by
 
+    netdata_mutex_unlock(&apps_and_stdout_mutex);
+
 close_and_send:
     buffer_json_member_add_time_t(wb, "expires", now_s + update_every);
     buffer_json_finalize(wb);

+ 54 - 368
src/collectors/apps.plugin/apps_groups.conf

@@ -1,90 +1,41 @@
-#
-# apps.plugin process grouping
-#
-# The apps.plugin displays charts with information about the processes running.
-# This config allows grouping processes together, so that several processes
-# will be reported as one.
-#
-# Only groups in this file are reported. All other processes will be reported
-# as 'other'.
-#
-# For each process given, its whole process tree will be grouped, not just
-# the process matched. The plugin will include both parents and childs.
-#
-# The format is:
-#
-#       group: process1 process2 process3 ...
-#
-# Each group can be given multiple times, to add more processes to it.
-#
-# The process names are the ones returned by:
-#
-#  -  ps -e or /proc/PID/stat
-#  -  in case of substring mode (see below): /proc/PID/cmdline
-#
-# To add process names with spaces, enclose them in quotes (single or double)
-# example: 'Plex Media Serv' "my other process".
-#
-# Note that spaces are not supported for process groups. Use a dash "-" instead.
-# example-process-group: process1 process2
-#
-# Wildcard support:
-# You can add an asterisk (*) at the beginning and/or the end of a process:
-#
-#  *name    suffix mode: will search for processes ending with 'name'
-#           (/proc/PID/stat)
-#
-#   name*   prefix mode: will search for processes beginning with 'name'
-#           (/proc/PID/stat)
-#
-#  *name*   substring mode: will search for 'name' in the whole command line
-#           (/proc/PID/cmdline)
-#
-# If you enter even just one *name* (substring), apps.plugin will process
-# /proc/PID/cmdline for all processes, just once (when they are first seen).
-#
-# To add processes with single quotes, enclose them in double quotes
-# example: "process with this ' single quote"
-#
-# To add processes with double quotes, enclose them in single quotes:
-# example: 'process with this " double quote'
-#
-# If a group or process name starts with a -, the dimension will be hidden
-# (cpu chart only).
-#
-# If a process starts with a +, debugging will be enabled for it
-# (debugging produces a lot of output - do not enable it in production systems)
-#
-# You can add any number of groups you like. Only the ones found running will
-# affect the charts generated. However, producing charts with hundreds of
-# dimensions may slow down your web browser.
-#
-# The order of the entries in this list is important: the first that matches
-# a process is used, so put important ones at the top. Processes not matched
-# by any row, will inherit it from their parents or children.
-#
-# The order also controls the order of the dimensions on the generated charts
-# (although applications started after apps.plugin is started, will be appended
-# to the existing list of dimensions the netdata daemon maintains).
+##
+## apps.plugin process grouping
+##
+## Documentation at:
+## https://github.com/netdata/netdata/blob/master/src/collectors/apps.plugin/README.md
+##
+## The list of process managers can be configured here (uncomment and edit):
 
-# -----------------------------------------------------------------------------
-# NETDATA processes accounting
+## Linux
+#managers: init systemd containerd-shim dumb-init gnome-shell docker-init
 
-# netdata main process
-netdata: netdata
+## FreeBSD
+#managers: init
+
+## MacOS
+#managers: launchd
+
+## Windows
+#managers: System services wininit
+
+## -----------------------------------------------------------------------------
+## Processes of interest
 
-# netdata known plugins
-# plugins not defined here will be accumulated in netdata, above
-apps.plugin: apps.plugin
-freeipmi.plugin: freeipmi.plugin
-nfacct.plugin: nfacct.plugin
-cups.plugin: cups.plugin
-xenstat.plugin: xenstat.plugin
-perf.plugin: perf.plugin
+## NETDATA processes accounting
+netdata: netdata
+## netdata known plugins
+## plugins not defined here will be accumulated into netdata, above
+apps.plugin: *apps.plugin*
+freeipmi.plugin: *freeipmi.plugin*
+nfacct.plugin: *nfacct.plugin*
+cups.plugin: *cups.plugin*
+xenstat.plugin: *xenstat.plugin*
+perf.plugin: *perf.plugin*
 charts.d.plugin: *charts.d.plugin*
 python.d.plugin: *python.d.plugin*
 systemd-journal.plugin: *systemd-journal.plugin*
 network-viewer.plugin: *network-viewer.plugin*
+windows-events.plugin: *windows-events.plugin*
 tc-qos-helper: *tc-qos-helper.sh*
 fping: fping
 ioping: ioping
@@ -93,262 +44,79 @@ slabinfo.plugin: *slabinfo.plugin*
 ebpf.plugin: *ebpf.plugin*
 debugfs.plugin: *debugfs.plugin*
 
-# agent-service-discovery
+## agent-service-discovery
 agent_sd: agent_sd
 
-# -----------------------------------------------------------------------------
-# authentication/authorization related servers
-
-auth: radius* openldap* ldap* slapd authelia sssd saslauthd polkitd gssproxy
-fail2ban: fail2ban*
-
-# -----------------------------------------------------------------------------
-# web/ftp servers
+## -----------------------------------------------------------------------------
 
-httpd: apache* httpd nginx* lighttpd hiawatha caddy h2o
-proxy: squid* c-icap squidGuard varnish*
-php: php* lsphp*
-ftpd: proftpd in.tftpd vsftpd
-uwsgi: uwsgi
 unicorn: *unicorn*
 puma: *puma*
-
-# -----------------------------------------------------------------------------
-# database servers
-
-sql: mysqld* mariad* postgres* postmaster* oracle_* ora_* sqlservr
-nosql: mongod redis* valkey* memcached *couchdb*
-timedb: prometheus *carbon-cache.py* *carbon-aggregator.py* *graphite/manage.py* *net.opentsdb.tools.TSDMain* influxd*
-
-clickhouse: clickhouse-serv* clickhouse-cli* clckhouse-watch
-
-# -----------------------------------------------------------------------------
-# email servers
-
-mta: amavis* zmstat-* zmdiaglog zmmailboxdmgr opendkim postfwd2 smtp* lmtp* sendmail postfix master pickup qmgr showq tlsmgr postscreen oqmgr msmtp* nullmailer*
-mda: dovecot *imapd *pop3d *popd
-
-# -----------------------------------------------------------------------------
-# network, routing, VPN
-
-ppp: ppp*
-vpn: openvpn pptp* cjdroute gvpe tincd wireguard tailscaled
-wifi: hostapd wpa_supplicant
-routing: ospfd* ospf6d* bgpd bfdd fabricd isisd eigrpd sharpd staticd ripd ripngd pimd pbrd nhrpd ldpd zebra vrrpd vtysh bird*
-modem: ModemManager
-netmanager: NetworkManager nm* systemd-networkd networkctl netplan connmand wicked* avahi-autoipd networkd-dispatcher
-firewall: firewalld ufw nft
-tor: tor
-bluetooth: bluetooth bluetoothd bluez bluedevil obexd
-
-# -----------------------------------------------------------------------------
-# high availability and balancers
-
+couchdb: *couchdb*
+graphite: *carbon-cache.py* *carbon-aggregator.py* *graphite/manage.py*
+opentsdb: *net.opentsdb.tools.TSDMain*
+imapd: *imapd
+pop3d: *pop3d
+popd: *popd
 camo: *camo*
-balancer: ipvs_* haproxy
-ha: corosync hs_logd ha_logd stonithd pacemakerd lrmd crmd keepalived ucarp*
-
-# -----------------------------------------------------------------------------
-# telephony
-
-pbx: asterisk safe_asterisk *vicidial*
-sip: opensips* stund
-
-# -----------------------------------------------------------------------------
-# chat
-
-chat: irssi *vines* *prosody* murmurd
-
-# -----------------------------------------------------------------------------
-# monitoring
-
-logs: ulogd* syslog* rsyslog* logrotate *systemd-journal* rotatelogs sysklogd metalog
-nms: snmpd vnstatd smokeping zabbix* munin* mon openhpid tailon nrpe
-monit: monit
-splunk: splunkd
+vicidial: *vicidial*
+vines: *vines*
+prosody: *prosody*
 azure: mdsd *waagent* *omiserver* *omiagent* hv_kvp_daemon hv_vss_daemon *auoms* *omsagent*
 datadog: *datadog*
-edgedelta: edgedelta
 newrelic: newrelic*
 google-agent: *google_guest_agent* *google_osconfig_agent*
-nvidia-smi: nvidia-smi
-intel_gpu_top: intel_gpu_top
-htop: htop
-watchdog: watchdog
-telegraf: telegraf
-grafana: grafana*
-
-# -----------------------------------------------------------------------------
-# storage, file systems and file servers
-
 ceph: ceph-* ceph_* radosgw* rbd-* cephfs-* osdmaptool crushtool
 samba: smbd nmbd winbindd ctdbd ctdb-* ctdb_*
 nfs: rpcbind rpc.* nfs*
 zfs: spl_* z_* txg_* zil_* arc_* l2arc*
-btrfs: btrfs*
 iscsi: iscsid iscsi_eh
 afp: netatalk afpd cnid_dbd cnid_metad
-ntfs-3g: ntfs-3g
-
-# -----------------------------------------------------------------------------
-# kubernetes
-
-kubelet: kubelet
-kube-dns: kube-dns
-kube-proxy: kube-proxy
-metrics-server: metrics-server
-heapster: heapster
-
-# -----------------------------------------------------------------------------
-# AWS
-
 aws-s3: '*aws s3*' s3cmd s5cmd
-aws: aws
-
-# -----------------------------------------------------------------------------
-# virtualization platform
-
 proxmox-ve: pve* spiceproxy
-
-# -----------------------------------------------------------------------------
-# containers & virtual machines
-
-containers: lxc* docker* balena* containerd
-VMs: vbox* VBox* qemu* kvm*
 libvirt: virtlogd virtqemud virtstoraged virtnetworkd virtlockd virtinterfaced
 libvirt: virtnodedevd virtproxyd virtsecretd libvirtd
 guest-agent: qemu-ga spice-vdagent cloud-init*
-
-# -----------------------------------------------------------------------------
-# ssh servers and clients
-
-ssh: ssh* scp sftp* dropbear
-
-# -----------------------------------------------------------------------------
-# print servers and clients
-
-print: cups* lpd lpq
-
-# -----------------------------------------------------------------------------
-# time servers and clients
-
-time: ntp* systemd-timesyn* chronyd ptp*
-
-# -----------------------------------------------------------------------------
-# dhcp servers and clients
-
 dhcp: *dhcp* dhclient
 
-# -----------------------------------------------------------------------------
-# name servers and clients
-
-dns: named unbound nsd pdns_server knotd gdnsd yadifad dnsmasq *systemd-resolve* pihole* avahi-daemon avahi-dnsconfd
-dnsdist: dnsdist
-
-# -----------------------------------------------------------------------------
-# installation / compilation / debugging
-
 build: cc1 cc1plus as gcc* cppcheck ld make cmake automake autoconf autoreconf
 build: cargo rustc bazel buck git gdb valgrind* rpmbuild dpkg-buildpackage
-
-# -----------------------------------------------------------------------------
-# package management
-
 packagemanager: apt* dpkg* dselect dnf yum rpm zypp* yast* pacman xbps* swupd* emerge*
 packagemanager: packagekitd pkgin pkg apk snapd slackpkg slapt-get
-
-# -----------------------------------------------------------------------------
-# antivirus
-
-antivirus: clam* *clam imunify360*
-
-# -----------------------------------------------------------------------------
-# torrent clients
-
-torrents: *deluge* transmission* *SickBeard* *CouchPotato* *rtorrent*
-
-# -----------------------------------------------------------------------------
-# backup servers and clients
-
+clam: clam* *clam
 backup: rsync lsyncd bacula* borg rclone
-
-# -----------------------------------------------------------------------------
-# cron
-
 cron: cron* atd anacron *systemd-cron* incrond
-
-# -----------------------------------------------------------------------------
-# UPS
-
 ups: upsmon upsd */nut/* apcupsd
-
-# -----------------------------------------------------------------------------
-# media players, servers, clients
-
-media: mplayer vlc xine mediatomb omxplayer* kodi* xbmc* mediacenter eventlircd
-media: mpd minidlnad mt-daapd Plex* jellyfin squeeze* jackett Ombi
-media: strawberry* clementine*
-
 audio: pulse* pipewire wireplumber jack*
 
-# -----------------------------------------------------------------------------
-# java applications
+rabbitmq: *rabbitmq*
+sidekiq: *sidekiq*
+erlang: beam.smp
+
+## -----------------------------------------------------------------------------
+## java applications
 
 hdfsdatanode: *org.apache.hadoop.hdfs.server.datanode.DataNode*
 hdfsnamenode: *org.apache.hadoop.hdfs.server.namenode.NameNode*
 hdfsjournalnode: *org.apache.hadoop.hdfs.qjournal.server.JournalNode*
 hdfszkfc: *org.apache.hadoop.hdfs.tools.DFSZKFailoverController*
-
 yarnnode: *org.apache.hadoop.yarn.server.nodemanager.NodeManager*
 yarnmgr: *org.apache.hadoop.yarn.server.resourcemanager.ResourceManager*
 yarnproxy: *org.apache.hadoop.yarn.server.webproxy.WebAppProxyServer*
-
 sparkworker: *org.apache.spark.deploy.worker.Worker*
 sparkmaster: *org.apache.spark.deploy.master.Master*
-
 hbaseregion: *org.apache.hadoop.hbase.regionserver.HRegionServer*
 hbaserest: *org.apache.hadoop.hbase.rest.RESTServer*
 hbasethrift: *org.apache.hadoop.hbase.thrift.ThriftServer*
 hbasemaster: *org.apache.hadoop.hbase.master.HMaster*
-
 zookeeper: *org.apache.zookeeper.server.quorum.QuorumPeerMain*
-
 hive2: *org.apache.hive.service.server.HiveServer2*
 hivemetastore: *org.apache.hadoop.hive.metastore.HiveMetaStore*
-
 solr: *solr.install.dir*
-
 airflow: *airflow*
+kafka: *kafka.Kafka*
 
-# -----------------------------------------------------------------------------
-# GUI
-
-X: X Xorg xinit xdm Xwayland xsettingsd touchegg
-wayland: swaylock swayidle waypipe wayvnc
-kde: *kdeinit* kdm sddm plasmashell startplasma-* kwin* kwallet* krunner kactivitymanager*
-gnome: gnome-* gdm gconf* mutter
-mate: mate-* msd-* marco*
-cinnamon: cinnamon* muffin
-xfce: xfwm4 xfdesktop xfce* Thunar xfsettingsd xfconf*
-lxde: lxde* startlxde lxdm lxappearance* lxlauncher* lxpanel* lxsession* lxsettings*
-lxqt: lxqt* startlxqt
-enlightenment: entrance enlightenment*
-i3: i3*
-awesome: awesome awesome-client
-dwm: dwm.*
-sway: sway
-weston: weston
-cage: cage
-wayfire: wayfire
-gui: lightdm colord seatd greetd gkrellm slim qingy dconf* *gvfs gvfs*
-gui: '*systemd --user*' xdg-* at-spi-*
-
-webbrowser: *chrome-sandbox* *google-chrome* *chromium* *firefox* vivaldi* opera* epiphany chrome*
-webbrowser: lynx elinks w3m w3mmee links
-mua: evolution-* thunderbird* mutt neomutt pine mailx alpine
-
-# -----------------------------------------------------------------------------
-# Kernel / System
+## -----------------------------------------------------------------------------
+## Kernel / System
 
 ksmd: ksmd
 khugepaged: khugepaged
@@ -356,87 +124,5 @@ kdamond: kdamond
 kswapd: kswapd
 zswap: zswap
 kcompactd: kcompactd
-
-system: systemd* udisks* udevd* *udevd ipv6_addrconf dbus-* rtkit*
-system: mdadm acpid uuidd upowerd elogind* eudev mdev lvmpolld dmeventd
-system: accounts-daemon rngd haveged rasdaemon irqbalance start-stop-daemon
-system: supervise-daemon openrc* init runit runsvdir runsv auditd lsmd
-system: abrt* nscd rtkit-daemon gpg-agent usbguard* boltd geoclue
-
-kernel: kworker kthreadd kauditd lockd khelper kdevtmpfs khungtaskd rpciod
-kernel: fsnotify_mark kthrotld deferwq scsi_* kdmflush oom_reaper kdevtempfs
-kernel: ksoftirqd
-
-# -----------------------------------------------------------------------------
-# inetd
-
-inetd: inetd xinetd
-
-# -----------------------------------------------------------------------------
-# other application servers
-
-nginxunit: unitd
-
-typesense: typesense-serve
-
-i2pd: i2pd
-
-rethinkdb: rethinkdb
-
-beanstalkd: beanstalkd
-
-rspamd: rspamd
-
-consul: consul
-
-kafka: *kafka.Kafka*
-
-rabbitmq: *rabbitmq*
-
-sidekiq: *sidekiq*
-java: java
-ipfs: ipfs
-erlang: beam.smp
-
-node: node
-factorio: factorio
-
-p4: p4*
-
-git-services: gitea gitlab-runner
-
-freeswitch: freeswitch*
-
-# -------- web3 / blockchains ----------
-
-go-ethereum: geth*
-nethermind-ethereum: nethermind*
-besu-ethereum: besu*
-openEthereum: openethereum*
-urbit: urbit*
-bitcoin-node: *bitcoind* lnd*
-filecoin: lotus* lotus-miner* lotus-worker*
-solana: solana*
-web3: *hardhat* *ganache* *truffle* *brownie* *waffle*
-terra: terra* mantle*
-
-# -----------------------------------------------------------------------------
-# chaos engineering tools
-
-stress: stress stress-ng*
-gremlin: gremlin*
-
-# -----------------------------------------------------------------------------
-# load testing tools
-
-locust: locust
-
-# -----------------------------------------------------------------------------
-# data science and machine learning tools
-
-jupyter: jupyter*
-
-# -----------------------------------------------------------------------------
-# File synchronization tools
-
-filesync: dropbox syncthing
+ipvs: ipvs_*
+btrfs: btrfs*

+ 188 - 0
src/collectors/apps.plugin/apps_incremental_collection.c

@@ -0,0 +1,188 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "apps_plugin.h"
+
+#if (INCREMENTAL_DATA_COLLECTION == 1)
+bool managed_log(struct pid_stat *p, PID_LOG log, bool status) {
+    if(unlikely(!status)) {
+        // netdata_log_error("command failed log %u, errno %d", log, errno);
+
+        if(unlikely(debug_enabled || errno != ENOENT)) {
+            if(unlikely(debug_enabled || !(p->log_thrown & log))) {
+                p->log_thrown |= log;
+                switch(log) {
+                    case PID_LOG_IO:
+#if !defined(OS_LINUX)
+                        netdata_log_error("Cannot fetch process %d I/O info (command '%s')", p->pid, pid_stat_comm(p));
+#else
+                        netdata_log_error("Cannot process %s/proc/%d/io (command '%s')", netdata_configured_host_prefix, p->pid, pid_stat_comm(p));
+#endif
+                        break;
+
+                    case PID_LOG_STATUS:
+#if !defined(OS_LINUX)
+                        netdata_log_error("Cannot fetch process %d status info (command '%s')", p->pid, pid_stat_comm(p));
+#else
+                        netdata_log_error("Cannot process %s/proc/%d/status (command '%s')", netdata_configured_host_prefix, p->pid, pid_stat_comm(p));
+#endif
+                        break;
+
+                    case PID_LOG_CMDLINE:
+#if !defined(OS_LINUX)
+                        netdata_log_error("Cannot fetch process %d command line (command '%s')", p->pid, pid_stat_comm(p));
+#else
+                        netdata_log_error("Cannot process %s/proc/%d/cmdline (command '%s')", netdata_configured_host_prefix, p->pid, pid_stat_comm(p));
+#endif
+                        break;
+
+                    case PID_LOG_FDS:
+#if !defined(OS_LINUX)
+                        netdata_log_error("Cannot fetch process %d files (command '%s')", p->pid, pid_stat_comm(p));
+#else
+                        netdata_log_error("Cannot process entries in %s/proc/%d/fd (command '%s')", netdata_configured_host_prefix, p->pid, pid_stat_comm(p));
+#endif
+                        break;
+
+                    case PID_LOG_LIMITS:
+#if !defined(OS_LINUX)
+                        ;
+#else
+                        netdata_log_error("Cannot process %s/proc/%d/limits (command '%s')", netdata_configured_host_prefix, p->pid, pid_stat_comm(p));
+#endif
+
+                    case PID_LOG_STAT:
+                        break;
+
+                    default:
+                        netdata_log_error("unhandled error for pid %d, command '%s'", p->pid, pid_stat_comm(p));
+                        break;
+                }
+            }
+        }
+        errno_clear();
+    }
+    else if(unlikely(p->log_thrown & log)) {
+        // netdata_log_error("unsetting log %u on pid %d", log, p->pid);
+        p->log_thrown &= ~log;
+    }
+
+    return status;
+}
+
+static inline bool incrementally_read_pid_stat(struct pid_stat *p, void *ptr) {
+    p->last_stat_collected_usec = p->stat_collected_usec;
+    p->stat_collected_usec = now_monotonic_usec();
+    calls_counter++;
+
+    if(!OS_FUNCTION(apps_os_read_pid_stat)(p, ptr))
+        return 0;
+
+    return 1;
+}
+
+static inline int incrementally_read_pid_io(struct pid_stat *p, void *ptr) {
+    p->last_io_collected_usec = p->io_collected_usec;
+    p->io_collected_usec = now_monotonic_usec();
+    calls_counter++;
+
+    bool ret = OS_FUNCTION(apps_os_read_pid_io)(p, ptr);
+
+    return ret ? 1 : 0;
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+int incrementally_collect_data_for_pid_stat(struct pid_stat *p, void *ptr) {
+    if(unlikely(p->read)) return 0;
+
+    pid_collection_started(p);
+
+    // --------------------------------------------------------------------
+    // /proc/<pid>/stat
+
+    if(unlikely(!managed_log(p, PID_LOG_STAT, incrementally_read_pid_stat(p, ptr)))) {
+        // there is no reason to proceed if we cannot get its status
+        pid_collection_failed(p);
+        return 0;
+    }
+
+    // check its parent pid
+    if(unlikely(p->ppid < INIT_PID))
+        p->ppid = 0;
+
+    // --------------------------------------------------------------------
+    // /proc/<pid>/io
+
+    managed_log(p, PID_LOG_IO, incrementally_read_pid_io(p, ptr));
+
+    // --------------------------------------------------------------------
+    // /proc/<pid>/status
+
+    if(unlikely(!managed_log(p, PID_LOG_STATUS, OS_FUNCTION(apps_os_read_pid_status)(p, ptr)))) {
+        // there is no reason to proceed if we cannot get its status
+        pid_collection_failed(p);
+        return 0;
+    }
+
+    // --------------------------------------------------------------------
+    // /proc/<pid>/fd
+
+#if (PROCESSES_HAVE_FDS == 1)
+    if(enable_file_charts) {
+        managed_log(p, PID_LOG_FDS, read_pid_file_descriptors(p, ptr));
+#if (PROCESSES_HAVE_PID_LIMITS == 1)
+        managed_log(p, PID_LOG_LIMITS, OS_FUNCTION(apps_os_read_pid_limits)(p, ptr));
+#endif
+    }
+#endif
+
+    // --------------------------------------------------------------------
+    // done!
+
+#if defined(NETDATA_INTERNAL_CHECKS) && (ALL_PIDS_ARE_READ_INSTANTLY == 0)
+    struct pid_stat *pp = p->parent;
+    if(unlikely(include_exited_childs && pp && !pp->read))
+        nd_log(NDLS_COLLECTORS, NDLP_WARNING,
+               "Read process %d (%s) sortlisted %"PRIu32", but its parent %d (%s) sortlisted %"PRIu32", is not read",
+               p->pid, pid_stat_comm(p), p->sortlist, pp->pid, pid_stat_comm(pp), pp->sortlist);
+#endif
+
+    pid_collection_completed(p);
+
+    return 1;
+}
+
+int incrementally_collect_data_for_pid(pid_t pid, void *ptr) {
+    if(unlikely(pid < INIT_PID)) {
+        netdata_log_error("Invalid pid %d read (expected >= %d). Ignoring process.", pid, INIT_PID);
+        return 0;
+    }
+
+    struct pid_stat *p = get_or_allocate_pid_entry(pid);
+    if(unlikely(!p)) return 0;
+
+    return incrementally_collect_data_for_pid_stat(p, ptr);
+}
+#endif
+
+// --------------------------------------------------------------------------------------------------------------------
+
+#if (PROCESSES_HAVE_CMDLINE == 1)
+int read_proc_pid_cmdline(struct pid_stat *p) {
+    static char cmdline[MAX_CMDLINE];
+
+    if(unlikely(!OS_FUNCTION(apps_os_get_pid_cmdline)(p, cmdline, sizeof(cmdline))))
+        goto cleanup;
+
+    string_freez(p->cmdline);
+    p->cmdline = string_strdupz(cmdline);
+
+    return 1;
+
+cleanup:
+    // copy the command to the command line
+    string_freez(p->cmdline);
+    p->cmdline = string_dup(p->comm);
+    return 0;
+}
+#endif

+ 365 - 0
src/collectors/apps.plugin/apps_os_freebsd.c

@@ -0,0 +1,365 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "apps_plugin.h"
+
+#if defined(OS_FREEBSD)
+
+usec_t system_current_time_ut;
+long global_block_size = 512;
+
+static long get_fs_block_size(void) {
+    struct statvfs vfs;
+    static long block_size = 0;
+
+    if (block_size == 0) {
+        if (statvfs("/", &vfs) == 0) {
+            block_size = vfs.f_frsize ? vfs.f_frsize : vfs.f_bsize;
+        } else {
+            // If statvfs fails, fall back to the typical block size
+            block_size = 512;
+        }
+    }
+
+    return block_size;
+}
+
+void apps_os_init_freebsd(void) {
+    global_block_size = get_fs_block_size();
+}
+
+static inline void get_current_time(void) {
+    struct timeval current_time;
+    gettimeofday(&current_time, NULL);
+    system_current_time_ut = timeval_usec(&current_time);
+}
+
+uint64_t apps_os_get_total_memory_freebsd(void) {
+    uint64_t ret = 0;
+
+    int mib[2] = {CTL_HW, HW_PHYSMEM};
+    size_t size = sizeof(ret);
+    if (sysctl(mib, 2, &ret, &size, NULL, 0) == -1) {
+        netdata_log_error("Failed to get total memory using sysctl");
+        return 0;
+    }
+
+    return ret;
+}
+
+bool apps_os_read_pid_fds_freebsd(struct pid_stat *p, void *ptr) {
+    int mib[4];
+    size_t size;
+    struct kinfo_file *fds;
+    static char *fdsbuf;
+    char *bfdsbuf, *efdsbuf;
+    char fdsname[FILENAME_MAX + 1];
+#define SHM_FORMAT_LEN 31 // format: 21 + size: 10
+    char shm_name[FILENAME_MAX - SHM_FORMAT_LEN + 1];
+
+    // we make all pid fds negative, so that
+    // we can detect unused file descriptors
+    // at the end, to free them
+    make_all_pid_fds_negative(p);
+
+    mib[0] = CTL_KERN;
+    mib[1] = KERN_PROC;
+    mib[2] = KERN_PROC_FILEDESC;
+    mib[3] = p->pid;
+
+    if (unlikely(sysctl(mib, 4, NULL, &size, NULL, 0))) {
+        netdata_log_error("sysctl error: Can't get file descriptors data size for pid %d", p->pid);
+        return false;
+    }
+    if (likely(size > 0))
+        fdsbuf = reallocz(fdsbuf, size);
+    if (unlikely(sysctl(mib, 4, fdsbuf, &size, NULL, 0))) {
+        netdata_log_error("sysctl error: Can't get file descriptors data for pid %d", p->pid);
+        return false;
+    }
+
+    bfdsbuf = fdsbuf;
+    efdsbuf = fdsbuf + size;
+    while (bfdsbuf < efdsbuf) {
+        fds = (struct kinfo_file *)(uintptr_t)bfdsbuf;
+        if (unlikely(fds->kf_structsize == 0))
+            break;
+
+        // do not process file descriptors for current working directory, root directory,
+        // jail directory, ktrace vnode, text vnode and controlling terminal
+        if (unlikely(fds->kf_fd < 0)) {
+            bfdsbuf += fds->kf_structsize;
+            continue;
+        }
+
+        // get file descriptors array index
+        size_t fdid = fds->kf_fd;
+
+        // check if the fds array is small
+        if (unlikely(fdid >= p->fds_size)) {
+            // it is small, extend it
+
+            debug_log("extending fd memory slots for %s from %d to %d", pid_stat_comm(p), p->fds_size, fdid + MAX_SPARE_FDS);
+
+            p->fds = reallocz(p->fds, (fdid + MAX_SPARE_FDS) * sizeof(struct pid_fd));
+
+            // and initialize it
+            init_pid_fds(p, p->fds_size, (fdid + MAX_SPARE_FDS) - p->fds_size);
+            p->fds_size = fdid + MAX_SPARE_FDS;
+        }
+
+        if (unlikely(p->fds[fdid].fd == 0)) {
+            // we don't know this fd, get it
+
+            switch (fds->kf_type) {
+                case KF_TYPE_FIFO:
+                case KF_TYPE_VNODE:
+                    if (unlikely(!fds->kf_path[0])) {
+                        sprintf(fdsname, "other: inode: %lu", fds->kf_un.kf_file.kf_file_fileid);
+                        break;
+                    }
+                    sprintf(fdsname, "%s", fds->kf_path);
+                    break;
+                case KF_TYPE_SOCKET:
+                    switch (fds->kf_sock_domain) {
+                        case AF_INET:
+                        case AF_INET6:
+#if __FreeBSD_version < 1400074
+                            if (fds->kf_sock_protocol == IPPROTO_TCP)
+                                sprintf(fdsname, "socket: %d %lx", fds->kf_sock_protocol, fds->kf_un.kf_sock.kf_sock_inpcb);
+                            else
+#endif
+                                sprintf(fdsname, "socket: %d %lx", fds->kf_sock_protocol, fds->kf_un.kf_sock.kf_sock_pcb);
+                            break;
+                        case AF_UNIX:
+                            /* print address of pcb and connected pcb */
+                            sprintf(fdsname, "socket: %lx %lx", fds->kf_un.kf_sock.kf_sock_pcb, fds->kf_un.kf_sock.kf_sock_unpconn);
+                            break;
+                        default:
+                            /* print protocol number and socket address */
+#if __FreeBSD_version < 1200031
+                            sprintf(fdsname, "socket: other: %d %s %s", fds->kf_sock_protocol, fds->kf_sa_local.__ss_pad1, fds->kf_sa_local.__ss_pad2);
+#else
+                            sprintf(fdsname, "socket: other: %d %s %s", fds->kf_sock_protocol, fds->kf_un.kf_sock.kf_sa_local.__ss_pad1, fds->kf_un.kf_sock.kf_sa_local.__ss_pad2);
+#endif
+                    }
+                    break;
+                case KF_TYPE_PIPE:
+                    sprintf(fdsname, "pipe: %lu %lu", fds->kf_un.kf_pipe.kf_pipe_addr, fds->kf_un.kf_pipe.kf_pipe_peer);
+                    break;
+                case KF_TYPE_PTS:
+#if __FreeBSD_version < 1200031
+                    sprintf(fdsname, "other: pts: %u", fds->kf_un.kf_pts.kf_pts_dev);
+#else
+                    sprintf(fdsname, "other: pts: %lu", fds->kf_un.kf_pts.kf_pts_dev);
+#endif
+                    break;
+                case KF_TYPE_SHM:
+                    strncpyz(shm_name, fds->kf_path, FILENAME_MAX - SHM_FORMAT_LEN);
+                    sprintf(fdsname, "other: shm: %s size: %lu", shm_name, fds->kf_un.kf_file.kf_file_size);
+                    break;
+                case KF_TYPE_SEM:
+                    sprintf(fdsname, "other: sem: %u", fds->kf_un.kf_sem.kf_sem_value);
+                    break;
+                default:
+                    sprintf(fdsname, "other: pid: %d fd: %d", fds->kf_un.kf_proc.kf_pid, fds->kf_fd);
+            }
+
+            // if another process already has this, we will get
+            // the same id
+            p->fds[fdid].fd = file_descriptor_find_or_add(fdsname, 0);
+        }
+
+        // else make it positive again, we need it
+        // of course, the actual file may have changed
+
+        else
+            p->fds[fdid].fd = -p->fds[fdid].fd;
+
+        bfdsbuf += fds->kf_structsize;
+    }
+
+    return true;
+}
+
+bool apps_os_get_pid_cmdline_freebsd(struct pid_stat *p, char *cmdline, size_t bytes) {
+    size_t i, b = bytes - 1;
+    int mib[4];
+
+    mib[0] = CTL_KERN;
+    mib[1] = KERN_PROC;
+    mib[2] = KERN_PROC_ARGS;
+    mib[3] = p->pid;
+    if (unlikely(sysctl(mib, 4, cmdline, &b, NULL, 0)))
+        return false;
+
+    cmdline[b] = '\0';
+    for(i = 0; i < b ; i++)
+        if(unlikely(!cmdline[i])) cmdline[i] = ' ';
+
+    return true;
+}
+
+bool apps_os_read_pid_io_freebsd(struct pid_stat *p, void *ptr) {
+    struct kinfo_proc *proc_info = (struct kinfo_proc *)ptr;
+
+    pid_incremental_rate(io, PDF_LREAD,  proc_info->ki_rusage.ru_inblock * global_block_size);
+    pid_incremental_rate(io, PDF_LWRITE, proc_info->ki_rusage.ru_oublock * global_block_size);
+
+    return true;
+}
+
+bool apps_os_read_pid_limits_freebsd(struct pid_stat *p __maybe_unused, void *ptr __maybe_unused) {
+    return false;
+}
+
+bool apps_os_read_pid_status_freebsd(struct pid_stat *p, void *ptr) {
+    struct kinfo_proc *proc_info = (struct kinfo_proc *)ptr;
+
+    p->uid                  = proc_info->ki_uid;
+    p->gid                  = proc_info->ki_groups[0];
+    p->values[PDF_VMSIZE]   = proc_info->ki_size;
+    p->values[PDF_VMRSS]    = proc_info->ki_rssize * pagesize;
+    // TODO: what about shared and swap memory on FreeBSD?
+    return true;
+}
+
+//bool apps_os_read_global_cpu_utilization_freebsd(void) {
+//    static kernel_uint_t utime_raw = 0, stime_raw = 0, ntime_raw = 0;
+//    static usec_t collected_usec = 0, last_collected_usec = 0;
+//    long cp_time[CPUSTATES];
+//
+//    if (unlikely(CPUSTATES != 5)) {
+//        goto cleanup;
+//    } else {
+//        static int mib[2] = {0, 0};
+//
+//        if (unlikely(GETSYSCTL_SIMPLE("kern.cp_time", mib, cp_time))) {
+//            goto cleanup;
+//        }
+//    }
+//
+//    last_collected_usec = collected_usec;
+//    collected_usec = now_monotonic_usec();
+//
+//    calls_counter++;
+//
+//    // temporary - it is added global_ntime;
+//    kernel_uint_t global_ntime = 0;
+//
+//    incremental_rate(global_utime, utime_raw, cp_time[0], collected_usec, last_collected_usec, (NSEC_PER_SEC / system_hz));
+//    incremental_rate(global_ntime, ntime_raw, cp_time[1], collected_usec, last_collected_usec, (NSEC_PER_SEC / system_hz));
+//    incremental_rate(global_stime, stime_raw, cp_time[2], collected_usec, last_collected_usec, (NSEC_PER_SEC / system_hz));
+//
+//    global_utime += global_ntime;
+//
+//    if(unlikely(global_iterations_counter == 1)) {
+//        global_utime = 0;
+//        global_stime = 0;
+//        global_gtime = 0;
+//    }
+//
+//    return 1;
+//
+//cleanup:
+//    global_utime = 0;
+//    global_stime = 0;
+//    global_gtime = 0;
+//    return 0;
+//}
+
+bool apps_os_read_pid_stat_freebsd(struct pid_stat *p, void *ptr) {
+    struct kinfo_proc *proc_info = (struct kinfo_proc *)ptr;
+    if (unlikely(proc_info->ki_tdflags & TDF_IDLETD))
+        goto cleanup;
+
+    char *comm          = proc_info->ki_comm;
+    p->ppid             = proc_info->ki_ppid;
+
+    update_pid_comm(p, comm);
+
+    pid_incremental_rate(stat, PDF_MINFLT,  (kernel_uint_t)proc_info->ki_rusage.ru_minflt);
+    pid_incremental_rate(stat, PDF_CMINFLT, (kernel_uint_t)proc_info->ki_rusage_ch.ru_minflt);
+    pid_incremental_rate(stat, PDF_MAJFLT,  (kernel_uint_t)proc_info->ki_rusage.ru_majflt);
+    pid_incremental_rate(stat, PDF_CMAJFLT, (kernel_uint_t)proc_info->ki_rusage_ch.ru_majflt);
+    pid_incremental_rate(stat, PDF_UTIME,   (kernel_uint_t)proc_info->ki_rusage.ru_utime.tv_sec * NSEC_PER_SEC + proc_info->ki_rusage.ru_utime.tv_usec * NSEC_PER_USEC);
+    pid_incremental_rate(stat, PDF_STIME,   (kernel_uint_t)proc_info->ki_rusage.ru_stime.tv_sec * NSEC_PER_SEC + proc_info->ki_rusage.ru_stime.tv_usec * NSEC_PER_USEC);
+    pid_incremental_rate(stat, PDF_CUTIME,  (kernel_uint_t)proc_info->ki_rusage_ch.ru_utime.tv_sec * NSEC_PER_SEC + proc_info->ki_rusage_ch.ru_utime.tv_usec * NSEC_PER_USEC);
+    pid_incremental_rate(stat, PDF_CSTIME,  (kernel_uint_t)proc_info->ki_rusage_ch.ru_stime.tv_sec * NSEC_PER_SEC + proc_info->ki_rusage_ch.ru_stime.tv_usec * NSEC_PER_USEC);
+
+    p->values[PDF_THREADS] = proc_info->ki_numthreads;
+
+    usec_t started_ut = timeval_usec(&proc_info->ki_start);
+    p->values[PDF_UPTIME] = (system_current_time_ut > started_ut) ? (system_current_time_ut - started_ut) / USEC_PER_SEC : 0;
+
+    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) VALUES: utime=" KERNEL_UINT_FORMAT ", stime=" KERNEL_UINT_FORMAT ", cutime=" KERNEL_UINT_FORMAT ", cstime=" KERNEL_UINT_FORMAT ", minflt=" KERNEL_UINT_FORMAT ", majflt=" KERNEL_UINT_FORMAT ", cminflt=" KERNEL_UINT_FORMAT ", cmajflt=" KERNEL_UINT_FORMAT ", threads=%d",
+                      netdata_configured_host_prefix, p->pid, pid_stat_comm(p), (p->target)?string2str(p->target->name):"UNSET",
+                      p->stat_collected_usec - p->last_stat_collected_usec,
+                      p->values[PDF_UTIME],
+                      p->values[PDF_STIME],
+                      p->values[PDF_CUTIME],
+                      p->values[PDF_CSTIME],
+                      p->values[PDF_MINFLT],
+                      p->values[PDF_MAJFLT],
+                      p->values[PDF_CMINFLT],
+                      p->values[PDF_CMAJFLT],
+                      p->values[PDF_THREADS]);
+
+    return true;
+
+cleanup:
+    return false;
+}
+
+bool apps_os_collect_all_pids_freebsd(void) {
+    // Mark all processes as unread before collecting new data
+    struct pid_stat *p = NULL;
+    int i, procnum;
+
+    static size_t procbase_size = 0;
+    static struct kinfo_proc *procbase = NULL;
+
+    size_t new_procbase_size;
+
+    int mib[3] = { CTL_KERN, KERN_PROC, KERN_PROC_PROC };
+    if (unlikely(sysctl(mib, 3, NULL, &new_procbase_size, NULL, 0))) {
+        netdata_log_error("sysctl error: Can't get processes data size");
+        return false;
+    }
+
+    // give it some air for processes that may be started
+    // during this little time.
+    new_procbase_size += 100 * sizeof(struct kinfo_proc);
+
+    // increase the buffer if needed
+    if(new_procbase_size > procbase_size) {
+        procbase_size = new_procbase_size;
+        procbase = reallocz(procbase, procbase_size);
+    }
+
+    // sysctl() gets from new_procbase_size the buffer size
+    // and also returns to it the amount of data filled in
+    new_procbase_size = procbase_size;
+
+    // get the processes from the system
+    if (unlikely(sysctl(mib, 3, procbase, &new_procbase_size, NULL, 0))) {
+        netdata_log_error("sysctl error: Can't get processes data");
+        return false;
+    }
+
+    // based on the amount of data filled in
+    // calculate the number of processes we got
+    procnum = new_procbase_size / sizeof(struct kinfo_proc);
+
+    get_current_time();
+
+    for (i = 0 ; i < procnum ; ++i) {
+        pid_t pid = procbase[i].ki_pid;
+        if (pid <= 0) continue;
+        incrementally_collect_data_for_pid(pid, &procbase[i]);
+    }
+
+    return true;
+}
+
+#endif

+ 771 - 0
src/collectors/apps.plugin/apps_os_linux.c

@@ -0,0 +1,771 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "apps_plugin.h"
+
+#if defined(OS_LINUX)
+
+#define MAX_PROC_PID_LIMITS 8192
+#define PROC_PID_LIMITS_MAX_OPEN_FILES_KEY "\nMax open files "
+
+int max_fds_cache_seconds = 60;
+kernel_uint_t system_uptime_secs;
+
+void apps_os_init_linux(void) {
+    ;
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// /proc/pid/fd
+
+struct arl_callback_ptr {
+    struct pid_stat *p;
+    procfile *ff;
+    size_t line;
+};
+
+bool apps_os_read_pid_fds_linux(struct pid_stat *p, void *ptr __maybe_unused) {
+    if(unlikely(!p->fds_dirname)) {
+        char dirname[FILENAME_MAX+1];
+        snprintfz(dirname, FILENAME_MAX, "%s/proc/%d/fd", netdata_configured_host_prefix, p->pid);
+        p->fds_dirname = strdupz(dirname);
+    }
+
+    DIR *fds = opendir(p->fds_dirname);
+    if(unlikely(!fds)) return false;
+
+    struct dirent *de;
+    char linkname[FILENAME_MAX + 1];
+
+    // we make all pid fds negative, so that
+    // we can detect unused file descriptors
+    // at the end, to free them
+    make_all_pid_fds_negative(p);
+
+    while((de = readdir(fds))) {
+        // we need only files with numeric names
+
+        if(unlikely(de->d_name[0] < '0' || de->d_name[0] > '9'))
+            continue;
+
+        // get its number
+        int fdid = (int) str2l(de->d_name);
+        if(unlikely(fdid < 0)) continue;
+
+        // check if the fds array is small
+        if(unlikely((size_t)fdid >= p->fds_size)) {
+            // it is small, extend it
+
+            debug_log("extending fd memory slots for %s from %d to %d"
+                      , pid_stat_comm(p)
+                          , p->fds_size
+                      , fdid + MAX_SPARE_FDS
+            );
+
+            p->fds = reallocz(p->fds, (fdid + MAX_SPARE_FDS) * sizeof(struct pid_fd));
+
+            // and initialize it
+            init_pid_fds(p, p->fds_size, (fdid + MAX_SPARE_FDS) - p->fds_size);
+            p->fds_size = (size_t)fdid + MAX_SPARE_FDS;
+        }
+
+        if(unlikely(p->fds[fdid].fd < 0 && de->d_ino != p->fds[fdid].inode)) {
+            // inodes do not match, clear the previous entry
+            inodes_changed_counter++;
+            file_descriptor_not_used(-p->fds[fdid].fd);
+            clear_pid_fd(&p->fds[fdid]);
+        }
+
+        if(p->fds[fdid].fd < 0 && p->fds[fdid].cache_iterations_counter > 0) {
+            p->fds[fdid].fd = -p->fds[fdid].fd;
+            p->fds[fdid].cache_iterations_counter--;
+            continue;
+        }
+
+        if(unlikely(!p->fds[fdid].filename)) {
+            filenames_allocated_counter++;
+            char fdname[FILENAME_MAX + 1];
+            snprintfz(fdname, FILENAME_MAX, "%s/proc/%d/fd/%s", netdata_configured_host_prefix, p->pid, de->d_name);
+            p->fds[fdid].filename = strdupz(fdname);
+        }
+
+        file_counter++;
+        ssize_t l = readlink(p->fds[fdid].filename, linkname, FILENAME_MAX);
+        if(unlikely(l == -1)) {
+            // cannot read the link
+
+            if(debug_enabled || (p->target && p->target->debug_enabled))
+                netdata_log_error("Cannot read link %s", p->fds[fdid].filename);
+
+            if(unlikely(p->fds[fdid].fd < 0)) {
+                file_descriptor_not_used(-p->fds[fdid].fd);
+                clear_pid_fd(&p->fds[fdid]);
+            }
+
+            continue;
+        }
+        else
+            linkname[l] = '\0';
+
+        uint32_t link_hash = simple_hash(linkname);
+
+        if(unlikely(p->fds[fdid].fd < 0 && p->fds[fdid].link_hash != link_hash)) {
+            // the link changed
+            links_changed_counter++;
+            file_descriptor_not_used(-p->fds[fdid].fd);
+            clear_pid_fd(&p->fds[fdid]);
+        }
+
+        if(unlikely(p->fds[fdid].fd == 0)) {
+            // we don't know this fd, get it
+
+            // if another process already has this, we will get
+            // the same id
+            p->fds[fdid].fd = (int)file_descriptor_find_or_add(linkname, link_hash);
+            p->fds[fdid].inode = de->d_ino;
+            p->fds[fdid].link_hash = link_hash;
+        }
+        else {
+            // else make it positive again, we need it
+            p->fds[fdid].fd = -p->fds[fdid].fd;
+        }
+
+        // caching control
+        // without this we read all the files on every iteration
+        if(max_fds_cache_seconds > 0) {
+            size_t spread = ((size_t)max_fds_cache_seconds > 10) ? 10 : (size_t)max_fds_cache_seconds;
+
+            // cache it for a few iterations
+            size_t max = ((size_t) max_fds_cache_seconds + (fdid % spread)) / (size_t) update_every;
+            p->fds[fdid].cache_iterations_reset++;
+
+            if(unlikely(p->fds[fdid].cache_iterations_reset % spread == (size_t) fdid % spread))
+                p->fds[fdid].cache_iterations_reset++;
+
+            if(unlikely((fdid <= 2 && p->fds[fdid].cache_iterations_reset > 5) ||
+                         p->fds[fdid].cache_iterations_reset > max)) {
+                // for stdin, stdout, stderr (fdid <= 2) we have checked a few times, or if it goes above the max, goto max
+                p->fds[fdid].cache_iterations_reset = max;
+            }
+
+            p->fds[fdid].cache_iterations_counter = p->fds[fdid].cache_iterations_reset;
+        }
+    }
+
+    closedir(fds);
+
+    return true;
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// /proc/meminfo
+
+uint64_t apps_os_get_total_memory_linux(void) {
+    uint64_t ret = 0;
+
+    char filename[FILENAME_MAX + 1];
+    snprintfz(filename, FILENAME_MAX, "%s/proc/meminfo", netdata_configured_host_prefix);
+
+    procfile *ff = procfile_open(filename, ": \t", PROCFILE_FLAG_DEFAULT);
+    if(!ff)
+        return ret;
+
+    ff = procfile_readall(ff);
+    if(!ff)
+        return ret;
+
+    size_t line, lines = procfile_lines(ff);
+
+    for(line = 0; line < lines ;line++) {
+        size_t words = procfile_linewords(ff, line);
+        if(words == 3 && strcmp(procfile_lineword(ff, line, 0), "MemTotal") == 0 && strcmp(procfile_lineword(ff, line, 2), "kB") == 0) {
+            ret = str2ull(procfile_lineword(ff, line, 1), NULL) * 1024;
+            break;
+        }
+    }
+
+    procfile_close(ff);
+
+    return ret;
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// /proc/pid/cmdline
+
+bool apps_os_get_pid_cmdline_linux(struct pid_stat *p, char *cmdline, size_t bytes) {
+    if(unlikely(!p->cmdline_filename)) {
+        char filename[FILENAME_MAX];
+        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))
+        return false;
+
+    ssize_t i, b = read(fd, cmdline, bytes - 1);
+    close(fd);
+
+    if(unlikely(b < 0))
+        return false;
+
+    cmdline[b] = '\0';
+    for(i = 0; i < b ; i++)
+        if(unlikely(!cmdline[i])) cmdline[i] = ' ';
+
+    // remove trailing spaces
+    while(b > 0 && cmdline[b - 1] == ' ')
+        cmdline[--b] = '\0';
+
+    return true;
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// /proc/pid/io
+
+bool apps_os_read_pid_io_linux(struct pid_stat *p, void *ptr __maybe_unused) {
+    static procfile *ff = NULL;
+
+    if(unlikely(!p->io_filename)) {
+        char filename[FILENAME_MAX + 1];
+        snprintfz(filename, FILENAME_MAX, "%s/proc/%d/io", netdata_configured_host_prefix, p->pid);
+        p->io_filename = strdupz(filename);
+    }
+
+    // open the file
+    ff = procfile_reopen(ff, p->io_filename, NULL, PROCFILE_FLAG_NO_ERROR_ON_FILE_IO);
+    if(unlikely(!ff)) goto cleanup;
+
+    ff = procfile_readall(ff);
+    if(unlikely(!ff)) goto cleanup;
+
+    pid_incremental_rate(io, PDF_LREAD,     str2kernel_uint_t(procfile_lineword(ff, 0,  1)));
+    pid_incremental_rate(io, PDF_LWRITE,    str2kernel_uint_t(procfile_lineword(ff, 1,  1)));
+    pid_incremental_rate(io, PDF_OREAD,     str2kernel_uint_t(procfile_lineword(ff, 2,  1)));
+    pid_incremental_rate(io, PDF_OWRITE,    str2kernel_uint_t(procfile_lineword(ff, 3,  1)));
+    pid_incremental_rate(io, PDF_PREAD,     str2kernel_uint_t(procfile_lineword(ff, 4,  1)));
+    pid_incremental_rate(io, PDF_PWRITE,    str2kernel_uint_t(procfile_lineword(ff, 5,  1)));
+
+    return true;
+
+cleanup:
+    return false;
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// /proc/pid/limits
+
+static inline kernel_uint_t get_proc_pid_limits_limit(char *buf, const char *key, size_t key_len, kernel_uint_t def) {
+    char *line = strstr(buf, key);
+    if(!line)
+        return def;
+
+    char *v = &line[key_len];
+    while(isspace((uint8_t)*v)) v++;
+
+    if(strcmp(v, "unlimited") == 0)
+        return 0;
+
+    return str2ull(v, NULL);
+}
+
+bool apps_os_read_pid_limits_linux(struct pid_stat *p, void *ptr __maybe_unused) {
+    static char proc_pid_limits_buffer[MAX_PROC_PID_LIMITS + 1];
+    bool ret = false;
+    bool read_limits = false;
+
+    errno_clear();
+    proc_pid_limits_buffer[0] = '\0';
+
+    kernel_uint_t all_fds = pid_openfds_sum(p);
+    if(all_fds < p->limits.max_open_files / 2 && p->io_collected_usec > p->last_limits_collected_usec && p->io_collected_usec - p->last_limits_collected_usec <= 60 * USEC_PER_SEC) {
+        // too frequent, we want to collect limits once per minute
+        ret = true;
+        goto cleanup;
+    }
+
+    if(unlikely(!p->limits_filename)) {
+        char filename[FILENAME_MAX + 1];
+        snprintfz(filename, FILENAME_MAX, "%s/proc/%d/limits", netdata_configured_host_prefix, p->pid);
+        p->limits_filename = strdupz(filename);
+    }
+
+    int fd = open(p->limits_filename, procfile_open_flags, 0666);
+    if(unlikely(fd == -1)) goto cleanup;
+
+    ssize_t bytes = read(fd, proc_pid_limits_buffer, MAX_PROC_PID_LIMITS);
+    close(fd);
+
+    if(bytes <= 0)
+        goto cleanup;
+
+    // make it '\0' terminated
+    if(bytes < MAX_PROC_PID_LIMITS)
+        proc_pid_limits_buffer[bytes] = '\0';
+    else
+        proc_pid_limits_buffer[MAX_PROC_PID_LIMITS - 1] = '\0';
+
+    p->limits.max_open_files = get_proc_pid_limits_limit(proc_pid_limits_buffer, PROC_PID_LIMITS_MAX_OPEN_FILES_KEY, sizeof(PROC_PID_LIMITS_MAX_OPEN_FILES_KEY) - 1, 0);
+    if(p->limits.max_open_files == 1) {
+        // it seems a bug in the kernel or something similar
+        // it sets max open files to 1 but the number of files
+        // the process has open are more than 1...
+        // https://github.com/netdata/netdata/issues/15443
+        p->limits.max_open_files = 0;
+        ret = true;
+        goto cleanup;
+    }
+
+    p->last_limits_collected_usec = p->io_collected_usec;
+    read_limits = true;
+
+    ret = true;
+
+cleanup:
+    if(p->limits.max_open_files)
+        p->openfds_limits_percent = (NETDATA_DOUBLE)all_fds * 100.0 / (NETDATA_DOUBLE)p->limits.max_open_files;
+    else
+        p->openfds_limits_percent = 0.0;
+
+    if(p->openfds_limits_percent > 100.0) {
+        if(!(p->log_thrown & PID_LOG_LIMITS_DETAIL)) {
+            char *line;
+
+            if(!read_limits) {
+                proc_pid_limits_buffer[0] = '\0';
+                line = "NOT READ";
+            }
+            else {
+                line = strstr(proc_pid_limits_buffer, PROC_PID_LIMITS_MAX_OPEN_FILES_KEY);
+                if (line) {
+                    line++; // skip the initial newline
+
+                    char *end = strchr(line, '\n');
+                    if (end)
+                        *end = '\0';
+                }
+            }
+
+            netdata_log_info(
+                "FDS_LIMITS: PID %d (%s) is using "
+                "%0.2f %% of its fds limits, "
+                "open fds = %"PRIu64 "("
+                "files = %"PRIu64 ", "
+                "pipes = %"PRIu64 ", "
+                "sockets = %"PRIu64", "
+                "inotifies = %"PRIu64", "
+                "eventfds = %"PRIu64", "
+                "timerfds = %"PRIu64", "
+                "signalfds = %"PRIu64", "
+                "eventpolls = %"PRIu64" "
+                "other = %"PRIu64" "
+                "), open fds limit = %"PRIu64", "
+                "%s, "
+                "original line [%s]",
+                p->pid, pid_stat_comm(p), p->openfds_limits_percent, all_fds,
+                p->openfds.files,
+                p->openfds.pipes,
+                p->openfds.sockets,
+                p->openfds.inotifies,
+                p->openfds.eventfds,
+                p->openfds.timerfds,
+                p->openfds.signalfds,
+                p->openfds.eventpolls,
+                p->openfds.other,
+                p->limits.max_open_files,
+                read_limits ? "and we have read the limits AFTER counting the fds"
+                              : "but we have read the limits BEFORE counting the fds",
+                line);
+
+            p->log_thrown |= PID_LOG_LIMITS_DETAIL;
+        }
+    }
+    else
+        p->log_thrown &= ~PID_LOG_LIMITS_DETAIL;
+
+    return ret;
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// /proc/pid/status
+
+void arl_callback_status_uid(const char *name, uint32_t hash, const char *value, void *dst) {
+    (void)name; (void)hash; (void)value;
+    struct arl_callback_ptr *aptr = (struct arl_callback_ptr *)dst;
+    if(unlikely(procfile_linewords(aptr->ff, aptr->line) < 5)) return;
+
+    //const char *real_uid = procfile_lineword(aptr->ff, aptr->line, 1);
+    const char *effective_uid = procfile_lineword(aptr->ff, aptr->line, 2);
+    //const char *saved_uid = procfile_lineword(aptr->ff, aptr->line, 3);
+    //const char *filesystem_uid = procfile_lineword(aptr->ff, aptr->line, 4);
+
+    if(likely(effective_uid && *effective_uid))
+        aptr->p->uid = (uid_t)str2l(effective_uid);
+}
+
+void arl_callback_status_gid(const char *name, uint32_t hash, const char *value, void *dst) {
+    (void)name; (void)hash; (void)value;
+    struct arl_callback_ptr *aptr = (struct arl_callback_ptr *)dst;
+    if(unlikely(procfile_linewords(aptr->ff, aptr->line) < 5)) return;
+
+    //const char *real_gid = procfile_lineword(aptr->ff, aptr->line, 1);
+    const char *effective_gid = procfile_lineword(aptr->ff, aptr->line, 2);
+    //const char *saved_gid = procfile_lineword(aptr->ff, aptr->line, 3);
+    //const char *filesystem_gid = procfile_lineword(aptr->ff, aptr->line, 4);
+
+    if(likely(effective_gid && *effective_gid))
+        aptr->p->gid = (uid_t)str2l(effective_gid);
+}
+
+void arl_callback_status_vmsize(const char *name, uint32_t hash, const char *value, void *dst) {
+    (void)name; (void)hash; (void)value;
+    struct arl_callback_ptr *aptr = (struct arl_callback_ptr *)dst;
+    if(unlikely(procfile_linewords(aptr->ff, aptr->line) < 3)) return;
+
+    aptr->p->values[PDF_VMSIZE] = str2kernel_uint_t(procfile_lineword(aptr->ff, aptr->line, 1)) * 1024;
+}
+
+void arl_callback_status_vmswap(const char *name, uint32_t hash, const char *value, void *dst) {
+    (void)name; (void)hash; (void)value;
+    struct arl_callback_ptr *aptr = (struct arl_callback_ptr *)dst;
+    if(unlikely(procfile_linewords(aptr->ff, aptr->line) < 3)) return;
+
+    aptr->p->values[PDF_VMSWAP] = str2kernel_uint_t(procfile_lineword(aptr->ff, aptr->line, 1)) * 1024;
+}
+
+void arl_callback_status_vmrss(const char *name, uint32_t hash, const char *value, void *dst) {
+    (void)name; (void)hash; (void)value;
+    struct arl_callback_ptr *aptr = (struct arl_callback_ptr *)dst;
+    if(unlikely(procfile_linewords(aptr->ff, aptr->line) < 3)) return;
+
+    aptr->p->values[PDF_VMRSS] = str2kernel_uint_t(procfile_lineword(aptr->ff, aptr->line, 1)) * 1024;
+}
+
+void arl_callback_status_rssfile(const char *name, uint32_t hash, const char *value, void *dst) {
+    (void)name; (void)hash; (void)value;
+    struct arl_callback_ptr *aptr = (struct arl_callback_ptr *)dst;
+    if(unlikely(procfile_linewords(aptr->ff, aptr->line) < 3)) return;
+
+    aptr->p->values[PDF_RSSFILE] = str2kernel_uint_t(procfile_lineword(aptr->ff, aptr->line, 1)) * 1024;
+}
+
+void arl_callback_status_rssshmem(const char *name, uint32_t hash, const char *value, void *dst) {
+    (void)name; (void)hash; (void)value;
+    struct arl_callback_ptr *aptr = (struct arl_callback_ptr *)dst;
+    if(unlikely(procfile_linewords(aptr->ff, aptr->line) < 3)) return;
+
+    aptr->p->values[PDF_RSSSHMEM] = str2kernel_uint_t(procfile_lineword(aptr->ff, aptr->line, 1)) * 1024;
+}
+
+void arl_callback_status_voluntary_ctxt_switches(const char *name, uint32_t hash, const char *value, void *dst) {
+    (void)name; (void)hash; (void)value;
+    struct arl_callback_ptr *aptr = (struct arl_callback_ptr *)dst;
+    if(unlikely(procfile_linewords(aptr->ff, aptr->line) < 2)) return;
+
+    struct pid_stat *p = aptr->p;
+    pid_incremental_rate(stat, PDF_VOLCTX, str2kernel_uint_t(procfile_lineword(aptr->ff, aptr->line, 1)));
+}
+
+void arl_callback_status_nonvoluntary_ctxt_switches(const char *name, uint32_t hash, const char *value, void *dst) {
+    (void)name; (void)hash; (void)value;
+    struct arl_callback_ptr *aptr = (struct arl_callback_ptr *)dst;
+    if(unlikely(procfile_linewords(aptr->ff, aptr->line) < 2)) return;
+
+    struct pid_stat *p = aptr->p;
+    pid_incremental_rate(stat, PDF_NVOLCTX, str2kernel_uint_t(procfile_lineword(aptr->ff, aptr->line, 1)));
+}
+
+bool apps_os_read_pid_status_linux(struct pid_stat *p, void *ptr __maybe_unused) {
+    static struct arl_callback_ptr arl_ptr;
+    static procfile *ff = NULL;
+
+    if(unlikely(!p->status_arl)) {
+        p->status_arl = arl_create("/proc/pid/status", NULL, 60);
+        arl_expect_custom(p->status_arl, "Uid", arl_callback_status_uid, &arl_ptr);
+        arl_expect_custom(p->status_arl, "Gid", arl_callback_status_gid, &arl_ptr);
+        arl_expect_custom(p->status_arl, "VmSize", arl_callback_status_vmsize, &arl_ptr);
+        arl_expect_custom(p->status_arl, "VmRSS", arl_callback_status_vmrss, &arl_ptr);
+        arl_expect_custom(p->status_arl, "RssFile", arl_callback_status_rssfile, &arl_ptr);
+        arl_expect_custom(p->status_arl, "RssShmem", arl_callback_status_rssshmem, &arl_ptr);
+        arl_expect_custom(p->status_arl, "VmSwap", arl_callback_status_vmswap, &arl_ptr);
+        arl_expect_custom(p->status_arl, "voluntary_ctxt_switches", arl_callback_status_voluntary_ctxt_switches, &arl_ptr);
+        arl_expect_custom(p->status_arl, "nonvoluntary_ctxt_switches", arl_callback_status_nonvoluntary_ctxt_switches, &arl_ptr);
+    }
+
+    if(unlikely(!p->status_filename)) {
+        char filename[FILENAME_MAX + 1];
+        snprintfz(filename, FILENAME_MAX, "%s/proc/%d/status", netdata_configured_host_prefix, p->pid);
+        p->status_filename = strdupz(filename);
+    }
+
+    ff = procfile_reopen(ff, p->status_filename, (!ff)?" \t:,-()/":NULL, PROCFILE_FLAG_NO_ERROR_ON_FILE_IO);
+    if(unlikely(!ff)) return false;
+
+    ff = procfile_readall(ff);
+    if(unlikely(!ff)) return false;
+
+    calls_counter++;
+
+    // let ARL use this pid
+    arl_ptr.p = p;
+    arl_ptr.ff = ff;
+
+    size_t lines = procfile_lines(ff), l;
+    arl_begin(p->status_arl);
+
+    for(l = 0; l < lines ;l++) {
+        // debug_log("CHECK: line %zu of %zu, key '%s' = '%s'", l, lines, procfile_lineword(ff, l, 0), procfile_lineword(ff, l, 1));
+        arl_ptr.line = l;
+        if(unlikely(arl_check(p->status_arl,
+                               procfile_lineword(ff, l, 0),
+                               procfile_lineword(ff, l, 1)))) break;
+    }
+
+    p->values[PDF_VMSHARED] = p->values[PDF_RSSFILE] + p->values[PDF_RSSSHMEM];
+    return true;
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// global CPU utilization
+
+bool apps_os_read_global_cpu_utilization_linux(void) {
+    static char filename[FILENAME_MAX + 1] = "";
+    static procfile *ff = NULL;
+    static kernel_uint_t utime_raw = 0, stime_raw = 0, gtime_raw = 0, gntime_raw = 0, ntime_raw = 0;
+    static usec_t collected_usec = 0, last_collected_usec = 0;
+
+    if(unlikely(!ff)) {
+        snprintfz(filename, FILENAME_MAX, "%s/proc/stat", netdata_configured_host_prefix);
+        ff = procfile_open(filename, " \t:", PROCFILE_FLAG_DEFAULT);
+        if(unlikely(!ff)) goto cleanup;
+    }
+
+    ff = procfile_readall(ff);
+    if(unlikely(!ff)) goto cleanup;
+
+    last_collected_usec = collected_usec;
+    collected_usec = now_monotonic_usec();
+
+    calls_counter++;
+
+    // temporary - it is added global_ntime;
+    kernel_uint_t global_ntime = 0;
+
+    incremental_rate(global_utime, utime_raw, str2kernel_uint_t(procfile_lineword(ff, 0,  1)), collected_usec, last_collected_usec, CPU_TO_NANOSECONDCORES);
+    incremental_rate(global_ntime, ntime_raw, str2kernel_uint_t(procfile_lineword(ff, 0,  2)), collected_usec, last_collected_usec, CPU_TO_NANOSECONDCORES);
+    incremental_rate(global_stime, stime_raw, str2kernel_uint_t(procfile_lineword(ff, 0,  3)), collected_usec, last_collected_usec, CPU_TO_NANOSECONDCORES);
+    incremental_rate(global_gtime, gtime_raw, str2kernel_uint_t(procfile_lineword(ff, 0, 10)), collected_usec, last_collected_usec, CPU_TO_NANOSECONDCORES);
+
+    global_utime += global_ntime;
+
+    if(enable_guest_charts) {
+        // temporary - it is added global_ntime;
+        kernel_uint_t global_gntime = 0;
+
+        // guest nice time, on guest time
+        incremental_rate(global_gntime, gntime_raw, str2kernel_uint_t(procfile_lineword(ff, 0, 11)), collected_usec, last_collected_usec, 1);
+
+        global_gtime += global_gntime;
+
+        // remove guest time from user time
+        global_utime -= (global_utime > global_gtime) ? global_gtime : global_utime;
+    }
+
+    if(unlikely(global_iterations_counter == 1)) {
+        global_utime = 0;
+        global_stime = 0;
+        global_gtime = 0;
+    }
+
+    return true;
+
+cleanup:
+    global_utime = 0;
+    global_stime = 0;
+    global_gtime = 0;
+    return false;
+}
+
+// --------------------------------------------------------------------------------------------------------------------
+// /proc/pid/stat
+
+static inline void update_proc_state_count(char proc_stt) {
+    switch (proc_stt) {
+        case 'S':
+            proc_state_count[PROC_STATUS_SLEEPING] += 1;
+            break;
+        case 'R':
+            proc_state_count[PROC_STATUS_RUNNING] += 1;
+            break;
+        case 'D':
+            proc_state_count[PROC_STATUS_SLEEPING_D] += 1;
+            break;
+        case 'Z':
+            proc_state_count[PROC_STATUS_ZOMBIE] += 1;
+            break;
+        case 'T':
+            proc_state_count[PROC_STATUS_STOPPED] += 1;
+            break;
+        default:
+            break;
+    }
+}
+
+bool apps_os_read_pid_stat_linux(struct pid_stat *p, void *ptr __maybe_unused) {
+    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);
+    }
+
+    bool set_quotes = (!ff) ? true : false;
+
+    ff = procfile_reopen(ff, p->stat_filename, NULL, PROCFILE_FLAG_NO_ERROR_ON_FILE_IO);
+    if(unlikely(!ff)) goto cleanup;
+
+    // if(set_quotes) procfile_set_quotes(ff, "()");
+    if(unlikely(set_quotes))
+        procfile_set_open_close(ff, "(", ")");
+
+    ff = procfile_readall(ff);
+    if(unlikely(!ff)) goto cleanup;
+
+    // p->pid           = str2pid_t(procfile_lineword(ff, 0, 0));
+    char *comm          = procfile_lineword(ff, 0, 1);
+    p->state            = *(procfile_lineword(ff, 0, 2));
+    p->ppid             = (int32_t)str2pid_t(procfile_lineword(ff, 0, 3));
+    // p->pgrp          = (int32_t)str2pid_t(procfile_lineword(ff, 0, 4));
+    // p->session       = (int32_t)str2pid_t(procfile_lineword(ff, 0, 5));
+    // p->tty_nr        = (int32_t)str2pid_t(procfile_lineword(ff, 0, 6));
+    // p->tpgid         = (int32_t)str2pid_t(procfile_lineword(ff, 0, 7));
+    // p->flags         = str2uint64_t(procfile_lineword(ff, 0, 8));
+
+    update_pid_comm(p, comm);
+
+    pid_incremental_rate(stat, PDF_MINFLT,  str2kernel_uint_t(procfile_lineword(ff, 0,  9)));
+    pid_incremental_rate(stat, PDF_CMINFLT, str2kernel_uint_t(procfile_lineword(ff, 0, 10)));
+    pid_incremental_rate(stat, PDF_MAJFLT,  str2kernel_uint_t(procfile_lineword(ff, 0, 11)));
+    pid_incremental_rate(stat, PDF_CMAJFLT, str2kernel_uint_t(procfile_lineword(ff, 0, 12)));
+    pid_incremental_cpu(stat, PDF_UTIME,   str2kernel_uint_t(procfile_lineword(ff, 0, 13)));
+    pid_incremental_cpu(stat, PDF_STIME,   str2kernel_uint_t(procfile_lineword(ff, 0, 14)));
+    pid_incremental_cpu(stat, PDF_CUTIME,  str2kernel_uint_t(procfile_lineword(ff, 0, 15)));
+    pid_incremental_cpu(stat, PDF_CSTIME,  str2kernel_uint_t(procfile_lineword(ff, 0, 16)));
+    // p->priority      = str2kernel_uint_t(procfile_lineword(ff, 0, 17));
+    // p->nice          = str2kernel_uint_t(procfile_lineword(ff, 0, 18));
+    p->values[PDF_THREADS] = (int32_t) str2uint32_t(procfile_lineword(ff, 0, 19), NULL);
+    // p->itrealvalue   = str2kernel_uint_t(procfile_lineword(ff, 0, 20));
+    kernel_uint_t collected_starttime = str2kernel_uint_t(procfile_lineword(ff, 0, 21)) / system_hz;
+    p->values[PDF_UPTIME] = (system_uptime_secs > collected_starttime)?(system_uptime_secs - collected_starttime):0;
+    // p->vsize         = str2kernel_uint_t(procfile_lineword(ff, 0, 22));
+    // p->rss           = str2kernel_uint_t(procfile_lineword(ff, 0, 23));
+    // p->rsslim        = str2kernel_uint_t(procfile_lineword(ff, 0, 24));
+    // p->starcode      = str2kernel_uint_t(procfile_lineword(ff, 0, 25));
+    // p->endcode       = str2kernel_uint_t(procfile_lineword(ff, 0, 26));
+    // p->startstack    = str2kernel_uint_t(procfile_lineword(ff, 0, 27));
+    // p->kstkesp       = str2kernel_uint_t(procfile_lineword(ff, 0, 28));
+    // p->kstkeip       = str2kernel_uint_t(procfile_lineword(ff, 0, 29));
+    // p->signal        = str2kernel_uint_t(procfile_lineword(ff, 0, 30));
+    // p->blocked       = str2kernel_uint_t(procfile_lineword(ff, 0, 31));
+    // p->sigignore     = str2kernel_uint_t(procfile_lineword(ff, 0, 32));
+    // p->sigcatch      = str2kernel_uint_t(procfile_lineword(ff, 0, 33));
+    // p->wchan         = str2kernel_uint_t(procfile_lineword(ff, 0, 34));
+    // p->nswap         = str2kernel_uint_t(procfile_lineword(ff, 0, 35));
+    // p->cnswap        = str2kernel_uint_t(procfile_lineword(ff, 0, 36));
+    // p->exit_signal   = str2kernel_uint_t(procfile_lineword(ff, 0, 37));
+    // p->processor     = str2kernel_uint_t(procfile_lineword(ff, 0, 38));
+    // p->rt_priority   = str2kernel_uint_t(procfile_lineword(ff, 0, 39));
+    // p->policy        = str2kernel_uint_t(procfile_lineword(ff, 0, 40));
+    // p->delayacct_blkio_ticks = str2kernel_uint_t(procfile_lineword(ff, 0, 41));
+
+    if(enable_guest_charts) {
+        pid_incremental_cpu(stat, PDF_GTIME,  str2kernel_uint_t(procfile_lineword(ff, 0, 42)));
+        pid_incremental_cpu(stat, PDF_CGTIME, str2kernel_uint_t(procfile_lineword(ff, 0, 43)));
+
+        if (show_guest_time || p->values[PDF_GTIME] || p->values[PDF_CGTIME]) {
+            p->values[PDF_UTIME] -= (p->values[PDF_UTIME] >= p->values[PDF_GTIME]) ? p->values[PDF_GTIME] : p->values[PDF_UTIME];
+            p->values[PDF_CUTIME] -= (p->values[PDF_CUTIME] >= p->values[PDF_CGTIME]) ? p->values[PDF_CGTIME] : p->values[PDF_CUTIME];
+            show_guest_time = true;
+        }
+    }
+
+    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) VALUES: utime=" KERNEL_UINT_FORMAT ", stime=" KERNEL_UINT_FORMAT ", cutime=" KERNEL_UINT_FORMAT ", cstime=" KERNEL_UINT_FORMAT ", minflt=" KERNEL_UINT_FORMAT ", majflt=" KERNEL_UINT_FORMAT ", cminflt=" KERNEL_UINT_FORMAT ", cmajflt=" KERNEL_UINT_FORMAT ", threads=" KERNEL_UINT_FORMAT,
+                      netdata_configured_host_prefix, p->pid, pid_stat_comm(p), (p->target)?string2str(p->target->name):"UNSET", p->stat_collected_usec - p->last_stat_collected_usec,
+                      p->values[PDF_UTIME],
+                      p->values[PDF_STIME],
+                      p->values[PDF_CUTIME],
+                      p->values[PDF_CSTIME],
+                      p->values[PDF_MINFLT],
+                      p->values[PDF_MAJFLT],
+                      p->values[PDF_CMINFLT],
+                      p->values[PDF_CMAJFLT],
+                      p->values[PDF_THREADS]);
+
+    update_proc_state_count(p->state);
+    return true;
+
+cleanup:
+    return false;
+}
+
+// ----------------------------------------------------------------------------
+
+// 1. read all files in /proc
+// 2. for each numeric directory:
+//    i.   read /proc/pid/stat
+//    ii.  read /proc/pid/status
+//    iii. read /proc/pid/io (requires root access)
+//    iii. read the entries in directory /proc/pid/fd (requires root access)
+//         for each entry:
+//         a. find or create a struct file_descriptor
+//         b. cleanup any old/unused file_descriptors
+
+// after all these, some pids may be linked to targets, while others may not
+
+// in case of errors, only 1 every 1000 errors is printed
+// to avoid filling up all disk space
+// if debug is enabled, all errors are printed
+
+bool apps_os_collect_all_pids_linux(void) {
+#if (PROCESSES_HAVE_STATE == 1)
+    // clear process state counter
+    memset(proc_state_count, 0, sizeof proc_state_count);
+#endif
+
+    // preload the parents and then their children
+    collect_parents_before_children();
+
+    static char uptime_filename[FILENAME_MAX + 1] = "";
+    if(*uptime_filename == '\0')
+        snprintfz(uptime_filename, FILENAME_MAX, "%s/proc/uptime", netdata_configured_host_prefix);
+
+    system_uptime_secs = (kernel_uint_t)(uptime_msec(uptime_filename) / MSEC_PER_SEC);
+
+    char dirname[FILENAME_MAX + 1];
+
+    snprintfz(dirname, FILENAME_MAX, "%s/proc", netdata_configured_host_prefix);
+    DIR *dir = opendir(dirname);
+    if(!dir) return false;
+
+    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;
+
+        incrementally_collect_data_for_pid(pid, NULL);
+    }
+    closedir(dir);
+
+    return true;
+}
+#endif

Some files were not shown because too many files changed in this diff