// SPDX-License-Identifier: GPL-3.0-or-later

#include "cgroup-internals.h"
#include "cgroup-netipc.h"
#include "cgroup-snapshot-store.h"
#include "cgroup-name-labels.h"

// grace period added on top of the cgroup-name timeout: the helper self-bounds
// at the operator timeout, this is the extra slack before discovery kills it
#define CGROUP_NAME_GRACE_MS 2000

// discovery cgroup thread worker jobs
#define WORKER_DISCOVERY_INIT               0
#define WORKER_DISCOVERY_FIND               1
#define WORKER_DISCOVERY_PROCESS            2
#define WORKER_DISCOVERY_PROCESS_RENAME     3
#define WORKER_DISCOVERY_PROCESS_NETWORK    4
#define WORKER_DISCOVERY_PROCESS_FIRST_TIME 5
#define WORKER_DISCOVERY_UPDATE             6
#define WORKER_DISCOVERY_CLEANUP            7
#define WORKER_DISCOVERY_COPY               8
#define WORKER_DISCOVERY_LOCK               9

#if WORKER_UTILIZATION_MAX_JOB_TYPES < 10
#error WORKER_UTILIZATION_MAX_JOB_TYPES has to be at least 10
#endif

struct cgroup *discovered_cgroup_root = NULL;

char cgroup_chart_id_prefix[] = "cgroup_";
char services_chart_id_prefix[] = "systemd_";
const char *cgroups_rename_script = NULL;
static DICTIONARY *discovery_walkdir_open_errors = NULL;
static size_t discovery_walkdir_open_errors_count = 0;

#define DISCOVERY_WALKDIR_OPEN_ERRORS_MAX 256

// Legacy SHM globals removed; cgroup-netipc.c serves metadata via netipc.

static RRDSET *cgroup_discovery_scans_st = NULL;
static RRDDIM *cgroup_discovery_scans_natural_rd = NULL;
static RRDDIM *cgroup_discovery_scans_opportunistic_rd = NULL;

// ----------------------------------------------------------------------------

static inline void free_pressure(struct pressure *res) {
    if (res->some.share_time.st) rrdset_is_obsolete___safe_from_collector_thread(res->some.share_time.st);
    if (res->some.total_time.st) rrdset_is_obsolete___safe_from_collector_thread(res->some.total_time.st);
    if (res->full.share_time.st) rrdset_is_obsolete___safe_from_collector_thread(res->full.share_time.st);
    if (res->full.total_time.st) rrdset_is_obsolete___safe_from_collector_thread(res->full.total_time.st);
    freez(res->filename);
}

static inline void cgroup_free_network_interfaces(struct cgroup *cg) {
    while(cg->interfaces) {
        struct cgroup_network_interface *i = cg->interfaces;
        cg->interfaces = i->next;

        // delete the registration of proc_net_dev rename
        cgroup_rename_task_device_del(i->host_device);

        freez((void *)i->host_device);
        freez((void *)i->container_device);
        freez((void *)i);
    }
}

static inline void cgroup_free(struct cgroup *cg) {
    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");

    if(cg->st_cpu && cg->chart_var_cpu_limit) {
        rrdvar_chart_variable_release(cg->st_cpu, cg->chart_var_cpu_limit);
        cg->chart_var_cpu_limit = NULL;
    }

    if(cg->st_mem_usage && cg->chart_var_memory_limit) {
        rrdvar_chart_variable_release(cg->st_mem_usage, cg->chart_var_memory_limit);
        cg->chart_var_memory_limit = NULL;
    }

    cgroup_netdev_delete(cg);

    if(cg->st_cpu) rrdset_is_obsolete___safe_from_collector_thread(cg->st_cpu);
    if(cg->st_cpu_limit) rrdset_is_obsolete___safe_from_collector_thread(cg->st_cpu_limit);
    if(cg->st_cpu_per_core) rrdset_is_obsolete___safe_from_collector_thread(cg->st_cpu_per_core);
    if(cg->st_cpu_nr_throttled) rrdset_is_obsolete___safe_from_collector_thread(cg->st_cpu_nr_throttled);
    if(cg->st_cpu_throttled_time) rrdset_is_obsolete___safe_from_collector_thread(cg->st_cpu_throttled_time);
    if(cg->st_cpu_shares) rrdset_is_obsolete___safe_from_collector_thread(cg->st_cpu_shares);
    if(cg->st_mem) rrdset_is_obsolete___safe_from_collector_thread(cg->st_mem);
    if(cg->st_writeback) rrdset_is_obsolete___safe_from_collector_thread(cg->st_writeback);
    if(cg->st_mem_activity) rrdset_is_obsolete___safe_from_collector_thread(cg->st_mem_activity);
    if(cg->st_pgfaults) rrdset_is_obsolete___safe_from_collector_thread(cg->st_pgfaults);
    if(cg->st_mem_usage) rrdset_is_obsolete___safe_from_collector_thread(cg->st_mem_usage);
    if(cg->st_mem_usage_limit) rrdset_is_obsolete___safe_from_collector_thread(cg->st_mem_usage_limit);
    if(cg->st_mem_utilization) rrdset_is_obsolete___safe_from_collector_thread(cg->st_mem_utilization);
    if(cg->st_mem_failcnt) rrdset_is_obsolete___safe_from_collector_thread(cg->st_mem_failcnt);
    if(cg->st_io) rrdset_is_obsolete___safe_from_collector_thread(cg->st_io);
    if(cg->st_serviced_ops) rrdset_is_obsolete___safe_from_collector_thread(cg->st_serviced_ops);
    if(cg->st_throttle_io) rrdset_is_obsolete___safe_from_collector_thread(cg->st_throttle_io);
    if(cg->st_throttle_serviced_ops) rrdset_is_obsolete___safe_from_collector_thread(cg->st_throttle_serviced_ops);
    if(cg->st_queued_ops) rrdset_is_obsolete___safe_from_collector_thread(cg->st_queued_ops);
    if(cg->st_merged_ops) rrdset_is_obsolete___safe_from_collector_thread(cg->st_merged_ops);
    if(cg->st_pids) rrdset_is_obsolete___safe_from_collector_thread(cg->st_pids);

    if(cg->st_cachestat_ratio)   rrdset_is_obsolete___safe_from_collector_thread(cg->st_cachestat_ratio);
    if(cg->st_cachestat_dirties) rrdset_is_obsolete___safe_from_collector_thread(cg->st_cachestat_dirties);
    if(cg->st_cachestat_hits)    rrdset_is_obsolete___safe_from_collector_thread(cg->st_cachestat_hits);
    if(cg->st_cachestat_misses)  rrdset_is_obsolete___safe_from_collector_thread(cg->st_cachestat_misses);

    if(cg->st_net_conn_ipv4)       rrdset_is_obsolete___safe_from_collector_thread(cg->st_net_conn_ipv4);
    if(cg->st_net_conn_ipv6)       rrdset_is_obsolete___safe_from_collector_thread(cg->st_net_conn_ipv6);
    if(cg->st_net_total_bandwidth) rrdset_is_obsolete___safe_from_collector_thread(cg->st_net_total_bandwidth);
    if(cg->st_net_tcp_recv)        rrdset_is_obsolete___safe_from_collector_thread(cg->st_net_tcp_recv);
    if(cg->st_net_tcp_send)        rrdset_is_obsolete___safe_from_collector_thread(cg->st_net_tcp_send);
    if(cg->st_net_retransmit)      rrdset_is_obsolete___safe_from_collector_thread(cg->st_net_retransmit);
    if(cg->st_net_udp_send)        rrdset_is_obsolete___safe_from_collector_thread(cg->st_net_udp_send);
    if(cg->st_net_udp_recv)        rrdset_is_obsolete___safe_from_collector_thread(cg->st_net_udp_recv);

    freez(cg->ebpf_pids);
    cg->ebpf_pids       = NULL;
    cg->ebpf_pids_count = 0;

    freez(cg->filename_cpuset_cpus);
    freez(cg->filename_cpu_cfs_period);
    freez(cg->filename_cpu_cfs_quota);
    freez(cg->filename_memory_limit);

    cgroup_free_network_interfaces(cg);

    freez(cg->cpuacct_usage.cpu_percpu);

    freez(cg->cpuacct_stat.filename);
    freez(cg->cpuacct_usage.filename);
    freez(cg->cpuacct_cpu_throttling.filename);
    freez(cg->cpuacct_cpu_shares.filename);

    arl_free(cg->memory.arl_base);
    freez(cg->memory.filename_detailed);
    freez(cg->memory.filename_failcnt);
    freez(cg->memory.filename_usage_in_bytes);
    freez(cg->memory.filename_msw_usage_in_bytes);

    freez(cg->io_service_bytes.filename);
    freez(cg->io_serviced.filename);

    freez(cg->throttle_io_service_bytes.filename);
    freez(cg->throttle_io_serviced.filename);

    freez(cg->io_merged.filename);
    freez(cg->io_queued.filename);
    freez(cg->pids_current.filename);

    free_pressure(&cg->cpu_pressure);
    free_pressure(&cg->io_pressure);
    free_pressure(&cg->memory_pressure);
    free_pressure(&cg->irq_pressure);

    freez(cg->id);
    freez(cg->intermediate_id);
    freez(cg->chart_id);
    freez(cg->name);

    rrdlabels_destroy(cg->chart_labels);

    memset(cg, 0, sizeof(*cg));
    freez(cg);

    cgroup_root_count--;
}

