/*
 * vmstat_netbsd1.c
 */

#include <net-snmp/net-snmp-config.h>

/*
 * Ripped from /usr/scr/usr.bin/vmstat/vmstat.c (covering all bases) 
 */
#include <sys/param.h>
#include <sys/time.h>
#include <sys/proc.h>
#include <sys/dkstat.h>
#include <sys/buf.h>
#include <sys/uio.h>
#include <sys/namei.h>
#include <sys/malloc.h>
#include <sys/signal.h>
#include <sys/fcntl.h>
#include <sys/ioctl.h>
#include <sys/sysctl.h>
#include <sys/vmmeter.h>
#include <sys/sched.h>

#if defined(HAVE_UVM_UVM_PARAM_H) && defined(HAVE_UVM_UVM_EXTERN_H)
#include <uvm/uvm_param.h>
#include <uvm/uvm_extern.h>
#elif defined(HAVE_VM_VM_PARAM_H) && defined(HAVE_VM_VM_EXTERN_H)
#include <vm/vm_param.h>
#include <vm/vm_extern.h>
#else
#error vmstat_netbsd1.c: Is this really a NetBSD system?
#endif

#include <time.h>
#include <nlist.h>
#include <kvm.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <paths.h>
#include <limits.h>


#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include <net-snmp/agent/auto_nlist.h>

#include "util_funcs/header_generic.h"
#include "vmstat.h"

/*
 * CPU percentage 
 */
#define CPU_PRC         100
#define CPTIME_SYMBOL	"cp_time"
#define BOOTTIME_SYMBOL	"boottime"

FindVarMethod var_extensible_vmstat;

void
init_vmstat_netbsd1(void)
{

    struct variable2 extensible_vmstat_variables[] = {
        {MIBINDEX, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
         var_extensible_vmstat, 1, {MIBINDEX}},
        {ERRORNAME, ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY,
         var_extensible_vmstat, 1, {ERRORNAME}},
        {SWAPIN, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
         var_extensible_vmstat, 1, {SWAPIN}},
        {SWAPOUT, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
         var_extensible_vmstat, 1, {SWAPOUT}},
        {IOSENT, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
         var_extensible_vmstat, 1, {IOSENT}},
        {IORECEIVE, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
         var_extensible_vmstat, 1, {IORECEIVE}},
        {SYSINTERRUPTS, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
         var_extensible_vmstat, 1, {SYSINTERRUPTS}},
        {SYSCONTEXT, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
         var_extensible_vmstat, 1, {SYSCONTEXT}},
        {CPUUSER, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
         var_extensible_vmstat, 1, {CPUUSER}},
        {CPUSYSTEM, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
         var_extensible_vmstat, 1, {CPUSYSTEM}},
        {CPUIDLE, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
         var_extensible_vmstat, 1, {CPUIDLE}},
        {CPURAWUSER, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
         var_extensible_vmstat, 1, {CPURAWUSER}},
        {CPURAWNICE, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
         var_extensible_vmstat, 1, {CPURAWNICE}},
        {CPURAWSYSTEM, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
         var_extensible_vmstat, 1, {CPURAWSYSTEM}},
        {CPURAWIDLE, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
         var_extensible_vmstat, 1, {CPURAWIDLE}},
        {CPURAWKERNEL, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
         var_extensible_vmstat, 1, {CPURAWKERNEL}},
        {CPURAWINTR, ASN_COUNTER, NETSNMP_OLDAPI_RONLY,
         var_extensible_vmstat, 1, {CPURAWINTR}},

        /*
         * Future use: 
         */
        /*
         * {ERRORFLAG, ASN_INTEGER, NETSNMP_OLDAPI_RONLY,
         *  var_extensible_vmstat, 1, {ERRORFLAG }},
         * {ERRORMSG, ASN_OCTET_STR, NETSNMP_OLDAPI_RONLY,
         *  var_extensible_vmstat, 1, {ERRORMSG }}
         */
    };

    /*
     * Define the OID pointer to the top of the mib tree that we're
     * registering underneath 
     */
    oid             vmstat_variables_oid[] = { NETSNMP_UCDAVIS_MIB, 11 };

    /*
     * register ourselves with the agent to handle our mib tree 
     */
    REGISTER_MIB("ucd-snmp/vmstat", extensible_vmstat_variables, variable2,
                 vmstat_variables_oid);

}


long
getuptime(void)
{
    static time_t   now, boottime;
    time_t          uptime;

    if (boottime == 0)
        auto_nlist(BOOTTIME_SYMBOL, (char *) &boottime, sizeof(boottime));

    time(&now);
    uptime = now - boottime;

    return (uptime);
}

