#include "sys_fs_cgroup.h"

#ifndef NETDATA_CGROUP_INTERNALS_H
#define NETDATA_CGROUP_INTERNALS_H 1

#ifdef NETDATA_INTERNAL_CHECKS
#define CGROUP_PROCFILE_FLAG PROCFILE_FLAG_DEFAULT
#else
#define CGROUP_PROCFILE_FLAG PROCFILE_FLAG_NO_ERROR_ON_FILE_IO
#endif

struct blkio {
    char *filename;
    bool staterr;

    int updated;

    unsigned long long Read;
    unsigned long long Write;
};

struct pids {
    char *filename;
    bool staterr;

    int updated;

    unsigned long long pids_current;
};

// https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt
struct memory {
    ARL_BASE *arl_base;
    ARL_ENTRY *arl_dirty;
    ARL_ENTRY *arl_swap;

    char *filename_usage_in_bytes;
    char *filename_detailed;
    char *filename_msw_usage_in_bytes;
    char *filename_failcnt;

    bool staterr_mem_current;
    bool staterr_mem_stat;
    bool staterr_failcnt;
    bool staterr_swap;

    int updated_usage_in_bytes;
    int updated_detailed;
    int updated_msw_usage_in_bytes;
    int updated_failcnt;

    int detailed_has_dirty;
    int detailed_has_swap;

    unsigned long long anon;
    unsigned long long kernel_stack;
    unsigned long long slab;
    unsigned long long sock;
    unsigned long long anon_thp;

    unsigned long long total_cache;
    unsigned long long total_rss;
    unsigned long long total_rss_huge;
    unsigned long long total_mapped_file;
    unsigned long long total_writeback;
    unsigned long long total_dirty;
    unsigned long long total_swap;
    unsigned long long total_pgpgin;
    unsigned long long total_pgpgout;
    unsigned long long total_pgfault;
    unsigned long long total_pgmajfault;

    unsigned long long total_inactive_file;

    // single file metrics
    unsigned long long usage_in_bytes;
    unsigned long long msw_usage_in_bytes;
    unsigned long long failcnt;
};

// https://www.kernel.org/doc/Documentation/cgroup-v1/cpuacct.txt
struct cpuacct_stat {
    char *filename;
    bool staterr;

    int updated;

    unsigned long long user;           // v1, v2(user_usec)
    unsigned long long system;         // v1, v2(system_usec)
};

// https://www.kernel.org/doc/Documentation/cgroup-v1/cpuacct.txt
struct cpuacct_usage {
    char *filename;
    bool disabled;
    int updated;

    unsigned int cpus;
    unsigned long long *cpu_percpu;
};

// represents cpuacct/cpu.stat, for v2 'cpuacct_stat' is used for 'user_usec', 'system_usec'
struct cpuacct_cpu_throttling {
    char *filename;
    bool staterr;

    int updated;

    unsigned long long nr_periods;
    unsigned long long nr_throttled;
    unsigned long long throttled_time;

    unsigned long long nr_throttled_perc;
};

// https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/resource_management_guide/sec-cpu#sect-cfs
// https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/managing_monitoring_and_updating_the_kernel/using-cgroups-v2-to-control-distribution-of-cpu-time-for-applications_managing-monitoring-and-updating-the-kernel#proc_controlling-distribution-of-cpu-time-for-applications-by-adjusting-cpu-weight_using-cgroups-v2-to-control-distribution-of-cpu-time-for-applications
struct cpuacct_cpu_shares {
    char *filename;
    bool staterr;

    int updated;

    unsigned long long shares;
};

struct cgroup_network_interface {
    const char *host_device;
    const char *container_device;
    struct cgroup_network_interface *next;
};

enum cgroups_container_orchestrator {
    CGROUPS_ORCHESTRATOR_UNSET,
    CGROUPS_ORCHESTRATOR_UNKNOWN,
    CGROUPS_ORCHESTRATOR_K8S
};