// ----------------------------------------------------------------------------
// add/remove/find cgroup objects

#define CGROUP_CHARTID_LINE_MAX 1024

static inline char *cgroup_chart_id_strdupz(const char *s) {
    if(!s || !*s) s = "/";

    if(*s == '/' && s[1] != '\0') s++;

    char *r = strdupz(s);
    netdata_fix_chart_id(r);

    return r;
}

// TODO: move the code to cgroup_chart_id_strdupz() when the renaming script is fixed
static inline void substitute_dots_in_id(char *s) {
    // dots are used to distinguish chart type and id in streaming, so we should replace them
    for (char *d = s; *d; d++) {
        if (*d == '.')
            *d = '-';
    }
}

// ----------------------------------------------------------------------------
// parse k8s labels

static char *cgroup_parse_resolved_name_and_labels(struct cgroup *cg, char *data) {
    if (!cg->chart_labels)
        cg->chart_labels = rrdlabels_create();

    bool ignored = false;
    char *name = cgroup_parse_name_and_labels(cg->chart_labels, data, &ignored);

    if(ignored)
        cg->options |= CGROUP_OPTIONS_DISABLED_EXCLUDED;
    else
        cg->options &= ~CGROUP_OPTIONS_DISABLED_EXCLUDED;

    return name;
}

static inline void discovery_rename_cgroup(struct cgroup *cg) {
    if (!cg->pending_renames) {
        return;
    }
    cg->pending_renames--;

    netdata_log_debug(D_CGROUP, "looking for the name of cgroup '%s' with chart id '%s'", cg->id, cg->chart_id);
    netdata_log_debug(D_CGROUP, "executing command %s \"%s\" for cgroup '%s'", cgroups_rename_script, cg->intermediate_id, cg->chart_id);

    POPEN_INSTANCE *instance = spawn_popen_run_variadic(cgroups_rename_script, cg->id, cg->intermediate_id, NULL);
    if (!instance) {
        collector_error("CGROUP: cannot popen(%s \"%s\", \"r\").", cgroups_rename_script, cg->intermediate_id);
        cg->pending_renames = 0;
        cg->processed = 1;
        return;
    }

    char buffer[CGROUP_NAME_LINE_MAX + 2]; // payload, newline, terminator
    char *new_name = NULL;

    // Bound the wait for the helper's reply independently of whether the
    // helper honors its own timeout: a wedged helper must not stall discovery.
    // Wait up to the operator timeout plus a grace period for the helper to
    // complete its framed line, then reclaim it. cgroup_name_timeout_ms == 0
    // keeps the legacy unbounded behavior.
    int wait_ms = -1;
    if (cgroup_name_timeout_ms > 0)
        wait_ms = cgroup_name_timeout_ms > INT_MAX - CGROUP_NAME_GRACE_MS ?
                      INT_MAX :
                      cgroup_name_timeout_ms + CGROUP_NAME_GRACE_MS;

    CGROUP_NAME_READ_RESULT read_result = cgroup_name_read_response(
        spawn_popen_read_fd(instance), buffer, sizeof(buffer), wait_ms);
    if (read_result == CGROUP_NAME_READ_COMPLETE)
        new_name = buffer;
    else if (read_result == CGROUP_NAME_READ_INVALID)
        collector_error("CGROUP: rename helper for '%s' produced an oversized or incomplete record.", cg->id);

    int exit_code = -1;
    if (read_result == CGROUP_NAME_READ_TIMEOUT || read_result == CGROUP_NAME_READ_ERROR) {
        // timeout or poll error: the helper hung. Kill it, drop any partial
        // output, and stop retrying this cgroup - re-running a helper that
        // already hung would just block discovery again every cycle. The cgroup
        // keeps its raw chart id.
        if (read_result == CGROUP_NAME_READ_TIMEOUT)
            collector_error(
                "CGROUP: rename helper for '%s' did not respond within %d ms; killing it.", cg->id, wait_ms);
        else
            collector_error("CGROUP: cannot read rename helper response for '%s'; killing it.", cg->id);
        spawn_popen_kill(instance, 0);
        cg->pending_renames = 0;
        new_name = NULL;
    } else {
        SPAWN_TIMEDWAIT_RESULT res = spawn_popen_timedwait(instance, CGROUP_NAME_GRACE_MS, &exit_code);
        if (res == SPAWN_TIMEDWAIT_EXITED) {
            switch (exit_code) {
                case 0:
                    cg->pending_renames = 0;
                    break;

                case 3:
                    cg->pending_renames = 0;
                    cg->processed = 1;
                    break;

                default:
                    // Exit 2 may carry a fallback while requesting a retry; exit
                    // 1 carries no usable record. Keep the normal retry ladder,
                    // then apply any fallback or continue with the raw chart id.
                    break;
            }
        } else {
            // still running after writing, or wait error: reclaim, drop the
            // output, and stop retrying (same reasoning as the timeout path).
            spawn_popen_kill(instance, 0);
            cg->pending_renames = 0;
            new_name = NULL;
        }
    }

    if (cg->pending_renames || cg->processed)
        return;
    // On a clean exit (0 or the final retry of exit 2) apply whatever name the
    // helper printed; the kill/timeout paths above already cleared new_name.
    if (!new_name || !*new_name || *new_name == '\n')
        return;
    if (!(new_name = trim(new_name)))
        return;

    char *name = cgroup_parse_resolved_name_and_labels(cg, new_name);

    freez(cg->name);
    cg->name = strdupz(name);

    freez(cg->chart_id);
    cg->chart_id = cgroup_chart_id_strdupz(name);

    substitute_dots_in_id(cg->chart_id);
    cg->hash_chart_id = simple_hash(cg->chart_id);
}

