/*
 * Copyright (C) Tildeslash Ltd. All rights reserved.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License version 3.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations
 * including the two.
 *
 * You must obey the GNU Affero General Public License in all respects
 * for all of the code used other than OpenSSL.
 */


#include "config.h"

#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif

#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef HAVE_STDDEF_H
#include <stddef.h>
#endif

#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif

#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef HAVE_STRING_H
#include <string.h>
#endif

#ifdef HAVE_ASM_PARAM_H
#include <asm/param.h>
#endif

#ifdef HAVE_GLOB_H
#include <glob.h>
#endif

#ifdef HAVE_SYS_SYSINFO_H
#include <sys/sysinfo.h>
#endif

#ifdef HAVE_DIRENT_H
#include <dirent.h>
#endif

#ifdef HAVE_SYS_RESOURCE_H
#include <sys/resource.h>
#endif

#include "monit.h"
#include "ProcessTree.h"
#include "process_sysdep.h"

// libmonit
#include "system/Time.h"

/**
 *  System dependent resource data collection code for Linux.
 *
 *  @file
 */


/* ------------------------------------------------------------- Definitions */


static struct {
        int hasIOStatistics; // True if /proc/<PID>/io is present
} _statistics = {};


typedef struct Proc_T {
        StringBuffer_T name;
        struct {
                int                 pid;
                int                 ppid;
                int                 uid;
                int                 euid;
                int                 gid;
                char                item_state;
                long                item_cutime;
                long                item_cstime;
                long                item_rss;
                int                 item_threads;
                unsigned long       item_utime;
                unsigned long       item_stime;
                unsigned long long  item_starttime;
                struct {
                        unsigned long long    bytes;
                        unsigned long long    bytesPhysical;
                        unsigned long long    operations;
                } read;
                struct {
                        unsigned long long    bytes;
                        unsigned long long    bytesPhysical;
                        unsigned long long    operations;
                } write;
                struct {
                        long long     open;
                        struct {
                                long long soft;
                                long long hard;
                        } limit;
                } filedescriptors;
                char                secattr[STRLEN];
        } data;
} *Proc_T;


/* --------------------------------------- Static constructor and destructor */


static void __attribute__ ((constructor)) _constructor(void) {
        struct stat sb;
        _statistics.hasIOStatistics = stat("/proc/self/io", &sb) == 0 ? true : false;
}


/* ----------------------------------------------------------------- Private */


#define NSEC_PER_SEC    1000000000L

static unsigned long long old_cpu_user       = 0;
static unsigned long long old_cpu_nice       = 0;
static unsigned long long old_cpu_syst       = 0;
static unsigned long long old_cpu_iowait     = 0;
static unsigned long long old_cpu_hardirq    = 0;
static unsigned long long old_cpu_softirq    = 0;
static unsigned long long old_cpu_steal      = 0;
static unsigned long long old_cpu_guest      = 0;
static unsigned long long old_cpu_guest_nice = 0;
static unsigned long long old_cpu_total      = 0;

static long page_size = 0;

static double hz = 0.;

/**
 * Get system start time
 * @return seconds since unix epoch
 */
static time_t _getStartTime(void) {
        struct sysinfo info;
        if (sysinfo(&info) < 0) {
                Log_error("system statistic error -- cannot get system uptime: %s\n", STRERROR);
                return 0;
        }
        return Time_now() - info.uptime;
}


// parse /proc/PID/stat
static bool _parseProcPidStat(Proc_T proc) {
        char buf[8192];
        char *tmp = NULL;
        if (! file_readProc(buf, sizeof(buf), "stat", proc->data.pid, NULL)) {
                DEBUG("system statistic error -- cannot read /proc/%d/stat\n", proc->data.pid);
                return false;
        }
        // Skip the process name (can have multiple words)
        if (! (tmp = strrchr(buf, ')'))) {
                DEBUG("system statistic error -- file /proc/%d/stat parse error\n", proc->data.pid);
                return false;
        }
        if (sscanf(tmp + 2,
                   "%c %d %*d %*d %*d %*d %*u %*u %*u %*u %*u %lu %lu %ld %ld %*d %*d %d %*u %llu %*u %ld %*u %*u %*u %*u %*u %*u %*u %*u %*u %*u %*u %*u %*u %*d %*d\n",
                   &(proc->data.item_state),
                   &(proc->data.ppid),
                   &(proc->data.item_utime),
                   &(proc->data.item_stime),
                   &(proc->data.item_cutime),
                   &(proc->data.item_cstime),
                   &(proc->data.item_threads),
                   &(proc->data.item_starttime),
                   &(proc->data.item_rss)) != 9) {
                DEBUG("system statistic error -- file /proc/%d/stat parse error\n", proc->data.pid);
                return false;
        }
        return true;
}


