]> arthur.barton.de Git - netdata.git/commitdiff
Merge pull request #308 from ktsaou/master
authorCosta Tsaousis <costa@tsaousis.gr>
Mon, 25 Apr 2016 19:53:03 +0000 (22:53 +0300)
committerCosta Tsaousis <costa@tsaousis.gr>
Mon, 25 Apr 2016 19:53:03 +0000 (22:53 +0300)
cgroups / lxc / docker performance monitoring

14 files changed:
CMakeLists.txt
netdata-installer.sh
plugins.d/Makefile.am
plugins.d/cgroup-name.sh [new file with mode: 0755]
src/Makefile.am
src/apps_plugin.c
src/common.c
src/common.h
src/log.c
src/log.h
src/main.c
src/plugin_proc.c
src/sys_fs_cgroup.c [new file with mode: 0644]
web/index.html

index 251461003822cd7f6cfea7d9a8783169ef28c730..1c42a9138cc7beca15fa03b0cf323b90399bd622 100755 (executable)
@@ -64,6 +64,7 @@ set(NETDATA_SOURCE_FILES
        src/storage_number.c
        src/storage_number.h
        src/sys_kernel_mm_ksm.c
+       src/sys_fs_cgroup.c
        src/unit_test.c
        src/unit_test.h
        src/url.c
index 4a3012a40f1519f6113d3b51c94f8a46a622d5da..8401b2d0e3543c398986f7c9f66d4535abfa7e6a 100755 (executable)
@@ -134,7 +134,7 @@ if [ "${UID}" -ne 0 ]
                then
                cat <<NONROOTNOPREFIX
 
-Sorry! This will wrong!
+Sorry! This will fail!
 
 You are attempting to install netdata as non-root, but you plan to install it
 in system paths.
@@ -415,7 +415,7 @@ do
                fi
        fi
 
-       run chmod 0775 "${x}" || echo >&2 "WARNING: Cannot change the permissions of the directory ${x} to 0755..."
+       run chmod 0755 "${x}" || echo >&2 "WARNING: Cannot change the permissions of the directory ${x} to 0755..."
 done
 
 if [ ${UID} -eq 0 ]
index a89ee4cdd03968868785cfce4d0cc9ead742444f..a717cbed101da670936e29093aa43658892b6fe8 100644 (file)
@@ -8,6 +8,7 @@ dist_plugins_DATA = \
        $(NULL)
 
 dist_plugins_SCRIPTS = \
+       cgroup-name.sh \
        charts.d.dryrun-helper.sh \
        charts.d.plugin \
        node.d.plugin \
diff --git a/plugins.d/cgroup-name.sh b/plugins.d/cgroup-name.sh
new file mode 100755 (executable)
index 0000000..f0a8959
--- /dev/null
@@ -0,0 +1,44 @@
+#!/bin/bash
+
+export PATH="${PATH}:/sbin:/usr/sbin:/usr/local/sbin"
+
+NETDATA_CONFIG_DIR="${NETDATA_CONFIG_DIR-/etc/netdata}"
+CONFIG="${NETDATA_CONFIG_DIR}/cgroups-names.conf"
+CGROUP="${1}"
+NAME=
+
+if [ -z "${CGROUP}" ]
+       then
+       echo >&2 "${0}: called without a cgroup name. Nothing to do."
+       exit 1
+fi
+
+if [ -f "${CONFIG}" ]
+       then
+       NAME="$(cat "${CONFIG}" | grep "^${CGROUP} " | sed "s/[[:space:]]\+/ /g" | cut -d ' ' -f 2)"
+       if [ -z "${NAME}" ]
+               then
+               echo >&2 "${0}: cannot find cgroup '${CGROUP}' in '${CONFIG}'."
+       fi
+else
+       echo >&2 "${0}: configuration file '${CONFIG}' is not available."
+fi
+
+if [ -z "${NAME}" -a "${CGROUP:0:7}" = "docker/" ]
+       then
+       NAME="$(docker ps | grep "^${CGROUP:7:12}" | sed "s/[[:space:]]\+/ /g" | cut -d ' ' -f 2)"
+       [ -z "${NAME}" ] && NAME="${CGROUP:0:19}"
+fi
+
+if [ -z "${NAME}" ]
+       then
+       if [ ${#CGROUP} -gt 20 ]
+               then
+               NAME="${CGROUP:0:20}"
+       else
+               NAME="${CGROUP}"
+       fi
+fi
+
+echo >&2 "${0}: cgroup '${CGROUP}' is named as '${NAME}'"
+echo "${NAME}"
index a6808f4242570197ff7af75fc0ec0061ac6be149..69c5e550cc645bff491aab803ec27e02c5dd13f1 100644 (file)
@@ -55,6 +55,7 @@ netdata_SOURCES = \
        proc_sys_kernel_random_entropy_avail.c \
        proc_vmstat.c \
        sys_kernel_mm_ksm.c \
+       sys_fs_cgroup.c \
        procfile.c procfile.h \
        rrd.c rrd.h \
        rrd2json.c rrd2json.h \
index e8a6f43aefe55a0bcc77d714b0c89f831016b042..b6396bb27e5f0a8bcc5c000e474dbfdd02db85a6 100644 (file)
@@ -43,8 +43,6 @@
 #define MAX_NAME 100
 #define MAX_CMDLINE 1024
 
-unsigned long long Hertz = 1;
-
 long processors = 1;
 long pid_max = 32768;
 int debug = 0;
@@ -267,28 +265,6 @@ long get_system_pid_max(void) {
        return mpid;
 }
 
-unsigned long long get_system_hertz(void)
-{
-       unsigned long long myhz = 1;
-
-#ifdef _SC_CLK_TCK
-       if((myhz = (unsigned long long int) sysconf(_SC_CLK_TCK)) > 0) {
-               return myhz;
-       }
-#endif
-
-#ifdef HZ
-       myhz = HZ;    /* <asm/param.h> */
-#else /* HZ */
-       /* If 32-bit or big-endian (not Alpha or ia64), assume HZ is 100. */
-       hz = (sizeof(long)==sizeof(int) || htons(999)==999) ? 100UL : 1024UL;
-#endif /* HZ */
-
-       error("Unknown HZ value. Assuming %llu.", myhz);
-       return myhz;
-}
-
-
 // ----------------------------------------------------------------------------
 // target
 // target is the structure that process data are aggregated
@@ -410,7 +386,7 @@ struct target *get_users_target(uid_t uid)
        else
                snprintf(w->name, MAX_NAME, "%s", pw->pw_name);
 
-       netdata_fix_id(w->name);
+       netdata_fix_chart_name(w->name);
 
        w->uid = uid;
 
@@ -448,7 +424,7 @@ struct target *get_groups_target(gid_t gid)
        else
                snprintf(w->name, MAX_NAME, "%s", gr->gr_name);
 
-       netdata_fix_id(w->name);
+       netdata_fix_chart_name(w->name);
 
        w->gid = gid;
 
@@ -2267,7 +2243,7 @@ void send_charts_updates_to_netdata(struct target *root, const char *type, const
        for (w = root; w ; w = w->next) {
                if(w->target || (!w->processes && !w->exposed)) continue;
 
-               fprintf(stdout, "DIMENSION %s '' incremental 100 %llu %s\n", w->name, Hertz, w->hidden ? "hidden,noreset" : "noreset");
+               fprintf(stdout, "DIMENSION %s '' incremental 100 %u %s\n", w->name, hz, w->hidden ? "hidden,noreset" : "noreset");
        }
 
        fprintf(stdout, "CHART %s.mem '' '%s Dedicated Memory (w/o shared)' 'MB' mem %s.mem stacked 20003 %d\n", type, title, type, update_every);
@@ -2295,14 +2271,14 @@ void send_charts_updates_to_netdata(struct target *root, const char *type, const
        for (w = root; w ; w = w->next) {
                if(w->target || (!w->processes && !w->exposed)) continue;
 
-               fprintf(stdout, "DIMENSION %s '' incremental 100 %llu noreset\n", w->name, Hertz * processors);
+               fprintf(stdout, "DIMENSION %s '' incremental 100 %ld noreset\n", w->name, hz * processors);
        }
 
        fprintf(stdout, "CHART %s.cpu_system '' '%s CPU System Time (%ld%% = %ld core%s)' 'cpu time %%' cpu %s.cpu_system stacked 20021 %d\n", type, title, (processors * 100), processors, (processors>1)?"s":"", type, update_every);
        for (w = root; w ; w = w->next) {
                if(w->target || (!w->processes && !w->exposed)) continue;
 
-               fprintf(stdout, "DIMENSION %s '' incremental 100 %llu noreset\n", w->name, Hertz * processors);
+               fprintf(stdout, "DIMENSION %s '' incremental 100 %ld noreset\n", w->name, hz * processors);
        }
 
        fprintf(stdout, "CHART %s.major_faults '' '%s Major Page Faults (swap read)' 'page faults/s' swap %s.major_faults stacked 20010 %d\n", type, title, type, update_every);
@@ -2411,12 +2387,6 @@ void parse_args(int argc, char **argv)
        }
 }
 
-unsigned long long sutime() {
-       struct timeval now;
-       gettimeofday(&now, NULL);
-       return now.tv_sec * 1000000ULL + now.tv_usec;
-}
-
 int main(int argc, char **argv)
 {
        // debug_flags = D_PROCFILE;
@@ -2427,6 +2397,10 @@ int main(int argc, char **argv)
        // disable syslog for apps.plugin
        error_log_syslog = 0;
 
+       // set errors flood protection to 100 logs per hour
+       error_log_errors_per_period = 100;
+       error_log_throttle_period = 3600;
+
        host_prefix = getenv("NETDATA_HOST_PREFIX");
        if(host_prefix == NULL) {
                info("NETDATA_HOST_PREFIX is not passed from netdata");
@@ -2447,7 +2421,7 @@ int main(int argc, char **argv)
 
        time_t started_t = time(NULL);
        time_t current_t;
-       Hertz = get_system_hertz();
+       get_HZ();
        pid_max = get_system_pid_max();
        processors = get_system_cpus();
 
@@ -2480,11 +2454,11 @@ int main(int argc, char **argv)
        for(;1; counter++) {
 #ifndef PROFILING_MODE
                // delay until it is our time to run
-               while((sunow = sutime()) < sunext)
+               while((sunow = timems()) < sunext)
                        usleep((useconds_t)(sunext - sunow));
 
                // find the next time we need to run
-               while(sutime() > sunext)
+               while(timems() > sunext)
                        sunext += update_every * 1000000ULL;
 #endif /* PROFILING_MODE */
 
index cb74b6335c2d0dbd65d3af7dfea63b1cffb6e2a1..ed04fdb91f6a1c743c818994a66044271f4e9865 100644 (file)
 char *global_host_prefix = "";
 int enable_ksm = 1;
 
-unsigned char netdata_keys_map[256] = {
+// time(NULL) in milliseconds
+unsigned long long timems(void) {
+       struct timeval now;
+       gettimeofday(&now, NULL);
+       return now.tv_sec * 1000000ULL + now.tv_usec;
+}
+
+unsigned char netdata_map_chart_names[256] = {
                [0] = '\0', //
                [1] = '_', //
                [2] = '_', //
@@ -281,8 +288,273 @@ unsigned char netdata_keys_map[256] = {
 
 // make sure the supplied string
 // is good for a netdata chart/dimension ID/NAME
-void netdata_fix_id(char *s) {
-       while((*s = netdata_keys_map[(unsigned char)*s])) s++;
+void netdata_fix_chart_name(char *s) {
+       while((*s = netdata_map_chart_names[(unsigned char)*s])) s++;
+}
+
+unsigned char netdata_map_chart_ids[256] = {
+               [0] = '\0', //
+               [1] = '_', //
+               [2] = '_', //
+               [3] = '_', //
+               [4] = '_', //
+               [5] = '_', //
+               [6] = '_', //
+               [7] = '_', //
+               [8] = '_', //
+               [9] = '_', //
+               [10] = '_', //
+               [11] = '_', //
+               [12] = '_', //
+               [13] = '_', //
+               [14] = '_', //
+               [15] = '_', //
+               [16] = '_', //
+               [17] = '_', //
+               [18] = '_', //
+               [19] = '_', //
+               [20] = '_', //
+               [21] = '_', //
+               [22] = '_', //
+               [23] = '_', //
+               [24] = '_', //
+               [25] = '_', //
+               [26] = '_', //
+               [27] = '_', //
+               [28] = '_', //
+               [29] = '_', //
+               [30] = '_', //
+               [31] = '_', //
+               [32] = '_', //
+               [33] = '_', // !
+               [34] = '_', // "
+               [35] = '_', // #
+               [36] = '_', // $
+               [37] = '_', // %
+               [38] = '_', // &
+               [39] = '_', // '
+               [40] = '_', // (
+               [41] = '_', // )
+               [42] = '_', // *
+               [43] = '_', // +
+               [44] = '.', // ,
+               [45] = '-', // -
+               [46] = '.', // .
+               [47] = '_', // /
+               [48] = '0', // 0
+               [49] = '1', // 1
+               [50] = '2', // 2
+               [51] = '3', // 3
+               [52] = '4', // 4
+               [53] = '5', // 5
+               [54] = '6', // 6
+               [55] = '7', // 7
+               [56] = '8', // 8
+               [57] = '9', // 9
+               [58] = '_', // :
+               [59] = '_', // ;
+               [60] = '_', // <
+               [61] = '_', // =
+               [62] = '_', // >
+               [63] = '_', // ?
+               [64] = '_', // @
+               [65] = 'a', // A
+               [66] = 'b', // B
+               [67] = 'c', // C
+               [68] = 'd', // D
+               [69] = 'e', // E
+               [70] = 'f', // F
+               [71] = 'g', // G
+               [72] = 'h', // H
+               [73] = 'i', // I
+               [74] = 'j', // J
+               [75] = 'k', // K
+               [76] = 'l', // L
+               [77] = 'm', // M
+               [78] = 'n', // N
+               [79] = 'o', // O
+               [80] = 'p', // P
+               [81] = 'q', // Q
+               [82] = 'r', // R
+               [83] = 's', // S
+               [84] = 't', // T
+               [85] = 'u', // U
+               [86] = 'v', // V
+               [87] = 'w', // W
+               [88] = 'x', // X
+               [89] = 'y', // Y
+               [90] = 'z', // Z
+               [91] = '_', // [
+               [92] = '/', // backslash
+               [93] = '_', // ]
+               [94] = '_', // ^
+               [95] = '_', // _
+               [96] = '_', // `
+               [97] = 'a', // a
+               [98] = 'b', // b
+               [99] = 'c', // c
+               [100] = 'd', // d
+               [101] = 'e', // e
+               [102] = 'f', // f
+               [103] = 'g', // g
+               [104] = 'h', // h
+               [105] = 'i', // i
+               [106] = 'j', // j
+               [107] = 'k', // k
+               [108] = 'l', // l
+               [109] = 'm', // m
+               [110] = 'n', // n
+               [111] = 'o', // o
+               [112] = 'p', // p
+               [113] = 'q', // q
+               [114] = 'r', // r
+               [115] = 's', // s
+               [116] = 't', // t
+               [117] = 'u', // u
+               [118] = 'v', // v
+               [119] = 'w', // w
+               [120] = 'x', // x
+               [121] = 'y', // y
+               [122] = 'z', // z
+               [123] = '_', // {
+               [124] = '_', // |
+               [125] = '_', // }
+               [126] = '_', // ~
+               [127] = '_', //
+               [128] = '_', //
+               [129] = '_', //
+               [130] = '_', //
+               [131] = '_', //
+               [132] = '_', //
+               [133] = '_', //
+               [134] = '_', //
+               [135] = '_', //
+               [136] = '_', //
+               [137] = '_', //
+               [138] = '_', //
+               [139] = '_', //
+               [140] = '_', //
+               [141] = '_', //
+               [142] = '_', //
+               [143] = '_', //
+               [144] = '_', //
+               [145] = '_', //
+               [146] = '_', //
+               [147] = '_', //
+               [148] = '_', //
+               [149] = '_', //
+               [150] = '_', //
+               [151] = '_', //
+               [152] = '_', //
+               [153] = '_', //
+               [154] = '_', //
+               [155] = '_', //
+               [156] = '_', //
+               [157] = '_', //
+               [158] = '_', //
+               [159] = '_', //
+               [160] = '_', //
+               [161] = '_', //
+               [162] = '_', //
+               [163] = '_', //
+               [164] = '_', //
+               [165] = '_', //
+               [166] = '_', //
+               [167] = '_', //
+               [168] = '_', //
+               [169] = '_', //
+               [170] = '_', //
+               [171] = '_', //
+               [172] = '_', //
+               [173] = '_', //
+               [174] = '_', //
+               [175] = '_', //
+               [176] = '_', //
+               [177] = '_', //
+               [178] = '_', //
+               [179] = '_', //
+               [180] = '_', //
+               [181] = '_', //
+               [182] = '_', //
+               [183] = '_', //
+               [184] = '_', //
+               [185] = '_', //
+               [186] = '_', //
+               [187] = '_', //
+               [188] = '_', //
+               [189] = '_', //
+               [190] = '_', //
+               [191] = '_', //
+               [192] = '_', //
+               [193] = '_', //
+               [194] = '_', //
+               [195] = '_', //
+               [196] = '_', //
+               [197] = '_', //
+               [198] = '_', //
+               [199] = '_', //
+               [200] = '_', //
+               [201] = '_', //
+               [202] = '_', //
+               [203] = '_', //
+               [204] = '_', //
+               [205] = '_', //
+               [206] = '_', //
+               [207] = '_', //
+               [208] = '_', //
+               [209] = '_', //
+               [210] = '_', //
+               [211] = '_', //
+               [212] = '_', //
+               [213] = '_', //
+               [214] = '_', //
+               [215] = '_', //
+               [216] = '_', //
+               [217] = '_', //
+               [218] = '_', //
+               [219] = '_', //
+               [220] = '_', //
+               [221] = '_', //
+               [222] = '_', //
+               [223] = '_', //
+               [224] = '_', //
+               [225] = '_', //
+               [226] = '_', //
+               [227] = '_', //
+               [228] = '_', //
+               [229] = '_', //
+               [230] = '_', //
+               [231] = '_', //
+               [232] = '_', //
+               [233] = '_', //
+               [234] = '_', //
+               [235] = '_', //
+               [236] = '_', //
+               [237] = '_', //
+               [238] = '_', //
+               [239] = '_', //
+               [240] = '_', //
+               [241] = '_', //
+               [242] = '_', //
+               [243] = '_', //
+               [244] = '_', //
+               [245] = '_', //
+               [246] = '_', //
+               [247] = '_', //
+               [248] = '_', //
+               [249] = '_', //
+               [250] = '_', //
+               [251] = '_', //
+               [252] = '_', //
+               [253] = '_', //
+               [254] = '_', //
+               [255] = '_'  //
+};
+
+// make sure the supplied string
+// is good for a netdata chart/dimension ID/NAME
+void netdata_fix_chart_id(char *s) {
+       while((*s = netdata_map_chart_ids[(unsigned char)*s])) s++;
 }
 
 /*
index e9987af72936eb1b08f9fd464b1ca84ee6922f3d..a028da7c52ca82b2677bc3a47a76f15917a81b9c 100644 (file)
@@ -15,7 +15,8 @@
 #define abs(x) ((x < 0)? -x : x)
 #define usecdiff(now, last) (((((now)->tv_sec * 1000000ULL) + (now)->tv_usec) - (((last)->tv_sec * 1000000ULL) + (last)->tv_usec)))
 
-extern void netdata_fix_id(char *s);
+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 void strreverse(char* begin, char* end);
@@ -31,12 +32,13 @@ extern char *global_host_prefix;
 extern int enable_ksm;
 
 /* Number of ticks per second */
-#define HZ        myhz
 extern unsigned int hz;
 extern void get_HZ(void);
 
 extern pid_t gettid(void);
 
+extern unsigned long long timems(void);
+
 /* fix for alpine linux */
 #ifndef RUSAGE_THREAD
 #ifdef RUSAGE_CHILDREN
index e5e0ad2c49a8d48e22225f8680d7a737e4bed5d2..2db0c662e3bad87442147222da47d081ff4e55e3 100644 (file)
--- a/src/log.c
+++ b/src/log.c
@@ -131,6 +131,7 @@ void debug_int( const char *file, const char *function, const unsigned long line
        vfprintf( stdout, fmt, args );
        va_end( args );
        fprintf(stdout, "\n");
+       fflush( stdout );
 
        if(output_log_syslog) {
                va_start( args, fmt );
index 0762621006916b45a8233992065d6754031e2e71..1607826e6748b1ac81d226e2056772c3538d2257 100644 (file)
--- a/src/log.h
+++ b/src/log.h
@@ -25,6 +25,7 @@
 #define D_RRD_CALLS                    0x00020000
 #define D_DICTIONARY           0x00040000
 #define D_MEMORY                       0x00080000
+#define D_CGROUP            0x00100000
 
 //#define DEBUG (D_WEB_CLIENT_ACCESS|D_LISTENER|D_RRD_STATS)
 //#define DEBUG 0xffffffff
index ad24debfaba419a648130074076f70b5e49771b6..ba02371ff8948cbe1081165201ca3584b8c81a22 100644 (file)
@@ -36,7 +36,8 @@
 #include "plugin_nfacct.h"
 
 #include "main.h"
-#include "../config.h"
+
+extern void *cgroups_main(void *ptr);
 
 int netdata_exit = 0;
 
@@ -84,6 +85,7 @@ struct netdata_static_thread static_threads[] = {
        {"tc",                  "plugins",      "tc",                   1, NULL, NULL,  tc_main},
        {"idlejitter",  "plugins",      "idlejitter",   1, NULL, NULL,  cpuidlejitter_main},
        {"proc",                "plugins",      "proc",                 1, NULL, NULL,  proc_main},
+       {"cgroups",             "plugins",      "cgroups",              1, NULL, NULL,  cgroups_main},
 
 #ifdef INTERNAL_PLUGIN_NFACCT
        // nfacct requires root access
index 4cd20afc56a5f594ba61c6745dca97229e353336..632ec1e86c25c202993d8179b5f89e881a06b9f7 100644 (file)
 #include "plugin_proc.h"
 #include "main.h"
 
-unsigned long long sutime() {
-       struct timeval now;
-       gettimeofday(&now, NULL);
-       return now.tv_sec * 1000000ULL + now.tv_usec;
-}
-
 void *proc_main(void *ptr)
 {
        if(ptr) { ; }
@@ -88,11 +82,11 @@ void *proc_main(void *ptr)
                if(unlikely(netdata_exit)) break;
 
                // delay until it is our time to run
-               while((sunow = sutime()) < sunext)
+               while((sunow = timems()) < sunext)
                        usleep((useconds_t)(sunext - sunow));
 
                // find the next time we need to run
-               while(sutime() > sunext)
+               while(timems() > sunext)
                        sunext += rrd_update_every * 1000000ULL;
 
                if(unlikely(netdata_exit)) break;
@@ -102,7 +96,7 @@ void *proc_main(void *ptr)
                if(!vdo_sys_kernel_mm_ksm) {
                        debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_sys_kernel_mm_ksm().");
 
-                       sunow = sutime();
+                       sunow = timems();
                        vdo_sys_kernel_mm_ksm = do_sys_kernel_mm_ksm(rrd_update_every, (sutime_sys_kernel_mm_ksm > 0)?sunow - sutime_sys_kernel_mm_ksm:0ULL);
                        sutime_sys_kernel_mm_ksm = sunow;
                }
@@ -110,7 +104,7 @@ void *proc_main(void *ptr)
 
                if(!vdo_proc_loadavg) {
                        debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_loadavg().");
-                       sunow = sutime();
+                       sunow = timems();
                        vdo_proc_loadavg = do_proc_loadavg(rrd_update_every, (sutime_proc_loadavg > 0)?sunow - sutime_proc_loadavg:0ULL);
                        sutime_proc_loadavg = sunow;
                }
@@ -118,7 +112,7 @@ void *proc_main(void *ptr)
 
                if(!vdo_proc_interrupts) {
                        debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_interrupts().");
-                       sunow = sutime();
+                       sunow = timems();
                        vdo_proc_interrupts = do_proc_interrupts(rrd_update_every, (sutime_proc_interrupts > 0)?sunow - sutime_proc_interrupts:0ULL);
                        sutime_proc_interrupts = sunow;
                }
@@ -126,7 +120,7 @@ void *proc_main(void *ptr)
 
                if(!vdo_proc_softirqs) {
                        debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_softirqs().");
-                       sunow = sutime();
+                       sunow = timems();
                        vdo_proc_softirqs = do_proc_softirqs(rrd_update_every, (sutime_proc_softirqs > 0)?sunow - sutime_proc_softirqs:0ULL);
                        sutime_proc_softirqs = sunow;
                }
@@ -134,7 +128,7 @@ void *proc_main(void *ptr)
 
                if(!vdo_proc_sys_kernel_random_entropy_avail) {
                        debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_sys_kernel_random_entropy_avail().");
-                       sunow = sutime();
+                       sunow = timems();
                        vdo_proc_sys_kernel_random_entropy_avail = do_proc_sys_kernel_random_entropy_avail(rrd_update_every, (sutime_proc_sys_kernel_random_entropy_avail > 0)?sunow - sutime_proc_sys_kernel_random_entropy_avail:0ULL);
                        sutime_proc_sys_kernel_random_entropy_avail = sunow;
                }
@@ -142,7 +136,7 @@ void *proc_main(void *ptr)
 
                if(!vdo_proc_net_dev) {
                        debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_net_dev().");
-                       sunow = sutime();
+                       sunow = timems();
                        vdo_proc_net_dev = do_proc_net_dev(rrd_update_every, (sutime_proc_net_dev > 0)?sunow - sutime_proc_net_dev:0ULL);
                        sutime_proc_net_dev = sunow;
                }
@@ -150,7 +144,7 @@ void *proc_main(void *ptr)
 
                if(!vdo_proc_diskstats) {
                        debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_diskstats().");
-                       sunow = sutime();
+                       sunow = timems();
                        vdo_proc_diskstats = do_proc_diskstats(rrd_update_every, (sutime_proc_diskstats > 0)?sunow - sutime_proc_diskstats:0ULL);
                        sutime_proc_diskstats = sunow;
                }
@@ -158,7 +152,7 @@ void *proc_main(void *ptr)
 
                if(!vdo_proc_net_snmp) {
                        debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_net_snmp().");
-                       sunow = sutime();
+                       sunow = timems();
                        vdo_proc_net_snmp = do_proc_net_snmp(rrd_update_every, (sutime_proc_net_snmp > 0)?sunow - sutime_proc_net_snmp:0ULL);
                        sutime_proc_net_snmp = sunow;
                }
@@ -166,7 +160,7 @@ void *proc_main(void *ptr)
 
                if(!vdo_proc_net_snmp6) {
                        debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_net_snmp6().");
-                       sunow = sutime();
+                       sunow = timems();
                        vdo_proc_net_snmp6 = do_proc_net_snmp6(rrd_update_every, (sutime_proc_net_snmp6 > 0)?sunow - sutime_proc_net_snmp6:0ULL);
                        sutime_proc_net_snmp6 = sunow;
                }
@@ -174,7 +168,7 @@ void *proc_main(void *ptr)
 
                if(!vdo_proc_net_netstat) {
                        debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_net_netstat().");
-                       sunow = sutime();
+                       sunow = timems();
                        vdo_proc_net_netstat = do_proc_net_netstat(rrd_update_every, (sutime_proc_net_netstat > 0)?sunow - sutime_proc_net_netstat:0ULL);
                        sutime_proc_net_netstat = sunow;
                }
@@ -182,7 +176,7 @@ void *proc_main(void *ptr)
 
                if(!vdo_proc_net_stat_conntrack) {
                        debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_net_stat_conntrack().");
-                       sunow = sutime();
+                       sunow = timems();
                        vdo_proc_net_stat_conntrack     = do_proc_net_stat_conntrack(rrd_update_every, (sutime_proc_net_stat_conntrack > 0)?sunow - sutime_proc_net_stat_conntrack:0ULL);
                        sutime_proc_net_stat_conntrack = sunow;
                }
@@ -190,7 +184,7 @@ void *proc_main(void *ptr)
 
                if(!vdo_proc_net_ip_vs_stats) {
                        debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling vdo_proc_net_ip_vs_stats().");
-                       sunow = sutime();
+                       sunow = timems();
                        vdo_proc_net_ip_vs_stats = do_proc_net_ip_vs_stats(rrd_update_every, (sutime_proc_net_ip_vs_stats > 0)?sunow - sutime_proc_net_ip_vs_stats:0ULL);
                        sutime_proc_net_ip_vs_stats = sunow;
                }
@@ -198,7 +192,7 @@ void *proc_main(void *ptr)
 
                if(!vdo_proc_net_stat_synproxy) {
                        debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling vdo_proc_net_stat_synproxy().");
-                       sunow = sutime();
+                       sunow = timems();
                        vdo_proc_net_stat_synproxy = do_proc_net_stat_synproxy(rrd_update_every, (sutime_proc_net_stat_synproxy > 0)?sunow - sutime_proc_net_stat_synproxy:0ULL);
                        sutime_proc_net_stat_synproxy = sunow;
                }
@@ -206,7 +200,7 @@ void *proc_main(void *ptr)
 
                if(!vdo_proc_stat) {
                        debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_stat().");
-                       sunow = sutime();
+                       sunow = timems();
                        vdo_proc_stat = do_proc_stat(rrd_update_every, (sutime_proc_stat > 0)?sunow - sutime_proc_stat:0ULL);
                        sutime_proc_stat = sunow;
                }
@@ -214,7 +208,7 @@ void *proc_main(void *ptr)
 
                if(!vdo_proc_meminfo) {
                        debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling vdo_proc_meminfo().");
-                       sunow = sutime();
+                       sunow = timems();
                        vdo_proc_meminfo = do_proc_meminfo(rrd_update_every, (sutime_proc_meminfo > 0)?sunow - sutime_proc_meminfo:0ULL);
                        sutime_proc_meminfo = sunow;
                }
@@ -222,7 +216,7 @@ void *proc_main(void *ptr)
 
                if(!vdo_proc_vmstat) {
                        debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling vdo_proc_vmstat().");
-                       sunow = sutime();
+                       sunow = timems();
                        vdo_proc_vmstat = do_proc_vmstat(rrd_update_every, (sutime_proc_vmstat > 0)?sunow - sutime_proc_vmstat:0ULL);
                        sutime_proc_vmstat = sunow;
                }
@@ -230,7 +224,7 @@ void *proc_main(void *ptr)
 
                if(!vdo_proc_net_rpc_nfsd) {
                        debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_proc_net_rpc_nfsd().");
-                       sunow = sutime();
+                       sunow = timems();
                        vdo_proc_net_rpc_nfsd = do_proc_net_rpc_nfsd(rrd_update_every, (sutime_proc_net_rpc_nfsd > 0)?sunow - sutime_proc_net_rpc_nfsd:0ULL);
                        sutime_proc_net_rpc_nfsd = sunow;
                }
diff --git a/src/sys_fs_cgroup.c b/src/sys_fs_cgroup.c
new file mode 100644 (file)
index 0000000..d8d6ef0
--- /dev/null
@@ -0,0 +1,1230 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <string.h>
+
+#include "common.h"
+#include "appconfig.h"
+#include "procfile.h"
+#include "log.h"
+#include "rrd.h"
+#include "main.h"
+#include "popen.h"
+
+// ----------------------------------------------------------------------------
+// cgroup globals
+
+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_blkio = CONFIG_ONDEMAND_ONDEMAND;
+static int cgroup_enable_new_cgroups_detected_at_runtime = 1;
+static int cgroup_check_for_new_every = 10;
+static char *cgroup_cpuacct_base = NULL;
+static char *cgroup_blkio_base = NULL;
+static char *cgroup_memory_base = NULL;
+
+static int cgroup_root_count = 0;
+static int cgroup_root_max = 50;
+static int cgroup_max_depth = 0;
+
+void read_cgroup_plugin_configuration() {
+       cgroup_check_for_new_every = config_get_number("plugin:cgroups", "check for new plugin every", cgroup_check_for_new_every);
+
+       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);
+
+       char filename[FILENAME_MAX + 1];
+       snprintf(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/sys/fs/cgroup/cpuacct");
+       cgroup_cpuacct_base = config_get("plugin:cgroups", "path to /sys/fs/cgroup/cpuacct", filename);
+
+       snprintf(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/sys/fs/cgroup/blkio");
+       cgroup_blkio_base = config_get("plugin:cgroups", "path to /sys/fs/cgroup/blkio", filename);
+
+       snprintf(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/sys/fs/cgroup/memory");
+       cgroup_memory_base = config_get("plugin:cgroups", "path to /sys/fs/cgroup/memory", 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_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);
+}
+
+// ----------------------------------------------------------------------------
+// cgroup objects
+
+struct blkio {
+       int updated;
+
+       char *filename;
+
+       unsigned long long Read;
+       unsigned long long Write;
+/*
+       unsigned long long Sync;
+       unsigned long long Async;
+       unsigned long long Total;
+*/
+};
+
+// https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt
+struct memory {
+       int updated;
+
+       char *filename;
+
+       int has_dirty_swap;
+
+       unsigned long long cache;
+       unsigned long long rss;
+       unsigned long long rss_huge;
+       unsigned long long mapped_file;
+       unsigned long long writeback;
+       unsigned long long dirty;
+       unsigned long long swap;
+       unsigned long long pgpgin;
+       unsigned long long pgpgout;
+       unsigned long long pgfault;
+       unsigned long long pgmajfault;
+/*
+       unsigned long long inactive_anon;
+       unsigned long long active_anon;
+       unsigned long long inactive_file;
+       unsigned long long active_file;
+       unsigned long long unevictable;
+       unsigned long long hierarchical_memory_limit;
+       unsigned long long total_cache;
+       unsigned long long total_rss;
+       unsigned long long total_rss_huge;
+       unsigned long long total_mapped_file;
+       unsigned long long total_writeback;
+       unsigned long long total_dirty;
+       unsigned long long total_swap;
+       unsigned long long total_pgpgin;
+       unsigned long long total_pgpgout;
+       unsigned long long total_pgfault;
+       unsigned long long total_pgmajfault;
+       unsigned long long total_inactive_anon;
+       unsigned long long total_active_anon;
+       unsigned long long total_inactive_file;
+       unsigned long long total_active_file;
+       unsigned long long total_unevictable;
+*/
+};
+
+// https://www.kernel.org/doc/Documentation/cgroup-v1/cpuacct.txt
+struct cpuacct_stat {
+       int updated;
+
+       char *filename;
+
+       unsigned long long user;
+       unsigned long long system;
+};
+
+// https://www.kernel.org/doc/Documentation/cgroup-v1/cpuacct.txt
+struct cpuacct_usage {
+       int updated;
+
+       char *filename;
+
+       unsigned int cpus;
+       unsigned long long *cpu_percpu;
+};
+
+struct cgroup {
+       int available;
+       int enabled;
+
+       char *id;
+       uint32_t hash;
+
+       char *chart_id;
+
+       struct cpuacct_stat cpuacct_stat;
+       struct cpuacct_usage cpuacct_usage;
+
+       struct memory memory;
+
+       struct blkio io_service_bytes;                          // bytes
+       struct blkio io_serviced;                                       // operations
+
+       struct blkio throttle_io_service_bytes;         // bytes
+       struct blkio throttle_io_serviced;                      // operations
+
+       struct blkio io_merged;                                         // operations
+       struct blkio io_queued;                                         // operations
+
+       struct cgroup *next;
+
+} *cgroup_root = NULL;
+
+// ----------------------------------------------------------------------------
+// read values from /sys
+
+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) {
+               ff = procfile_reopen(ff, cp->filename, NULL, PROCFILE_FLAG_DEFAULT);
+               if(!ff) return;
+
+               ff = procfile_readall(ff);
+               if(!ff) return;
+
+               unsigned long i, lines = procfile_lines(ff);
+
+               if(lines < 1) {
+                       error("File '%s' should have 1+ lines.", cp->filename);
+                       return;
+               }
+
+               for(i = 0; i < lines ; i++) {
+                       char *s = procfile_lineword(ff, i, 0);
+                       uint32_t hash = simple_hash(s);
+
+                       if(hash == user_hash && !strcmp(s, "user"))
+                               cp->user = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+                       else if(hash == system_hash && !strcmp(s, "system"))
+                               cp->system = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+               }
+
+               cp->updated = 1;
+
+               // fprintf(stderr, "READ '%s': user: %llu, system: %llu\n", cp->filename, cp->user, cp->system);
+       }
+}
+
+void cgroup_read_cpuacct_usage(struct cpuacct_usage *ca) {
+       static procfile *ff = NULL;
+
+       ca->updated = 0;
+       if(ca->filename) {
+               ff = procfile_reopen(ff, ca->filename, NULL, PROCFILE_FLAG_DEFAULT);
+               if(!ff) return;
+
+               ff = procfile_readall(ff);
+               if(!ff) return;
+
+               if(procfile_lines(ff) < 1) {
+                       error("File '%s' should have 1+ lines but has %d.", ca->filename, procfile_lines(ff));
+                       return;
+               }
+
+               unsigned long i = procfile_linewords(ff, 0);
+               if(i <= 0) return;
+
+               // we may have 1 more CPU reported
+               while(i > 0) {
+                       char *s = procfile_lineword(ff, 0, i - 1);
+                       if(!*s) i--;
+                       else break;
+               }
+
+               if(i != ca->cpus) {
+                       free(ca->cpu_percpu);
+               }
+               ca->cpu_percpu = malloc(sizeof(unsigned long long) * i);
+               if(!ca->cpu_percpu)
+                       fatal("Cannot allocate memory (%z bytes)", sizeof(unsigned long long) * i);
+
+               ca->cpus = i;
+
+               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));
+               }
+
+               ca->updated = 1;
+       }
+}
+
+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");
+*/
+       }
+
+       io->updated = 0;
+       if(io->filename) {
+               ff = procfile_reopen(ff, io->filename, NULL, PROCFILE_FLAG_DEFAULT);
+               if(!ff) return;
+
+               ff = procfile_readall(ff);
+               if(!ff) return;
+
+               unsigned long i, lines = procfile_lines(ff);
+
+               if(lines < 1) {
+                       error("File '%s' should have 1+ lines.", io->filename);
+                       return;
+               }
+
+               io->Read = 0;
+               io->Write = 0;
+/*
+               io->Sync = 0;
+               io->Async = 0;
+               io->Total = 0;
+*/
+
+               for(i = 0; i < lines ; i++) {
+                       char *s = procfile_lineword(ff, i, 1);
+                       uint32_t hash = simple_hash(s);
+
+                       if(hash == Read_hash && !strcmp(s, "Read"))
+                               io->Read += strtoull(procfile_lineword(ff, i, 2), NULL, 10);
+
+                       else if(hash == Write_hash && !strcmp(s, "Write"))
+                               io->Write += strtoull(procfile_lineword(ff, i, 2), NULL, 10);
+
+/*
+                       else if(hash == Sync_hash && !strcmp(s, "Sync"))
+                               io->Sync += strtoull(procfile_lineword(ff, i, 2), NULL, 10);
+
+                       else if(hash == Async_hash && !strcmp(s, "Async"))
+                               io->Async += strtoull(procfile_lineword(ff, i, 2), NULL, 10);
+
+                       else if(hash == Total_hash && !strcmp(s, "Total"))
+                               io->Total += strtoull(procfile_lineword(ff, i, 2), NULL, 10);
+*/
+               }
+
+               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);
+       }
+}
+
+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");
+*/
+       }
+
+       mem->updated = 0;
+       if(mem->filename) {
+               ff = procfile_reopen(ff, mem->filename, NULL, PROCFILE_FLAG_DEFAULT);
+               if(!ff) return;
+
+               ff = procfile_readall(ff);
+               if(!ff) return;
+
+               unsigned long i, lines = procfile_lines(ff);
+
+               if(lines < 1) {
+                       error("File '%s' should have 1+ lines.", mem->filename);
+                       return;
+               }
+
+               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"))
+                               mem->cache = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+                       else if(hash == rss_hash && !strcmp(s, "rss"))
+                               mem->rss = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+                       else if(hash == rss_huge_hash && !strcmp(s, "rss_huge"))
+                               mem->rss_huge = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+                       else if(hash == mapped_file_hash && !strcmp(s, "mapped_file"))
+                               mem->mapped_file = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+                       else if(hash == writeback_hash && !strcmp(s, "writeback"))
+                               mem->writeback = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+                       else if(hash == dirty_hash && !strcmp(s, "dirty")) {
+                               mem->dirty = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                               mem->has_dirty_swap = 1;
+                       }
+
+                       else if(hash == swap_hash && !strcmp(s, "swap")) {
+                               mem->swap = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+                               mem->has_dirty_swap = 1;
+                       }
+
+                       else if(hash == pgpgin_hash && !strcmp(s, "pgpgin"))
+                               mem->pgpgin = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+                       else if(hash == pgpgout_hash && !strcmp(s, "pgpgout"))
+                               mem->pgpgout = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+                       else if(hash == pgfault_hash && !strcmp(s, "pgfault"))
+                               mem->pgfault = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+                       else if(hash == pgmajfault_hash && !strcmp(s, "pgmajfault"))
+                               mem->pgmajfault = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+/*
+                       else if(hash == inactive_anon_hash && !strcmp(s, "inactive_anon"))
+                               mem->inactive_anon = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+                       else if(hash == active_anon_hash && !strcmp(s, "active_anon"))
+                               mem->active_anon = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+                       else if(hash == inactive_file_hash && !strcmp(s, "inactive_file"))
+                               mem->inactive_file = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+                       else if(hash == active_file_hash && !strcmp(s, "active_file"))
+                               mem->active_file = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+                       else if(hash == unevictable_hash && !strcmp(s, "unevictable"))
+                               mem->unevictable = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+                       else if(hash == hierarchical_memory_limit_hash && !strcmp(s, "hierarchical_memory_limit"))
+                               mem->hierarchical_memory_limit = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+               else if(hash == total_cache_hash && !strcmp(s, "total_cache"))
+                               mem->total_cache = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+                       else if(hash == total_rss_hash && !strcmp(s, "total_rss"))
+                               mem->total_rss = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+                       else if(hash == total_rss_huge_hash && !strcmp(s, "total_rss_huge"))
+                               mem->total_rss_huge = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+                       else if(hash == total_mapped_file_hash && !strcmp(s, "total_mapped_file"))
+                               mem->total_mapped_file = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+                       else if(hash == total_writeback_hash && !strcmp(s, "total_writeback"))
+                               mem->total_writeback = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+                       else if(hash == total_dirty_hash && !strcmp(s, "total_dirty"))
+                               mem->total_dirty = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+                       else if(hash == total_swap_hash && !strcmp(s, "total_swap"))
+                               mem->total_swap = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+                       else if(hash == total_pgpgin_hash && !strcmp(s, "total_pgpgin"))
+                               mem->total_pgpgin = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+                       else if(hash == total_pgpgout_hash && !strcmp(s, "total_pgpgout"))
+                               mem->total_pgpgout = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+                       else if(hash == total_pgfault_hash && !strcmp(s, "total_pgfault"))
+                               mem->total_pgfault = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+                       else if(hash == total_pgmajfault_hash && !strcmp(s, "total_pgmajfault"))
+                               mem->total_pgmajfault = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+                       else if(hash == total_inactive_anon_hash && !strcmp(s, "total_inactive_anon"))
+                               mem->total_inactive_anon = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+                       else if(hash == total_active_anon_hash && !strcmp(s, "total_active_anon"))
+                               mem->total_active_anon = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+                       else if(hash == total_inactive_file_hash && !strcmp(s, "total_inactive_file"))
+                               mem->total_inactive_file = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+                       else if(hash == total_active_file_hash && !strcmp(s, "total_active_file"))
+                               mem->total_active_file = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+
+                       else if(hash == total_unevictable_hash && !strcmp(s, "total_unevictable"))
+                               mem->total_unevictable = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
+*/
+               }
+
+               // 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;
+       }
+}
+
+void cgroup_read(struct cgroup *cg) {
+       debug(D_CGROUP, "reading metrics for cgroups '%s'", cg->id);
+
+       cgroup_read_cpuacct_stat(&cg->cpuacct_stat);
+       cgroup_read_cpuacct_usage(&cg->cpuacct_usage);
+       cgroup_read_memory(&cg->memory);
+       cgroup_read_blkio(&cg->io_service_bytes);
+       cgroup_read_blkio(&cg->io_serviced);
+       cgroup_read_blkio(&cg->throttle_io_service_bytes);
+       cgroup_read_blkio(&cg->throttle_io_serviced);
+       cgroup_read_blkio(&cg->io_merged);
+       cgroup_read_blkio(&cg->io_queued);
+}
+
+void read_all_cgroups(struct cgroup *root) {
+       debug(D_CGROUP, "reading metrics for all cgroups");
+
+       struct cgroup *cg;
+
+       for(cg = root; cg ; cg = cg->next)
+               if(cg->enabled)
+                       cgroup_read(cg);
+}
+
+// ----------------------------------------------------------------------------
+// add/remove/find cgroup objects
+
+#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);
+
+       pid_t cgroup_pid;
+       char buffer[CGROUP_CHARTID_LINE_MAX + 1];
+
+       snprintf(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);
+
+       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(s && *s && *s != '\n') {
+               debug(D_CGROUP, "cgroup '%s' should be renamed to '%s'", cg->id, s);
+
+               trim(s);
+               netdata_fix_chart_id(s);
+               free(cg->chart_id);
+               cg->chart_id = strdup(s);
+               if(!cg->chart_id)
+                       fatal("CGROUP: Cannot allocate memory for chart id of cgroup '%s' chart id: '%s'", cg->id, s);
+
+               debug(D_CGROUP, "cgroup '%s' renamed to '%s'", cg->id, cg->chart_id);
+       }
+       else debug(D_CGROUP, "cgroup '%s' is not to be renamed (will be shown as '%s')", cg->id, cg->chart_id);
+}
+
+struct cgroup *cgroup_add(const char *id) {
+       debug(D_CGROUP, "adding cgroup '%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 host cgroup
+               def = 0;
+               debug(D_CGROUP, "cgroup '%s' is the host container (by default %s)", id, (def)?"enabled":"disabled");
+       }
+       else {
+               if(*chart_id == '/') chart_id++;
+
+               // disable by default the parent cgroup
+               // for known cgroup managers
+               if(!strcmp(chart_id, "lxc") || !strcmp(chart_id, "docker")) {
+                       def = 0;
+                       debug(D_CGROUP, "cgroup '%s' is container manager (by default %s)", id, (def)?"enabled":"disabled");
+               }
+       }
+
+       struct cgroup *cg = calloc(1, sizeof(struct cgroup));
+       if(!cg) fatal("Cannot allocate memory for cgroup '%s'", id);
+
+       debug(D_CGROUP, "adding cgroup '%s'", id);
+
+       cg->id = strdup(id);
+       if(!cg->id) fatal("Cannot allocate memory for cgroup '%s'", id);
+
+       cg->hash = simple_hash(cg->id);
+
+       cg->chart_id = strdup(chart_id);
+       if(!cg->chart_id) fatal("Cannot allocate memory for cgroup '%s'", id);
+
+       if(!cgroup_root)
+               cgroup_root = cg;
+       else {
+               // append it
+               struct cgroup *e;
+               for(e = cgroup_root; e->next ;e = e->next) ;
+               e->next = cg;
+       }
+
+       cgroup_root_count++;
+
+       // fprintf(stderr, " > added cgroup No %d, with id '%s' (%u) and name '%s'\n", cgroup_root_count, cg->id, cg->hash, cg->name);
+
+       // fix the name by calling the external script
+       cgroup_get_chart_id(cg);
+
+       char option[FILENAME_MAX + 1];
+       snprintf(option, FILENAME_MAX, "enable cgroup %s", cg->chart_id);
+       cg->enabled = config_get_boolean("plugin:cgroups", option, def);
+       debug(D_CGROUP, "finally: new cgroup '%s' is named '%s' and is %s (default was %s)", cg->id, cg->chart_id, (cg->enabled)?"enabled":"disabled", (def)?"enabled":"disabled");
+
+       return cg;
+}
+
+void cgroup_remove(struct cgroup *cg) {
+       debug(D_CGROUP, "removing cgroup '%s'", cg->id);
+
+       if(cg == cgroup_root) {
+               cgroup_root = cg->next;
+       }
+       else {
+               struct cgroup *e;
+               for(e = cgroup_root; e->next ;e = e->next)
+                       if(unlikely(e->next == cg)) break;
+
+               if(e->next != cg) {
+                       error("Cannot find cgroup '%s' in list of cgroups", cg->id);
+               }
+               else {
+                       e->next = cg->next;
+                       cg->next = NULL;
+               }
+       }
+
+       free(cg->cpuacct_usage.cpu_percpu);
+
+       free(cg->cpuacct_stat.filename);
+       free(cg->cpuacct_usage.filename);
+       free(cg->memory.filename);
+       free(cg->io_service_bytes.filename);
+       free(cg->io_serviced.filename);
+       free(cg->throttle_io_service_bytes.filename);
+       free(cg->throttle_io_serviced.filename);
+       free(cg->io_merged.filename);
+       free(cg->io_queued.filename);
+
+       free(cg->id);
+       free(cg->chart_id);
+       free(cg);
+}
+
+// find if a given cgroup exists
+struct cgroup *cgroup_find(const char *id) {
+       debug(D_CGROUP, "searching for cgroup '%s'", id);
+
+       uint32_t hash = simple_hash(id);
+
+       // fprintf(stderr, " > searching for '%s' (%u)\n", id, hash);
+
+       struct cgroup *cg;
+       for(cg = cgroup_root; cg ; cg = cg->next) {
+               if(hash == cg->hash && strcmp(id, cg->id) == 0)
+                       break;
+       }
+
+       debug(D_CGROUP, "cgroup '%s' %s", id, (cg)?"found":"not found");
+       return cg;
+}
+
+// ----------------------------------------------------------------------------
+// detect running cgroups
+
+// callback for find_file_in_subdirs()
+void found_dir_in_subdir(const char *dir) {
+       debug(D_CGROUP, "examining cgroup dir '%s'", dir);
+
+       struct cgroup *cg = cgroup_find(dir);
+       if(!cg) {
+               if(*dir && cgroup_max_depth > 0) {
+                       int depth = 0;
+                       const char *s;
+
+                       for(s = dir; *s ;s++)
+                               if(unlikely(*s == '/'))
+                                       depth++;
+
+                       if(depth > cgroup_max_depth) {
+                               info("cgroup '%s' is too deep (%d, while max is %d)", dir, depth, cgroup_max_depth);
+                               return;
+                       }
+               }
+               debug(D_CGROUP, "will add dir '%s' as cgroup", dir);
+               cg = cgroup_add(dir);
+       }
+
+       if(cg) cg->available = 1;
+}
+
+void find_dir_in_subdirs(const char *base, const char *this, void (*callback)(const char *)) {
+       if(!this) this = base;
+       size_t dirlen = strlen(this), baselen = strlen(base);
+
+       DIR *dir = opendir(this);
+       if(!dir) return;
+
+       callback(&this[baselen]);
+
+       struct dirent *de = NULL;
+       while((de = readdir(dir))) {
+               if(de->d_type == DT_DIR
+                       && (
+                               (de->d_name[0] == '.' && de->d_name[1] == '\0')
+                               || (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')
+                               ))
+                       continue;
+
+               debug(D_CGROUP, "examining '%s/%s'", this, de->d_name);
+
+               if(de->d_type == DT_DIR) {
+                       char *s = malloc(dirlen + strlen(de->d_name) + 2);
+                       if(s) {
+                               strcpy(s, this);
+                               strcat(s, "/");
+                               strcat(s, de->d_name);
+                               find_dir_in_subdirs(base, s, callback);
+                               free(s);
+                       }
+               }
+       }
+
+       closedir(dir);
+}
+
+void mark_all_cgroups_as_not_available() {
+       debug(D_CGROUP, "marking all cgroups as not available");
+
+       struct cgroup *cg;
+
+       // mark all as not available
+       for(cg = cgroup_root; cg ; cg = cg->next)
+               cg->available = 0;
+}
+
+struct cgroup *find_all_cgroups() {
+       debug(D_CGROUP, "searching for cgroups");
+
+       mark_all_cgroups_as_not_available();
+
+       if(cgroup_enable_cpuacct_stat || cgroup_enable_cpuacct_usage)
+               find_dir_in_subdirs(cgroup_cpuacct_base, NULL, found_dir_in_subdir);
+
+       if(cgroup_enable_blkio)
+               find_dir_in_subdirs(cgroup_blkio_base, NULL, found_dir_in_subdir);
+
+       if(cgroup_enable_memory)
+               find_dir_in_subdirs(cgroup_memory_base, NULL, found_dir_in_subdir);
+
+       struct cgroup *cg;
+       for(cg = cgroup_root; cg ; cg = cg->next) {
+               // fprintf(stderr, " >>> CGROUP '%s' (%u - %s) with name '%s'\n", cg->id, cg->hash, cg->available?"available":"stopped", cg->name);
+
+               if(!cg->available) {
+                       // do not remove the cgroup
+                       // it will be added back on the next scan
+                       // cgroup_remove(cg);
+                       continue;
+               }
+
+               debug(D_CGROUP, "checking paths for cgroup '%s'", cg->id);
+
+               // check for newly added cgroups
+               // and update the filenames they read
+               char filename[FILENAME_MAX + 1];
+               if(cgroup_enable_cpuacct_stat && !cg->cpuacct_stat.filename) {
+                       snprintf(filename, FILENAME_MAX, "%s%s/cpuacct.stat", cgroup_cpuacct_base, cg->id);
+                       cg->cpuacct_stat.filename = strdup(filename);
+                       debug(D_CGROUP, "cpuacct.stat filename for cgroup '%s': '%s'", cg->id, cg->cpuacct_stat.filename);
+               }
+               if(cgroup_enable_cpuacct_usage && !cg->cpuacct_usage.filename) {
+                       snprintf(filename, FILENAME_MAX, "%s%s/cpuacct.usage_percpu", cgroup_cpuacct_base, cg->id);
+                       cg->cpuacct_usage.filename = strdup(filename);
+                       debug(D_CGROUP, "cpuacct.usage_percpu filename for cgroup '%s': '%s'", cg->id, cg->cpuacct_usage.filename);
+               }
+               if(cgroup_enable_memory && !cg->memory.filename) {
+                       snprintf(filename, FILENAME_MAX, "%s%s/memory.stat", cgroup_memory_base, cg->id);
+                       cg->memory.filename = strdup(filename);
+                       debug(D_CGROUP, "memory.stat filename for cgroup '%s': '%s'", cg->id, cg->memory.filename);
+               }
+               if(cgroup_enable_blkio) {
+                       if(!cg->io_service_bytes.filename) {
+                               snprintf(filename, FILENAME_MAX, "%s%s/blkio.io_service_bytes", cgroup_blkio_base, cg->id);
+                               cg->io_service_bytes.filename = strdup(filename);
+                               debug(D_CGROUP, "io_service_bytes filename for cgroup '%s': '%s'", cg->id, cg->io_service_bytes.filename);
+                       }
+                       if(!cg->io_serviced.filename) {
+                               snprintf(filename, FILENAME_MAX, "%s%s/blkio.io_serviced", cgroup_blkio_base, cg->id);
+                               cg->io_serviced.filename = strdup(filename);
+                               debug(D_CGROUP, "io_serviced filename for cgroup '%s': '%s'", cg->id, cg->io_serviced.filename);
+                       }
+                       if(!cg->throttle_io_service_bytes.filename) {
+                               snprintf(filename, FILENAME_MAX, "%s%s/blkio.throttle.io_service_bytes", cgroup_blkio_base, cg->id);
+                               cg->throttle_io_service_bytes.filename = strdup(filename);
+                               debug(D_CGROUP, "throttle_io_service_bytes filename for cgroup '%s': '%s'", cg->id, cg->throttle_io_service_bytes.filename);
+                       }
+                       if(!cg->throttle_io_serviced.filename) {
+                               snprintf(filename, FILENAME_MAX, "%s%s/blkio.throttle.io_serviced", cgroup_blkio_base, cg->id);
+                               cg->throttle_io_serviced.filename = strdup(filename);
+                               debug(D_CGROUP, "throttle_io_serviced filename for cgroup '%s': '%s'", cg->id, cg->throttle_io_serviced.filename);
+                       }
+                       if(!cg->io_merged.filename) {
+                               snprintf(filename, FILENAME_MAX, "%s%s/blkio.io_merged", cgroup_blkio_base, cg->id);
+                               cg->io_merged.filename = strdup(filename);
+                               debug(D_CGROUP, "io_merged filename for cgroup '%s': '%s'", cg->id, cg->io_merged.filename);
+                       }
+                       if(!cg->io_queued.filename) {
+                               snprintf(filename, FILENAME_MAX, "%s%s/blkio.io_queued", cgroup_blkio_base, cg->id);
+                               cg->io_queued.filename = strdup(filename);
+                               debug(D_CGROUP, "io_queued filename for cgroup '%s': '%s'", cg->id, cg->io_queued.filename);
+                       }
+               }
+       }
+
+       debug(D_CGROUP, "done searching for cgroups");
+       return cgroup_root;
+}
+
+// ----------------------------------------------------------------------------
+// generate charts
+
+#define CHART_TITLE_MAX 300
+
+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;
+
+       for(cg = cgroup_root; cg ; cg = cg->next) {
+               if(!cg->available || !cg->enabled) continue;
+
+               if(cg->id[0] == '\0')
+                       strcpy(type, "cgroup_host");
+               else if(cg->id[0] == '/')
+                       snprintf(type, RRD_ID_LENGTH_MAX, "cgroup_%s", cg->chart_id);
+               else
+                       snprintf(type, RRD_ID_LENGTH_MAX, "cgroup_%s", cg->chart_id);
+
+               netdata_fix_chart_id(type);
+
+               if(cg->cpuacct_stat.updated) {
+                       st = rrdset_find_bytype(type, "cpu");
+                       if(!st) {
+                               snprintf(title, CHART_TITLE_MAX, "CPU Usage for cgroup %s", cg->chart_id);
+                               st = rrdset_create(type, "cpu", NULL, "cpu", "cgroup.cpu", title, "%", 40000, update_every, RRDSET_TYPE_STACKED);
+
+                               rrddim_add(st, "user", NULL, 100, hz, RRDDIM_INCREMENTAL);
+                               rrddim_add(st, "system", NULL, 100, hz, RRDDIM_INCREMENTAL);
+                       }
+                       else rrdset_next(st);
+
+                       rrddim_set(st, "user", cg->cpuacct_stat.user);
+                       rrddim_set(st, "system", cg->cpuacct_stat.system);
+                       rrdset_done(st);
+               }
+
+               if(cg->cpuacct_usage.updated) {
+                       char id[RRD_ID_LENGTH_MAX + 1];
+                       unsigned int i;
+
+                       st = rrdset_find_bytype(type, "cpu_per_core");
+                       if(!st) {
+                               snprintf(title, CHART_TITLE_MAX, "CPU Usage Per Core for cgroup %s", cg->chart_id);
+                               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++) {
+                                       snprintf(id, CHART_TITLE_MAX, "cpu%d", i);
+                                       rrddim_add(st, id, NULL, 100, 1000000, RRDDIM_INCREMENTAL);
+                               }
+                       }
+                       else rrdset_next(st);
+
+                       for(i = 0; i < cg->cpuacct_usage.cpus ;i++) {
+                               snprintf(id, CHART_TITLE_MAX, "cpu%d", i);
+                               rrddim_set(st, id, cg->cpuacct_usage.cpu_percpu[i]);
+                       }
+                       rrdset_done(st);
+               }
+
+               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) {
+                                       snprintf(title, CHART_TITLE_MAX, "Memory Usage for cgroup %s", cg->chart_id);
+                                       st = rrdset_create(type, "mem", NULL, "mem", "cgroup.mem", title, "MB", 40200, 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);
+                               }
+                               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);
+                       }
+
+                       st = rrdset_find_bytype(type, "writeback");
+                       if(!st) {
+                               snprintf(title, CHART_TITLE_MAX, "Writeback Memory for cgroup %s", cg->chart_id);
+                               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);
+                       }
+                       else rrdset_next(st);
+
+                       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.pgpgin + cg->memory.pgpgout > 0) {
+                               st = rrdset_find_bytype(type, "mem_activity");
+                               if(!st) {
+                                       snprintf(title, CHART_TITLE_MAX, "Memory Activity for cgroup %s", cg->chart_id);
+                                       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);
+                               }
+                               else rrdset_next(st);
+
+                               rrddim_set(st, "pgpgin", cg->memory.pgpgin);
+                               rrddim_set(st, "pgpgout", cg->memory.pgpgout);
+                               rrdset_done(st);
+                       }
+
+                       if(cg->memory.pgfault + cg->memory.pgmajfault > 0) {
+                               st = rrdset_find_bytype(type, "pgfaults");
+                               if(!st) {
+                                       snprintf(title, CHART_TITLE_MAX, "Memory Page Faults for cgroup %s", cg->chart_id);
+                                       st = rrdset_create(type, "pgfaults", NULL, "mem", "cgroup.pgfaults", title, "MB/s", 40500,
+                                                          update_every, RRDSET_TYPE_LINE);
+
+                                       rrddim_add(st, "pgfault", NULL, sysconf(_SC_PAGESIZE), 1024 * 1024, RRDDIM_INCREMENTAL);
+                                       rrddim_add(st, "pgmajfault", "swap", -sysconf(_SC_PAGESIZE), 1024 * 1024, RRDDIM_INCREMENTAL);
+                               }
+                               else rrdset_next(st);
+
+                               rrddim_set(st, "pgfault", cg->memory.pgfault);
+                               rrddim_set(st, "pgmajfault", cg->memory.pgmajfault);
+                               rrdset_done(st);
+                       }
+               }
+
+               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) {
+                               snprintf(title, CHART_TITLE_MAX, "I/O Bandwidth (all disks) for cgroup %s", cg->chart_id);
+                               st = rrdset_create(type, "io", NULL, "disk", "cgroup.io", title, "KB/s", 41200,
+                                                  update_every, RRDSET_TYPE_LINE);
+
+                               rrddim_add(st, "read", NULL, 1, 1024, RRDDIM_INCREMENTAL);
+                               rrddim_add(st, "write", NULL, -1, 1024, RRDDIM_INCREMENTAL);
+                       }
+                       else rrdset_next(st);
+
+                       rrddim_set(st, "read", cg->io_service_bytes.Read);
+                       rrddim_set(st, "write", cg->io_service_bytes.Write);
+                       rrdset_done(st);
+               }
+
+               if(cg->io_serviced.updated && cg->io_serviced.Read + cg->io_serviced.Write > 0) {
+                       st = rrdset_find_bytype(type, "serviced_ops");
+                       if(!st) {
+                               snprintf(title, CHART_TITLE_MAX, "Serviced I/O Operations (all disks) for cgroup %s", cg->chart_id);
+                               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);
+                       }
+                       else rrdset_next(st);
+
+                       rrddim_set(st, "read", cg->io_serviced.Read);
+                       rrddim_set(st, "write", cg->io_serviced.Write);
+                       rrdset_done(st);
+               }
+
+               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, "io");
+                       if(!st) {
+                               snprintf(title, CHART_TITLE_MAX, "Throttle I/O Bandwidth (all disks) for cgroup %s", cg->chart_id);
+                               st = rrdset_create(type, "io", NULL, "disk", "cgroup.io", title, "KB/s", 41200,
+                                                  update_every, RRDSET_TYPE_LINE);
+
+                               rrddim_add(st, "read", NULL, 1, 1024, RRDDIM_INCREMENTAL);
+                               rrddim_add(st, "write", NULL, -1, 1024, RRDDIM_INCREMENTAL);
+                       }
+                       else rrdset_next(st);
+
+                       rrddim_set(st, "read", cg->throttle_io_service_bytes.Read);
+                       rrddim_set(st, "write", cg->throttle_io_service_bytes.Write);
+                       rrdset_done(st);
+               }
+
+
+               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) {
+                               snprintf(title, CHART_TITLE_MAX, "Throttle Serviced I/O Operations (all disks) for cgroup %s", cg->chart_id);
+                               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);
+                       }
+                       else rrdset_next(st);
+
+                       rrddim_set(st, "read", cg->throttle_io_serviced.Read);
+                       rrddim_set(st, "write", cg->throttle_io_serviced.Write);
+                       rrdset_done(st);
+               }
+
+               if(cg->io_queued.updated) {
+                       st = rrdset_find_bytype(type, "queued_ops");
+                       if(!st) {
+                               snprintf(title, CHART_TITLE_MAX, "Queued I/O Operations (all disks) for cgroup %s", cg->chart_id);
+                               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);
+                       }
+                       else rrdset_next(st);
+
+                       rrddim_set(st, "read", cg->io_queued.Read);
+                       rrddim_set(st, "write", cg->io_queued.Write);
+                       rrdset_done(st);
+               }
+
+               if(cg->io_merged.updated && cg->io_merged.Read + cg->io_merged.Write > 0) {
+                       st = rrdset_find_bytype(type, "merged_ops");
+                       if(!st) {
+                               snprintf(title, CHART_TITLE_MAX, "Merged I/O Operations (all disks) for cgroup %s", cg->chart_id);
+                               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);
+                       }
+                       else rrdset_next(st);
+
+                       rrddim_set(st, "read", cg->io_merged.Read);
+                       rrddim_set(st, "write", cg->io_merged.Write);
+                       rrdset_done(st);
+               }
+       }
+
+       debug(D_CGROUP, "done updating cgroups charts");
+}
+
+// ----------------------------------------------------------------------------
+// cgroups main
+
+int do_sys_fs_cgroup(int update_every, unsigned long long dt) {
+       static int cgroup_global_config_read = 0;
+       static time_t last_run = 0;
+       time_t now = time(NULL);
+
+       if(dt) {};
+
+       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)
+{
+       if(ptr) { ; }
+
+       info("CGROUP Plugin 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.");
+
+       struct rusage thread;
+
+       // when ZERO, attempt to do it
+       int vdo_sys_fs_cgroup                   = 0;
+       int vdo_cpu_netdata                     = !config_get_boolean("plugin:cgroups", "netdata server resources", 1);
+
+       // keep track of the time each module was called
+       unsigned long long sutime_sys_fs_cgroup = 0ULL;
+
+       // the next time we will run - aligned properly
+       unsigned long long sunext = (time(NULL) - (time(NULL) % rrd_update_every) + rrd_update_every) * 1000000ULL;
+       unsigned long long sunow;
+
+       RRDSET *stcpu_thread = NULL;
+
+       for(;1;) {
+               if(unlikely(netdata_exit)) break;
+
+               // delay until it is our time to run
+               while((sunow = timems()) < sunext)
+                       usleep((useconds_t)(sunext - sunow));
+
+               // find the next time we need to run
+               while(timems() > sunext)
+                       sunext += rrd_update_every * 1000000ULL;
+
+               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 = timems();
+                       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(netdata_exit)) break;
+
+               // END -- the job is done
+
+               // --------------------------------------------------------------------
+
+               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", 131000, rrd_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);
+
+                       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);
+                       rrdset_done(stcpu_thread);
+               }
+       }
+
+       pthread_exit(NULL);
+       return NULL;
+}
index 32aa1473814f8f3928c9f824cb1639fdb89fb76f..7be0bc3b35b58a6179f9301de18b1eef006c5ae4 100644 (file)
@@ -1012,6 +1012,21 @@ var menuData = {
                title: 'Example Charts',
                info: undefined
        },
