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

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

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

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

#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif

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

#ifdef HAVE_KVM_H
#include <kvm.h>
#endif

#ifdef HAVE_UVM_UVM_H
#include <uvm/uvm.h>
#endif

#ifdef HAVE_SYS_SYSCTL_H
#include <sys/sysctl.h>
#endif

#ifdef HAVE_SYS_DKSTAT_H
#include <sys/dkstat.h>
#endif

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


/**
 *  System dependent resource gathering code for NetBSD.
 *
 *  @file
 */


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


static int      hz;
static int      pagesize_kbyte; 
static long     total_old    = 0;
static long     cpu_user_old = 0;
static long     cpu_syst_old = 0;
static unsigned maxslp;


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


int init_process_info_sysdep(void) {
  int              mib[2];
  size_t           len;
  struct clockinfo clock;

  mib[0] = CTL_KERN;
  mib[1] = KERN_CLOCKRATE;
  len    = sizeof(clock);
  if (sysctl(mib, 2, &clock, &len, NULL, 0) == -1) {
    DEBUG("system statistic error -- cannot get clock rate: %s\n", STRERROR);
    return FALSE;
  }
  hz     = clock.hz;

  mib[0] = CTL_HW;

  mib[1] = HW_NCPU;
  len    = sizeof(systeminfo.cpus);
  if (sysctl(mib, 2, &systeminfo.cpus, &len, NULL, 0) == -1) {
    DEBUG("system statistic error -- cannot get cpu count: %s\n", STRERROR);
    return FALSE;
  }

  mib[1] = HW_PHYSMEM;
  len    = sizeof(systeminfo.mem_kbyte_max);
  if (sysctl(mib, 2, &systeminfo.mem_kbyte_max, &len, NULL, 0) == -1) {
    DEBUG("system statistic error -- cannot get real memory amount: %s\n", STRERROR);
    return FALSE;
  }
  systeminfo.mem_kbyte_max /= 1024;

  mib[1] = HW_PAGESIZE;
  len    = sizeof(pagesize_kbyte);
  if (sysctl(mib, 2, &pagesize_kbyte, &len, NULL, 0) == -1) {
    DEBUG("system statistic error -- cannot get memory page size: %s\n", STRERROR);
    return FALSE;
  }
  pagesize_kbyte /= 1024;

  return TRUE;
}


/**
 * Read all processes to initialize the information tree.
 * @param reference  reference of ProcessTree
 * @return treesize>0 if succeeded otherwise =0.
 */
int initprocesstree_sysdep(ProcessTree_T ** reference) {
  int                        treesize;
  size_t                     size = sizeof(maxslp);
  char                       buf[_POSIX2_LINE_MAX];
  int                        mib_proc2[6] = {CTL_KERN, KERN_PROC2, KERN_PROC_ALL, 0, sizeof(struct kinfo_proc2), 0};
  static int                 mib_maxslp[] = {CTL_VM, VM_MAXSLP};
  ProcessTree_T             *pt;
  kvm_t                     *kvm_handle;
  static struct kinfo_proc2 *pinfo;

  if (sysctl(mib_maxslp, 2, &maxslp, &size, NULL, 0) < 0) {
    LogError("system statistic error -- vm.maxslp failed");
    return FALSE;
  }

  if (sysctl(mib_proc2, 6, NULL, &size, NULL, 0) == -1) {
    LogError("system statistic error -- kern.proc2 #1 failed");
    return FALSE;
  }

  size *= 2; // Add reserve for new processes which were created between calls of sysctl
  pinfo = CALLOC(1, size);
  mib_proc2[5] = (int)(size / sizeof(struct kinfo_proc2));
  if (sysctl(mib_proc2, 6, pinfo, &size, NULL, 0) == -1) {
    FREE(pinfo);
    LogError("system statistic error -- kern.proc2 #2 failed");
    return FALSE;
  }

  treesize = (int)(size / sizeof(struct kinfo_proc2));

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

  if (! (kvm_handle = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, buf))) {
    FREE(pinfo);
    FREE(pt);
    LogError("system statistic error -- kvm_openfiles failed: %s", buf);
    return FALSE;
  }

  for (int i = 0; i < treesize; i++) {
    pt[i].pid         = pinfo[i].p_pid;
    pt[i].ppid        = pinfo[i].p_ppid;
    pt[i].starttime   = pinfo[i].p_ustart_sec;
    pt[i].cputime     = (long)((pinfo[i].p_rtime_sec * 10) + (pinfo[i].p_rtime_usec / 100000));
    pt[i].cpu_percent = 0;
    pt[i].mem_kbyte   = (unsigned long)(pinfo[i].p_vm_rssize * pagesize_kbyte);
    if (pinfo[i].p_stat == SZOMB)
      pt[i].status_flag |= PROCESS_ZOMBIE;
    pt[i].time = get_float_time();
    char **args;
    if ((args = kvm_getargv2(kvm_handle, &pinfo[i], 0))) {
      StringBuffer_T cmdline = StringBuffer_create(64);
      for (int j = 0; args[j]; j++)
        StringBuffer_append(cmdline, args[j + 1] ? "%s " : "%s", args[j]);
      pt[i].cmdline = Str_dup(StringBuffer_toString(StringBuffer_trim(cmdline)));
      StringBuffer_free(&cmdline);
    }
    if (! pt[i].cmdline || ! *pt[i].cmdline) {
      FREE(pt[i].cmdline);
      pt[i].cmdline = Str_dup(pinfo[i].p_comm);
    }
  }
  FREE(pinfo);
  kvm_close(kvm_handle);

  *reference = pt;

  return treesize;
}