// parse /proc/PID/status
static bool _parseProcPidStatus(Proc_T proc) {
        char buf[4096];
        char *tmp = NULL;
        if (! file_readProc(buf, sizeof(buf), "status", proc->data.pid, NULL)) {
                DEBUG("system statistic error -- cannot read /proc/%d/status\n", proc->data.pid);
                return false;
        }
        if (! (tmp = strstr(buf, "Uid:"))) {
                DEBUG("system statistic error -- cannot find process uid\n");
                return false;
        }
        if (sscanf(tmp + 4, "\t%d\t%d", &(proc->data.uid), &(proc->data.euid)) != 2) {
                DEBUG("system statistic error -- cannot read process uid\n");
                return false;
        }
        if (! (tmp = strstr(buf, "Gid:"))) {
                DEBUG("system statistic error -- cannot find process gid\n");
                return false;
        }
        if (sscanf(tmp + 4, "\t%d", &(proc->data.gid)) != 1) {
                DEBUG("system statistic error -- cannot read process gid\n");
                return false;
        }
        return true;
}


// parse /proc/PID/io
static bool _parseProcPidIO(Proc_T proc) {
        char buf[4096];
        char *tmp = NULL;
        if (_statistics.hasIOStatistics) {
                if (file_readProc(buf, sizeof(buf), "io", proc->data.pid, NULL)) {
                        // read bytes (total)
                        if (! (tmp = strstr(buf, "rchar:"))) {
                                DEBUG("system statistic error -- cannot find process read bytes\n");
                                return false;
                        }
                        if (sscanf(tmp + 6, "\t%llu", &(proc->data.read.bytes)) != 1) {
                                DEBUG("system statistic error -- cannot get process read bytes\n");
                                return false;
                        }
                        // write bytes (total)
                        if (! (tmp = strstr(tmp, "wchar:"))) {
                                DEBUG("system statistic error -- cannot find process write bytes\n");
                                return false;
                        }
                        if (sscanf(tmp + 6, "\t%llu", &(proc->data.write.bytes)) != 1) {
                                DEBUG("system statistic error -- cannot get process write bytes\n");
                                return false;
                        }
                        // read operations
                        if (! (tmp = strstr(tmp, "syscr:"))) {
                                DEBUG("system statistic error -- cannot find process read system calls count\n");
                                return false;
                        }
                        if (sscanf(tmp + 6, "\t%llu", &(proc->data.read.operations)) != 1) {
                                DEBUG("system statistic error -- cannot get process read system calls count\n");
                                return false;
                        }
                        // write operations
                        if (! (tmp = strstr(tmp, "syscw:"))) {
                                DEBUG("system statistic error -- cannot find process write system calls count\n");
                                return false;
                        }
                        if (sscanf(tmp + 6, "\t%llu", &(proc->data.write.operations)) != 1) {
                                DEBUG("system statistic error -- cannot get process write system calls count\n");
                                return false;
                        }
                        // read bytes (physical I/O)
                        if (! (tmp = strstr(tmp, "read_bytes:"))) {
                                DEBUG("system statistic error -- cannot find process physical read bytes\n");
                                return false;
                        }
                        if (sscanf(tmp + 11, "\t%llu", &(proc->data.read.bytesPhysical)) != 1) {
                                DEBUG("system statistic error -- cannot get process physical read bytes\n");
                                return false;
                        }
                        // write bytes (physical I/O)
                        if (! (tmp = strstr(tmp, "write_bytes:"))) {
                                DEBUG("system statistic error -- cannot find process physical write bytes\n");
                                return false;
                        }
                        if (sscanf(tmp + 12, "\t%llu", &(proc->data.write.bytesPhysical)) != 1) {
                                DEBUG("system statistic error -- cannot get process physical write bytes\n");
                                return false;
                        }
                } else {
                        // file_readProc() already printed a DEBUG() message
                        // return false;
                        // sometimes no io data is available, this is not a problem.
                        return true;
                }
        }
        return true;
}