static inline void convert_cgroup_to_systemd_service(struct cgroup *cg) {
    char buffer[CGROUP_CHARTID_LINE_MAX + 1];
    cg->options |= CGROUP_OPTIONS_SYSTEM_SLICE_SERVICE;
    strncpyz(buffer, cg->id, CGROUP_CHARTID_LINE_MAX);
    char *s = buffer;

    // skip to the last slash
    char *slash = strrchr(s, '/');
    if (unlikely(slash && slash != s))
        s = slash + 1;

    // remove extension
    char *dot = strrchr(s, '.');
    if (unlikely(dot && dot != s))
        *dot = '\0';

    freez(cg->name);
    cg->name = strdupz(s);

    freez(cg->chart_id);
    cg->chart_id = cgroup_chart_id_strdupz(s);
    substitute_dots_in_id(cg->chart_id);
    cg->hash_chart_id = simple_hash(cg->chart_id);
}

static inline struct cgroup *discovery_cgroup_add(const char *id) {
    netdata_log_debug(D_CGROUP, "adding to list, cgroup with id '%s'", id);

    struct cgroup *cg = callocz(1, sizeof(struct cgroup));

    cg->id = strdupz(id);
    cg->hash = simple_hash(cg->id);

    cg->name = strdupz(id);

    cg->intermediate_id = cgroup_chart_id_strdupz(id);

    cg->chart_id = cgroup_chart_id_strdupz(id);
    substitute_dots_in_id(cg->chart_id);
    cg->hash_chart_id = simple_hash(cg->chart_id);

    if (cgroup_use_unified_cgroups) {
        cg->options |= CGROUP_OPTIONS_IS_UNIFIED;
    }

    if (!discovered_cgroup_root)
        discovered_cgroup_root = cg;
    else {
        struct cgroup *t;
        for (t = discovered_cgroup_root; t->discovered_next; t = t->discovered_next) {
        }
        t->discovered_next = cg;
    }

    return cg;
}

static inline struct cgroup *discovery_cgroup_find(const char *id) {
    netdata_log_debug(D_CGROUP, "searching for cgroup '%s'", id);

    uint32_t hash = simple_hash(id);

    struct cgroup *cg;
    for(cg = discovered_cgroup_root; cg ; cg = cg->discovered_next) {
        if(hash == cg->hash && strcmp(id, cg->id) == 0)
            break;
    }

    netdata_log_debug(D_CGROUP, "cgroup '%s' %s in memory", id, (cg)?"found":"not found");
    return cg;
}

struct discovery_walkdir_open_error_prune {
    size_t remaining;
};

static int discovery_walkdir_open_error_prune_cb(const DICTIONARY_ITEM *item, void *value __maybe_unused, void *data) {
    struct discovery_walkdir_open_error_prune *prune = data;

    if (!prune->remaining)
        return -1;

    dictionary_del(discovery_walkdir_open_errors, dictionary_acquired_item_name(item));
    discovery_walkdir_open_errors_count--;
    prune->remaining--;

    return prune->remaining ? 0 : -1;
}

static inline bool discovery_walkdir_open_error_first(const char *dirpath) {
    if (unlikely(!discovery_walkdir_open_errors))
        discovery_walkdir_open_errors = dictionary_create(DICT_OPTION_SINGLE_THREADED);

    if (dictionary_get(discovery_walkdir_open_errors, dirpath))
        return false;

    if (discovery_walkdir_open_errors_count >= DISCOVERY_WALKDIR_OPEN_ERRORS_MAX) {
        struct discovery_walkdir_open_error_prune prune = {
            .remaining = discovery_walkdir_open_errors_count - DISCOVERY_WALKDIR_OPEN_ERRORS_MAX + 1,
        };

        dictionary_walkthrough_write(discovery_walkdir_open_errors, discovery_walkdir_open_error_prune_cb, &prune);
    }

    uint8_t logged = 1;
    dictionary_set(discovery_walkdir_open_errors, dirpath, &logged, sizeof(logged));
    discovery_walkdir_open_errors_count++;

    return true;
}

static inline void discovery_walkdir_open_errors_cleanup(void) {
    if (!discovery_walkdir_open_errors)
        return;

    dictionary_destroy(discovery_walkdir_open_errors);
    discovery_walkdir_open_errors = NULL;
    discovery_walkdir_open_errors_count = 0;
}

static int calc_cgroup_depth(const char *id) {
    int depth = 0;
    const char *s;
    for (s = id; *s; s++) {
        depth += unlikely(*s == '/');
    }
    return depth;
}

static inline void discovery_find_cgroup_in_dir(const char *dir) {
    if (!dir || !*dir) {
        dir = "/";
    }

    struct cgroup *cg = discovery_cgroup_find(dir);
    if (cg) {
        cg->available = 1;
        return;
    }

    if (cgroup_root_count >= cgroup_root_max) {
        nd_log_limit_static_global_var(erl, 3600, 0);
        nd_log_limit(&erl, NDLS_COLLECTORS, NDLP_WARNING, "CGROUP: maximum number of cgroups reached (%d). No more cgroups will be added.", cgroup_root_count);
        return;
    }

    if (cgroup_max_depth > 0) {
        int depth = calc_cgroup_depth(dir);
        if (depth > cgroup_max_depth) {
            nd_log_collector(NDLP_DEBUG, "CGROUP: '%s' is too deep (%d, while max is %d)", dir, depth, cgroup_max_depth);
            return;
        }
    }

    cg = discovery_cgroup_add(dir);
    cg->available = 1;
    cg->first_time_seen = 1;
    cg->function_ready = false;
    cgroup_root_count++;
}

static inline int discovery_find_walkdir(const char *base, const char *dirpath) {
    if (!dirpath)
        dirpath = base;

    netdata_log_debug(D_CGROUP, "searching for directories in '%s' (base '%s')", dirpath ? dirpath : "", base);

    size_t dirlen = strlen(dirpath), baselen = strlen(base);

    int ret = -1;
    int enabled = -1;

    const char *relative_path = &dirpath[baselen];
    if (!*relative_path)
        relative_path = "/";

    DIR *dir = opendir(dirpath);
    if(!dir) {
        int err = errno;
        bool first_log_for_path = discovery_walkdir_open_error_first(dirpath);

        if(err == EACCES && strcmp(dirpath, base) != 0) {
            if (first_log_for_path)
                nd_log_collector(NDLP_DEBUG, "CGROUP: cannot open directory '%s': %s", dirpath, strerror(err));
        }
        else if (first_log_for_path) {
            collector_error("CGROUP: cannot open directory '%s': %s", dirpath, strerror(err));
        }
        return ret;
    }
    ret = 1;

    discovery_find_cgroup_in_dir(relative_path);

    struct dirent *de = NULL;
    while((de = readdir(dir))) {
        if (de->d_type == DT_DIR && ((de->d_name[0] == '.' && de->d_name[1] == '\0') ||
                                     (de->d_name[0] == '.' && de->d_name[1] == '.' && de->d_name[2] == '\0')))
            continue;

        if(de->d_type == DT_DIR) {
            if(enabled == -1) {
                const char *r = relative_path;
                if (*r == '\0')
                    r = "/";

                // do not decent in directories we are not interested
                enabled = matches_search_cgroup_paths(r);
            }

            if(enabled) {
                char *s = mallocz(dirlen + strlen(de->d_name) + 2);
                strcpy(s, dirpath);
                strcat(s, "/");
                strcat(s, de->d_name);
                int ret2 = discovery_find_walkdir(base, s);
                if (ret2 > 0)
                    ret += ret2;
                freez(s);
            }
        }
    }

    closedir(dir);
    return ret;
}

