13 #include "appconfig.h"
20 // ----------------------------------------------------------------------------
23 static int cgroup_enable_cpuacct_stat = CONFIG_ONDEMAND_ONDEMAND;
24 static int cgroup_enable_cpuacct_usage = CONFIG_ONDEMAND_ONDEMAND;
25 static int cgroup_enable_memory = CONFIG_ONDEMAND_ONDEMAND;
26 static int cgroup_enable_blkio = CONFIG_ONDEMAND_ONDEMAND;
27 static int cgroup_enable_new_cgroups_detected_at_runtime = 1;
28 static int cgroup_check_for_new_every = 10;
29 static char *cgroup_cpuacct_base = NULL;
30 static char *cgroup_blkio_base = NULL;
31 static char *cgroup_memory_base = NULL;
33 static int cgroup_root_count = 0;
34 static int cgroup_root_max = 50;
35 static int cgroup_max_depth = 0;
37 void read_cgroup_plugin_configuration() {
38 cgroup_check_for_new_every = config_get_number("plugin:cgroups", "check for new plugin every", cgroup_check_for_new_every);
40 cgroup_enable_cpuacct_stat = config_get_boolean_ondemand("plugin:cgroups", "enable cpuacct stat", cgroup_enable_cpuacct_stat);
41 cgroup_enable_cpuacct_usage = config_get_boolean_ondemand("plugin:cgroups", "enable cpuacct usage", cgroup_enable_cpuacct_usage);
42 cgroup_enable_memory = config_get_boolean_ondemand("plugin:cgroups", "enable memory", cgroup_enable_memory);
43 cgroup_enable_blkio = config_get_boolean_ondemand("plugin:cgroups", "enable blkio", cgroup_enable_blkio);
45 char filename[FILENAME_MAX + 1];
46 snprintf(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/sys/fs/cgroup/cpuacct");
47 cgroup_cpuacct_base = config_get("plugin:cgroups", "path to /sys/fs/cgroup/cpuacct", filename);
49 snprintf(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/sys/fs/cgroup/blkio");
50 cgroup_blkio_base = config_get("plugin:cgroups", "path to /sys/fs/cgroup/blkio", filename);
52 snprintf(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/sys/fs/cgroup/memory");
53 cgroup_memory_base = config_get("plugin:cgroups", "path to /sys/fs/cgroup/memory", filename);
55 cgroup_root_max = config_get_number("plugin:cgroups", "max cgroups to allow", cgroup_root_max);
56 cgroup_max_depth = config_get_number("plugin:cgroups", "max cgroups depth to monitor", cgroup_max_depth);
58 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);
61 // ----------------------------------------------------------------------------
69 unsigned long long Read;
70 unsigned long long Write;
71 unsigned long long Sync;
72 unsigned long long Async;
73 unsigned long long Total;
76 // https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt
84 unsigned long long cache;
85 unsigned long long rss;
86 unsigned long long rss_huge;
87 unsigned long long mapped_file;
88 unsigned long long writeback;
89 unsigned long long dirty;
90 unsigned long long swap;
91 unsigned long long pgpgin;
92 unsigned long long pgpgout;
93 unsigned long long pgfault;
94 unsigned long long pgmajfault;
96 unsigned long long inactive_anon;
97 unsigned long long active_anon;
98 unsigned long long inactive_file;
99 unsigned long long active_file;
100 unsigned long long unevictable;
101 unsigned long long hierarchical_memory_limit;
102 unsigned long long total_cache;
103 unsigned long long total_rss;
104 unsigned long long total_rss_huge;
105 unsigned long long total_mapped_file;
106 unsigned long long total_writeback;
107 unsigned long long total_dirty;
108 unsigned long long total_swap;
109 unsigned long long total_pgpgin;
110 unsigned long long total_pgpgout;
111 unsigned long long total_pgfault;
112 unsigned long long total_pgmajfault;
113 unsigned long long total_inactive_anon;
114 unsigned long long total_active_anon;
115 unsigned long long total_inactive_file;
116 unsigned long long total_active_file;
117 unsigned long long total_unevictable;
121 // https://www.kernel.org/doc/Documentation/cgroup-v1/cpuacct.txt
122 struct cpuacct_stat {
127 unsigned long long user;
128 unsigned long long system;
131 // https://www.kernel.org/doc/Documentation/cgroup-v1/cpuacct.txt
132 struct cpuacct_usage {
138 unsigned long long *cpu_percpu;
150 struct cpuacct_stat cpuacct_stat;
151 struct cpuacct_usage cpuacct_usage;
153 struct memory memory;
155 struct blkio io_service_bytes; // bytes
156 struct blkio io_serviced; // operations
158 struct blkio throttle_io_service_bytes; // bytes
159 struct blkio throttle_io_serviced; // operations
161 struct blkio io_merged; // operations
162 struct blkio io_queued; // operations
166 } *cgroup_root = NULL;
168 // ----------------------------------------------------------------------------
169 // read values from /sys
171 void cgroup_read_cpuacct_stat(struct cpuacct_stat *cp) {
172 static procfile *ff = NULL;
174 static uint32_t user_hash = 0;
175 static uint32_t system_hash = 0;
177 if(unlikely(user_hash == 0)) {
178 user_hash = simple_hash("user");
179 system_hash = simple_hash("system");
184 ff = procfile_reopen(ff, cp->filename, NULL, PROCFILE_FLAG_DEFAULT);
187 ff = procfile_readall(ff);
190 unsigned long i, lines = procfile_lines(ff);
193 error("File '%s' should have 1+ lines.", cp->filename);
197 for(i = 0; i < lines ; i++) {
198 char *s = procfile_lineword(ff, i, 0);
199 uint32_t hash = simple_hash(s);
201 if(hash == user_hash && !strcmp(s, "user"))
202 cp->user = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
204 else if(hash == system_hash && !strcmp(s, "system"))
205 cp->system = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
210 // fprintf(stderr, "READ '%s': user: %llu, system: %llu\n", cp->filename, cp->user, cp->system);
214 void cgroup_read_cpuacct_usage(struct cpuacct_usage *ca) {
215 static procfile *ff = NULL;
219 ff = procfile_reopen(ff, ca->filename, NULL, PROCFILE_FLAG_DEFAULT);
222 ff = procfile_readall(ff);
225 if(procfile_lines(ff) < 1) {
226 error("File '%s' should have 1+ lines but has %d.", ca->filename, procfile_lines(ff));
230 unsigned long i = procfile_linewords(ff, 0);
233 // we may have 1 more CPU reported
235 char *s = procfile_lineword(ff, 0, i - 1);
241 free(ca->cpu_percpu);
243 ca->cpu_percpu = malloc(sizeof(unsigned long long) * i);
245 fatal("Cannot allocate memory (%z bytes)", sizeof(unsigned long long) * i);
249 for(i = 0; i < ca->cpus ;i++) {
250 ca->cpu_percpu[i] = strtoull(procfile_lineword(ff, 0, i), NULL, 10);
251 // fprintf(stderr, "READ '%s': cpu%d/%d: %llu ('%s')\n", ca->filename, i, ca->cpus, ca->cpu_percpu[i], procfile_lineword(ff, 0, i));
258 void cgroup_read_blkio(struct blkio *io) {
259 static procfile *ff = NULL;
261 static uint32_t Read_hash = 0;
262 static uint32_t Write_hash = 0;
263 static uint32_t Sync_hash = 0;
264 static uint32_t Async_hash = 0;
265 static uint32_t Total_hash = 0;
267 if(unlikely(Read_hash == 0)) {
268 Read_hash = simple_hash("Read");
269 Write_hash = simple_hash("Write");
270 Sync_hash = simple_hash("Sync");
271 Async_hash = simple_hash("Async");
272 Total_hash = simple_hash("Total");
277 ff = procfile_reopen(ff, io->filename, NULL, PROCFILE_FLAG_DEFAULT);
280 ff = procfile_readall(ff);
283 unsigned long i, lines = procfile_lines(ff);
286 error("File '%s' should have 1+ lines.", io->filename);
296 for(i = 0; i < lines ; i++) {
297 char *s = procfile_lineword(ff, i, 1);
298 uint32_t hash = simple_hash(s);
300 if(hash == Read_hash && !strcmp(s, "Read"))
301 io->Read += strtoull(procfile_lineword(ff, i, 2), NULL, 10);
303 else if(hash == Write_hash && !strcmp(s, "Write"))
304 io->Write += strtoull(procfile_lineword(ff, i, 2), NULL, 10);
306 else if(hash == Sync_hash && !strcmp(s, "Sync"))
307 io->Sync += strtoull(procfile_lineword(ff, i, 2), NULL, 10);
309 else if(hash == Async_hash && !strcmp(s, "Async"))
310 io->Async += strtoull(procfile_lineword(ff, i, 2), NULL, 10);
312 else if(hash == Total_hash && !strcmp(s, "Total"))
313 io->Total += strtoull(procfile_lineword(ff, i, 2), NULL, 10);
317 // 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);
321 void cgroup_read_memory(struct memory *mem) {
322 static procfile *ff = NULL;
324 static uint32_t cache_hash = 0;
325 static uint32_t rss_hash = 0;
326 static uint32_t rss_huge_hash = 0;
327 static uint32_t mapped_file_hash = 0;
328 static uint32_t writeback_hash = 0;
329 static uint32_t dirty_hash = 0;
330 static uint32_t swap_hash = 0;
331 static uint32_t pgpgin_hash = 0;
332 static uint32_t pgpgout_hash = 0;
333 static uint32_t pgfault_hash = 0;
334 static uint32_t pgmajfault_hash = 0;
336 static uint32_t inactive_anon_hash = 0;
337 static uint32_t active_anon_hash = 0;
338 static uint32_t inactive_file_hash = 0;
339 static uint32_t active_file_hash = 0;
340 static uint32_t unevictable_hash = 0;
341 static uint32_t hierarchical_memory_limit_hash = 0;
342 static uint32_t total_cache_hash = 0;
343 static uint32_t total_rss_hash = 0;
344 static uint32_t total_rss_huge_hash = 0;
345 static uint32_t total_mapped_file_hash = 0;
346 static uint32_t total_writeback_hash = 0;
347 static uint32_t total_dirty_hash = 0;
348 static uint32_t total_swap_hash = 0;
349 static uint32_t total_pgpgin_hash = 0;
350 static uint32_t total_pgpgout_hash = 0;
351 static uint32_t total_pgfault_hash = 0;
352 static uint32_t total_pgmajfault_hash = 0;
353 static uint32_t total_inactive_anon_hash = 0;
354 static uint32_t total_active_anon_hash = 0;
355 static uint32_t total_inactive_file_hash = 0;
356 static uint32_t total_active_file_hash = 0;
357 static uint32_t total_unevictable_hash = 0;
359 if(unlikely(cache_hash == 0)) {
360 cache_hash = simple_hash("cache");
361 rss_hash = simple_hash("rss");
362 rss_huge_hash = simple_hash("rss_huge");
363 mapped_file_hash = simple_hash("mapped_file");
364 writeback_hash = simple_hash("writeback");
365 dirty_hash = simple_hash("dirty");
366 swap_hash = simple_hash("swap");
367 pgpgin_hash = simple_hash("pgpgin");
368 pgpgout_hash = simple_hash("pgpgout");
369 pgfault_hash = simple_hash("pgfault");
370 pgmajfault_hash = simple_hash("pgmajfault");
372 inactive_anon_hash = simple_hash("inactive_anon");
373 active_anon_hash = simple_hash("active_anon");
374 inactive_file_hash = simple_hash("inactive_file");
375 active_file_hash = simple_hash("active_file");
376 unevictable_hash = simple_hash("unevictable");
377 hierarchical_memory_limit_hash = simple_hash("hierarchical_memory_limit");
378 total_cache_hash = simple_hash("total_cache");
379 total_rss_hash = simple_hash("total_rss");
380 total_rss_huge_hash = simple_hash("total_rss_huge");
381 total_mapped_file_hash = simple_hash("total_mapped_file");
382 total_writeback_hash = simple_hash("total_writeback");
383 total_dirty_hash = simple_hash("total_dirty");
384 total_swap_hash = simple_hash("total_swap");
385 total_pgpgin_hash = simple_hash("total_pgpgin");
386 total_pgpgout_hash = simple_hash("total_pgpgout");
387 total_pgfault_hash = simple_hash("total_pgfault");
388 total_pgmajfault_hash = simple_hash("total_pgmajfault");
389 total_inactive_anon_hash = simple_hash("total_inactive_anon");
390 total_active_anon_hash = simple_hash("total_active_anon");
391 total_inactive_file_hash = simple_hash("total_inactive_file");
392 total_active_file_hash = simple_hash("total_active_file");
393 total_unevictable_hash = simple_hash("total_unevictable");
399 ff = procfile_reopen(ff, mem->filename, NULL, PROCFILE_FLAG_DEFAULT);
402 ff = procfile_readall(ff);
405 unsigned long i, lines = procfile_lines(ff);
408 error("File '%s' should have 1+ lines.", mem->filename);
412 for(i = 0; i < lines ; i++) {
413 char *s = procfile_lineword(ff, i, 0);
414 uint32_t hash = simple_hash(s);
416 if(hash == cache_hash && !strcmp(s, "cache"))
417 mem->cache = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
419 else if(hash == rss_hash && !strcmp(s, "rss"))
420 mem->rss = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
422 else if(hash == rss_huge_hash && !strcmp(s, "rss_huge"))
423 mem->rss_huge = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
425 else if(hash == mapped_file_hash && !strcmp(s, "mapped_file"))
426 mem->mapped_file = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
428 else if(hash == writeback_hash && !strcmp(s, "writeback"))
429 mem->writeback = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
431 else if(hash == dirty_hash && !strcmp(s, "dirty")) {
432 mem->dirty = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
433 mem->has_dirty_swap = 1;
436 else if(hash == swap_hash && !strcmp(s, "swap")) {
437 mem->swap = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
438 mem->has_dirty_swap = 1;
441 else if(hash == pgpgin_hash && !strcmp(s, "pgpgin"))
442 mem->pgpgin = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
444 else if(hash == pgpgout_hash && !strcmp(s, "pgpgout"))
445 mem->pgpgout = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
447 else if(hash == pgfault_hash && !strcmp(s, "pgfault"))
448 mem->pgfault = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
450 else if(hash == pgmajfault_hash && !strcmp(s, "pgmajfault"))
451 mem->pgmajfault = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
454 else if(hash == inactive_anon_hash && !strcmp(s, "inactive_anon"))
455 mem->inactive_anon = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
457 else if(hash == active_anon_hash && !strcmp(s, "active_anon"))
458 mem->active_anon = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
460 else if(hash == inactive_file_hash && !strcmp(s, "inactive_file"))
461 mem->inactive_file = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
463 else if(hash == active_file_hash && !strcmp(s, "active_file"))
464 mem->active_file = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
466 else if(hash == unevictable_hash && !strcmp(s, "unevictable"))
467 mem->unevictable = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
469 else if(hash == hierarchical_memory_limit_hash && !strcmp(s, "hierarchical_memory_limit"))
470 mem->hierarchical_memory_limit = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
472 else if(hash == total_cache_hash && !strcmp(s, "total_cache"))
473 mem->total_cache = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
475 else if(hash == total_rss_hash && !strcmp(s, "total_rss"))
476 mem->total_rss = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
478 else if(hash == total_rss_huge_hash && !strcmp(s, "total_rss_huge"))
479 mem->total_rss_huge = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
481 else if(hash == total_mapped_file_hash && !strcmp(s, "total_mapped_file"))
482 mem->total_mapped_file = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
484 else if(hash == total_writeback_hash && !strcmp(s, "total_writeback"))
485 mem->total_writeback = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
487 else if(hash == total_dirty_hash && !strcmp(s, "total_dirty"))
488 mem->total_dirty = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
490 else if(hash == total_swap_hash && !strcmp(s, "total_swap"))
491 mem->total_swap = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
493 else if(hash == total_pgpgin_hash && !strcmp(s, "total_pgpgin"))
494 mem->total_pgpgin = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
496 else if(hash == total_pgpgout_hash && !strcmp(s, "total_pgpgout"))
497 mem->total_pgpgout = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
499 else if(hash == total_pgfault_hash && !strcmp(s, "total_pgfault"))
500 mem->total_pgfault = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
502 else if(hash == total_pgmajfault_hash && !strcmp(s, "total_pgmajfault"))
503 mem->total_pgmajfault = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
505 else if(hash == total_inactive_anon_hash && !strcmp(s, "total_inactive_anon"))
506 mem->total_inactive_anon = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
508 else if(hash == total_active_anon_hash && !strcmp(s, "total_active_anon"))
509 mem->total_active_anon = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
511 else if(hash == total_inactive_file_hash && !strcmp(s, "total_inactive_file"))
512 mem->total_inactive_file = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
514 else if(hash == total_active_file_hash && !strcmp(s, "total_active_file"))
515 mem->total_active_file = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
517 else if(hash == total_unevictable_hash && !strcmp(s, "total_unevictable"))
518 mem->total_unevictable = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
522 // 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);
528 void cgroup_read(struct cgroup *cg) {
529 cgroup_read_cpuacct_stat(&cg->cpuacct_stat);
530 cgroup_read_cpuacct_usage(&cg->cpuacct_usage);
531 cgroup_read_memory(&cg->memory);
532 cgroup_read_blkio(&cg->io_service_bytes);
533 cgroup_read_blkio(&cg->io_serviced);
534 cgroup_read_blkio(&cg->throttle_io_service_bytes);
535 cgroup_read_blkio(&cg->throttle_io_serviced);
536 cgroup_read_blkio(&cg->io_merged);
537 cgroup_read_blkio(&cg->io_queued);
540 void read_all_cgroups(struct cgroup *cg) {
543 for(i = cg; i ; i = i->next)
547 // ----------------------------------------------------------------------------
548 // add/remove/find cgroup objects
550 #define CGROUP_NAME_LINE_MAX 1024
552 void cgroup_get_name(struct cgroup *cg) {
554 char buffer[CGROUP_NAME_LINE_MAX + 1];
556 snprintf(buffer, CGROUP_NAME_LINE_MAX, "exec %s '%s'",
557 config_get("plugin:cgroups", "script to get cgroup names", PLUGINS_DIR "/cgroup-name.sh"), cg->name);
559 FILE *fp = mypopen(buffer, &cgroup_pid);
561 error("CGROUP: Cannot popen(\"%s\", \"r\").", buffer);
564 char *s = fgets(buffer, CGROUP_NAME_LINE_MAX, fp);
565 mypclose(fp, cgroup_pid);
567 if(s && *s && *s != '\n') {
569 netdata_fix_chart_name(s);
571 cg->name = strdup(s);
573 fatal("CGROUP: Cannot allocate memory for name cgroup %s name: '%s'", cg->id, s);
577 struct cgroup *cgroup_add(const char *id) {
578 if(cgroup_root_count >= cgroup_root_max) {
579 info("Maximum number of cgroups reached (%d). Not adding cgroup '%s'", cgroup_root_count, id);
583 int def = cgroup_enable_new_cgroups_detected_at_runtime;
584 const char *name = id;
588 // disable by default the host cgroup
592 if(*name == '/') name++;
594 // disable by default the parent cgroup
595 // for known cgroup managers
596 if(!strcmp(name, "lxc") || !strcmp(name, "docker"))
600 char option[FILENAME_MAX + 1];
601 snprintf(option, FILENAME_MAX, "enable cgroup %s", name);
602 if(!config_get_boolean("plugin:cgroups", option, def))
605 struct cgroup *cg = calloc(1, sizeof(struct cgroup));
608 if(!cg->id) fatal("Cannot allocate memory for cgroup '%s'", id);
610 cg->hash = simple_hash(cg->id);
612 cg->name = strdup(name);
613 if(!cg->name) fatal("Cannot allocate memory for cgroup '%s'", id);
620 for(e = cgroup_root; e->next ;e = e->next) ;
626 // fprintf(stderr, " > added cgroup No %d, with id '%s' (%u) and name '%s'\n", cgroup_root_count, cg->id, cg->hash, cg->name);
628 // fix the name by calling the external script
631 else fatal("Cannot allocate memory for cgroup '%s'", id);
636 void cgroup_remove(struct cgroup *cg) {
637 if(cg == cgroup_root) {
638 cgroup_root = cg->next;
642 for(e = cgroup_root; e->next ;e = e->next)
643 if(unlikely(e->next == cg)) break;
646 error("Cannot find cgroup '%s' in list of cgroups", cg->id);
654 free(cg->cpuacct_usage.cpu_percpu);
656 free(cg->cpuacct_stat.filename);
657 free(cg->cpuacct_usage.filename);
658 free(cg->memory.filename);
659 free(cg->io_service_bytes.filename);
660 free(cg->io_serviced.filename);
661 free(cg->throttle_io_service_bytes.filename);
662 free(cg->throttle_io_serviced.filename);
663 free(cg->io_merged.filename);
664 free(cg->io_queued.filename);
671 // find if a given cgroup exists
672 struct cgroup *cgroup_find(const char *id) {
673 uint32_t hash = simple_hash(id);
675 // fprintf(stderr, " > searching for '%s' (%u)\n", id, hash);
678 for(cg = cgroup_root; cg ; cg = cg->next) {
679 if(hash == cg->hash && strcmp(id, cg->id) == 0)
686 // ----------------------------------------------------------------------------
687 // detect running cgroups
689 // callback for find_file_in_subdirs()
690 void found_dir_in_subdir(const char *dir) {
691 // fprintf(stderr, "found dir '%s'\n", dir);
693 struct cgroup *cg = cgroup_find(dir);
695 if(*dir && cgroup_max_depth > 0) {
699 for(s = dir; *s ;s++)
700 if(unlikely(*s == '/'))
703 if(depth > cgroup_max_depth) {
704 info("cgroup '%s' is too deep (%d, while max is %d)", dir, depth, cgroup_max_depth);
708 cg = cgroup_add(dir);
711 if(cg) cg->available = 1;
714 void find_dir_in_subdirs(const char *base, const char *this, void (*callback)(const char *)) {
715 if(!this) this = base;
716 size_t dirlen = strlen(this), baselen = strlen(base);
718 DIR *dir = opendir(this);
721 callback(&this[baselen]);
723 struct dirent *de = NULL;
724 while((de = readdir(dir))) {
725 if(de->d_type == DT_DIR
727 (de->d_name[0] == '.' && de->d_name[1] == '\0')
728 || (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')
732 // fprintf(stderr, "examining '%s/%s'\n", this, de->d_name);
734 if(de->d_type == DT_DIR) {
735 char *s = malloc(dirlen + strlen(de->d_name) + 2);
739 strcat(s, de->d_name);
740 find_dir_in_subdirs(base, s, callback);
749 void mark_all_cgroups_as_not_available() {
752 // mark all as not available
753 for(cg = cgroup_root; cg ; cg = cg->next)
757 struct cgroup *find_all_cgroups() {
759 mark_all_cgroups_as_not_available();
761 if(cgroup_enable_cpuacct_stat || cgroup_enable_cpuacct_usage)
762 find_dir_in_subdirs(cgroup_cpuacct_base, NULL, found_dir_in_subdir);
764 if(cgroup_enable_blkio)
765 find_dir_in_subdirs(cgroup_blkio_base, NULL, found_dir_in_subdir);
767 if(cgroup_enable_memory)
768 find_dir_in_subdirs(cgroup_memory_base, NULL, found_dir_in_subdir);
771 for(cg = cgroup_root; cg ; cg = cg->next) {
772 // fprintf(stderr, " >>> CGROUP '%s' (%u - %s) with name '%s'\n", cg->id, cg->hash, cg->available?"available":"stopped", cg->name);
779 // check for newly added cgroups
780 // and update the filenames they read
781 char filename[FILENAME_MAX + 1];
782 if(cgroup_enable_cpuacct_stat && !cg->cpuacct_stat.filename) {
783 snprintf(filename, FILENAME_MAX, "%s%s/cpuacct.stat", cgroup_cpuacct_base, cg->id);
784 cg->cpuacct_stat.filename = strdup(filename);
786 if(cgroup_enable_cpuacct_usage && !cg->cpuacct_usage.filename) {
787 snprintf(filename, FILENAME_MAX, "%s%s/cpuacct.usage_percpu", cgroup_cpuacct_base, cg->id);
788 cg->cpuacct_usage.filename = strdup(filename);
790 if(cgroup_enable_memory && !cg->memory.filename) {
791 snprintf(filename, FILENAME_MAX, "%s%s/memory.stat", cgroup_memory_base, cg->id);
792 cg->memory.filename = strdup(filename);
794 if(cgroup_enable_blkio) {
795 if(!cg->io_service_bytes.filename) {
796 snprintf(filename, FILENAME_MAX, "%s%s/blkio.io_service_bytes", cgroup_blkio_base, cg->id);
797 cg->io_service_bytes.filename = strdup(filename);
799 if(!cg->io_serviced.filename) {
800 snprintf(filename, FILENAME_MAX, "%s%s/blkio.io_serviced", cgroup_blkio_base, cg->id);
801 cg->io_serviced.filename = strdup(filename);
803 if(!cg->throttle_io_service_bytes.filename) {
804 snprintf(filename, FILENAME_MAX, "%s%s/blkio.throttle.io_service_bytes", cgroup_blkio_base, cg->id);
805 cg->throttle_io_service_bytes.filename = strdup(filename);
807 if(!cg->throttle_io_serviced.filename) {
808 snprintf(filename, FILENAME_MAX, "%s%s/blkio.throttle.io_serviced", cgroup_blkio_base, cg->id);
809 cg->throttle_io_serviced.filename = strdup(filename);
811 if(!cg->io_merged.filename) {
812 snprintf(filename, FILENAME_MAX, "%s%s/blkio.io_merged", cgroup_blkio_base, cg->id);
813 cg->io_merged.filename = strdup(filename);
815 if(!cg->io_queued.filename) {
816 snprintf(filename, FILENAME_MAX, "%s%s/blkio.io_queued", cgroup_blkio_base, cg->id);
817 cg->io_queued.filename = strdup(filename);
825 // ----------------------------------------------------------------------------
828 #define CHART_TITLE_MAX 300
830 void update_cgroup_charts(int update_every) {
831 char type[RRD_ID_LENGTH_MAX + 1];
832 char title[CHART_TITLE_MAX + 1];
837 for(cg = cgroup_root; cg ; cg = cg->next) {
838 if(!cg->available) continue;
840 if(cg->id[0] == '\0')
841 strcpy(type, "cgroup_host");
842 else if(cg->id[0] == '/')
843 snprintf(type, RRD_ID_LENGTH_MAX, "cgroup_%s", cg->name);
845 snprintf(type, RRD_ID_LENGTH_MAX, "cgroup_%s", cg->name);
847 netdata_fix_chart_id(type);
849 if(cg->cpuacct_stat.updated) {
850 st = rrdset_find_bytype(type, "cpu");
852 snprintf(title, CHART_TITLE_MAX, "CPU Usage for cgroup %s", cg->name);
853 st = rrdset_create(type, "cpu", NULL, "cpu", "cgroup.cpu", title, "%", 40000, update_every, RRDSET_TYPE_STACKED);
855 rrddim_add(st, "user", NULL, 100, hz, RRDDIM_INCREMENTAL);
856 rrddim_add(st, "system", NULL, 100, hz, RRDDIM_INCREMENTAL);
858 else rrdset_next(st);
860 rrddim_set(st, "user", cg->cpuacct_stat.user);
861 rrddim_set(st, "system", cg->cpuacct_stat.system);
865 if(cg->cpuacct_usage.updated) {
866 char id[RRD_ID_LENGTH_MAX + 1];
869 st = rrdset_find_bytype(type, "cpu_per_core");
871 snprintf(title, CHART_TITLE_MAX, "CPU Usage Per Core for cgroup %s", cg->name);
872 st = rrdset_create(type, "cpu_per_core", NULL, "cpu", "cgroup.cpu_per_core", title, "%", 40100, update_every, RRDSET_TYPE_STACKED);
874 for(i = 0; i < cg->cpuacct_usage.cpus ;i++) {
875 snprintf(id, CHART_TITLE_MAX, "cpu%d", i);
876 rrddim_add(st, id, NULL, 100, 1000000, RRDDIM_INCREMENTAL);
879 else rrdset_next(st);
881 for(i = 0; i < cg->cpuacct_usage.cpus ;i++) {
882 snprintf(id, CHART_TITLE_MAX, "cpu%d", i);
883 rrddim_set(st, id, cg->cpuacct_usage.cpu_percpu[i]);
888 if(cg->memory.updated) {
889 if(cg->memory.cache + cg->memory.rss + cg->memory.rss_huge + cg->memory.mapped_file > 0) {
890 st = rrdset_find_bytype(type, "mem");
892 snprintf(title, CHART_TITLE_MAX, "Memory Usage for cgroup %s", cg->name);
893 st = rrdset_create(type, "mem", NULL, "mem", "cgroup.mem", title, "MB", 40200, update_every,
894 RRDSET_TYPE_STACKED);
896 rrddim_add(st, "cache", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
897 rrddim_add(st, "rss", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
898 if(cg->memory.has_dirty_swap)
899 rrddim_add(st, "swap", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
900 rrddim_add(st, "rss_huge", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
901 rrddim_add(st, "mapped_file", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
903 else rrdset_next(st);
905 rrddim_set(st, "cache", cg->memory.cache);
906 rrddim_set(st, "rss", cg->memory.rss);
907 if(cg->memory.has_dirty_swap)
908 rrddim_set(st, "swap", cg->memory.swap);
909 rrddim_set(st, "rss_huge", cg->memory.rss_huge);
910 rrddim_set(st, "mapped_file", cg->memory.mapped_file);
914 st = rrdset_find_bytype(type, "writeback");
916 snprintf(title, CHART_TITLE_MAX, "Writeback Memory for cgroup %s", cg->name);
917 st = rrdset_create(type, "writeback", NULL, "mem", "cgroup.writeback", title, "MB", 40300,
918 update_every, RRDSET_TYPE_AREA);
920 if(cg->memory.has_dirty_swap)
921 rrddim_add(st, "dirty", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
922 rrddim_add(st, "writeback", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
924 else rrdset_next(st);
926 if(cg->memory.has_dirty_swap)
927 rrddim_set(st, "dirty", cg->memory.dirty);
928 rrddim_set(st, "writeback", cg->memory.writeback);
931 if(cg->memory.pgpgin + cg->memory.pgpgout > 0) {
932 st = rrdset_find_bytype(type, "mem_activity");
934 snprintf(title, CHART_TITLE_MAX, "Memory Activity for cgroup %s", cg->name);
935 st = rrdset_create(type, "mem_activity", NULL, "mem", "cgroup.mem_activity", title, "MB/s",
936 40400, update_every, RRDSET_TYPE_LINE);
938 rrddim_add(st, "pgpgin", "in", sysconf(_SC_PAGESIZE), 1024 * 1024, RRDDIM_INCREMENTAL);
939 rrddim_add(st, "pgpgout", "out", -sysconf(_SC_PAGESIZE), 1024 * 1024, RRDDIM_INCREMENTAL);
941 else rrdset_next(st);
943 rrddim_set(st, "pgpgin", cg->memory.pgpgin);
944 rrddim_set(st, "pgpgout", cg->memory.pgpgout);
948 if(cg->memory.pgfault + cg->memory.pgmajfault > 0) {
949 st = rrdset_find_bytype(type, "pgfaults");
951 snprintf(title, CHART_TITLE_MAX, "Memory Page Faults for cgroup %s", cg->name);
952 st = rrdset_create(type, "pgfaults", NULL, "mem", "cgroup.pgfaults", title, "MB/s", 40500,
953 update_every, RRDSET_TYPE_LINE);
955 rrddim_add(st, "pgfault", NULL, sysconf(_SC_PAGESIZE), 1024 * 1024, RRDDIM_INCREMENTAL);
956 rrddim_add(st, "pgmajfault", "swap", -sysconf(_SC_PAGESIZE), 1024 * 1024, RRDDIM_INCREMENTAL);
958 else rrdset_next(st);
960 rrddim_set(st, "pgfault", cg->memory.pgfault);
961 rrddim_set(st, "pgmajfault", cg->memory.pgmajfault);
966 if(cg->io_service_bytes.updated && cg->io_service_bytes.Read + cg->io_service_bytes.Write > 0) {
967 st = rrdset_find_bytype(type, "io");
969 snprintf(title, CHART_TITLE_MAX, "I/O Bandwidth (all disks) for cgroup %s", cg->name);
970 st = rrdset_create(type, "io", NULL, "disk", "cgroup.io", title, "KB/s", 41200,
971 update_every, RRDSET_TYPE_LINE);
973 rrddim_add(st, "read", NULL, 1, 1024, RRDDIM_INCREMENTAL);
974 rrddim_add(st, "write", NULL, -1, 1024, RRDDIM_INCREMENTAL);
976 else rrdset_next(st);
978 rrddim_set(st, "read", cg->io_service_bytes.Read);
979 rrddim_set(st, "write", cg->io_service_bytes.Write);
983 if(cg->io_serviced.updated && cg->io_serviced.Read + cg->io_serviced.Write > 0) {
984 st = rrdset_find_bytype(type, "serviced_ops");
986 snprintf(title, CHART_TITLE_MAX, "Serviced I/O Operations (all disks) for cgroup %s", cg->name);
987 st = rrdset_create(type, "serviced_ops", NULL, "disk", "cgroup.serviced_ops", title, "operations/s", 41200,
988 update_every, RRDSET_TYPE_LINE);
990 rrddim_add(st, "read", NULL, 1, 1, RRDDIM_INCREMENTAL);
991 rrddim_add(st, "write", NULL, -1, 1, RRDDIM_INCREMENTAL);
993 else rrdset_next(st);
995 rrddim_set(st, "read", cg->io_serviced.Read);
996 rrddim_set(st, "write", cg->io_serviced.Write);
1000 if(cg->throttle_io_service_bytes.updated && cg->throttle_io_service_bytes.Read + cg->throttle_io_service_bytes.Write > 0) {
1001 st = rrdset_find_bytype(type, "io");
1003 snprintf(title, CHART_TITLE_MAX, "Throttle I/O Bandwidth (all disks) for cgroup %s", cg->name);
1004 st = rrdset_create(type, "io", NULL, "disk", "cgroup.io", title, "KB/s", 41200,
1005 update_every, RRDSET_TYPE_LINE);
1007 rrddim_add(st, "read", NULL, 1, 1024, RRDDIM_INCREMENTAL);
1008 rrddim_add(st, "write", NULL, -1, 1024, RRDDIM_INCREMENTAL);
1010 else rrdset_next(st);
1012 rrddim_set(st, "read", cg->throttle_io_service_bytes.Read);
1013 rrddim_set(st, "write", cg->throttle_io_service_bytes.Write);
1018 if(cg->throttle_io_serviced.updated && cg->throttle_io_serviced.Read + cg->throttle_io_serviced.Write > 0) {
1019 st = rrdset_find_bytype(type, "throttle_serviced_ops");
1021 snprintf(title, CHART_TITLE_MAX, "Throttle Serviced I/O Operations (all disks) for cgroup %s", cg->name);
1022 st = rrdset_create(type, "throttle_serviced_ops", NULL, "disk", "cgroup.throttle_serviced_ops", title, "operations/s", 41200,
1023 update_every, RRDSET_TYPE_LINE);
1025 rrddim_add(st, "read", NULL, 1, 1, RRDDIM_INCREMENTAL);
1026 rrddim_add(st, "write", NULL, -1, 1, RRDDIM_INCREMENTAL);
1028 else rrdset_next(st);
1030 rrddim_set(st, "read", cg->throttle_io_serviced.Read);
1031 rrddim_set(st, "write", cg->throttle_io_serviced.Write);
1035 if(cg->io_queued.updated) {
1036 st = rrdset_find_bytype(type, "queued_ops");
1038 snprintf(title, CHART_TITLE_MAX, "Queued I/O Operations (all disks) for cgroup %s", cg->name);
1039 st = rrdset_create(type, "queued_ops", NULL, "disk", "cgroup.queued_ops", title, "operations", 42000,
1040 update_every, RRDSET_TYPE_LINE);
1042 rrddim_add(st, "read", NULL, 1, 1, RRDDIM_ABSOLUTE);
1043 rrddim_add(st, "write", NULL, -1, 1, RRDDIM_ABSOLUTE);
1045 else rrdset_next(st);
1047 rrddim_set(st, "read", cg->io_queued.Read);
1048 rrddim_set(st, "write", cg->io_queued.Write);
1052 if(cg->io_merged.updated && cg->io_merged.Read + cg->io_merged.Write > 0) {
1053 st = rrdset_find_bytype(type, "merged_ops");
1055 snprintf(title, CHART_TITLE_MAX, "Merged I/O Operations (all disks) for cgroup %s", cg->name);
1056 st = rrdset_create(type, "merged_ops", NULL, "disk", "cgroup.merged_ops", title, "operations/s", 42100,
1057 update_every, RRDSET_TYPE_LINE);
1059 rrddim_add(st, "read", NULL, 1, 1024, RRDDIM_INCREMENTAL);
1060 rrddim_add(st, "write", NULL, -1, 1024, RRDDIM_INCREMENTAL);
1062 else rrdset_next(st);
1064 rrddim_set(st, "read", cg->io_merged.Read);
1065 rrddim_set(st, "write", cg->io_merged.Write);
1071 // ----------------------------------------------------------------------------
1074 int do_sys_fs_cgroup(int update_every, unsigned long long dt) {
1075 static int cgroup_global_config_read = 0;
1076 static time_t last_run = 0;
1077 time_t now = time(NULL);
1081 if(unlikely(!cgroup_global_config_read)) {
1082 read_cgroup_plugin_configuration();
1083 cgroup_global_config_read = 1;
1086 if(unlikely(cgroup_enable_new_cgroups_detected_at_runtime && now - last_run > cgroup_check_for_new_every)) {
1091 read_all_cgroups(cgroup_root);
1092 update_cgroup_charts(update_every);
1097 void *cgroups_main(void *ptr)
1101 info("CGROUP Plugin thread created with task id %d", gettid());
1103 if(pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL) != 0)
1104 error("Cannot set pthread cancel type to DEFERRED.");
1106 if(pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL) != 0)
1107 error("Cannot set pthread cancel state to ENABLE.");
1109 struct rusage thread;
1111 // when ZERO, attempt to do it
1112 int vdo_sys_fs_cgroup = 0;
1113 int vdo_cpu_netdata = !config_get_boolean("plugin:cgroups", "netdata server resources", 1);
1115 // keep track of the time each module was called
1116 unsigned long long sutime_sys_fs_cgroup = 0ULL;
1118 // the next time we will run - aligned properly
1119 unsigned long long sunext = (time(NULL) - (time(NULL) % rrd_update_every) + rrd_update_every) * 1000000ULL;
1120 unsigned long long sunow;
1122 RRDSET *stcpu_thread = NULL;
1125 if(unlikely(netdata_exit)) break;
1127 // delay until it is our time to run
1128 while((sunow = timems()) < sunext)
1129 usleep((useconds_t)(sunext - sunow));
1131 // find the next time we need to run
1132 while(timems() > sunext)
1133 sunext += rrd_update_every * 1000000ULL;
1135 if(unlikely(netdata_exit)) break;
1137 // BEGIN -- the job to be done
1139 if(!vdo_sys_fs_cgroup) {
1140 debug(D_PROCNETDEV_LOOP, "PROCNETDEV: calling do_sys_fs_cgroup().");
1142 vdo_sys_fs_cgroup = do_sys_fs_cgroup(rrd_update_every, (sutime_sys_fs_cgroup > 0)?sunow - sutime_sys_fs_cgroup:0ULL);
1143 sutime_sys_fs_cgroup = sunow;
1145 if(unlikely(netdata_exit)) break;
1147 // END -- the job is done
1149 // --------------------------------------------------------------------
1151 if(!vdo_cpu_netdata) {
1152 getrusage(RUSAGE_THREAD, &thread);
1154 if(!stcpu_thread) stcpu_thread = rrdset_find("netdata.plugin_cgroups_cpu");
1156 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);
1158 rrddim_add(stcpu_thread, "user", NULL, 1, 1000, RRDDIM_INCREMENTAL);
1159 rrddim_add(stcpu_thread, "system", NULL, 1, 1000, RRDDIM_INCREMENTAL);
1161 else rrdset_next(stcpu_thread);
1163 rrddim_set(stcpu_thread, "user" , thread.ru_utime.tv_sec * 1000000ULL + thread.ru_utime.tv_usec);
1164 rrddim_set(stcpu_thread, "system", thread.ru_stime.tv_sec * 1000000ULL + thread.ru_stime.tv_usec);
1165 rrdset_done(stcpu_thread);