/*
 * vmstat_freebsd2.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>
#if defined(dragonfly)
#include <sys/user.h>
#else
#include <sys/proc.h>
#endif
#if defined(freebsd5) && __FreeBSD_version >= 500101
#include <sys/resource.h>
#elif defined(dragonfly)
#include <kinfo.h>
#else
#include <sys/dkstat.h>
#endif
#ifdef freebsd5
#include <sys/bio.h>
#endif
#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>

#if HAVE_SYS_VMPARAM_H
#include <sys/vmparam.h>
#else
#include <vm/vm_param.h>
#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.h"
#include "vmstat.h"
#include "vmstat_freebsd2.h"


/*
 * nlist symbols 
 */
#define CPTIME_SYMBOL   "cp_time"
#define SUM_SYMBOL      "cnt"
#define INTRCNT_SYMBOL  "intrcnt"
#define EINTRCNT_SYMBOL "eintrcnt"
#define BOOTTIME_SYMBOL "boottime"

/*
 * Number of interrupts 
 */
#define INT_COUNT       10

/*
 * CPU percentage 
 */
#define CPU_PRC         100

FindVarMethod var_extensible_vmstat;

void
init_vmstat_freebsd2(void)
{

    struct variable2 extensible_vmstat_variables[] = {
        {MIBINDEX, ASN_INTEGER, RONLY, var_extensible_vmstat, 1,
         {MIBINDEX}},
        {ERRORNAME, ASN_OCTET_STR, RONLY, var_extensible_vmstat, 1,
         {ERRORNAME}},
        {SWAPIN, ASN_INTEGER, RONLY, var_extensible_vmstat, 1, {SWAPIN}},
        {SWAPOUT, ASN_INTEGER, RONLY, var_extensible_vmstat, 1, {SWAPOUT}},
        {IOSENT, ASN_INTEGER, RONLY, var_extensible_vmstat, 1, {IOSENT}},
        {IORECEIVE, ASN_INTEGER, RONLY, var_extensible_vmstat, 1,
         {IORECEIVE}},
        {SYSINTERRUPTS, ASN_INTEGER, RONLY, var_extensible_vmstat, 1,
         {SYSINTERRUPTS}},
        {SYSCONTEXT, ASN_INTEGER, RONLY, var_extensible_vmstat, 1,
         {SYSCONTEXT}},
        {CPUUSER, ASN_INTEGER, RONLY, var_extensible_vmstat, 1, {CPUUSER}},
        {CPUSYSTEM, ASN_INTEGER, RONLY, var_extensible_vmstat, 1,
         {CPUSYSTEM}},
        {CPUIDLE, ASN_INTEGER, RONLY, var_extensible_vmstat, 1, {CPUIDLE}},
        {CPURAWUSER, ASN_COUNTER, RONLY, var_extensible_vmstat, 1,
         {CPURAWUSER}},
        {CPURAWNICE, ASN_COUNTER, RONLY, var_extensible_vmstat, 1,
         {CPURAWNICE}},
        {CPURAWSYSTEM, ASN_COUNTER, RONLY, var_extensible_vmstat, 1,
         {CPURAWSYSTEM}},
        {CPURAWIDLE, ASN_COUNTER, RONLY, var_extensible_vmstat, 1,
         {CPURAWIDLE}},
        {CPURAWKERNEL, ASN_COUNTER, RONLY, var_extensible_vmstat, 1,
         {CPURAWKERNEL}},
        {CPURAWINTR, ASN_COUNTER, RONLY, var_extensible_vmstat, 1,
         {CPURAWINTR}},
        {SYSRAWINTERRUPTS, ASN_COUNTER, RONLY, var_extensible_vmstat, 1,
         {SYSRAWINTERRUPTS}},
        {SYSRAWCONTEXT, ASN_COUNTER, RONLY, var_extensible_vmstat, 1,
         {SYSRAWCONTEXT}},
        /*
         * Future use: 
         */
        /*
         * {ERRORFLAG, ASN_INTEGER, RONLY, var_extensible_vmstat, 1, {ERRORFLAG }},
         * {ERRORMSG, ASN_OCTET_STR, 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(dragonfly)
    static struct kinfo_cputime cpu_old, cpu_new, cpu_diff;
    static uint64_t cpu_total;
    uint64_t cpu_sum;
    static int pagesize;
#else
    static long     cpu_old[CPUSTATES];
    static long     cpu_new[CPUSTATES];
    static long     cpu_diff[CPUSTATES];
    static long     cpu_total;
    long            cpu_sum;
#endif
    double          cpu_prc;

    static struct vmmeter mem_old, mem_new;

    static long     long_ret;
    static char     errmsg[300];

#if defined(dragonfly)
    if (pagesize == 0)
	    pagesize = getpagesize() >> 10;
#endif

    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) {
        time_diff = time_new - time_old;
        time_old = time_new;

        /*
         * CPU usage 
         */

        cpu_total = 0;

#if defined(dragonfly)
	kinfo_get_sched_cputime(&cpu_new);
#define CP_UPDATE(field) cpu_diff.field = cpu_new.field - cpu_old.field; cpu_total += cpu_diff.field;
	CP_UPDATE(cp_user);
	CP_UPDATE(cp_nice);
	CP_UPDATE(cp_sys);
	CP_UPDATE(cp_intr);
	CP_UPDATE(cp_idle);
	cpu_old = cpu_new;
#undef CP_UPDATE
#else
        auto_nlist(CPTIME_SYMBOL, (char *) cpu_new, sizeof(cpu_new));

        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];
        }