// *** WARNING *** The fields are not thread safe. Take care of safe usage.
struct cgroup {
    uint32_t options;

    int first_time_seen; // first time seen by the discoverer
    int processed;       // the discoverer is done processing a cgroup (resolved name, set 'enabled' option)

    char available;      // found in the filesystem
    char enabled;        // enabled in the config

    bool function_ready; // true after the first iteration of chart creation/update

    char pending_renames;

    char *id;
    uint32_t hash;

    char *intermediate_id; // TODO: remove it when the renaming script is fixed

    char *chart_id;
    uint32_t hash_chart_id;

    // 'cgroup_name' label value.
    // by default this is the *id (path), later changed to the resolved name (cgroup-name.sh) or systemd service name.
    char *name;

    RRDLABELS *chart_labels;

    int container_orchestrator;

    struct cpuacct_stat cpuacct_stat;
    struct cpuacct_usage cpuacct_usage;
    struct cpuacct_cpu_throttling cpuacct_cpu_throttling;
    struct cpuacct_cpu_shares cpuacct_cpu_shares;

    struct memory memory;

    struct blkio io_service_bytes;              // bytes
    struct blkio io_serviced;                   // operations

    struct blkio throttle_io_service_bytes;     // bytes
    struct blkio throttle_io_serviced;          // operations

    struct blkio io_merged;                     // operations
    struct blkio io_queued;                     // operations

    struct pids pids_current;

    struct cgroup_network_interface *interfaces;

    struct pressure cpu_pressure;
    struct pressure io_pressure;
    struct pressure memory_pressure;
    struct pressure irq_pressure;

    // Cpu
    RRDSET *st_cpu;
    RRDDIM *st_cpu_rd_user;
    RRDDIM *st_cpu_rd_system;

    RRDSET *st_cpu_limit;
    RRDSET *st_cpu_per_core;
    RRDSET *st_cpu_nr_throttled;
    RRDSET *st_cpu_throttled_time;
    RRDSET *st_cpu_shares;

    // Memory
    RRDSET *st_mem;
    RRDDIM *st_mem_rd_ram;
    RRDDIM *st_mem_rd_swap;

    RRDSET *st_mem_utilization;
    RRDSET *st_writeback;
    RRDSET *st_mem_activity;
    RRDSET *st_pgfaults;
    RRDSET *st_mem_usage;
    RRDSET *st_mem_usage_limit;
    RRDSET *st_mem_failcnt;

    // Blkio
    RRDSET *st_io;
    RRDDIM *st_io_rd_read;
    RRDDIM *st_io_rd_written;

    RRDSET *st_serviced_ops;

    RRDSET *st_throttle_io;
    RRDDIM *st_throttle_io_rd_read;
    RRDDIM *st_throttle_io_rd_written;

    RRDSET *st_throttle_serviced_ops;

    RRDSET *st_queued_ops;
    RRDSET *st_merged_ops;

    // Pids
    RRDSET *st_pids;
    RRDDIM *st_pids_rd_pids_current;

    // per cgroup chart variables
    char *filename_cpuset_cpus;
    unsigned long long cpuset_cpus;

    char *filename_cpu_cfs_period;
    unsigned long long cpu_cfs_period;

    char *filename_cpu_cfs_quota;
    unsigned long long cpu_cfs_quota;

    const RRDVAR_ACQUIRED *chart_var_cpu_limit;
    NETDATA_DOUBLE prev_cpu_usage;

    char *filename_memory_limit;
    unsigned long long memory_limit;
    const RRDVAR_ACQUIRED *chart_var_memory_limit;

    char *filename_memoryswap_limit;
    unsigned long long memoryswap_limit;
    const RRDVAR_ACQUIRED *chart_var_memoryswap_limit;

    const DICTIONARY_ITEM *cgroup_netdev_link;

    struct cgroup *next;
    struct cgroup *discovered_next;

};