static inline void discovery_mark_as_unavailable_all_cgroups() {
    for (struct cgroup *cg = discovered_cgroup_root; cg; cg = cg->discovered_next) {
        cg->available = 0;
    }
}

static inline void discovery_update_filenames_cgroup_v1(struct cgroup *cg) {
    char filename[FILENAME_MAX + 1];
    struct stat buf;

    // CPU
    if (likely(cgroup_enable_cpuacct)) {
        if (unlikely(!cg->cpuacct_stat.staterr && !cg->cpuacct_stat.filename)) {
            snprintfz(filename, FILENAME_MAX, "%s%s/cpuacct.stat", cgroup_cpuacct_base, cg->id);
            if (!(cg->cpuacct_stat.staterr = stat(filename, &buf) != 0)) {
                cg->cpuacct_stat.filename = strdupz(filename);

                snprintfz(filename, FILENAME_MAX, "%s%s/cpuset.cpus", cgroup_cpuset_base, cg->id);
                cg->filename_cpuset_cpus = strdupz(filename);

                snprintfz(filename, FILENAME_MAX, "%s%s/cpu.cfs_period_us", cgroup_cpuacct_base, cg->id);
                cg->filename_cpu_cfs_period = strdupz(filename);

                snprintfz(filename, FILENAME_MAX, "%s%s/cpu.cfs_quota_us", cgroup_cpuacct_base, cg->id);
                cg->filename_cpu_cfs_quota = strdupz(filename);
            }
        }
        if (!is_cgroup_systemd_service(cg)) {
            if (unlikely(!cg->cpuacct_cpu_throttling.staterr && !cg->cpuacct_cpu_throttling.filename)) {
                snprintfz(filename, FILENAME_MAX, "%s%s/cpu.stat", cgroup_cpuacct_base, cg->id);
                if (!(cg->cpuacct_cpu_throttling.staterr = stat(filename, &buf) != 0)) {
                    cg->cpuacct_cpu_throttling.filename = strdupz(filename);
                }
            }

            if (cgroup_enable_cpuacct_cpu_shares) {
                if (unlikely(!cg->cpuacct_cpu_shares.staterr && !cg->cpuacct_cpu_shares.filename)) {
                    snprintfz(filename, FILENAME_MAX, "%s%s/cpu.shares", cgroup_cpuacct_base, cg->id);

                    if (!(cg->cpuacct_cpu_shares.staterr = stat(filename, &buf) != 0)) {
                        cg->cpuacct_cpu_shares.filename = strdupz(filename);
                    }
                }
            }
        }
    }

    // Memory
    if (likely(cgroup_enable_memory)) {
        if (unlikely(!cg->memory.staterr_mem_current && !cg->memory.filename_usage_in_bytes)) {
            snprintfz(filename, FILENAME_MAX, "%s%s/memory.usage_in_bytes", cgroup_memory_base, cg->id);
            if (!(cg->memory.staterr_mem_current = stat(filename, &buf) != 0)) {
                cg->memory.filename_usage_in_bytes = strdupz(filename);

                snprintfz(filename, FILENAME_MAX, "%s%s/memory.limit_in_bytes", cgroup_memory_base, cg->id);
                cg->filename_memory_limit = strdupz(filename);
            }
        }

        if (unlikely(!cg->memory.staterr_mem_stat && !cg->memory.filename_detailed)) {
            snprintfz(filename, FILENAME_MAX, "%s%s/memory.stat", cgroup_memory_base, cg->id);
            if (!(cg->memory.staterr_mem_stat = stat(filename, &buf) != 0)) {
                cg->memory.filename_detailed = strdupz(filename);
            }
        }

        if (unlikely(!cg->memory.staterr_failcnt && !cg->memory.filename_failcnt)) {
            snprintfz(filename, FILENAME_MAX, "%s%s/memory.failcnt", cgroup_memory_base, cg->id);
            if (!(cg->memory.staterr_failcnt = stat(filename, &buf) != 0)) {
                cg->memory.filename_failcnt = strdupz(filename);
            }
        }
    }

    // Blkio
    if (likely(cgroup_enable_blkio)) {
        if (unlikely(!cg->io_service_bytes.staterr && !cg->io_service_bytes.filename)) {
            snprintfz(filename, FILENAME_MAX, "%s%s/blkio.io_service_bytes_recursive", cgroup_blkio_base, cg->id);
            if (!(cg->io_service_bytes.staterr = stat(filename, &buf) != 0)) {
                cg->io_service_bytes.filename = strdupz(filename);
            } else {
                snprintfz(filename, FILENAME_MAX, "%s%s/blkio.io_service_bytes", cgroup_blkio_base, cg->id);
                if (!(cg->io_service_bytes.staterr = stat(filename, &buf) != 0)) {
                    cg->io_service_bytes.filename = strdupz(filename);
                }
            }
        }

        if (unlikely(!cg->io_serviced.staterr && !cg->io_serviced.filename)) {
            snprintfz(filename, FILENAME_MAX, "%s%s/blkio.io_serviced_recursive", cgroup_blkio_base, cg->id);
            if (!(cg->io_serviced.staterr = stat(filename, &buf) != 0)) {
                cg->io_serviced.filename = strdupz(filename);
            } else {
                snprintfz(filename, FILENAME_MAX, "%s%s/blkio.io_serviced", cgroup_blkio_base, cg->id);
                if (!(cg->io_serviced.staterr = stat(filename, &buf) != 0)) {
                    cg->io_serviced.filename = strdupz(filename);
                }
            }
        }

        if (unlikely(!cg->throttle_io_service_bytes.staterr && !cg->throttle_io_service_bytes.filename)) {
            snprintfz(
                filename, FILENAME_MAX, "%s%s/blkio.throttle.io_service_bytes_recursive", cgroup_blkio_base, cg->id);
            if (!(cg->throttle_io_service_bytes.staterr = stat(filename, &buf) != 0)) {
                cg->throttle_io_service_bytes.filename = strdupz(filename);
            } else {
                snprintfz(filename, FILENAME_MAX, "%s%s/blkio.throttle.io_service_bytes", cgroup_blkio_base, cg->id);
                if (!(cg->throttle_io_service_bytes.staterr = stat(filename, &buf) != 0)) {
                    cg->throttle_io_service_bytes.filename = strdupz(filename);
                }
            }
        }

        if (unlikely(!cg->throttle_io_serviced.staterr && !cg->throttle_io_serviced.filename)) {
            snprintfz(filename, FILENAME_MAX, "%s%s/blkio.throttle.io_serviced_recursive", cgroup_blkio_base, cg->id);
            if(!(cg->throttle_io_serviced.staterr = stat(filename, &buf) != 0)) {
                cg->throttle_io_serviced.filename = strdupz(filename);
            } else {
                snprintfz(filename, FILENAME_MAX, "%s%s/blkio.throttle.io_serviced", cgroup_blkio_base, cg->id);
                if (!(cg->throttle_io_serviced.staterr = stat(filename, &buf) != 0)) {
                    cg->throttle_io_serviced.filename = strdupz(filename);
                }
            }
        }

        if (unlikely(!cg->io_merged.staterr && !cg->io_merged.filename)) {
            snprintfz(filename, FILENAME_MAX, "%s%s/blkio.io_merged_recursive", cgroup_blkio_base, cg->id);
            if (!(cg->io_merged.staterr = stat(filename, &buf) != 0)) {
                cg->io_merged.filename = strdupz(filename);
            } else {
                snprintfz(filename, FILENAME_MAX, "%s%s/blkio.io_merged", cgroup_blkio_base, cg->id);
                if (!(cg->io_merged.staterr = stat(filename, &buf) != 0)) {
                    cg->io_merged.filename = strdupz(filename);
                }
            }
        }

        if (unlikely(!cg->io_queued.staterr && !cg->io_queued.filename)) {
            snprintfz(filename, FILENAME_MAX, "%s%s/blkio.io_queued_recursive", cgroup_blkio_base, cg->id);
            if (!(cg->io_queued.staterr = stat(filename, &buf) != 0)) {
                cg->io_queued.filename = strdupz(filename);
            } else {
                snprintfz(filename, FILENAME_MAX, "%s%s/blkio.io_queued", cgroup_blkio_base, cg->id);
                if (!(cg->io_queued.staterr = stat(filename, &buf) != 0)) {
                    cg->io_queued.filename = strdupz(filename);
                }
            }
        }
    }

    // Pids
    if (unlikely(!cg->pids_current.staterr && !cg->pids_current.filename)) {
        snprintfz(filename, FILENAME_MAX, "%s%s/pids.current", cgroup_pids_base, cg->id);
        if (!(cg->pids_current.staterr = stat(filename, &buf) != 0)) {
            cg->pids_current.filename = strdupz(filename);
        }
    }
}

