]> arthur.barton.de Git - netdata.git/commitdiff
Merge pull request #1568 from l2isbad/varnish_plugin
authorCosta Tsaousis <costa@tsaousis.gr>
Wed, 18 Jan 2017 07:41:43 +0000 (09:41 +0200)
committerGitHub <noreply@github.com>
Wed, 18 Jan 2017 07:41:43 +0000 (09:41 +0200)
varnish plugin (varnishstat)

44 files changed:
CMakeLists.txt
conf.d/health.d/disks.conf
conf.d/health.d/net.conf
configs.signatures
configure.ac
m4/ax_c_statement_expressions.m4 [new file with mode: 0644]
src/Makefile.am
src/backends.c
src/common.c
src/common.h
src/global_statistics.c
src/health.c
src/health.h
src/inlined.h [new file with mode: 0644]
src/macos_sysctl.c
src/main.c
src/main.h
src/plugin_checks.c
src/plugin_freebsd.c
src/plugin_idlejitter.c
src/plugin_macos.c
src/plugin_macos.h
src/plugin_nfacct.c
src/plugin_proc.c
src/plugin_proc_diskspace.c [new file with mode: 0644]
src/plugin_proc_diskspace.h [new file with mode: 0644]
src/plugin_tc.c
src/plugin_tc.h
src/plugins_d.c
src/plugins_d.h
src/proc_diskstats.c
src/proc_net_dev.c
src/proc_self_mountinfo.c
src/proc_self_mountinfo.h
src/rrd.c
src/simple_pattern.c [new file with mode: 0644]
src/simple_pattern.h [new file with mode: 0644]
src/sys_devices_system_edac_mc.c
src/sys_fs_cgroup.c
src/web_client.c
src/web_server.c
web/dashboard.js
web/dashboard_info.js
web/index.html