struct discovery_thread {
    uv_thread_t thread;
    uv_mutex_t mutex;
    uv_cond_t cond_var;
    int exited;
};

extern struct discovery_thread discovery_thread;

extern char *cgroups_rename_script;
extern char cgroup_chart_id_prefix[];
extern char services_chart_id_prefix[];
extern uv_mutex_t cgroup_root_mutex;

void cgroup_discovery_worker(void *ptr);

extern bool is_inside_k8s;
extern long system_page_size;

extern int cgroup_use_unified_cgroups;
extern bool cgroup_unified_exist;

extern bool cgroup_enable_cpuacct;
extern bool cgroup_enable_memory;
extern bool cgroup_enable_blkio;
extern bool cgroup_enable_pressure;
extern bool cgroup_enable_cpuacct_cpu_shares;

extern int cgroup_check_for_new_every;
extern int cgroup_update_every;

extern char *cgroup_cpuacct_base;
extern char *cgroup_cpuset_base;
extern char *cgroup_blkio_base;
extern char *cgroup_memory_base;
extern char *cgroup_pids_base;
extern char *cgroup_unified_base;

extern int cgroup_root_count;
extern int cgroup_root_max;
extern int cgroup_max_depth;

extern SIMPLE_PATTERN *enabled_cgroup_paths;
extern SIMPLE_PATTERN *enabled_cgroup_names;
extern SIMPLE_PATTERN *search_cgroup_paths;
extern SIMPLE_PATTERN *enabled_cgroup_renames;
extern SIMPLE_PATTERN *systemd_services_cgroups;
extern SIMPLE_PATTERN *entrypoint_parent_process_comm;

extern char *cgroups_network_interface_script;

extern int cgroups_check;

extern uint32_t Read_hash;
extern uint32_t Write_hash;
extern uint32_t user_hash;
extern uint32_t system_hash;
extern uint32_t user_usec_hash;
extern uint32_t system_usec_hash;
extern uint32_t nr_periods_hash;
extern uint32_t nr_throttled_hash;
extern uint32_t throttled_time_hash;
extern uint32_t throttled_usec_hash;

extern struct cgroup *cgroup_root;

enum cgroups_type { CGROUPS_AUTODETECT_FAIL, CGROUPS_V1, CGROUPS_V2 };

enum cgroups_systemd_setting {
    SYSTEMD_CGROUP_ERR,
    SYSTEMD_CGROUP_LEGACY,
    SYSTEMD_CGROUP_HYBRID,
    SYSTEMD_CGROUP_UNIFIED
};

struct cgroups_systemd_config_setting {
    char *name;
    enum cgroups_systemd_setting setting;
};

extern struct cgroups_systemd_config_setting cgroups_systemd_options[];

static inline int matches_enabled_cgroup_paths(char *id) {
    return simple_pattern_matches(enabled_cgroup_paths, id);
}

static inline int matches_enabled_cgroup_names(char *name) {
    return simple_pattern_matches(enabled_cgroup_names, name);
}

static inline int matches_enabled_cgroup_renames(char *id) {
    return simple_pattern_matches(enabled_cgroup_renames, id);
}

static inline int matches_systemd_services_cgroups(char *id) {
    return simple_pattern_matches(systemd_services_cgroups, id);
}

static inline int matches_search_cgroup_paths(const char *dir) {
    return simple_pattern_matches(search_cgroup_paths, dir);
}

static inline int matches_entrypoint_parent_process_comm(const char *comm) {
    return simple_pattern_matches(entrypoint_parent_process_comm, comm);
}

static inline int is_cgroup_systemd_service(struct cgroup *cg) {
    return (int)(cg->options & CGROUP_OPTIONS_SYSTEM_SLICE_SERVICE);
}

static inline int k8s_is_kubepod(struct cgroup *cg) {
    return cg->container_orchestrator == CGROUPS_ORCHESTRATOR_K8S;
}

