/*
 * 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 <http://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"

#include "monit.h"

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

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

#ifdef HAVE_UNISTD_H
#include <unistd.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 TIME_WITH_SYS_TIME
#include <time.h>

#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#else
#include <time.h>
#endif

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

#ifdef HAVE_PROCINFO_H
#include <procinfo.h>
#endif

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

#ifdef HAVE_SYS_PROC_H
#include <sys/proc.h>
#endif

#ifdef HAVE_SYS_PROCFS_H
#include <sys/procfs.h>
#endif

#ifdef HAVE_CF_H
#include <cf.h>
#endif

#ifdef HAVE_SYS_CFGODM_H
#include <sys/cfgodm.h>
#endif

#ifdef HAVE_SYS_CFGDB_H
#include <sys/cfgdb.h>
#endif

#ifdef HAVE_SYS_SYSTEMCFG_H
#include <sys/systemcfg.h>
#endif

#ifdef HAVE_SYS_PROC_H
#include <sys/proc.h>
#endif

#ifdef HAVE_SYS_PROTOSW_H
#include <sys/protosw.h>
#endif

#ifdef HAVE_LIBPERFSTAT_H
#include <sys/protosw.h>
#include <libperfstat.h>
#endif

#include "process.h"
#include "process_sysdep.h"

/**
 *  System dependent resource gathering code for AIX
 *
 *  @file
 */

/* There is no prototype for getprocs64 in AIX <= 5.3 */
int getprocs64(void *, int, void *, int, pid_t *, int);

static int                page_size;
static int                cpu_initialized = 0;
static unsigned long long cpu_total_old = 0ULL;
static unsigned long long cpu_user_old  = 0ULL;
static unsigned long long cpu_syst_old  = 0ULL;
static unsigned long long cpu_wait_old  = 0ULL;

struct procentry64 *procs = NULL;


int init_process_info_sysdep(void) {
  perfstat_memory_total_t mem;

  if (perfstat_memory_total(NULL, &mem, sizeof(perfstat_memory_total_t), 1) < 1) {
    LogError("system statistic error -- perfstat_memory_total failed: %s\n", STRERROR);
    return FALSE;
  }

  page_size                = getpagesize();
  systeminfo.mem_kbyte_max = (unsigned long)(mem.real_total * (page_size / 1024));
  systeminfo.cpus          = sysconf(_SC_NPROCESSORS_ONLN);

  return TRUE;
}


/**
 * 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) {
  perfstat_cpu_total_t cpu;

  if (perfstat_cpu_total(NULL, &cpu, sizeof(perfstat_cpu_total_t), 1) < 1) {
      LogError("system statistic error -- perfstat_cpu_total failed: %s\n", STRERROR);
      return -1;
  }

  switch (nelem) {
    case 3:
      loadv[2] = (double)cpu.loadavg[2] / (double)(1<<SBITS);

    case 2:
      loadv[1] = (double)cpu.loadavg[1] / (double)(1<<SBITS);

    case 1:
      loadv[0] = (double)cpu.loadavg[0] / (double)(1<<SBITS);
  }

  return TRUE;
}


/**
 * Read all processes to initialize the process tree
 * @param reference  reference of ProcessTree
 * @return treesize>0 if succeeded otherwise =0.
 */