// parse /proc/PID/cmdline
static bool _parseProcPidCmdline(Proc_T proc, ProcessEngine_Flags pflags) {
        if (pflags & ProcessEngine_CollectCommandLine) {
                char filename[STRLEN];
                // Try to collect the command-line from the procfs cmdline (user-space processes)
                snprintf(filename, sizeof(filename), "/proc/%d/cmdline", proc->data.pid);
                FILE *f = fopen(filename, "r");
                if (! f) {
                        DEBUG("system statistic error -- cannot open /proc/%d/cmdline: %s\n", proc->data.pid, STRERROR);
                        return false;
                }
                size_t n;
                char buf[STRLEN] = {};
                while ((n = fread(buf, 1, sizeof(buf) - 1, f)) > 0) {
                        // The cmdline file contains argv elements/strings separated by '\0' => join the string
                        for (size_t i = 0; i < n; i++) {
                                if (buf[i] == 0)
                                        StringBuffer_append(proc->name, " ");
                                else
                                        StringBuffer_append(proc->name, "%c", buf[i]);
                        }
                }
                fclose(f);
                StringBuffer_trim(proc->name);
                // Fallback to procfs stat process name if cmdline was empty (even kernel-space processes have information here)
                if (! StringBuffer_length(proc->name)) {
                        char buffer[8192];
                        char *tmp = NULL;
                        char *procname = NULL;
                        if (! file_readProc(buffer, sizeof(buffer), "stat", proc->data.pid, NULL)) {
                                DEBUG("system statistic error -- cannot read /proc/%d/stat\n", proc->data.pid);
                                return false;
                        }
                        if (! (tmp = strrchr(buffer, ')'))) {
                                DEBUG("system statistic error -- file /proc/%d/stat parse error\n", proc->data.pid);
                                return false;
                        }
                        *tmp = 0;
                        if (! (procname = strchr(buffer, '('))) {
                                DEBUG("system statistic error -- file /proc/%d/stat parse error\n", proc->data.pid);
                                return false;
                        }
                        StringBuffer_append(proc->name, "%s", procname + 1);
                }
        }
        return true;
}


// parse /proc/PID/attr/current
static bool _parseProcPidAttrCurrent(Proc_T proc) {
        if (file_readProc(proc->data.secattr, sizeof(proc->data.secattr), "attr/current", proc->data.pid, NULL)) {
                Str_trim(proc->data.secattr);
                return true;
        }
        return false;
}

// count entries in /proc/PID/fd
static bool _parseProcFdCount(Proc_T proc) {
        char path[PATH_MAX] = {};
        unsigned long long file_count = 0;

        snprintf(path, sizeof(path), "/proc/%d/fd", proc->data.pid);
        DIR *dirp = opendir(path);
        if (! dirp) {
                if (Run.debug >= 2)
                        DEBUG("system statistic error -- opendir %s: %s\n", path, STRERROR);
                return false;
        }
        errno = 0;
        while (readdir(dirp) != NULL) {
                // count everything
                file_count++;
        }
        // do not closedir() until readdir errno has been evaluated
        if (errno) {
                DEBUG("system statistic error -- cannot iterate %s: %s\n", path, STRERROR);
                closedir(dirp);
                return false;
        }
        closedir(dirp);
        // assert at least '.' and '..' have been found
        if (file_count < 2) {
                DEBUG("system statistic error -- cannot find basic entries in %s\n", path);
                return false;
        }
        // subtract entries '.' and '..'
        proc->data.filedescriptors.open = file_count - 2;

        // get process's limits
        snprintf(path, sizeof(path), "/proc/%d/limits", proc->data.pid);
        FILE *f = fopen(path, "r");
        if (f) {
                int softLimit;
                int hardLimit;
                char line[STRLEN];
                while (fgets(line, sizeof(line), f)) {
                        if (sscanf(line, "Max open files %d %d", &softLimit, &hardLimit) == 2) {
                                proc->data.filedescriptors.limit.soft = softLimit;
                                proc->data.filedescriptors.limit.hard = hardLimit;
                                break;
                        }
                }
                fclose(f);
        } else {
                DEBUG("system statistic error -- cannot open %s\n", path);
                return false;
        }

        return true;
}

