/*
 * vmstat_freebsd2.c
 */

#include <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>
#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 "mibincl.h"
#include "util_funcs.h"

#include "vmstat.h"
#include "vmstat_freebsd2.h"
#include "auto_nlist.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

static 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[] = { EXTENSIBLEMIB,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);
}

static
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;

    static long cpu_old[CPUSTATES];
    static long cpu_new[CPUSTATES];
    static long cpu_diff[CPUSTATES];
    static long cpu_total;
    long cpu_sum;
    double cpu_prc;

    static struct vmmeter mem_old, mem_new;

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

	/* CPU usage */
	auto_nlist(CPTIME_SYMBOL, (char *)cpu_new, sizeof (cpu_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;
	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 */
#define ptok(p) ((p) * (mem_new.v_page_size >> 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:
#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 NO_DUMMY_VALUES
	return NULL;
#endif
	long_ret = -1;
	return((u_char *) (&long_ret));
    case IORECEIVE:
#if 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:
	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));
    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;
}