int initprocesstree_sysdep(ProcessTree_T ** reference) {
  int             i;
  int             treesize;
  struct userinfo user;
  ProcessTree_T  *pt;
  pid_t           firstproc = 0;

  memset(&user, 0, sizeof(struct userinfo));

  if ((treesize = getprocs64(NULL, 0, NULL, 0, &firstproc, PID_MAX)) < 0) {
    LogError("system statistic error -- getprocs64 failed: %s\n", STRERROR);
    return FALSE;
  }

  procs = CALLOC(sizeof(struct procentry64), treesize);

  firstproc = 0;
  if ((treesize = getprocs64(procs, sizeof(struct procentry64), NULL, 0, &firstproc, treesize)) < 0) {
    FREE(procs);
    LogError("system statistic error -- getprocs64 failed: %s\n", STRERROR);
    return FALSE;
  }

  pt = CALLOC(sizeof(ProcessTree_T), treesize);

  for (i = 0; i < treesize; i++) {
    int fd;
    struct psinfo ps;
    char filename[STRLEN];

    pt[i].cputime     = 0;
    pt[i].cpu_percent = 0;
    pt[i].mem_kbyte   = 0;
    pt[i].pid         = procs[i].pi_pid;
    pt[i].ppid        = procs[i].pi_ppid;
    pt[i].starttime   = procs[i].pi_start;

    if (procs[i].pi_state == SZOMB) {
      pt[i].status_flag |= PROCESS_ZOMBIE;
    } else if (getuser(&(procs[i]), sizeof(struct procinfo), &user, sizeof(struct userinfo)) != -1) {
      pt[i].mem_kbyte = (user.ui_drss + user.ui_trss) * (page_size / 1024);
      pt[i].cputime   = (user.ui_ru.ru_utime.tv_sec + user.ui_ru.ru_utime.tv_usec * 1.0e-6 + user.ui_ru.ru_stime.tv_sec + user.ui_ru.ru_stime.tv_usec * 1.0e-6) * 10;
    }

    snprintf(filename, sizeof(filename), "/proc/%d/psinfo", pt[i].pid);
    if ((fd = open(filename, O_RDONLY)) < 0) {
      DEBUG("%s: Cannot open proc file %s -- %s\n", prog, filename, STRERROR);
      continue;
    }
    if (read(fd, &ps, sizeof(ps)) < 0) {
      DEBUG("%s: Cannot read proc file %s -- %s\n", prog, filename, STRERROR);
      if (close(fd) < 0)
        LogError("%s: Socket close failed -- %s\n", prog, STRERROR);
      return FALSE;
    }
    if (close(fd) < 0)
      LogError("%s: Socket close failed -- %s\n", prog, STRERROR);
    pt[i].cmdline = (ps.pr_psargs && *ps.pr_psargs) ? Str_dup(ps.pr_psargs) : Str_dup(procs[i].pi_comm);
  }

  FREE(procs);
  *reference = pt;

  return treesize;
}


/**
 * This routine returns kbyte of real memory in use.
 * @return: TRUE if successful, FALSE if failed (or not available)
 */
int used_system_memory_sysdep(SystemInfo_T *si) {
  perfstat_memory_total_t  mem;

  /* Memory */
  if (perfstat_memory_total(NULL, &mem, sizeof(perfstat_memory_total_t), 1) < 1) {
    LogError("system statistic error -- perfstat_memory_total failed: %s\n", STRERROR);
    return FALSE;
  }
  si->total_mem_kbyte = (unsigned long)((mem.real_total - mem.real_free - mem.numperm) * (page_size / 1024));

  /* Swap */
  si->swap_kbyte_max   = (unsigned long)(mem.pgsp_total * 4);                   /* 4kB blocks */
  si->total_swap_kbyte = (unsigned long)((mem.pgsp_total - mem.pgsp_free) * 4); /* 4kB blocks */

  return TRUE;
}


/**
 * This routine returns system/user CPU time in use.
 * @return: TRUE if successful, FALSE if failed (or not available)
 */
int used_system_cpu_sysdep(SystemInfo_T *si) {
  perfstat_cpu_total_t cpu;
  unsigned long long cpu_total;
  unsigned long long cpu_total_new = 0ULL;
  unsigned long long cpu_user      = 0ULL;
  unsigned long long cpu_syst      = 0ULL;
  unsigned long long cpu_wait      = 0ULL;

  if (perfstat_cpu_total(NULL, &cpu, sizeof(perfstat_cpu_total_t), 1) < 0) {
      LogError("system statistic error -- perfstat_cpu_total failed: %s\n", STRERROR);
      return -1;
  }

  cpu_total_new = (cpu.user + cpu.sys + cpu.wait + cpu.idle) / cpu.ncpus;
  cpu_total     = cpu_total_new - cpu_total_old;
  cpu_total_old = cpu_total_new;
  cpu_user      = cpu.user / cpu.ncpus;
  cpu_syst      = cpu.sys / cpu.ncpus;
  cpu_wait      = cpu.wait / cpu.ncpus;

  if (cpu_initialized) {
    if (cpu_total > 0) {
      si->total_cpu_user_percent = 1000 * ((double)(cpu_user - cpu_user_old) / (double)cpu_total);
      si->total_cpu_syst_percent = 1000 * ((double)(cpu_syst - cpu_syst_old) / (double)cpu_total);
      si->total_cpu_wait_percent = 1000 * ((double)(cpu_wait - cpu_wait_old) / (double)cpu_total);
    } else {
      si->total_cpu_user_percent = 0;
      si->total_cpu_syst_percent = 0;
      si->total_cpu_wait_percent = 0;
    }
  }

  cpu_user_old = cpu_user;
  cpu_syst_old = cpu_syst;
  cpu_wait_old = cpu_wait;

  cpu_initialized = 1;

  return TRUE;
}

