master
c 1,263 lines 45.7 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "cgroup-internals.h"
4 #include "cgroup-netipc.h"
5
6 // discovery cgroup thread worker jobs
7 #define WORKER_DISCOVERY_INIT 0
8 #define WORKER_DISCOVERY_FIND 1
9 #define WORKER_DISCOVERY_PROCESS 2
10 #define WORKER_DISCOVERY_PROCESS_RENAME 3
11 #define WORKER_DISCOVERY_PROCESS_NETWORK 4
12 #define WORKER_DISCOVERY_PROCESS_FIRST_TIME 5
13 #define WORKER_DISCOVERY_UPDATE 6
14 #define WORKER_DISCOVERY_CLEANUP 7
15 #define WORKER_DISCOVERY_COPY 8
16 #define WORKER_DISCOVERY_LOCK 9
17
18 #if WORKER_UTILIZATION_MAX_JOB_TYPES < 10
19 #error WORKER_UTILIZATION_MAX_JOB_TYPES has to be at least 10
20 #endif
21
22 struct cgroup *discovered_cgroup_root = NULL;
23
24 char cgroup_chart_id_prefix[] = "cgroup_";
25 char services_chart_id_prefix[] = "systemd_";
26 const char *cgroups_rename_script = NULL;
27 static DICTIONARY *discovery_walkdir_open_errors = NULL;
28 static size_t discovery_walkdir_open_errors_count = 0;
29
30 #define DISCOVERY_WALKDIR_OPEN_ERRORS_MAX 256
31
32 // (legacy SHM globals removed — replaced by netipc in cgroup-netipc.c)
33
34 // ----------------------------------------------------------------------------
35
36 static inline void free_pressure(struct pressure *res) {
37 if (res->some.share_time.st) rrdset_is_obsolete___safe_from_collector_thread(res->some.share_time.st);
38 if (res->some.total_time.st) rrdset_is_obsolete___safe_from_collector_thread(res->some.total_time.st);
39 if (res->full.share_time.st) rrdset_is_obsolete___safe_from_collector_thread(res->full.share_time.st);
40 if (res->full.total_time.st) rrdset_is_obsolete___safe_from_collector_thread(res->full.total_time.st);
41 freez(res->filename);
42 }
43
44 static inline void cgroup_free_network_interfaces(struct cgroup *cg) {
45 while(cg->interfaces) {
46 struct cgroup_network_interface *i = cg->interfaces;
47 cg->interfaces = i->next;
48
49 // delete the registration of proc_net_dev rename
50 cgroup_rename_task_device_del(i->host_device);
51
52 freez((void *)i->host_device);
53 freez((void *)i->container_device);
54 freez((void *)i);
55 }
56 }
57
58 static inline void cgroup_free(struct cgroup *cg) {
59 netdata_log_debug(D_CGROUP, "Removing cgroup '%s' with chart id '%s' (was %s and %s)", cg->id, cg->chart_id, (cg->enabled)?"enabled":"disabled", (cg->available)?"available":"not available");
60
61 if(cg->st_cpu && cg->chart_var_cpu_limit) {
62 rrdvar_chart_variable_release(cg->st_cpu, cg->chart_var_cpu_limit);
63 cg->chart_var_cpu_limit = NULL;
64 }
65
66 if(cg->st_mem_usage && cg->chart_var_memory_limit) {
67 rrdvar_chart_variable_release(cg->st_mem_usage, cg->chart_var_memory_limit);
68 cg->chart_var_memory_limit = NULL;
69 }
70
71 cgroup_netdev_delete(cg);
72
73 if(cg->st_cpu) rrdset_is_obsolete___safe_from_collector_thread(cg->st_cpu);
74 if(cg->st_cpu_limit) rrdset_is_obsolete___safe_from_collector_thread(cg->st_cpu_limit);
75 if(cg->st_cpu_per_core) rrdset_is_obsolete___safe_from_collector_thread(cg->st_cpu_per_core);
76 if(cg->st_cpu_nr_throttled) rrdset_is_obsolete___safe_from_collector_thread(cg->st_cpu_nr_throttled);
77 if(cg->st_cpu_throttled_time) rrdset_is_obsolete___safe_from_collector_thread(cg->st_cpu_throttled_time);
78 if(cg->st_cpu_shares) rrdset_is_obsolete___safe_from_collector_thread(cg->st_cpu_shares);
79 if(cg->st_mem) rrdset_is_obsolete___safe_from_collector_thread(cg->st_mem);
80 if(cg->st_writeback) rrdset_is_obsolete___safe_from_collector_thread(cg->st_writeback);
81 if(cg->st_mem_activity) rrdset_is_obsolete___safe_from_collector_thread(cg->st_mem_activity);
82 if(cg->st_pgfaults) rrdset_is_obsolete___safe_from_collector_thread(cg->st_pgfaults);
83 if(cg->st_mem_usage) rrdset_is_obsolete___safe_from_collector_thread(cg->st_mem_usage);
84 if(cg->st_mem_usage_limit) rrdset_is_obsolete___safe_from_collector_thread(cg->st_mem_usage_limit);
85 if(cg->st_mem_utilization) rrdset_is_obsolete___safe_from_collector_thread(cg->st_mem_utilization);
86 if(cg->st_mem_failcnt) rrdset_is_obsolete___safe_from_collector_thread(cg->st_mem_failcnt);
87 if(cg->st_io) rrdset_is_obsolete___safe_from_collector_thread(cg->st_io);
88 if(cg->st_serviced_ops) rrdset_is_obsolete___safe_from_collector_thread(cg->st_serviced_ops);
89 if(cg->st_throttle_io) rrdset_is_obsolete___safe_from_collector_thread(cg->st_throttle_io);
90 if(cg->st_throttle_serviced_ops) rrdset_is_obsolete___safe_from_collector_thread(cg->st_throttle_serviced_ops);
91 if(cg->st_queued_ops) rrdset_is_obsolete___safe_from_collector_thread(cg->st_queued_ops);
92 if(cg->st_merged_ops) rrdset_is_obsolete___safe_from_collector_thread(cg->st_merged_ops);
93 if(cg->st_pids) rrdset_is_obsolete___safe_from_collector_thread(cg->st_pids);
94
95 freez(cg->filename_cpuset_cpus);
96 freez(cg->filename_cpu_cfs_period);
97 freez(cg->filename_cpu_cfs_quota);
98 freez(cg->filename_memory_limit);
99
100 cgroup_free_network_interfaces(cg);
101
102 freez(cg->cpuacct_usage.cpu_percpu);
103
104 freez(cg->cpuacct_stat.filename);
105 freez(cg->cpuacct_usage.filename);
106 freez(cg->cpuacct_cpu_throttling.filename);
107 freez(cg->cpuacct_cpu_shares.filename);
108
109 arl_free(cg->memory.arl_base);
110 freez(cg->memory.filename_detailed);
111 freez(cg->memory.filename_failcnt);
112 freez(cg->memory.filename_usage_in_bytes);
113 freez(cg->memory.filename_msw_usage_in_bytes);
114
115 freez(cg->io_service_bytes.filename);
116 freez(cg->io_serviced.filename);
117
118 freez(cg->throttle_io_service_bytes.filename);
119 freez(cg->throttle_io_serviced.filename);
120
121 freez(cg->io_merged.filename);
122 freez(cg->io_queued.filename);
123 freez(cg->pids_current.filename);
124
125 free_pressure(&cg->cpu_pressure);
126 free_pressure(&cg->io_pressure);
127 free_pressure(&cg->memory_pressure);
128 free_pressure(&cg->irq_pressure);
129
130 freez(cg->id);
131 freez(cg->intermediate_id);
132 freez(cg->chart_id);
133 freez(cg->name);
134
135 rrdlabels_destroy(cg->chart_labels);
136
137 memset(cg, 0, sizeof(*cg));
138 freez(cg);
139
140 cgroup_root_count--;
141 }
142
143 // ----------------------------------------------------------------------------
144 // add/remove/find cgroup objects
145
146 #define CGROUP_CHARTID_LINE_MAX 1024
147
148 static inline char *cgroup_chart_id_strdupz(const char *s) {
149 if(!s || !*s) s = "/";
150
151 if(*s == '/' && s[1] != '\0') s++;
152
153 char *r = strdupz(s);
154 netdata_fix_chart_id(r);
155
156 return r;
157 }
158
159 // TODO: move the code to cgroup_chart_id_strdupz() when the renaming script is fixed
160 static inline void substitute_dots_in_id(char *s) {
161 // dots are used to distinguish chart type and id in streaming, so we should replace them
162 for (char *d = s; *d; d++) {
163 if (*d == '.')
164 *d = '-';
165 }
166 }
167
168 // ----------------------------------------------------------------------------
169 // parse k8s labels
170
171 #define CGROUP_NETDATA_CLOUD_LABEL_PREFIX "netdata.cloud/"
172 #define CGROUP_RENAME_LABEL "cgroup.name="
173 #define CGROUP_IGNORE_LABEL "ignore="
174
175 static char *cgroup_parse_resolved_name_and_labels(struct cgroup *cg, char *data) {
176 if (!cg->chart_labels)
177 cg->chart_labels = rrdlabels_create();
178
179 rrdlabels_unmark_all(cg->chart_labels);
180
181 // the first word, up to the first space is the name
182 char *name = strsep_skip_consecutive_separators(&data, " ");
183
184 bool ignored = false;
185
186 // the rest are key=value pairs separated by comma
187 while(data) {
188 char *pair = strsep_skip_consecutive_separators(&data, ",");
189
190 if(strncmp(pair, CGROUP_NETDATA_CLOUD_LABEL_PREFIX, sizeof(CGROUP_NETDATA_CLOUD_LABEL_PREFIX) - 1) == 0) {
191 // a netdata.cloud label
192 char *key = &pair[sizeof(CGROUP_NETDATA_CLOUD_LABEL_PREFIX) - 1];
193
194 if(strncmp(key, CGROUP_RENAME_LABEL, sizeof(CGROUP_RENAME_LABEL) - 1) == 0) {
195 char *n = &key[sizeof(CGROUP_RENAME_LABEL) - 1];
196 size_t len = strlen(n);
197 if(n[0] == '"' && n[len - 1] == '"') {
198 n[len - 1] = '\0';
199 n++;
200 }
201 if(*n) name = n;
202
203 // no need to add this label
204 }
205 else if(strncmp(key, CGROUP_IGNORE_LABEL, sizeof(CGROUP_IGNORE_LABEL) - 1) == 0) {
206 char *v = &key[sizeof(CGROUP_IGNORE_LABEL) - 1];
207 if(strcasecmp(v, "\"true\"") == 0 || strcasecmp(v, "\"yes\"") == 0)
208 ignored = true;
209 else
210 ignored = false;
211
212 // no need to add this label
213 }
214 }
215 else
216 // add the label as-is
217 rrdlabels_add_pair(cg->chart_labels, pair, RRDLABEL_SRC_AUTO | RRDLABEL_SRC_K8S);
218 }
219
220 rrdlabels_remove_all_unmarked(cg->chart_labels);
221
222 if(ignored)
223 cg->options |= CGROUP_OPTIONS_DISABLED_EXCLUDED;
224 else
225 cg->options &= ~CGROUP_OPTIONS_DISABLED_EXCLUDED;
226
227 return name;
228 }
229
230 static inline void discovery_rename_cgroup(struct cgroup *cg) {
231 if (!cg->pending_renames) {
232 return;
233 }
234 cg->pending_renames--;
235
236 netdata_log_debug(D_CGROUP, "looking for the name of cgroup '%s' with chart id '%s'", cg->id, cg->chart_id);
237 netdata_log_debug(D_CGROUP, "executing command %s \"%s\" for cgroup '%s'", cgroups_rename_script, cg->intermediate_id, cg->chart_id);
238
239 POPEN_INSTANCE *instance = spawn_popen_run_variadic(cgroups_rename_script, cg->id, cg->intermediate_id, NULL);
240 if (!instance) {
241 collector_error("CGROUP: cannot popen(%s \"%s\", \"r\").", cgroups_rename_script, cg->intermediate_id);
242 cg->pending_renames = 0;
243 cg->processed = 1;
244 return;
245 }
246
247 char buffer[8192]; // we need some size for labels
248 char *new_name = fgets(buffer, sizeof(buffer), spawn_popen_stdout(instance));
249 int exit_code = spawn_popen_wait(instance);
250
251 switch (exit_code) {
252 case 0:
253 cg->pending_renames = 0;
254 break;
255
256 case 3:
257 cg->pending_renames = 0;
258 cg->processed = 1;
259 break;
260
261 default:
262 break;
263 }
264
265 if (cg->pending_renames || cg->processed)
266 return;
267 if (!new_name || !*new_name || *new_name == '\n')
268 return;
269 if (!(new_name = trim(new_name)))
270 return;
271
272 char *name = cgroup_parse_resolved_name_and_labels(cg, new_name);
273
274 freez(cg->name);
275 cg->name = strdupz(name);
276
277 freez(cg->chart_id);
278 cg->chart_id = cgroup_chart_id_strdupz(name);
279
280 substitute_dots_in_id(cg->chart_id);
281 cg->hash_chart_id = simple_hash(cg->chart_id);
282 }
283
284 static inline void convert_cgroup_to_systemd_service(struct cgroup *cg) {
285 char buffer[CGROUP_CHARTID_LINE_MAX + 1];
286 cg->options |= CGROUP_OPTIONS_SYSTEM_SLICE_SERVICE;
287 strncpyz(buffer, cg->id, CGROUP_CHARTID_LINE_MAX);
288 char *s = buffer;
289
290 // skip to the last slash
291 char *slash = strrchr(s, '/');
292 if (unlikely(slash && slash != s))
293 s = slash + 1;
294
295 // remove extension
296 char *dot = strrchr(s, '.');
297 if (unlikely(dot && dot != s))
298 *dot = '\0';
299
300 freez(cg->name);
301 cg->name = strdupz(s);
302
303 freez(cg->chart_id);
304 cg->chart_id = cgroup_chart_id_strdupz(s);
305 substitute_dots_in_id(cg->chart_id);
306 cg->hash_chart_id = simple_hash(cg->chart_id);
307 }
308
309 static inline struct cgroup *discovery_cgroup_add(const char *id) {
310 netdata_log_debug(D_CGROUP, "adding to list, cgroup with id '%s'", id);
311
312 struct cgroup *cg = callocz(1, sizeof(struct cgroup));
313
314 cg->id = strdupz(id);
315 cg->hash = simple_hash(cg->id);
316
317 cg->name = strdupz(id);
318
319 cg->intermediate_id = cgroup_chart_id_strdupz(id);
320
321 cg->chart_id = cgroup_chart_id_strdupz(id);
322 substitute_dots_in_id(cg->chart_id);
323 cg->hash_chart_id = simple_hash(cg->chart_id);
324
325 if (cgroup_use_unified_cgroups) {
326 cg->options |= CGROUP_OPTIONS_IS_UNIFIED;
327 }
328
329 if (!discovered_cgroup_root)
330 discovered_cgroup_root = cg;
331 else {
332 struct cgroup *t;
333 for (t = discovered_cgroup_root; t->discovered_next; t = t->discovered_next) {
334 }
335 t->discovered_next = cg;
336 }
337
338 return cg;
339 }
340
341 static inline struct cgroup *discovery_cgroup_find(const char *id) {
342 netdata_log_debug(D_CGROUP, "searching for cgroup '%s'", id);
343
344 uint32_t hash = simple_hash(id);
345
346 struct cgroup *cg;
347 for(cg = discovered_cgroup_root; cg ; cg = cg->discovered_next) {
348 if(hash == cg->hash && strcmp(id, cg->id) == 0)
349 break;
350 }
351
352 netdata_log_debug(D_CGROUP, "cgroup '%s' %s in memory", id, (cg)?"found":"not found");
353 return cg;
354 }
355
356 struct discovery_walkdir_open_error_prune {
357 size_t remaining;
358 };
359
360 static int discovery_walkdir_open_error_prune_cb(const DICTIONARY_ITEM *item, void *value __maybe_unused, void *data) {
361 struct discovery_walkdir_open_error_prune *prune = data;
362
363 if (!prune->remaining)
364 return -1;
365
366 dictionary_del(discovery_walkdir_open_errors, dictionary_acquired_item_name(item));
367 discovery_walkdir_open_errors_count--;
368 prune->remaining--;
369
370 return prune->remaining ? 0 : -1;
371 }
372
373 static inline bool discovery_walkdir_open_error_first(const char *dirpath) {
374 if (unlikely(!discovery_walkdir_open_errors))
375 discovery_walkdir_open_errors = dictionary_create(DICT_OPTION_SINGLE_THREADED);
376
377 if (dictionary_get(discovery_walkdir_open_errors, dirpath))
378 return false;
379
380 if (discovery_walkdir_open_errors_count >= DISCOVERY_WALKDIR_OPEN_ERRORS_MAX) {
381 struct discovery_walkdir_open_error_prune prune = {
382 .remaining = discovery_walkdir_open_errors_count - DISCOVERY_WALKDIR_OPEN_ERRORS_MAX + 1,
383 };
384
385 dictionary_walkthrough_write(discovery_walkdir_open_errors, discovery_walkdir_open_error_prune_cb, &prune);
386 }
387
388 uint8_t logged = 1;
389 dictionary_set(discovery_walkdir_open_errors, dirpath, &logged, sizeof(logged));
390 discovery_walkdir_open_errors_count++;
391
392 return true;
393 }
394
395 static inline void discovery_walkdir_open_errors_cleanup(void) {
396 if (!discovery_walkdir_open_errors)
397 return;
398
399 dictionary_destroy(discovery_walkdir_open_errors);
400 discovery_walkdir_open_errors = NULL;
401 discovery_walkdir_open_errors_count = 0;
402 }
403
404 static int calc_cgroup_depth(const char *id) {
405 int depth = 0;
406 const char *s;
407 for (s = id; *s; s++) {
408 depth += unlikely(*s == '/');
409 }
410 return depth;
411 }
412
413 static inline void discovery_find_cgroup_in_dir(const char *dir) {
414 if (!dir || !*dir) {
415 dir = "/";
416 }
417
418 struct cgroup *cg = discovery_cgroup_find(dir);
419 if (cg) {
420 cg->available = 1;
421 return;
422 }
423
424 if (cgroup_root_count >= cgroup_root_max) {
425 nd_log_limit_static_global_var(erl, 3600, 0);
426 nd_log_limit(&erl, NDLS_COLLECTORS, NDLP_WARNING, "CGROUP: maximum number of cgroups reached (%d). No more cgroups will be added.", cgroup_root_count);
427 return;
428 }
429
430 if (cgroup_max_depth > 0) {
431 int depth = calc_cgroup_depth(dir);
432 if (depth > cgroup_max_depth) {
433 nd_log_collector(NDLP_DEBUG, "CGROUP: '%s' is too deep (%d, while max is %d)", dir, depth, cgroup_max_depth);
434 return;
435 }
436 }
437
438 cg = discovery_cgroup_add(dir);
439 cg->available = 1;
440 cg->first_time_seen = 1;
441 cg->function_ready = false;
442 cgroup_root_count++;
443 }
444
445 static inline int discovery_find_walkdir(const char *base, const char *dirpath) {
446 if (!dirpath)
447 dirpath = base;
448
449 netdata_log_debug(D_CGROUP, "searching for directories in '%s' (base '%s')", dirpath ? dirpath : "", base);
450
451 size_t dirlen = strlen(dirpath), baselen = strlen(base);
452
453 int ret = -1;
454 int enabled = -1;
455
456 const char *relative_path = &dirpath[baselen];
457 if (!*relative_path)
458 relative_path = "/";
459
460 DIR *dir = opendir(dirpath);
461 if(!dir) {
462 int err = errno;
463 bool first_log_for_path = discovery_walkdir_open_error_first(dirpath);
464
465 if(err == EACCES && strcmp(dirpath, base) != 0) {
466 if (first_log_for_path)
467 nd_log_collector(NDLP_DEBUG, "CGROUP: cannot open directory '%s': %s", dirpath, strerror(err));
468 }
469 else if (first_log_for_path) {
470 collector_error("CGROUP: cannot open directory '%s': %s", dirpath, strerror(err));
471 }
472 return ret;
473 }
474 ret = 1;
475
476 discovery_find_cgroup_in_dir(relative_path);
477
478 struct dirent *de = NULL;
479 while((de = readdir(dir))) {
480 if (de->d_type == DT_DIR && ((de->d_name[0] == '.' && de->d_name[1] == '\0') ||
481 (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')))
482 continue;
483
484 if(de->d_type == DT_DIR) {
485 if(enabled == -1) {
486 const char *r = relative_path;
487 if (*r == '\0')
488 r = "/";
489
490 // do not decent in directories we are not interested
491 enabled = matches_search_cgroup_paths(r);
492 }
493
494 if(enabled) {
495 char *s = mallocz(dirlen + strlen(de->d_name) + 2);
496 strcpy(s, dirpath);
497 strcat(s, "/");
498 strcat(s, de->d_name);
499 int ret2 = discovery_find_walkdir(base, s);
500 if (ret2 > 0)
501 ret += ret2;
502 freez(s);
503 }
504 }
505 }
506
507 closedir(dir);
508 return ret;
509 }
510
511 static inline void discovery_mark_as_unavailable_all_cgroups() {
512 for (struct cgroup *cg = discovered_cgroup_root; cg; cg = cg->discovered_next) {
513 cg->available = 0;
514 }
515 }
516
517 static inline void discovery_update_filenames_cgroup_v1(struct cgroup *cg) {
518 char filename[FILENAME_MAX + 1];
519 struct stat buf;
520
521 // CPU
522 if (likely(cgroup_enable_cpuacct)) {
523 if (unlikely(!cg->cpuacct_stat.staterr && !cg->cpuacct_stat.filename)) {
524 snprintfz(filename, FILENAME_MAX, "%s%s/cpuacct.stat", cgroup_cpuacct_base, cg->id);
525 if (!(cg->cpuacct_stat.staterr = stat(filename, &buf) != 0)) {
526 cg->cpuacct_stat.filename = strdupz(filename);
527
528 snprintfz(filename, FILENAME_MAX, "%s%s/cpuset.cpus", cgroup_cpuset_base, cg->id);
529 cg->filename_cpuset_cpus = strdupz(filename);
530
531 snprintfz(filename, FILENAME_MAX, "%s%s/cpu.cfs_period_us", cgroup_cpuacct_base, cg->id);
532 cg->filename_cpu_cfs_period = strdupz(filename);
533
534 snprintfz(filename, FILENAME_MAX, "%s%s/cpu.cfs_quota_us", cgroup_cpuacct_base, cg->id);
535 cg->filename_cpu_cfs_quota = strdupz(filename);
536 }
537 }
538 if (!is_cgroup_systemd_service(cg)) {
539 if (unlikely(!cg->cpuacct_cpu_throttling.staterr && !cg->cpuacct_cpu_throttling.filename)) {
540 snprintfz(filename, FILENAME_MAX, "%s%s/cpu.stat", cgroup_cpuacct_base, cg->id);
541 if (!(cg->cpuacct_cpu_throttling.staterr = stat(filename, &buf) != 0)) {
542 cg->cpuacct_cpu_throttling.filename = strdupz(filename);
543 }
544 }
545
546 if (cgroup_enable_cpuacct_cpu_shares) {
547 if (unlikely(!cg->cpuacct_cpu_shares.staterr && !cg->cpuacct_cpu_shares.filename)) {
548 snprintfz(filename, FILENAME_MAX, "%s%s/cpu.shares", cgroup_cpuacct_base, cg->id);
549
550 if (!(cg->cpuacct_cpu_shares.staterr = stat(filename, &buf) != 0)) {
551 cg->cpuacct_cpu_shares.filename = strdupz(filename);
552 }
553 }
554 }
555 }
556 }
557
558 // Memory
559 if (likely(cgroup_enable_memory)) {
560 if (unlikely(!cg->memory.staterr_mem_current && !cg->memory.filename_usage_in_bytes)) {
561 snprintfz(filename, FILENAME_MAX, "%s%s/memory.usage_in_bytes", cgroup_memory_base, cg->id);
562 if (!(cg->memory.staterr_mem_current = stat(filename, &buf) != 0)) {
563 cg->memory.filename_usage_in_bytes = strdupz(filename);
564
565 snprintfz(filename, FILENAME_MAX, "%s%s/memory.limit_in_bytes", cgroup_memory_base, cg->id);
566 cg->filename_memory_limit = strdupz(filename);
567 }
568 }
569
570 if (unlikely(!cg->memory.staterr_mem_stat && !cg->memory.filename_detailed)) {
571 snprintfz(filename, FILENAME_MAX, "%s%s/memory.stat", cgroup_memory_base, cg->id);
572 if (!(cg->memory.staterr_mem_stat = stat(filename, &buf) != 0)) {
573 cg->memory.filename_detailed = strdupz(filename);
574 }
575 }
576
577 if (unlikely(!cg->memory.staterr_failcnt && !cg->memory.filename_failcnt)) {
578 snprintfz(filename, FILENAME_MAX, "%s%s/memory.failcnt", cgroup_memory_base, cg->id);
579 if (!(cg->memory.staterr_failcnt = stat(filename, &buf) != 0)) {
580 cg->memory.filename_failcnt = strdupz(filename);
581 }
582 }
583 }
584
585 // Blkio
586 if (likely(cgroup_enable_blkio)) {
587 if (unlikely(!cg->io_service_bytes.staterr && !cg->io_service_bytes.filename)) {
588 snprintfz(filename, FILENAME_MAX, "%s%s/blkio.io_service_bytes_recursive", cgroup_blkio_base, cg->id);
589 if (!(cg->io_service_bytes.staterr = stat(filename, &buf) != 0)) {
590 cg->io_service_bytes.filename = strdupz(filename);
591 } else {
592 snprintfz(filename, FILENAME_MAX, "%s%s/blkio.io_service_bytes", cgroup_blkio_base, cg->id);
593 if (!(cg->io_service_bytes.staterr = stat(filename, &buf) != 0)) {
594 cg->io_service_bytes.filename = strdupz(filename);
595 }
596 }
597 }
598
599 if (unlikely(!cg->io_serviced.staterr && !cg->io_serviced.filename)) {
600 snprintfz(filename, FILENAME_MAX, "%s%s/blkio.io_serviced_recursive", cgroup_blkio_base, cg->id);
601 if (!(cg->io_serviced.staterr = stat(filename, &buf) != 0)) {
602 cg->io_serviced.filename = strdupz(filename);
603 } else {
604 snprintfz(filename, FILENAME_MAX, "%s%s/blkio.io_serviced", cgroup_blkio_base, cg->id);
605 if (!(cg->io_serviced.staterr = stat(filename, &buf) != 0)) {
606 cg->io_serviced.filename = strdupz(filename);
607 }
608 }
609 }
610
611 if (unlikely(!cg->throttle_io_service_bytes.staterr && !cg->throttle_io_service_bytes.filename)) {
612 snprintfz(
613 filename, FILENAME_MAX, "%s%s/blkio.throttle.io_service_bytes_recursive", cgroup_blkio_base, cg->id);
614 if (!(cg->throttle_io_service_bytes.staterr = stat(filename, &buf) != 0)) {
615 cg->throttle_io_service_bytes.filename = strdupz(filename);
616 } else {
617 snprintfz(filename, FILENAME_MAX, "%s%s/blkio.throttle.io_service_bytes", cgroup_blkio_base, cg->id);
618 if (!(cg->throttle_io_service_bytes.staterr = stat(filename, &buf) != 0)) {
619 cg->throttle_io_service_bytes.filename = strdupz(filename);
620 }
621 }
622 }
623
624 if (unlikely(!cg->throttle_io_serviced.staterr && !cg->throttle_io_serviced.filename)) {
625 snprintfz(filename, FILENAME_MAX, "%s%s/blkio.throttle.io_serviced_recursive", cgroup_blkio_base, cg->id);
626 if(!(cg->throttle_io_serviced.staterr = stat(filename, &buf) != 0)) {
627 cg->throttle_io_serviced.filename = strdupz(filename);
628 } else {
629 snprintfz(filename, FILENAME_MAX, "%s%s/blkio.throttle.io_serviced", cgroup_blkio_base, cg->id);
630 if (!(cg->throttle_io_serviced.staterr = stat(filename, &buf) != 0)) {
631 cg->throttle_io_serviced.filename = strdupz(filename);
632 }
633 }
634 }
635
636 if (unlikely(!cg->io_merged.staterr && !cg->io_merged.filename)) {
637 snprintfz(filename, FILENAME_MAX, "%s%s/blkio.io_merged_recursive", cgroup_blkio_base, cg->id);
638 if (!(cg->io_merged.staterr = stat(filename, &buf) != 0)) {
639 cg->io_merged.filename = strdupz(filename);
640 } else {
641 snprintfz(filename, FILENAME_MAX, "%s%s/blkio.io_merged", cgroup_blkio_base, cg->id);
642 if (!(cg->io_merged.staterr = stat(filename, &buf) != 0)) {
643 cg->io_merged.filename = strdupz(filename);
644 }
645 }
646 }
647
648 if (unlikely(!cg->io_queued.staterr && !cg->io_queued.filename)) {
649 snprintfz(filename, FILENAME_MAX, "%s%s/blkio.io_queued_recursive", cgroup_blkio_base, cg->id);
650 if (!(cg->io_queued.staterr = stat(filename, &buf) != 0)) {
651 cg->io_queued.filename = strdupz(filename);
652 } else {
653 snprintfz(filename, FILENAME_MAX, "%s%s/blkio.io_queued", cgroup_blkio_base, cg->id);
654 if (!(cg->io_queued.staterr = stat(filename, &buf) != 0)) {
655 cg->io_queued.filename = strdupz(filename);
656 }
657 }
658 }
659 }
660
661 // Pids
662 if (unlikely(!cg->pids_current.staterr && !cg->pids_current.filename)) {
663 snprintfz(filename, FILENAME_MAX, "%s%s/pids.current", cgroup_pids_base, cg->id);
664 if (!(cg->pids_current.staterr = stat(filename, &buf) != 0)) {
665 cg->pids_current.filename = strdupz(filename);
666 }
667 }
668 }
669
670 static inline void discovery_update_filenames_cgroup_v2(struct cgroup *cg) {
671 if (!cgroup_unified_exist)
672 return;
673
674 char filename[FILENAME_MAX + 1];
675 struct stat buf;
676
677 // CPU
678 if (unlikely((!cg->cpuacct_stat.staterr && !cg->cpuacct_stat.filename))) {
679 snprintfz(filename, FILENAME_MAX, "%s%s/cpu.stat", cgroup_unified_base, cg->id);
680 if (!(cg->cpuacct_stat.staterr = stat(filename, &buf) != 0)) {
681 cg->cpuacct_stat.filename = strdupz(filename);
682 cg->filename_cpuset_cpus = NULL;
683 cg->filename_cpu_cfs_period = NULL;
684
685 snprintfz(filename, FILENAME_MAX, "%s%s/cpu.max", cgroup_unified_base, cg->id);
686 cg->filename_cpu_cfs_quota = strdupz(filename);
687 }
688 }
689 if (cgroup_enable_cpuacct_cpu_shares) {
690 if (unlikely(!cg->cpuacct_cpu_shares.staterr && !cg->cpuacct_cpu_shares.filename)) {
691 snprintfz(filename, FILENAME_MAX, "%s%s/cpu.weight", cgroup_unified_base, cg->id);
692 if (!(cg->cpuacct_cpu_shares.staterr = stat(filename, &buf) != 0)) {
693 cg->cpuacct_cpu_shares.filename = strdupz(filename);
694 }
695 }
696 }
697
698 // Memory
699 if (unlikely(!cg->memory.staterr_mem_current && !cg->memory.filename_usage_in_bytes)) {
700 snprintfz(filename, FILENAME_MAX, "%s%s/memory.current", cgroup_unified_base, cg->id);
701 if (!(cg->memory.staterr_mem_current = stat(filename, &buf) != 0)) {
702 cg->memory.filename_usage_in_bytes = strdupz(filename);
703
704 snprintfz(filename, FILENAME_MAX, "%s%s/memory.max", cgroup_unified_base, cg->id);
705 cg->filename_memory_limit = strdupz(filename);
706 }
707 }
708
709 if (unlikely(!cg->memory.staterr_mem_stat && !cg->memory.filename_detailed)) {
710 snprintfz(filename, FILENAME_MAX, "%s%s/memory.stat", cgroup_unified_base, cg->id);
711 if (!(cg->memory.staterr_mem_stat = stat(filename, &buf) != 0)) {
712 cg->memory.filename_detailed = strdupz(filename);
713 }
714 }
715
716 // Blkio
717 if (unlikely(!cg->io_service_bytes.staterr && !cg->io_service_bytes.filename)) {
718 snprintfz(filename, FILENAME_MAX, "%s%s/io.stat", cgroup_unified_base, cg->id);
719 if (!(cg->io_service_bytes.staterr = stat(filename, &buf) != 0)) {
720 cg->io_service_bytes.filename = strdupz(filename);
721 }
722 }
723
724 if (unlikely(!cg->io_serviced.staterr && !cg->io_serviced.filename)) {
725 snprintfz(filename, FILENAME_MAX, "%s%s/io.stat", cgroup_unified_base, cg->id);
726 if (!(cg->io_serviced.staterr = stat(filename, &buf) != 0)) {
727 cg->io_serviced.filename = strdupz(filename);
728 }
729 }
730
731 // PSI
732 if (unlikely(!cg->cpu_pressure.staterr && !cg->cpu_pressure.filename)) {
733 snprintfz(filename, FILENAME_MAX, "%s%s/cpu.pressure", cgroup_unified_base, cg->id);
734 if (!(cg->cpu_pressure.staterr = stat(filename, &buf) != 0)) {
735 cg->cpu_pressure.filename = strdupz(filename);
736 cg->cpu_pressure.some.enabled = CONFIG_BOOLEAN_YES;
737 cg->cpu_pressure.full.enabled = CONFIG_BOOLEAN_YES;
738 }
739 }
740
741 if (unlikely(!cg->io_pressure.staterr && !cg->io_pressure.filename)) {
742 snprintfz(filename, FILENAME_MAX, "%s%s/io.pressure", cgroup_unified_base, cg->id);
743 if (!(cg->io_pressure.staterr = stat(filename, &buf) != 0)) {
744 cg->io_pressure.filename = strdupz(filename);
745 cg->io_pressure.some.enabled = CONFIG_BOOLEAN_YES;
746 cg->io_pressure.full.enabled = CONFIG_BOOLEAN_YES;
747 }
748 }
749
750 if (unlikely(!cg->memory_pressure.staterr && !cg->memory_pressure.filename)) {
751 snprintfz(filename, FILENAME_MAX, "%s%s/memory.pressure", cgroup_unified_base, cg->id);
752 if (!(cg->memory_pressure.staterr = stat(filename, &buf) != 0)) {
753 cg->memory_pressure.filename = strdupz(filename);
754 cg->memory_pressure.some.enabled = CONFIG_BOOLEAN_YES;
755 cg->memory_pressure.full.enabled = CONFIG_BOOLEAN_YES;
756 }
757 }
758
759 if (unlikely(!cg->irq_pressure.staterr && !cg->irq_pressure.filename)) {
760 snprintfz(filename, FILENAME_MAX, "%s%s/irq.pressure", cgroup_unified_base, cg->id);
761 if (!(cg->irq_pressure.staterr = stat(filename, &buf) != 0)) {
762 cg->irq_pressure.filename = strdupz(filename);
763 cg->irq_pressure.some.enabled = CONFIG_BOOLEAN_YES;
764 cg->irq_pressure.full.enabled = CONFIG_BOOLEAN_YES;
765 }
766 }
767
768 // Pids
769 if (unlikely(!cg->pids_current.staterr && !cg->pids_current.filename)) {
770 snprintfz(filename, FILENAME_MAX, "%s%s/pids.current", cgroup_unified_base, cg->id);
771 if (!(cg->pids_current.staterr = stat(filename, &buf) != 0)) {
772 cg->pids_current.filename = strdupz(filename);
773 }
774 }
775 }
776
777 static inline void discovery_update_filenames_all_cgroups() {
778 for (struct cgroup *cg = discovered_cgroup_root; cg; cg = cg->discovered_next) {
779 if (unlikely(!cg->available || !cg->enabled || cg->pending_renames))
780 continue;
781
782 if (!cgroup_use_unified_cgroups)
783 discovery_update_filenames_cgroup_v1(cg);
784 else
785 discovery_update_filenames_cgroup_v2(cg);
786 }
787 }
788
789 static inline void discovery_cleanup_all_cgroups() {
790 struct cgroup *cg = discovered_cgroup_root, *last = NULL;
791
792 for(; cg ;) {
793 if(!cg->available) {
794 // enable the first duplicate cgroup
795 {
796 struct cgroup *t;
797 for (t = discovered_cgroup_root; t; t = t->discovered_next) {
798 if (t != cg && t->available && !t->enabled && t->options & CGROUP_OPTIONS_DISABLED_DUPLICATE &&
799 (is_cgroup_systemd_service(t) == is_cgroup_systemd_service(cg)) &&
800 t->hash_chart_id == cg->hash_chart_id && !strcmp(t->chart_id, cg->chart_id)) {
801 netdata_log_debug(D_CGROUP, "Enabling duplicate of cgroup '%s' with id '%s', because the original with id '%s' stopped.", t->chart_id, t->id, cg->id);
802 t->enabled = 1;
803 t->options &= ~CGROUP_OPTIONS_DISABLED_DUPLICATE;
804 break;
805 }
806 }
807 }
808
809 if(!last)
810 discovered_cgroup_root = cg->discovered_next;
811 else
812 last->discovered_next = cg->discovered_next;
813
814 cgroup_free(cg);
815
816 if(!last)
817 cg = discovered_cgroup_root;
818 else
819 cg = last->discovered_next;
820 }
821 else {
822 last = cg;
823 cg = cg->discovered_next;
824 }
825 }
826 }
827
828 static inline void discovery_copy_discovered_cgroups_to_reader() {
829 netdata_log_debug(D_CGROUP, "copy discovered cgroups to the main group list");
830
831 struct cgroup *cg;
832
833 for (cg = discovered_cgroup_root; cg; cg = cg->discovered_next) {
834 cg->next = cg->discovered_next;
835 }
836
837 cgroup_root = discovered_cgroup_root;
838 }
839
840 static inline void discovery_find_all_cgroups_v1() {
841 if (cgroup_enable_cpuacct) {
842 if (discovery_find_walkdir(cgroup_cpuacct_base, NULL) == -1) {
843 cgroup_enable_cpuacct = false;
844 collector_error("CGROUP: disabled cpu statistics.");
845 }
846 }
847
848 if (cgroup_enable_blkio) {
849 if (discovery_find_walkdir(cgroup_blkio_base, NULL) == -1) {
850 cgroup_enable_blkio = false;
851 collector_error("CGROUP: disabled blkio statistics.");
852 }
853 }
854
855 if (cgroup_enable_memory) {
856 if (discovery_find_walkdir(cgroup_memory_base, NULL) == -1) {
857 cgroup_enable_memory = false;
858 collector_error("CGROUP: disabled memory statistics.");
859 }
860 }
861 }
862
863 static inline void discovery_find_all_cgroups_v2() {
864 if (cgroup_unified_exist) {
865 if (discovery_find_walkdir(cgroup_unified_base, NULL) == -1) {
866 cgroup_unified_exist = false;
867 collector_error("CGROUP: disabled unified cgroups statistics.");
868 }
869 }
870 }
871
872 static int is_digits_only(const char *s) {
873 do {
874 if (!isdigit(*s++)) {
875 return 0;
876 }
877 } while (*s);
878
879 return 1;
880 }
881
882 static int is_cgroup_k8s_container(const char *id) {
883 // examples:
884 // https://github.com/netdata/netdata/blob/0fc101679dcd12f1cb8acdd07bb4c85d8e553e53/collectors/cgroups.plugin/cgroup-name.sh#L121-L147
885 const char *p = id;
886 const char *pp = NULL;
887 int i = 0;
888 size_t l = 3; // pod
889 while ((p = strstr(p, "pod"))) {
890 i++;
891 p += l;
892 pp = p;
893 }
894 return !(i < 2 || !pp || !(pp = strchr(pp, '/')) || !pp++ || !*pp);
895 }
896
897 #define TASK_COMM_LEN 16
898
899 static int k8s_get_container_first_proc_comm(const char *id, char *comm) {
900 if (!is_cgroup_k8s_container(id)) {
901 return 1;
902 }
903
904 static procfile *ff = NULL;
905
906 char filename[FILENAME_MAX + 1];
907 snprintfz(filename, FILENAME_MAX, "%s/%s/cgroup.procs", cgroup_cpuacct_base, id);
908
909 ff = procfile_reopen(ff, filename, NULL, CGROUP_PROCFILE_FLAG);
910 if (unlikely(!ff)) {
911 netdata_log_debug(D_CGROUP, "CGROUP: k8s_is_pause_container(): cannot open file '%s'.", filename);
912 return 1;
913 }
914
915 ff = procfile_readall(ff);
916 if (unlikely(!ff)) {
917 netdata_log_debug(D_CGROUP, "CGROUP: k8s_is_pause_container(): cannot read file '%s'.", filename);
918 return 1;
919 }
920
921 unsigned long lines = procfile_lines(ff);
922 if (likely(lines < 2)) {
923 return 1;
924 }
925
926 char *pid = procfile_lineword(ff, 0, 0);
927 if (!pid || !*pid) {
928 return 1;
929 }
930
931 snprintfz(filename, FILENAME_MAX, "%s/proc/%s/comm", netdata_configured_host_prefix, pid);
932
933 ff = procfile_reopen(ff, filename, NULL, PROCFILE_FLAG_DEFAULT);
934 if (unlikely(!ff)) {
935 netdata_log_debug(D_CGROUP, "CGROUP: k8s_is_pause_container(): cannot open file '%s'.", filename);
936 return 1;
937 }
938
939 ff = procfile_readall(ff);
940 if (unlikely(!ff)) {
941 netdata_log_debug(D_CGROUP, "CGROUP: k8s_is_pause_container(): cannot read file '%s'.", filename);
942 return 1;
943 }
944
945 lines = procfile_lines(ff);
946 if (unlikely(lines != 2)) {
947 return 1;
948 }
949
950 char *proc_comm = procfile_lineword(ff, 0, 0);
951 if (!proc_comm || !*proc_comm) {
952 return 1;
953 }
954
955 strncpyz(comm, proc_comm, TASK_COMM_LEN);
956 return 0;
957 }
958
959 static inline void discovery_process_first_time_seen_cgroup(struct cgroup *cg) {
960 if (!cg->first_time_seen) {
961 return;
962 }
963 cg->first_time_seen = 0;
964
965 char comm[TASK_COMM_LEN + 1];
966
967 if (cg->container_orchestrator == CGROUPS_ORCHESTRATOR_UNSET) {
968 if (strstr(cg->id, "kubepods")) {
969 cg->container_orchestrator = CGROUPS_ORCHESTRATOR_K8S;
970 } else {
971 cg->container_orchestrator = CGROUPS_ORCHESTRATOR_UNKNOWN;
972 }
973 }
974
975 if (is_inside_k8s && !k8s_get_container_first_proc_comm(cg->id, comm)) {
976 // container initialization may take some time when CPU % is high
977 // seen on GKE: comm is '6' before 'runc:[2:INIT]' (dunno if it could be another number)
978 if (is_digits_only(comm) || matches_entrypoint_parent_process_comm(comm)) {
979 cg->first_time_seen = 1;
980 return;
981 }
982 if (!strcmp(comm, "pause")) {
983 // a container that holds the network namespace for the pod
984 // we don't need to collect its metrics
985 cg->processed = 1;
986 return;
987 }
988 }
989
990 if (matches_systemd_services_cgroups(cg->id)) {
991 netdata_log_debug(D_CGROUP, "cgroup '%s' (name '%s') matches 'cgroups to match as systemd services'", cg->id, cg->chart_id);
992 convert_cgroup_to_systemd_service(cg);
993 return;
994 }
995
996 if (matches_enabled_cgroup_renames(cg->id)) {
997 netdata_log_debug(D_CGROUP, "cgroup '%s' (name '%s') matches 'run script to rename cgroups matching', will try to rename it", cg->id, cg->chart_id);
998 if (is_inside_k8s && is_cgroup_k8s_container(cg->id)) {
999 // it may take up to a minute for the K8s API to return data for the container
1000 // tested on AWS K8s cluster with 100% CPU utilization
1001 cg->pending_renames = 9; // 1.5 minute
1002 } else {
1003 cg->pending_renames = 2;
1004 }
1005 }
1006 }
1007
1008 static int discovery_is_cgroup_duplicate(struct cgroup *cg) {
1009 // https://github.com/netdata/netdata/issues/797#issuecomment-241248884
1010 struct cgroup *c;
1011 for (c = discovered_cgroup_root; c; c = c->discovered_next) {
1012 if (c != cg && c->enabled && c->available && (is_cgroup_systemd_service(c) == is_cgroup_systemd_service(cg)) &&
1013 c->hash_chart_id == cg->hash_chart_id && !strcmp(c->chart_id, cg->chart_id)) {
1014 collector_error(
1015 "CGROUP: chart id '%s' already exists with id '%s' and is enabled and available. Disabling cgroup with id '%s'.",
1016 cg->chart_id,
1017 c->id,
1018 cg->id);
1019 return 1;
1020 }
1021 }
1022 return 0;
1023 }
1024
1025 // ----------------------------------------------------------------------------
1026 // cgroup network interfaces
1027
1028 #define CGROUP_NETWORK_INTERFACE_MAX_LINE 2048
1029
1030 static inline void read_cgroup_network_interfaces(struct cgroup *cg) {
1031 netdata_log_debug(D_CGROUP, "looking for the network interfaces of cgroup '%s' with chart id '%s'", cg->id, cg->chart_id);
1032
1033 char cgroup_identifier[CGROUP_NETWORK_INTERFACE_MAX_LINE + 1];
1034
1035 if(!(cg->options & CGROUP_OPTIONS_IS_UNIFIED)) {
1036 snprintfz(cgroup_identifier, CGROUP_NETWORK_INTERFACE_MAX_LINE, "%s%s", cgroup_cpuacct_base, cg->id);
1037 }
1038 else {
1039 snprintfz(cgroup_identifier, CGROUP_NETWORK_INTERFACE_MAX_LINE, "%s%s", cgroup_unified_base, cg->id);
1040 }
1041
1042 netdata_log_debug(D_CGROUP, "executing cgroup_identifier %s --cgroup '%s' for cgroup '%s'", cgroups_network_interface_script, cgroup_identifier, cg->id);
1043 POPEN_INSTANCE *instance = spawn_popen_run_variadic(cgroups_network_interface_script, "--cgroup", cgroup_identifier, NULL);
1044 if(!instance) {
1045 collector_error("CGROUP: cannot popen(%s --cgroup \"%s\", \"r\").", cgroups_network_interface_script, cgroup_identifier);
1046 return;
1047 }
1048
1049 char *s;
1050 char buffer[CGROUP_NETWORK_INTERFACE_MAX_LINE + 1];
1051 while((s = fgets(buffer, CGROUP_NETWORK_INTERFACE_MAX_LINE, spawn_popen_stdout(instance)))) {
1052 trim(s);
1053
1054 if(*s && *s != '\n') {
1055 char *t = s;
1056 while(*t && *t != ' ') t++;
1057 if(*t == ' ') {
1058 *t = '\0';
1059 t++;
1060 }
1061
1062 if(!*s) {
1063 collector_error("CGROUP: empty host interface returned by script");
1064 continue;
1065 }
1066
1067 if(!*t) {
1068 collector_error("CGROUP: empty guest interface returned by script");
1069 continue;
1070 }
1071
1072 struct cgroup_network_interface *i = callocz(1, sizeof(struct cgroup_network_interface));
1073 i->host_device = strdupz(s);
1074 i->container_device = strdupz(t);
1075 i->next = cg->interfaces;
1076 cg->interfaces = i;
1077
1078 collector_info("CGROUP: cgroup '%s' has network interface '%s' as '%s'", cg->id, i->host_device, i->container_device);
1079
1080 // register a device rename to proc_net_dev.c
1081 cgroup_rename_task_add(
1082 i->host_device,
1083 i->container_device,
1084 cg->chart_id,
1085 cg->chart_labels,
1086 k8s_is_kubepod(cg) ? "k8s." : "",
1087 cgroup_netdev_get(cg));
1088 }
1089 }
1090
1091 spawn_popen_wait(instance);
1092 }
1093
1094 static inline void discovery_process_cgroup(struct cgroup *cg) {
1095 if (!cg->available || cg->processed) {
1096 return;
1097 }
1098
1099 if (cg->first_time_seen) {
1100 worker_is_busy(WORKER_DISCOVERY_PROCESS_FIRST_TIME);
1101 discovery_process_first_time_seen_cgroup(cg);
1102 if (unlikely(cg->first_time_seen || cg->processed)) {
1103 return;
1104 }
1105 }
1106
1107 if (cg->pending_renames) {
1108 worker_is_busy(WORKER_DISCOVERY_PROCESS_RENAME);
1109 discovery_rename_cgroup(cg);
1110 if (unlikely(cg->pending_renames || cg->processed)) {
1111 return;
1112 }
1113 }
1114
1115 cg->processed = 1;
1116
1117 if ((strlen(cg->chart_id) + strlen(cgroup_chart_id_prefix)) >= RRD_ID_LENGTH_MAX) {
1118 collector_info("cgroup '%s' (chart id '%s') disabled because chart_id exceeds the limit (RRD_ID_LENGTH_MAX)", cg->id, cg->chart_id);
1119 return;
1120 }
1121
1122 if (is_cgroup_systemd_service(cg)) {
1123 if (discovery_is_cgroup_duplicate(cg)) {
1124 cg->enabled = 0;
1125 cg->options |= CGROUP_OPTIONS_DISABLED_DUPLICATE;
1126 return;
1127 }
1128 if (!cg->chart_labels)
1129 cg->chart_labels = rrdlabels_create();
1130 rrdlabels_add(cg->chart_labels, "service_name", cg->name, RRDLABEL_SRC_AUTO);
1131 cg->enabled = 1;
1132 return;
1133 }
1134
1135 if (cg->options & CGROUP_OPTIONS_DISABLED_EXCLUDED) {
1136 cg->enabled = 0;
1137 return;
1138 }
1139
1140 if (!(cg->enabled = matches_enabled_cgroup_names(cg->name))) {
1141 netdata_log_debug(D_CGROUP, "cgroup '%s' (name '%s') disabled by 'enable by default cgroups names matching'", cg->id, cg->name);
1142 return;
1143 }
1144
1145 if (!(cg->enabled = matches_enabled_cgroup_paths(cg->id))) {
1146 netdata_log_debug(D_CGROUP, "cgroup '%s' (name '%s') disabled by 'enable by default cgroups matching'", cg->id, cg->name);
1147 return;
1148 }
1149
1150 if (discovery_is_cgroup_duplicate(cg)) {
1151 cg->enabled = 0;
1152 cg->options |= CGROUP_OPTIONS_DISABLED_DUPLICATE;
1153 return;
1154 }
1155
1156 if (!cg->chart_labels)
1157 cg->chart_labels = rrdlabels_create();
1158
1159 if (!k8s_is_kubepod(cg)) {
1160 rrdlabels_add(cg->chart_labels, "cgroup_name", cg->name, RRDLABEL_SRC_AUTO);
1161 if (!rrdlabels_exist(cg->chart_labels, "image"))
1162 rrdlabels_add(cg->chart_labels, "image", "", RRDLABEL_SRC_AUTO);
1163 }
1164
1165 worker_is_busy(WORKER_DISCOVERY_PROCESS_NETWORK);
1166 read_cgroup_network_interfaces(cg);
1167 }
1168
1169 static inline void discovery_find_all_cgroups() {
1170 netdata_log_debug(D_CGROUP, "searching for cgroups");
1171
1172 worker_is_busy(WORKER_DISCOVERY_INIT);
1173 discovery_mark_as_unavailable_all_cgroups();
1174
1175 worker_is_busy(WORKER_DISCOVERY_FIND);
1176 if (!cgroup_use_unified_cgroups) {
1177 discovery_find_all_cgroups_v1();
1178 } else {
1179 discovery_find_all_cgroups_v2();
1180 }
1181
1182 for (struct cgroup *cg = discovered_cgroup_root; cg && service_running(SERVICE_COLLECTORS); cg = cg->discovered_next) {
1183 worker_is_busy(WORKER_DISCOVERY_PROCESS);
1184 discovery_process_cgroup(cg);
1185 }
1186
1187 worker_is_busy(WORKER_DISCOVERY_UPDATE);
1188 discovery_update_filenames_all_cgroups();
1189
1190 worker_is_busy(WORKER_DISCOVERY_LOCK);
1191 netdata_mutex_lock(&cgroup_root_mutex);
1192
1193 worker_is_busy(WORKER_DISCOVERY_CLEANUP);
1194 discovery_cleanup_all_cgroups();
1195
1196 worker_is_busy(WORKER_DISCOVERY_COPY);
1197 discovery_copy_discovered_cgroups_to_reader();
1198
1199 netdata_mutex_unlock(&cgroup_root_mutex);
1200
1201 // cgroup metadata is now served on-demand via netipc (cgroup-netipc.c)
1202
1203 netdata_log_debug(D_CGROUP, "done searching for cgroups");
1204 }
1205
1206 void cgroup_discovery_worker(void *ptr)
1207 {
1208 UNUSED(ptr);
1209 uv_thread_set_name_np("P[cgroupsdisc]");
1210
1211 worker_register("CGROUPSDISC");
1212 worker_register_job_name(WORKER_DISCOVERY_INIT, "init");
1213 worker_register_job_name(WORKER_DISCOVERY_FIND, "find");
1214 worker_register_job_name(WORKER_DISCOVERY_PROCESS, "process");
1215 worker_register_job_name(WORKER_DISCOVERY_PROCESS_RENAME, "rename");
1216 worker_register_job_name(WORKER_DISCOVERY_PROCESS_NETWORK, "network");
1217 worker_register_job_name(WORKER_DISCOVERY_PROCESS_FIRST_TIME, "new");
1218 worker_register_job_name(WORKER_DISCOVERY_UPDATE, "update");
1219 worker_register_job_name(WORKER_DISCOVERY_CLEANUP, "cleanup");
1220 worker_register_job_name(WORKER_DISCOVERY_COPY, "copy");
1221 worker_register_job_name(WORKER_DISCOVERY_LOCK, "lock");
1222
1223 entrypoint_parent_process_comm = simple_pattern_create(
1224 " runc:[* " // http://terenceli.github.io/%E6%8A%80%E6%9C%AF/2021/12/28/runc-internals-3)
1225 " exe ", // https://github.com/falcosecurity/falco/blob/9d41b0a151b83693929d3a9c84f7c5c85d070d3a/rules/falco_rules.yaml#L1961
1226 NULL,
1227 SIMPLE_PATTERN_EXACT, true);
1228
1229 service_register(NULL, NULL, NULL);
1230
1231 cgroup_netipc_init();
1232
1233 while (service_running(SERVICE_COLLECTORS)) {
1234 worker_is_idle();
1235
1236 netdata_mutex_lock(&discovery_thread.mutex);
1237 netdata_cond_wait(&discovery_thread.cond_var, &discovery_thread.mutex);
1238 netdata_mutex_unlock(&discovery_thread.mutex);
1239
1240 if (unlikely(!service_running(SERVICE_COLLECTORS)))
1241 break;
1242
1243 discovery_find_all_cgroups();
1244 }
1245
1246 // Stop the netipc server first so its worker threads cannot iterate cgroup_root while we free it.
1247 cgroup_netipc_cleanup();
1248 discovery_walkdir_open_errors_cleanup();
1249
1250 // free all cgroups
1251 netdata_mutex_lock(&cgroup_root_mutex);
1252 while(cgroup_root) {
1253 struct cgroup *cg = cgroup_root;
1254 cgroup_root = cg->next;
1255 cgroup_free(cg);
1256 }
1257 netdata_mutex_unlock(&cgroup_root_mutex);
1258
1259 collector_info("discovery thread stopped");
1260 worker_unregister();
1261 service_exits();
1262 __atomic_store_n(&discovery_thread.exited, 1, __ATOMIC_RELEASE);
1263 }