static inline void discovery_update_filenames_cgroup_v2(struct cgroup *cg) {
    if (!cgroup_unified_exist)
        return;

    char filename[FILENAME_MAX + 1];
    struct stat buf;

    // CPU
    if (unlikely((!cg->cpuacct_stat.staterr && !cg->cpuacct_stat.filename))) {
        snprintfz(filename, FILENAME_MAX, "%s%s/cpu.stat", cgroup_unified_base, cg->id);
        if (!(cg->cpuacct_stat.staterr = stat(filename, &buf) != 0)) {
            cg->cpuacct_stat.filename = strdupz(filename);
            cg->filename_cpuset_cpus = NULL;
            cg->filename_cpu_cfs_period = NULL;

            snprintfz(filename, FILENAME_MAX, "%s%s/cpu.max", cgroup_unified_base, cg->id);
            cg->filename_cpu_cfs_quota = strdupz(filename);
        }
    }
    if (cgroup_enable_cpuacct_cpu_shares) {
        if (unlikely(!cg->cpuacct_cpu_shares.staterr && !cg->cpuacct_cpu_shares.filename)) {
            snprintfz(filename, FILENAME_MAX, "%s%s/cpu.weight", cgroup_unified_base, cg->id);
            if (!(cg->cpuacct_cpu_shares.staterr = stat(filename, &buf) != 0)) {
                cg->cpuacct_cpu_shares.filename = strdupz(filename);
            }
        }
    }

    // Memory
    if (unlikely(!cg->memory.staterr_mem_current && !cg->memory.filename_usage_in_bytes)) {
        snprintfz(filename, FILENAME_MAX, "%s%s/memory.current", cgroup_unified_base, cg->id);
        if (!(cg->memory.staterr_mem_current = stat(filename, &buf) != 0)) {
            cg->memory.filename_usage_in_bytes = strdupz(filename);

            snprintfz(filename, FILENAME_MAX, "%s%s/memory.max", cgroup_unified_base, cg->id);
            cg->filename_memory_limit = strdupz(filename);
        }
    }

    if (unlikely(!cg->memory.staterr_mem_stat && !cg->memory.filename_detailed)) {
        snprintfz(filename, FILENAME_MAX, "%s%s/memory.stat", cgroup_unified_base, cg->id);
        if (!(cg->memory.staterr_mem_stat = stat(filename, &buf) != 0)) {
            cg->memory.filename_detailed = strdupz(filename);
        }
    }

    // Blkio
    if (unlikely(!cg->io_service_bytes.staterr && !cg->io_service_bytes.filename)) {
        snprintfz(filename, FILENAME_MAX, "%s%s/io.stat", cgroup_unified_base, cg->id);
        if (!(cg->io_service_bytes.staterr = stat(filename, &buf) != 0)) {
            cg->io_service_bytes.filename = strdupz(filename);
        }
    }

    if (unlikely(!cg->io_serviced.staterr && !cg->io_serviced.filename)) {
        snprintfz(filename, FILENAME_MAX, "%s%s/io.stat", cgroup_unified_base, cg->id);
        if (!(cg->io_serviced.staterr = stat(filename, &buf) != 0)) {
            cg->io_serviced.filename = strdupz(filename);
        }
    }

    // PSI
    if (unlikely(!cg->cpu_pressure.staterr && !cg->cpu_pressure.filename)) {
        snprintfz(filename, FILENAME_MAX, "%s%s/cpu.pressure", cgroup_unified_base, cg->id);
        if (!(cg->cpu_pressure.staterr = stat(filename, &buf) != 0)) {
            cg->cpu_pressure.filename = strdupz(filename);
            cg->cpu_pressure.some.enabled = CONFIG_BOOLEAN_YES;
            cg->cpu_pressure.full.enabled = CONFIG_BOOLEAN_YES;
        }
    }

    if (unlikely(!cg->io_pressure.staterr && !cg->io_pressure.filename)) {
        snprintfz(filename, FILENAME_MAX, "%s%s/io.pressure", cgroup_unified_base, cg->id);
        if (!(cg->io_pressure.staterr = stat(filename, &buf) != 0)) {
            cg->io_pressure.filename = strdupz(filename);
            cg->io_pressure.some.enabled = CONFIG_BOOLEAN_YES;
            cg->io_pressure.full.enabled = CONFIG_BOOLEAN_YES;
        }
    }

    if (unlikely(!cg->memory_pressure.staterr && !cg->memory_pressure.filename)) {
        snprintfz(filename, FILENAME_MAX, "%s%s/memory.pressure", cgroup_unified_base, cg->id);
        if (!(cg->memory_pressure.staterr = stat(filename, &buf) != 0)) {
            cg->memory_pressure.filename = strdupz(filename);
            cg->memory_pressure.some.enabled = CONFIG_BOOLEAN_YES;
            cg->memory_pressure.full.enabled = CONFIG_BOOLEAN_YES;
        }
    }

    if (unlikely(!cg->irq_pressure.staterr && !cg->irq_pressure.filename)) {
        snprintfz(filename, FILENAME_MAX, "%s%s/irq.pressure", cgroup_unified_base, cg->id);
        if (!(cg->irq_pressure.staterr = stat(filename, &buf) != 0)) {
            cg->irq_pressure.filename = strdupz(filename);
            cg->irq_pressure.some.enabled = CONFIG_BOOLEAN_YES;
            cg->irq_pressure.full.enabled = CONFIG_BOOLEAN_YES;
        }
    }

    // Pids
    if (unlikely(!cg->pids_current.staterr && !cg->pids_current.filename)) {
        snprintfz(filename, FILENAME_MAX, "%s%s/pids.current", cgroup_unified_base, cg->id);
        if (!(cg->pids_current.staterr = stat(filename, &buf) != 0)) {
            cg->pids_current.filename = strdupz(filename);
        }
    }
}