unsigned char  *
var_extensible_vmstat(struct variable *vp,
                      oid * name,
                      size_t * length,
                      int exact,
                      size_t * var_len, WriteMethod ** write_method)
{

    int             loop;

    time_t          time_new = getuptime();
    static time_t   time_old;
    static time_t   time_diff;

#if defined(KERN_CP_TIME)
    static uint64_t  cpu_old[CPUSTATES];
    static uint64_t  cpu_new[CPUSTATES];
    static uint64_t  cpu_diff[CPUSTATES];
    static uint64_t  cpu_total;
#elif defined(KERN_CPTIME)
    static long     cpu_old[CPUSTATES];
    static long     cpu_new[CPUSTATES];
    static long     cpu_diff[CPUSTATES];
    static long     cpu_total;
#else
    static long     cpu_old[CPUSTATES];
    static long     cpu_new[CPUSTATES];
    static long     cpu_diff[CPUSTATES];
    static long     cpu_total;
#endif
    long            cpu_sum;
    double          cpu_prc;

    static struct uvmexp mem_old, mem_new;
    int             mem_mib[] = { CTL_VM, VM_UVMEXP };
    size_t          mem_size = sizeof(struct uvmexp);

    static long     long_ret;
    static char     errmsg[300];

    long_ret = 0;               /* set to 0 as default */

    if (header_generic(vp, name, length, exact, var_len, write_method))
        return (NULL);

    /*
     * Update structures (only if time has passed) 
     */
    if (time_new != time_old) {
#ifdef KERN_CP_TIME
        int             mib[2] = { CTL_KERN, KERN_CP_TIME };
        size_t          ssize = sizeof(cpu_new);

        if (sysctl(mib, 2, cpu_new, &ssize, NULL, 0) < 0)
            memset(cpu_new, 0, sizeof(cpu_new));
#elif defined(KERN_CPTIME)
        int             mib[2] = { CTL_KERN, KERN_CPTIME };
        size_t          ssize = sizeof(cpu_new);

        if (sysctl(mib, 2, cpu_new, &ssize, NULL, 0) < 0)
            memset(cpu_new, 0, sizeof(cpu_new));
#else
        /*
         * CPU usage 
         */
        auto_nlist(CPTIME_SYMBOL, (char *) cpu_new, sizeof(cpu_new));
#endif

        time_diff = time_new - time_old;
        time_old = time_new;

        cpu_total = 0;

        for (loop = 0; loop < CPUSTATES; loop++) {
            cpu_diff[loop] = cpu_new[loop] - cpu_old[loop];
            cpu_old[loop] = cpu_new[loop];
            cpu_total += cpu_diff[loop];
        }

        if (cpu_total == 0)
            cpu_total = 1;

        /*
         * Memory info 
         */
        mem_old = mem_new;
        sysctl(mem_mib, 2, &mem_new, &mem_size, NULL, 0);
    }

    /*
     * Rate macro 
     */
#define rate(x) (((x)+ time_diff/2) / time_diff)

    /*
     * Page-to-kb macro 
     */
#define ptok(p) ((p) * (mem_new.pagesize >> 10))

    switch (vp->magic) {
    case MIBINDEX:
        long_ret = 1;
        return ((u_char *) (&long_ret));
    case ERRORNAME:            /* dummy name */
        sprintf(errmsg, "systemStats");
        *var_len = strlen(errmsg);
        return ((u_char *) (errmsg));
    case SWAPIN:
        long_ret = ptok(mem_new.swapins - mem_old.swapins);
        long_ret = rate(long_ret);
        return ((u_char *) (&long_ret));
    case SWAPOUT:
        long_ret = ptok(mem_new.swapouts - mem_old.swapouts);
        long_ret = rate(long_ret);
        return ((u_char *) (&long_ret));
    case IOSENT:
#ifdef NETSNMP_NO_DUMMY_VALUES
        return NULL;
#endif
        long_ret = -1;
        return ((u_char *) (&long_ret));
    case IORECEIVE:
#ifdef NETSNMP_NO_DUMMY_VALUES
        return NULL;
#endif
        long_ret = -1;
        return ((u_char *) (&long_ret));
    case SYSINTERRUPTS:
        long_ret = rate(mem_new.intrs - mem_old.intrs);
        return ((u_char *) (&long_ret));
    case SYSCONTEXT:
        long_ret = rate(mem_new.swtch - mem_old.swtch);
        return ((u_char *) (&long_ret));
    case CPUUSER:
        cpu_sum = cpu_diff[CP_USER] + cpu_diff[CP_NICE];
        cpu_prc = (float) cpu_sum / (float) cpu_total;
        long_ret = cpu_prc * CPU_PRC;
        return ((u_char *) (&long_ret));
    case CPUSYSTEM:
        cpu_sum = cpu_diff[CP_SYS] + cpu_diff[CP_INTR];
        cpu_prc = (float) cpu_sum / (float) cpu_total;
        long_ret = cpu_prc * CPU_PRC;
        return ((u_char *) (&long_ret));
    case CPUIDLE:
        cpu_sum = cpu_diff[CP_IDLE];
        cpu_prc = (float) cpu_sum / (float) cpu_total;
        long_ret = cpu_prc * CPU_PRC;
        return ((u_char *) (&long_ret));
    case CPURAWUSER:
        long_ret = cpu_new[CP_USER];
        return ((u_char *) (&long_ret));
    case CPURAWNICE:
        long_ret = cpu_new[CP_NICE];
        return ((u_char *) (&long_ret));
    case CPURAWSYSTEM:
        long_ret = cpu_new[CP_SYS] + cpu_new[CP_INTR];
        return ((u_char *) (&long_ret));
    case CPURAWIDLE:
        long_ret = cpu_new[CP_IDLE];
        return ((u_char *) (&long_ret));
    case CPURAWKERNEL:
        long_ret = cpu_new[CP_SYS];
        return ((u_char *) (&long_ret));
    case CPURAWINTR:
        long_ret = cpu_new[CP_INTR];
        return ((u_char *) (&long_ret));
        /*
         * reserved for future use 
         */
        /*
         * case ERRORFLAG:
         * return((u_char *) (&long_ret));
         * case ERRORMSG:
         * return((u_char *) (&long_ret));
         */
    }
    return NULL;
}