+
+       'cgroup': {
+               title: 'Container',
+               info: undefined
+       },
+
+       'mysql': {
+               title: 'MySQL',
+               info: undefined
+       },
+
+       'named': {
+               title: 'named',
+               info: undefined
+       },
 };
 
 var submenuData = {
@@ -1322,8 +1337,13 @@ function anyAttribute(obj, attr, key, def) {
        return def;
 }
 
-function menuTitle(menu) {
-       return anyAttribute(menuData, 'title', menu, menu);
+function menuTitle(chart) {
+       if(typeof chart.menu_pattern !== 'undefined') {
+               return anyAttribute(menuData, 'title', chart.menu_pattern, chart.menu_pattern).toString()
+                               + ': ' + chart.type.slice(-(chart.type.length - chart.menu_pattern.length - 1)).toString();
+       }
+
+       return anyAttribute(menuData, 'title', chart.menu, chart.menu);
 }
 
 function menuInfo(menu) {
@@ -1379,6 +1399,13 @@ function enrichChartData(chart) {
                        chart.menu = tmp;
                        break;
 
+               case 'mysql':
+               case 'named':
+               case 'cgroup':
+                       chart.menu = chart.type;
+                       chart.menu_pattern = tmp;
+                       break;
+
                case 'tc':
                        chart.menu = tmp;
 
@@ -1654,7 +1681,7 @@ function renderChartsAndMenu(data) {
                        menus[charts[c].menu] = {
                                priority: charts[c].priority,
                                submenus: {},
-                               title: menuTitle(charts[c].menu),
+                               title: menuTitle(charts[c]),
                                info: menuInfo(charts[c].menu),
                                height: menuHeight(charts[c].menu, options.chartsHeight)
                        };
@@ -1681,6 +1708,8 @@ function renderChartsAndMenu(data) {
                menus[charts[c].menu].submenus[charts[c].submenu].charts.push(charts[c]);
        }
 
+       // propagate the descriptive subname given to QoS
+       // to all the other submenus with the same name
        for(var m in menus) {
                for(var s in menus[m].submenus) {
                        // set the family using a name