static double _usagePercent(unsigned long long previous, unsigned long long current, double total) {
        if (current < previous) {
                // The counter jumped back (observed for cpu wait metric on Linux 4.15) or wrapped
                return 0.;
        }
        return (double)(current - previous) / total * 100.;
}


/* ------------------------------------------------------------------ Public */


bool init_systeminfo_sysdep(void) {
        if ((hz = sysconf(_SC_CLK_TCK)) <= 0.) {
                DEBUG("system statistic error -- cannot get hz: %s\n", STRERROR);
                return false;
        }

        if ((page_size = sysconf(_SC_PAGESIZE)) <= 0) {
                DEBUG("system statistic error -- cannot get page size: %s\n", STRERROR);
                return false;
        }

        if ((System_Info.cpu.count = sysconf(_SC_NPROCESSORS_ONLN)) < 0) {
                DEBUG("system statistic error -- cannot get cpu count: %s\n", STRERROR);
                return false;
        } else if (System_Info.cpu.count == 0) {
                DEBUG("system reports cpu count 0, setting dummy cpu count 1\n");
                System_Info.cpu.count = 1;
        }

        FILE *f = fopen("/proc/meminfo", "r");
        if (f) {
                char line[STRLEN];
                System_Info.memory.size = 0L;
                while (fgets(line, sizeof(line), f)) {
                        if (sscanf(line, "MemTotal: %llu", &System_Info.memory.size) == 1) {
                                System_Info.memory.size *= 1024;
                                break;
                        }
                }
                fclose(f);
                if (! System_Info.memory.size)
                        DEBUG("system statistic error -- cannot get real memory amount\n");
        } else {
                DEBUG("system statistic error -- cannot open /proc/meminfo\n");
        }

        System_Info.booted = (long long)_getStartTime();

        return true;
}


/**
 * Read all processes of the proc files system to initialize the process tree
 * @param reference reference of ProcessTree
 * @param pflags Process engine flags
 * @return treesize > 0 if succeeded otherwise 0
 */
int init_processtree_sysdep(ProcessTree_T **reference, ProcessEngine_Flags pflags) {
        assert(reference);

        // Find all processes in the /proc directory
        glob_t globbuf;
        int rv = glob("/proc/[0-9]*", 0, NULL, &globbuf);
        if (rv) {
                Log_error("system statistic error -- glob failed: %d (%s)\n", rv, STRERROR);
                return 0;
        }

        ProcessTree_T *pt = CALLOC(sizeof(ProcessTree_T), globbuf.gl_pathc);

        int count = 0;
        struct Proc_T proc = {
                .name = StringBuffer_create(64)
        };
        time_t starttime = _getStartTime();
        for (size_t i = 0; i < globbuf.gl_pathc; i++) {
                proc.data.pid = atoi(globbuf.gl_pathv[i] + 6); // skip "/proc/"
                if (_parseProcPidStat(&proc) && _parseProcPidStatus(&proc) && _parseProcPidIO(&proc) && _parseProcPidCmdline(&proc, pflags)) {
                        // Non-mandatory statistics (may not exist)
                        _parseProcFdCount(&proc);
                        _parseProcPidAttrCurrent(&proc);
                        // Set the data in ptree only if all process related reads succeeded (prevent partial data in the case that continue was called during data collecting)
                        pt[count].pid = proc.data.pid;
                        pt[count].ppid = proc.data.ppid;
                        pt[count].cred.uid = proc.data.uid;
                        pt[count].cred.euid = proc.data.euid;
                        pt[count].cred.gid = proc.data.gid;
                        pt[count].threads.self = proc.data.item_threads;
                        pt[count].uptime = starttime > 0 ? (System_Info.time / 10. - (starttime + (time_t)(proc.data.item_starttime / hz))) : 0;
                        pt[count].cpu.time = (double)(proc.data.item_utime + proc.data.item_stime) / hz * 10.; // jiffies -> seconds = 1/hz
                        pt[count].memory.usage = (unsigned long long)proc.data.item_rss * (unsigned long long)page_size;
                        pt[count].read.bytes = proc.data.read.bytes;
                        pt[count].read.bytesPhysical = proc.data.read.bytesPhysical;
                        pt[count].read.operations = proc.data.read.operations;
                        pt[count].write.bytes = proc.data.write.bytes;
                        pt[count].write.bytesPhysical = proc.data.write.bytesPhysical;
                        pt[count].write.operations = proc.data.write.operations;
                        pt[count].read.time = pt[count].write.time = Time_milli();
                        pt[count].zombie = proc.data.item_state == 'Z' ? true : false;
                        pt[count].cmdline = Str_dup(StringBuffer_toString(proc.name));
                        pt[count].secattr = Str_dup(proc.data.secattr);
                        pt[count].filedescriptors.usage = proc.data.filedescriptors.open;
                        pt[count].filedescriptors.limit.soft = proc.data.filedescriptors.limit.soft;
                        pt[count].filedescriptors.limit.hard = proc.data.filedescriptors.limit.hard;
                        count++;
                        // Clear
                        memset(&proc.data, 0, sizeof(proc.data));
                        StringBuffer_clear(proc.name);
                }
        }
        StringBuffer_free(&(proc.name));

        *reference = pt;
        globfree(&globbuf);

        return count;
}