static inline void discovery_update_filenames_all_cgroups() {
    for (struct cgroup *cg = discovered_cgroup_root; cg; cg = cg->discovered_next) {
        if (unlikely(!cg->available || !cg->enabled || cg->pending_renames))
            continue;

        if (!cgroup_use_unified_cgroups)
            discovery_update_filenames_cgroup_v1(cg);
        else
            discovery_update_filenames_cgroup_v2(cg);
    }
}

static inline void discovery_cleanup_all_cgroups() {
    struct cgroup *cg = discovered_cgroup_root, *last = NULL;

    while(cg) {
        if(!cg->available) {
            // enable the first duplicate cgroup
            {
                struct cgroup *t;
                for (t = discovered_cgroup_root; t; t = t->discovered_next) {
                    if (t != cg && t->available && !t->enabled && t->options & CGROUP_OPTIONS_DISABLED_DUPLICATE &&
                        (is_cgroup_systemd_service(t) == is_cgroup_systemd_service(cg)) &&
                        t->hash_chart_id == cg->hash_chart_id && !strcmp(t->chart_id, cg->chart_id)) {
                        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);
                        t->enabled = 1;
                        t->options &= ~CGROUP_OPTIONS_DISABLED_DUPLICATE;
                        break;
                    }
                }
            }

            if(!last)
                discovered_cgroup_root = cg->discovered_next;
            else
                last->discovered_next = cg->discovered_next;

            cgroup_free(cg);

            if(!last)
                cg = discovered_cgroup_root;
            else
                cg = last->discovered_next;
        }
        else {
            last = cg;
            cg = cg->discovered_next;
        }
    }
}

static inline void discovery_copy_discovered_cgroups_to_reader() {
    netdata_log_debug(D_CGROUP, "copy discovered cgroups to the main group list");

    struct cgroup *cg;

    for (cg = discovered_cgroup_root; cg; cg = cg->discovered_next) {
        cg->next = cg->discovered_next;
    }

    cgroup_root = discovered_cgroup_root;
}

static inline void discovery_find_all_cgroups_v1() {
    if (cgroup_enable_cpuacct) {
        if (discovery_find_walkdir(cgroup_cpuacct_base, NULL) == -1) {
            cgroup_enable_cpuacct = false;
            collector_error("CGROUP: disabled cpu statistics.");
        }
    }

    if (cgroup_enable_blkio) {
        if (discovery_find_walkdir(cgroup_blkio_base, NULL) == -1) {
            cgroup_enable_blkio = false;
            collector_error("CGROUP: disabled blkio statistics.");
        }
    }

    if (cgroup_enable_memory) {
        if (discovery_find_walkdir(cgroup_memory_base, NULL) == -1) {
            cgroup_enable_memory = false;
            collector_error("CGROUP: disabled memory statistics.");
        }
    }
}

static inline void discovery_find_all_cgroups_v2() {
    if (cgroup_unified_exist) {
        if (discovery_find_walkdir(cgroup_unified_base, NULL) == -1) {
            cgroup_unified_exist = false;
            collector_error("CGROUP: disabled unified cgroups statistics.");
        }
    }
}

static int is_digits_only(const char *s) {
    do {
        if (!isdigit((uint8_t)*s++)) {
            return 0;
        }
    } while (*s);

    return 1;
}

static int is_cgroup_k8s_container(const char *id) {
    // examples:
    // See src/collectors/cgroups.plugin/cgroup-name/FLOW.md for the cgroup-name Kubernetes path rules.
    const char *p = id;
    const char *pp = NULL;
    int i = 0;
    size_t l = 3; // pod
    while ((p = strstr(p, "pod"))) {
        i++;
        p += l;
        pp = p;
    }
    return !(i < 2 || !pp || !(pp = strchr(pp, '/')) || !pp++ || !*pp);
}

#define TASK_COMM_LEN 16

static int k8s_get_container_first_proc_comm(const char *id, char *comm) {
    if (!is_cgroup_k8s_container(id)) {
        return 1;
    }

    static procfile *ff = NULL;

    char filename[FILENAME_MAX + 1];
    snprintfz(filename, FILENAME_MAX, "%s/%s/cgroup.procs", cgroup_cpuacct_base, id);

    ff = procfile_reopen(ff, filename, NULL, CGROUP_PROCFILE_FLAG);
    if (unlikely(!ff)) {
        netdata_log_debug(D_CGROUP, "CGROUP: k8s_is_pause_container(): cannot open file '%s'.", filename);
        return 1;
    }

    ff = procfile_readall(ff);
    if (unlikely(!ff)) {
        netdata_log_debug(D_CGROUP, "CGROUP: k8s_is_pause_container(): cannot read file '%s'.", filename);
        return 1;
    }

    unsigned long lines = procfile_lines(ff);
    if (likely(lines < 2)) {
        return 1;
    }

    char *pid = procfile_lineword(ff, 0, 0);
    if (!pid || !*pid) {
        return 1;
    }

    snprintfz(filename, FILENAME_MAX, "%s/proc/%s/comm", netdata_configured_host_prefix, pid);

    ff = procfile_reopen(ff, filename, NULL, PROCFILE_FLAG_DEFAULT);
    if (unlikely(!ff)) {
        netdata_log_debug(D_CGROUP, "CGROUP: k8s_is_pause_container(): cannot open file '%s'.", filename);
        return 1;
    }

    ff = procfile_readall(ff);
    if (unlikely(!ff)) {
        netdata_log_debug(D_CGROUP, "CGROUP: k8s_is_pause_container(): cannot read file '%s'.", filename);
        return 1;
    }

    lines = procfile_lines(ff);
    if (unlikely(lines != 2)) {
        return 1;
    }

    char *proc_comm = procfile_lineword(ff, 0, 0);
    if (!proc_comm || !*proc_comm) {
        return 1;
    }

    strncpyz(comm, proc_comm, TASK_COMM_LEN);
    return 0;
}

static inline void discovery_process_first_time_seen_cgroup(struct cgroup *cg) {
    if (!cg->first_time_seen) {
        return;
    }
    cg->first_time_seen = 0;

    char comm[TASK_COMM_LEN + 1];

    discovery_classify_orchestrator(cg);

    if (is_inside_k8s && !k8s_get_container_first_proc_comm(cg->id, comm)) {
        // container initialization may take some time when CPU % is high
        // seen on GKE: comm is '6' before 'runc:[2:INIT]' (dunno if it could be another number)
        if (is_digits_only(comm) || matches_entrypoint_parent_process_comm(comm)) {
            cg->first_time_seen = 1;
            return;
        }
        if (!strcmp(comm, "pause")) {
            // a container that holds the network namespace for the pod
            // we don't need to collect its metrics
            cg->processed = 1;
            return;
        }
    }

    if (matches_systemd_services_cgroups(cg->id)) {
        netdata_log_debug(D_CGROUP, "cgroup '%s' (name '%s') matches 'cgroups to match as systemd services'", cg->id, cg->chart_id);
        convert_cgroup_to_systemd_service(cg);
        return;
    }

    if (matches_enabled_cgroup_renames(cg->id)) {
        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);
        if (is_inside_k8s && is_cgroup_k8s_container(cg->id)) {
            // it may take up to a minute for the K8s API to return data for the container
            // tested on AWS K8s cluster with 100% CPU utilization
            cg->pending_renames = 9; // 1.5 minute
        } else {
            cg->pending_renames = 2;
        }
    }
}