/**
 * 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) {
  return getloadavg(loadv, nelem);
}


/**
 * 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) {
  int            mib[2];
  size_t         len;
  struct vmtotal vm;
  struct uvmexp  vmexp;

  /* Memory */
  mib[0] = CTL_VM;
  mib[1] = VM_METER;
  len    = sizeof(struct vmtotal);
  if (sysctl(mib, 2, &vm, &len, NULL, 0) == -1) {
    LogError("system statistic error -- cannot get real memory usage: %s\n", STRERROR);
    return FALSE;
  }
  si->total_mem_kbyte = (unsigned long)(vm.t_arm * pagesize_kbyte);

  /* Swap */
  mib[1] = VM_UVMEXP;
  len = sizeof(vmexp);
  if (sysctl(mib, 2, &vmexp, &len, NULL, 0) == -1) {
    LogError("system statistic error -- cannot get swap usage: %s\n", STRERROR);
    si->swap_kbyte_max = 0;
    return FALSE;
  }
  si->swap_kbyte_max   = (unsigned long)((double)vmexp.swpages   * ((double)vmexp.pagesize / 1024.));
  si->total_swap_kbyte = (unsigned long)((double)vmexp.swpginuse * ((double)vmexp.pagesize / 1024.));

  return TRUE;
}


/**
 * This routine returns system/user CPU time in use.
 * @return: TRUE if successful, FALSE if failed
 */
int used_system_cpu_sysdep(SystemInfo_T *si) {
  int       i;
  int       mib[] = {CTL_KERN, KERN_CP_TIME};
  long long cp_time[CPUSTATES];
  long      total_new = 0;
  long      total;
  size_t    len;

  len = sizeof(cp_time);
  if (sysctl(mib, 2, &cp_time, &len, NULL, 0) == -1) {
    LogError("system statistic error -- cannot get cpu time: %s\n", STRERROR);
    return FALSE;
  }

  for (i = 0; i < CPUSTATES; i++)
    total_new += cp_time[i];
  total     = total_new - total_old;
  total_old = total_new;

  si->total_cpu_user_percent = (total > 0)?(int)(1000 * (double)(cp_time[CP_USER] - cpu_user_old) / total):-10;
  si->total_cpu_syst_percent = (total > 0)?(int)(1000 * (double)(cp_time[CP_SYS] - cpu_syst_old) / total):-10;
  si->total_cpu_wait_percent = 0; /* there is no wait statistic available */

  cpu_user_old = cp_time[CP_USER];
  cpu_syst_old = cp_time[CP_SYS];

  return TRUE;  
}