/**
 * This routine returns 'nelem' double precision floats containing
 * the load averages in 'loadv'; at most 3 values will be returned.
 * @param loadv destination of the load averages
 * @param nelem number of averages
 * @return: 0 if successful, -1 if failed (and all load averages are 0).
 */
int getloadavg_sysdep(double *loadv, int nelem) {
#ifdef HAVE_GETLOADAVG
        return getloadavg(loadv, nelem);
#else
        char buf[STRLEN];
        double load[3];
        if (! file_readProc(buf, sizeof(buf), "loadavg", -1, NULL))
                return -1;
        if (sscanf(buf, "%lf %lf %lf", &load[0], &load[1], &load[2]) != 3) {
                DEBUG("system statistic error -- cannot get load average\n");
                return -1;
        }
        for (int i = 0; i < nelem; i++)
                loadv[i] = load[i];
        return 0;
#endif
}


/**
 * This routine returns real memory in use.
 * @return: true if successful, false if failed
 */
bool used_system_memory_sysdep(SystemInfo_T *si) {
        char          *ptr;
        char           buf[2048];
        unsigned long long mem_total = 0ULL;
        unsigned long long mem_available = 0ULL;
        unsigned long long mem_free = 0ULL;
        unsigned long long buffers = 0ULL;
        unsigned long long cached = 0ULL;
        unsigned long long slabreclaimable = 0ULL;
        unsigned long long swap_total = 0ULL;
        unsigned long long swap_free = 0ULL;
        unsigned long long zfsarcsize = 0ULL;

        if (! file_readProc(buf, sizeof(buf), "meminfo", -1, NULL)) {
                Log_error("system statistic error -- cannot get system memory info\n");
                goto error;
        }

        // Update memory total (physical memory can be added to the online system on some machines, also LXC/KVM containers MemTotal is dynamic and changes frequently
        if ((ptr = strstr(buf, "MemTotal:")) && sscanf(ptr + 9, "%llu", &mem_total) == 1) {
                System_Info.memory.size = mem_total * 1024;
        }

        // Check if the "MemAvailable" value is available on this system. If it is, we will use it. Otherwise we will attempt to calculate the amount of available memory ourself
        if ((ptr = strstr(buf, "MemAvailable:")) && sscanf(ptr + 13, "%llu", &mem_available) == 1) {
                si->memory.usage.bytes = System_Info.memory.size - mem_available * 1024;
        } else {
                DEBUG("'MemAvailable' value not available on this system. Attempting to calculate available memory manually...\n");
                if (! (ptr = strstr(buf, "MemFree:")) || sscanf(ptr + 8, "%llu", &mem_free) != 1) {
                        Log_error("system statistic error -- cannot get real memory free amount\n");
                        goto error;
                }
                if (! (ptr = strstr(buf, "Buffers:")) || sscanf(ptr + 8, "%llu", &buffers) != 1)
                        DEBUG("system statistic error -- cannot get real memory buffers amount\n");
                if (! (ptr = strstr(buf, "Cached:")) || sscanf(ptr + 7, "%llu", &cached) != 1)
                        DEBUG("system statistic error -- cannot get real memory cache amount\n");
                if (! (ptr = strstr(buf, "SReclaimable:")) || sscanf(ptr + 13, "%llu", &slabreclaimable) != 1)
                        DEBUG("system statistic error -- cannot get slab reclaimable memory amount\n");
                FILE *f = fopen("/proc/spl/kstat/zfs/arcstats", "r");
                if (f) {
                        char line[STRLEN];
                        while (fgets(line, sizeof(line), f)) {
                                if (sscanf(line, "size %*d %llu", &zfsarcsize) == 1) {
                                        break;
                                }
                        }
                        fclose(f);
                }
                si->memory.usage.bytes = System_Info.memory.size - zfsarcsize - (unsigned long long)(mem_free + buffers + cached + slabreclaimable) * 1024;
        }

        // Swap
        if (! (ptr = strstr(buf, "SwapTotal:")) || sscanf(ptr + 10, "%llu", &swap_total) != 1) {
                Log_error("system statistic error -- cannot get swap total amount\n");
                goto error;
        }
        if (! (ptr = strstr(buf, "SwapFree:")) || sscanf(ptr + 9, "%llu", &swap_free) != 1) {
                Log_error("system statistic error -- cannot get swap free amount\n");
                goto error;
        }
        si->swap.size = swap_total * 1024;
        si->swap.usage.bytes = (swap_total - swap_free) * 1024;

        return true;

error:
        si->memory.usage.bytes = 0ULL;
        si->swap.size = 0ULL;
        return false;
}