static int discovery_is_cgroup_duplicate(struct cgroup *cg) {
    // https://github.com/netdata/netdata/issues/797#issuecomment-241248884
    struct cgroup *c;
    for (c = discovered_cgroup_root; c; c = c->discovered_next) {
        if (c != cg && c->enabled && c->available && (is_cgroup_systemd_service(c) == is_cgroup_systemd_service(cg)) &&
            c->hash_chart_id == cg->hash_chart_id && !strcmp(c->chart_id, cg->chart_id)) {
            collector_error(
                    "CGROUP: chart id '%s' already exists with id '%s' and is enabled and available. Disabling cgroup with id '%s'.",
                    cg->chart_id,
                    c->id,
                    cg->id);
            return 1;
        }
    }
    return 0;
}

// ----------------------------------------------------------------------------
// cgroup network interfaces

#define CGROUP_NETWORK_INTERFACE_MAX_LINE 2048

static inline void read_cgroup_network_interfaces(struct cgroup *cg) {
    netdata_log_debug(D_CGROUP, "looking for the network interfaces of cgroup '%s' with chart id '%s'", cg->id, cg->chart_id);

    char cgroup_identifier[CGROUP_NETWORK_INTERFACE_MAX_LINE + 1];

    if(!(cg->options & CGROUP_OPTIONS_IS_UNIFIED)) {
        snprintfz(cgroup_identifier, CGROUP_NETWORK_INTERFACE_MAX_LINE, "%s%s", cgroup_cpuacct_base, cg->id);
    }
    else {
        snprintfz(cgroup_identifier, CGROUP_NETWORK_INTERFACE_MAX_LINE, "%s%s", cgroup_unified_base, cg->id);
    }

    netdata_log_debug(D_CGROUP, "executing cgroup_identifier %s --cgroup '%s' for cgroup '%s'", cgroups_network_interface_script, cgroup_identifier, cg->id);
    POPEN_INSTANCE *instance = spawn_popen_run_variadic(cgroups_network_interface_script, "--cgroup", cgroup_identifier, NULL);
    if(!instance) {
        collector_error("CGROUP: cannot popen(%s --cgroup \"%s\", \"r\").", cgroups_network_interface_script, cgroup_identifier);
        return;
    }

    FILE *child_stdout = spawn_popen_stdout(instance);
    if(unlikely(!child_stdout)) {
        spawn_popen_kill(instance, 0);
        return;
    }

    char *s;
    char buffer[CGROUP_NETWORK_INTERFACE_MAX_LINE + 1];
    while((s = fgets(buffer, CGROUP_NETWORK_INTERFACE_MAX_LINE, child_stdout))) {
        trim(s);

        if(*s && *s != '\n') {
            char *t = s;
            while(*t && *t != ' ') t++;
            if(*t == ' ') {
                *t = '\0';
                t++;
            }

            if(!*s) {
                collector_error("CGROUP: empty host interface returned by script");
                continue;
            }

            if(!*t) {
                collector_error("CGROUP: empty guest interface returned by script");
                continue;
            }

            struct cgroup_network_interface *i = callocz(1, sizeof(struct cgroup_network_interface));
            i->host_device = strdupz(s);
            i->container_device = strdupz(t);
            i->next = cg->interfaces;
            cg->interfaces = i;

            collector_info("CGROUP: cgroup '%s' has network interface '%s' as '%s'", cg->id, i->host_device, i->container_device);

            // register a device rename to proc_net_dev.c
            cgroup_rename_task_add(
                i->host_device,
                i->container_device,
                cg->chart_id,
                cg->chart_labels,
                k8s_is_kubepod(cg) ? "k8s." : "",
                cgroup_netdev_get(cg));
        }
    }

    spawn_popen_wait(instance);
}

static inline void discovery_process_cgroup(struct cgroup *cg) {
    if (!cg->available || cg->processed) {
        return;
    }

    if (cg->first_time_seen) {
        worker_is_busy(WORKER_DISCOVERY_PROCESS_FIRST_TIME);
        discovery_process_first_time_seen_cgroup(cg);
        if (unlikely(cg->first_time_seen || cg->processed)) {
            return;
        }
    }

    if (cg->pending_renames) {
        worker_is_busy(WORKER_DISCOVERY_PROCESS_RENAME);
        discovery_rename_cgroup(cg);
        if (unlikely(cg->pending_renames || cg->processed)) {
            return;
        }
    }

    cg->processed = 1;

    if ((strlen(cg->chart_id) + strlen(cgroup_chart_id_prefix)) >= RRD_ID_LENGTH_MAX) {
        collector_info("cgroup '%s' (chart id '%s') disabled because chart_id exceeds the limit (RRD_ID_LENGTH_MAX)", cg->id, cg->chart_id);
        return;
    }

    if (is_cgroup_systemd_service(cg)) {
        if (discovery_is_cgroup_duplicate(cg)) {
            cg->enabled = 0;
            cg->options |= CGROUP_OPTIONS_DISABLED_DUPLICATE;
            return;
        }
        if (!cg->chart_labels)
            cg->chart_labels = rrdlabels_create();
        rrdlabels_add(cg->chart_labels, "service_name", cg->name, RRDLABEL_SRC_AUTO);
        cg->enabled = 1;
        return;
    }

    if (cg->options & CGROUP_OPTIONS_DISABLED_EXCLUDED) {
        cg->enabled = 0;
        return;
    }

    if (!(cg->enabled = matches_enabled_cgroup_names(cg->name))) {
        netdata_log_debug(D_CGROUP, "cgroup '%s' (name '%s') disabled by 'enable by default cgroups names matching'", cg->id, cg->name);
        return;
    }

    if (!(cg->enabled = matches_enabled_cgroup_paths(cg->id))) {
        netdata_log_debug(D_CGROUP, "cgroup '%s' (name '%s') disabled by 'enable by default cgroups matching'", cg->id, cg->name);
        return;
    }

    if (discovery_is_cgroup_duplicate(cg)) {
        cg->enabled = 0;
        cg->options |= CGROUP_OPTIONS_DISABLED_DUPLICATE;
        return;
    }

    if (!cg->chart_labels)
        cg->chart_labels = rrdlabels_create();

    if (!k8s_is_kubepod(cg)) {
        rrdlabels_add(cg->chart_labels, "cgroup_name", cg->name, RRDLABEL_SRC_AUTO);
        if (!rrdlabels_exist(cg->chart_labels, "image"))
            rrdlabels_add(cg->chart_labels, "image", "", RRDLABEL_SRC_AUTO);
    }

    worker_is_busy(WORKER_DISCOVERY_PROCESS_NETWORK);
    read_cgroup_network_interfaces(cg);
}

