]> arthur.barton.de Git - netdata.git/blob - src/sys_fs_cgroup.c
added support for monitoring cgroups
[netdata.git] / src / sys_fs_cgroup.c
1 #ifdef HAVE_CONFIG_H
2 #include <config.h>
3 #endif
4
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <inttypes.h>
8 #include <sys/types.h>
9 #include <dirent.h>
10 #include <string.h>
11
12 #include "common.h"
13 #include "appconfig.h"
14 #include "procfile.h"
15 #include "log.h"
16 #include "rrd.h"
17
18 // ----------------------------------------------------------------------------
19 // cgroup globals
20
21 static int cgroup_enable_cpuacct_stat = CONFIG_ONDEMAND_ONDEMAND;
22 static int cgroup_enable_cpuacct_usage = CONFIG_ONDEMAND_ONDEMAND;
23 static int cgroup_enable_memory = CONFIG_ONDEMAND_ONDEMAND;
24 static int cgroup_enable_blkio = CONFIG_ONDEMAND_ONDEMAND;
25 static int cgroup_enable_new_cgroups_detected_at_runtime = 1;
26 static int cgroup_check_for_new_every = 10;
27 static char *cgroup_cpuacct_base = NULL;
28 static char *cgroup_blkio_base = NULL;
29 static char *cgroup_memory_base = NULL;
30
31 static int cgroup_root_count = 0;
32 static int cgroup_root_max = 50;
33 static int cgroup_max_depth = 0;
34
35 void read_cgroup_plugin_configuration() {
36         cgroup_check_for_new_every = config_get_number("plugin:cgroups", "check for new plugin every", cgroup_check_for_new_every);
37
38         cgroup_enable_cpuacct_stat = config_get_boolean_ondemand("plugin:cgroups", "enable cpuacct stat", cgroup_enable_cpuacct_stat);
39         cgroup_enable_cpuacct_usage = config_get_boolean_ondemand("plugin:cgroups", "enable cpuacct usage", cgroup_enable_cpuacct_usage);
40         cgroup_enable_memory = config_get_boolean_ondemand("plugin:cgroups", "enable memory", cgroup_enable_memory);
41         cgroup_enable_blkio = config_get_boolean_ondemand("plugin:cgroups", "enable blkio", cgroup_enable_blkio);
42
43         char filename[FILENAME_MAX + 1];
44         snprintf(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/sys/fs/cgroup/cpuacct");
45         cgroup_cpuacct_base = config_get("plugin:cgroups", "path to /sys/fs/cgroup/cpuacct", filename);
46
47         snprintf(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/sys/fs/cgroup/blkio");
48         cgroup_blkio_base = config_get("plugin:cgroups", "path to /sys/fs/cgroup/blkio", filename);
49
50         snprintf(filename, FILENAME_MAX, "%s%s", global_host_prefix, "/sys/fs/cgroup/memory");
51         cgroup_memory_base = config_get("plugin:cgroups", "path to /sys/fs/cgroup/memory", filename);
52
53         cgroup_root_max = config_get_number("plugin:cgroups", "max cgroups to allow", cgroup_root_max);
54         cgroup_max_depth = config_get_number("plugin:cgroups", "max cgroups depth to monitor", cgroup_max_depth);
55
56         cgroup_enable_new_cgroups_detected_at_runtime = config_get_boolean("plugin:cgroups", "enable cgroups detected after first run", cgroup_enable_new_cgroups_detected_at_runtime);
57 }
58
59 // ----------------------------------------------------------------------------
60 // cgroup objects
61
62 struct blkio {
63         int updated;
64
65         char *filename;
66
67         unsigned long long Read;
68         unsigned long long Write;
69         unsigned long long Sync;
70         unsigned long long Async;
71         unsigned long long Total;
72 };
73
74 // https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt
75 struct memory {
76         int updated;
77
78         char *filename;
79
80         unsigned long long cache;
81         unsigned long long rss;
82         unsigned long long rss_huge;
83         unsigned long long mapped_file;
84         unsigned long long writeback;
85         unsigned long long pgpgin;
86         unsigned long long pgpgout;
87         unsigned long long pgfault;
88         unsigned long long pgmajfault;
89 /*      unsigned long long inactive_anon;
90         unsigned long long active_anon;
91         unsigned long long inactive_file;
92         unsigned long long active_file;
93         unsigned long long unevictable;
94         unsigned long long hierarchical_memory_limit;
95         unsigned long long total_cache;
96         unsigned long long total_rss;
97         unsigned long long total_rss_huge;
98         unsigned long long total_mapped_file;
99         unsigned long long total_writeback;
100         unsigned long long total_pgpgin;
101         unsigned long long total_pgpgout;
102         unsigned long long total_pgfault;
103         unsigned long long total_pgmajfault;
104         unsigned long long total_inactive_anon;
105         unsigned long long total_active_anon;
106         unsigned long long total_inactive_file;
107         unsigned long long total_active_file;
108         unsigned long long total_unevictable;
109 */
110 };
111
112 // https://www.kernel.org/doc/Documentation/cgroup-v1/cpuacct.txt
113 struct cpuacct_stat {
114         int updated;
115
116         char *filename;
117
118         unsigned long long user;
119         unsigned long long system;
120 };
121
122 // https://www.kernel.org/doc/Documentation/cgroup-v1/cpuacct.txt
123 struct cpuacct_usage {
124         int updated;
125
126         char *filename;
127
128         unsigned int cpus;
129         unsigned long long *cpu_percpu;
130 };
131
132 struct cgroup {
133         int available;
134         int enabled;
135
136         char *id;
137         uint32_t hash;
138
139         char *name;
140
141         struct cpuacct_stat cpuacct_stat;
142         struct cpuacct_usage cpuacct_usage;
143
144         struct memory memory;
145
146         struct blkio io_service_bytes;                          // bytes
147         struct blkio io_serviced;                                       // operations
148
149         struct blkio throttle_io_service_bytes;         // bytes
150         struct blkio throttle_io_serviced;                      // operations
151
152         struct blkio io_merged;                                         // operations
153         struct blkio io_queued;                                         // operations
154
155         struct cgroup *next;
156
157 } *cgroup_root = NULL;
158
159 // ----------------------------------------------------------------------------
160 // read values from /sys
161
162 void cgroup_read_cpuacct_stat(struct cpuacct_stat *cp) {
163         static procfile *ff = NULL;
164
165         static uint32_t user_hash = 0;
166         static uint32_t system_hash = 0;
167
168         if(unlikely(user_hash == 0)) {
169                 user_hash = simple_hash("user");
170                 system_hash = simple_hash("system");
171         }
172
173         cp->updated = 0;
174         if(cp->filename) {
175                 ff = procfile_reopen(ff, cp->filename, NULL, PROCFILE_FLAG_DEFAULT);
176                 if(!ff) return;
177
178                 ff = procfile_readall(ff);
179                 if(!ff) return;
180
181                 unsigned long i, lines = procfile_lines(ff);
182
183                 if(lines < 1) {
184                         error("File '%s' should have 1+ lines.", cp->filename);
185                         return;
186                 }
187
188                 for(i = 0; i < lines ; i++) {
189                         char *s = procfile_lineword(ff, i, 0);
190                         uint32_t hash = simple_hash(s);
191
192                         if(hash == user_hash && !strcmp(s, "user"))
193                                 cp->user = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
194
195                         else if(hash == system_hash && !strcmp(s, "system"))
196                                 cp->system = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
197                 }
198
199                 cp->updated = 1;
200
201                 // fprintf(stderr, "READ '%s': user: %llu, system: %llu\n", cp->filename, cp->user, cp->system);
202         }
203 }
204
205 void cgroup_read_cpuacct_usage(struct cpuacct_usage *ca) {
206         static procfile *ff = NULL;
207
208         ca->updated = 0;
209         if(ca->filename) {
210                 ff = procfile_reopen(ff, ca->filename, NULL, PROCFILE_FLAG_DEFAULT);
211                 if(!ff) return;
212
213                 ff = procfile_readall(ff);
214                 if(!ff) return;
215
216                 if(procfile_lines(ff) < 1) {
217                         error("File '%s' should have 1+ lines but has %d.", ca->filename, procfile_lines(ff));
218                         return;
219                 }
220
221                 unsigned long i = procfile_linewords(ff, 0);
222                 if(i <= 0) return;
223
224                 // we may have 1 more CPU reported
225                 while(i > 0) {
226                         char *s = procfile_lineword(ff, 0, i - 1);
227                         if(!*s) i--;
228                         else break;
229                 }
230
231                 if(i != ca->cpus) {
232                         free(ca->cpu_percpu);
233                 }
234                 ca->cpu_percpu = malloc(sizeof(unsigned long long) * i);
235                 if(!ca->cpu_percpu)
236                         fatal("Cannot allocate memory (%z bytes)", sizeof(unsigned long long) * i);
237
238                 ca->cpus = i;
239
240                 for(i = 0; i < ca->cpus ;i++) {
241                         ca->cpu_percpu[i] = strtoull(procfile_lineword(ff, 0, i), NULL, 10);
242                         // fprintf(stderr, "READ '%s': cpu%d/%d: %llu ('%s')\n", ca->filename, i, ca->cpus, ca->cpu_percpu[i], procfile_lineword(ff, 0, i));
243                 }
244
245                 ca->updated = 1;
246         }
247 }
248
249 void cgroup_read_blkio(struct blkio *io) {
250         static procfile *ff = NULL;
251
252         static uint32_t Read_hash = 0;
253         static uint32_t Write_hash = 0;
254         static uint32_t Sync_hash = 0;
255         static uint32_t Async_hash = 0;
256         static uint32_t Total_hash = 0;
257
258         if(unlikely(Read_hash == 0)) {
259                 Read_hash = simple_hash("Read");
260                 Write_hash = simple_hash("Write");
261                 Sync_hash = simple_hash("Sync");
262                 Async_hash = simple_hash("Async");
263                 Total_hash = simple_hash("Total");
264         }
265
266         io->updated = 0;
267         if(io->filename) {
268                 ff = procfile_reopen(ff, io->filename, NULL, PROCFILE_FLAG_DEFAULT);
269                 if(!ff) return;
270
271                 ff = procfile_readall(ff);
272                 if(!ff) return;
273
274                 unsigned long i, lines = procfile_lines(ff);
275
276                 if(lines < 1) {
277                         error("File '%s' should have 1+ lines.", io->filename);
278                         return;
279                 }
280
281                 io->Read = 0;
282                 io->Write = 0;
283                 io->Sync = 0;
284                 io->Async = 0;
285                 io->Total = 0;
286
287                 for(i = 0; i < lines ; i++) {
288                         char *s = procfile_lineword(ff, i, 1);
289                         uint32_t hash = simple_hash(s);
290
291                         if(hash == Read_hash && !strcmp(s, "Read"))
292                                 io->Read += strtoull(procfile_lineword(ff, i, 2), NULL, 10);
293
294                         else if(hash == Write_hash && !strcmp(s, "Write"))
295                                 io->Write += strtoull(procfile_lineword(ff, i, 2), NULL, 10);
296
297                         else if(hash == Sync_hash && !strcmp(s, "Sync"))
298                                 io->Sync += strtoull(procfile_lineword(ff, i, 2), NULL, 10);
299
300                         else if(hash == Async_hash && !strcmp(s, "Async"))
301                                 io->Async += strtoull(procfile_lineword(ff, i, 2), NULL, 10);
302
303                         else if(hash == Total_hash && !strcmp(s, "Total"))
304                                 io->Total += strtoull(procfile_lineword(ff, i, 2), NULL, 10);
305                 }
306
307                 io->updated = 1;
308                 // 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);
309         }
310 }
311
312 void cgroup_read_memory(struct memory *mem) {
313         static procfile *ff = NULL;
314
315         static uint32_t cache_hash = 0;
316         static uint32_t rss_hash = 0;
317         static uint32_t rss_huge_hash = 0;
318         static uint32_t mapped_file_hash = 0;
319         static uint32_t writeback_hash = 0;
320         static uint32_t pgpgin_hash = 0;
321         static uint32_t pgpgout_hash = 0;
322         static uint32_t pgfault_hash = 0;
323         static uint32_t pgmajfault_hash = 0;
324 /*      static uint32_t inactive_anon_hash = 0;
325         static uint32_t active_anon_hash = 0;
326         static uint32_t inactive_file_hash = 0;
327         static uint32_t active_file_hash = 0;
328         static uint32_t unevictable_hash = 0;
329         static uint32_t hierarchical_memory_limit_hash = 0;
330         static uint32_t total_cache_hash = 0;
331         static uint32_t total_rss_hash = 0;
332         static uint32_t total_rss_huge_hash = 0;
333         static uint32_t total_mapped_file_hash = 0;
334         static uint32_t total_writeback_hash = 0;
335         static uint32_t total_pgpgin_hash = 0;
336         static uint32_t total_pgpgout_hash = 0;
337         static uint32_t total_pgfault_hash = 0;
338         static uint32_t total_pgmajfault_hash = 0;
339         static uint32_t total_inactive_anon_hash = 0;
340         static uint32_t total_active_anon_hash = 0;
341         static uint32_t total_inactive_file_hash = 0;
342         static uint32_t total_active_file_hash = 0;
343         static uint32_t total_unevictable_hash = 0;
344 */
345         if(unlikely(cache_hash == 0)) {
346                 cache_hash = simple_hash("cache");
347                 rss_hash = simple_hash("rss");
348                 rss_huge_hash = simple_hash("rss_huge");
349                 mapped_file_hash = simple_hash("mapped_file");
350                 writeback_hash = simple_hash("writeback");
351                 pgpgin_hash = simple_hash("pgpgin");
352                 pgpgout_hash = simple_hash("pgpgout");
353                 pgfault_hash = simple_hash("pgfault");
354                 pgmajfault_hash = simple_hash("pgmajfault");
355 /*              inactive_anon_hash = simple_hash("inactive_anon");
356                 active_anon_hash = simple_hash("active_anon");
357                 inactive_file_hash = simple_hash("inactive_file");
358                 active_file_hash = simple_hash("active_file");
359                 unevictable_hash = simple_hash("unevictable");
360                 hierarchical_memory_limit_hash = simple_hash("hierarchical_memory_limit");
361                 total_cache_hash = simple_hash("total_cache");
362                 total_rss_hash = simple_hash("total_rss");
363                 total_rss_huge_hash = simple_hash("total_rss_huge");
364                 total_mapped_file_hash = simple_hash("total_mapped_file");
365                 total_writeback_hash = simple_hash("total_writeback");
366                 total_pgpgin_hash = simple_hash("total_pgpgin");
367                 total_pgpgout_hash = simple_hash("total_pgpgout");
368                 total_pgfault_hash = simple_hash("total_pgfault");
369                 total_pgmajfault_hash = simple_hash("total_pgmajfault");
370                 total_inactive_anon_hash = simple_hash("total_inactive_anon");
371                 total_active_anon_hash = simple_hash("total_active_anon");
372                 total_inactive_file_hash = simple_hash("total_inactive_file");
373                 total_active_file_hash = simple_hash("total_active_file");
374                 total_unevictable_hash = simple_hash("total_unevictable");
375 */
376         }
377
378         mem->updated = 0;
379         if(mem->filename) {
380                 ff = procfile_reopen(ff, mem->filename, NULL, PROCFILE_FLAG_DEFAULT);
381                 if(!ff) return;
382
383                 ff = procfile_readall(ff);
384                 if(!ff) return;
385
386                 unsigned long i, lines = procfile_lines(ff);
387
388                 if(lines < 1) {
389                         error("File '%s' should have 1+ lines.", mem->filename);
390                         return;
391                 }
392
393                 for(i = 0; i < lines ; i++) {
394                         char *s = procfile_lineword(ff, i, 0);
395                         uint32_t hash = simple_hash(s);
396
397                         if(hash == cache_hash && !strcmp(s, "cache"))
398                                 mem->cache = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
399
400                         else if(hash == rss_hash && !strcmp(s, "rss"))
401                                 mem->rss = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
402
403                         else if(hash == rss_huge_hash && !strcmp(s, "rss_huge"))
404                                 mem->rss_huge = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
405
406                         else if(hash == mapped_file_hash && !strcmp(s, "mapped_file"))
407                                 mem->mapped_file = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
408
409                         else if(hash == writeback_hash && !strcmp(s, "writeback"))
410                                 mem->writeback = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
411
412                         else if(hash == pgpgin_hash && !strcmp(s, "pgpgin"))
413                                 mem->pgpgin = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
414
415                         else if(hash == pgpgout_hash && !strcmp(s, "pgpgout"))
416                                 mem->pgpgout = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
417
418                         else if(hash == pgfault_hash && !strcmp(s, "pgfault"))
419                                 mem->pgfault = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
420
421                         else if(hash == pgmajfault_hash && !strcmp(s, "pgmajfault"))
422                                 mem->pgmajfault = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
423
424 /*                      else if(hash == inactive_anon_hash && !strcmp(s, "inactive_anon"))
425                                 mem->inactive_anon = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
426
427                         else if(hash == active_anon_hash && !strcmp(s, "active_anon"))
428                                 mem->active_anon = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
429
430                         else if(hash == inactive_file_hash && !strcmp(s, "inactive_file"))
431                                 mem->inactive_file = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
432
433                         else if(hash == active_file_hash && !strcmp(s, "active_file"))
434                                 mem->active_file = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
435
436                         else if(hash == unevictable_hash && !strcmp(s, "unevictable"))
437                                 mem->unevictable = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
438
439                         else if(hash == hierarchical_memory_limit_hash && !strcmp(s, "hierarchical_memory_limit"))
440                                 mem->hierarchical_memory_limit = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
441
442                 else if(hash == total_cache_hash && !strcmp(s, "total_cache"))
443                                 mem->total_cache = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
444
445                         else if(hash == total_rss_hash && !strcmp(s, "total_rss"))
446                                 mem->total_rss = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
447
448                         else if(hash == total_rss_huge_hash && !strcmp(s, "total_rss_huge"))
449                                 mem->total_rss_huge = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
450
451                         else if(hash == total_mapped_file_hash && !strcmp(s, "total_mapped_file"))
452                                 mem->total_mapped_file = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
453
454                         else if(hash == total_writeback_hash && !strcmp(s, "total_writeback"))
455                                 mem->total_writeback = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
456
457                         else if(hash == total_pgpgin_hash && !strcmp(s, "total_pgpgin"))
458                                 mem->total_pgpgin = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
459
460                         else if(hash == total_pgpgout_hash && !strcmp(s, "total_pgpgout"))
461                                 mem->total_pgpgout = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
462
463                         else if(hash == total_pgfault_hash && !strcmp(s, "total_pgfault"))
464                                 mem->total_pgfault = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
465
466                         else if(hash == total_pgmajfault_hash && !strcmp(s, "total_pgmajfault"))
467                                 mem->total_pgmajfault = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
468
469                         else if(hash == total_inactive_anon_hash && !strcmp(s, "total_inactive_anon"))
470                                 mem->total_inactive_anon = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
471
472                         else if(hash == total_active_anon_hash && !strcmp(s, "total_active_anon"))
473                                 mem->total_active_anon = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
474
475                         else if(hash == total_inactive_file_hash && !strcmp(s, "total_inactive_file"))
476                                 mem->total_inactive_file = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
477
478                         else if(hash == total_active_file_hash && !strcmp(s, "total_active_file"))
479                                 mem->total_active_file = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
480
481                         else if(hash == total_unevictable_hash && !strcmp(s, "total_unevictable"))
482                                 mem->total_unevictable = strtoull(procfile_lineword(ff, i, 1), NULL, 10);
483 */
484                 }
485
486                 // fprintf(stderr, "READ: '%s', cache: %llu, rss: %llu, rss_huge: %llu, mapped_file: %llu, writeback: %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_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->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_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);
487
488                 mem->updated = 1;
489         }
490 }
491
492 void cgroup_read(struct cgroup *cg) {
493         cgroup_read_cpuacct_stat(&cg->cpuacct_stat);
494         cgroup_read_cpuacct_usage(&cg->cpuacct_usage);
495         cgroup_read_memory(&cg->memory);
496         cgroup_read_blkio(&cg->io_service_bytes);
497         cgroup_read_blkio(&cg->io_serviced);
498         cgroup_read_blkio(&cg->throttle_io_service_bytes);
499         cgroup_read_blkio(&cg->throttle_io_serviced);
500         cgroup_read_blkio(&cg->io_merged);
501         cgroup_read_blkio(&cg->io_queued);
502 }
503
504 void read_all_cgroups(struct cgroup *cg) {
505         struct cgroup *i;
506
507         for(i = cg; i ; i = i->next)
508                 cgroup_read(i);
509 }
510
511 // ----------------------------------------------------------------------------
512 // add/remove/find cgroup objects
513
514 struct cgroup *cgroup_add(const char *id) {
515         if(cgroup_root_count >= cgroup_root_max) {
516                 info("Maximum number of cgroups reached (%d). Not adding cgroup '%s'", cgroup_root_count, id);
517                 return NULL;
518         }
519
520         int def = cgroup_enable_new_cgroups_detected_at_runtime;
521         const char *name = id;
522         if(!*name) {
523                 name = "/";
524
525                 // disable by default the host cgroup
526                 def = 0;
527         }
528         else {
529                 if(*name == '/') name++;
530
531                 // disable by default the parent cgroup
532                 // for known cgroup managers
533                 if(!strcmp(name, "lxc") || !strcmp(name, "docker"))
534                         def = 0;
535         }
536
537         char option[FILENAME_MAX + 1];
538         snprintf(option, FILENAME_MAX, "enable cgroup %s", name);
539         if(!config_get_boolean("plugin:cgroups", option, def))
540                 return NULL;
541
542         struct cgroup *cg = calloc(1, sizeof(struct cgroup));
543         if(cg) {
544                 cg->id = strdup(id);
545                 if(!cg->id) fatal("Cannot allocate memory for cgroup '%s'", id);
546
547                 cg->hash = simple_hash(cg->id);
548
549                 cg->name = strdup(name);
550                 if(!cg->name) fatal("Cannot allocate memory for cgroup '%s'", id);
551
552                 if(!cgroup_root)
553                         cgroup_root = cg;
554                 else {
555                         // append it
556                         struct cgroup *e;
557                         for(e = cgroup_root; e->next ;e = e->next) ;
558                         e->next = cg;
559                 }
560
561                 cgroup_root_count++;
562
563                 // fprintf(stderr, " > added cgroup No %d, with id '%s' (%u) and name '%s'\n", cgroup_root_count, cg->id, cg->hash, cg->name);
564         }
565         else fatal("Cannot allocate memory for cgroup '%s'", id);
566
567         return cg;
568 }
569
570 void cgroup_remove(struct cgroup *cg) {
571         if(cg == cgroup_root) {
572                 cgroup_root = cg->next;
573         }
574         else {
575                 struct cgroup *e;
576                 for(e = cgroup_root; e->next ;e = e->next)
577                         if(unlikely(e->next == cg)) break;
578
579                 if(e->next != cg) {
580                         error("Cannot find cgroup '%s' in list of cgroups", cg->id);
581                 }
582                 else {
583                         e->next = cg->next;
584                         cg->next = NULL;
585                 }
586         }
587
588         free(cg->cpuacct_usage.cpu_percpu);
589
590         free(cg->cpuacct_stat.filename);
591         free(cg->cpuacct_usage.filename);
592         free(cg->memory.filename);
593         free(cg->io_service_bytes.filename);
594         free(cg->io_serviced.filename);
595         free(cg->throttle_io_service_bytes.filename);
596         free(cg->throttle_io_serviced.filename);
597         free(cg->io_merged.filename);
598         free(cg->io_queued.filename);
599
600         free(cg->id);
601         free(cg->name);
602         free(cg);
603 }
604
605 // find if a given cgroup exists
606 struct cgroup *cgroup_find(const char *id) {
607         uint32_t hash = simple_hash(id);
608
609         // fprintf(stderr, " > searching for '%s' (%u)\n", id, hash);
610
611         struct cgroup *cg;
612         for(cg = cgroup_root; cg ; cg = cg->next) {
613                 if(hash == cg->hash && strcmp(id, cg->id) == 0)
614                         break;
615         }
616
617         return cg;
618 }
619
620 // ----------------------------------------------------------------------------
621 // detect running cgroups
622
623 // callback for find_file_in_subdirs()
624 void found_dir_in_subdir(const char *dir) {
625         // fprintf(stderr, "found dir '%s'\n", dir);
626
627         struct cgroup *cg = cgroup_find(dir);
628         if(!cg) {
629                 if(*dir && cgroup_max_depth > 0) {
630                         int depth = 0;
631                         const char *s;
632
633                         for(s = dir; *s ;s++)
634                                 if(unlikely(*s == '/'))
635                                         depth++;
636
637                         if(depth > cgroup_max_depth) {
638                                 info("cgroup '%s' is too deep (%d, while max is %d)", dir, depth, cgroup_max_depth);
639                                 return;
640                         }
641                 }
642                 cg = cgroup_add(dir);
643         }
644
645         if(cg) cg->available = 1;
646 }
647
648 void find_dir_in_subdirs(const char *base, const char *this, void (*callback)(const char *)) {
649         if(!this) this = base;
650         size_t dirlen = strlen(this), baselen = strlen(base);
651
652         DIR *dir = opendir(this);
653         if(!dir) return;
654
655         callback(&this[baselen]);
656
657         struct dirent *de = NULL;
658         while((de = readdir(dir))) {
659                 if(de->d_type == DT_DIR
660                         && (
661                                 (de->d_name[0] == '.' && de->d_name[1] == '\0')
662                                 || (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')
663                                 ))
664                         continue;
665
666                 // fprintf(stderr, "examining '%s/%s'\n", this, de->d_name);
667
668                 if(de->d_type == DT_DIR) {
669                         char *s = malloc(dirlen + strlen(de->d_name) + 2);
670                         if(s) {
671                                 strcpy(s, this);
672                                 strcat(s, "/");
673                                 strcat(s, de->d_name);
674                                 find_dir_in_subdirs(base, s, callback);
675                                 free(s);
676                         }
677                 }
678         }
679
680         closedir(dir);
681 }
682
683 void mark_all_cgroups_as_not_available() {
684         struct cgroup *cg;
685
686         // mark all as not available
687         for(cg = cgroup_root; cg ; cg = cg->next)
688                 cg->available = 0;
689 }
690
691 struct cgroup *find_all_cgroups() {
692
693         mark_all_cgroups_as_not_available();
694
695         if(cgroup_enable_cpuacct_stat || cgroup_enable_cpuacct_usage)
696                 find_dir_in_subdirs(cgroup_cpuacct_base, NULL, found_dir_in_subdir);
697
698         if(cgroup_enable_blkio)
699                 find_dir_in_subdirs(cgroup_blkio_base, NULL, found_dir_in_subdir);
700
701         if(cgroup_enable_memory)
702                 find_dir_in_subdirs(cgroup_memory_base, NULL, found_dir_in_subdir);
703
704         struct cgroup *cg;
705         for(cg = cgroup_root; cg ; cg = cg->next) {
706                 // fprintf(stderr, " >>> CGROUP '%s' (%u - %s) with name '%s'\n", cg->id, cg->hash, cg->available?"available":"stopped", cg->name);
707
708                 if(!cg->available) {
709                         cgroup_remove(cg);
710                         continue;
711                 }
712
713                 // check for newly added cgroups
714                 // and update the filenames they read
715                 char filename[FILENAME_MAX + 1];
716                 if(cgroup_enable_cpuacct_stat && !cg->cpuacct_stat.filename) {
717                         snprintf(filename, FILENAME_MAX, "%s%s/cpuacct.stat", cgroup_cpuacct_base, cg->id);
718                         cg->cpuacct_stat.filename = strdup(filename);
719                 }
720                 if(cgroup_enable_cpuacct_usage && !cg->cpuacct_usage.filename) {
721                         snprintf(filename, FILENAME_MAX, "%s%s/cpuacct.usage_percpu", cgroup_cpuacct_base, cg->id);
722                         cg->cpuacct_usage.filename = strdup(filename);
723                 }
724                 if(cgroup_enable_memory && !cg->memory.filename) {
725                         snprintf(filename, FILENAME_MAX, "%s%s/memory.stat", cgroup_memory_base, cg->id);
726                         cg->memory.filename = strdup(filename);
727                 }
728                 if(cgroup_enable_blkio) {
729                         if(!cg->io_service_bytes.filename) {
730                                 snprintf(filename, FILENAME_MAX, "%s%s/blkio.io_service_bytes", cgroup_blkio_base, cg->id);
731                                 cg->io_service_bytes.filename = strdup(filename);
732                         }
733                         if(!cg->io_serviced.filename) {
734                                 snprintf(filename, FILENAME_MAX, "%s%s/blkio.io_serviced", cgroup_blkio_base, cg->id);
735                                 cg->io_serviced.filename = strdup(filename);
736                         }
737                         if(!cg->throttle_io_service_bytes.filename) {
738                                 snprintf(filename, FILENAME_MAX, "%s%s/blkio.throttle.io_service_bytes", cgroup_blkio_base, cg->id);
739                                 cg->throttle_io_service_bytes.filename = strdup(filename);
740                         }
741                         if(!cg->throttle_io_serviced.filename) {
742                                 snprintf(filename, FILENAME_MAX, "%s%s/blkio.throttle.io_serviced", cgroup_blkio_base, cg->id);
743                                 cg->throttle_io_serviced.filename = strdup(filename);
744                         }
745                         if(!cg->io_merged.filename) {
746                                 snprintf(filename, FILENAME_MAX, "%s%s/blkio.io_merged", cgroup_blkio_base, cg->id);
747                                 cg->io_merged.filename = strdup(filename);
748                         }
749                         if(!cg->io_queued.filename) {
750                                 snprintf(filename, FILENAME_MAX, "%s%s/blkio.io_queued", cgroup_blkio_base, cg->id);
751                                 cg->io_queued.filename = strdup(filename);
752                         }
753                 }
754         }
755
756         return cgroup_root;
757 }
758
759 // ----------------------------------------------------------------------------
760 // generate charts
761
762 #define CHART_TITLE_MAX 300
763
764 void update_cgroup_charts(int update_every) {
765         char type[RRD_ID_LENGTH_MAX + 1];
766         char title[CHART_TITLE_MAX + 1];
767
768         struct cgroup *cg;
769         RRDSET *st;
770
771         for(cg = cgroup_root; cg ; cg = cg->next) {
772                 if(!cg->available) continue;
773
774                 if(cg->id[0] == '\0')
775                         strcpy(type, "cgroup_host");
776                 else if(cg->id[0] == '/')
777                         snprintf(type, RRD_ID_LENGTH_MAX, "cgroup%s", cg->id);
778                 else
779                         snprintf(type, RRD_ID_LENGTH_MAX, "cgroup_%s", cg->id);
780
781                 netdata_fix_chart_id(type);
782
783                 if(cg->cpuacct_stat.updated) {
784                         st = rrdset_find_bytype(type, "cpu");
785                         if(!st) {
786                                 snprintf(title, CHART_TITLE_MAX, "CPU Usage for cgroup %s", cg->name);
787                                 st = rrdset_create(type, "cpu", NULL, "cpu", "cgroup.cpu", title, "%", 40000, update_every, RRDSET_TYPE_STACKED);
788
789                                 rrddim_add(st, "user", NULL, 100, hz, RRDDIM_INCREMENTAL);
790                                 rrddim_add(st, "system", NULL, 100, hz, RRDDIM_INCREMENTAL);
791                         }
792                         else rrdset_next(st);
793
794                         rrddim_set(st, "user", cg->cpuacct_stat.user);
795                         rrddim_set(st, "system", cg->cpuacct_stat.system);
796                         rrdset_done(st);
797                 }
798
799                 if(cg->cpuacct_usage.updated) {
800                         char id[RRD_ID_LENGTH_MAX + 1];
801                         unsigned int i;
802
803                         st = rrdset_find_bytype(type, "cpu_per_core");
804                         if(!st) {
805                                 snprintf(title, CHART_TITLE_MAX, "CPU Usage Per Core for cgroup %s", cg->name);
806                                 st = rrdset_create(type, "cpu_per_core", NULL, "cpu", "cgroup.cpu_per_core", title, "%", 40100, update_every, RRDSET_TYPE_STACKED);
807
808                                 for(i = 0; i < cg->cpuacct_usage.cpus ;i++) {
809                                         snprintf(id, CHART_TITLE_MAX, "cpu%d", i);
810                                         rrddim_add(st, id, NULL, 100, 1000000, RRDDIM_INCREMENTAL);
811                                 }
812                         }
813                         else rrdset_next(st);
814
815                         for(i = 0; i < cg->cpuacct_usage.cpus ;i++) {
816                                 snprintf(id, CHART_TITLE_MAX, "cpu%d", i);
817                                 rrddim_set(st, id, cg->cpuacct_usage.cpu_percpu[i]);
818                         }
819                         rrdset_done(st);
820                 }
821
822                 if(cg->memory.updated) {
823                         if(cg->memory.cache + cg->memory.rss + cg->memory.rss_huge + cg->memory.mapped_file > 0) {
824                                 st = rrdset_find_bytype(type, "mem");
825                                 if(!st) {
826                                         snprintf(title, CHART_TITLE_MAX, "Memory Usage for cgroup %s", cg->name);
827                                         st = rrdset_create(type, "mem", NULL, "mem", "cgroup.mem", title, "MB", 40200, update_every,
828                                                            RRDSET_TYPE_STACKED);
829
830                                         rrddim_add(st, "cache", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
831                                         rrddim_add(st, "rss", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
832                                         rrddim_add(st, "rss_huge", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
833                                         rrddim_add(st, "mapped_file", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
834                                 }
835                                 else rrdset_next(st);
836
837                                 rrddim_set(st, "cache", cg->memory.cache);
838                                 rrddim_set(st, "rss", cg->memory.rss);
839                                 rrddim_set(st, "rss_huge", cg->memory.rss_huge);
840                                 rrddim_set(st, "mapped_file", cg->memory.mapped_file);
841                                 rrdset_done(st);
842                         }
843
844                         st = rrdset_find_bytype(type, "writeback");
845                         if(!st) {
846                                 snprintf(title, CHART_TITLE_MAX, "Writable Memory for cgroup %s", cg->name);
847                                 st = rrdset_create(type, "writeback", NULL, "mem", "cgroup.writeback", title, "MB", 40300,
848                                                    update_every, RRDSET_TYPE_AREA);
849
850                                 rrddim_add(st, "writeback", NULL, 1, 1024 * 1024, RRDDIM_ABSOLUTE);
851                         }
852                         else rrdset_next(st);
853
854                         rrddim_set(st, "writeback", cg->memory.writeback);
855                         rrdset_done(st);
856
857                         if(cg->memory.pgpgin + cg->memory.pgpgout > 0) {
858                                 st = rrdset_find_bytype(type, "mem_activity");
859                                 if(!st) {
860                                         snprintf(title, CHART_TITLE_MAX, "Memory Activity for cgroup %s", cg->name);
861                                         st = rrdset_create(type, "mem_activity", NULL, "mem", "cgroup.mem_activity", title, "MB/s",
862                                                            40400, update_every, RRDSET_TYPE_LINE);
863
864                                         rrddim_add(st, "pgpgin", "in", sysconf(_SC_PAGESIZE), 1024 * 1024, RRDDIM_INCREMENTAL);
865                                         rrddim_add(st, "pgpgout", "out", -sysconf(_SC_PAGESIZE), 1024 * 1024, RRDDIM_INCREMENTAL);
866                                 }
867                                 else rrdset_next(st);
868
869                                 rrddim_set(st, "pgpgin", cg->memory.pgpgin);
870                                 rrddim_set(st, "pgpgout", cg->memory.pgpgout);
871                                 rrdset_done(st);
872                         }
873
874                         if(cg->memory.pgfault + cg->memory.pgmajfault > 0) {
875                                 st = rrdset_find_bytype(type, "pgfaults");
876                                 if(!st) {
877                                         snprintf(title, CHART_TITLE_MAX, "Memory Page Faults for cgroup %s", cg->name);
878                                         st = rrdset_create(type, "pgfaults", NULL, "mem", "cgroup.pgfaults", title, "MB/s", 40500,
879                                                            update_every, RRDSET_TYPE_LINE);
880
881                                         rrddim_add(st, "pgfault", NULL, sysconf(_SC_PAGESIZE), 1024 * 1024, RRDDIM_INCREMENTAL);
882                                         rrddim_add(st, "pgmajfault", "swap", -sysconf(_SC_PAGESIZE), 1024 * 1024, RRDDIM_INCREMENTAL);
883                                 }
884                                 else rrdset_next(st);
885
886                                 rrddim_set(st, "pgfault", cg->memory.pgfault);
887                                 rrddim_set(st, "pgmajfault", cg->memory.pgmajfault);
888                                 rrdset_done(st);
889                         }
890                 }
891
892                 if(cg->io_service_bytes.updated && cg->io_service_bytes.Read + cg->io_service_bytes.Write > 0) {
893                         st = rrdset_find_bytype(type, "io");
894                         if(!st) {
895                                 snprintf(title, CHART_TITLE_MAX, "I/O Bandwidth (all disks) for cgroup %s", cg->name);
896                                 st = rrdset_create(type, "io", NULL, "disk", "cgroup.io", title, "KB/s", 41200,
897                                                    update_every, RRDSET_TYPE_LINE);
898
899                                 rrddim_add(st, "read", NULL, 1, 1024, RRDDIM_INCREMENTAL);
900                                 rrddim_add(st, "write", NULL, -1, 1024, RRDDIM_INCREMENTAL);
901                         }
902                         else rrdset_next(st);
903
904                         rrddim_set(st, "read", cg->io_service_bytes.Read);
905                         rrddim_set(st, "write", cg->io_service_bytes.Write);
906                         rrdset_done(st);
907                 }
908
909                 if(cg->io_serviced.updated && cg->io_serviced.Read + cg->io_serviced.Write > 0) {
910                         st = rrdset_find_bytype(type, "serviced_ops");
911                         if(!st) {
912                                 snprintf(title, CHART_TITLE_MAX, "Serviced I/O Operations (all disks) for cgroup %s", cg->name);
913                                 st = rrdset_create(type, "serviced_ops", NULL, "disk", "cgroup.serviced_ops", title, "operations/s", 41200,
914                                                    update_every, RRDSET_TYPE_LINE);
915
916                                 rrddim_add(st, "read", NULL, 1, 1, RRDDIM_INCREMENTAL);
917                                 rrddim_add(st, "write", NULL, -1, 1, RRDDIM_INCREMENTAL);
918                         }
919                         else rrdset_next(st);
920
921                         rrddim_set(st, "read", cg->io_serviced.Read);
922                         rrddim_set(st, "write", cg->io_serviced.Write);
923                         rrdset_done(st);
924                 }
925
926                 if(cg->throttle_io_service_bytes.updated && cg->throttle_io_service_bytes.Read + cg->throttle_io_service_bytes.Write > 0) {
927                         st = rrdset_find_bytype(type, "io");
928                         if(!st) {
929                                 snprintf(title, CHART_TITLE_MAX, "Throttle I/O Bandwidth (all disks) for cgroup %s", cg->name);
930                                 st = rrdset_create(type, "io", NULL, "disk", "cgroup.io", title, "KB/s", 41200,
931                                                    update_every, RRDSET_TYPE_LINE);
932
933                                 rrddim_add(st, "read", NULL, 1, 1024, RRDDIM_INCREMENTAL);
934                                 rrddim_add(st, "write", NULL, -1, 1024, RRDDIM_INCREMENTAL);
935                         }
936                         else rrdset_next(st);
937
938                         rrddim_set(st, "read", cg->throttle_io_service_bytes.Read);
939                         rrddim_set(st, "write", cg->throttle_io_service_bytes.Write);
940                         rrdset_done(st);
941                 }
942
943
944                 if(cg->throttle_io_serviced.updated && cg->throttle_io_serviced.Read + cg->throttle_io_serviced.Write > 0) {
945                         st = rrdset_find_bytype(type, "throttle_serviced_ops");
946                         if(!st) {
947                                 snprintf(title, CHART_TITLE_MAX, "Throttle Serviced I/O Operations (all disks) for cgroup %s", cg->name);
948                                 st = rrdset_create(type, "throttle_serviced_ops", NULL, "disk", "cgroup.throttle_serviced_ops", title, "operations/s", 41200,
949                                                    update_every, RRDSET_TYPE_LINE);
950
951                                 rrddim_add(st, "read", NULL, 1, 1, RRDDIM_INCREMENTAL);
952                                 rrddim_add(st, "write", NULL, -1, 1, RRDDIM_INCREMENTAL);
953                         }
954                         else rrdset_next(st);
955
956                         rrddim_set(st, "read", cg->throttle_io_serviced.Read);
957                         rrddim_set(st, "write", cg->throttle_io_serviced.Write);
958                         rrdset_done(st);
959                 }
960
961                 if(cg->io_queued.updated) {
962                         st = rrdset_find_bytype(type, "queued_ops");
963                         if(!st) {
964                                 snprintf(title, CHART_TITLE_MAX, "Queued I/O Operations (all disks) for cgroup %s", cg->name);
965                                 st = rrdset_create(type, "queued_ops", NULL, "disk", "cgroup.queued_ops", title, "operations", 42000,
966                                                    update_every, RRDSET_TYPE_LINE);
967
968                                 rrddim_add(st, "read", NULL, 1, 1, RRDDIM_ABSOLUTE);
969                                 rrddim_add(st, "write", NULL, -1, 1, RRDDIM_ABSOLUTE);
970                         }
971                         else rrdset_next(st);
972
973                         rrddim_set(st, "read", cg->io_queued.Read);
974                         rrddim_set(st, "write", cg->io_queued.Write);
975                         rrdset_done(st);
976                 }
977
978                 if(cg->io_merged.updated && cg->io_merged.Read + cg->io_merged.Write > 0) {
979                         st = rrdset_find_bytype(type, "merged_ops");
980                         if(!st) {
981                                 snprintf(title, CHART_TITLE_MAX, "Merged I/O Operations (all disks) for cgroup %s", cg->name);
982                                 st = rrdset_create(type, "merged_ops", NULL, "disk", "cgroup.merged_ops", title, "operations/s", 42100,
983                                                    update_every, RRDSET_TYPE_LINE);
984
985                                 rrddim_add(st, "read", NULL, 1, 1024, RRDDIM_INCREMENTAL);
986                                 rrddim_add(st, "write", NULL, -1, 1024, RRDDIM_INCREMENTAL);
987                         }
988                         else rrdset_next(st);
989
990                         rrddim_set(st, "read", cg->io_merged.Read);
991                         rrddim_set(st, "write", cg->io_merged.Write);
992                         rrdset_done(st);
993                 }
994         }
995 }
996
997 // ----------------------------------------------------------------------------
998 // cgroups main
999
1000 int do_sys_fs_cgroup(int update_every, unsigned long long dt) {
1001         static int cgroup_global_config_read = 0;
1002         static time_t last_run = 0;
1003         time_t now = time(NULL);
1004
1005         if(dt) {};
1006
1007         if(unlikely(!cgroup_global_config_read)) {
1008                 read_cgroup_plugin_configuration();
1009                 cgroup_global_config_read = 1;
1010         }
1011
1012         if(unlikely(cgroup_enable_new_cgroups_detected_at_runtime && now - last_run > cgroup_check_for_new_every)) {
1013                 find_all_cgroups();
1014                 last_run = now;
1015         }
1016
1017         read_all_cgroups(cgroup_root);
1018         update_cgroup_charts(update_every);
1019
1020         return 0;
1021 }