/**
 * This routine returns system/user CPU time in use.
 * @return: true if successful, false if failed (or not available)
 */
bool used_system_cpu_sysdep(SystemInfo_T *si) {
        int rv;
        unsigned long long cpu_total;      // Total CPU time
        unsigned long long cpu_user;       // Time spent in user mode
        unsigned long long cpu_nice;       // Time spent in user mode with low priority (nice)
        unsigned long long cpu_syst;       // Time spent in system mode
        unsigned long long cpu_idle;       // Time idle
        unsigned long long cpu_iowait;     // Time waiting for I/O to complete. This value is not reliable
        unsigned long long cpu_hardirq;    // Time servicing hardware interrupts
        unsigned long long cpu_softirq;    // Time servicing software interrupts
        unsigned long long cpu_steal;      // Stolen time, which is the time spent in other operating systems when running in a virtualized environment
        unsigned long long cpu_guest;      // Time spent running a virtual CPU for guest operating systems under the control of the Linux kernel
        unsigned long long cpu_guest_nice; // Time spent running a niced guest (virtual CPU for guest operating systems under the control of the Linux kernel)
        char buf[8192];

        if (! file_readProc(buf, sizeof(buf), "stat", -1, NULL)) {
                Log_error("system statistic error -- cannot read /proc/stat\n");
                goto error;
        }

        rv = sscanf(buf, "cpu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu",
                    &cpu_user,
                    &cpu_nice,
                    &cpu_syst,
                    &cpu_idle,
                    &cpu_iowait,
                    &cpu_hardirq,
                    &cpu_softirq,
                    &cpu_steal,
                    &cpu_guest,
                    &cpu_guest_nice);
        switch (rv) {
                case 4:
                        // linux < 2.5.41
                        cpu_iowait = 0;
                        cpu_hardirq = 0;
                        cpu_softirq = 0;
                        cpu_steal = 0;
                        cpu_guest = 0;
                        cpu_guest_nice = 0;
                        break;
                case 5:
                        // linux >= 2.5.41
                        cpu_hardirq = 0;
                        cpu_softirq = 0;
                        cpu_steal = 0;
                        cpu_guest = 0;
                        cpu_guest_nice = 0;
                        break;
                case 7:
                        // linux >= 2.6.0-test4
                        cpu_steal = 0;
                        cpu_guest = 0;
                        cpu_guest_nice = 0;
                        break;
                case 8:
                        // linux 2.6.11
                        cpu_guest = 0;
                        cpu_guest_nice = 0;
                        break;
                case 9:
                        // linux >= 2.6.24
                        cpu_guest_nice = 0;
                        break;
                case 10:
                        // linux >= 2.6.33
                        break;
                default:
                        Log_error("system statistic error -- cannot read cpu usage\n");
                        goto error;
        }

        cpu_total = cpu_user + cpu_nice + cpu_syst + cpu_idle + cpu_iowait + cpu_hardirq + cpu_softirq + cpu_steal; // Note: cpu_guest and cpu_guest_nice are included in user and nice already

        if (old_cpu_total == 0) {
                si->cpu.usage.user       = -1.;
                si->cpu.usage.nice       = -1.;
                si->cpu.usage.system     = -1.;
                si->cpu.usage.iowait     = -1.;
                si->cpu.usage.hardirq    = -1.;
                si->cpu.usage.softirq    = -1.;
                si->cpu.usage.steal      = -1.;
                si->cpu.usage.guest      = -1.;
                si->cpu.usage.guest_nice = -1.;
        } else {
                double delta = cpu_total - old_cpu_total;
                si->cpu.usage.user       = _usagePercent(old_cpu_user - old_cpu_guest, cpu_user - cpu_guest, delta); // the guest (if available) is sub-statistics of user
                si->cpu.usage.nice       = _usagePercent(old_cpu_nice - old_cpu_guest_nice, cpu_nice - cpu_guest_nice, delta); // the guest_nice (if available) is sub-statistics of nice
                si->cpu.usage.system     = _usagePercent(old_cpu_syst, cpu_syst, delta);
                si->cpu.usage.iowait     = _usagePercent(old_cpu_iowait, cpu_iowait, delta);
                si->cpu.usage.hardirq    = _usagePercent(old_cpu_hardirq, cpu_hardirq, delta);
                si->cpu.usage.softirq    = _usagePercent(old_cpu_softirq, cpu_softirq, delta);
                si->cpu.usage.steal      = _usagePercent(old_cpu_steal, cpu_steal, delta);
                si->cpu.usage.guest      = _usagePercent(old_cpu_guest, cpu_guest, delta);
                si->cpu.usage.guest_nice = _usagePercent(old_cpu_guest_nice, cpu_guest_nice, delta);
        }

        old_cpu_user       = cpu_user;
        old_cpu_nice       = cpu_nice;
        old_cpu_syst       = cpu_syst;
        old_cpu_iowait     = cpu_iowait;
        old_cpu_hardirq    = cpu_hardirq;
        old_cpu_softirq    = cpu_softirq;
        old_cpu_steal      = cpu_steal;
        old_cpu_guest      = cpu_guest;
        old_cpu_guest_nice = cpu_guest_nice;
        old_cpu_total      = cpu_total;
        return true;

error:
        si->cpu.usage.user       = 0.;
        si->cpu.usage.nice       = 0.;
        si->cpu.usage.system     = 0.;
        si->cpu.usage.iowait     = 0.;
        si->cpu.usage.hardirq    = 0.;
        si->cpu.usage.softirq    = 0.;
        si->cpu.usage.steal      = 0.;
        si->cpu.usage.guest      = 0.;
        si->cpu.usage.guest_nice = 0.;
        return false;
}