#endif

        if (cpu_total == 0)
            cpu_total = 1;

        /*
         * Memory info 
         */
        mem_old = mem_new;
        auto_nlist(SUM_SYMBOL, (char *) &mem_new, sizeof(mem_new));
    }

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

    /*
     * Page-to-kb macro 
     */
#if defined(dragonfly)
#define ptok(p) ((p) * pagesize)
#else
#define ptok(p) ((p) * (mem_new.v_page_size >> 10))
#endif

    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:
#if defined(openbsd2) || defined(darwin)
        long_ret = ptok(mem_new.v_swpin - mem_old.v_swpin);
#else
        long_ret = ptok(mem_new.v_swappgsin - mem_old.v_swappgsin +
                        mem_new.v_vnodepgsin - mem_old.v_vnodepgsin);
#endif
        long_ret = rate(long_ret);
        return ((u_char *) (&long_ret));
    case SWAPOUT:
#if defined(openbsd2) || defined(darwin)
        long_ret = ptok(mem_new.v_swpout - mem_old.v_swpout);
#else
        long_ret = ptok(mem_new.v_swappgsout - mem_old.v_swappgsout +
                        mem_new.v_vnodepgsout - mem_old.v_vnodepgsout);
#endif
        long_ret = rate(long_ret);
        return ((u_char *) (&long_ret));
    case IOSENT:
#if NETSNMP_NO_DUMMY_VALUES
        return NULL;
#endif
        long_ret = -1;
        return ((u_char *) (&long_ret));
    case IORECEIVE:
#if NETSNMP_NO_DUMMY_VALUES
        return NULL;
#endif
        long_ret = -1;
        return ((u_char *) (&long_ret));
    case SYSINTERRUPTS:
        long_ret = rate(mem_new.v_intr - mem_old.v_intr);
        return ((u_char *) (&long_ret));
    case SYSCONTEXT:
        long_ret = rate(mem_new.v_swtch - mem_old.v_swtch);
        return ((u_char *) (&long_ret));
    case CPUUSER:
#if defined(dragonfly)
        cpu_sum = cpu_diff.cp_user  + cpu_diff.cp_nice;
#else
        cpu_sum = cpu_diff[CP_USER] + cpu_diff[CP_NICE];
#endif
        cpu_prc = (float) cpu_sum / (float) cpu_total;
        long_ret = cpu_prc * CPU_PRC;
        return ((u_char *) (&long_ret));
    case CPUSYSTEM:
#if defined(dragonfly)
        cpu_sum = cpu_diff.cp_sys  + cpu_diff.cp_intr;
#else
        cpu_sum = cpu_diff[CP_SYS] + cpu_diff[CP_INTR];
#endif
        cpu_prc = (float) cpu_sum / (float) cpu_total;
        long_ret = cpu_prc * CPU_PRC;
        return ((u_char *) (&long_ret));
    case CPUIDLE:
#if defined(dragonfly)
        cpu_sum = cpu_diff.cp_idle;
#else
        cpu_sum = cpu_diff[CP_IDLE];
#endif
        cpu_prc = (float) cpu_sum / (float) cpu_total;
        long_ret = cpu_prc * CPU_PRC;
        return ((u_char *) (&long_ret));
    case CPURAWUSER:
#if defined(dragonfly)
        long_ret = cpu_new.cp_user;
#else
        long_ret = cpu_new[CP_USER];
#endif
        return ((u_char *) (&long_ret));
    case CPURAWNICE:
#if defined(dragonfly)
        long_ret = cpu_new.cp_nice;
#else
        long_ret = cpu_new[CP_NICE];
#endif
        return ((u_char *) (&long_ret));
    case CPURAWSYSTEM:
#if defined(dragonfly)
        long_ret = cpu_new.cp_sys  + cpu_new.cp_nice;
#else
        long_ret = cpu_new[CP_SYS] + cpu_new[CP_INTR];
#endif
        return ((u_char *) (&long_ret));
    case CPURAWIDLE:
#if defined(dragonfly)
        long_ret = cpu_new.cp_idle;
#else
        long_ret = cpu_new[CP_IDLE];
#endif
        return ((u_char *) (&long_ret));
    case CPURAWKERNEL:
#if defined(dragonfly)
        long_ret = cpu_new.cp_sys;
#else
        long_ret = cpu_new[CP_SYS];
#endif
        return ((u_char *) (&long_ret));
    case CPURAWINTR:
#if defined(dragonfly)
        long_ret = cpu_new.cp_intr;
#else
        long_ret = cpu_new[CP_INTR];
#endif
        return ((u_char *) (&long_ret));
    case SYSRAWINTERRUPTS:
        long_ret = mem_new.v_intr;
        return ((u_char *) (&long_ret));
    case SYSRAWCONTEXT:
        long_ret = mem_new.v_swtch;
        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;
}