static inline char *cgroup_chart_type(char *buffer, struct cgroup *cg) {
    buffer[0] = '\0';

    if (cg->chart_id[0] == '\0' || (cg->chart_id[0] == '/' && cg->chart_id[1] == '\0'))
        strncpy(buffer, "cgroup_root", RRD_ID_LENGTH_MAX);
    else if (is_cgroup_systemd_service(cg))
        snprintfz(buffer, RRD_ID_LENGTH_MAX, "%s%s", services_chart_id_prefix, cg->chart_id);
    else
        snprintfz(buffer, RRD_ID_LENGTH_MAX, "%s%s", cgroup_chart_id_prefix, cg->chart_id);

    return buffer;
}

#define RRDFUNCTIONS_CGTOP_HELP "View running containers"
#define RRDFUNCTIONS_SYSTEMD_SERVICES_HELP "View systemd services"

int cgroup_function_cgroup_top(BUFFER *wb, const char *function);
int cgroup_function_systemd_top(BUFFER *wb, const char *function);

void cgroup_netdev_link_init(void);
const DICTIONARY_ITEM *cgroup_netdev_get(struct cgroup *cg);
void cgroup_netdev_delete(struct cgroup *cg);

void update_cpu_utilization_chart(struct cgroup *cg);
void update_cpu_utilization_limit_chart(struct cgroup *cg, NETDATA_DOUBLE cpu_limit);
void update_cpu_throttled_chart(struct cgroup *cg);
void update_cpu_throttled_duration_chart(struct cgroup *cg);
void update_cpu_shares_chart(struct cgroup *cg);
void update_cpu_per_core_usage_chart(struct cgroup *cg);

void update_mem_usage_limit_chart(struct cgroup *cg, unsigned long long memory_limit);
void update_mem_utilization_chart(struct cgroup *cg, unsigned long long memory_limit);
void update_mem_usage_detailed_chart(struct cgroup *cg);
void update_mem_writeback_chart(struct cgroup *cg);
void update_mem_activity_chart(struct cgroup *cg);
void update_mem_pgfaults_chart(struct cgroup *cg);
void update_mem_failcnt_chart(struct cgroup *cg);
void update_mem_usage_chart(struct cgroup *cg);

void update_io_serviced_bytes_chart(struct cgroup *cg);
void update_io_serviced_ops_chart(struct cgroup *cg);
void update_throttle_io_serviced_bytes_chart(struct cgroup *cg);
void update_throttle_io_serviced_ops_chart(struct cgroup *cg);
void update_io_queued_ops_chart(struct cgroup *cg);
void update_io_merged_ops_chart(struct cgroup *cg);

void update_pids_current_chart(struct cgroup *cg);

void update_cpu_some_pressure_chart(struct cgroup *cg);
void update_cpu_some_pressure_stall_time_chart(struct cgroup *cg);
void update_cpu_full_pressure_chart(struct cgroup *cg);
void update_cpu_full_pressure_stall_time_chart(struct cgroup *cg);

void update_mem_some_pressure_chart(struct cgroup *cg);
void update_mem_some_pressure_stall_time_chart(struct cgroup *cg);
void update_mem_full_pressure_chart(struct cgroup *cg);
void update_mem_full_pressure_stall_time_chart(struct cgroup *cg);

void update_irq_some_pressure_chart(struct cgroup *cg);
void update_irq_some_pressure_stall_time_chart(struct cgroup *cg);
void update_irq_full_pressure_chart(struct cgroup *cg);
void update_irq_full_pressure_stall_time_chart(struct cgroup *cg);

void update_io_some_pressure_chart(struct cgroup *cg);
void update_io_some_pressure_stall_time_chart(struct cgroup *cg);
void update_io_full_pressure_chart(struct cgroup *cg);
void update_io_full_pressure_stall_time_chart(struct cgroup *cg);

#endif // NETDATA_CGROUP_INTERNALS_H