/**
 * This routine returns filedescriptors statistics
 * @return: true if successful, false if failed (or not available)
 */
bool used_system_filedescriptors_sysdep(SystemInfo_T *si) {
        bool rv = false;
        FILE *f = fopen("/proc/sys/fs/file-nr", "r");
        if (f) {
                char line[STRLEN];
                if (fgets(line, sizeof(line), f)) {
                        if (sscanf(line, "%lld %lld %lld\n", &(si->filedescriptors.allocated), &(si->filedescriptors.unused), &(si->filedescriptors.maximum)) == 3) {
                                rv = true;
                        }
                }
                fclose(f);
        } else {
                DEBUG("system statistic error -- cannot open /proc/sys/fs/file-nr\n");
        }
        return rv;
}


bool available_statistics(SystemInfo_T *si) {
        int rv;
        unsigned long long cpu_user;       // Time spent in user mode
        unsigned long long cpu_nice;       // Time spent in user mode with low priority (nice)
        unsigned long long cpu_syst;       // Time spent in system mode
        unsigned long long cpu_idle;       // Time idle
        unsigned long long cpu_iowait;     // Time waiting for I/O to complete. This value is not reliable
        unsigned long long cpu_hardirq;    // Time servicing hardware interrupts
        unsigned long long cpu_softirq;    // Time servicing software interrupts
        unsigned long long cpu_steal;      // Stolen time, which is the time spent in other operating systems when running in a virtualized environment
        unsigned long long cpu_guest;      // Time spent running a virtual CPU for guest operating systems under the control of the Linux kernel
        unsigned long long cpu_guest_nice; // Time spent running a niced guest (virtual CPU for guest operating systems under the control of the Linux kernel)
        char buf[8192];

        if (! file_readProc(buf, sizeof(buf), "stat", -1, NULL)) {
                Log_error("system statistic error -- cannot read /proc/stat\n");
                return false;
        }

        rv = sscanf(buf, "cpu %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu",
                    &cpu_user,
                    &cpu_nice,
                    &cpu_syst,
                    &cpu_idle,
                    &cpu_iowait,
                    &cpu_hardirq,
                    &cpu_softirq,
                    &cpu_steal,
                    &cpu_guest,
                    &cpu_guest_nice);
        switch (rv) {
                case 4:
                        // linux < 2.5.41
                        si->statisticsAvailable |= Statistics_CpuUser | Statistics_CpuNice | Statistics_CpuSystem;
                        break;
                case 5:
                        // linux >= 2.5.41
                        si->statisticsAvailable |= Statistics_CpuUser | Statistics_CpuNice | Statistics_CpuSystem | Statistics_CpuIOWait;
                        break;
                case 7:
                        // linux >= 2.6.0-test4
                        si->statisticsAvailable |= Statistics_CpuUser | Statistics_CpuNice | Statistics_CpuSystem | Statistics_CpuIOWait | Statistics_CpuHardIRQ | Statistics_CpuSoftIRQ;
                        break;
                case 8:
                        // linux 2.6.11
                        si->statisticsAvailable |= Statistics_CpuUser | Statistics_CpuNice | Statistics_CpuSystem | Statistics_CpuIOWait | Statistics_CpuHardIRQ | Statistics_CpuSoftIRQ | Statistics_CpuSteal;
                        break;
                case 9:
                        // linux >= 2.6.24
                        si->statisticsAvailable |= Statistics_CpuUser | Statistics_CpuNice | Statistics_CpuSystem | Statistics_CpuIOWait | Statistics_CpuHardIRQ | Statistics_CpuSoftIRQ | Statistics_CpuSteal | Statistics_CpuGuest;
                        break;
                case 10:
                        // linux >= 2.6.33
                        si->statisticsAvailable |= Statistics_CpuUser | Statistics_CpuNice | Statistics_CpuSystem | Statistics_CpuIOWait | Statistics_CpuHardIRQ | Statistics_CpuSoftIRQ | Statistics_CpuSteal | Statistics_CpuGuest | Statistics_CpuGuestNice;
                        break;
                default:
                        Log_error("system statistic error -- cannot read cpu usage\n");
                        return false;
        }

        si->statisticsAvailable |= Statistics_FiledescriptorsPerSystem | Statistics_FiledescriptorsPerProcess;

        struct stat sb;
        if (stat("/proc/sys/fs/file-nr", &sb) == 0)
                si->statisticsAvailable |= Statistics_FiledescriptorsPerProcessMax;

        return true;
}


pid_t Sysdep_getMainThread(pid_t pid) {
        char path[PATH_MAX];

        // Read PID's thread group (TGID)
        snprintf(path, sizeof(path), "/proc/%d/status", pid);
        FILE *fp = fopen(path, "r");
        if (! fp) {
                // Process doesn't exist
                return 0;
        }

        char line[STRLEN];
        pid_t tgid = -1;
        while (fgets(line, sizeof(line), fp)) {
                if (sscanf(line, "Tgid:\t%d", &tgid) == 1) {
                        break;
                }
        }
        fclose(fp);

        // Return the thread group leader (main thread's PID)
        return tgid;
}