static inline void discovery_find_all_cgroups() {
    netdata_log_debug(D_CGROUP, "searching for cgroups");

    worker_is_busy(WORKER_DISCOVERY_INIT);
    discovery_orchestrator_begin_cycle();
    discovery_mark_as_unavailable_all_cgroups();

    worker_is_busy(WORKER_DISCOVERY_FIND);
    if (!cgroup_use_unified_cgroups) {
        discovery_find_all_cgroups_v1();
    } else {
        discovery_find_all_cgroups_v2();
    }

    for (struct cgroup *cg = discovered_cgroup_root; cg && service_running(SERVICE_COLLECTORS); cg = cg->discovered_next) {
        worker_is_busy(WORKER_DISCOVERY_PROCESS);
        discovery_process_cgroup(cg);
    }

    worker_is_busy(WORKER_DISCOVERY_UPDATE);
    discovery_update_filenames_all_cgroups();

    worker_is_busy(WORKER_DISCOVERY_LOCK);
    netdata_mutex_lock(&cgroup_root_mutex);

    worker_is_busy(WORKER_DISCOVERY_CLEANUP);
    discovery_cleanup_all_cgroups();

    worker_is_busy(WORKER_DISCOVERY_COPY);
    discovery_copy_discovered_cgroups_to_reader();

    __atomic_add_fetch(&cgroup_discovery_generation, 1, __ATOMIC_RELEASE);

    netdata_mutex_unlock(&cgroup_root_mutex);

    // Build and publish the immutable snapshot the netipc handlers serve. This
    // runs after the splice unlock and off cgroup_root_mutex: discovery is the
    // sole writer of the fields it reads and will not splice again this cycle.
    cgroup_snapshot_rebuild_and_publish();

    netdata_log_debug(D_CGROUP, "done searching for cgroups");
}

static void discovery_run_all_cgroups(bool opportunistic)
{
    if (opportunistic)
        __atomic_add_fetch(&cgroup_discovery_scans_opportunistic, 1, __ATOMIC_RELAXED);
    else
        __atomic_add_fetch(&cgroup_discovery_scans_natural, 1, __ATOMIC_RELAXED);

    discovery_find_all_cgroups();
}

void cgroup_discovery_update_charts(int update_every)
{
    if (unlikely(!cgroup_discovery_scans_st)) {
        cgroup_discovery_scans_st = rrdset_create_localhost(
            "netdata",
            "collector_cgroups_discovery_scans",
            NULL,
            "discovery",
            "netdata.collector.cgroups.discovery.scans",
            "cgroups Discovery Scans",
            "scans/s",
            PLUGIN_CGROUPS_NAME,
            PLUGIN_CGROUPS_MODULE_CGROUPS_NAME,
            NETDATA_CHART_PRIO_CGROUPS_CONTAINERS + 3002,
            update_every,
            RRDSET_TYPE_LINE);

        cgroup_discovery_scans_natural_rd = rrddim_add(
            cgroup_discovery_scans_st, "discovery_scans_natural", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
        cgroup_discovery_scans_opportunistic_rd = rrddim_add(
            cgroup_discovery_scans_st, "discovery_scans_opportunistic", NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
    }

    rrddim_set_by_pointer(
        cgroup_discovery_scans_st,
        cgroup_discovery_scans_natural_rd,
        (collected_number)__atomic_load_n(&cgroup_discovery_scans_natural, __ATOMIC_RELAXED));
    rrddim_set_by_pointer(
        cgroup_discovery_scans_st,
        cgroup_discovery_scans_opportunistic_rd,
        (collected_number)__atomic_load_n(&cgroup_discovery_scans_opportunistic, __ATOMIC_RELAXED));
    rrdset_done(cgroup_discovery_scans_st);
}

void cgroup_discovery_worker(void *ptr)
{
    UNUSED(ptr);
    uv_thread_set_name_np("P[cgroupsdisc]");

    worker_register("CGROUPSDISC");
    worker_register_job_name(WORKER_DISCOVERY_INIT,               "init");
    worker_register_job_name(WORKER_DISCOVERY_FIND,               "find");
    worker_register_job_name(WORKER_DISCOVERY_PROCESS,            "process");
    worker_register_job_name(WORKER_DISCOVERY_PROCESS_RENAME,     "rename");
    worker_register_job_name(WORKER_DISCOVERY_PROCESS_NETWORK,    "network");
    worker_register_job_name(WORKER_DISCOVERY_PROCESS_FIRST_TIME, "new");
    worker_register_job_name(WORKER_DISCOVERY_UPDATE,             "update");
    worker_register_job_name(WORKER_DISCOVERY_CLEANUP,            "cleanup");
    worker_register_job_name(WORKER_DISCOVERY_COPY,               "copy");
    worker_register_job_name(WORKER_DISCOVERY_LOCK,               "lock");

    entrypoint_parent_process_comm = simple_pattern_create(
            " runc:[* " // http://terenceli.github.io/%E6%8A%80%E6%9C%AF/2021/12/28/runc-internals-3)
            " exe ", // https://github.com/falcosecurity/falco/blob/9d41b0a151b83693929d3a9c84f7c5c85d070d3a/rules/falco_rules.yaml#L1961
            NULL,
            SIMPLE_PATTERN_EXACT, true);

    service_register(NULL, NULL, NULL);

    cgroup_snapshot_store_init();

    cgroup_netipc_init();
    cgroup_netipc_lookup_init();

    bool just_did_bounded_rescan = false;

    while (service_running(SERVICE_COLLECTORS)) {
        worker_is_idle();

        netdata_mutex_lock(&discovery_thread.mutex);
        bool pending_lookup_signal = __atomic_load_n(&discovery_signal_pending, __ATOMIC_ACQUIRE);
        if (just_did_bounded_rescan) {
            netdata_cond_wait(&discovery_thread.cond_var, &discovery_thread.mutex);
            just_did_bounded_rescan = false;
        }
        else if (!pending_lookup_signal)
            netdata_cond_wait(&discovery_thread.cond_var, &discovery_thread.mutex);
        netdata_mutex_unlock(&discovery_thread.mutex);

        if (unlikely(!service_running(SERVICE_COLLECTORS)))
            break;

        bool opportunistic = __atomic_exchange_n(&discovery_signal_pending, false, __ATOMIC_ACQUIRE);
        discovery_run_all_cgroups(opportunistic);

        if (unlikely(!service_running(SERVICE_COLLECTORS)))
            break;

        if (__atomic_load_n(&discovery_signal_pending, __ATOMIC_ACQUIRE)) {
            opportunistic = __atomic_exchange_n(&discovery_signal_pending, false, __ATOMIC_ACQUIRE);
            discovery_run_all_cgroups(opportunistic);
            just_did_bounded_rescan = true;
        }
    }

    // Stop both netipc servers first so their worker threads cannot read the
    // snapshot store; then tear the store down.
    cgroup_netipc_lookup_cleanup();
    cgroup_netipc_cleanup();
    cgroup_snapshot_store_shutdown();
    discovery_walkdir_open_errors_cleanup();

    // free all cgroups
    netdata_mutex_lock(&cgroup_root_mutex);
    while(cgroup_root) {
        struct cgroup *cg = cgroup_root;
        cgroup_root = cg->next;
        cgroup_free(cg);
    }
    netdata_mutex_unlock(&cgroup_root_mutex);

    collector_info("discovery thread stopped");
    worker_unregister();
    service_exits();
    __atomic_store_n(&discovery_thread.exited, 1, __ATOMIC_RELEASE);
}