index 49ae6b0c88d6711250c27b134a32871ad3a7645a..4daf76cb5f90515dab482e5a20afa3fb97a406c2 100755 (executable)
@@ -101,7 +101,7 @@ set(NETDATA_SOURCE_FILES
         src/registry_person.c
         src/registry_person.h
         src/registry_machine.c
-        src/registry_machine.h src/registry_internals.c src/registry_init.c src/registry_db.c src/registry_log.c src/proc_uptime.c src/sys_devices_system_edac_mc.c)
+        src/registry_machine.h src/registry_internals.c src/registry_init.c src/registry_db.c src/registry_log.c src/proc_uptime.c src/sys_devices_system_edac_mc.c src/plugin_proc_diskspace.c src/plugin_proc_diskspace.h src/simple_pattern.c src/simple_pattern.h src/inlined.h)
 
 set(APPS_PLUGIN_SOURCE_FILES
         src/appconfig.c
@@ -126,5 +126,5 @@ add_definitions(-DHAVE_CONFIG_H -DCACHE_DIR="/var/cache/netdata" -DCONFIG_DIR="/
 add_executable(netdata ${NETDATA_SOURCE_FILES})
 target_link_libraries (netdata m z uuid ${CMAKE_THREAD_LIBS_INIT})
 
-add_executable(apps.plugin ${APPS_PLUGIN_SOURCE_FILES})
+add_executable(apps.plugin ${APPS_PLUGIN_SOURCE_FILES} src/inlined.h)
 target_link_libraries (apps.plugin m ${CMAKE_THREAD_LIBS_INIT})
index 8e3753164e53e0ea153c7f30107b0e459dcd8b53..0549bac268b51ea0034c1b8b6edc025d5d2a05d2 100644 (file)
@@ -4,6 +4,7 @@
 # for mount points
 template: disk_space_last_collected_secs
       on: disk.space
+families: *
     calc: $now - $last_collected_t
    units: seconds ago
    every: 10s
@@ -16,6 +17,7 @@ template: disk_space_last_collected_secs
 # for block devices
 template: disk_last_collected_secs
       on: disk.io
+families: *
     calc: $now - $last_collected_t
    units: seconds ago
    every: 10s
@@ -35,6 +37,7 @@ template: disk_last_collected_secs
 
 template: disk_space_usage
       on: disk.space
+families: *
     calc: $used * 100 / ($avail + $used)
    units: %
    every: 1m
@@ -46,6 +49,7 @@ template: disk_space_usage
 
 template: disk_inode_usage
       on: disk.inodes
+families: *
     calc: $used * 100 / ($avail + $used)
    units: %
    every: 1m
@@ -69,6 +73,7 @@ template: disk_inode_usage
 
 template: disk_fill_rate
       on: disk.space
+families: *
   lookup: min -10m at -50m unaligned of avail
     calc: ($this - $avail) / (($now - $after) / 3600)
    every: 1m
@@ -82,6 +87,7 @@ template: disk_fill_rate
 
 template: out_of_disk_space_time
       on: disk.space
+families: *
     calc: ($disk_fill_rate > 0) ? ($avail / $disk_fill_rate) : (0)
    units: hours
    every: 10s
@@ -101,6 +107,7 @@ template: out_of_disk_space_time
 
 template: 10min_disk_utilization
       on: disk.util
+families: *
   lookup: average -10m unaligned
    units: %
    every: 1m
@@ -120,6 +127,7 @@ template: 10min_disk_utilization
 
 template: 10min_disk_backlog
       on: disk.backlog
+families: *
   lookup: average -10m unaligned
    units: ms
    every: 1m
index f969f17b9c47cbe0e2f109392e9656d6311ebfad..11f7c43e7bf9480c5b2ffbd09b6dfd7cc21fedb5 100644 (file)
@@ -3,6 +3,7 @@
 
 template: interface_last_collected_secs
       on: net.net
+families: *
     calc: $now - $last_collected_t
    units: seconds ago
    every: 10s
@@ -22,6 +23,7 @@ template: interface_last_collected_secs
 
 template: 1hour_packet_drops_inbound
       on: net.drops
+families: *
   lookup: sum -1h unaligned absolute of inbound
    units: packets
    every: 1m
@@ -32,6 +34,7 @@ template: 1hour_packet_drops_inbound
 
 template: 1hour_packet_drops_outbound
       on: net.drops
+families: *
   lookup: sum -1h unaligned absolute of outbound
    units: packets
    every: 1m
@@ -42,6 +45,7 @@ template: 1hour_packet_drops_outbound
 
 template: 1hour_packet_drops_ratio_inbound
       on: net.packets
+families: *
   lookup: sum -1h unaligned absolute of received
     calc: (($1hour_packet_drops_inbound != nan AND $this > 0) ? ($1hour_packet_drops_inbound * 100 / $this) : (0))
    units: %
@@ -54,6 +58,7 @@ template: 1hour_packet_drops_ratio_inbound
 
 template: 1hour_packet_drops_ratio_outbound
       on: net.packets
+families: *
   lookup: sum -1h unaligned absolute of sent
     calc: (($1hour_packet_drops_outbound != nan AND $this > 0) ? ($1hour_packet_drops_outbound * 100 / $this) : (0))
    units: %
@@ -75,6 +80,7 @@ template: 1hour_packet_drops_ratio_outbound
 
 template: 1hour_fifo_errors
       on: net.fifo
+families: *
   lookup: sum -1h unaligned absolute
    units: errors
    every: 1m
@@ -95,6 +101,7 @@ template: 1hour_fifo_errors
 
 template: 1m_received_packets_rate
       on: net.packets
+families: *
   lookup: average -1m of received
    units: packets
    every: 10s
@@ -102,6 +109,7 @@ template: 1m_received_packets_rate
 
 template: 10s_received_packets_storm
       on: net.packets
+families: *
   lookup: average -10s of received
     calc: $this * 100 / (($1m_received_packets_rate < 1000)?(1000):($1m_received_packets_rate))
    every: 10s
index fa9bda88ccc0d1eaa97f0e0fe4c083f210033ace..3b6ec0090e1cb8353d001213db425638a64b2a43 100644 (file)
@@ -18,6 +18,7 @@ declare -A configs_signatures=(
   ['08ff5218f938fc48e09e718821169d14']='health.d/redis.conf'
   ['091572888425bc3b8b559c3c53367ec7']='apps_groups.conf'
   ['09264cec953ae1c4c2985e6446abb386']='health.d/mysql.conf'
+  ['093540fdc2a228e976ce5d48a3adf9fc']='health.d/disks.conf'
   ['0c5e0fa364d7bdf7c16e8459a0544572']='health.d/netfilter.conf'
   ['0cd4e1fb57497e4d4c2451a9e58f724d']='python.d/redis.conf'
   ['0d29fe9919a2975107db1f2583344e7a']='health.d/mdstat.conf'
@@ -38,6 +39,7 @@ declare -A configs_signatures=(
   ['1ef0fd38e7969c023bc3fa6d89eaf6d6']='python.d/mdstat.conf'
   ['1f5545b3ff52b3eb75ee05401f67a9bc']='fping.conf'
   ['1fa47f32ab52a22f8e7087cae85dd25e']='health.d/net.conf'
+  ['203678a5be09d65993dcb3a9d475d187']='health.d/ipfs.conf'
   ['20be73f473e59bc7de1fe61d53466aba']='health.d/ram.conf'
   ['21924a6ab8008d16ffac340f226ebad9']='python.d/nginx.conf'
   ['219c5bb81965fa17d4940d4aa343c282']='health.d/mysql.conf'
@@ -95,6 +97,7 @@ declare -A configs_signatures=(
   ['4332dee96e4f38fc73c962df3494ab7c']='health_alarm_notify.conf'
   ['43ebb7f224c3b232d8ad044d7e9508b6']='health.d/net.conf'
   ['444e20cf75e2cd019e8d412d5d1f4a7f']='charts.d/cpu_apps.conf'
+  ['4461bfacf9a3da47770fb3ca31f4c91f']='health.d/net.conf'
   ['450667c552ab7a7d8d4a2c214fdacca5']='health.d/entropy.conf'
   ['45a77ac36ba9f1898144b902de17204b']='health.d/memcached.conf'
   ['46798cda21e1a5faa769abf4e5d27c48']='health.d/disks.conf'
@@ -331,6 +334,7 @@ declare -A configs_signatures=(
   ['e449e5582279742496550df14b6fca95']='health.d/entropy.conf'
   ['e5f32f54d6d6728f21f9ac26f37d6573']='python.d/example.conf'
   ['e734c5951a8764d4d9de046dd7cf7407']='health.d/softnet.conf'
+  ['e7bc22a1942cffbd2b1b0cfd119ee328']='health.d/ipfs.conf'
   ['e8ec8046c7007af6ca3e8c51e62c99f8']='health.d/disks.conf'
   ['eb5168f0b516bc982aac45e59da6e52e']='health.d/nginx.conf'
   ['eb748d6fb69d11b0d29c5794657e206c']='health.d/qos.conf'
index 872427944e1acdc8ef587921948211d7cca42b31..002a3aa599acd58d83fcb8c0ee407860d03159ac 100644 (file)
@@ -129,6 +129,7 @@ AC_C_INLINE
 AC_FUNC_STRERROR_R
 AC_C__GENERIC
 AC_C___ATOMIC
+AC_C_STMT_EXPR
 AC_CHECK_SIZEOF([void *])
 AC_CANONICAL_HOST
 AC_HEADER_MAJOR
diff --git a/m4/ax_c_statement_expressions.m4 b/m4/ax_c_statement_expressions.m4
new file mode 100644 (file)
index 0000000..fb259e7
--- /dev/null
@@ -0,0 +1,23 @@
+# AC_C_STMT_EXPR
+# -------------
+# Define HAVE_STMT_EXPR if compiler has statement expressions.
+AN_IDENTIFIER([_Generic], [AC_C_STMT_EXPR])
+AC_DEFUN([AC_C_STMT_EXPR],
+[AC_CACHE_CHECK([for statement expressions], ac_cv_c_stmt_expr,
+[AC_COMPILE_IFELSE(
+   [AC_LANG_SOURCE(
+      [[int
+        main (int argc, char **argv)
+        {
+          int x = ({ int y = 1; y; });
+          return x;
+        }
+      ]])],
+   [ac_cv_c_stmt_expr=yes],
+   [ac_cv_c_stmt_expr=no])])
+if test $ac_cv_c_stmt_expr = yes; then
+  AC_DEFINE([HAVE_STMT_EXPR], 1,
+           [Define to 1 if compiler supports statement expressions.])
+fi
+])# AC_C_STMT_EXPR
+
index 5533c95167cfb72f5947c90785f54a7630a621c1..b5c83883694632b8b555878eee0aa489cfc42813 100644 (file)
@@ -24,7 +24,9 @@ dist_cache_DATA = .keep
 dist_varlib_DATA = .keep
 dist_registry_DATA = .keep
 dist_log_DATA = .keep
+if !MACOS
 plugins_PROGRAMS = apps.plugin
+endif
 
 netdata_SOURCES = \
        appconfig.c appconfig.h \
@@ -37,6 +39,7 @@ netdata_SOURCES = \
        eval.c eval.h \
        global_statistics.c global_statistics.h \
        health.c health.h \
+       inlined.h \
        log.c log.h \
        main.c main.h \
        plugin_checks.c plugin_checks.h \
@@ -46,6 +49,7 @@ netdata_SOURCES = \
        plugins_d.c plugins_d.h \
        popen.c popen.h \
        socket.c socket.h \
+       simple_pattern.c simple_pattern.h \
        sys_fs_cgroup.c \
        sys_devices_system_edac_mc.c \
        procfile.c procfile.h \
@@ -86,6 +90,7 @@ else
 netdata_SOURCES += \
        ipc.c ipc.h \
        plugin_proc.c plugin_proc.h \
+       plugin_proc_diskspace.c plugin_proc_diskspace.h \
        proc_diskstats.c \
        proc_interrupts.c \
        proc_softirqs.c \
@@ -122,6 +127,7 @@ apps_plugin_SOURCES = \
        avl.c avl.h \
        clocks.c clocks.h \
        common.c common.h \
+       inlined.h \
        log.c log.h \
        procfile.c procfile.h \
        web_buffer.c web_buffer.h \
index a3b2231dbca2dc0ef77597b77b846dae564075fe..1272d0473d585d01c38ab77de132025170884dbc 100644 (file)
@@ -129,7 +129,7 @@ static inline int process_opentsdb_response(BUFFER *b) {
 }
 
 void *backends_main(void *ptr) {
-    (void)ptr;
+    struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr;
 
     BUFFER *b = buffer_create(1), *response = buffer_create(1);
     int (*backend_request_formatter)(BUFFER *b, const char *prefix, RRDHOST *host, const char *hostname, RRDSET *st, RRDDIM *rd, time_t after, time_t before, uint32_t options) = NULL;
@@ -543,6 +543,7 @@ cleanup:
 
     info("BACKEND thread exiting");
 
+    static_thread->enabled = 0;
     pthread_exit(NULL);
     return NULL;
 }
index 8abd2d447ba44213981a029a50882936a5c620de..f6f2ee1a40c761e0a95a4fdcf0a614d7767ee3a7 100644 (file)
@@ -807,7 +807,7 @@ uint32_t simple_hash(const char *name)
 }
 */
 
-
+/*
 // http://isthe.com/chongo/tech/comp/fnv/#FNV-1a
 uint32_t simple_hash(const char *name) {
     unsigned char *s = (unsigned char *) name;
@@ -842,6 +842,7 @@ uint32_t simple_uhash(const char *name) {
     }
     return hval;
 }
+*/
 
 /*
 // http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx
@@ -1100,6 +1101,18 @@ int processors = 1;
 long get_system_cpus(void) {
     processors = 1;
 
+    #ifdef __APPLE__
+        int32_t tmp_processors;
+
+        if (unlikely(GETSYSCTL("hw.logicalcpu", tmp_processors))) {
+            error("Assuming system has %d processors.", processors);
+        } else {
+            processors = tmp_processors;
+        }
+
+        return processors;
+    #else
+
     char filename[FILENAME_MAX + 1];
     snprintfz(filename, FILENAME_MAX, "%s/proc/stat", global_host_prefix);
 
@@ -1129,10 +1142,19 @@ long get_system_cpus(void) {
 
     debug(D_SYSTEM, "System has %d processors.", processors);
     return processors;
+
+    #endif /* __APPLE__ */
 }
 
 pid_t pid_max = 32768;
 pid_t get_system_pid_max(void) {
+    #ifdef __APPLE__
+        // As we currently do not know a solution to query pid_max from the os
+        // we use the number defined in bsd/sys/proc_internal.h in XNU sources
+        pid_max = 99999;
+        return pid_max;
+    #else
+
     char filename[FILENAME_MAX + 1];
     snprintfz(filename, FILENAME_MAX, "%s/proc/sys/kernel/pid_max", global_host_prefix);
     procfile *ff = procfile_open(filename, NULL, PROCFILE_FLAG_DEFAULT);
@@ -1158,6 +1180,8 @@ pid_t get_system_pid_max(void) {
     procfile_close(ff);
     debug(D_SYSTEM, "System supports %d pids.", pid_max);
     return pid_max;
+
+    #endif /* __APPLE__ */
 }
 
 unsigned int hz;
@@ -1165,150 +1189,8 @@ void get_system_HZ(void) {
     long ticks;
 
     if ((ticks = sysconf(_SC_CLK_TCK)) == -1) {
-        perror("sysconf");
+        error("Cannot get system clock ticks");
     }
 
     hz = (unsigned int) ticks;
 }
-
-int read_single_number_file(const char *filename, unsigned long long *result) {
-    char buffer[1024 + 1];
-
-    int fd = open(filename, O_RDONLY, 0666);
-    if(unlikely(fd == -1)) return 1;
-
-    ssize_t r = read(fd, buffer, 1024);
-    if(unlikely(r == -1)) {
-        close(fd);
-        return 2;
-    }
-
-    close(fd);
-    *result = strtoull(buffer, NULL, 0);
-    return 0;
-}
-
-// ----------------------------------------------------------------------------
-// simple_pattern_match
-
-struct simple_pattern {
-    const char *match;
-    size_t len;
-    NETDATA_SIMPLE_PREFIX_MODE mode;
-    struct simple_pattern *next;
-};
-
-NETDATA_SIMPLE_PATTERN *netdata_simple_pattern_list_create(const char *list, NETDATA_SIMPLE_PREFIX_MODE default_mode) {
-    struct simple_pattern *root = NULL;
-
-    if(unlikely(!list || !*list)) return root;
-
-    char *a = strdupz(list);
-    if(a && *a) {
-        char *s = a;
-
-        while(s && *s) {
-            // skip all spaces
-            while(isspace(*s)) s++;
-
-            // empty string
-            if(unlikely(!*s)) break;
-
-            // find the next space
-            char *c = s;
-            while(*c && !isspace(*c)) c++;
-
-            // find the next word
-            char *n;
-            if(likely(*c)) n = c + 1;
-            else n = NULL;
-
-            // terminate our string
-            *c = '\0';
-
-            char buf[100 + 1];
-            strncpy(buf, s, 100);
-            buf[100] = '\0';
-            if(likely(n)) *c = ' ';
-            s = buf;
-
-            NETDATA_SIMPLE_PREFIX_MODE mode;
-            size_t len = strlen(s);
-            if(len >= 2 && *s == '*' && s[len - 1] == '*') {
-                s[len - 1] = '\0';
-                s++;
-                len -= 2;
-                mode = NETDATA_SIMPLE_PATTERN_MODE_SUBSTRING;
-            }
-            else if(len >= 1 && *s == '*') {
-                s++;
-                len--;
-                mode = NETDATA_SIMPLE_PATTERN_MODE_SUFFIX;
-            }
-            else if(len >= 1 && s[len - 1] == '*') {
-                s[len - 1] = '\0';
-                len--;
-                mode = NETDATA_SIMPLE_PATTERN_MODE_PREFIX;
-            }
-            else
-                mode = default_mode;
-
-            if(len) {
-                if(*s == '*')
-                    error("simple pattern '%s' includes '%s' that is invalid", a, s);
-
-                // allocate the structure
-                struct simple_pattern *m = mallocz(sizeof(struct simple_pattern));
-                m->match = strdup(s);
-                m->len = strlen(m->match);
-                m->mode = mode;
-                m->next = root;
-                root = m;
-            }
-            else
-                error("simple pattern '%s' includes invalid matches", a);
-
-            // prepare for next loop
-            s = n;
-        }
-    }
-
-    free(a);
-    return (NETDATA_SIMPLE_PATTERN *)root;
-}
-
-int netdata_simple_pattern_list_matches(NETDATA_SIMPLE_PATTERN *list, const char *str) {
-    struct simple_pattern *m, *root = (struct simple_pattern *)list;
-
-    if(unlikely(!root)) return 0;
-
-    size_t len = strlen(str);
-    for(m = root; m ; m = m->next) {
-        if(m->len <= len) {
-            switch(m->mode) {
-                case NETDATA_SIMPLE_PATTERN_MODE_SUBSTRING:
-                    if(unlikely(strstr(str, m->match)))
-                        return 1;
-                    break;
-
-                case NETDATA_SIMPLE_PATTERN_MODE_PREFIX:
-                    if(unlikely(strncmp(str, m->match, m->len) == 0))
-                        return 1;
-                    break;
-
-                case NETDATA_SIMPLE_PATTERN_MODE_SUFFIX:
-                    if(unlikely(strcmp(&str[len - m->len], m->match) == 0))
-                        return 1;
-                    break;
-
-                case NETDATA_SIMPLE_PATTERN_MODE_EXACT:
-                default:
-                    if(unlikely(strcmp(str, m->match) == 0))
-                        return 1;
-                    break;
-            }
-        }
-    }
-
-    return 0;
-}
index de4ff7a5d89190d06b3cccc950f50ee71e906760..26ad467db2f00b6eeb471db070194bd89706e404 100644 (file)
 // ----------------------------------------------------------------------------
 // netdata include files
 
+#include "simple_pattern.h"
 #include "avl.h"
 #include "clocks.h"
 #include "log.h"
 #include "plugin_macos.h"
 #else
 #include "plugin_proc.h"
+#include "plugin_proc_diskspace.h"
 #endif /* __FreeBSD__, __APPLE__*/
 
 #include "plugin_tc.h"
 #include "unit_test.h"
 #include "ipc.h"
 #include "backends.h"
+#include "inlined.h"
 
 extern void netdata_fix_chart_id(char *s);
 extern void netdata_fix_chart_name(char *s);
 
-extern uint32_t simple_hash(const char *name);
-extern uint32_t simple_uhash(const char *name);
-
 extern void strreverse(char* begin, char* end);
 extern char *mystrsep(char **ptr, char *s);
 extern char *trim(char *s);
@@ -275,16 +275,4 @@ extern void get_system_HZ(void);
 #endif
 #endif
 
-extern int read_single_number_file(const char *filename, unsigned long long *result);
-
-typedef enum {
-    NETDATA_SIMPLE_PATTERN_MODE_EXACT,
-    NETDATA_SIMPLE_PATTERN_MODE_PREFIX,
-    NETDATA_SIMPLE_PATTERN_MODE_SUFFIX,
-    NETDATA_SIMPLE_PATTERN_MODE_SUBSTRING
-} NETDATA_SIMPLE_PREFIX_MODE;
-typedef void NETDATA_SIMPLE_PATTERN;
-extern NETDATA_SIMPLE_PATTERN *netdata_simple_pattern_list_create(const char *list, NETDATA_SIMPLE_PREFIX_MODE default_mode);
-extern int netdata_simple_pattern_list_matches(NETDATA_SIMPLE_PATTERN *list, const char *str);
-
 #endif /* NETDATA_COMMON_H */
index bb2b1f08f7bf6dc6941e17766d34d39dff183627..a698615f4e770aaf9e947b1b7419f44e8a42872a 100644 (file)
@@ -131,7 +131,7 @@ void global_statistics_charts(void) {
 
     if (!stcpu_thread) stcpu_thread = rrdset_find("netdata.plugin_proc_cpu");
     if (!stcpu_thread) {
-        stcpu_thread = rrdset_create("netdata", "plugin_proc_cpu", NULL, "proc.internal", NULL,
+        stcpu_thread = rrdset_create("netdata", "plugin_proc_cpu", NULL, "proc", NULL,
                                      "NetData Proc Plugin CPU usage", "milliseconds/s", 132000, rrd_update_every,
                                      RRDSET_TYPE_STACKED);
 
index dd2d82644e481b60ec6870dc7f9137b039021232..31fe8e06c48fa85c8780d63240cf72fa4e13f48d 100755 (executable)
@@ -1400,7 +1400,8 @@ void rrdcalctemplate_link_matching(RRDSET *st) {
     RRDCALCTEMPLATE *rt;
 
     for(rt = st->rrdhost->templates; rt ; rt = rt->next) {
-        if(rt->hash_context == st->hash_context && !strcmp(rt->context, st->context)) {
+        if(rt->hash_context == st->hash_context && !strcmp(rt->context, st->context)
+                && (!rt->family_pattern || simple_pattern_matches(rt->family_pattern, st->family))) {
             RRDCALC *rc = rrdcalc_create(st->rrdhost, rt, st->id);
             if(unlikely(!rc))
                 error("Health tried to create alarm from template '%s', but it failed", rt->name);
@@ -1436,6 +1437,9 @@ static inline void rrdcalctemplate_free(RRDHOST *host, RRDCALCTEMPLATE *rt) {
     expression_free(rt->warning);
     expression_free(rt->critical);
 
+    freez(rt->family_match);
+    simple_pattern_free(rt->family_pattern);
+
     freez(rt->name);
     freez(rt->exec);
     freez(rt->recipient);
@@ -1455,6 +1459,7 @@ static inline void rrdcalctemplate_free(RRDHOST *host, RRDCALCTEMPLATE *rt) {
 #define HEALTH_ALARM_KEY "alarm"
 #define HEALTH_TEMPLATE_KEY "template"
 #define HEALTH_ON_KEY "on"
+#define HEALTH_FAMILIES_KEY "families"
 #define HEALTH_LOOKUP_KEY "lookup"
 #define HEALTH_CALC_KEY "calc"
 #define HEALTH_EVERY_KEY "every"
@@ -1825,13 +1830,14 @@ static inline void strip_quotes(char *s) {
 int health_readfile(const char *path, const char *filename) {
     debug(D_HEALTH, "Health configuration reading file '%s/%s'", path, filename);
 
-    static uint32_t hash_alarm = 0, hash_template = 0, hash_on = 0, hash_calc = 0, hash_green = 0, hash_red = 0, hash_warn = 0, hash_crit = 0, hash_exec = 0, hash_every = 0, hash_lookup = 0, hash_units = 0, hash_info = 0, hash_recipient = 0, hash_delay = 0;
+    static uint32_t hash_alarm = 0, hash_template = 0, hash_on = 0, hash_families = 0, hash_calc = 0, hash_green = 0, hash_red = 0, hash_warn = 0, hash_crit = 0, hash_exec = 0, hash_every = 0, hash_lookup = 0, hash_units = 0, hash_info = 0, hash_recipient = 0, hash_delay = 0;
     char buffer[HEALTH_CONF_MAX_LINE + 1];
 
     if(unlikely(!hash_alarm)) {
         hash_alarm = simple_uhash(HEALTH_ALARM_KEY);
         hash_template = simple_uhash(HEALTH_TEMPLATE_KEY);
         hash_on = simple_uhash(HEALTH_ON_KEY);
+        hash_families = simple_uhash(HEALTH_FAMILIES_KEY);
         hash_calc = simple_uhash(HEALTH_CALC_KEY);
         hash_lookup = simple_uhash(HEALTH_LOOKUP_KEY);
         hash_green = simple_uhash(HEALTH_GREEN_KEY);
@@ -1951,7 +1957,7 @@ int health_readfile(const char *path, const char *filename) {
                 if(rc->chart) {
                     if(strcmp(rc->chart, value))
                         error("Health configuration at line %zu of file '%s/%s' for alarm '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
-                             line, path, filename, rc->name, key, rc->chart, value, value);
+                                line, path, filename, rc->name, key, rc->chart, value, value);
 
                     freez(rc->chart);
                 }
@@ -2066,17 +2072,23 @@ int health_readfile(const char *path, const char *filename) {
                 if(rt->context) {
                     if(strcmp(rt->context, value))
                         error("Health configuration at line %zu of file '%s/%s' for template '%s' has key '%s' twice, once with value '%s' and later with value '%s'. Using ('%s').",
-                             line, path, filename, rt->name, key, rt->context, value, value);
+                                line, path, filename, rt->name, key, rt->context, value, value);
 
                     freez(rt->context);
                 }
                 rt->context = tabs2spaces(strdupz(value));
                 rt->hash_context = simple_hash(rt->context);
             }
+            else if(hash == hash_families && !strcasecmp(key, HEALTH_FAMILIES_KEY)) {
+                freez(rt->family_match);
+                simple_pattern_free(rt->family_pattern);
+
+                rt->family_match = tabs2spaces(strdupz(value));
+                rt->family_pattern = simple_pattern_create(rt->family_match, SIMPLE_PATTERN_EXACT);
+            }
             else if(hash == hash_lookup && !strcasecmp(key, HEALTH_LOOKUP_KEY)) {
                 health_parse_db_lookup(line, path, filename, value, &rt->group, &rt->after, &rt->before,
-                                       &rt->update_every,
-                                       &rt->options, &rt->dimensions);
+                                       &rt->update_every, &rt->options, &rt->dimensions);
             }
             else if(hash == hash_every && !strcasecmp(key, HEALTH_EVERY_KEY)) {
                 if(!health_parse_duration(value, &rt->update_every))
@@ -2806,7 +2818,7 @@ static inline int rrdcalc_isrunnable(RRDCALC *rc, time_t now, time_t *next_run)
 }
 
 void *health_main(void *ptr) {
-    (void)ptr;
+    struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr;
 
     info("HEALTH thread created with task id %d", gettid());
 
@@ -3113,6 +3125,8 @@ void *health_main(void *ptr) {
     buffer_free(wb);
 
     info("HEALTH thread exiting");
+
+    static_thread->enabled = 0;
     pthread_exit(NULL);
     return NULL;
 }
index 5b4d8538254447ede04abfa03a83422fa091de78..79831d4fc563257a24ce26cd7e9f42bad1036649 100644 (file)
@@ -232,6 +232,9 @@ typedef struct rrdcalctemplate {
     char *context;
     uint32_t hash_context;
 
+    char *family_match;
+    SIMPLE_PATTERN *family_pattern;
+
     char *source;                   // the source of this alarm
     char *units;                    // the units of the alarm
     char *info;                     // a short description of the alarm
diff --git a/src/inlined.h b/src/inlined.h
new file mode 100644 (file)
index 0000000..c862bf8
--- /dev/null
@@ -0,0 +1,79 @@
+#ifndef NETDATA_INLINED_H
+#define NETDATA_INLINED_H
+
+#include "common.h"
+
+#ifdef HAVE_STMT_EXPR
+// GCC extension to define a function as a preprocessor macro
+
+#define simple_hash(name) ({                                         \
+    register unsigned char *__hash_source = (unsigned char *)(name); \
+    register uint32_t __hash_value = 0x811c9dc5;                     \
+    while (*__hash_source) {                                         \
+        __hash_value *= 16777619;                                    \
+        __hash_value ^= (uint32_t) *__hash_source++;                 \
+    }                                                                \
+    __hash_value;                                                    \
+})
+
+#define simple_uhash(name) ({                                        \
+    register unsigned char *__hash_source = (unsigned char *)(name); \
+    register uint32_t __hash_value = 0x811c9dc5, __hash_char;        \
+    while ((__hash_char = *__hash_source++)) {                       \
+        if (unlikely(__hash_char >= 'A' && __hash_char <= 'Z'))      \
+            __hash_char += 'a' - 'A';                                \
+        __hash_value *= 16777619;                                    \
+        __hash_value ^= __hash_char;                                 \
+    }                                                                \
+    __hash_value;                                                    \
+})
+
+#else /* ! HAVE_STMT_EXPR */
+
+// for faster execution, allow the compiler to inline
+// these functions that are called to hash strings
+static inline uint32_t simple_hash(const char *name) {
+    register unsigned char *s = (unsigned char *) name;
+    register uint32_t hval = 0x811c9dc5;
+    while (*s) {
+        hval *= 16777619;
+        hval ^= (uint32_t) *s++;
+    }
+    return hval;
+}
+
+static inline uint32_t simple_uhash(const char *name) {
+    register unsigned char *s = (unsigned char *) name;
+    register uint32_t hval = 0x811c9dc5, c;
+    while ((c = *s++)) {
+        if (unlikely(c >= 'A' && c <= 'Z')) c += 'a' - 'A';
+        hval *= 16777619;
+        hval ^= c;
+    }
+    return hval;
+}
+
+#endif /* HAVE_STMT_EXPR */
+
+static inline int read_single_number_file(const char *filename, unsigned long long *result) {
+    char buffer[1024 + 1];
+
+    int fd = open(filename, O_RDONLY, 0666);
+    if(unlikely(fd == -1)) {
+        *result = 0;
+        return 1;
+    }
+
+    ssize_t r = read(fd, buffer, 1024);
+    if(unlikely(r == -1)) {
+        *result = 0;
+        close(fd);
+        return 2;
+    }
+
+    close(fd);
+    *result = strtoull(buffer, NULL, 0);
+    return 0;
+}
+
+#endif //NETDATA_INLINED_H
index 3a8498efa96a3d7a65b25a4ee43e8a485aaf2f47..955b70757eb02c87ddb4ecaa7be9ad1abdbd8681 100644 (file)
 // NEEDED BY do_uptime
 #include <time.h>
 
-#define GETSYSCTL(name, var) getsysctl(name, &(var), sizeof(var))
-
 // MacOS calculates load averages once every 5 seconds
 #define MIN_LOADAVG_UPDATE_EVERY 5
 
-int getsysctl(const char *name, void *ptr, size_t len);
-
 int do_macos_sysctl(int update_every, usec_t dt) {
     (void)dt;
 
index d55e1c2662148da5d8d036fea426d27cfc86b1a5..36c022f8cdec4bbb926bd7860884c5389761f31a 100644 (file)
@@ -8,35 +8,29 @@ void netdata_cleanup_and_exit(int ret) {
     error_log_limit_unlimited();
 
     debug(D_EXIT, "Called: netdata_cleanup_and_exit()");
-#ifdef NETDATA_INTERNAL_CHECKS
-    rrdset_free_all();
-#else
+
+    // save the database
     rrdset_save_all();
-#endif
-    // kill_childs();
 
+    // unlink the pid
     if(pidfile[0]) {
         if(unlink(pidfile) != 0)
             error("Cannot unlink pidfile '%s'.", pidfile);
     }
 
-    info("NetData exiting. Bye bye...");
-    exit(ret);
-}
-
-struct netdata_static_thread {
-    char *name;
-
-    char *config_section;
-    char *config_name;
+#ifdef NETDATA_INTERNAL_CHECKS
+    // kill all childs
+    //kill_childs();
 
-    int enabled;
+    // free database
+    rrdset_free_all();
+#endif
 
-    pthread_t *thread;
+    info("netdata exiting. Bye bye...");
+    exit(ret);
+}
 
-    void (*init_routine) (void);
-    void *(*start_routine) (void *);
-} static_threads[] = {
+struct netdata_static_thread static_threads[] = {
 #ifdef INTERNAL_PLUGIN_NFACCT
 // nfacct requires root access
     // so, we build it as an external plugin with setuid to root
@@ -51,6 +45,7 @@ struct netdata_static_thread {
     {"macos",              "plugins",   "macos",      1, NULL, NULL, macos_main},
 #else
     {"proc",               "plugins",   "proc",       1, NULL, NULL, proc_main},
+    {"diskspace",          "plugins",   "diskspace",  1, NULL, NULL, proc_diskspace_main},
 #endif /* __FreeBSD__, __APPLE__*/
     {"cgroups",            "plugins",   "cgroups",    1, NULL, NULL, cgroups_main},
     {"check",              "plugins",   "checks",     0, NULL, NULL, checks_main},
@@ -156,27 +151,32 @@ int killpid(pid_t pid, int sig)
 
 void kill_childs()
 {
+    error_log_limit_unlimited();
+
     siginfo_t info;
 
     struct web_client *w;
     for(w = web_clients; w ; w = w->next) {
-        debug(D_EXIT, "Stopping web client %s", w->client_ip);
+        info("Stopping web client %s", w->client_ip);
         pthread_cancel(w->thread);
-        pthread_join(w->thread, NULL);
+        // it is detached
+        // pthread_join(w->thread, NULL);
     }
 
     int i;
     for (i = 0; static_threads[i].name != NULL ; i++) {
-        if(static_threads[i].thread) {
-            debug(D_EXIT, "Stopping %s thread", static_threads[i].name);
+        if(static_threads[i].enabled && static_threads[i].thread) {
+            info("Stopping %s thread", static_threads[i].name);
             pthread_cancel(*static_threads[i].thread);
-            pthread_join(*static_threads[i].thread, NULL);
+            // it is detached
+            // pthread_join(*static_threads[i].thread, NULL);
+
             static_threads[i].thread = NULL;
         }
     }
 
     if(tc_child_pid) {
-        debug(D_EXIT, "Killing tc-qos-helper procees");
+        info("Killing tc-qos-helper process %d", tc_child_pid);
         if(killpid(tc_child_pid, SIGTERM) != -1)
             waitid(P_PID, (id_t) tc_child_pid, &info, WEXITED);
     }
@@ -184,39 +184,49 @@ void kill_childs()
 
     struct plugind *cd;
     for(cd = pluginsd_root ; cd ; cd = cd->next) {
-        debug(D_EXIT, "Stopping %s plugin thread", cd->id);
-        pthread_cancel(cd->thread);
-        pthread_join(cd->thread, NULL);
-
-        if(cd->pid && !cd->obsolete) {
-            debug(D_EXIT, "killing %s plugin process", cd->id);
-            if(killpid(cd->pid, SIGTERM) != -1)
-                waitid(P_PID, (id_t) cd->pid, &info, WEXITED);
+        if(cd->enabled && !cd->obsolete) {
+            if(cd->thread != (pthread_t)NULL) {
+                info("Stopping %s plugin thread", cd->id);
+                pthread_cancel(cd->thread);
+                // they are detached
+                // pthread_join(cd->thread, NULL);
+                cd->thread = (pthread_t)NULL;
+            }
+
+            if(cd->pid) {
+                info("killing %s plugin child process pid %d", cd->id, cd->pid);
+                if(killpid(cd->pid, SIGTERM) != -1)
+                    waitid(P_PID, (id_t) cd->pid, &info, WEXITED);
+
+                cd->pid = 0;
+            }
+
+            cd->obsolete = 1;
         }
     }
 
     // if, for any reason there is any child exited
     // catch it here
+    info("Cleaning up an other children");
     waitid(P_PID, 0, &info, WEXITED|WNOHANG);
 
-    debug(D_EXIT, "All threads/childs stopped.");
+    info("All threads/childs stopped.");
 }
 
 struct option_def options[] = {
-    // opt description                                                       arg name                     default value
-    {'c', "Load alternate configuration file",                               "config_file",                          CONFIG_DIR "/" CONFIG_FILENAME},
-    {'D', "Disable fork into background",                                    NULL,                                   NULL},
-    {'h', "Display help message",                                            NULL,                                   NULL},
-    {'P', "File to save a pid while running",                                "FILE",                                 NULL},
-    {'i', "The IP address to listen to.",                                    "address",                              "All addresses"},
-    {'k', "Check daemon configuration.",                                     NULL,                                   NULL},
-    {'p', "Port to listen. Can be from 1 to 65535.",                         "port_number",                          "19999"},
-    {'s', "Path to access host /proc and /sys when running in a container.", "PATH",                                 NULL},
-    {'t', "The frequency in seconds, for data collection. \
-Same as 'update every' config file option.",                                 "seconds",                              "1"},
-    {'u', "System username to run as.",                                      "username",                             "netdata"},
-    {'v', "Version of the program",                                          NULL,                                   NULL},
-    {'W', "vendor options.",                                                 "stacksize=N|unittest|debug_flags=N",   NULL},
+    // opt description                                    arg name       default value
+    { 'c', "Configuration file to load.",                 "filename",    CONFIG_DIR "/" CONFIG_FILENAME},
+    { 'D', "Do not fork. Run in the foreground.",         NULL,          "run in the background"},
+    { 'h', "Display this help message.",                  NULL,          NULL},
+    { 'P', "File to save a pid while running.",           "filename",    "do not save pid to a file"},
+    { 'i', "The IP address to listen to.",                "IP",          "all IP addresses IPv4 and IPv6"},
+    { 'k', "Check health configuration and exit.",        NULL,          NULL},
+    { 'p', "API/Web port to use.",                        "port",        "19999"},
+    { 's', "Prefix for /proc and /sys (for containers).", "path",        "no prefix"},
+    { 't', "The internal clock of netdata.",              "seconds",     "1"},
+    { 'u', "Run as user.",                                "username",    "netdata"},
+    { 'v', "Print netdata version and exit.",             NULL,          NULL},
+    { 'W', "See Advanced options below.",                 "options",     NULL},
 };
 
 void help(int exitcode) {
@@ -238,20 +248,63 @@ void help(int exitcode) {
         }
     }
 
-    fprintf(stream, "SYNOPSIS: netdata [options]\n");
+    if(max_len_arg > 30) max_len_arg = 30;
+    if(max_len_arg < 20) max_len_arg = 20;
+
+    fprintf(stream, "%s", "\n"
+            " ^\n"
+            " |.-.   .-.   .-.   .-.   .  netdata                                         \n"
+            " |   '-'   '-'   '-'   '-'   real-time performance monitoring, done right!   \n"
+            " +----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+--->\n"
+            "\n"
+            " Copyright (C) 2017, Costa Tsaousis <costa@tsaousis.gr>\n"
+            " Released under GNU Public License v3 or later.\n"
+            " All rights reserved.\n"
+            "\n"
+            " Home Page  : https://my-netdata.io\n"
+            " Source Code: https://github.com/firehol/netdata\n"
+            " Wiki / Docs: https://github.com/firehol/netdata/wiki\n"
+            " Support    : https://github.com/firehol/netdata/issues\n"
+            " License    : https://github.com/firehol/netdata/blob/master/LICENSE.md\n"
+            "\n"
+            " Twitter    : https://twitter.com/linuxnetdata\n"
+            " Facebook   : https://www.facebook.com/linuxnetdata/\n"
+            "\n"
+            " netdata is a https://firehol.org project.\n"
+            "\n"
+            "\n"
+    );
+
+    fprintf(stream, " SYNOPSIS: netdata [options]\n");
     fprintf(stream, "\n");
-    fprintf(stream, "Options:\n");
+    fprintf(stream, " Options:\n\n");
 
     // Output options description.
     for( i = 0; i < num_opts; i++ ) {
         fprintf(stream, "  -%c %-*s  %s", options[i].val, max_len_arg, options[i].arg_name ? options[i].arg_name : "", options[i].description);
         if(options[i].default_value) {
-            fprintf(stream, " Default: %s\n", options[i].default_value);
+            fprintf(stream, "\n   %c %-*s  Default: %s\n", ' ', max_len_arg, "", options[i].default_value);
         } else {
             fprintf(stream, "\n");
         }
+        fprintf(stream, "\n");
     }
 
+    fprintf(stream, "\n Advanced options:\n\n"
+            "  -W stacksize=N           Set the stacksize (in bytes).\n\n"
+            "  -W debug_flags=N         Set runtime tracing to debug.log.\n\n"
+            "  -W unittest              Run internal unittests and exit.\n\n"
+            "  -W simple-pattern pattern string\n"
+            "                           Check if string matches pattern and exit.\n\n"
+    );
+
+    fprintf(stream, "\n Signals netdata handles:\n\n"
+            "  - HUP                    Close and reopen log files.\n"
+            "  - USR1                   Save internal DB to disk.\n"
+            "  - USR2                   Reload health configuration.\n"
+            "\n"
+    );
+
     fflush(stream);
     exit(exitcode);
 }
@@ -336,6 +389,9 @@ int main(int argc, char **argv)
                 string_i++;
             }
         }
+        // terminate optstring
+        optstring[string_i] ='\0';
+        optstring[(num_opts *2)] ='\0';
 
         int opt;
         while( (opt = getopt(argc, argv, optstring)) != -1 ) {
@@ -393,10 +449,54 @@ int main(int argc, char **argv)
                             if(unit_test_storage()) exit(1);
                             fprintf(stderr, "\n\nALL TESTS PASSED\n\n");
                             exit(0);
-                        } else if(strncmp(optarg, stacksize_string, strlen(stacksize_string)) == 0) {
+                        }
+                        else if(strcmp(optarg, "simple-pattern") == 0) {
+                            if(optind + 2 > argc) {
+                                fprintf(stderr, "%s", "\nUSAGE: -W simple-pattern 'pattern' 'string'\n\n"
+                                        " Checks if 'pattern' matches the given 'string'.\n"
+                                        " - 'pattern' can be one or more space separated words.\n"
+                                        " - each 'word' can contain one or more asterisks.\n"
+                                        " - words starting with '!' give negative matches.\n"
+                                        " - words are processed left to right\n"
+                                        "\n"
+                                        "Examples:\n"
+                                        "\n"
+                                        " > match all veth interfaces, except veth0:\n"
+                                        "\n"
+                                        "   -W simple-pattern '!veth0 veth*' 'veth12'\n"
+                                        "\n"
+                                        "\n"
+                                        " > match all *.ext files directly in /path/:\n"
+                                        "   (this will not match *.ext files in a subdir of /path/)\n"
+                                        "\n"
+                                        "   -W simple-pattern '!/path/*/*.ext /path/*.ext' '/path/test.ext'\n"
+                                        "\n"
+                                );
+                                exit(1);
+                            }
+
+                            const char *heystack = argv[optind];
+                            const char *needle = argv[optind + 1];
+
+                            SIMPLE_PATTERN *p = simple_pattern_create(heystack
+                                                                      , SIMPLE_PATTERN_EXACT);
+                            int ret = simple_pattern_matches(p, needle);
+                            simple_pattern_free(p);
+
+                            if(ret) {
+                                fprintf(stdout, "RESULT: MATCHED - pattern '%s' matches '%s'\n", heystack, needle);
+                                exit(0);
+                            }
+                            else {
+                                fprintf(stdout, "RESULT: NOT MATCHED - pattern '%s' does not match '%s'\n", heystack, needle);
+                                exit(1);
+                            }
+                        }
+                        else if(strncmp(optarg, stacksize_string, strlen(stacksize_string)) == 0) {
                             optarg += strlen(stacksize_string);
                             config_set("global", "pthread stack size", optarg);
-                        } else if(strncmp(optarg, debug_flags_string, strlen(debug_flags_string)) == 0) {
+                        }
+                        else if(strncmp(optarg, debug_flags_string, strlen(debug_flags_string)) == 0) {
                             optarg += strlen(debug_flags_string);
                             config_set("global", "debug flags",  optarg);
                             debug_flags = strtoull(optarg, NULL, 0);
@@ -656,8 +756,7 @@ int main(int argc, char **argv)
     if(become_daemon(dont_fork, user) == -1)
         fatal("Cannot daemonize myself.");
 
-    info("NetData started on pid %d", getpid());
-
+    info("netdata started on pid %d.", getpid());
 
     // ------------------------------------------------------------------------
     // get default pthread stack size
@@ -706,7 +805,7 @@ int main(int argc, char **argv)
 
             debug(D_SYSTEM, "Starting thread %s.", st->name);
 
-            if(pthread_create(st->thread, &attr, st->start_routine, NULL))
+            if(pthread_create(st->thread, &attr, st->start_routine, st))
                 error("failed to create new thread for %s.", st->name);
 
             else if(pthread_detach(*st->thread))
@@ -715,6 +814,8 @@ int main(int argc, char **argv)
         else debug(D_SYSTEM, "Not starting thread %s.", st->name);
     }
 
+    info("netdata initialization completed. Enjoy real-time performance monitoring!");
+
     // ------------------------------------------------------------------------
     // block signals while initializing threads.
     sigset_t sigset;
index be2d1c677b9ead4da24936b27e91b9758c6be758..288536baa914b47727806d45d5a3a4f38a42fb11 100644 (file)
@@ -24,6 +24,21 @@ struct option_def {
  */
 extern struct option_def options[];
 
+
+struct netdata_static_thread {
+    char *name;
+
+    char *config_section;
+    char *config_name;
+
+    volatile int enabled;
+
+    pthread_t *thread;
+
+    void (*init_routine) (void);
+    void *(*start_routine) (void *);
+};
+
 extern void kill_childs(void);
 extern int killpid(pid_t pid, int signal);
 extern void netdata_cleanup_and_exit(int ret) NORETURN;
index 12f48ff85556bedd9e3b7e6d1156e6f02c2e7dd2..fcc542e689ead4f61c79d722170cfbf2c75a7c25 100644 (file)
@@ -1,8 +1,7 @@
 #include "common.h"
 
-void *checks_main(void *ptr)
-{
-    if(ptr) { ; }
+void *checks_main(void *ptr) {
+    struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr;
 
     info("CHECKS thread created with task id %d", gettid());
 
@@ -78,6 +77,9 @@ void *checks_main(void *ptr)
         rrdset_done(check3);
     }
 
+    info("CHECKS thread exiting");
+
+    static_thread->enabled = 0;
     pthread_exit(NULL);
     return NULL;
 }
index 9231b538294e01c329e85486c30559c370624448..bdc3599ea7de6474b9407f111c55df696cb3ac7c 100644 (file)
@@ -1,8 +1,7 @@
 #include "common.h"
 
-void *freebsd_main(void *ptr)
-{
-    (void)ptr;
+void *freebsd_main(void *ptr) {
+    struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr;
 
     info("FREEBSD Plugin thread created with task id %d", gettid());
 
@@ -59,6 +58,7 @@ void *freebsd_main(void *ptr)
 
     info("FREEBSD thread exiting");
 
+    static_thread->enabled = 0;
     pthread_exit(NULL);
     return NULL;
 }
index 77fab3fa8b605f3dc371a74079830ad09a3cda3c..7d4a4c189af035ed931c2a0ffc3fb4142149adee 100644 (file)
@@ -2,11 +2,10 @@
 
 #define CPU_IDLEJITTER_SLEEP_TIME_MS 20
 
-void *cpuidlejitter_main(void *ptr)
-{
-    if(ptr) { ; }
+void *cpuidlejitter_main(void *ptr) {
+    struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr;
 
-    info("CPU Idle Jitter thread created with task id %d", gettid());
+    info("IDLEJITTER thread created with task id %d", gettid());
 
     if(pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL) != 0)
         error("Cannot set pthread cancel type to DEFERRED.");
@@ -48,6 +47,9 @@ void *cpuidlejitter_main(void *ptr)
         rrdset_done(st);
     }
 
+    info("IDLEJITTER thread exiting");
+
+    static_thread->enabled = 0;
     pthread_exit(NULL);
     return NULL;
 }
index 9f88d1e2e6ca8ada0d44c645b100ee5bcffc7d90..3955c141442db9f3178547bb127b1ddeb31dc69b 100644 (file)
@@ -1,8 +1,7 @@
 #include "common.h"
 
-void *macos_main(void *ptr)
-{
-    (void)ptr;
+void *macos_main(void *ptr) {
+    struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr;
 
     info("MACOS Plugin thread created with task id %d", gettid());
 
@@ -79,6 +78,7 @@ void *macos_main(void *ptr)
 
     info("MACOS thread exiting");
 
+    static_thread->enabled = 0;
     pthread_exit(NULL);
     return NULL;
 }
index a6f966a87e0e7271b526ffd44bfffbc6618a83f6..a21e5601de13faed21661b7ca51d76f2283640ba 100644 (file)
@@ -3,6 +3,10 @@
 
 void *macos_main(void *ptr);
 
+#define GETSYSCTL(name, var) getsysctl(name, &(var), sizeof(var))
+
+extern int getsysctl(const char *name, void *ptr, size_t len);
+
 extern int do_macos_sysctl(int update_every, usec_t dt);
 extern int do_macos_mach_smi(int update_every, usec_t dt);
 extern int do_macos_iokit(int update_every, usec_t dt);
index 03de379170f4f8847def30e8d6ffcd674a1f868c..7aae33c0cf385bbd22a30b5a0c13c81e2db45ab8 100644 (file)
@@ -55,7 +55,7 @@ static int nfacct_callback(const struct nlmsghdr *nlh, void *data) {
 }
 
 void *nfacct_main(void *ptr) {
-    if(ptr) { ; }
+    struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr;
 
     info("NFACCT thread created with task id %d", gettid());
 
@@ -75,15 +75,12 @@ void *nfacct_main(void *ptr) {
     nl  = mnl_socket_open(NETLINK_NETFILTER);
     if(!nl) {
         error("nfacct.plugin: mnl_socket_open() failed");
-        pthread_exit(NULL);
-        return NULL;
+        goto cleanup;
     }
 
     if(mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
-        mnl_socket_close(nl);
         error("nfacct.plugin: mnl_socket_bind() failed");
-        pthread_exit(NULL);
-        return NULL;
+        goto cleanup;
     }
     portid = mnl_socket_get_portid(nl);
 
@@ -104,16 +101,13 @@ void *nfacct_main(void *ptr) {
 
         nlh = nfacct_nlmsg_build_hdr(buf, NFNL_MSG_ACCT_GET, NLM_F_DUMP, seq);
         if(!nlh) {
-            mnl_socket_close(nl);
             error("nfacct.plugin: nfacct_nlmsg_build_hdr() failed");
-            pthread_exit(NULL);
-            return NULL;
+            goto cleanup;
         }
 
         if(mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) {
             error("nfacct.plugin: mnl_socket_send");
-            pthread_exit(NULL);
-            return NULL;
+            goto cleanup;
         }
 
         if(nfacct_list) nfacct_list->len = 0;
@@ -125,8 +119,7 @@ void *nfacct_main(void *ptr) {
 
         if (ret == -1) {
             error("nfacct.plugin: error communicating with kernel.");
-            pthread_exit(NULL);
-            return NULL;
+            goto cleanup;
         }
 
         // --------------------------------------------------------------------
@@ -191,7 +184,12 @@ void *nfacct_main(void *ptr) {
         memmove(&last, &now, sizeof(struct timeval));
     }
 
-    mnl_socket_close(nl);
+cleanup:
+    info("NFACCT thread exiting");
+
+    if(nl) mnl_socket_close(nl);
+
+    static_thread->enabled = 0;
     pthread_exit(NULL);
     return NULL;
 }
index 9b99d82e1d15a79bbd762a38266f924c65683340..bf0e6fb0df75d62e047009f37ac0c2fa735cd250 100644 (file)
@@ -56,9 +56,8 @@ static struct proc_module {
         { .name = NULL, .dim = NULL, .func = NULL }
 };
 
-void *proc_main(void *ptr)
-{
-    (void)ptr;
+void *proc_main(void *ptr) {
+    struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr;
 
     info("PROC Plugin thread created with task id %d", gettid());
 
@@ -73,10 +72,12 @@ void *proc_main(void *ptr)
     // check the enabled status for each module
     int i;
     for(i = 0 ; proc_modules[i].name ;i++) {
-        proc_modules[i].enabled = config_get_boolean("plugin:proc", proc_modules[i].name, 1);
-        proc_modules[i].last_run_usec = 0ULL;
-        proc_modules[i].duration = 0ULL;
-        proc_modules[i].rd = NULL;
+        struct proc_module *pm = &proc_modules[i];
+
+        pm->enabled = config_get_boolean("plugin:proc", pm->name, 1);
+        pm->last_run_usec = 0ULL;
+        pm->duration = 0ULL;
+        pm->rd = NULL;
     }
 
     usec_t step = rrd_update_every * USEC_PER_SEC;
@@ -94,15 +95,16 @@ void *proc_main(void *ptr)
         // BEGIN -- the job to be done
 
         for(i = 0 ; proc_modules[i].name ;i++) {
-            if(unlikely(!proc_modules[i].enabled)) continue;
+            struct proc_module *pm = &proc_modules[i];
+            if(unlikely(!pm->enabled)) continue;
 
-            debug(D_PROCNETDEV_LOOP, "PROC calling %s.", proc_modules[i].name);
+            debug(D_PROCNETDEV_LOOP, "PROC calling %s.", pm->name);
 
-            proc_modules[i].enabled = !proc_modules[i].func(rrd_update_every, (proc_modules[i].last_run_usec > 0)?now - proc_modules[i].last_run_usec:0ULL);
-            proc_modules[i].last_run_usec = now;
+            pm->enabled = !pm->func(rrd_update_every, (pm->last_run_usec > 0)?now - pm->last_run_usec:0ULL);
+            pm->last_run_usec = now;
 
             now = now_monotonic_usec();
-            proc_modules[i].duration = now - proc_modules[i].last_run_usec;
+            pm->duration = now - pm->last_run_usec;
 
             if(unlikely(netdata_exit)) break;
         }
@@ -113,23 +115,28 @@ void *proc_main(void *ptr)
 
         if(vdo_cpu_netdata) {
             static RRDSET *st = NULL;
+
             if(unlikely(!st)) {
                 st = rrdset_find_bytype("netdata", "plugin_proc_modules");
 
                 if(!st) {
-                    st = rrdset_create("netdata", "plugin_proc_modules", NULL, "proc.internal", NULL, "NetData Proc Plugin Modules Durations", "milliseconds/run", 132001, rrd_update_every, RRDSET_TYPE_STACKED);
+                    st = rrdset_create("netdata", "plugin_proc_modules", NULL, "proc", NULL, "NetData Proc Plugin Modules Durations", "milliseconds/run", 132001, rrd_update_every, RRDSET_TYPE_STACKED);
 
                     for(i = 0 ; proc_modules[i].name ;i++) {
-                        if(unlikely(!proc_modules[i].enabled)) continue;
-                        proc_modules[i].rd = rrddim_add(st, proc_modules[i].dim, NULL, 1, 1000, RRDDIM_ABSOLUTE);
+                        struct proc_module *pm = &proc_modules[i];
+                        if(unlikely(!pm->enabled)) continue;
+
+                        pm->rd = rrddim_add(st, pm->dim, NULL, 1, 1000, RRDDIM_ABSOLUTE);
                     }
                 }
             }
             else rrdset_next(st);
 
             for(i = 0 ; proc_modules[i].name ;i++) {
-                if(unlikely(!proc_modules[i].enabled)) continue;
-                rrddim_set_by_pointer(st, proc_modules[i].rd, proc_modules[i].duration);
+                struct proc_module *pm = &proc_modules[i];
+                if(unlikely(!pm->enabled)) continue;
+
+                rrddim_set_by_pointer(st, pm->rd, pm->duration);
             }
             rrdset_done(st);
 
@@ -140,6 +147,7 @@ void *proc_main(void *ptr)
 
     info("PROC thread exiting");
 
+    static_thread->enabled = 0;
     pthread_exit(NULL);
     return NULL;
 }
diff --git a/src/plugin_proc_diskspace.c b/src/plugin_proc_diskspace.c
new file mode 100644 (file)
index 0000000..7043385
--- /dev/null
@@ -0,0 +1,281 @@
+#include "common.h"
+
+#define DELAULT_EXLUDED_PATHS "/proc/* /sys/* /var/run/user/* /run/user/*"
+
+static struct mountinfo *disk_mountinfo_root = NULL;
+static int check_for_new_mountpoints_every = 15;
+
+static inline void mountinfo_reload(int force) {
+    static time_t last_loaded = 0;
+    time_t now = now_realtime_sec();
+
+    if(force || now - last_loaded >= check_for_new_mountpoints_every) {
+        // mountinfo_free() can be called with NULL disk_mountinfo_root
+        mountinfo_free(disk_mountinfo_root);
+
+        // re-read mountinfo in case something changed
+        disk_mountinfo_root = mountinfo_read(1);
+
+        last_loaded = now;
+    }
+}
+
+// Data to be stored in DICTIONARY mount_points used by do_disk_space_stats().
+// This DICTIONARY is used to lookup the settings of the mount point on each iteration.
+struct mount_point_metadata {
+    int do_space;
+    int do_inodes;
+};
+
+static inline void do_disk_space_stats(struct mountinfo *mi, int update_every) {
+    const char *family = mi->mount_point;
+    const char *disk = mi->persistent_id;
+
+    static DICTIONARY *mount_points = NULL;
+    static SIMPLE_PATTERN *excluded_mountpoints = NULL;
+    int do_space, do_inodes;
+
+    if(unlikely(!mount_points)) {
+        const char *s;
+        SIMPLE_PREFIX_MODE mode = SIMPLE_PATTERN_EXACT;
+
+        if(config_exists("plugin:proc:/proc/diskstats", "exclude space metrics on paths") && !config_exists("plugin:proc:diskspace", "exclude space metrics on paths")) {
+            // the config exists in the old section
+            s = config_get("plugin:proc:/proc/diskstats", "exclude space metrics on paths", DELAULT_EXLUDED_PATHS);
+            mode = SIMPLE_PATTERN_PREFIX;
+        }
+        else
+            s = config_get("plugin:proc:diskspace", "exclude space metrics on paths", DELAULT_EXLUDED_PATHS);
+
+        mount_points = dictionary_create(DICTIONARY_FLAG_SINGLE_THREADED);
+        excluded_mountpoints = simple_pattern_create(s, mode);
+    }
+
+    struct mount_point_metadata *m = dictionary_get(mount_points, mi->mount_point);
+    if(unlikely(!m)) {
+        char var_name[4096 + 1];
+        snprintfz(var_name, 4096, "plugin:proc:diskspace:%s", mi->mount_point);
+
+        int def_space = config_get_boolean_ondemand("plugin:proc:diskspace", "space usage for all disks", CONFIG_ONDEMAND_ONDEMAND);
+        int def_inodes = config_get_boolean_ondemand("plugin:proc:diskspace", "inodes usage for all disks", CONFIG_ONDEMAND_ONDEMAND);
+
+        if(unlikely(simple_pattern_matches(excluded_mountpoints, mi->mount_point))) {
+            def_space = CONFIG_ONDEMAND_NO;
+            def_inodes = CONFIG_ONDEMAND_NO;
+        }
+
+        do_space = config_get_boolean_ondemand(var_name, "space usage", def_space);
+        do_inodes = config_get_boolean_ondemand(var_name, "inodes usage", def_inodes);
+
+        struct mount_point_metadata mp = {
+                .do_space = do_space,
+                .do_inodes = do_inodes
+        };
+
+        dictionary_set(mount_points, mi->mount_point, &mp, sizeof(struct mount_point_metadata));
+    }
+    else {
+        do_space = m->do_space;
+        do_inodes = m->do_inodes;
+    }
+
+    if(unlikely(do_space == CONFIG_ONDEMAND_NO && do_inodes == CONFIG_ONDEMAND_NO))
+        return;
+
+    struct statvfs buff_statvfs;
+    if (statvfs(mi->mount_point, &buff_statvfs) < 0) {
+        error("Failed statvfs() for '%s' (disk '%s')", mi->mount_point, disk);
+        return;
+    }
+
+    // taken from get_fs_usage() found in coreutils
+    unsigned long bsize = (buff_statvfs.f_frsize) ? buff_statvfs.f_frsize : buff_statvfs.f_bsize;
+
+    fsblkcnt_t bavail         = buff_statvfs.f_bavail;
+    fsblkcnt_t btotal         = buff_statvfs.f_blocks;
+    fsblkcnt_t bavail_root    = buff_statvfs.f_bfree;
+    fsblkcnt_t breserved_root = bavail_root - bavail;
+    fsblkcnt_t bused;
+    if(likely(btotal >= bavail_root))
+        bused = btotal - bavail_root;
+    else
+        bused = bavail_root - btotal;
+
+#ifdef NETDATA_INTERNAL_CHECKS
+    if(unlikely(btotal != bavail + breserved_root + bused))
+        error("Disk block statistics for '%s' (disk '%s') do not sum up: total = %llu, available = %llu, reserved = %llu, used = %llu", mi->mount_point, disk, (unsigned long long)btotal, (unsigned long long)bavail, (unsigned long long)breserved_root, (unsigned long long)bused);
+#endif
+
+    // --------------------------------------------------------------------------
+
+    fsfilcnt_t favail         = buff_statvfs.f_favail;
+    fsfilcnt_t ftotal         = buff_statvfs.f_files;
+    fsfilcnt_t favail_root    = buff_statvfs.f_ffree;
+    fsfilcnt_t freserved_root = favail_root - favail;
+    fsfilcnt_t fused          = ftotal - favail_root;
+
+#ifdef NETDATA_INTERNAL_CHECKS
+    if(unlikely(btotal != bavail + breserved_root + bused))
+        error("Disk inode statistics for '%s' (disk '%s') do not sum up: total = %llu, available = %llu, reserved = %llu, used = %llu", mi->mount_point, disk, (unsigned long long)ftotal, (unsigned long long)favail, (unsigned long long)freserved_root, (unsigned long long)fused);
+#endif
+
+    // --------------------------------------------------------------------------
+
+    RRDSET *st;
+
+    if(do_space == CONFIG_ONDEMAND_YES || (do_space == CONFIG_ONDEMAND_ONDEMAND && (bavail || breserved_root || bused))) {
+        st = rrdset_find_bytype("disk_space", disk);
+        if(unlikely(!st)) {
+            char title[4096 + 1];
+            snprintfz(title, 4096, "Disk Space Usage for %s [%s]", family, mi->mount_source);
+            st = rrdset_create("disk_space", disk, NULL, family, "disk.space", title, "GB", 2023, update_every, RRDSET_TYPE_STACKED);
+
+            rrddim_add(st, "avail", NULL, bsize, 1024*1024*1024, RRDDIM_ABSOLUTE);
+            rrddim_add(st, "used" , NULL, bsize, 1024*1024*1024, RRDDIM_ABSOLUTE);
+            rrddim_add(st, "reserved_for_root", "reserved for root", bsize, 1024*1024*1024, RRDDIM_ABSOLUTE);
+        }
+        else rrdset_next(st);
+
+        rrddim_set(st, "avail", (collected_number)bavail);
+        rrddim_set(st, "used", (collected_number)bused);
+        rrddim_set(st, "reserved_for_root", (collected_number)breserved_root);
+        rrdset_done(st);
+    }
+
+    // --------------------------------------------------------------------------
+
+    if(do_inodes == CONFIG_ONDEMAND_YES || (do_inodes == CONFIG_ONDEMAND_ONDEMAND && (favail || freserved_root || fused))) {
+        st = rrdset_find_bytype("disk_inodes", disk);
+        if(unlikely(!st)) {
+            char title[4096 + 1];
+            snprintfz(title, 4096, "Disk Files (inodes) Usage for %s [%s]", family, mi->mount_source);
+            st = rrdset_create("disk_inodes", disk, NULL, family, "disk.inodes", title, "Inodes", 2024, update_every, RRDSET_TYPE_STACKED);
+
+            rrddim_add(st, "avail", NULL, 1, 1, RRDDIM_ABSOLUTE);
+            rrddim_add(st, "used" , NULL, 1, 1, RRDDIM_ABSOLUTE);
+            rrddim_add(st, "reserved_for_root", "reserved for root", 1, 1, RRDDIM_ABSOLUTE);
+        }
+        else rrdset_next(st);
+
+        rrddim_set(st, "avail", (collected_number)favail);
+        rrddim_set(st, "used", (collected_number)fused);
+        rrddim_set(st, "reserved_for_root", (collected_number)freserved_root);
+        rrdset_done(st);
+    }
+}
+
+void *proc_diskspace_main(void *ptr) {
+    struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr;
+
+    info("DISKSPACE thread created with task id %d", gettid());
+
+    if(pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL) != 0)
+        error("Cannot set pthread cancel type to DEFERRED.");
+
+    if(pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) != 0)
+        error("Cannot set pthread cancel state to ENABLE.");
+
+    int vdo_cpu_netdata = config_get_boolean("plugin:proc", "netdata server resources", 1);
+
+    int update_every = (int)config_get_number("plugin:proc:diskspace", "update every", rrd_update_every);
+    if(update_every < rrd_update_every)
+        update_every = rrd_update_every;
+
+    check_for_new_mountpoints_every = (int)config_get_number("plugin:proc:diskspace", "check for new mount points every", check_for_new_mountpoints_every);
+    if(check_for_new_mountpoints_every < update_every)
+        check_for_new_mountpoints_every = update_every;
+
+    RRDSET *stcpu_thread = NULL, *st_duration = NULL;
+    RRDDIM *rd_user = NULL, *rd_system = NULL, *rd_duration = NULL;
+    struct rusage thread;
+
+    usec_t last = 0, dt = 0;
+    usec_t step = update_every * USEC_PER_SEC;
+    for(;;) {
+        usec_t now = now_monotonic_usec();
+        usec_t next = now - (now % step) + step;
+
+        dt = (last)?now - last:0;
+
+        while(now < next) {
+            sleep_usec(next - now);
+            now = now_monotonic_usec();
+        }
+
+        last = now;
+
+        if(unlikely(netdata_exit)) break;
+
+
+        // --------------------------------------------------------------------------
+        // this is smart enough not to reload it every time
+
+        mountinfo_reload(0);
+
+
+        // --------------------------------------------------------------------------
+        // disk space metrics
+
+        struct mountinfo *mi;
+        for(mi = disk_mountinfo_root; mi; mi = mi->next) {
+
+            if(unlikely(mi->flags &
+                        (MOUNTINFO_IS_DUMMY | MOUNTINFO_IS_BIND | MOUNTINFO_IS_SAME_DEV | MOUNTINFO_NO_STAT |
+                         MOUNTINFO_NO_SIZE | MOUNTINFO_READONLY)))
+                continue;
+
+            do_disk_space_stats(mi, update_every);
+            if(unlikely(netdata_exit)) break;
+        }
+
+        if(unlikely(netdata_exit)) break;
+
+        if(vdo_cpu_netdata) {
+            // ----------------------------------------------------------------
+
+            getrusage(RUSAGE_THREAD, &thread);
+
+            if(!stcpu_thread) {
+                stcpu_thread = rrdset_find("netdata.plugin_diskspace");
+                if(!stcpu_thread) stcpu_thread = rrdset_create("netdata", "plugin_diskspace", NULL, "diskspace", NULL
+                                                 , "NetData Disk Space Plugin CPU usage", "milliseconds/s", 132020
+                                                 , update_every, RRDSET_TYPE_STACKED);
+
+                rd_user = rrddim_add(stcpu_thread, "user", NULL, 1, 1000, RRDDIM_INCREMENTAL);
+                rd_system = rrddim_add(stcpu_thread, "system", NULL, 1, 1000, RRDDIM_INCREMENTAL);
+            }
+            else
+                rrdset_next(stcpu_thread);
+
+            rrddim_set_by_pointer(stcpu_thread, rd_user, thread.ru_utime.tv_sec * 1000000ULL + thread.ru_utime.tv_usec);
+            rrddim_set_by_pointer(stcpu_thread, rd_system, thread.ru_stime.tv_sec * 1000000ULL + thread.ru_stime.tv_usec);
+            rrdset_done(stcpu_thread);
+
+            // ----------------------------------------------------------------
+
+            if(!st_duration) {
+                st_duration = rrdset_find("netdata.plugin_diskspace_dt");
+                if(!st_duration) st_duration = rrdset_create("netdata", "plugin_diskspace_dt", NULL, "diskspace", NULL
+                                                 , "NetData Disk Space Plugin Duration", "milliseconds/run", 132021
+                                                 , update_every, RRDSET_TYPE_AREA);
+
+                rd_duration = rrddim_add(st_duration, "duration", NULL, 1, 1000, RRDDIM_ABSOLUTE);
+            }
+            else
+                rrdset_next(st_duration);
+
+            rrddim_set_by_pointer(st_duration, rd_duration, dt);
+            rrdset_done(st_duration);
+
+            // ----------------------------------------------------------------
+
+            if(unlikely(netdata_exit)) break;
+        }
+    }
+
+    info("DISKSPACE thread exiting");
+
+    static_thread->enabled = 0;
+    pthread_exit(NULL);
+    return NULL;
+}
diff --git a/src/plugin_proc_diskspace.h b/src/plugin_proc_diskspace.h
new file mode 100644 (file)
index 0000000..dcec28f
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef NETDATA_PLUGIN_PROC_DISKSPACE_H
+#define NETDATA_PLUGIN_PROC_DISKSPACE_H
+
+extern void *proc_diskspace_main(void *ptr);
+
+#endif //NETDATA_PLUGIN_PROC_DISKSPACE_H
index 1eef22f2f9f9e37639a9cc385d19c91baf43a7bd..dd4a3d27d5cfeca01e27f925c50f64876327dced 100644 (file)
@@ -747,9 +747,9 @@ static inline void tc_split_words(char *str, char **words, int max_words) {
     while(i < max_words) words[i++] = NULL;
 }
 
-pid_t tc_child_pid = 0;
+volatile pid_t tc_child_pid = 0;
 void *tc_main(void *ptr) {
-    (void)ptr;
+    struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr;
 
     info("TC thread created with task id %d", gettid());
 
@@ -793,11 +793,10 @@ void *tc_main(void *ptr) {
         snprintfz(buffer, TC_LINE_MAX, "exec %s %d", tc_script, rrd_update_every);
         debug(D_TC_LOOP, "executing '%s'", buffer);
 
-        fp = mypopen(buffer, &tc_child_pid);
+        fp = mypopen(buffer, (pid_t *)&tc_child_pid);
         if(unlikely(!fp)) {
             error("TC: Cannot popen(\"%s\", \"r\").", buffer);
-            pthread_exit(NULL);
-            return NULL;
+            goto cleanup;
         }
 
         while(fgets(buffer, TC_LINE_MAX, fp) != NULL) {
@@ -987,7 +986,7 @@ void *tc_main(void *ptr) {
         }
 
         // fgets() failed or loop broke
-        int code = mypclose(fp, tc_child_pid);
+        int code = mypclose(fp, (pid_t)tc_child_pid);
         tc_child_pid = 0;
 
         if(unlikely(device)) {
@@ -998,8 +997,7 @@ void *tc_main(void *ptr) {
 
         if(unlikely(netdata_exit)) {
             tc_device_free_all();
-            pthread_exit(NULL);
-            return NULL;
+            goto cleanup;
         }
 
         if(code == 1 || code == 127) {
@@ -1008,13 +1006,16 @@ void *tc_main(void *ptr) {
             error("TC: tc-qos-helper.sh exited with code %d. Disabling it.", code);
 
             tc_device_free_all();
-            pthread_exit(NULL);
-            return NULL;
+            goto cleanup;
         }
 
         sleep((unsigned int) rrd_update_every);
     }
 
+cleanup:
+    info("TC thread exiting");
+
+    static_thread->enabled = 0;
     pthread_exit(NULL);
     return NULL;
 }
index c3abbddd0334c87b9df7455eb09cd940dcf263a7..9a0a19cce5a76f8661650e6dc227ef07ca244fe6 100644 (file)
@@ -1,7 +1,7 @@
 #ifndef NETDATA_PLUGIN_TC_H
 #define NETDATA_PLUGIN_TC_H 1
 
-extern pid_t tc_child_pid;
+extern volatile pid_t tc_child_pid;
 extern void *tc_main(void *ptr);
 
 #endif /* NETDATA_PLUGIN_TC_H */
index 23550bd89a2fa3f32038dcdfcf8f54842391feef..4b2bdc8d3797879e094ff042682b8d0a4443f9f0 100644 (file)
@@ -87,6 +87,8 @@ static int pluginsd_split_words(char *str, char **words, int max_words) {
 void *pluginsd_worker_thread(void *arg)
 {
     struct plugind *cd = (struct plugind *)arg;
+    cd->obsolete = 0;
+
     char line[PLUGINSD_LINE_MAX + 1];
 
 #ifdef DETACH_PLUGINS_FROM_NETDATA
@@ -428,12 +430,13 @@ void *pluginsd_worker_thread(void *arg)
     info("PLUGINSD: '%s' thread exiting", cd->fullfilename);
 
     cd->obsolete = 1;
+    cd->thread = (pthread_t)NULL;
     pthread_exit(NULL);
     return NULL;
 }
 
 void *pluginsd_main(void *ptr) {
-    (void)ptr;
+    struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr;
 
     info("PLUGINS.D thread created with task id %d", gettid());
 
@@ -461,8 +464,7 @@ void *pluginsd_main(void *ptr) {
         dir = opendir(dir_name);
         if(unlikely(!dir)) {
             error("Cannot open directory '%s'.", dir_name);
-            pthread_exit(NULL);
-            return NULL;
+            goto cleanup;
         }
 
         while(likely((file = readdir(dir)))) {
@@ -489,9 +491,9 @@ void *pluginsd_main(void *ptr) {
             }
 
             // check if it runs already
-            for(cd = pluginsd_root ; likely(cd) ; cd = cd->next) {
+            for(cd = pluginsd_root ; cd ; cd = cd->next)
                 if(unlikely(strcmp(cd->filename, file->d_name) == 0)) break;
-            }
+
             if(likely(cd && !cd->obsolete)) {
                 debug(D_PLUGINSD, "PLUGINSD: plugin '%s' is already running", cd->filename);
                 continue;
@@ -517,26 +519,29 @@ void *pluginsd_main(void *ptr) {
                 // link it
                 if(likely(pluginsd_root)) cd->next = pluginsd_root;
                 pluginsd_root = cd;
-            }
-            cd->obsolete = 0;
 
-            if(unlikely(!cd->enabled)) continue;
-
-            // spawn a new thread for it
-            if(unlikely(pthread_create(&cd->thread, NULL, pluginsd_worker_thread, cd) != 0)) {
-                error("PLUGINSD: failed to create new thread for plugin '%s'.", cd->filename);
+                // it is not currently running
                 cd->obsolete = 1;
+
+                if(cd->enabled) {
+                    // spawn a new thread for it
+                    if(unlikely(pthread_create(&cd->thread, NULL, pluginsd_worker_thread, cd) != 0))
+                        error("PLUGINSD: failed to create new thread for plugin '%s'.", cd->filename);
+
+                    else if(unlikely(pthread_detach(cd->thread) != 0))
+                        error("PLUGINSD: Cannot request detach of newly created thread for plugin '%s'.", cd->filename);
+                }
             }
-            else if(unlikely(pthread_detach(cd->thread) != 0))
-                error("PLUGINSD: Cannot request detach of newly created thread for plugin '%s'.", cd->filename);
         }
 
         closedir(dir);
         sleep((unsigned int) scan_frequency);
     }
 
+cleanup:
     info("PLUGINS.D thread exiting");
 
+    static_thread->enabled = 0;
     pthread_exit(NULL);
     return NULL;
 }
index 6f1fbd6e10fe85793a5f3c99871ab7e48ac284de..3c74355a3d6b30f6a7a93e2ba02e5898cdf4ad4e 100644 (file)
@@ -23,8 +23,8 @@ struct plugind {
                                         // without collecting values
 
     int update_every;                   // the plugin default data collection frequency
-    int obsolete;                       // do not touch this structure after setting this to 1
-    int enabled;                        // if this is enabled or not
+    volatile int obsolete;              // do not touch this structure after setting this to 1
+    volatile int enabled;               // if this is enabled or not
 
     time_t started_t;
 
index 574470dc86b501c3396eed7762ab896c4bb71ead..146aeeaa4b254916d0e344c10b56b788c88c6279 100644 (file)
@@ -6,10 +6,6 @@
 #define DISK_TYPE_PARTITION 2
 #define DISK_TYPE_CONTAINER 3
 
-#ifndef NETDATA_RELOAD_MOUNTINFO_EVERY
-#define NETDATA_RELOAD_MOUNTINFO_EVERY 10
-#endif
-
 static struct disk {
     char *disk;             // the name of the disk (sda, sdb, etc)
     unsigned long major;
@@ -32,167 +28,12 @@ static struct disk {
     struct disk *next;
 } *disk_root = NULL;
 
-static struct mountinfo *disk_mountinfo_root = NULL;
-
-static inline void mountinfo_reload(int force) {
-    static time_t last_loaded = 0;
-    time_t now = now_realtime_sec();
-
-    if(force || now - last_loaded >= NETDATA_RELOAD_MOUNTINFO_EVERY) {
-//#ifdef NETDATA_INTERNAL_CHECKS
-//        info("Reloading mountinfo");
-//#endif
-
-        // mountinfo_free() can be called with NULL disk_mountinfo_root
-        mountinfo_free(disk_mountinfo_root);
-
-        // re-read mountinfo in case something changed
-        disk_mountinfo_root = mountinfo_read();
-
-        last_loaded = now;
-    }
-}
-
-
-// Data to be stored in DICTIONARY mount_points used by do_disk_space_stats().
-// This DICTIONARY is used to lookup the settings of the mount point on each iteration.
-struct mount_point_metadata {
-    int do_space;
-    int do_inodes;
-};
-
-static inline void do_disk_space_stats(struct mountinfo *mi, int update_every, usec_t dt) {
-    (void)dt;
-
-    const char *family = mi->mount_point;
-    const char *disk = mi->persistent_id;
-
-    static DICTIONARY *mount_points = NULL;
-    static NETDATA_SIMPLE_PATTERN *excluded_mountpoints = NULL;
-    int do_space, do_inodes;
-
-    if(unlikely(!mount_points)) {
-        mount_points = dictionary_create(DICTIONARY_FLAG_SINGLE_THREADED);
-        excluded_mountpoints = netdata_simple_pattern_list_create(config_get("plugin:proc:/proc/diskstats", "exclude space metrics on paths", "/proc/ /sys/ /var/run/user/ /run/user/"), NETDATA_SIMPLE_PATTERN_MODE_PREFIX);
-    }
-
-    struct mount_point_metadata *m = dictionary_get(mount_points, mi->mount_point);
-    if(unlikely(!m)) {
-        char var_name[4096 + 1];
-        snprintfz(var_name, 4096, "plugin:proc:/proc/diskstats:%s", mi->mount_point);
-
-        int def_space = config_get_boolean_ondemand("plugin:proc:/proc/diskstats", "space usage for all disks", CONFIG_ONDEMAND_ONDEMAND);
-        int def_inodes = config_get_boolean_ondemand("plugin:proc:/proc/diskstats", "inodes usage for all disks", CONFIG_ONDEMAND_ONDEMAND);
-
-        if(unlikely(netdata_simple_pattern_list_matches(excluded_mountpoints, mi->mount_point))) {
-            def_space = CONFIG_ONDEMAND_NO;
-            def_inodes = CONFIG_ONDEMAND_NO;
-        }
-
-        do_space = config_get_boolean_ondemand(var_name, "space usage", def_space);
-        do_inodes = config_get_boolean_ondemand(var_name, "inodes usage", def_inodes);
-
-        struct mount_point_metadata mp = {
-            .do_space = do_space,
-            .do_inodes = do_inodes
-        };
-
-        dictionary_set(mount_points, mi->mount_point, &mp, sizeof(struct mount_point_metadata));
-    }
-    else {
-        do_space = m->do_space;
-        do_inodes = m->do_inodes;
-    }
-
-    if(unlikely(do_space == CONFIG_ONDEMAND_NO && do_inodes == CONFIG_ONDEMAND_NO))
-        return;
-
-    struct statvfs buff_statvfs;
-    if (statvfs(mi->mount_point, &buff_statvfs) < 0) {
-        error("Failed statvfs() for '%s' (disk '%s')", mi->mount_point, disk);
-        return;
-    }
-
-    // taken from get_fs_usage() found in coreutils
-    unsigned long bsize = (buff_statvfs.f_frsize) ? buff_statvfs.f_frsize : buff_statvfs.f_bsize;
-
-    fsblkcnt_t bavail         = buff_statvfs.f_bavail;
-    fsblkcnt_t btotal         = buff_statvfs.f_blocks;
-    fsblkcnt_t bavail_root    = buff_statvfs.f_bfree;
-    fsblkcnt_t breserved_root = bavail_root - bavail;
-    fsblkcnt_t bused;
-    if(likely(btotal >= bavail_root))
-        bused = btotal - bavail_root;
-    else
-        bused = bavail_root - btotal;
-
-#ifdef NETDATA_INTERNAL_CHECKS
-    if(unlikely(btotal != bavail + breserved_root + bused))
-        error("Disk block statistics for '%s' (disk '%s') do not sum up: total = %llu, available = %llu, reserved = %llu, used = %llu", mi->mount_point, disk, (unsigned long long)btotal, (unsigned long long)bavail, (unsigned long long)breserved_root, (unsigned long long)bused);
-#endif
-
-    // --------------------------------------------------------------------------
-
-    fsfilcnt_t favail         = buff_statvfs.f_favail;
-    fsfilcnt_t ftotal         = buff_statvfs.f_files;
-    fsfilcnt_t favail_root    = buff_statvfs.f_ffree;
-    fsfilcnt_t freserved_root = favail_root - favail;
-    fsfilcnt_t fused          = ftotal - favail_root;
-
-#ifdef NETDATA_INTERNAL_CHECKS
-    if(unlikely(btotal != bavail + breserved_root + bused))
-        error("Disk inode statistics for '%s' (disk '%s') do not sum up: total = %llu, available = %llu, reserved = %llu, used = %llu", mi->mount_point, disk, (unsigned long long)ftotal, (unsigned long long)favail, (unsigned long long)freserved_root, (unsigned long long)fused);
-#endif
-
-    // --------------------------------------------------------------------------
-
-    RRDSET *st;
-
-    if(do_space == CONFIG_ONDEMAND_YES || (do_space == CONFIG_ONDEMAND_ONDEMAND && (bavail || breserved_root || bused))) {
-        st = rrdset_find_bytype("disk_space", disk);
-        if(unlikely(!st)) {
-            char title[4096 + 1];
-            snprintfz(title, 4096, "Disk Space Usage for %s [%s]", family, mi->mount_source);
-            st = rrdset_create("disk_space", disk, NULL, family, "disk.space", title, "GB", 2023, update_every, RRDSET_TYPE_STACKED);
-
-            rrddim_add(st, "avail", NULL, bsize, 1024*1024*1024, RRDDIM_ABSOLUTE);
-            rrddim_add(st, "used" , NULL, bsize, 1024*1024*1024, RRDDIM_ABSOLUTE);
-            rrddim_add(st, "reserved_for_root", "reserved for root", bsize, 1024*1024*1024, RRDDIM_ABSOLUTE);
-        }
-        else rrdset_next(st);
-
-        rrddim_set(st, "avail", (collected_number)bavail);
-        rrddim_set(st, "used", (collected_number)bused);
-        rrddim_set(st, "reserved_for_root", (collected_number)breserved_root);
-        rrdset_done(st);
-    }
-
-    // --------------------------------------------------------------------------
-
-    if(do_inodes == CONFIG_ONDEMAND_YES || (do_inodes == CONFIG_ONDEMAND_ONDEMAND && (favail || freserved_root || fused))) {
-        st = rrdset_find_bytype("disk_inodes", disk);
-        if(unlikely(!st)) {
-            char title[4096 + 1];
-            snprintfz(title, 4096, "Disk Files (inodes) Usage for %s [%s]", family, mi->mount_source);
-            st = rrdset_create("disk_inodes", disk, NULL, family, "disk.inodes", title, "Inodes", 2024, update_every, RRDSET_TYPE_STACKED);
-
-            rrddim_add(st, "avail", NULL, 1, 1, RRDDIM_ABSOLUTE);
-            rrddim_add(st, "used" , NULL, 1, 1, RRDDIM_ABSOLUTE);
-            rrddim_add(st, "reserved_for_root", "reserved for root", 1, 1, RRDDIM_ABSOLUTE);
-        }
-        else rrdset_next(st);
-
-        rrddim_set(st, "avail", (collected_number)favail);
-        rrddim_set(st, "used", (collected_number)fused);
-        rrddim_set(st, "reserved_for_root", (collected_number)freserved_root);
-        rrdset_done(st);
-    }
-}
-
 static struct disk *get_disk(unsigned long major, unsigned long minor, char *disk) {
     static char path_to_get_hw_sector_size[FILENAME_MAX + 1] = "";
     static char path_to_get_hw_sector_size_partitions[FILENAME_MAX + 1] = "";
     static char path_find_block_device[FILENAME_MAX + 1] = "";
+    static struct mountinfo *disk_mountinfo_root = NULL;
+
     struct disk *d;
 
     // search for it in our RAM list.
@@ -201,11 +42,7 @@ static struct disk *get_disk(unsigned long major, unsigned long minor, char *dis
     // should not be that many, it should be acceptable
     for(d = disk_root; d ; d = d->next)
         if(unlikely(d->major == major && d->minor == minor))
-            break;
-
-    // if we found it, return it
-    if(likely(d))
-        return d;
+            return d;
 
     // not found
     // create a new disk structure
@@ -273,6 +110,13 @@ static struct disk *get_disk(unsigned long major, unsigned long minor, char *dis
 
     // mountinfo_find() can be called with NULL disk_mountinfo_root
     struct mountinfo *mi = mountinfo_find(disk_mountinfo_root, d->major, d->minor);
+    if(unlikely(!mi)) {
+        // mountinfo_free can be called with NULL
+        mountinfo_free(disk_mountinfo_root);
+        disk_mountinfo_root = mountinfo_read(0);
+        mi = mountinfo_find(disk_mountinfo_root, d->major, d->minor);
+    }
+
     if(unlikely(mi))
         d->mount_point = strdupz(mi->mount_point);
     else
@@ -325,15 +169,6 @@ static struct disk *get_disk(unsigned long major, unsigned long minor, char *dis
     return d;
 }
 
-static inline int select_positive_option(int option1, int option2) {
-    if(unlikely(option1 == CONFIG_ONDEMAND_YES || option2 == CONFIG_ONDEMAND_YES))
-        return CONFIG_ONDEMAND_YES;
-    else if(unlikely(option1 == CONFIG_ONDEMAND_ONDEMAND || option2 == CONFIG_ONDEMAND_ONDEMAND))
-        return CONFIG_ONDEMAND_ONDEMAND;
-
-    return CONFIG_ONDEMAND_NO;
-}
-
 static inline int is_major_enabled(int major) {
     static char *major_configs = NULL;
     static size_t major_size = 0;
@@ -396,22 +231,6 @@ int do_proc_diskstats(int update_every, usec_t dt) {
         globals_initialized = 1;
     }
 
-    // --------------------------------------------------------------------------
-    // this is smart enough not to reload it every time
-
-    mountinfo_reload(0);
-
-    // --------------------------------------------------------------------------
-    // disk space metrics
-
-    struct mountinfo *mi;
-    for(mi = disk_mountinfo_root; mi ;mi = mi->next) {
-        if(unlikely(mi->flags & (MOUNTINFO_IS_DUMMY|MOUNTINFO_IS_BIND|MOUNTINFO_IS_SAME_DEV|MOUNTINFO_NO_STAT|MOUNTINFO_NO_SIZE|MOUNTINFO_READONLY)))
-            continue;
-
-        do_disk_space_stats(mi, update_every, dt);
-    }
-
     // --------------------------------------------------------------------------
 
     if(unlikely(!ff)) {
index e0242fcc4632f1dae22691e3781bfa7ec39b1838..a330010abe40110a2959851a461a55617af126df 100644 (file)
@@ -112,7 +112,7 @@ static struct netdev *get_netdev(const char *name) {
 int do_proc_net_dev(int update_every, usec_t dt) {
     (void)dt;
 
-    static NETDATA_SIMPLE_PATTERN *disabled_list = NULL;
+    static SIMPLE_PATTERN *disabled_list = NULL;
     static procfile *ff = NULL;
     static int enable_new_interfaces = -1;
     static int do_bandwidth = -1, do_packets = -1, do_errors = -1, do_drops = -1, do_fifo = -1, do_compressed = -1, do_events = -1;
@@ -128,7 +128,9 @@ int do_proc_net_dev(int update_every, usec_t dt) {
         do_compressed   = config_get_boolean_ondemand("plugin:proc:/proc/net/dev", "compressed packets for all interfaces", CONFIG_ONDEMAND_ONDEMAND);
         do_events       = config_get_boolean_ondemand("plugin:proc:/proc/net/dev", "frames, collisions, carrier counters for all interfaces", CONFIG_ONDEMAND_ONDEMAND);
 
-        disabled_list = netdata_simple_pattern_list_create(config_get("plugin:proc:/proc/net/dev", "disable by default interfaces matching", "lo fireqos* *-ifb"), NETDATA_SIMPLE_PATTERN_MODE_EXACT);
+        disabled_list = simple_pattern_create(
+                config_get("plugin:proc:/proc/net/dev", "disable by default interfaces matching", "lo fireqos* *-ifb")
+                , SIMPLE_PATTERN_EXACT);
     }
 
     if(unlikely(!ff)) {
@@ -157,7 +159,7 @@ int do_proc_net_dev(int update_every, usec_t dt) {
             d->enabled = enable_new_interfaces;
 
             if(d->enabled)
-                d->enabled = !netdata_simple_pattern_list_matches(disabled_list, d->name);
+                d->enabled = !simple_pattern_matches(disabled_list, d->name);
 
             char var_name[512 + 1];
             snprintfz(var_name, 512, "plugin:proc:/proc/net/dev:%s", d->name);
@@ -205,11 +207,11 @@ int do_proc_net_dev(int update_every, usec_t dt) {
             if(unlikely(!d->st_bandwidth)) {
                 d->st_bandwidth = rrdset_find_bytype("net", d->name);
 
-                if(!d->st_bandwidth) {
+                if(!d->st_bandwidth)
                     d->st_bandwidth = rrdset_create("net", d->name, NULL, d->name, "net.net", "Bandwidth", "kilobits/s", 7000, update_every, RRDSET_TYPE_AREA);
-                    d->rd_rbytes = rrddim_add(d->st_bandwidth, "received", NULL, 8, 1024, RRDDIM_INCREMENTAL);
-                    d->rd_tbytes = rrddim_add(d->st_bandwidth, "sent", NULL, -8, 1024, RRDDIM_INCREMENTAL);
-                }
+
+                d->rd_rbytes = rrddim_add(d->st_bandwidth, "received", NULL, 8, 1024, RRDDIM_INCREMENTAL);
+                d->rd_tbytes = rrddim_add(d->st_bandwidth, "sent", NULL, -8, 1024, RRDDIM_INCREMENTAL);
             }
             else rrdset_next(d->st_bandwidth);
 
@@ -227,14 +229,14 @@ int do_proc_net_dev(int update_every, usec_t dt) {
             if(unlikely(!d->st_packets)) {
                 d->st_packets = rrdset_find_bytype("net_packets", d->name);
 
-                if(!d->st_packets) {
+                if(!d->st_packets)
                     d->st_packets = rrdset_create("net_packets", d->name, NULL, d->name, "net.packets", "Packets", "packets/s", 7001, update_every, RRDSET_TYPE_LINE);
-                    d->st_packets->isdetail = 1;
 
-                    d->rd_rpackets = rrddim_add(d->st_packets, "received", NULL, 1, 1, RRDDIM_INCREMENTAL);
-                    d->rd_tpackets = rrddim_add(d->st_packets, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL);
-                    d->rd_rmulticast = rrddim_add(d->st_packets, "multicast", NULL, 1, 1, RRDDIM_INCREMENTAL);
-                }
+                d->st_packets->isdetail = 1;
+
+                d->rd_rpackets = rrddim_add(d->st_packets, "received", NULL, 1, 1, RRDDIM_INCREMENTAL);
+                d->rd_tpackets = rrddim_add(d->st_packets, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL);
+                d->rd_rmulticast = rrddim_add(d->st_packets, "multicast", NULL, 1, 1, RRDDIM_INCREMENTAL);
             }
             else rrdset_next(d->st_packets);
 
@@ -253,13 +255,13 @@ int do_proc_net_dev(int update_every, usec_t dt) {
             if(unlikely(!d->st_errors)) {
                 d->st_errors = rrdset_find_bytype("net_errors", d->name);
 
-                if(!d->st_errors) {
+                if(!d->st_errors)
                     d->st_errors = rrdset_create("net_errors", d->name, NULL, d->name, "net.errors", "Interface Errors", "errors/s", 7002, update_every, RRDSET_TYPE_LINE);
-                    d->st_errors->isdetail = 1;
 
-                    d->rd_rerrors = rrddim_add(d->st_errors, "inbound", NULL, 1, 1, RRDDIM_INCREMENTAL);
-                    d->rd_terrors = rrddim_add(d->st_errors, "outbound", NULL, -1, 1, RRDDIM_INCREMENTAL);
-                }
+                d->st_errors->isdetail = 1;
+
+                d->rd_rerrors = rrddim_add(d->st_errors, "inbound", NULL, 1, 1, RRDDIM_INCREMENTAL);
+                d->rd_terrors = rrddim_add(d->st_errors, "outbound", NULL, -1, 1, RRDDIM_INCREMENTAL);
             }
             else rrdset_next(d->st_errors);
 
@@ -277,13 +279,13 @@ int do_proc_net_dev(int update_every, usec_t dt) {
             if(unlikely(!d->st_drops)) {
                 d->st_drops = rrdset_find_bytype("net_drops", d->name);
 
-                if(!d->st_drops) {
+                if(!d->st_drops)
                     d->st_drops = rrdset_create("net_drops", d->name, NULL, d->name, "net.drops", "Interface Drops", "drops/s", 7003, update_every, RRDSET_TYPE_LINE);
-                    d->st_drops->isdetail = 1;
 
-                    d->rd_rdrops = rrddim_add(d->st_drops, "inbound", NULL, 1, 1, RRDDIM_INCREMENTAL);
-                    d->rd_tdrops = rrddim_add(d->st_drops, "outbound", NULL, -1, 1, RRDDIM_INCREMENTAL);
-                }
+                d->st_drops->isdetail = 1;
+
+                d->rd_rdrops = rrddim_add(d->st_drops, "inbound", NULL, 1, 1, RRDDIM_INCREMENTAL);
+                d->rd_tdrops = rrddim_add(d->st_drops, "outbound", NULL, -1, 1, RRDDIM_INCREMENTAL);
             }
             else rrdset_next(d->st_drops);
 
@@ -301,13 +303,13 @@ int do_proc_net_dev(int update_every, usec_t dt) {
             if(unlikely(!d->st_fifo)) {
                 d->st_fifo = rrdset_find_bytype("net_fifo", d->name);
 
-                if(!d->st_fifo) {
+                if(!d->st_fifo)
                     d->st_fifo = rrdset_create("net_fifo", d->name, NULL, d->name, "net.fifo", "Interface FIFO Buffer Errors", "errors", 7004, update_every, RRDSET_TYPE_LINE);
-                    d->st_fifo->isdetail = 1;
 
-                    d->rd_rfifo = rrddim_add(d->st_fifo, "receive", NULL, 1, 1, RRDDIM_INCREMENTAL);
-                    d->rd_tfifo = rrddim_add(d->st_fifo, "transmit", NULL, -1, 1, RRDDIM_INCREMENTAL);
-                }
+                d->st_fifo->isdetail = 1;
+
+                d->rd_rfifo = rrddim_add(d->st_fifo, "receive", NULL, 1, 1, RRDDIM_INCREMENTAL);
+                d->rd_tfifo = rrddim_add(d->st_fifo, "transmit", NULL, -1, 1, RRDDIM_INCREMENTAL);
             }
             else rrdset_next(d->st_fifo);
 
@@ -324,13 +326,13 @@ int do_proc_net_dev(int update_every, usec_t dt) {
         if(d->do_compressed == CONFIG_ONDEMAND_YES) {
             if(unlikely(!d->st_compressed)) {
                 d->st_compressed = rrdset_find_bytype("net_compressed", d->name);
-                if(!d->st_compressed) {
+                if(!d->st_compressed)
                     d->st_compressed = rrdset_create("net_compressed", d->name, NULL, d->name, "net.compressed", "Compressed Packets", "packets/s", 7005, update_every, RRDSET_TYPE_LINE);
-                    d->st_compressed->isdetail = 1;
 
-                    d->rd_rcompressed = rrddim_add(d->st_compressed, "received", NULL, 1, 1, RRDDIM_INCREMENTAL);
-                    d->rd_tcompressed = rrddim_add(d->st_compressed, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL);
-                }
+                d->st_compressed->isdetail = 1;
+
+                d->rd_rcompressed = rrddim_add(d->st_compressed, "received", NULL, 1, 1, RRDDIM_INCREMENTAL);
+                d->rd_tcompressed = rrddim_add(d->st_compressed, "sent", NULL, -1, 1, RRDDIM_INCREMENTAL);
             }
             else rrdset_next(d->st_compressed);
 
@@ -347,14 +349,14 @@ int do_proc_net_dev(int update_every, usec_t dt) {
         if(d->do_events == CONFIG_ONDEMAND_YES) {
             if(unlikely(!d->st_events)) {
                 d->st_events = rrdset_find_bytype("net_events", d->name);
-                if(!d->st_events) {
+                if(!d->st_events)
                     d->st_events = rrdset_create("net_events", d->name, NULL, d->name, "net.events", "Network Interface Events", "events/s", 7006, update_every, RRDSET_TYPE_LINE);
-                    d->st_events->isdetail = 1;
 
-                    d->rd_rframe      = rrddim_add(d->st_events, "frames", NULL, 1, 1, RRDDIM_INCREMENTAL);
-                    d->rd_tcollisions = rrddim_add(d->st_events, "collisions", NULL, -1, 1, RRDDIM_INCREMENTAL);
-                    d->rd_tcarrier    = rrddim_add(d->st_events, "carrier", NULL, -1, 1, RRDDIM_INCREMENTAL);
-                }
+                d->st_events->isdetail = 1;
+
+                d->rd_rframe      = rrddim_add(d->st_events, "frames", NULL, 1, 1, RRDDIM_INCREMENTAL);
+                d->rd_tcollisions = rrddim_add(d->st_events, "collisions", NULL, -1, 1, RRDDIM_INCREMENTAL);
+                d->rd_tcarrier    = rrddim_add(d->st_events, "carrier", NULL, -1, 1, RRDDIM_INCREMENTAL);
             }
             else rrdset_next(d->st_events);
 
index 7c1d315585cffa87fa4aa07849e8591500da3b2f..8471d407bd4e01c533cfdd957daeaf932afa6040 100644 (file)
@@ -173,7 +173,7 @@ static inline int is_read_only(const char *s) {
 }
 
 // read the whole mountinfo into a linked list
-struct mountinfo *mountinfo_read() {
+struct mountinfo *mountinfo_read(int do_statvfs) {
     char filename[FILENAME_MAX + 1];
     snprintfz(filename, FILENAME_MAX, "%s/proc/self/mountinfo", global_host_prefix);
     procfile *ff = procfile_open(filename, " \t", PROCFILE_FLAG_DEFAULT);
@@ -282,7 +282,7 @@ struct mountinfo *mountinfo_read() {
                 mi->flags |= MOUNTINFO_IS_REMOTE;
 
             // mark as BIND the duplicates (i.e. same filesystem + same source)
-            {
+            if(do_statvfs) {
                 struct stat buf;
                 if(unlikely(stat(mi->mount_point, &buf) == -1)) {
                     mi->st_dev = 0;
@@ -302,6 +302,9 @@ struct mountinfo *mountinfo_read() {
                     }
                 }
             }
+            else {
+                mi->st_dev = 0;
+            }
         }
         else {
             mi->filesystem = NULL;
@@ -316,7 +319,7 @@ struct mountinfo *mountinfo_read() {
         }
 
         // check if it has size
-        {
+        if(do_statvfs) {
             struct statvfs buff_statvfs;
             if(unlikely(statvfs(mi->mount_point, &buff_statvfs) < 0)) {
                 mi->flags |= MOUNTINFO_NO_STAT;
index 53f4456498f4340994af1e6e1e55f6be8e9aa50a..00cf699abc39698dde5bf059f616baeba428019f 100644 (file)
@@ -50,6 +50,6 @@ extern struct mountinfo *mountinfo_find_by_filesystem_mount_source(struct mounti
 extern struct mountinfo *mountinfo_find_by_filesystem_super_option(struct mountinfo *root, const char *filesystem, const char *super_options);
 
 extern void mountinfo_free(struct mountinfo *mi);
-extern struct mountinfo *mountinfo_read();
+extern struct mountinfo *mountinfo_read(int do_statvfs);
 
 #endif /* NETDATA_PROC_SELF_MOUNTINFO_H */
\ No newline at end of file
index e2de10430fd0273c611d2ef905090986476830d5..27e8e6b235620fe004ee371ec7e1108f08fd9c1b 100644 (file)
--- a/src/rrd.c
+++ b/src/rrd.c
@@ -648,18 +648,26 @@ RRDSET *rrdset_create(const char *type, const char *id, const char *name, const
 
 RRDDIM *rrddim_add(RRDSET *st, const char *id, const char *name, long multiplier, long divisor, int algorithm)
 {
+    RRDDIM *rd = rrddim_find(st, id);
+    if(rd) {
+        error("Cannot create rrd dimension '%s/%s', it already exists.", st->id, name);
+        return rd;
+    }
+
     char filename[FILENAME_MAX + 1];
     char fullfilename[FILENAME_MAX + 1];
 
     char varname[CONFIG_MAX_NAME + 1];
-    RRDDIM *rd = NULL;
     unsigned long size = sizeof(RRDDIM) + (st->entries * sizeof(storage_number));
 
     debug(D_RRD_CALLS, "Adding dimension '%s/%s'.", st->id, id);
 
     rrdset_strncpyz_name(filename, id, FILENAME_MAX);
     snprintfz(fullfilename, FILENAME_MAX, "%s/%s.db", st->cache_dir, filename);
-    if(rrd_memory_mode != RRD_MEMORY_MODE_RAM) rd = (RRDDIM *)mymmap(fullfilename, size, ((rrd_memory_mode == RRD_MEMORY_MODE_MAP)?MAP_SHARED:MAP_PRIVATE), 1);
+
+    if(rrd_memory_mode != RRD_MEMORY_MODE_RAM)
+        rd = (RRDDIM *)mymmap(fullfilename, size, ((rrd_memory_mode == RRD_MEMORY_MODE_MAP)?MAP_SHARED:MAP_PRIVATE), 1);
+
     if(rd) {
         struct timeval now;
         now_realtime_timeval(&now);
@@ -872,14 +880,7 @@ void rrdset_free_all(void)
 
         pthread_rwlock_unlock(&st->rwlock);
 
-        if(st->mapped == RRD_MEMORY_MODE_SAVE) {
-            debug(D_RRD_CALLS, "Saving stats '%s' to '%s'.", st->name, st->cache_filename);
-            savememory(st->cache_filename, st, st->memsize);
-
-            debug(D_RRD_CALLS, "Unmapping stats '%s'.", st->name);
-            munmap(st, st->memsize);
-        }
-        else if(st->mapped == RRD_MEMORY_MODE_MAP) {
+        if(st->mapped == RRD_MEMORY_MODE_SAVE || st->mapped == RRD_MEMORY_MODE_MAP) {
             debug(D_RRD_CALLS, "Unmapping stats '%s'.", st->name);
             munmap(st, st->memsize);
         }
@@ -901,9 +902,12 @@ void rrdset_save_all(void) {
     RRDSET *st;
     RRDDIM *rd;
 
+    // we get an write lock
+    // to ensure only one thread is saving the database
     rrdhost_rwlock(&localhost);
+
     for(st = localhost.rrdset_root; st ; st = st->next) {
-        pthread_rwlock_wrlock(&st->rwlock);
+        pthread_rwlock_rdlock(&st->rwlock);
 
         if(st->mapped == RRD_MEMORY_MODE_SAVE) {
             debug(D_RRD_CALLS, "Saving stats '%s' to '%s'.", st->name, st->cache_filename);
@@ -919,6 +923,7 @@ void rrdset_save_all(void) {
 
         pthread_rwlock_unlock(&st->rwlock);
     }
+
     rrdhost_unlock(&localhost);
 }
 
diff --git a/src/simple_pattern.c b/src/simple_pattern.c
new file mode 100644 (file)
index 0000000..7e44242
--- /dev/null
@@ -0,0 +1,197 @@
+#include "common.h"
+
+struct simple_pattern {
+    const char *match;
+    size_t len;
+
+    SIMPLE_PREFIX_MODE mode;
+    char negative;
+
+    struct simple_pattern *child;
+
+    struct simple_pattern *next;
+};
+
+static inline struct simple_pattern *parse_pattern(const char *str, SIMPLE_PREFIX_MODE default_mode) {
+    SIMPLE_PREFIX_MODE mode;
+    struct simple_pattern *child = NULL;
+
+    char *buf = strdupz(str);
+    char *s = buf, *c = buf;
+
+    // skip asterisks in front
+    while(*c == '*') c++;
+
+    // find the next asterisk
+    while(*c && *c != '*') c++;
+
+    // do we have an asterisk in the middle?
+    if(*c == '*' && c[1] != '\0') {
+        // yes, we have
+        child = parse_pattern(c, default_mode);
+        c[1] = '\0';
+    }
+
+    // check what this one matches
+
+    size_t len = strlen(s);
+    if(len >= 2 && *s == '*' && s[len - 1] == '*') {
+        s[len - 1] = '\0';
+        s++;
+        mode = SIMPLE_PATTERN_SUBSTRING;
+    }
+    else if(len >= 1 && *s == '*') {
+        s++;
+        mode = SIMPLE_PATTERN_SUFFIX;
+    }
+    else if(len >= 1 && s[len - 1] == '*') {
+        s[len - 1] = '\0';
+        mode = SIMPLE_PATTERN_PREFIX;
+    }
+    else
+        mode = default_mode;
+
+    // allocate the structure
+    struct simple_pattern *m = callocz(1, sizeof(struct simple_pattern));
+    if(*s) {
+        m->match = strdupz(s);
+        m->len = strlen(m->match);
+        m->mode = mode;
+    }
+    else {
+        m->mode = SIMPLE_PATTERN_SUBSTRING;
+    }
+
+    m->child = child;
+
+    freez(buf);
+
+    return m;
+}
+
+SIMPLE_PATTERN *simple_pattern_create(const char *list, SIMPLE_PREFIX_MODE default_mode) {
+    struct simple_pattern *root = NULL, *last = NULL;
+
+    if(unlikely(!list || !*list)) return root;
+
+    char *buf = strdupz(list);
+    if(buf && *buf) {
+        char *s = buf;
+
+        while(s && *s) {
+            char negative = 0;
+
+            // skip all spaces
+            while(isspace(*s)) s++;
+
+            if(*s == '!') {
+                negative = 1;
+                s++;
+            }
+
+            // empty string
+            if(unlikely(!*s)) break;
+
+            // find the next space
+            char *c = s;
+            while(*c && !isspace(*c)) c++;
+
+            // find the next word
+            char *n;
+            if(likely(*c)) n = c + 1;
+            else n = NULL;
+
+            // terminate our string
+            *c = '\0';
+
+            struct simple_pattern *m = parse_pattern(s, default_mode);
+            m->negative = negative;
+
+            if(likely(n)) *c = ' ';
+
+            // link it at the end
+            if(unlikely(!root))
+                root = last = m;
+            else {
+                last->next = m;
+                last = m;
+            }
+
+            // prepare for next loop
+            s = n;
+        }
+    }
+
+    freez(buf);
+    return (SIMPLE_PATTERN *)root;
+}
+
+static inline int match_pattern(struct simple_pattern *m, const char *str, size_t len) {
+    char *s;
+
+    if(m->len <= len) {
+        switch(m->mode) {
+            case SIMPLE_PATTERN_SUBSTRING:
+                if(!m->len) return 1;
+                if((s = strstr(str, m->match))) {
+                    if(!m->child) return 1;
+                    return match_pattern(m->child, &s[m->len], len - (s - str) - m->len);
+                }
+                break;
+
+            case SIMPLE_PATTERN_PREFIX:
+                if(unlikely(strncmp(str, m->match, m->len) == 0)) {
+                    if(!m->child) return 1;
+                    return match_pattern(m->child, &str[m->len], len - m->len);
+                }
+                break;
+
+            case SIMPLE_PATTERN_SUFFIX:
+                if(unlikely(strcmp(&str[len - m->len], m->match) == 0)) {
+                    if(!m->child) return 1;
+                    return 0;
+                }
+                break;
+
+            case SIMPLE_PATTERN_EXACT:
+            default:
+                if(unlikely(strcmp(str, m->match) == 0)) {
+                    if(!m->child) return 1;
+                    return 0;
+                }
+                break;
+        }
+    }
+
+    return 0;
+}
+
+int simple_pattern_matches(SIMPLE_PATTERN *list, const char *str) {
+    struct simple_pattern *m, *root = (struct simple_pattern *)list;
+
+    if(unlikely(!root)) return 0;
+
+    size_t len = strlen(str);
+    for(m = root; m ; m = m->next)
+        if(match_pattern(m, str, len)) {
+            if(m->negative) return 0;
+            return 1;
+        }
+
+    return 0;
+}
+
+static inline void free_pattern(struct simple_pattern *m) {
+    if(!m) return;
+
+    if(m->next) free_pattern(m->next);
+    if(m->child) free_pattern(m->child);
+    freez((void *)m->match);
+    freez(m);
+}
+
+void simple_pattern_free(SIMPLE_PATTERN *list) {
+    if(!list) return;
+
+    free_pattern(((struct simple_pattern *)list)->next);
+}
diff --git a/src/simple_pattern.h b/src/simple_pattern.h
new file mode 100644 (file)
index 0000000..3768c50
--- /dev/null
@@ -0,0 +1,25 @@
+#ifndef NETDATA_SIMPLE_PATTERN_H
+#define NETDATA_SIMPLE_PATTERN_H
+
+typedef enum {
+    SIMPLE_PATTERN_EXACT,
+    SIMPLE_PATTERN_PREFIX,
+    SIMPLE_PATTERN_SUFFIX,
+    SIMPLE_PATTERN_SUBSTRING
+} SIMPLE_PREFIX_MODE;
+
+typedef void SIMPLE_PATTERN;
+
+// create a simple_pattern from the string given
+// default_mode is used in cases where EXACT matches, without an asterisk,
+// should be considered PREFIX matches.
+extern SIMPLE_PATTERN *simple_pattern_create(const char *list, SIMPLE_PREFIX_MODE default_mode);
+
+// test if string str is matched from the pattern
+extern int simple_pattern_matches(SIMPLE_PATTERN *list, const char *str);
+
+// free a simple_pattern that was created with simple_pattern_create()
+// list can be NULL, in which case, this does nothing.
+extern void simple_pattern_free(SIMPLE_PATTERN *list);
+
+#endif //NETDATA_SIMPLE_PATTERN_H
index 1e1aa9c7584a74f201fd8f4394b71e39f47c24e3..868329e3ade696bb034a4a1ddd76966248368e59 100644 (file)
@@ -131,12 +131,11 @@ int do_proc_sys_devices_system_edac_mc(int update_every, usec_t dt) {
 
         static RRDSET *ce_st = NULL;
 
-        if(unlikely(!ce_st))
-            ce_st = rrdset_find("mem.ecc_ce");
-
         if(unlikely(!ce_st)) {
-            ce_st = rrdset_create("mem", "ecc_ce", NULL, "ecc", NULL, "ECC Memory Correctable Errors", "errors", 6600
-                                  , update_every, RRDSET_TYPE_LINE);
+            ce_st = rrdset_find("mem.ecc_ce");
+            if(unlikely(!ce_st))
+                ce_st = rrdset_create("mem", "ecc_ce", NULL, "ecc", NULL, "ECC Memory Correctable Errors", "errors",
+                        6600, update_every, RRDSET_TYPE_LINE);
 
             for(m = mc_root; m; m = m->next)
                 if(m->ce_count_filename)
@@ -159,12 +158,12 @@ int do_proc_sys_devices_system_edac_mc(int update_every, usec_t dt) {
 
         static RRDSET *ue_st = NULL;
 
-        if(unlikely(!ue_st))
+        if(unlikely(!ue_st)) {
             ue_st = rrdset_find("mem.ecc_ue");
 
-        if(unlikely(!ue_st)) {
-            ue_st = rrdset_create("mem", "ecc_ue", NULL, "ecc", NULL, "ECC Memory Uncorrectable Errors", "errors", 6610
-                                  , update_every, RRDSET_TYPE_LINE);
+            if(unlikely(!ue_st))
+                ue_st = rrdset_create("mem", "ecc_ue", NULL, "ecc", NULL, "ECC Memory Uncorrectable Errors", "errors",
+                        6610, update_every, RRDSET_TYPE_LINE);
 
             for(m = mc_root; m; m = m->next)
                 if(m->ue_count_filename)
index c2c68ebce016d35aebacae8b66adf2ac2f1f4ecc..21cd9a5e7a657a2adb40f06d81b1529e2afb7e37 100644 (file)
@@ -3,13 +3,38 @@
 // ----------------------------------------------------------------------------
 // cgroup globals
 
+#define CHART_PRIORITY_SYSTEMD_SERVICES 19000
+#define CHART_PRIORITY_CONTAINERS       40000
+
+static long system_page_size = 4096; // system will be queried via sysconf() in configuration()
+
 static int cgroup_enable_cpuacct_stat = CONFIG_ONDEMAND_ONDEMAND;
 static int cgroup_enable_cpuacct_usage = CONFIG_ONDEMAND_ONDEMAND;
 static int cgroup_enable_memory = CONFIG_ONDEMAND_ONDEMAND;
-static int cgroup_enable_devices = CONFIG_ONDEMAND_ONDEMAND;
-static int cgroup_enable_blkio = CONFIG_ONDEMAND_ONDEMAND;
+static int cgroup_enable_detailed_memory = CONFIG_ONDEMAND_ONDEMAND;
+static int cgroup_enable_memory_failcnt = CONFIG_ONDEMAND_ONDEMAND;
+static int cgroup_enable_swap = CONFIG_ONDEMAND_ONDEMAND;
+static int cgroup_enable_blkio_io = CONFIG_ONDEMAND_ONDEMAND;
+static int cgroup_enable_blkio_ops = CONFIG_ONDEMAND_ONDEMAND;
+static int cgroup_enable_blkio_throttle_io = CONFIG_ONDEMAND_ONDEMAND;
+static int cgroup_enable_blkio_throttle_ops = CONFIG_ONDEMAND_ONDEMAND;
+static int cgroup_enable_blkio_merged_ops = CONFIG_ONDEMAND_ONDEMAND;
+static int cgroup_enable_blkio_queued_ops = CONFIG_ONDEMAND_ONDEMAND;
+
+static int cgroup_enable_systemd_services = CONFIG_ONDEMAND_YES;
+static int cgroup_enable_systemd_services_detailed_memory = CONFIG_ONDEMAND_NO;
+static int cgroup_used_memory_without_cache = CONFIG_ONDEMAND_YES;
+
+static int cgroup_search_in_devices = 1;
+
 static int cgroup_enable_new_cgroups_detected_at_runtime = 1;
 static int cgroup_check_for_new_every = 10;
+static int cgroup_update_every = 1;
+
+static int cgroup_recheck_zero_blkio_every_iterations = 10;
+static int cgroup_recheck_zero_mem_failcnt_every_iterations = 10;
+static int cgroup_recheck_zero_mem_detailed_every_iterations = 10;
+
 static char *cgroup_cpuacct_base = NULL;
 static char *cgroup_blkio_base = NULL;
 static char *cgroup_memory_base = NULL;
@@ -19,16 +44,131 @@ static int cgroup_root_count = 0;
 static int cgroup_root_max = 500;
 static int cgroup_max_depth = 0;
 
-void read_cgroup_plugin_configuration() {
-    cgroup_check_for_new_every = config_get_number("plugin:cgroups", "check for new cgroups every", cgroup_check_for_new_every);
+static SIMPLE_PATTERN *enabled_cgroup_patterns = NULL;
+static SIMPLE_PATTERN *enabled_cgroup_paths = NULL;
+static SIMPLE_PATTERN *enabled_cgroup_renames = NULL;
+static SIMPLE_PATTERN *systemd_services_cgroups = NULL;
+
+static char *cgroups_rename_script = PLUGINS_DIR "/cgroup-name.sh";
+
+static uint32_t Read_hash = 0;
+static uint32_t Write_hash = 0;
+static uint32_t Sync_hash = 0;
+static uint32_t Async_hash = 0;
+static uint32_t Total_hash = 0;
+static uint32_t user_hash = 0;
+static uint32_t system_hash = 0;
+static uint32_t cache_hash = 0;
+static uint32_t rss_hash = 0;
+static uint32_t rss_huge_hash = 0;
+static uint32_t mapped_file_hash = 0;
+static uint32_t writeback_hash = 0;
+static uint32_t dirty_hash = 0;
+static uint32_t swap_hash = 0;
+static uint32_t pgpgin_hash = 0;
+static uint32_t pgpgout_hash = 0;
+static uint32_t pgfault_hash = 0;
+static uint32_t pgmajfault_hash = 0;
+static uint32_t inactive_anon_hash = 0;
+static uint32_t active_anon_hash = 0;
+static uint32_t inactive_file_hash = 0;
+static uint32_t active_file_hash = 0;
+static uint32_t unevictable_hash = 0;
+static uint32_t hierarchical_memory_limit_hash = 0;
+static uint32_t total_cache_hash = 0;
+static uint32_t total_rss_hash = 0;
+static uint32_t total_rss_huge_hash = 0;
+static uint32_t total_mapped_file_hash = 0;
+static uint32_t total_writeback_hash = 0;
+static uint32_t total_dirty_hash = 0;
+static uint32_t total_swap_hash = 0;
+static uint32_t total_pgpgin_hash = 0;
+static uint32_t total_pgpgout_hash = 0;
+static uint32_t total_pgfault_hash = 0;
+static uint32_t total_pgmajfault_hash = 0;
+static uint32_t total_inactive_anon_hash = 0;
+static uint32_t total_active_anon_hash = 0;
+static uint32_t total_inactive_file_hash = 0;
+static uint32_t total_active_file_hash = 0;
+static uint32_t total_unevictable_hash = 0;
 
-    cgroup_enable_cpuacct_stat = config_get_boolean_ondemand("plugin:cgroups", "enable cpuacct stat", cgroup_enable_cpuacct_stat);
-    cgroup_enable_cpuacct_usage = config_get_boolean_ondemand("plugin:cgroups", "enable cpuacct usage", cgroup_enable_cpuacct_usage);
-    cgroup_enable_memory = config_get_boolean_ondemand("plugin:cgroups", "enable memory", cgroup_enable_memory);
-    cgroup_enable_blkio = config_get_boolean_ondemand("plugin:cgroups", "enable blkio", cgroup_enable_blkio);
+void read_cgroup_plugin_configuration() {
+    system_page_size = sysconf(_SC_PAGESIZE);
+
+    Read_hash = simple_hash("Read");
+    Write_hash = simple_hash("Write");
+    Sync_hash = simple_hash("Sync");
+    Async_hash = simple_hash("Async");
+    Total_hash = simple_hash("Total");
+    user_hash = simple_hash("user");
+    system_hash = simple_hash("system");
+    cache_hash = simple_hash("cache");
+    rss_hash = simple_hash("rss");
+    rss_huge_hash = simple_hash("rss_huge");
+    mapped_file_hash = simple_hash("mapped_file");
+    writeback_hash = simple_hash("writeback");
+    dirty_hash = simple_hash("dirty");
+    swap_hash = simple_hash("swap");
+    pgpgin_hash = simple_hash("pgpgin");
+    pgpgout_hash = simple_hash("pgpgout");
+    pgfault_hash = simple_hash("pgfault");
+    pgmajfault_hash = simple_hash("pgmajfault");
+    inactive_anon_hash = simple_hash("inactive_anon");
+    active_anon_hash = simple_hash("active_anon");
+    inactive_file_hash = simple_hash("inactive_file");
+    active_file_hash = simple_hash("active_file");
+    unevictable_hash = simple_hash("unevictable");
+    hierarchical_memory_limit_hash = simple_hash("hierarchical_memory_limit");
+    total_cache_hash = simple_hash("total_cache");
+    total_rss_hash = simple_hash("total_rss");
+    total_rss_huge_hash = simple_hash("total_rss_huge");
+    total_mapped_file_hash = simple_hash("total_mapped_file");
+    total_writeback_hash = simple_hash("total_writeback");
+    total_dirty_hash = simple_hash("total_dirty");
+    total_swap_hash = simple_hash("total_swap");
+    total_pgpgin_hash = simple_hash("total_pgpgin");
+    total_pgpgout_hash = simple_hash("total_pgpgout");
+    total_pgfault_hash = simple_hash("total_pgfault");
+    total_pgmajfault_hash = simple_hash("total_pgmajfault");
+    total_inactive_anon_hash = simple_hash("total_inactive_anon");
+    total_active_anon_hash = simple_hash("total_active_anon");
+    total_inactive_file_hash = simple_hash("total_inactive_file");
+    total_active_file_hash = simple_hash("total_active_file");
+    total_unevictable_hash = simple_hash("total_unevictable");
+
+    cgroup_update_every = (int)config_get_number("plugin:cgroups", "update every", rrd_update_every);
+    if(cgroup_update_every < rrd_update_every)
+        cgroup_update_every = rrd_update_every;
+
+    cgroup_check_for_new_every = (int)config_get_number("plugin:cgroups", "check for new cgroups every", cgroup_check_for_new_every * cgroup_update_every);
+    if(cgroup_check_for_new_every < cgroup_update_every)
+        cgroup_check_for_new_every = cgroup_update_every;
+
+    cgroup_enable_cpuacct_stat = config_get_boolean_ondemand("plugin:cgroups", "enable cpuacct stat (total CPU)", cgroup_enable_cpuacct_stat);
+    cgroup_enable_cpuacct_usage = config_get_boolean_ondemand("plugin:cgroups", "enable cpuacct usage (per core CPU)", cgroup_enable_cpuacct_usage);
+
+    cgroup_enable_memory = config_get_boolean_ondemand("plugin:cgroups", "enable memory (used mem including cache)", cgroup_enable_memory);
+    cgroup_enable_detailed_memory = config_get_boolean_ondemand("plugin:cgroups", "enable detailed memory", cgroup_enable_detailed_memory);
+    cgroup_enable_memory_failcnt = config_get_boolean_ondemand("plugin:cgroups", "enable memory limits fail count", cgroup_enable_memory_failcnt);
+    cgroup_enable_swap = config_get_boolean_ondemand("plugin:cgroups", "enable swap memory", cgroup_enable_swap);
+
+    cgroup_enable_blkio_io = config_get_boolean_ondemand("plugin:cgroups", "enable blkio bandwidth", cgroup_enable_blkio_io);
+    cgroup_enable_blkio_ops = config_get_boolean_ondemand("plugin:cgroups", "enable blkio operations", cgroup_enable_blkio_ops);
+    cgroup_enable_blkio_throttle_io = config_get_boolean_ondemand("plugin:cgroups", "enable blkio throttle bandwidth", cgroup_enable_blkio_throttle_io);
+    cgroup_enable_blkio_throttle_ops = config_get_boolean_ondemand("plugin:cgroups", "enable blkio throttle operations", cgroup_enable_blkio_throttle_ops);
+    cgroup_enable_blkio_queued_ops = config_get_boolean_ondemand("plugin:cgroups", "enable blkio queued operations", cgroup_enable_blkio_queued_ops);
+    cgroup_enable_blkio_merged_ops = config_get_boolean_ondemand("plugin:cgroups", "enable blkio merged operations", cgroup_enable_blkio_merged_ops);
+
+    cgroup_recheck_zero_blkio_every_iterations = (int)config_get_number("plugin:cgroups", "recheck zero blkio every iterations", cgroup_recheck_zero_blkio_every_iterations);
+    cgroup_recheck_zero_mem_failcnt_every_iterations = (int)config_get_number("plugin:cgroups", "recheck zero memory failcnt every iterations", cgroup_recheck_zero_mem_failcnt_every_iterations);
+    cgroup_recheck_zero_mem_detailed_every_iterations = (int)config_get_number("plugin:cgroups", "recheck zero detailed memory every iterations", cgroup_recheck_zero_mem_detailed_every_iterations);
+
+    cgroup_enable_systemd_services = config_get_boolean("plugin:cgroups", "enable systemd services", cgroup_enable_systemd_services);
+    cgroup_enable_systemd_services_detailed_memory = config_get_boolean("plugin:cgroups", "enable systemd services detailed memory", cgroup_enable_systemd_services_detailed_memory);
+    cgroup_used_memory_without_cache = config_get_boolean("plugin:cgroups", "report used memory without cache", cgroup_used_memory_without_cache);
 
     char filename[FILENAME_MAX + 1], *s;
-    struct mountinfo *mi, *root = mountinfo_read();
+    struct mountinfo *mi, *root = mountinfo_read(0);
 
     mi = mountinfo_find_by_filesystem_super_option(root, "cgroup", "cpuacct");
     if(!mi) mi = mountinfo_find_by_filesystem_mount_source(root, "cgroup", "cpuacct");
@@ -70,11 +210,67 @@ void read_cgroup_plugin_configuration() {
     snprintfz(filename, FILENAME_MAX, "%s%s", global_host_prefix, s);
     cgroup_devices_base = config_get("plugin:cgroups", "path to /sys/fs/cgroup/devices", filename);
 
-    cgroup_root_max = config_get_number("plugin:cgroups", "max cgroups to allow", cgroup_root_max);
-    cgroup_max_depth = config_get_number("plugin:cgroups", "max cgroups depth to monitor", cgroup_max_depth);
+    cgroup_root_max = (int)config_get_number("plugin:cgroups", "max cgroups to allow", cgroup_root_max);
+    cgroup_max_depth = (int)config_get_number("plugin:cgroups", "max cgroups depth to monitor", cgroup_max_depth);
 
     cgroup_enable_new_cgroups_detected_at_runtime = config_get_boolean("plugin:cgroups", "enable new cgroups detected at run time", cgroup_enable_new_cgroups_detected_at_runtime);
 
+    enabled_cgroup_patterns = simple_pattern_create(
+            config_get("plugin:cgroups", "enable by default cgroups matching",
+                    " !*.mount "
+                    " !*.partition "
+                    " !*.scope "
+                    " !*.service "
+                    " !*.slice "
+                    " !*.swap "
+                    " !*.user "
+                    " !/ "
+                    " !/docker "
+                    " !/libvirt "
+                    " !/lxc "
+                    " !/lxc/*/ns "         //  #1397
+                    " !/machine "
+                    " !/qemu "
+                    " !/system "
+                    " !/systemd "
+                    " !/user "
+                    " * "                  // enable anything else
+            ), SIMPLE_PATTERN_EXACT);
+
+    enabled_cgroup_paths = simple_pattern_create(
+            config_get("plugin:cgroups", "search for cgroups in subpaths matching",
+                    " !*-qemu "            //  #345
+                    " !/init.scope "
+                    " !/system "
+                    " !/systemd "
+                    " !/user "
+                    " !/user.slice "
+                    " * "
+            ), SIMPLE_PATTERN_EXACT);
+
+    cgroups_rename_script = config_get("plugin:cgroups", "script to get cgroup names", cgroups_rename_script);
+
+    enabled_cgroup_renames = simple_pattern_create(
+            config_get("plugin:cgroups", "run script to rename cgroups matching",
+                    " !/ "
+                    " !*.mount "
+                    " !*.partition "
+                    " !*.scope "
+                    " !*.service "
+                    " !*.slice "
+                    " !*.swap "
+                    " !*.user "
+                    " * "
+            ), SIMPLE_PATTERN_EXACT);
+
+    if(cgroup_enable_systemd_services) {
+        systemd_services_cgroups = simple_pattern_create(
+                config_get("plugin:cgroups", "cgroups to match as systemd services",
+                        " !/system.slice/*/*.service "
+                        " /system.slice/*.service "
+                ), SIMPLE_PATTERN_EXACT);
+    }
+
     mountinfo_free(root);
 }
 
@@ -83,6 +279,8 @@ void read_cgroup_plugin_configuration() {
 
 struct blkio {
     int updated;
+    int enabled; // CONFIG_ONDEMAND_YES or CONFIG_ONDEMAND_ONDEMAND
+    int delay_counter;
 
     char *filename;
 
@@ -97,12 +295,28 @@ struct blkio {
 
 // https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt
 struct memory {
-    int updated;
+    int updated_detailed;
+    int updated_usage_in_bytes;
+    int updated_msw_usage_in_bytes;
+    int updated_failcnt;
 
-    char *filename;
+    int enabled_detailed;           // CONFIG_ONDEMAND_YES or CONFIG_ONDEMAND_ONDEMAND
+    int enabled_usage_in_bytes;     // CONFIG_ONDEMAND_YES or CONFIG_ONDEMAND_ONDEMAND
+    int enabled_msw_usage_in_bytes; // CONFIG_ONDEMAND_YES or CONFIG_ONDEMAND_ONDEMAND
+    int enabled_failcnt;            // CONFIG_ONDEMAND_YES or CONFIG_ONDEMAND_ONDEMAND
 
-    int has_dirty_swap;
+    int delay_counter_detailed;
+    int delay_counter_failcnt;
 
+    char *filename_detailed;
+    char *filename_usage_in_bytes;
+    char *filename_msw_usage_in_bytes;
+    char *filename_failcnt;
+
+    int detailed_has_dirty;
+    int detailed_has_swap;
+
+    // detailed metrics
     unsigned long long cache;
     unsigned long long rss;
     unsigned long long rss_huge;
@@ -139,22 +353,16 @@ struct memory {
     unsigned long long total_unevictable;
 */
 
-    int usage_in_bytes_updated;
-    char *filename_usage_in_bytes;
+    // single file metrics
     unsigned long long usage_in_bytes;
-
-    int msw_usage_in_bytes_updated;
-    char *filename_msw_usage_in_bytes;
     unsigned long long msw_usage_in_bytes;
-
-    int failcnt_updated;
-    char *filename_failcnt;
     unsigned long long failcnt;
 };
 
 // https://www.kernel.org/doc/Documentation/cgroup-v1/cpuacct.txt
 struct cpuacct_stat {
     int updated;
+    int enabled; // CONFIG_ONDEMAND_YES or CONFIG_ONDEMAND_ONDEMAND
 
     char *filename;
 
@@ -165,6 +373,7 @@ struct cpuacct_stat {
 // https://www.kernel.org/doc/Documentation/cgroup-v1/cpuacct.txt
 struct cpuacct_usage {
     int updated;
+    int enabled; // CONFIG_ONDEMAND_YES or CONFIG_ONDEMAND_ONDEMAND
 
     char *filename;
 
@@ -172,7 +381,8 @@ struct cpuacct_usage {
     unsigned long long *cpu_percpu;
 };
 
-#define CGROUP_OPTIONS_DISABLED_DUPLICATE 0x00000001
+#define CGROUP_OPTIONS_DISABLED_DUPLICATE   0x00000001
+#define CGROUP_OPTIONS_SYSTEM_SLICE_SERVICE 0x00000002
 
 struct cgroup {
     uint32_t options;
@@ -202,6 +412,53 @@ struct cgroup {
     struct blkio io_merged;                     // operations
     struct blkio io_queued;                     // operations
 
+    // per cgroup charts
+    RRDSET *st_cpu;
+    RRDSET *st_cpu_per_core;
+    RRDSET *st_mem;
+    RRDSET *st_writeback;
+    RRDSET *st_mem_activity;
+    RRDSET *st_pgfaults;
+    RRDSET *st_mem_usage;
+    RRDSET *st_mem_failcnt;
+    RRDSET *st_io;
+    RRDSET *st_serviced_ops;
+    RRDSET *st_throttle_io;
+    RRDSET *st_throttle_serviced_ops;
+    RRDSET *st_queued_ops;
+    RRDSET *st_merged_ops;
+
+    // services
+    RRDDIM *rd_cpu;
+    RRDDIM *rd_mem_usage;
+    RRDDIM *rd_mem_failcnt;
+    RRDDIM *rd_swap_usage;
+
+    RRDDIM *rd_mem_detailed_cache;
+    RRDDIM *rd_mem_detailed_rss;
+    RRDDIM *rd_mem_detailed_mapped;
+    RRDDIM *rd_mem_detailed_writeback;
+    RRDDIM *rd_mem_detailed_dirty;
+    RRDDIM *rd_mem_detailed_swap;
+    RRDDIM *rd_mem_detailed_pgpgin;
+    RRDDIM *rd_mem_detailed_pgpgout;
+    RRDDIM *rd_mem_detailed_pgfault;
+    RRDDIM *rd_mem_detailed_pgmajfault;
+
+    RRDDIM *rd_io_service_bytes_read;
+    RRDDIM *rd_io_serviced_read;
+    RRDDIM *rd_throttle_io_read;
+    RRDDIM *rd_throttle_io_serviced_read;
+    RRDDIM *rd_io_queued_read;
+    RRDDIM *rd_io_merged_read;
+
+    RRDDIM *rd_io_service_bytes_write;
+    RRDDIM *rd_io_serviced_write;
+    RRDDIM *rd_throttle_io_write;
+    RRDDIM *rd_throttle_io_serviced_write;
+    RRDDIM *rd_io_queued_write;
+    RRDDIM *rd_io_merged_write;
+
     struct cgroup *next;
 
 } *cgroup_root = NULL;
@@ -209,29 +466,27 @@ struct cgroup {
 // ----------------------------------------------------------------------------
 // read values from /sys
 
-void cgroup_read_cpuacct_stat(struct cpuacct_stat *cp) {
+static inline void cgroup_read_cpuacct_stat(struct cpuacct_stat *cp) {
     static procfile *ff = NULL;
 
-    static uint32_t user_hash = 0;
-    static uint32_t system_hash = 0;
-
-    if(unlikely(user_hash == 0)) {
-        user_hash = simple_hash("user");
-        system_hash = simple_hash("system");
-    }
-
-    cp->updated = 0;
-    if(cp->filename) {
+    if(likely(cp->filename)) {
         ff = procfile_reopen(ff, cp->filename, NULL, PROCFILE_FLAG_DEFAULT);
-        if(!ff) return;
+        if(unlikely(!ff)) {
+            cp->updated = 0;
+            return;
+        }
 
         ff = procfile_readall(ff);
-        if(!ff) return;
+        if(unlikely(!ff)) {
+            cp->updated = 0;
+            return;
+        }
 
         unsigned long i, lines = procfile_lines(ff);
 
-        if(lines < 1) {
+        if(unlikely(lines < 1)) {
             error("File '%s' should have 1+ lines.", cp->filename);
+            cp->updated = 0;
             return;
         }
 
@@ -239,37 +494,51 @@ void cgroup_read_cpuacct_stat(struct cpuacct_stat *cp) {
             char *s = procfile_lineword(ff, i, 0);
             uint32_t hash = simple_hash(s);
 
-            if(hash == user_hash && !strcmp(s, "user"))
+            if(unlikely(hash == user_hash && !strcmp(s, "user"))) {
                 cp->user = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 
-            else if(hash == system_hash && !strcmp(s, "system"))
+            if(unlikely(hash == system_hash && !strcmp(s, "system"))) {
                 cp->system = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
         }
 
         cp->updated = 1;
 
-        // fprintf(stderr, "READ '%s': user: %llu, system: %llu\n", cp->filename, cp->user, cp->system);
+        if(unlikely(cp->enabled == CONFIG_ONDEMAND_ONDEMAND && (cp->user || cp->system)))
+            cp->enabled = CONFIG_ONDEMAND_YES;
     }
 }
 
-void cgroup_read_cpuacct_usage(struct cpuacct_usage *ca) {
+static inline void cgroup_read_cpuacct_usage(struct cpuacct_usage *ca) {
     static procfile *ff = NULL;
 
-    ca->updated = 0;
-    if(ca->filename) {
+    if(likely(ca->filename)) {
         ff = procfile_reopen(ff, ca->filename, NULL, PROCFILE_FLAG_DEFAULT);
-        if(!ff) return;
+        if(unlikely(!ff)) {
+            ca->updated = 0;
+            return;
+        }
 
         ff = procfile_readall(ff);
-        if(!ff) return;
+        if(unlikely(!ff)) {
+            ca->updated = 0;
+            return;
+        }
 
-        if(procfile_lines(ff) < 1) {
+        if(unlikely(procfile_lines(ff) < 1)) {
             error("File '%s' should have 1+ lines but has %u.", ca->filename, procfile_lines(ff));
+            ca->updated = 0;
             return;
         }
 
         unsigned long i = procfile_linewords(ff, 0);
-        if(i == 0) return;
+        if(unlikely(i == 0)) {
+            return;
+            ca->updated = 0;
+        }
 
         // we may have 1 more CPU reported
         while(i > 0) {
@@ -278,54 +547,52 @@ void cgroup_read_cpuacct_usage(struct cpuacct_usage *ca) {
             else break;
         }
 
-        if(i != ca->cpus) {
+        if(unlikely(i != ca->cpus)) {
             freez(ca->cpu_percpu);
             ca->cpu_percpu = mallocz(sizeof(unsigned long long) * i);
             ca->cpus = (unsigned int)i;
         }
 
+        unsigned long long total = 0;
         for(i = 0; i < ca->cpus ;i++) {
-            ca->cpu_percpu[i] = strtoull(procfile_lineword(ff, 0, i), NULL, 10);
-            // fprintf(stderr, "READ '%s': cpu%d/%d: %llu ('%s')\n", ca->filename, i, ca->cpus, ca->cpu_percpu[i], procfile_lineword(ff, 0, i));
+            unsigned long long n = strtoull(procfile_lineword(ff, 0, i), NULL, 10);
+            ca->cpu_percpu[i] = n;
+            total += n;
         }
 
         ca->updated = 1;
+
+        if(unlikely(ca->enabled == CONFIG_ONDEMAND_ONDEMAND && total))
+            ca->enabled = CONFIG_ONDEMAND_YES;
     }
 }
 
-void cgroup_read_blkio(struct blkio *io) {
+static inline void cgroup_read_blkio(struct blkio *io) {
     static procfile *ff = NULL;
 
-    static uint32_t Read_hash = 0;
-    static uint32_t Write_hash = 0;
-/*
-    static uint32_t Sync_hash = 0;
-    static uint32_t Async_hash = 0;
-    static uint32_t Total_hash = 0;
-*/
-
-    if(unlikely(Read_hash == 0)) {
-        Read_hash = simple_hash("Read");
-        Write_hash = simple_hash("Write");
-/*
-        Sync_hash = simple_hash("Sync");
-        Async_hash = simple_hash("Async");
-        Total_hash = simple_hash("Total");
-*/
+    if(unlikely(io->enabled == CONFIG_ONDEMAND_ONDEMAND && io->delay_counter > 0)) {
+        io->delay_counter--;
+        return;
     }
 
-    io->updated = 0;
-    if(io->filename) {
+    if(likely(io->filename)) {
         ff = procfile_reopen(ff, io->filename, NULL, PROCFILE_FLAG_DEFAULT);
-        if(!ff) return;
+        if(unlikely(!ff)) {
+            io->updated = 0;
+            return;
+        }
 
         ff = procfile_readall(ff);
-        if(!ff) return;
+        if(unlikely(!ff)) {
+            io->updated = 0;
+            return;
+        }
 
         unsigned long i, lines = procfile_lines(ff);
 
-        if(lines < 1) {
+        if(unlikely(lines < 1)) {
             error("File '%s' should have 1+ lines.", io->filename);
+            io->updated = 0;
             return;
         }
 
@@ -341,255 +608,296 @@ void cgroup_read_blkio(struct blkio *io) {
             char *s = procfile_lineword(ff, i, 1);
             uint32_t hash = simple_hash(s);
 
-            if(hash == Read_hash && !strcmp(s, "Read"))
+            if(unlikely(hash == Read_hash && !strcmp(s, "Read"))) {
                 io->Read += strtoull(procfile_lineword(ff, i, 2), NULL, 10);
+                continue;
+            }
 
-            else if(hash == Write_hash && !strcmp(s, "Write"))
+            if(unlikely(hash == Write_hash && !strcmp(s, "Write"))) {
                 io->Write += strtoull(procfile_lineword(ff, i, 2), NULL, 10);
+                continue;
+            }
 
 /*
-            else if(hash == Sync_hash && !strcmp(s, "Sync"))
+            if(hash == Sync_hash && !strcmp(s, "Sync")) {
                 io->Sync += strtoull(procfile_lineword(ff, i, 2), NULL, 10);
+                continue;
+            }
 
-            else if(hash == Async_hash && !strcmp(s, "Async"))
+            if(hash == Async_hash && !strcmp(s, "Async")) {
                 io->Async += strtoull(procfile_lineword(ff, i, 2), NULL, 10);
+                continue;
+            }
 
-            else if(hash == Total_hash && !strcmp(s, "Total"))
+            if(hash == Total_hash && !strcmp(s, "Total")) {
                 io->Total += strtoull(procfile_lineword(ff, i, 2), NULL, 10);
+                continue;
+            }
 */
         }
 
         io->updated = 1;
-        // fprintf(stderr, "READ '%s': Read: %llu, Write: %llu, Sync: %llu, Async: %llu, Total: %llu\n", io->filename, io->Read, io->Write, io->Sync, io->Async, io->Total);
+
+        if(unlikely(io->enabled == CONFIG_ONDEMAND_ONDEMAND)) {
+            if(unlikely(io->Read || io->Write))
+                io->enabled = CONFIG_ONDEMAND_YES;
+            else
+                io->delay_counter = cgroup_recheck_zero_blkio_every_iterations;
+        }
     }
 }
 
-void cgroup_read_memory(struct memory *mem) {
+static inline void cgroup_read_memory(struct memory *mem) {
     static procfile *ff = NULL;
 
-    static uint32_t cache_hash = 0;
-    static uint32_t rss_hash = 0;
-    static uint32_t rss_huge_hash = 0;
-    static uint32_t mapped_file_hash = 0;
-    static uint32_t writeback_hash = 0;
-    static uint32_t dirty_hash = 0;
-    static uint32_t swap_hash = 0;
-    static uint32_t pgpgin_hash = 0;
-    static uint32_t pgpgout_hash = 0;
-    static uint32_t pgfault_hash = 0;
-    static uint32_t pgmajfault_hash = 0;
-/*
-    static uint32_t inactive_anon_hash = 0;
-    static uint32_t active_anon_hash = 0;
-    static uint32_t inactive_file_hash = 0;
-    static uint32_t active_file_hash = 0;
-    static uint32_t unevictable_hash = 0;
-    static uint32_t hierarchical_memory_limit_hash = 0;
-    static uint32_t total_cache_hash = 0;
-    static uint32_t total_rss_hash = 0;
-    static uint32_t total_rss_huge_hash = 0;
-    static uint32_t total_mapped_file_hash = 0;
-    static uint32_t total_writeback_hash = 0;
-    static uint32_t total_dirty_hash = 0;
-    static uint32_t total_swap_hash = 0;
-    static uint32_t total_pgpgin_hash = 0;
-    static uint32_t total_pgpgout_hash = 0;
-    static uint32_t total_pgfault_hash = 0;
-    static uint32_t total_pgmajfault_hash = 0;
-    static uint32_t total_inactive_anon_hash = 0;
-    static uint32_t total_active_anon_hash = 0;
-    static uint32_t total_inactive_file_hash = 0;
-    static uint32_t total_active_file_hash = 0;
-    static uint32_t total_unevictable_hash = 0;
-*/
-    if(unlikely(cache_hash == 0)) {
-        cache_hash = simple_hash("cache");
-        rss_hash = simple_hash("rss");
-        rss_huge_hash = simple_hash("rss_huge");
-        mapped_file_hash = simple_hash("mapped_file");
-        writeback_hash = simple_hash("writeback");
-        dirty_hash = simple_hash("dirty");
-        swap_hash = simple_hash("swap");
-        pgpgin_hash = simple_hash("pgpgin");
-        pgpgout_hash = simple_hash("pgpgout");
-        pgfault_hash = simple_hash("pgfault");
-        pgmajfault_hash = simple_hash("pgmajfault");
-/*
-        inactive_anon_hash = simple_hash("inactive_anon");
-        active_anon_hash = simple_hash("active_anon");
-        inactive_file_hash = simple_hash("inactive_file");
-        active_file_hash = simple_hash("active_file");
-        unevictable_hash = simple_hash("unevictable");
-        hierarchical_memory_limit_hash = simple_hash("hierarchical_memory_limit");
-        total_cache_hash = simple_hash("total_cache");
-        total_rss_hash = simple_hash("total_rss");
-        total_rss_huge_hash = simple_hash("total_rss_huge");
-        total_mapped_file_hash = simple_hash("total_mapped_file");
-        total_writeback_hash = simple_hash("total_writeback");
-        total_dirty_hash = simple_hash("total_dirty");
-        total_swap_hash = simple_hash("total_swap");
-        total_pgpgin_hash = simple_hash("total_pgpgin");
-        total_pgpgout_hash = simple_hash("total_pgpgout");
-        total_pgfault_hash = simple_hash("total_pgfault");
-        total_pgmajfault_hash = simple_hash("total_pgmajfault");
-        total_inactive_anon_hash = simple_hash("total_inactive_anon");
-        total_active_anon_hash = simple_hash("total_active_anon");
-        total_inactive_file_hash = simple_hash("total_inactive_file");
-        total_active_file_hash = simple_hash("total_active_file");
-        total_unevictable_hash = simple_hash("total_unevictable");
-*/
-    }
+    // read detailed ram usage
+    if(likely(mem->filename_detailed)) {
+        if(unlikely(mem->enabled_detailed == CONFIG_ONDEMAND_ONDEMAND && mem->delay_counter_detailed > 0)) {
+            mem->delay_counter_detailed--;
+            goto memory_next;
+        }
 
-    mem->updated = 0;
-    if(mem->filename) {
-        ff = procfile_reopen(ff, mem->filename, NULL, PROCFILE_FLAG_DEFAULT);
-        if(!ff) return;
+        ff = procfile_reopen(ff, mem->filename_detailed, NULL, PROCFILE_FLAG_DEFAULT);
+        if(unlikely(!ff)) {
+            mem->updated_detailed = 0;
+            goto memory_next;
+        }
 
         ff = procfile_readall(ff);
-        if(!ff) return;
+        if(unlikely(!ff)) {
+            mem->updated_detailed = 0;
+            goto memory_next;
+        }
 
         unsigned long i, lines = procfile_lines(ff);
 
-        if(lines < 1) {
-            error("File '%s' should have 1+ lines.", mem->filename);
-            return;
+        if(unlikely(lines < 1)) {
+            error("File '%s' should have 1+ lines.", mem->filename_detailed);
+            mem->updated_detailed = 0;
+            goto memory_next;
         }
 
         for(i = 0; i < lines ; i++) {
             char *s = procfile_lineword(ff, i, 0);
             uint32_t hash = simple_hash(s);
 
-            if(hash == cache_hash && !strcmp(s, "cache"))
+            if(unlikely(hash == cache_hash && !strcmp(s, "cache"))) {
                 mem->cache = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 
-            else if(hash == rss_hash && !strcmp(s, "rss"))
+            if(unlikely(hash == rss_hash && !strcmp(s, "rss"))) {
                 mem->rss = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 
-            else if(hash == rss_huge_hash && !strcmp(s, "rss_huge"))
+            if(unlikely(hash == rss_huge_hash && !strcmp(s, "rss_huge"))) {
                 mem->rss_huge = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 
-            else if(hash == mapped_file_hash && !strcmp(s, "mapped_file"))
+            if(unlikely(hash == mapped_file_hash && !strcmp(s, "mapped_file"))) {
                 mem->mapped_file = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 
-            else if(hash == writeback_hash && !strcmp(s, "writeback"))
+            if(unlikely(hash == writeback_hash && !strcmp(s, "writeback"))) {
                 mem->writeback = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 
-            else if(hash == dirty_hash && !strcmp(s, "dirty")) {
+            if(unlikely(hash == dirty_hash && !strcmp(s, "dirty"))) {
                 mem->dirty = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
-                mem->has_dirty_swap = 1;
+                mem->detailed_has_dirty = 1;
+                continue;
             }
 
-            else if(hash == swap_hash && !strcmp(s, "swap")) {
+            if(unlikely(hash == swap_hash && !strcmp(s, "swap"))) {
                 mem->swap = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
-                mem->has_dirty_swap = 1;
+                mem->detailed_has_swap = 1;
+                continue;
             }
 
-            else if(hash == pgpgin_hash && !strcmp(s, "pgpgin"))
+            if(unlikely(hash == pgpgin_hash && !strcmp(s, "pgpgin"))) {
                 mem->pgpgin = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 
-            else if(hash == pgpgout_hash && !strcmp(s, "pgpgout"))
+            if(unlikely(hash == pgpgout_hash && !strcmp(s, "pgpgout"))) {
                 mem->pgpgout = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 
-            else if(hash == pgfault_hash && !strcmp(s, "pgfault"))
+            if(unlikely(hash == pgfault_hash && !strcmp(s, "pgfault"))) {
                 mem->pgfault = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 
-            else if(hash == pgmajfault_hash && !strcmp(s, "pgmajfault"))
+            if(unlikely(hash == pgmajfault_hash && !strcmp(s, "pgmajfault"))) {
                 mem->pgmajfault = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 
 /*
-            else if(hash == inactive_anon_hash && !strcmp(s, "inactive_anon"))
+            if(unlikely(hash == inactive_anon_hash && !strcmp(s, "inactive_anon"))) {
                 mem->inactive_anon = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 
-            else if(hash == active_anon_hash && !strcmp(s, "active_anon"))
+            if(unlikely(hash == active_anon_hash && !strcmp(s, "active_anon"))) {
                 mem->active_anon = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 
-            else if(hash == inactive_file_hash && !strcmp(s, "inactive_file"))
+            if(unlikely(hash == inactive_file_hash && !strcmp(s, "inactive_file"))) {
                 mem->inactive_file = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 
-            else if(hash == active_file_hash && !strcmp(s, "active_file"))
+            if(unlikely(hash == active_file_hash && !strcmp(s, "active_file"))) {
                 mem->active_file = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 
-            else if(hash == unevictable_hash && !strcmp(s, "unevictable"))
+            if(unlikely(hash == unevictable_hash && !strcmp(s, "unevictable"))) {
                 mem->unevictable = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 
-            else if(hash == hierarchical_memory_limit_hash && !strcmp(s, "hierarchical_memory_limit"))
+            if(unlikely(hash == hierarchical_memory_limit_hash && !strcmp(s, "hierarchical_memory_limit"))) {
                 mem->hierarchical_memory_limit = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 
-            else if(hash == total_cache_hash && !strcmp(s, "total_cache"))
+            if(unlikely(hash == total_cache_hash && !strcmp(s, "total_cache"))) {
                 mem->total_cache = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 
-            else if(hash == total_rss_hash && !strcmp(s, "total_rss"))
+            if(unlikely(hash == total_rss_hash && !strcmp(s, "total_rss"))) {
                 mem->total_rss = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 
-            else if(hash == total_rss_huge_hash && !strcmp(s, "total_rss_huge"))
+            if(unlikely(hash == total_rss_huge_hash && !strcmp(s, "total_rss_huge"))) {
                 mem->total_rss_huge = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 
-            else if(hash == total_mapped_file_hash && !strcmp(s, "total_mapped_file"))
+            if(unlikely(hash == total_mapped_file_hash && !strcmp(s, "total_mapped_file"))) {
                 mem->total_mapped_file = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 
-            else if(hash == total_writeback_hash && !strcmp(s, "total_writeback"))
+            if(unlikely(hash == total_writeback_hash && !strcmp(s, "total_writeback"))) {
                 mem->total_writeback = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 
-            else if(hash == total_dirty_hash && !strcmp(s, "total_dirty"))
+            if(unlikely(hash == total_dirty_hash && !strcmp(s, "total_dirty"))) {
                 mem->total_dirty = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 
-            else if(hash == total_swap_hash && !strcmp(s, "total_swap"))
+            if(unlikely(hash == total_swap_hash && !strcmp(s, "total_swap"))) {
                 mem->total_swap = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 
-            else if(hash == total_pgpgin_hash && !strcmp(s, "total_pgpgin"))
+            if(unlikely(hash == total_pgpgin_hash && !strcmp(s, "total_pgpgin"))) {
                 mem->total_pgpgin = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 
-            else if(hash == total_pgpgout_hash && !strcmp(s, "total_pgpgout"))
+            if(unlikely(hash == total_pgpgout_hash && !strcmp(s, "total_pgpgout"))) {
                 mem->total_pgpgout = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 
-            else if(hash == total_pgfault_hash && !strcmp(s, "total_pgfault"))
+            if(unlikely(hash == total_pgfault_hash && !strcmp(s, "total_pgfault"))) {
                 mem->total_pgfault = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 
-            else if(hash == total_pgmajfault_hash && !strcmp(s, "total_pgmajfault"))
+            if(unlikely(hash == total_pgmajfault_hash && !strcmp(s, "total_pgmajfault"))) {
                 mem->total_pgmajfault = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 
-            else if(hash == total_inactive_anon_hash && !strcmp(s, "total_inactive_anon"))
+            if(unlikely(hash == total_inactive_anon_hash && !strcmp(s, "total_inactive_anon"))) {
                 mem->total_inactive_anon = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 
-            else if(hash == total_active_anon_hash && !strcmp(s, "total_active_anon"))
+            if(unlikely(hash == total_active_anon_hash && !strcmp(s, "total_active_anon"))) {
                 mem->total_active_anon = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 
-            else if(hash == total_inactive_file_hash && !strcmp(s, "total_inactive_file"))
+            if(unlikely(hash == total_inactive_file_hash && !strcmp(s, "total_inactive_file"))) {
                 mem->total_inactive_file = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 
-            else if(hash == total_active_file_hash && !strcmp(s, "total_active_file"))
+            if(unlikely(hash == total_active_file_hash && !strcmp(s, "total_active_file"))) {
                 mem->total_active_file = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 
-            else if(hash == total_unevictable_hash && !strcmp(s, "total_unevictable"))
+            if(unlikely(hash == total_unevictable_hash && !strcmp(s, "total_unevictable"))) {
                 mem->total_unevictable = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                continue;
+            }
 */
         }
 
         // fprintf(stderr, "READ: '%s', cache: %llu, rss: %llu, rss_huge: %llu, mapped_file: %llu, writeback: %llu, dirty: %llu, swap: %llu, pgpgin: %llu, pgpgout: %llu, pgfault: %llu, pgmajfault: %llu, inactive_anon: %llu, active_anon: %llu, inactive_file: %llu, active_file: %llu, unevictable: %llu, hierarchical_memory_limit: %llu, total_cache: %llu, total_rss: %llu, total_rss_huge: %llu, total_mapped_file: %llu, total_writeback: %llu, total_dirty: %llu, total_swap: %llu, total_pgpgin: %llu, total_pgpgout: %llu, total_pgfault: %llu, total_pgmajfault: %llu, total_inactive_anon: %llu, total_active_anon: %llu, total_inactive_file: %llu, total_active_file: %llu, total_unevictable: %llu\n", mem->filename, mem->cache, mem->rss, mem->rss_huge, mem->mapped_file, mem->writeback, mem->dirty, mem->swap, mem->pgpgin, mem->pgpgout, mem->pgfault, mem->pgmajfault, mem->inactive_anon, mem->active_anon, mem->inactive_file, mem->active_file, mem->unevictable, mem->hierarchical_memory_limit, mem->total_cache, mem->total_rss, mem->total_rss_huge, mem->total_mapped_file, mem->total_writeback, mem->total_dirty, mem->total_swap, mem->total_pgpgin, mem->total_pgpgout, mem->total_pgfault, mem->total_pgmajfault, mem->total_inactive_anon, mem->total_active_anon, mem->total_inactive_file, mem->total_active_file, mem->total_unevictable);
 
-        mem->updated = 1;
+        mem->updated_detailed = 1;
+
+        if(unlikely(mem->enabled_detailed == CONFIG_ONDEMAND_ONDEMAND)) {
+            if(mem->cache || mem->dirty || mem->rss || mem->rss_huge || mem->mapped_file || mem->writeback || mem->swap || mem->pgpgin || mem->pgpgout || mem->pgfault || mem->pgmajfault)
+                mem->enabled_detailed = CONFIG_ONDEMAND_YES;
+            else
+                mem->delay_counter_detailed = cgroup_recheck_zero_mem_detailed_every_iterations;
+        }
     }
 
-    mem->usage_in_bytes_updated = 0;
-    if(mem->filename_usage_in_bytes) {
-        if(likely(!read_single_number_file(mem->filename_usage_in_bytes, &mem->usage_in_bytes)))
-            mem->usage_in_bytes_updated = 1;
+memory_next:
+
+    // read usage_in_bytes
+    if(likely(mem->filename_usage_in_bytes)) {
+        mem->updated_usage_in_bytes = !read_single_number_file(mem->filename_usage_in_bytes, &mem->usage_in_bytes);
+        if(unlikely(mem->updated_usage_in_bytes && mem->enabled_usage_in_bytes == CONFIG_ONDEMAND_ONDEMAND && mem->usage_in_bytes))
+            mem->enabled_usage_in_bytes = CONFIG_ONDEMAND_YES;
     }
 
-    mem->msw_usage_in_bytes_updated = 0;
-    if(mem->filename_msw_usage_in_bytes) {
-        if(likely(!read_single_number_file(mem->filename_msw_usage_in_bytes, &mem->msw_usage_in_bytes)))
-            mem->msw_usage_in_bytes_updated = 1;
+    // read msw_usage_in_bytes
+    if(likely(mem->filename_msw_usage_in_bytes)) {
+        mem->updated_msw_usage_in_bytes = !read_single_number_file(mem->filename_msw_usage_in_bytes, &mem->msw_usage_in_bytes);
+        if(unlikely(mem->updated_msw_usage_in_bytes && mem->enabled_msw_usage_in_bytes == CONFIG_ONDEMAND_ONDEMAND && mem->msw_usage_in_bytes))
+            mem->enabled_msw_usage_in_bytes = CONFIG_ONDEMAND_YES;
     }
 
-    mem->failcnt_updated = 0;
-    if(mem->filename_failcnt) {
-        if(likely(!read_single_number_file(mem->filename_failcnt, &mem->failcnt)))
-            mem->failcnt_updated = 1;
+    // read failcnt
+    if(likely(mem->filename_failcnt)) {
+        if(unlikely(mem->enabled_failcnt == CONFIG_ONDEMAND_ONDEMAND && mem->delay_counter_failcnt > 0)) {
+            mem->updated_failcnt = 0;
+            mem->delay_counter_failcnt--;
+        }
+        else {
+            mem->updated_failcnt = !read_single_number_file(mem->filename_failcnt, &mem->failcnt);
+            if(unlikely(mem->updated_failcnt && mem->enabled_failcnt == CONFIG_ONDEMAND_ONDEMAND)) {
+                if(unlikely(!mem->failcnt))
+                    mem->delay_counter_failcnt = cgroup_recheck_zero_mem_failcnt_every_iterations;
+                else
+                    mem->enabled_failcnt = CONFIG_ONDEMAND_YES;
+            }
+        }
     }
 }
 
-void cgroup_read(struct cgroup *cg) {
+static inline void cgroup_read(struct cgroup *cg) {
     debug(D_CGROUP, "reading metrics for cgroups '%s'", cg->id);
 
     cgroup_read_cpuacct_stat(&cg->cpuacct_stat);
@@ -603,7 +911,7 @@ void cgroup_read(struct cgroup *cg) {
     cgroup_read_blkio(&cg->io_queued);
 }
 
-void read_all_cgroups(struct cgroup *root) {
+static inline void read_all_cgroups(struct cgroup *root) {
     debug(D_CGROUP, "reading metrics for all cgroups");
 
     struct cgroup *cg;
@@ -618,109 +926,81 @@ void read_all_cgroups(struct cgroup *root) {
 
 #define CGROUP_CHARTID_LINE_MAX 1024
 
-void cgroup_get_chart_id(struct cgroup *cg) {
-    debug(D_CGROUP, "getting the name of cgroup '%s'", cg->id);
+static inline char *cgroup_title_strdupz(const char *s) {
+    if(!s || !*s) s = "/";
+
+    if(*s == '/' && s[1] != '\0') s++;
+
+    char *r = strdupz(s);
+    netdata_fix_chart_name(r);
+
+    return r;
+}
+
+static inline char *cgroup_chart_id_strdupz(const char *s) {
+    if(!s || !*s) s = "/";
+
+    if(*s == '/' && s[1] != '\0') s++;
+
+    char *r = strdupz(s);
+    netdata_fix_chart_id(r);
+
+    return r;
+}
+
+static inline void cgroup_get_chart_name(struct cgroup *cg) {
+    debug(D_CGROUP, "looking for the name of cgroup '%s' with chart id '%s' and title '%s'", cg->id, cg->chart_id, cg->chart_title);
 
     pid_t cgroup_pid;
     char buffer[CGROUP_CHARTID_LINE_MAX + 1];
 
-    snprintfz(buffer, CGROUP_CHARTID_LINE_MAX, "exec %s '%s'",
-             config_get("plugin:cgroups", "script to get cgroup names", PLUGINS_DIR "/cgroup-name.sh"), cg->chart_id);
+    snprintfz(buffer, CGROUP_CHARTID_LINE_MAX, "exec %s '%s'", cgroups_rename_script, cg->chart_id);
 
     debug(D_CGROUP, "executing command '%s' for cgroup '%s'", buffer, cg->id);
     FILE *fp = mypopen(buffer, &cgroup_pid);
-    if(!fp) {
-        error("CGROUP: Cannot popen(\"%s\", \"r\").", buffer);
-        return;
-    }
-    debug(D_CGROUP, "reading from command '%s' for cgroup '%s'", buffer, cg->id);
-    char *s = fgets(buffer, CGROUP_CHARTID_LINE_MAX, fp);
-    debug(D_CGROUP, "closing command for cgroup '%s'", cg->id);
-    mypclose(fp, cgroup_pid);
-    debug(D_CGROUP, "closed command for cgroup '%s'", cg->id);
+    if(fp) {
+        // debug(D_CGROUP, "reading from command '%s' for cgroup '%s'", buffer, cg->id);
+        char *s = fgets(buffer, CGROUP_CHARTID_LINE_MAX, fp);
+        // debug(D_CGROUP, "closing command for cgroup '%s'", cg->id);
+        mypclose(fp, cgroup_pid);
+        // debug(D_CGROUP, "closed command for cgroup '%s'", cg->id);
 
-    if(s && *s && *s != '\n') {
-        debug(D_CGROUP, "cgroup '%s' should be renamed to '%s'", cg->id, s);
+        if(s && *s && *s != '\n') {
+            debug(D_CGROUP, "cgroup '%s' should be renamed to '%s'", cg->id, s);
 
-        trim(s);
+            trim(s);
 
-        freez(cg->chart_title);
-        cg->chart_title = strdupz(s);
-        netdata_fix_chart_name(cg->chart_title);
+            freez(cg->chart_title);
+            cg->chart_title = cgroup_title_strdupz(s);
 
-        freez(cg->chart_id);
-        cg->chart_id = strdupz(s);
-        netdata_fix_chart_id(cg->chart_id);
-        cg->hash_chart = simple_hash(cg->chart_id);
-
-        debug(D_CGROUP, "cgroup '%s' renamed to '%s' (title: '%s')", cg->id, cg->chart_id, cg->chart_title);
+            freez(cg->chart_id);
+            cg->chart_id = cgroup_chart_id_strdupz(s);
+            cg->hash_chart = simple_hash(cg->chart_id);
+        }
     }
-    else debug(D_CGROUP, "cgroup '%s' is not to be renamed (will be shown as '%s')", cg->id, cg->chart_id);
+    else
+        error("CGROUP: Cannot popen(\"%s\", \"r\").", buffer);
 }
 
-struct cgroup *cgroup_add(const char *id) {
-    debug(D_CGROUP, "adding cgroup '%s'", id);
+static inline struct cgroup *cgroup_add(const char *id) {
+    if(!id || !*id) id = "/";
+    debug(D_CGROUP, "adding to list, cgroup with id '%s'", id);
 
     if(cgroup_root_count >= cgroup_root_max) {
         info("Maximum number of cgroups reached (%d). Not adding cgroup '%s'", cgroup_root_count, id);
         return NULL;
     }
 
-    int def = cgroup_enable_new_cgroups_detected_at_runtime;
-    const char *chart_id = id;
-    if(!*chart_id) {
-        chart_id = "/";
-
-        // disable by default the root cgroup
-        def = 0;
-    }
-    else {
-        if(*chart_id == '/') chart_id++;
-
-        size_t len = strlen(chart_id);
-
-        // disable by default the parent cgroup
-        // for known cgroup managers
-        if(!strcmp(chart_id, "lxc") ||
-                !strcmp(chart_id, "docker") ||
-                !strcmp(chart_id, "libvirt") ||
-                !strcmp(chart_id, "qemu") ||
-                !strcmp(chart_id, "systemd") ||
-                !strcmp(chart_id, "system.slice") ||
-                !strcmp(chart_id, "machine.slice") ||
-                !strcmp(chart_id, "init.scope") ||
-                !strcmp(chart_id, "user") ||
-                !strcmp(chart_id, "system") ||
-                !strcmp(chart_id, "machine") ||
-                // starts with them
-                (len >  5 && !strncmp(chart_id, "user/", 5)) ||
-                (len > 11 && !strncmp(chart_id, "user.slice/", 11)) ||
-                // ends with them
-                (len >  5 && !strncmp(&chart_id[len -  5], ".user", 5)) ||
-                (len >  5 && !strncmp(&chart_id[len -  5], ".swap", 5)) ||
-                (len >  6 && !strncmp(&chart_id[len -  6], ".slice", 6)) ||
-                (len >  6 && !strncmp(&chart_id[len -  6], ".mount", 6)) ||
-                (len >  8 && !strncmp(&chart_id[len -  8], ".session", 8)) ||
-                (len >  8 && !strncmp(&chart_id[len -  8], ".service", 8)) ||
-                (len > 10 && !strncmp(&chart_id[len - 10], ".partition", 10)) ||
-                // starts and ends with them
-                (len > 7 && !strncmp(chart_id, "lxc/", 4) && !strncmp(&chart_id[len - 3], "/ns", 3)) // #1397
-                ) {
-            def = 0;
-        }
-    }
-    debug(D_CGROUP, "cgroup '%s' (chart_id '%s') is (by default) %s", id, chart_id, (def)?"enabled":"disabled");
-
+    int def = simple_pattern_matches(enabled_cgroup_patterns, id)?cgroup_enable_new_cgroups_detected_at_runtime:0;
     struct cgroup *cg = callocz(1, sizeof(struct cgroup));
 
     cg->id = strdupz(id);
     cg->hash = simple_hash(cg->id);
 
-    cg->chart_id = strdupz(chart_id);
-    netdata_fix_chart_id(cg->chart_id);
-    cg->hash_chart = simple_hash(cg->chart_id);
+    cg->chart_title = cgroup_title_strdupz(id);
 
-    cg->chart_title = strdupz(chart_id);
+    cg->chart_id = cgroup_chart_id_strdupz(id);
+    cg->hash_chart = simple_hash(cg->chart_id);
 
     if(!cgroup_root)
         cgroup_root = cg;
@@ -733,15 +1013,64 @@ struct cgroup *cgroup_add(const char *id) {
 
     cgroup_root_count++;
 
-    // fix the name by calling the external script
-    cgroup_get_chart_id(cg);
+    // fix the chart_id and title by calling the external script
+    if(simple_pattern_matches(enabled_cgroup_renames, cg->id)) {
+
+        cgroup_get_chart_name(cg);
+
+        debug(D_CGROUP, "cgroup '%s' renamed to '%s' (title: '%s')", cg->id, cg->chart_id, cg->chart_title);
+    }
+    else
+        debug(D_CGROUP, "cgroup '%s' will not be renamed - it matches the list of disabled cgroup renames (will be shown as '%s')", cg->id, cg->chart_id);
+
+    int user_configurable = 1;
+
+    // check if this cgroup should be a systemd service
+    if(cgroup_enable_systemd_services) {
+        if(simple_pattern_matches(systemd_services_cgroups, cg->id) ||
+                simple_pattern_matches(systemd_services_cgroups, cg->chart_id)) {
+            debug(D_CGROUP, "cgroup '%s' with chart id '%s' (title: '%s') matches systemd services cgroups", cg->id, cg->chart_id, cg->chart_title);
+
+            char buffer[CGROUP_CHARTID_LINE_MAX + 1];
+            cg->options |= CGROUP_OPTIONS_SYSTEM_SLICE_SERVICE;
+
+            strncpy(buffer, cg->id, CGROUP_CHARTID_LINE_MAX);
+            char *s = buffer;
+
+            //freez(cg->chart_id);
+            //cg->chart_id = cgroup_chart_id_strdupz(s);
+            //cg->hash_chart = simple_hash(cg->chart_id);
+
+            // skip to the last slash
+            size_t len = strlen(s);
+            while(len--) if(unlikely(s[len] == '/')) break;
+            if(len) s = &s[len + 1];
+
+            // remove extension
+            len = strlen(s);
+            while(len--) if(unlikely(s[len] == '.')) break;
+            if(len) s[len] = '\0';
 
-    debug(D_CGROUP, "adding cgroup '%s' with chart id '%s'", id, chart_id);
+            freez(cg->chart_title);
+            cg->chart_title = cgroup_title_strdupz(s);
 
-    char option[FILENAME_MAX + 1];
-    snprintfz(option, FILENAME_MAX, "enable cgroup %s", cg->chart_title);
-    cg->enabled = config_get_boolean("plugin:cgroups", option, def);
+            cg->enabled = 1;
+            user_configurable = 0;
 
+            debug(D_CGROUP, "cgroup '%s' renamed to '%s' (title: '%s')", cg->id, cg->chart_id, cg->chart_title);
+        }
+        else
+            debug(D_CGROUP, "cgroup '%s' with chart id '%s' (title: '%s') does not match systemd services groups", cg->id, cg->chart_id, cg->chart_title);
+    }
+
+    if(user_configurable) {
+        // allow the user to enable/disable this individualy
+        char option[FILENAME_MAX + 1];
+        snprintfz(option, FILENAME_MAX, "enable cgroup %s", cg->chart_title);
+        cg->enabled = (char) config_get_boolean("plugin:cgroups", option, def);
+    }
+
+    // detect duplicate cgroups
     if(cg->enabled) {
         struct cgroup *t;
         for (t = cgroup_root; t; t = t->next) {
@@ -768,36 +1097,44 @@ struct cgroup *cgroup_add(const char *id) {
         }
     }
 
-    debug(D_CGROUP, "Added cgroup '%s' with chart id '%s' and title '%s' as %s (default was %s)", cg->id, cg->chart_id, cg->chart_title, (cg->enabled)?"enabled":"disabled", (def)?"enabled":"disabled");
+    debug(D_CGROUP, "ADDED CGROUP: '%s' with chart id '%s' and title '%s' as %s (default was %s)", cg->id, cg->chart_id, cg->chart_title, (cg->enabled)?"enabled":"disabled", (def)?"enabled":"disabled");
 
     return cg;
 }
 
-void cgroup_free(struct cgroup *cg) {
+static inline void cgroup_free(struct cgroup *cg) {
     debug(D_CGROUP, "Removing cgroup '%s' with chart id '%s' (was %s and %s)", cg->id, cg->chart_id, (cg->enabled)?"enabled":"disabled", (cg->available)?"available":"not available");
 
     freez(cg->cpuacct_usage.cpu_percpu);
 
     freez(cg->cpuacct_stat.filename);
     freez(cg->cpuacct_usage.filename);
-    freez(cg->memory.filename);
+
+    freez(cg->memory.filename_detailed);
+    freez(cg->memory.filename_failcnt);
+    freez(cg->memory.filename_usage_in_bytes);
+    freez(cg->memory.filename_msw_usage_in_bytes);
+
     freez(cg->io_service_bytes.filename);
     freez(cg->io_serviced.filename);
+
     freez(cg->throttle_io_service_bytes.filename);
     freez(cg->throttle_io_serviced.filename);
+
     freez(cg->io_merged.filename);
     freez(cg->io_queued.filename);
 
     freez(cg->id);
     freez(cg->chart_id);
     freez(cg->chart_title);
+
     freez(cg);
 
     cgroup_root_count--;
 }
 
 // find if a given cgroup exists
-struct cgroup *cgroup_find(const char *id) {
+static inline struct cgroup *cgroup_find(const char *id) {
     debug(D_CGROUP, "searching for cgroup '%s'", id);
 
     uint32_t hash = simple_hash(id);
@@ -808,7 +1145,7 @@ struct cgroup *cgroup_find(const char *id) {
             break;
     }
 
-    debug(D_CGROUP, "cgroup_find('%s') %s", id, (cg)?"found":"not found");
+    debug(D_CGROUP, "cgroup '%s' %s in memory", id, (cg)?"found":"not found");
     return cg;
 }
 
@@ -816,7 +1153,7 @@ struct cgroup *cgroup_find(const char *id) {
 // detect running cgroups
 
 // callback for find_file_in_subdirs()
-void found_subdir_in_dir(const char *dir) {
+static inline void found_subdir_in_dir(const char *dir) {
     debug(D_CGROUP, "examining cgroup dir '%s'", dir);
 
     struct cgroup *cg = cgroup_find(dir);
@@ -834,21 +1171,24 @@ void found_subdir_in_dir(const char *dir) {
                 return;
             }
         }
-        debug(D_CGROUP, "will add dir '%s' as cgroup", dir);
+        // debug(D_CGROUP, "will add dir '%s' as cgroup", dir);
         cg = cgroup_add(dir);
     }
 
     if(cg) cg->available = 1;
 }
 
-int find_dir_in_subdirs(const char *base, const char *this, void (*callback)(const char *)) {
-    debug(D_CGROUP, "searching for directories in '%s'", base);
+static inline int find_dir_in_subdirs(const char *base, const char *this, void (*callback)(const char *)) {
+    if(!this) this = base;
+    debug(D_CGROUP, "searching for directories in '%s' (base '%s')", this?this:"", base);
+
+    size_t dirlen = strlen(this), baselen = strlen(base);
 
     int ret = -1;
     int enabled = -1;
-    if(!this) this = base;
-    size_t dirlen = strlen(this), baselen = strlen(base);
+
     const char *relative_path = &this[baselen];
+    if(!*relative_path) relative_path = "/";
 
     DIR *dir = opendir(this);
     if(!dir) {
@@ -868,20 +1208,13 @@ int find_dir_in_subdirs(const char *base, const char *this, void (*callback)(con
                 ))
             continue;
 
-        debug(D_CGROUP, "examining '%s/%s'", this, de->d_name);
-
         if(de->d_type == DT_DIR) {
             if(enabled == -1) {
                 const char *r = relative_path;
                 if(*r == '\0') r = "/";
-                else if (*r == '/') r++;
 
                 // do not decent in directories we are not interested
-                // https://github.com/firehol/netdata/issues/345
-                int def = 1;
-                size_t len = strlen(r);
-                if(len >  5 && !strncmp(&r[len -  5], "-qemu", 5))
-                    def = 0;
+                int def = simple_pattern_matches(enabled_cgroup_paths, r);
 
                 // we check for this option here
                 // so that the config will not have settings
@@ -908,7 +1241,7 @@ int find_dir_in_subdirs(const char *base, const char *this, void (*callback)(con
     return ret;
 }
 
-void mark_all_cgroups_as_not_available() {
+static inline void mark_all_cgroups_as_not_available() {
     debug(D_CGROUP, "marking all cgroups as not available");
 
     struct cgroup *cg;
@@ -919,7 +1252,7 @@ void mark_all_cgroups_as_not_available() {
     }
 }
 
-void cleanup_all_cgroups() {
+static inline void cleanup_all_cgroups() {
     struct cgroup *cg = cgroup_root, *last = NULL;
 
     for(; cg ;) {
@@ -956,36 +1289,45 @@ void cleanup_all_cgroups() {
     }
 }
 
-void find_all_cgroups() {
+static inline void find_all_cgroups() {
     debug(D_CGROUP, "searching for cgroups");
 
     mark_all_cgroups_as_not_available();
 
     if(cgroup_enable_cpuacct_stat || cgroup_enable_cpuacct_usage) {
-        if (find_dir_in_subdirs(cgroup_cpuacct_base, NULL, found_subdir_in_dir) == -1) {
-            cgroup_enable_cpuacct_stat = cgroup_enable_cpuacct_usage = 0;
-            error("disabled cgroup cpu statistics.");
+        if(find_dir_in_subdirs(cgroup_cpuacct_base, NULL, found_subdir_in_dir) == -1) {
+            cgroup_enable_cpuacct_stat =
+            cgroup_enable_cpuacct_usage = CONFIG_ONDEMAND_NO;
+            error("disabled CGROUP cpu statistics.");
         }
     }
 
-    if(cgroup_enable_blkio) {
-        if (find_dir_in_subdirs(cgroup_blkio_base, NULL, found_subdir_in_dir) == -1) {
-            cgroup_enable_blkio = 0;
-            error("disabled cgroup blkio statistics.");
+    if(cgroup_enable_blkio_io || cgroup_enable_blkio_ops || cgroup_enable_blkio_throttle_io || cgroup_enable_blkio_throttle_ops || cgroup_enable_blkio_merged_ops || cgroup_enable_blkio_queued_ops) {
+        if(find_dir_in_subdirs(cgroup_blkio_base, NULL, found_subdir_in_dir) == -1) {
+            cgroup_enable_blkio_io =
+            cgroup_enable_blkio_ops =
+            cgroup_enable_blkio_throttle_io =
+            cgroup_enable_blkio_throttle_ops =
+            cgroup_enable_blkio_merged_ops =
+            cgroup_enable_blkio_queued_ops = CONFIG_ONDEMAND_NO;
+            error("disabled CGROUP blkio statistics.");
         }
     }
 
-    if(cgroup_enable_memory) {
+    if(cgroup_enable_memory || cgroup_enable_detailed_memory || cgroup_enable_swap || cgroup_enable_memory_failcnt) {
         if(find_dir_in_subdirs(cgroup_memory_base, NULL, found_subdir_in_dir) == -1) {
-            cgroup_enable_memory = 0;
-            error("disabled cgroup memory statistics.");
+            cgroup_enable_memory =
+            cgroup_enable_detailed_memory =
+            cgroup_enable_swap =
+            cgroup_enable_memory_failcnt = CONFIG_ONDEMAND_NO;
+            error("disabled CGROUP memory statistics.");
         }
     }
 
-    if(cgroup_enable_devices) {
+    if(cgroup_search_in_devices) {
         if(find_dir_in_subdirs(cgroup_devices_base, NULL, found_subdir_in_dir) == -1) {
-            cgroup_enable_devices = 0;
-            error("disabled cgroup devices statistics.");
+            cgroup_search_in_devices = 0;
+            error("disabled CGROUP devices statistics.");
         }
     }
 
@@ -1007,110 +1349,134 @@ void find_all_cgroups() {
         char filename[FILENAME_MAX + 1];
         if(unlikely(cgroup_enable_cpuacct_stat && !cg->cpuacct_stat.filename)) {
             snprintfz(filename, FILENAME_MAX, "%s%s/cpuacct.stat", cgroup_cpuacct_base, cg->id);
-            if(stat(filename, &buf) != -1) {
+            if(likely(stat(filename, &buf) != -1)) {
                 cg->cpuacct_stat.filename = strdupz(filename);
+                cg->cpuacct_stat.enabled = cgroup_enable_cpuacct_stat;
                 debug(D_CGROUP, "cpuacct.stat filename for cgroup '%s': '%s'", cg->id, cg->cpuacct_stat.filename);
             }
-            else debug(D_CGROUP, "cpuacct.stat file for cgroup '%s': '%s' does not exist.", cg->id, filename);
+            else
+                debug(D_CGROUP, "cpuacct.stat file for cgroup '%s': '%s' does not exist.", cg->id, filename);
         }
 
-        if(unlikely(cgroup_enable_cpuacct_usage && !cg->cpuacct_usage.filename)) {
+        if(unlikely(cgroup_enable_cpuacct_usage && !cg->cpuacct_usage.filename && !(cg->options & CGROUP_OPTIONS_SYSTEM_SLICE_SERVICE))) {
             snprintfz(filename, FILENAME_MAX, "%s%s/cpuacct.usage_percpu", cgroup_cpuacct_base, cg->id);
-            if(stat(filename, &buf) != -1) {
+            if(likely(stat(filename, &buf) != -1)) {
                 cg->cpuacct_usage.filename = strdupz(filename);
+                cg->cpuacct_usage.enabled = cgroup_enable_cpuacct_usage;
                 debug(D_CGROUP, "cpuacct.usage_percpu filename for cgroup '%s': '%s'", cg->id, cg->cpuacct_usage.filename);
             }
-            else debug(D_CGROUP, "cpuacct.usage_percpu file for cgroup '%s': '%s' does not exist.", cg->id, filename);
+            else
+                debug(D_CGROUP, "cpuacct.usage_percpu file for cgroup '%s': '%s' does not exist.", cg->id, filename);
         }
 
-        if(unlikely(cgroup_enable_memory)) {
-            if(unlikely(!cg->memory.filename)) {
-                snprintfz(filename, FILENAME_MAX, "%s%s/memory.stat", cgroup_memory_base, cg->id);
-                if(stat(filename, &buf) != -1) {
-                    cg->memory.filename = strdupz(filename);
-                    debug(D_CGROUP, "memory.stat filename for cgroup '%s': '%s'", cg->id, cg->memory.filename);
-                }
-                else
-                    debug(D_CGROUP, "memory.stat file for cgroup '%s': '%s' does not exist.", cg->id, filename);
+        if(unlikely((cgroup_enable_detailed_memory || cgroup_used_memory_without_cache) && !cg->memory.filename_detailed && (cgroup_used_memory_without_cache || cgroup_enable_systemd_services_detailed_memory || !(cg->options & CGROUP_OPTIONS_SYSTEM_SLICE_SERVICE)))) {
+            snprintfz(filename, FILENAME_MAX, "%s%s/memory.stat", cgroup_memory_base, cg->id);
+            if(likely(stat(filename, &buf) != -1)) {
+                cg->memory.filename_detailed = strdupz(filename);
+                cg->memory.enabled_detailed = (cgroup_enable_detailed_memory == CONFIG_ONDEMAND_YES)?CONFIG_ONDEMAND_YES:CONFIG_ONDEMAND_ONDEMAND;
+                debug(D_CGROUP, "memory.stat filename for cgroup '%s': '%s'", cg->id, cg->memory.filename_detailed);
             }
-            if(unlikely(!cg->memory.filename_usage_in_bytes)) {
-                snprintfz(filename, FILENAME_MAX, "%s%s/memory.usage_in_bytes", cgroup_memory_base, cg->id);
-                if(stat(filename, &buf) != -1) {
-                    cg->memory.filename_usage_in_bytes = strdupz(filename);
-                    debug(D_CGROUP, "memory.usage_in_bytes filename for cgroup '%s': '%s'", cg->id, cg->memory.filename_usage_in_bytes);
-                }
-                else
-                    debug(D_CGROUP, "memory.usage_in_bytes file for cgroup '%s': '%s' does not exist.", cg->id, filename);
+            else
+                debug(D_CGROUP, "memory.stat file for cgroup '%s': '%s' does not exist.", cg->id, filename);
+        }
+
+        if(unlikely(cgroup_enable_memory && !cg->memory.filename_usage_in_bytes)) {
+            snprintfz(filename, FILENAME_MAX, "%s%s/memory.usage_in_bytes", cgroup_memory_base, cg->id);
+            if(likely(stat(filename, &buf) != -1)) {
+                cg->memory.filename_usage_in_bytes = strdupz(filename);
+                cg->memory.enabled_usage_in_bytes = cgroup_enable_memory;
+                debug(D_CGROUP, "memory.usage_in_bytes filename for cgroup '%s': '%s'", cg->id, cg->memory.filename_usage_in_bytes);
             }
-            if(unlikely(!cg->memory.filename_msw_usage_in_bytes)) {
-                snprintfz(filename, FILENAME_MAX, "%s%s/memory.msw_usage_in_bytes", cgroup_memory_base, cg->id);
-                if(stat(filename, &buf) != -1) {
-                    cg->memory.filename_msw_usage_in_bytes = strdupz(filename);
-                    debug(D_CGROUP, "memory.msw_usage_in_bytes filename for cgroup '%s': '%s'", cg->id, cg->memory.filename_msw_usage_in_bytes);
-                }
-                else
-                    debug(D_CGROUP, "memory.msw_usage_in_bytes file for cgroup '%s': '%s' does not exist.", cg->id, filename);
+            else
+                debug(D_CGROUP, "memory.usage_in_bytes file for cgroup '%s': '%s' does not exist.", cg->id, filename);
+        }
+
+        if(unlikely(cgroup_enable_swap && !cg->memory.filename_msw_usage_in_bytes)) {
+            snprintfz(filename, FILENAME_MAX, "%s%s/memory.msw_usage_in_bytes", cgroup_memory_base, cg->id);
+            if(likely(stat(filename, &buf) != -1)) {
+                cg->memory.filename_msw_usage_in_bytes = strdupz(filename);
+                cg->memory.enabled_msw_usage_in_bytes = cgroup_enable_swap;
+                debug(D_CGROUP, "memory.msw_usage_in_bytes filename for cgroup '%s': '%s'", cg->id, cg->memory.filename_msw_usage_in_bytes);
             }
-            if(unlikely(!cg->memory.filename_failcnt)) {
-                snprintfz(filename, FILENAME_MAX, "%s%s/memory.failcnt", cgroup_memory_base, cg->id);
-                if(stat(filename, &buf) != -1) {
-                    cg->memory.filename_failcnt = strdupz(filename);
-                    debug(D_CGROUP, "memory.failcnt filename for cgroup '%s': '%s'", cg->id, cg->memory.filename_failcnt);
-                }
-                else
-                    debug(D_CGROUP, "memory.failcnt file for cgroup '%s': '%s' does not exist.", cg->id, filename);
+            else
+                debug(D_CGROUP, "memory.msw_usage_in_bytes file for cgroup '%s': '%s' does not exist.", cg->id, filename);
+        }
+
+        if(unlikely(cgroup_enable_memory_failcnt && !cg->memory.filename_failcnt)) {
+            snprintfz(filename, FILENAME_MAX, "%s%s/memory.failcnt", cgroup_memory_base, cg->id);
+            if(likely(stat(filename, &buf) != -1)) {
+                cg->memory.filename_failcnt = strdupz(filename);
+                cg->memory.enabled_failcnt = cgroup_enable_memory_failcnt;
+                debug(D_CGROUP, "memory.failcnt filename for cgroup '%s': '%s'", cg->id, cg->memory.filename_failcnt);
             }
+            else
+                debug(D_CGROUP, "memory.failcnt file for cgroup '%s': '%s' does not exist.", cg->id, filename);
         }
 
-        if(unlikely(cgroup_enable_blkio)) {
-            if(unlikely(!cg->io_service_bytes.filename)) {
-                snprintfz(filename, FILENAME_MAX, "%s%s/blkio.io_service_bytes", cgroup_blkio_base, cg->id);
-                if(stat(filename, &buf) != -1) {
-                    cg->io_service_bytes.filename = strdupz(filename);
-                    debug(D_CGROUP, "io_service_bytes filename for cgroup '%s': '%s'", cg->id, cg->io_service_bytes.filename);
-                }
-                else debug(D_CGROUP, "io_service_bytes file for cgroup '%s': '%s' does not exist.", cg->id, filename);
+        if(unlikely(cgroup_enable_blkio_io && !cg->io_service_bytes.filename)) {
+            snprintfz(filename, FILENAME_MAX, "%s%s/blkio.io_service_bytes", cgroup_blkio_base, cg->id);
+            if(likely(stat(filename, &buf) != -1)) {
+                cg->io_service_bytes.filename = strdupz(filename);
+                cg->io_service_bytes.enabled = cgroup_enable_blkio_io;
+                debug(D_CGROUP, "io_service_bytes filename for cgroup '%s': '%s'", cg->id, cg->io_service_bytes.filename);
             }
-            if(unlikely(!cg->io_serviced.filename)) {
-                snprintfz(filename, FILENAME_MAX, "%s%s/blkio.io_serviced", cgroup_blkio_base, cg->id);
-                if(stat(filename, &buf) != -1) {
-                    cg->io_serviced.filename = strdupz(filename);
-                    debug(D_CGROUP, "io_serviced filename for cgroup '%s': '%s'", cg->id, cg->io_serviced.filename);
-                }
-                else debug(D_CGROUP, "io_serviced file for cgroup '%s': '%s' does not exist.", cg->id, filename);
+            else
+                debug(D_CGROUP, "io_service_bytes file for cgroup '%s': '%s' does not exist.", cg->id, filename);
+        }
+
+        if(unlikely(cgroup_enable_blkio_ops && !cg->io_serviced.filename)) {
+            snprintfz(filename, FILENAME_MAX, "%s%s/blkio.io_serviced", cgroup_blkio_base, cg->id);
+            if(likely(stat(filename, &buf) != -1)) {
+                cg->io_serviced.filename = strdupz(filename);
+                cg->io_serviced.enabled = cgroup_enable_blkio_ops;
+                debug(D_CGROUP, "io_serviced filename for cgroup '%s': '%s'", cg->id, cg->io_serviced.filename);
             }
-            if(unlikely(!cg->throttle_io_service_bytes.filename)) {
-                snprintfz(filename, FILENAME_MAX, "%s%s/blkio.throttle.io_service_bytes", cgroup_blkio_base, cg->id);
-                if(stat(filename, &buf) != -1) {
-                    cg->throttle_io_service_bytes.filename = strdupz(filename);
-                    debug(D_CGROUP, "throttle_io_service_bytes filename for cgroup '%s': '%s'", cg->id, cg->throttle_io_service_bytes.filename);
-                }
-                else debug(D_CGROUP, "throttle_io_service_bytes file for cgroup '%s': '%s' does not exist.", cg->id, filename);
+            else
+                debug(D_CGROUP, "io_serviced file for cgroup '%s': '%s' does not exist.", cg->id, filename);
+        }
+
+        if(unlikely(cgroup_enable_blkio_throttle_io && !cg->throttle_io_service_bytes.filename)) {
+            snprintfz(filename, FILENAME_MAX, "%s%s/blkio.throttle.io_service_bytes", cgroup_blkio_base, cg->id);
+            if(likely(stat(filename, &buf) != -1)) {
+                cg->throttle_io_service_bytes.filename = strdupz(filename);
+                cg->throttle_io_service_bytes.enabled = cgroup_enable_blkio_throttle_io;
+                debug(D_CGROUP, "throttle_io_service_bytes filename for cgroup '%s': '%s'", cg->id, cg->throttle_io_service_bytes.filename);
             }
-            if(unlikely(!cg->throttle_io_serviced.filename)) {
-                snprintfz(filename, FILENAME_MAX, "%s%s/blkio.throttle.io_serviced", cgroup_blkio_base, cg->id);
-                if(stat(filename, &buf) != -1) {
-                    cg->throttle_io_serviced.filename = strdupz(filename);
-                    debug(D_CGROUP, "throttle_io_serviced filename for cgroup '%s': '%s'", cg->id, cg->throttle_io_serviced.filename);
-                }
-                else debug(D_CGROUP, "throttle_io_serviced file for cgroup '%s': '%s' does not exist.", cg->id, filename);
+            else
+                debug(D_CGROUP, "throttle_io_service_bytes file for cgroup '%s': '%s' does not exist.", cg->id, filename);
+        }
+
+        if(unlikely(cgroup_enable_blkio_throttle_ops && !cg->throttle_io_serviced.filename)) {
+            snprintfz(filename, FILENAME_MAX, "%s%s/blkio.throttle.io_serviced", cgroup_blkio_base, cg->id);
+            if(likely(stat(filename, &buf) != -1)) {
+                cg->throttle_io_serviced.filename = strdupz(filename);
+                cg->throttle_io_serviced.enabled = cgroup_enable_blkio_throttle_ops;
+                debug(D_CGROUP, "throttle_io_serviced filename for cgroup '%s': '%s'", cg->id, cg->throttle_io_serviced.filename);
             }
-            if(unlikely(!cg->io_merged.filename)) {
-                snprintfz(filename, FILENAME_MAX, "%s%s/blkio.io_merged", cgroup_blkio_base, cg->id);
-                if(stat(filename, &buf) != -1) {
-                    cg->io_merged.filename = strdupz(filename);
-                    debug(D_CGROUP, "io_merged filename for cgroup '%s': '%s'", cg->id, cg->io_merged.filename);
-                }
-                else debug(D_CGROUP, "io_merged file for cgroup '%s': '%s' does not exist.", cg->id, filename);
+            else
+                debug(D_CGROUP, "throttle_io_serviced file for cgroup '%s': '%s' does not exist.", cg->id, filename);
+        }
+
+        if(unlikely(cgroup_enable_blkio_merged_ops && !cg->io_merged.filename)) {
+            snprintfz(filename, FILENAME_MAX, "%s%s/blkio.io_merged", cgroup_blkio_base, cg->id);
+            if(likely(stat(filename, &buf) != -1)) {
+                cg->io_merged.filename = strdupz(filename);
+                cg->io_merged.enabled = cgroup_enable_blkio_merged_ops;
+                debug(D_CGROUP, "io_merged filename for cgroup '%s': '%s'", cg->id, cg->io_merged.filename);
             }
-            if(unlikely(!cg->io_queued.filename)) {
-                snprintfz(filename, FILENAME_MAX, "%s%s/blkio.io_queued", cgroup_blkio_base, cg->id);
-                if(stat(filename, &buf) != -1) {
-                    cg->io_queued.filename = strdupz(filename);
-                    debug(D_CGROUP, "io_queued filename for cgroup '%s': '%s'", cg->id, cg->io_queued.filename);
-                }
-                else debug(D_CGROUP, "io_queued file for cgroup '%s': '%s' does not exist.", cg->id, filename);
+            else
+                debug(D_CGROUP, "io_merged file for cgroup '%s': '%s' does not exist.", cg->id, filename);
+        }
+
+        if(unlikely(cgroup_enable_blkio_queued_ops && !cg->io_queued.filename)) {
+            snprintfz(filename, FILENAME_MAX, "%s%s/blkio.io_queued", cgroup_blkio_base, cg->id);
+            if(likely(stat(filename, &buf) != -1)) {
+                cg->io_queued.filename = strdupz(filename);
+                cg->io_queued.enabled = cgroup_enable_blkio_queued_ops;
+                debug(D_CGROUP, "io_queued filename for cgroup '%s': '%s'", cg->id, cg->io_queued.filename);
             }
+            else
+                debug(D_CGROUP, "io_queued file for cgroup '%s': '%s' does not exist.", cg->id, filename);
         }
     }
 
@@ -1123,313 +1489,805 @@ void find_all_cgroups() {
 
 #define CHART_TITLE_MAX 300
 
+void update_services_charts(int update_every,
+        int do_cpu,
+        int do_mem_usage,
+        int do_mem_detailed,
+        int do_mem_failcnt,
+        int do_swap_usage,
+        int do_io,
+        int do_io_ops,
+        int do_throttle_io,
+        int do_throttle_ops,
+        int do_queued_ops,
+        int do_merged_ops
+) {
+    static RRDSET
+        *st_cpu = NULL,
+        *st_mem_usage = NULL,
+        *st_mem_failcnt = NULL,
+        *st_swap_usage = NULL,
+
+        *st_mem_detailed_cache = NULL,
+        *st_mem_detailed_rss = NULL,
+        *st_mem_detailed_mapped = NULL,
+        *st_mem_detailed_writeback = NULL,
+        *st_mem_detailed_pgfault = NULL,
+        *st_mem_detailed_pgmajfault = NULL,
+        *st_mem_detailed_pgpgin = NULL,
+        *st_mem_detailed_pgpgout = NULL,
+
+        *st_io_read = NULL,
+        *st_io_serviced_read = NULL,
+        *st_throttle_io_read = NULL,
+        *st_throttle_ops_read = NULL,
+        *st_queued_ops_read = NULL,
+        *st_merged_ops_read = NULL,
+
+        *st_io_write = NULL,
+        *st_io_serviced_write = NULL,
+        *st_throttle_io_write = NULL,
+        *st_throttle_ops_write = NULL,
+        *st_queued_ops_write = NULL,
+        *st_merged_ops_write = NULL;
+
+    // create the charts
+
+    if(likely(do_cpu)) {
+        if(unlikely(!st_cpu)) {
+            char title[CHART_TITLE_MAX + 1];
+
+            st_cpu = rrdset_find_bytype("services", "cpu");
+            if(likely(!st_cpu)) {
+                snprintfz(title, CHART_TITLE_MAX, "Systemd Services CPU utilization (%d%% = %d core%s)", (processors * 100), processors, (processors > 1) ? "s" : "");
+                st_cpu = rrdset_create("services", "cpu", NULL, "cpu", "services.cpu", title, "%", CHART_PRIORITY_SYSTEMD_SERVICES, update_every, RRDSET_TYPE_STACKED);
+            }
+        }
+        else
+            rrdset_next(st_cpu);
+    }
+
+    if(likely(do_mem_usage)) {
+        if(unlikely(!st_mem_usage)) {
+            st_mem_usage = rrdset_find_bytype("services", "mem_usage");
+            if(likely(!st_mem_usage))
+                st_mem_usage = rrdset_create("services", "mem_usage", NULL, "mem", "services.mem_usage", (cgroup_used_memory_without_cache)?"Systemd Services Used Memory without Cache":"Systemd Services Used Memory", "MB", CHART_PRIORITY_SYSTEMD_SERVICES + 10, update_every, RRDSET_TYPE_STACKED);
+        }
+        else
+            rrdset_next(st_mem_usage);
+    }
+
+    if(likely(do_mem_detailed)) {
+        if(unlikely(!st_mem_detailed_rss)) {
+            st_mem_detailed_rss = rrdset_find_bytype("services", "mem_rss");
+            if(likely(!st_mem_detailed_rss))
+                st_mem_detailed_rss = rrdset_create("services", "mem_rss", NULL, "mem", "services.mem_rss", "Systemd Services RSS Memory", "MB", CHART_PRIORITY_SYSTEMD_SERVICES + 20, update_every, RRDSET_TYPE_STACKED);
+        }
+        else
+            rrdset_next(st_mem_detailed_rss);
+
+        if(unlikely(!st_mem_detailed_mapped)) {
+            st_mem_detailed_mapped = rrdset_find_bytype("services", "mem_mapped");
+            if(likely(!st_mem_detailed_mapped))
+                st_mem_detailed_mapped = rrdset_create("services", "mem_mapped", NULL, "mem", "services.mem_mapped", "Systemd Services Mapped Memory", "MB", CHART_PRIORITY_SYSTEMD_SERVICES + 30, update_every, RRDSET_TYPE_STACKED);
+        }
+        else
+            rrdset_next(st_mem_detailed_mapped);
+
+        if(unlikely(!st_mem_detailed_cache)) {
+            st_mem_detailed_cache = rrdset_find_bytype("services", "mem_cache");
+            if(likely(!st_mem_detailed_cache))
+                st_mem_detailed_cache = rrdset_create("services", "mem_cache", NULL, "mem", "services.mem_cache", "Systemd Services Cache Memory", "MB", CHART_PRIORITY_SYSTEMD_SERVICES + 40, update_every, RRDSET_TYPE_STACKED);
+        }
+        else
+            rrdset_next(st_mem_detailed_cache);
+
+        if(unlikely(!st_mem_detailed_writeback)) {
+            st_mem_detailed_writeback = rrdset_find_bytype("services", "mem_writeback");
+            if(likely(!st_mem_detailed_writeback))
+                st_mem_detailed_writeback = rrdset_create("services", "mem_writeback", NULL, "mem", "services.mem_writeback", "Systemd Services Writeback Memory", "MB", CHART_PRIORITY_SYSTEMD_SERVICES + 50, update_every, RRDSET_TYPE_STACKED);
+        }
+        else
+            rrdset_next(st_mem_detailed_writeback);
+
+        if(unlikely(!st_mem_detailed_pgfault)) {
+            st_mem_detailed_pgfault = rrdset_find_bytype("services", "mem_pgfault");
+            if(likely(!st_mem_detailed_pgfault))
+                st_mem_detailed_pgfault = rrdset_create("services", "mem_pgfault", NULL, "mem", "services.mem_pgfault", "Systemd Services Memory Minor Page Faults", "MB/s", CHART_PRIORITY_SYSTEMD_SERVICES + 60, update_every, RRDSET_TYPE_STACKED);
+        }
+        else
+            rrdset_next(st_mem_detailed_pgfault);
+
+        if(unlikely(!st_mem_detailed_pgmajfault)) {
+            st_mem_detailed_pgmajfault = rrdset_find_bytype("services", "mem_pgmajfault");
+            if(likely(!st_mem_detailed_pgmajfault))
+                st_mem_detailed_pgmajfault = rrdset_create("services", "mem_pgmajfault", NULL, "mem", "services.mem_pgmajfault", "Systemd Services Memory Major Page Faults", "MB/s", CHART_PRIORITY_SYSTEMD_SERVICES + 70, update_every, RRDSET_TYPE_STACKED);
+        }
+        else
+            rrdset_next(st_mem_detailed_pgmajfault);
+
+        if(unlikely(!st_mem_detailed_pgpgin)) {
+            st_mem_detailed_pgpgin = rrdset_find_bytype("services", "mem_pgpgin");
+            if(likely(!st_mem_detailed_pgpgin))
+                st_mem_detailed_pgpgin = rrdset_create("services", "mem_pgpgin", NULL, "mem", "services.mem_pgpgin", "Systemd Services Memory Charging Activity", "MB/s", CHART_PRIORITY_SYSTEMD_SERVICES + 80, update_every, RRDSET_TYPE_STACKED);
+        }
+        else
+            rrdset_next(st_mem_detailed_pgpgin);
+
+        if(unlikely(!st_mem_detailed_pgpgout)) {
+            st_mem_detailed_pgpgout = rrdset_find_bytype("services", "mem_pgpgout");
+            if(likely(!st_mem_detailed_pgpgout))
+                st_mem_detailed_pgpgout = rrdset_create("services", "mem_pgpgout", NULL, "mem", "services.mem_pgpgout", "Systemd Services Memory Uncharging Activity", "MB/s", CHART_PRIORITY_SYSTEMD_SERVICES + 90, update_every, RRDSET_TYPE_STACKED);
+        }
+        else
+            rrdset_next(st_mem_detailed_pgpgout);
+    }
+
+    if(likely(do_mem_failcnt)) {
+        if(unlikely(!st_mem_failcnt)) {
+            st_mem_failcnt = rrdset_find_bytype("services", "mem_failcnt");
+            if(likely(!st_mem_failcnt))
+                st_mem_failcnt = rrdset_create("services", "mem_failcnt", NULL, "mem", "services.mem_failcnt", "Systemd Services Memory Limit Failures", "MB", CHART_PRIORITY_SYSTEMD_SERVICES + 110, update_every, RRDSET_TYPE_STACKED);
+        }
+        else
+            rrdset_next(st_mem_failcnt);
+    }
+
+    if(likely(do_swap_usage)) {
+        if(unlikely(!st_swap_usage)) {
+            st_swap_usage = rrdset_find_bytype("services", "swap_usage");
+            if(likely(!st_swap_usage))
+                st_swap_usage = rrdset_create("services", "swap_usage", NULL, "swap", "services.swap_usage", "Systemd Services Swap Memory Used", "MB", CHART_PRIORITY_SYSTEMD_SERVICES + 100, update_every, RRDSET_TYPE_STACKED);
+        }
+        else
+            rrdset_next(st_swap_usage);
+    }
+
+    if(likely(do_io)) {
+        if(unlikely(!st_io_read)) {
+            st_io_read = rrdset_find_bytype("services", "io_read");
+            if(likely(!st_io_read))
+                st_io_read = rrdset_create("services", "io_read", NULL, "disk", "services.io_read", "Systemd Services Disk Read Bandwidth", "KB/s", CHART_PRIORITY_SYSTEMD_SERVICES + 120, update_every, RRDSET_TYPE_STACKED);
+        }
+        else
+            rrdset_next(st_io_read);
+
+        if(unlikely(!st_io_write)) {
+            st_io_write = rrdset_find_bytype("services", "io_write");
+            if(likely(!st_io_write))
+                st_io_write = rrdset_create("services", "io_write", NULL, "disk", "services.io_write", "Systemd Services Disk Write Bandwidth", "KB/s", CHART_PRIORITY_SYSTEMD_SERVICES + 130, update_every, RRDSET_TYPE_STACKED);
+        }
+        else
+            rrdset_next(st_io_write);
+    }
+
+    if(likely(do_io_ops)) {
+        if(unlikely(!st_io_serviced_read)) {
+            st_io_serviced_read = rrdset_find_bytype("services", "io_ops_read");
+            if(likely(!st_io_serviced_read))
+                st_io_serviced_read = rrdset_create("services", "io_ops_read", NULL, "disk", "services.io_ops_read", "Systemd Services Disk Read Operations", "operations/s", CHART_PRIORITY_SYSTEMD_SERVICES + 140, update_every, RRDSET_TYPE_STACKED);
+        }
+        else
+            rrdset_next(st_io_serviced_read);
+
+        if(unlikely(!st_io_serviced_write)) {
+            st_io_serviced_write = rrdset_find_bytype("services", "io_ops_write");
+            if(likely(!st_io_serviced_write))
+                st_io_serviced_write = rrdset_create("services", "io_ops_write", NULL, "disk", "services.io_ops_write", "Systemd Services Disk Write Operations", "operations/s", CHART_PRIORITY_SYSTEMD_SERVICES + 150, update_every, RRDSET_TYPE_STACKED);
+        }
+        else
+            rrdset_next(st_io_serviced_write);
+    }
+
+    if(likely(do_throttle_io)) {
+        if(unlikely(!st_throttle_io_read)) {
+            st_throttle_io_read = rrdset_find_bytype("services", "throttle_io_read");
+            if(likely(!st_throttle_io_read))
+                st_throttle_io_read = rrdset_create("services", "throttle_io_read", NULL, "disk", "services.throttle_io_read", "Systemd Services Throttle Disk Read Bandwidth", "KB/s", CHART_PRIORITY_SYSTEMD_SERVICES + 160, update_every, RRDSET_TYPE_STACKED);
+        }
+        else
+            rrdset_next(st_throttle_io_read);
+
+        if(unlikely(!st_throttle_io_write)) {
+            st_throttle_io_write = rrdset_find_bytype("services", "throttle_io_write");
+            if(likely(!st_throttle_io_write))
+                st_throttle_io_write = rrdset_create("services", "throttle_io_write", NULL, "disk", "services.throttle_io_write", "Systemd Services Throttle Disk Write Bandwidth", "KB/s", CHART_PRIORITY_SYSTEMD_SERVICES + 170, update_every, RRDSET_TYPE_STACKED);
+        }
+        else
+            rrdset_next(st_throttle_io_write);
+    }
+
+    if(likely(do_throttle_ops)) {
+        if(unlikely(!st_throttle_ops_read)) {
+            st_throttle_ops_read = rrdset_find_bytype("services", "throttle_io_ops_read");
+            if(likely(!st_throttle_ops_read))
+                st_throttle_ops_read = rrdset_create("services", "throttle_io_ops_read", NULL, "disk", "services.throttle_io_ops_read", "Systemd Services Throttle Disk Read Operations", "operations/s", CHART_PRIORITY_SYSTEMD_SERVICES + 180, update_every, RRDSET_TYPE_STACKED);
+        }
+        else
+            rrdset_next(st_throttle_ops_read);
+
+        if(unlikely(!st_throttle_ops_write)) {
+            st_throttle_ops_write = rrdset_find_bytype("services", "throttle_io_ops_write");
+            if(likely(!st_throttle_ops_write))
+                st_throttle_ops_write = rrdset_create("services", "throttle_io_ops_write", NULL, "disk", "services.throttle_io_ops_write", "Systemd Services Throttle Disk Write Operations", "operations/s", CHART_PRIORITY_SYSTEMD_SERVICES + 190, update_every, RRDSET_TYPE_STACKED);
+        }
+        else
+            rrdset_next(st_throttle_ops_write);
+    }
+
+    if(likely(do_queued_ops)) {
+        if(unlikely(!st_queued_ops_read)) {
+            st_queued_ops_read = rrdset_find_bytype("services", "queued_io_ops_read");
+            if(likely(!st_queued_ops_read))
+                st_queued_ops_read = rrdset_create("services", "queued_io_ops_read", NULL, "disk", "services.queued_io_ops_read", "Systemd Services Queued Disk Read Operations", "operations/s", CHART_PRIORITY_SYSTEMD_SERVICES + 200, update_every, RRDSET_TYPE_STACKED);
+        }
+        else
+            rrdset_next(st_queued_ops_read);
+
+        if(unlikely(!st_queued_ops_write)) {
+            st_queued_ops_write = rrdset_find_bytype("services", "queued_io_ops_write");
+            if(likely(!st_queued_ops_write))
+                st_queued_ops_write = rrdset_create("services", "queued_io_ops_write", NULL, "disk", "services.queued_io_ops_write", "Systemd Services Queued Disk Write Operations", "operations/s", CHART_PRIORITY_SYSTEMD_SERVICES + 210, update_every, RRDSET_TYPE_STACKED);
+        }
+        else
+            rrdset_next(st_queued_ops_write);
+    }
+
+    if(likely(do_merged_ops)) {
+        if(unlikely(!st_merged_ops_read)) {
+            st_merged_ops_read = rrdset_find_bytype("services", "merged_io_ops_read");
+            if(likely(!st_merged_ops_read))
+                st_merged_ops_read = rrdset_create("services", "merged_io_ops_read", NULL, "disk", "services.merged_io_ops_read", "Systemd Services Merged Disk Read Operations", "operations/s", CHART_PRIORITY_SYSTEMD_SERVICES + 220, update_every, RRDSET_TYPE_STACKED);
+        }
+        else
+            rrdset_next(st_merged_ops_read);
+
+        if(unlikely(!st_merged_ops_write)) {
+            st_merged_ops_write = rrdset_find_bytype("services", "merged_io_ops_write");
+            if(likely(!st_merged_ops_write))
+                st_merged_ops_write = rrdset_create("services", "merged_io_ops_write", NULL, "disk", "services.merged_io_ops_write", "Systemd Services Merged Disk Write Operations", "operations/s", CHART_PRIORITY_SYSTEMD_SERVICES + 230, update_every, RRDSET_TYPE_STACKED);
+        }
+        else
+            rrdset_next(st_merged_ops_write);
+    }
+
+    // update the values
+    struct cgroup *cg;
+    for(cg = cgroup_root; cg ; cg = cg->next) {
+        if(unlikely(!cg->available || !cg->enabled || !(cg->options & CGROUP_OPTIONS_SYSTEM_SLICE_SERVICE)))
+            continue;
+
+        if(likely(do_cpu && cg->cpuacct_stat.updated)) {
+            if(unlikely(!cg->rd_cpu))
+                cg->rd_cpu = rrddim_add(st_cpu, cg->chart_id, cg->chart_title, 100, hz, RRDDIM_INCREMENTAL);
+
+            rrddim_set_by_pointer(st_cpu, cg->rd_cpu, cg->cpuacct_stat.user + cg->cpuacct_stat.system);
+        }
+
+        if(likely(do_mem_usage && cg->memory.updated_usage_in_bytes)) {
+            if(unlikely(!cg->rd_mem_usage))
+                cg->rd_mem_usage = rrddim_add(st_mem_usage, cg->chart_id, cg->chart_title, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
+
+            rrddim_set_by_pointer(st_mem_usage, cg->rd_mem_usage, cg->memory.usage_in_bytes - ((cgroup_used_memory_without_cache)?cg->memory.cache:0));
+        }
+
+        if(likely(do_mem_detailed && cg->memory.updated_detailed)) {
+            if(unlikely(!cg->rd_mem_detailed_rss))
+                cg->rd_mem_detailed_rss = rrddim_add(st_mem_detailed_rss, cg->chart_id, cg->chart_title, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
+            rrddim_set_by_pointer(st_mem_detailed_rss, cg->rd_mem_detailed_rss, cg->memory.rss + cg->memory.rss_huge);
+
+            if(unlikely(!cg->rd_mem_detailed_mapped))
+                cg->rd_mem_detailed_mapped = rrddim_add(st_mem_detailed_mapped, cg->chart_id, cg->chart_title, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
+            rrddim_set_by_pointer(st_mem_detailed_mapped, cg->rd_mem_detailed_mapped, cg->memory.mapped_file);
+
+            if(unlikely(!cg->rd_mem_detailed_cache))
+                cg->rd_mem_detailed_cache = rrddim_add(st_mem_detailed_cache, cg->chart_id, cg->chart_title, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
+            rrddim_set_by_pointer(st_mem_detailed_cache, cg->rd_mem_detailed_cache, cg->memory.cache);
+
+            if(unlikely(!cg->rd_mem_detailed_writeback))
+                cg->rd_mem_detailed_writeback = rrddim_add(st_mem_detailed_writeback, cg->chart_id, cg->chart_title, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
+            rrddim_set_by_pointer(st_mem_detailed_writeback, cg->rd_mem_detailed_writeback, cg->memory.writeback);
+
+            if(unlikely(!cg->rd_mem_detailed_pgfault))
+                cg->rd_mem_detailed_pgfault = rrddim_add(st_mem_detailed_pgfault, cg->chart_id, cg->chart_title, system_page_size, 1024 * 1024, RRDDIM_INCREMENTAL);
+            rrddim_set_by_pointer(st_mem_detailed_pgfault, cg->rd_mem_detailed_pgfault, cg->memory.pgfault);
+
+            if(unlikely(!cg->rd_mem_detailed_pgmajfault))
+                cg->rd_mem_detailed_pgmajfault = rrddim_add(st_mem_detailed_pgmajfault, cg->chart_id, cg->chart_title, system_page_size, 1024 * 1024, RRDDIM_INCREMENTAL);
+            rrddim_set_by_pointer(st_mem_detailed_pgmajfault, cg->rd_mem_detailed_pgmajfault, cg->memory.pgmajfault);
+
+            if(unlikely(!cg->rd_mem_detailed_pgpgin))
+                cg->rd_mem_detailed_pgpgin = rrddim_add(st_mem_detailed_pgpgin, cg->chart_id, cg->chart_title, system_page_size, 1024 * 1024, RRDDIM_INCREMENTAL);
+            rrddim_set_by_pointer(st_mem_detailed_pgpgin, cg->rd_mem_detailed_pgpgin, cg->memory.pgpgin);
+
+            if(unlikely(!cg->rd_mem_detailed_pgpgout))
+                cg->rd_mem_detailed_pgpgout = rrddim_add(st_mem_detailed_pgpgout, cg->chart_id, cg->chart_title, system_page_size, 1024 * 1024, RRDDIM_INCREMENTAL);
+            rrddim_set_by_pointer(st_mem_detailed_pgpgout, cg->rd_mem_detailed_pgpgout, cg->memory.pgpgout);
+        }
+
+        if(likely(do_mem_failcnt && cg->memory.updated_failcnt)) {
+            if(unlikely(!cg->rd_mem_failcnt))
+                cg->rd_mem_failcnt = rrddim_add(st_mem_failcnt, cg->chart_id, cg->chart_title, 1, 1, RRDDIM_INCREMENTAL);
+
+            rrddim_set_by_pointer(st_mem_failcnt, cg->rd_mem_failcnt, cg->memory.failcnt);
+        }
+
+        if(likely(do_swap_usage && cg->memory.updated_msw_usage_in_bytes)) {
+            if(unlikely(!cg->rd_swap_usage))
+                cg->rd_swap_usage = rrddim_add(st_swap_usage, cg->chart_id, cg->chart_title, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
+
+            rrddim_set_by_pointer(st_swap_usage, cg->rd_swap_usage, cg->memory.msw_usage_in_bytes);
+        }
+
+        if(likely(do_io && cg->io_service_bytes.updated)) {
+            if(unlikely(!cg->rd_io_service_bytes_read))
+                cg->rd_io_service_bytes_read = rrddim_add(st_io_read, cg->chart_id, cg->chart_title, 1, 1024, RRDDIM_INCREMENTAL);
+
+            rrddim_set_by_pointer(st_io_read, cg->rd_io_service_bytes_read, cg->io_service_bytes.Read);
+
+            if(unlikely(!cg->rd_io_service_bytes_write))
+                cg->rd_io_service_bytes_write = rrddim_add(st_io_write, cg->chart_id, cg->chart_title, 1, 1024, RRDDIM_INCREMENTAL);
+
+            rrddim_set_by_pointer(st_io_write, cg->rd_io_service_bytes_write, cg->io_service_bytes.Write);
+        }
+
+        if(likely(do_io_ops && cg->io_serviced.updated)) {
+            if(unlikely(!cg->rd_io_serviced_read))
+                cg->rd_io_serviced_read = rrddim_add(st_io_serviced_read, cg->chart_id, cg->chart_title, 1, 1, RRDDIM_INCREMENTAL);
+
+            rrddim_set_by_pointer(st_io_serviced_read, cg->rd_io_serviced_read, cg->io_serviced.Read);
+
+            if(unlikely(!cg->rd_io_serviced_write))
+                cg->rd_io_serviced_write = rrddim_add(st_io_serviced_write, cg->chart_id, cg->chart_title, 1, 1, RRDDIM_INCREMENTAL);
+
+            rrddim_set_by_pointer(st_io_serviced_write, cg->rd_io_serviced_write, cg->io_serviced.Write);
+        }
+
+        if(likely(do_throttle_io && cg->throttle_io_service_bytes.updated)) {
+            if(unlikely(!cg->rd_throttle_io_read))
+                cg->rd_throttle_io_read = rrddim_add(st_throttle_io_read, cg->chart_id, cg->chart_title, 1, 1024, RRDDIM_INCREMENTAL);
+
+            rrddim_set_by_pointer(st_throttle_io_read, cg->rd_throttle_io_read, cg->throttle_io_service_bytes.Read);
+
+            if(unlikely(!cg->rd_throttle_io_write))
+                cg->rd_throttle_io_write = rrddim_add(st_throttle_io_write, cg->chart_id, cg->chart_title, 1, 1024, RRDDIM_INCREMENTAL);
+
+            rrddim_set_by_pointer(st_throttle_io_write, cg->rd_throttle_io_write, cg->throttle_io_service_bytes.Write);
+        }
+
+        if(likely(do_throttle_ops && cg->throttle_io_serviced.updated)) {
+            if(unlikely(!cg->rd_throttle_io_serviced_read))
+                cg->rd_throttle_io_serviced_read = rrddim_add(st_throttle_ops_read, cg->chart_id, cg->chart_title, 1, 1, RRDDIM_INCREMENTAL);
+
+            rrddim_set_by_pointer(st_throttle_ops_read, cg->rd_throttle_io_serviced_read, cg->throttle_io_serviced.Read);
+
+            if(unlikely(!cg->rd_throttle_io_serviced_write))
+                cg->rd_throttle_io_serviced_write = rrddim_add(st_throttle_ops_write, cg->chart_id, cg->chart_title, 1, 1, RRDDIM_INCREMENTAL);
+
+            rrddim_set_by_pointer(st_throttle_ops_write, cg->rd_throttle_io_serviced_write, cg->throttle_io_serviced.Write);
+        }
+
+        if(likely(do_queued_ops && cg->io_queued.updated)) {
+            if(unlikely(!cg->rd_io_queued_read))
+                cg->rd_io_queued_read = rrddim_add(st_queued_ops_read, cg->chart_id, cg->chart_title, 1, 1, RRDDIM_INCREMENTAL);
+
+            rrddim_set_by_pointer(st_queued_ops_read, cg->rd_io_queued_read, cg->io_queued.Read);
+
+            if(unlikely(!cg->rd_io_queued_write))
+                cg->rd_io_queued_write = rrddim_add(st_queued_ops_write, cg->chart_id, cg->chart_title, 1, 1, RRDDIM_INCREMENTAL);
+
+            rrddim_set_by_pointer(st_queued_ops_write, cg->rd_io_queued_write, cg->io_queued.Write);
+        }
+
+        if(likely(do_merged_ops && cg->io_merged.updated)) {
+            if(unlikely(!cg->rd_io_merged_read))
+                cg->rd_io_merged_read = rrddim_add(st_merged_ops_read, cg->chart_id, cg->chart_title, 1, 1, RRDDIM_INCREMENTAL);
+
+            rrddim_set_by_pointer(st_merged_ops_read, cg->rd_io_merged_read, cg->io_merged.Read);
+
+            if(unlikely(!cg->rd_io_merged_write))
+                cg->rd_io_merged_write = rrddim_add(st_merged_ops_write, cg->chart_id, cg->chart_title, 1, 1, RRDDIM_INCREMENTAL);
+
+            rrddim_set_by_pointer(st_merged_ops_write, cg->rd_io_merged_write, cg->io_merged.Write);
+        }
+    }
+
+    // complete the iteration
+    if(likely(do_cpu))
+        rrdset_done(st_cpu);
+
+    if(likely(do_mem_usage))
+        rrdset_done(st_mem_usage);
+
+    if(unlikely(do_mem_detailed)) {
+        rrdset_done(st_mem_detailed_cache);
+        rrdset_done(st_mem_detailed_rss);
+        rrdset_done(st_mem_detailed_mapped);
+        rrdset_done(st_mem_detailed_writeback);
+        rrdset_done(st_mem_detailed_pgfault);
+        rrdset_done(st_mem_detailed_pgmajfault);
+        rrdset_done(st_mem_detailed_pgpgin);
+        rrdset_done(st_mem_detailed_pgpgout);
+    }
+
+    if(likely(do_mem_failcnt))
+        rrdset_done(st_mem_failcnt);
+
+    if(likely(do_swap_usage))
+        rrdset_done(st_swap_usage);
+
+    if(likely(do_io)) {
+        rrdset_done(st_io_read);
+        rrdset_done(st_io_write);
+    }
+
+    if(likely(do_io_ops)) {
+        rrdset_done(st_io_serviced_read);
+        rrdset_done(st_io_serviced_write);
+    }
+
+    if(likely(do_throttle_io)) {
+        rrdset_done(st_throttle_io_read);
+        rrdset_done(st_throttle_io_write);
+    }
+
+    if(likely(do_throttle_ops)) {
+        rrdset_done(st_throttle_ops_read);
+        rrdset_done(st_throttle_ops_write);
+    }
+
+    if(likely(do_queued_ops)) {
+        rrdset_done(st_queued_ops_read);
+        rrdset_done(st_queued_ops_write);
+    }
+
+    if(likely(do_merged_ops)) {
+        rrdset_done(st_merged_ops_read);
+        rrdset_done(st_merged_ops_write);
+    }
+}
+
+static inline char *cgroup_chart_type(char *buffer, const char *id, size_t len) {
+    if(buffer[0]) return buffer;
+
+    if(id[0] == '\0' || (id[0] == '/' && id[1] == '\0'))
+        strncpy(buffer, "cgroup_root", len);
+    else
+        snprintfz(buffer, len, "cgroup_%s", id);
+
+    netdata_fix_chart_id(buffer);
+    return buffer;
+}
+
 void update_cgroup_charts(int update_every) {
     debug(D_CGROUP, "updating cgroups charts");
 
     char type[RRD_ID_LENGTH_MAX + 1];
     char title[CHART_TITLE_MAX + 1];
 
-    struct cgroup *cg;
-    RRDSET *st;
+    int services_do_cpu = 0,
+            services_do_mem_usage = 0,
+            services_do_mem_detailed = 0,
+            services_do_mem_failcnt = 0,
+            services_do_swap_usage = 0,
+            services_do_io = 0,
+            services_do_io_ops = 0,
+            services_do_throttle_io = 0,
+            services_do_throttle_ops = 0,
+            services_do_queued_ops = 0,
+            services_do_merged_ops = 0;
 
+    struct cgroup *cg;
     for(cg = cgroup_root; cg ; cg = cg->next) {
-        if(!cg->available || !cg->enabled)
+        if(unlikely(!cg->available || !cg->enabled))
             continue;
 
-        if(cg->id[0] == '\0')
-            strcpy(type, "cgroup_root");
-        else if(cg->id[0] == '/')
-            snprintfz(type, RRD_ID_LENGTH_MAX, "cgroup_%s", cg->chart_id);
-        else
-            snprintfz(type, RRD_ID_LENGTH_MAX, "cgroup_%s", cg->chart_id);
+        if(likely(cgroup_enable_systemd_services && cg->options & CGROUP_OPTIONS_SYSTEM_SLICE_SERVICE)) {
+            if(cg->cpuacct_stat.updated && cg->cpuacct_stat.enabled == CONFIG_ONDEMAND_YES) services_do_cpu++;
 
-        netdata_fix_chart_id(type);
+            if(cgroup_enable_systemd_services_detailed_memory && cg->memory.updated_detailed && cg->memory.enabled_detailed) services_do_mem_detailed++;
+            if(cg->memory.updated_usage_in_bytes && cg->memory.enabled_usage_in_bytes == CONFIG_ONDEMAND_YES) services_do_mem_usage++;
+            if(cg->memory.updated_failcnt && cg->memory.enabled_failcnt == CONFIG_ONDEMAND_YES) services_do_mem_failcnt++;
+            if(cg->memory.updated_msw_usage_in_bytes && cg->memory.enabled_msw_usage_in_bytes == CONFIG_ONDEMAND_YES) services_do_swap_usage++;
+
+            if(cg->io_service_bytes.updated && cg->io_service_bytes.enabled == CONFIG_ONDEMAND_YES) services_do_io++;
+            if(cg->io_serviced.updated && cg->io_serviced.enabled == CONFIG_ONDEMAND_YES) services_do_io_ops++;
+            if(cg->throttle_io_service_bytes.updated && cg->throttle_io_service_bytes.enabled == CONFIG_ONDEMAND_YES) services_do_throttle_io++;
+            if(cg->throttle_io_serviced.updated && cg->throttle_io_serviced.enabled == CONFIG_ONDEMAND_YES) services_do_throttle_ops++;
+            if(cg->io_queued.updated && cg->io_queued.enabled == CONFIG_ONDEMAND_YES) services_do_queued_ops++;
+            if(cg->io_merged.updated && cg->io_merged.enabled == CONFIG_ONDEMAND_YES) services_do_merged_ops++;
+            continue;
+        }
 
-        if(cg->cpuacct_stat.updated) {
-            st = rrdset_find_bytype(type, "cpu");
-            if(!st) {
-                snprintfz(title, CHART_TITLE_MAX, "CPU Usage (%d%% = %d core%s) for cgroup %s", (processors * 100), processors, (processors>1)?"s":"", cg->chart_title);
-                st = rrdset_create(type, "cpu", NULL, "cpu", "cgroup.cpu", title, "%", 40000, update_every, RRDSET_TYPE_STACKED);
+        type[0] = '\0';
 
-                rrddim_add(st, "user", NULL, 100, hz, RRDDIM_INCREMENTAL);
-                rrddim_add(st, "system", NULL, 100, hz, RRDDIM_INCREMENTAL);
+        if(likely(cg->cpuacct_stat.updated && cg->cpuacct_stat.enabled == CONFIG_ONDEMAND_YES)) {
+            if(unlikely(!cg->st_cpu)) {
+                cg->st_cpu = rrdset_find_bytype(cgroup_chart_type(type, cg->chart_id, RRD_ID_LENGTH_MAX), "cpu");
+                if(likely(!cg->st_cpu)) {
+                    snprintfz(title, CHART_TITLE_MAX, "CPU Usage (%d%% = %d core%s) for cgroup %s", (processors * 100), processors, (processors > 1) ? "s" : "", cg->chart_title);
+                    cg->st_cpu = rrdset_create(type, "cpu", NULL, "cpu", "cgroup.cpu", title, "%", CHART_PRIORITY_CONTAINERS, update_every, RRDSET_TYPE_STACKED);
+                }
+                rrddim_add(cg->st_cpu, "user", NULL, 100, hz, RRDDIM_INCREMENTAL);
+                rrddim_add(cg->st_cpu, "system", NULL, 100, hz, RRDDIM_INCREMENTAL);
             }
-            else rrdset_next(st);
+            else
+                rrdset_next(cg->st_cpu);
 
-            rrddim_set(st, "user", cg->cpuacct_stat.user);
-            rrddim_set(st, "system", cg->cpuacct_stat.system);
-            rrdset_done(st);
+            rrddim_set(cg->st_cpu, "user", cg->cpuacct_stat.user);
+            rrddim_set(cg->st_cpu, "system", cg->cpuacct_stat.system);
+            rrdset_done(cg->st_cpu);
         }
 
-        if(cg->cpuacct_usage.updated) {
+        if(likely(cg->cpuacct_usage.updated && cg->cpuacct_usage.enabled == CONFIG_ONDEMAND_YES)) {
             char id[RRD_ID_LENGTH_MAX + 1];
             unsigned int i;
 
-            st = rrdset_find_bytype(type, "cpu_per_core");
-            if(!st) {
-                snprintfz(title, CHART_TITLE_MAX, "CPU Usage (%d%% = %d core%s) Per Core for cgroup %s", (processors * 100), processors, (processors>1)?"s":"", cg->chart_title);
-                st = rrdset_create(type, "cpu_per_core", NULL, "cpu", "cgroup.cpu_per_core", title, "%", 40100, update_every, RRDSET_TYPE_STACKED);
-
-                for(i = 0; i < cg->cpuacct_usage.cpus ;i++) {
+            if(unlikely(!cg->st_cpu_per_core)) {
+                cg->st_cpu_per_core = rrdset_find_bytype(cgroup_chart_type(type, cg->chart_id, RRD_ID_LENGTH_MAX), "cpu_per_core");
+                if(likely(!cg->st_cpu_per_core)) {
+                    snprintfz(title, CHART_TITLE_MAX, "CPU Usage (%d%% = %d core%s) Per Core for cgroup %s", (processors * 100), processors, (processors > 1) ? "s" : "", cg->chart_title);
+                    cg->st_cpu_per_core = rrdset_create(type, "cpu_per_core", NULL, "cpu", "cgroup.cpu_per_core", title, "%", CHART_PRIORITY_CONTAINERS + 100, update_every, RRDSET_TYPE_STACKED);
+                }
+                for(i = 0; i < cg->cpuacct_usage.cpus; i++) {
                     snprintfz(id, CHART_TITLE_MAX, "cpu%u", i);
-                    rrddim_add(st, id, NULL, 100, 1000000000, RRDDIM_INCREMENTAL);
+                    rrddim_add(cg->st_cpu_per_core, id, NULL, 100, 1000000000, RRDDIM_INCREMENTAL);
                 }
             }
-            else rrdset_next(st);
+            else
+                rrdset_next(cg->st_cpu_per_core);
 
             for(i = 0; i < cg->cpuacct_usage.cpus ;i++) {
                 snprintfz(id, CHART_TITLE_MAX, "cpu%u", i);
-                rrddim_set(st, id, cg->cpuacct_usage.cpu_percpu[i]);
+                rrddim_set(cg->st_cpu_per_core, id, cg->cpuacct_usage.cpu_percpu[i]);
             }
-            rrdset_done(st);
+            rrdset_done(cg->st_cpu_per_core);
         }
 
-        if(cg->memory.updated) {
-            if(cg->memory.cache + cg->memory.rss + cg->memory.rss_huge + cg->memory.mapped_file > 0) {
-                st = rrdset_find_bytype(type, "mem");
-                if(!st) {
+        if(likely(cg->memory.updated_detailed && cg->memory.enabled_detailed == CONFIG_ONDEMAND_YES)) {
+            if(unlikely(!cg->st_mem)) {
+                cg->st_mem = rrdset_find_bytype(cgroup_chart_type(type, cg->chart_id, RRD_ID_LENGTH_MAX), "mem");
+                if(likely(!cg->st_mem)) {
                     snprintfz(title, CHART_TITLE_MAX, "Memory Usage for cgroup %s", cg->chart_title);
-                    st = rrdset_create(type, "mem", NULL, "mem", "cgroup.mem", title, "MB", 40210, update_every,
-                                       RRDSET_TYPE_STACKED);
-
-                    rrddim_add(st, "cache", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
-                    rrddim_add(st, "rss", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
-                    if(cg->memory.has_dirty_swap)
-                        rrddim_add(st, "swap", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
-                    rrddim_add(st, "rss_huge", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
-                    rrddim_add(st, "mapped_file", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
+                    cg->st_mem = rrdset_create(type, "mem", NULL, "mem", "cgroup.mem", title, "MB", CHART_PRIORITY_CONTAINERS + 210, update_every, RRDSET_TYPE_STACKED);
                 }
-                else rrdset_next(st);
 
-                rrddim_set(st, "cache", cg->memory.cache);
-                rrddim_set(st, "rss", cg->memory.rss);
-                if(cg->memory.has_dirty_swap)
-                    rrddim_set(st, "swap", cg->memory.swap);
-                rrddim_set(st, "rss_huge", cg->memory.rss_huge);
-                rrddim_set(st, "mapped_file", cg->memory.mapped_file);
-                rrdset_done(st);
+                rrddim_add(cg->st_mem, "cache", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
+                rrddim_add(cg->st_mem, "rss", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
+                if(cg->memory.detailed_has_swap)
+                    rrddim_add(cg->st_mem, "swap", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
+                rrddim_add(cg->st_mem, "rss_huge", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
+                rrddim_add(cg->st_mem, "mapped_file", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
             }
+            else
+                rrdset_next(cg->st_mem);
+
+            rrddim_set(cg->st_mem, "cache", cg->memory.cache);
+            rrddim_set(cg->st_mem, "rss", cg->memory.rss);
+            if(cg->memory.detailed_has_swap)
+                rrddim_set(cg->st_mem, "swap", cg->memory.swap);
+            rrddim_set(cg->st_mem, "rss_huge", cg->memory.rss_huge);
+            rrddim_set(cg->st_mem, "mapped_file", cg->memory.mapped_file);
+            rrdset_done(cg->st_mem);
+
+            if(unlikely(!cg->st_writeback)) {
+                cg->st_writeback = rrdset_find_bytype(cgroup_chart_type(type, cg->chart_id, RRD_ID_LENGTH_MAX), "writeback");
+                if(likely(!cg->st_writeback)) {
+                    snprintfz(title, CHART_TITLE_MAX, "Writeback Memory for cgroup %s", cg->chart_title);
+                    cg->st_writeback = rrdset_create(type, "writeback", NULL, "mem", "cgroup.writeback", title, "MB", CHART_PRIORITY_CONTAINERS + 300, update_every, RRDSET_TYPE_AREA);
+                }
 
-            st = rrdset_find_bytype(type, "writeback");
-            if(!st) {
-                snprintfz(title, CHART_TITLE_MAX, "Writeback Memory for cgroup %s", cg->chart_title);
-                st = rrdset_create(type, "writeback", NULL, "mem", "cgroup.writeback", title, "MB", 40300,
-                                   update_every, RRDSET_TYPE_AREA);
-
-                if(cg->memory.has_dirty_swap)
-                    rrddim_add(st, "dirty", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
-                rrddim_add(st, "writeback", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
+                if(cg->memory.detailed_has_dirty)
+                    rrddim_add(cg->st_writeback, "dirty", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
+                rrddim_add(cg->st_writeback, "writeback", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
             }
-            else rrdset_next(st);
+            else
+                rrdset_next(cg->st_writeback);
 
-            if(cg->memory.has_dirty_swap)
-                rrddim_set(st, "dirty", cg->memory.dirty);
-            rrddim_set(st, "writeback", cg->memory.writeback);
-            rrdset_done(st);
+            if(cg->memory.detailed_has_dirty)
+                rrddim_set(cg->st_writeback, "dirty", cg->memory.dirty);
+            rrddim_set(cg->st_writeback, "writeback", cg->memory.writeback);
+            rrdset_done(cg->st_writeback);
 
-            if(cg->memory.pgpgin + cg->memory.pgpgout > 0) {
-                st = rrdset_find_bytype(type, "mem_activity");
-                if(!st) {
+            if(unlikely(!cg->st_mem_activity)) {
+                cg->st_mem_activity = rrdset_find_bytype(cgroup_chart_type(type, cg->chart_id, RRD_ID_LENGTH_MAX), "mem_activity");
+                if(likely(!cg->st_mem_activity)) {
                     snprintfz(title, CHART_TITLE_MAX, "Memory Activity for cgroup %s", cg->chart_title);
-                    st = rrdset_create(type, "mem_activity", NULL, "mem", "cgroup.mem_activity", title, "MB/s",
-                                       40400, update_every, RRDSET_TYPE_LINE);
-
-                    rrddim_add(st, "pgpgin", "in", sysconf(_SC_PAGESIZE), 1024 * 1024, RRDDIM_INCREMENTAL);
-                    rrddim_add(st, "pgpgout", "out", -sysconf(_SC_PAGESIZE), 1024 * 1024, RRDDIM_INCREMENTAL);
+                    cg->st_mem_activity = rrdset_create(type, "mem_activity", NULL, "mem", "cgroup.mem_activity", title, "MB/s", CHART_PRIORITY_CONTAINERS + 400, update_every, RRDSET_TYPE_LINE);
                 }
-                else rrdset_next(st);
-
-                rrddim_set(st, "pgpgin", cg->memory.pgpgin);
-                rrddim_set(st, "pgpgout", cg->memory.pgpgout);
-                rrdset_done(st);
+                rrddim_add(cg->st_mem_activity, "pgpgin", "in", system_page_size, 1024 * 1024, RRDDIM_INCREMENTAL);
+                rrddim_add(cg->st_mem_activity, "pgpgout", "out", -system_page_size, 1024 * 1024, RRDDIM_INCREMENTAL);
             }
+            else
+                rrdset_next(cg->st_mem_activity);
 
-            if(cg->memory.pgfault + cg->memory.pgmajfault > 0) {
-                st = rrdset_find_bytype(type, "pgfaults");
-                if(!st) {
-                    snprintfz(title, CHART_TITLE_MAX, "Memory Page Faults for cgroup %s", cg->chart_title);
-                    st = rrdset_create(type, "pgfaults", NULL, "mem", "cgroup.pgfaults", title, "MB/s", 40500,
-                                       update_every, RRDSET_TYPE_LINE);
+            rrddim_set(cg->st_mem_activity, "pgpgin", cg->memory.pgpgin);
+            rrddim_set(cg->st_mem_activity, "pgpgout", cg->memory.pgpgout);
+            rrdset_done(cg->st_mem_activity);
 
-                    rrddim_add(st, "pgfault", NULL, sysconf(_SC_PAGESIZE), 1024 * 1024, RRDDIM_INCREMENTAL);
-                    rrddim_add(st, "pgmajfault", "swap", -sysconf(_SC_PAGESIZE), 1024 * 1024, RRDDIM_INCREMENTAL);
+            if(unlikely(!cg->st_pgfaults)) {
+                cg->st_pgfaults = rrdset_find_bytype(cgroup_chart_type(type, cg->chart_id, RRD_ID_LENGTH_MAX), "pgfaults");
+                if(likely(!cg->st_pgfaults)) {
+                    snprintfz(title, CHART_TITLE_MAX, "Memory Page Faults for cgroup %s", cg->chart_title);
+                    cg->st_pgfaults = rrdset_create(type, "pgfaults", NULL, "mem", "cgroup.pgfaults", title, "MB/s", CHART_PRIORITY_CONTAINERS + 500, update_every, RRDSET_TYPE_LINE);
                 }
-                else rrdset_next(st);
-
-                rrddim_set(st, "pgfault", cg->memory.pgfault);
-                rrddim_set(st, "pgmajfault", cg->memory.pgmajfault);
-                rrdset_done(st);
+                rrddim_add(cg->st_pgfaults, "pgfault", NULL, system_page_size, 1024 * 1024, RRDDIM_INCREMENTAL);
+                rrddim_add(cg->st_pgfaults, "pgmajfault", "swap", -system_page_size, 1024 * 1024, RRDDIM_INCREMENTAL);
             }
-        }
+            else
+                rrdset_next(cg->st_pgfaults);
 
-        if(cg->memory.usage_in_bytes_updated) {
-            st = rrdset_find_bytype(type, "mem_usage");
-            if(!st) {
-                snprintfz(title, CHART_TITLE_MAX, "Total Memory for cgroup %s", cg->chart_title);
-                st = rrdset_create(type, "mem_usage", NULL, "mem", "cgroup.mem_usage", title, "MB", 40200,
-                                   update_every, RRDSET_TYPE_STACKED);
+            rrddim_set(cg->st_pgfaults, "pgfault", cg->memory.pgfault);
+            rrddim_set(cg->st_pgfaults, "pgmajfault", cg->memory.pgmajfault);
+            rrdset_done(cg->st_pgfaults);
+        }
 
-                rrddim_add(st, "ram", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
-                rrddim_add(st, "swap", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
+        if(likely(cg->memory.updated_usage_in_bytes && cg->memory.enabled_usage_in_bytes == CONFIG_ONDEMAND_YES)) {
+            if(unlikely(!cg->st_mem_usage)) {
+                cg->st_mem_usage = rrdset_find_bytype(cgroup_chart_type(type, cg->chart_id, RRD_ID_LENGTH_MAX), "mem_usage");
+                if(likely(!cg->st_mem_usage)) {
+                    snprintfz(title, CHART_TITLE_MAX, "Used Memory %sfor cgroup %s", (cgroup_used_memory_without_cache && cg->memory.updated_detailed)?"without Cache ":"", cg->chart_title);
+                    cg->st_mem_usage = rrdset_create(type, "mem_usage", NULL, "mem", "cgroup.mem_usage", title, "MB", CHART_PRIORITY_CONTAINERS + 200, update_every, RRDSET_TYPE_STACKED);
+                }
+                rrddim_add(cg->st_mem_usage, "ram", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
+                rrddim_add(cg->st_mem_usage, "swap", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
             }
-            else rrdset_next(st);
+            else
+                rrdset_next(cg->st_mem_usage);
 
-            rrddim_set(st, "ram", cg->memory.usage_in_bytes);
-            rrddim_set(st, "swap", (cg->memory.msw_usage_in_bytes > cg->memory.usage_in_bytes)?cg->memory.msw_usage_in_bytes - cg->memory.usage_in_bytes:0);
-            rrdset_done(st);
+            rrddim_set(cg->st_mem_usage, "ram", cg->memory.usage_in_bytes - ((cgroup_used_memory_without_cache)?cg->memory.cache:0));
+            rrddim_set(cg->st_mem_usage, "swap", (cg->memory.msw_usage_in_bytes > cg->memory.usage_in_bytes)?cg->memory.msw_usage_in_bytes - cg->memory.usage_in_bytes:0);
+            rrdset_done(cg->st_mem_usage);
         }
 
-        if(cg->memory.failcnt_updated && cg->memory.failcnt > 0) {
-            st = rrdset_find_bytype(type, "mem_failcnt");
-            if(!st) {
-                snprintfz(title, CHART_TITLE_MAX, "Memory Limit Failures for cgroup %s", cg->chart_title);
-                st = rrdset_create(type, "mem_failcnt", NULL, "mem", "cgroup.mem_failcnt", title, "MB", 40250,
-                                   update_every, RRDSET_TYPE_LINE);
-
-                rrddim_add(st, "failures", NULL, 1, 1, RRDDIM_INCREMENTAL);
+        if(likely(cg->memory.updated_failcnt && cg->memory.enabled_failcnt == CONFIG_ONDEMAND_YES)) {
+            if(unlikely(!cg->st_mem_failcnt)) {
+                cg->st_mem_failcnt = rrdset_find_bytype(cgroup_chart_type(type, cg->chart_id, RRD_ID_LENGTH_MAX), "mem_failcnt");
+                if(likely(!cg->st_mem_failcnt)) {
+                    snprintfz(title, CHART_TITLE_MAX, "Memory Limit Failures for cgroup %s", cg->chart_title);
+                    cg->st_mem_failcnt = rrdset_create(type, "mem_failcnt", NULL, "mem", "cgroup.mem_failcnt", title, "count", CHART_PRIORITY_CONTAINERS + 250, update_every, RRDSET_TYPE_LINE);
+                }
+                rrddim_add(cg->st_mem_failcnt, "failures", NULL, 1, 1, RRDDIM_INCREMENTAL);
             }
-            else rrdset_next(st);
+            else
+                rrdset_next(cg->st_mem_failcnt);
 
-            rrddim_set(st, "failures", cg->memory.failcnt);
-            rrdset_done(st);
+            rrddim_set(cg->st_mem_failcnt, "failures", cg->memory.failcnt);
+            rrdset_done(cg->st_mem_failcnt);
         }
 
-        if(cg->io_service_bytes.updated && cg->io_service_bytes.Read + cg->io_service_bytes.Write > 0) {
-            st = rrdset_find_bytype(type, "io");
-            if(!st) {
-                snprintfz(title, CHART_TITLE_MAX, "I/O Bandwidth (all disks) for cgroup %s", cg->chart_title);
-                st = rrdset_create(type, "io", NULL, "disk", "cgroup.io", title, "KB/s", 41200,
-                                   update_every, RRDSET_TYPE_AREA);
-
-                rrddim_add(st, "read", NULL, 1, 1024, RRDDIM_INCREMENTAL);
-                rrddim_add(st, "write", NULL, -1, 1024, RRDDIM_INCREMENTAL);
+        if(likely(cg->io_service_bytes.updated && cg->io_service_bytes.enabled == CONFIG_ONDEMAND_YES)) {
+            if(unlikely(!cg->st_io)) {
+                cg->st_io = rrdset_find_bytype(cgroup_chart_type(type, cg->chart_id, RRD_ID_LENGTH_MAX), "io");
+                if(likely(!cg->st_io)) {
+                    snprintfz(title, CHART_TITLE_MAX, "I/O Bandwidth (all disks) for cgroup %s", cg->chart_title);
+                    cg->st_io = rrdset_create(type, "io", NULL, "disk", "cgroup.io", title, "KB/s", CHART_PRIORITY_CONTAINERS + 1200, update_every, RRDSET_TYPE_AREA);
+                }
+                rrddim_add(cg->st_io, "read", NULL, 1, 1024, RRDDIM_INCREMENTAL);
+                rrddim_add(cg->st_io, "write", NULL, -1, 1024, RRDDIM_INCREMENTAL);
             }
-            else rrdset_next(st);
+            else
+                rrdset_next(cg->st_io);
 
-            rrddim_set(st, "read", cg->io_service_bytes.Read);
-            rrddim_set(st, "write", cg->io_service_bytes.Write);
-            rrdset_done(st);
+            rrddim_set(cg->st_io, "read", cg->io_service_bytes.Read);
+            rrddim_set(cg->st_io, "write", cg->io_service_bytes.Write);
+            rrdset_done(cg->st_io);
         }
 
-        if(cg->io_serviced.updated && cg->io_serviced.Read + cg->io_serviced.Write > 0) {
-            st = rrdset_find_bytype(type, "serviced_ops");
-            if(!st) {
-                snprintfz(title, CHART_TITLE_MAX, "Serviced I/O Operations (all disks) for cgroup %s", cg->chart_title);
-                st = rrdset_create(type, "serviced_ops", NULL, "disk", "cgroup.serviced_ops", title, "operations/s", 41200,
-                                   update_every, RRDSET_TYPE_LINE);
-
-                rrddim_add(st, "read", NULL, 1, 1, RRDDIM_INCREMENTAL);
-                rrddim_add(st, "write", NULL, -1, 1, RRDDIM_INCREMENTAL);
+        if(likely(cg->io_serviced.updated && cg->io_serviced.enabled == CONFIG_ONDEMAND_YES)) {
+            if(unlikely(!cg->st_serviced_ops)) {
+                cg->st_serviced_ops = rrdset_find_bytype(cgroup_chart_type(type, cg->chart_id, RRD_ID_LENGTH_MAX), "serviced_ops");
+                if(likely(!cg->st_serviced_ops)) {
+                    snprintfz(title, CHART_TITLE_MAX, "Serviced I/O Operations (all disks) for cgroup %s", cg->chart_title);
+                    cg->st_serviced_ops = rrdset_create(type, "serviced_ops", NULL, "disk", "cgroup.serviced_ops", title, "operations/s", CHART_PRIORITY_CONTAINERS + 1200, update_every, RRDSET_TYPE_LINE);
+                }
+                rrddim_add(cg->st_serviced_ops, "read", NULL, 1, 1, RRDDIM_INCREMENTAL);
+                rrddim_add(cg->st_serviced_ops, "write", NULL, -1, 1, RRDDIM_INCREMENTAL);
             }
-            else rrdset_next(st);
+            else
+                rrdset_next(cg->st_serviced_ops);
 
-            rrddim_set(st, "read", cg->io_serviced.Read);
-            rrddim_set(st, "write", cg->io_serviced.Write);
-            rrdset_done(st);
+            rrddim_set(cg->st_serviced_ops, "read", cg->io_serviced.Read);
+            rrddim_set(cg->st_serviced_ops, "write", cg->io_serviced.Write);
+            rrdset_done(cg->st_serviced_ops);
         }
 
-        if(cg->throttle_io_service_bytes.updated && cg->throttle_io_service_bytes.Read + cg->throttle_io_service_bytes.Write > 0) {
-            st = rrdset_find_bytype(type, "throttle_io");
-            if(!st) {
-                snprintfz(title, CHART_TITLE_MAX, "Throttle I/O Bandwidth (all disks) for cgroup %s", cg->chart_title);
-                st = rrdset_create(type, "throttle_io", NULL, "disk", "cgroup.throttle_io", title, "KB/s", 41200,
-                                   update_every, RRDSET_TYPE_AREA);
-
-                rrddim_add(st, "read", NULL, 1, 1024, RRDDIM_INCREMENTAL);
-                rrddim_add(st, "write", NULL, -1, 1024, RRDDIM_INCREMENTAL);
+        if(likely(cg->throttle_io_service_bytes.updated && cg->throttle_io_service_bytes.enabled == CONFIG_ONDEMAND_YES)) {
+            if(unlikely(!cg->st_throttle_io)) {
+                cg->st_throttle_io = rrdset_find_bytype(cgroup_chart_type(type, cg->chart_id, RRD_ID_LENGTH_MAX), "throttle_io");
+                if(likely(!cg->st_throttle_io)) {
+                    snprintfz(title, CHART_TITLE_MAX, "Throttle I/O Bandwidth (all disks) for cgroup %s", cg->chart_title);
+                    cg->st_throttle_io = rrdset_create(type, "throttle_io", NULL, "disk", "cgroup.throttle_io", title, "KB/s", CHART_PRIORITY_CONTAINERS + 1200, update_every, RRDSET_TYPE_AREA);
+                }
+                rrddim_add(cg->st_throttle_io, "read", NULL, 1, 1024, RRDDIM_INCREMENTAL);
+                rrddim_add(cg->st_throttle_io, "write", NULL, -1, 1024, RRDDIM_INCREMENTAL);
             }
-            else rrdset_next(st);
+            else
+                rrdset_next(cg->st_throttle_io);
 
-            rrddim_set(st, "read", cg->throttle_io_service_bytes.Read);
-            rrddim_set(st, "write", cg->throttle_io_service_bytes.Write);
-            rrdset_done(st);
+            rrddim_set(cg->st_throttle_io, "read", cg->throttle_io_service_bytes.Read);
+            rrddim_set(cg->st_throttle_io, "write", cg->throttle_io_service_bytes.Write);
+            rrdset_done(cg->st_throttle_io);
         }
 
-
-        if(cg->throttle_io_serviced.updated && cg->throttle_io_serviced.Read + cg->throttle_io_serviced.Write > 0) {
-            st = rrdset_find_bytype(type, "throttle_serviced_ops");
-            if(!st) {
-                snprintfz(title, CHART_TITLE_MAX, "Throttle Serviced I/O Operations (all disks) for cgroup %s", cg->chart_title);
-                st = rrdset_create(type, "throttle_serviced_ops", NULL, "disk", "cgroup.throttle_serviced_ops", title, "operations/s", 41200,
-                                   update_every, RRDSET_TYPE_LINE);
-
-                rrddim_add(st, "read", NULL, 1, 1, RRDDIM_INCREMENTAL);
-                rrddim_add(st, "write", NULL, -1, 1, RRDDIM_INCREMENTAL);
+        if(likely(cg->throttle_io_serviced.updated && cg->throttle_io_serviced.enabled == CONFIG_ONDEMAND_YES)) {
+            if(unlikely(!cg->st_throttle_serviced_ops)) {
+                cg->st_throttle_serviced_ops = rrdset_find_bytype(cgroup_chart_type(type, cg->chart_id, RRD_ID_LENGTH_MAX), "throttle_serviced_ops");
+                if(likely(!cg->st_throttle_serviced_ops)) {
+                    snprintfz(title, CHART_TITLE_MAX, "Throttle Serviced I/O Operations (all disks) for cgroup %s", cg->chart_title);
+                    cg->st_throttle_serviced_ops = rrdset_create(type, "throttle_serviced_ops", NULL, "disk", "cgroup.throttle_serviced_ops", title, "operations/s", CHART_PRIORITY_CONTAINERS + 1200, update_every, RRDSET_TYPE_LINE);
+                }
+                rrddim_add(cg->st_throttle_serviced_ops, "read", NULL, 1, 1, RRDDIM_INCREMENTAL);
+                rrddim_add(cg->st_throttle_serviced_ops, "write", NULL, -1, 1, RRDDIM_INCREMENTAL);
             }
-            else rrdset_next(st);
+            else
+                rrdset_next(cg->st_throttle_serviced_ops);
 
-            rrddim_set(st, "read", cg->throttle_io_serviced.Read);
-            rrddim_set(st, "write", cg->throttle_io_serviced.Write);
-            rrdset_done(st);
+            rrddim_set(cg->st_throttle_serviced_ops, "read", cg->throttle_io_serviced.Read);
+            rrddim_set(cg->st_throttle_serviced_ops, "write", cg->throttle_io_serviced.Write);
+            rrdset_done(cg->st_throttle_serviced_ops);
         }
 
-        if(cg->io_queued.updated) {
-            st = rrdset_find_bytype(type, "queued_ops");
-            if(!st) {
-                snprintfz(title, CHART_TITLE_MAX, "Queued I/O Operations (all disks) for cgroup %s", cg->chart_title);
-                st = rrdset_create(type, "queued_ops", NULL, "disk", "cgroup.queued_ops", title, "operations", 42000,
-                                   update_every, RRDSET_TYPE_LINE);
-
-                rrddim_add(st, "read", NULL, 1, 1, RRDDIM_ABSOLUTE);
-                rrddim_add(st, "write", NULL, -1, 1, RRDDIM_ABSOLUTE);
+        if(likely(cg->io_queued.updated && cg->io_queued.enabled == CONFIG_ONDEMAND_YES)) {
+            if(unlikely(!cg->st_queued_ops)) {
+                cg->st_queued_ops = rrdset_find_bytype(cgroup_chart_type(type, cg->chart_id, RRD_ID_LENGTH_MAX), "queued_ops");
+                if(likely(!cg->st_queued_ops)) {
+                    snprintfz(title, CHART_TITLE_MAX, "Queued I/O Operations (all disks) for cgroup %s", cg->chart_title);
+                    cg->st_queued_ops = rrdset_create(type, "queued_ops", NULL, "disk", "cgroup.queued_ops", title, "operations", CHART_PRIORITY_CONTAINERS + 2000, update_every, RRDSET_TYPE_LINE);
+                }
+                rrddim_add(cg->st_queued_ops, "read", NULL, 1, 1, RRDDIM_ABSOLUTE);
+                rrddim_add(cg->st_queued_ops, "write", NULL, -1, 1, RRDDIM_ABSOLUTE);
             }
-            else rrdset_next(st);
+            else
+                rrdset_next(cg->st_queued_ops);
 
-            rrddim_set(st, "read", cg->io_queued.Read);
-            rrddim_set(st, "write", cg->io_queued.Write);
-            rrdset_done(st);
+            rrddim_set(cg->st_queued_ops, "read", cg->io_queued.Read);
+            rrddim_set(cg->st_queued_ops, "write", cg->io_queued.Write);
+            rrdset_done(cg->st_queued_ops);
         }
 
-        if(cg->io_merged.updated && cg->io_merged.Read + cg->io_merged.Write > 0) {
-            st = rrdset_find_bytype(type, "merged_ops");
-            if(!st) {
-                snprintfz(title, CHART_TITLE_MAX, "Merged I/O Operations (all disks) for cgroup %s", cg->chart_title);
-                st = rrdset_create(type, "merged_ops", NULL, "disk", "cgroup.merged_ops", title, "operations/s", 42100,
-                                   update_every, RRDSET_TYPE_LINE);
-
-                rrddim_add(st, "read", NULL, 1, 1024, RRDDIM_INCREMENTAL);
-                rrddim_add(st, "write", NULL, -1, 1024, RRDDIM_INCREMENTAL);
+        if(likely(cg->io_merged.updated && cg->io_merged.enabled == CONFIG_ONDEMAND_YES)) {
+            if(unlikely(!cg->st_merged_ops)) {
+                cg->st_merged_ops = rrdset_find_bytype(cgroup_chart_type(type, cg->chart_id, RRD_ID_LENGTH_MAX), "merged_ops");
+                if(likely(!cg->st_merged_ops)) {
+                    snprintfz(title, CHART_TITLE_MAX, "Merged I/O Operations (all disks) for cgroup %s", cg->chart_title);
+                    cg->st_merged_ops = rrdset_create(type, "merged_ops", NULL, "disk", "cgroup.merged_ops", title, "operations/s", CHART_PRIORITY_CONTAINERS + 2100, update_every, RRDSET_TYPE_LINE);
+                }
+                rrddim_add(cg->st_merged_ops, "read", NULL, 1, 1024, RRDDIM_INCREMENTAL);
+                rrddim_add(cg->st_merged_ops, "write", NULL, -1, 1024, RRDDIM_INCREMENTAL);
             }
-            else rrdset_next(st);
+            else
+                rrdset_next(cg->st_merged_ops);
 
-            rrddim_set(st, "read", cg->io_merged.Read);
-            rrddim_set(st, "write", cg->io_merged.Write);
-            rrdset_done(st);
+            rrddim_set(cg->st_merged_ops, "read", cg->io_merged.Read);
+            rrddim_set(cg->st_merged_ops, "write", cg->io_merged.Write);
+            rrdset_done(cg->st_merged_ops);
         }
     }
 
+    if(likely(cgroup_enable_systemd_services))
+        update_services_charts(update_every,
+                services_do_cpu,
+                services_do_mem_usage,
+                services_do_mem_detailed,
+                services_do_mem_failcnt,
+                services_do_swap_usage,
+                services_do_io,
+                services_do_io_ops,
+                services_do_throttle_io,
+                services_do_throttle_ops,
+                services_do_queued_ops,
+                services_do_merged_ops
+        );
+
     debug(D_CGROUP, "done updating cgroups charts");
 }
 
 // ----------------------------------------------------------------------------
 // cgroups main
 
-int do_sys_fs_cgroup(int update_every, usec_t dt) {
-    (void)dt;
-
-    static int cgroup_global_config_read = 0;
-    static time_t last_run = 0;
-    time_t now = now_realtime_sec();
-
-    if(unlikely(!cgroup_global_config_read)) {
-        read_cgroup_plugin_configuration();
-        cgroup_global_config_read = 1;
-    }
-
-    if(unlikely(cgroup_enable_new_cgroups_detected_at_runtime && now - last_run > cgroup_check_for_new_every)) {
-        find_all_cgroups();
-        last_run = now;
-    }
-
-    read_all_cgroups(cgroup_root);
-    update_cgroup_charts(update_every);
-
-    return 0;
-}
-
-void *cgroups_main(void *ptr)
-{
-    (void)ptr;
+void *cgroups_main(void *ptr) {
+    struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr;
 
     info("CGROUP Plugin thread created with task id %d", gettid());
 
@@ -1442,56 +2300,52 @@ void *cgroups_main(void *ptr)
     struct rusage thread;
 
     // when ZERO, attempt to do it
-    int vdo_sys_fs_cgroup           = 0;
-    int vdo_cpu_netdata             = !config_get_boolean("plugin:cgroups", "cgroups plugin resources", 1);
-
-    // keep track of the time each module was called
-    usec_t sutime_sys_fs_cgroup = 0ULL;
+    int vdo_cpu_netdata = config_get_boolean("plugin:cgroups", "cgroups plugin resource charts", 1);
 
-    // the next time we will run - aligned properly
-    usec_t sunext = (now_realtime_sec() - (now_realtime_sec() % rrd_update_every) + rrd_update_every) * USEC_PER_SEC;
+    read_cgroup_plugin_configuration();
 
     RRDSET *stcpu_thread = NULL;
 
+    usec_t step = cgroup_update_every * USEC_PER_SEC;
+    usec_t find_every = cgroup_check_for_new_every * USEC_PER_SEC, find_next = 0;
     for(;;) {
-        usec_t sunow;
-        if(unlikely(netdata_exit)) break;
-
-        // delay until it is our time to run
-        while((sunow = now_realtime_usec()) < sunext)
-            sleep_usec(sunext - sunow);
+        usec_t now = now_monotonic_usec();
+        usec_t next = now - (now % step) + step;
 
-        // find the next time we need to run
-        while(now_realtime_usec() > sunext)
-            sunext += rrd_update_every * USEC_PER_SEC;
+        while(now < next) {
+            sleep_usec(next - now);
+            now = now_monotonic_usec();
+        }
 
         if(unlikely(netdata_exit)) break;
 
         // BEGIN -- the job to be done
 
-        if(!vdo_sys_fs_cgroup) {
-            debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_sys_fs_cgroup().");
-            sunow = now_realtime_usec();
-            vdo_sys_fs_cgroup = do_sys_fs_cgroup(rrd_update_every, (sutime_sys_fs_cgroup > 0)?sunow - sutime_sys_fs_cgroup:0ULL);
-            sutime_sys_fs_cgroup = sunow;
+        if(unlikely(now >= find_next)) {
+            find_all_cgroups();
+            find_next = now + find_every;
         }
-        if(unlikely(netdata_exit)) break;
+
+        read_all_cgroups(cgroup_root);
+        update_cgroup_charts(cgroup_update_every);
 
         // END -- the job is done
 
         // --------------------------------------------------------------------
 
-        if(!vdo_cpu_netdata) {
+        if(vdo_cpu_netdata) {
             getrusage(RUSAGE_THREAD, &thread);
 
-            if(!stcpu_thread) stcpu_thread = rrdset_find("netdata.plugin_cgroups_cpu");
-            if(!stcpu_thread) {
-                stcpu_thread = rrdset_create("netdata", "plugin_cgroups_cpu", NULL, "proc.internal", NULL, "NetData CGroups Plugin CPU usage", "milliseconds/s", 132000, rrd_update_every, RRDSET_TYPE_STACKED);
+            if(unlikely(!stcpu_thread)) {
+                stcpu_thread = rrdset_find("netdata.plugin_cgroups_cpu");
+                if(unlikely(!stcpu_thread))
+                    stcpu_thread = rrdset_create("netdata", "plugin_cgroups_cpu", NULL, "cgroups", NULL, "NetData CGroups Plugin CPU usage", "milliseconds/s", 132000, cgroup_update_every, RRDSET_TYPE_STACKED);
 
                 rrddim_add(stcpu_thread, "user",  NULL,  1, 1000, RRDDIM_INCREMENTAL);
                 rrddim_add(stcpu_thread, "system", NULL, 1, 1000, RRDDIM_INCREMENTAL);
             }
-            else rrdset_next(stcpu_thread);
+            else
+                rrdset_next(stcpu_thread);
 
             rrddim_set(stcpu_thread, "user"  , thread.ru_utime.tv_sec * 1000000ULL + thread.ru_utime.tv_usec);
             rrddim_set(stcpu_thread, "system", thread.ru_stime.tv_sec * 1000000ULL + thread.ru_stime.tv_usec);
@@ -1501,6 +2355,7 @@ void *cgroups_main(void *ptr)
 
     info("CGROUP thread exiting");
 
+    static_thread->enabled = 0;
     pthread_exit(NULL);
     return NULL;
 }
index f7bf2e5a6bfac4514f36db57884e9b87a7786d80..2765767f6738ce98db67642085d3115da1246937 100644 (file)
@@ -2097,7 +2097,6 @@ void web_client_process(struct web_client *w) {
 
                     error("web request to exit received.");
                     netdata_cleanup_and_exit(0);
-                    netdata_exit = 1;
                 }
                 else if(hash == hash_debug && strcmp(tok, "debug") == 0) {
                     buffer_flush(w->response.data);
index b0e26283384f85e6a80480494273989dbab41496..8e942a59d549e8dc380447de1f2f5f1753fa1305 100644 (file)
@@ -390,7 +390,7 @@ static inline void cleanup_web_clients(void) {
 #define CLEANUP_EVERY_EVENTS 100
 
 void *socket_listen_main_multi_threaded(void *ptr) {
-    (void)ptr;
+    struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr;
 
     web_server_mode = WEB_SERVER_MODE_MULTI_THREADED;
     info("Multi-threaded WEB SERVER thread created with task id %d", gettid());
@@ -470,6 +470,10 @@ void *socket_listen_main_multi_threaded(void *ptr) {
     debug(D_WEB_CLIENT, "LISTENER: exit!");
     close_listen_sockets();
 
+    freez(fds);
+
+    static_thread->enabled = 0;
+    pthread_exit(NULL);
     return NULL;
 }
 
@@ -518,7 +522,7 @@ static inline int single_threaded_unlink_client(struct web_client *w, fd_set *if
 }
 
 void *socket_listen_main_single_threaded(void *ptr) {
-    (void)ptr;
+    struct netdata_static_thread *static_thread = (struct netdata_static_thread *)ptr;
 
     web_server_mode = WEB_SERVER_MODE_SINGLE_THREADED;
 
@@ -637,5 +641,8 @@ void *socket_listen_main_single_threaded(void *ptr) {
 
     debug(D_WEB_CLIENT, "LISTENER: exit!");
     close_listen_sockets();
+
+    static_thread->enabled = 0;
+    pthread_exit(NULL);
     return NULL;
 }
index 5bf42ff8b8fbfd2eded57b33675380f5b6481bcd..6fc294204249e0f3cd331a3b1c8f350b1f8cfeb0 100644 (file)
@@ -4336,6 +4336,8 @@ var NETDATA = window.NETDATA || {};
                         var series = data.series[i];
                         if(series.isVisible === true)
                             state.legendSetLabelValue(series.label, series.y);
+                        else
+                            state.legendSetLabelValue(series.label, null);
                     }
                 }
 
index b103bbc99ad768cfda501667b179702ddd3c7f42..bb5f2d02311a93a62f57f1ffdb294704ef76369d 100644 (file)
@@ -11,6 +11,12 @@ netdataDashboard.menu = {
         info: 'Overview of the key system metrics.'
     },
 
+    'services': {
+        title: 'Systemd Services',
+        icon: '<i class="fa fa-cogs" aria-hidden="true"></i>',
+        info: 'Resources utilization of systemd services.'
+    },
+
     'ap': {
         title: 'Access Points',
         icon: '<i class="fa fa-wifi" aria-hidden="true"></i>',
index b0a6c957d35b49ffd3691a6cdb6c6a48e5c45d42..9d8a2455fc13a069719776c1d9d03ad4d03a7a21 100644 (file)
             });
 
             NETDATA.requiredJs.push({
-                url: NETDATA.serverDefault + 'dashboard_info.js?v20161226-1',
+                url: NETDATA.serverDefault + 'dashboard_info.js?v20170115-1',
                 async: false,
                 isAlreadyLoaded: function() { return false; }
             });
     </div>
 </body>
 </html>
-<script type="text/javascript" src="dashboard.js?v20170107-5"></script>
+<script type="text/javascript" src="dashboard.js?v20170118-11"></script>