
/* Disgusting hack :)  I pulled this code out of top.  josh@hitchhiker.org */
/* gcc cpustate.c -o cpustate -lelf -lkvm -lkstat */
/* usage: cpustate <num cpus> <interval> */

#include <kvm.h>
#include <kstat.h>
#include <sys/sysinfo.h>
#include <stdio.h>

static kstat_t **cpu_ks;
static cpu_stat_t *cpu_stat;
static int ncpus=0;
static int poll_int=0;

#define CPUSTATES     5
int cpu_states[CPUSTATES];
char *cpustatenames[] = {"idle", "user", "kernel", "iowait", "swap", NULL};
#define CPUSTATE_IOWAIT 3
#define CPUSTATE_SWAP   4


/*
 *  percentages(cnt, out, new, old, diffs) - calculate percentage change
 *	between array "old" and "new", putting the percentages i "out".
 *	"cnt" is size of each array and "diffs" is used for scratch space.
 *	The array "old" is updated on each call.
 *	The routine assumes modulo arithmetic.  This function is especially
 *	useful on BSD mchines for calculating cpu state percentages.
 */

long percentages(cnt, out, new, old, diffs)

int cnt;
int *out;
register long *new;
register long *old;
long *diffs;

{
    register int i;
    register long change;
    register long total_change;
    register long *dp;
    long half_total;

    /* initialization */
    total_change = 0;
    dp = diffs;

    /* calculate changes for each state and the overall change */
    for (i = 0; i < cnt; i++)
    {
	if ((change = *new - *old) < 0)
	{
	    /* this only happens when the counter wraps */
	    change = (int)
		((unsigned long)*new-(unsigned long)*old);
	}
	total_change += (*dp++ = change);
	*old++ = *new++;
    }

    /* avoid divide by zero potential */
    if (total_change == 0)
    {
	total_change = 1;
    }

    /* calculate percentages based on overall change, rounding up */
    half_total = total_change / 2l;
    for (i = 0; i < cnt; i++)
    {
	*out++ = (int)((*diffs++ * 1000 + half_total) / total_change);
    }

    /* return the total in case the caller wants to use it */
    return(total_change);
}



#define UPDKCID(nk,ok) \
if (nk == -1) { \
  perror("kstat_read "); \
  exit(1); \
} \
if (nk != ok)\
  goto kcid_changed;

kupdate()
{
  kstat_t *ks;
  kid_t nkcid;
  int i;
  int changed = 0;
  static kstat_ctl_t *kc = NULL;
  int ncpu = 0;
  static kid_t kcid = 0;

/*
 * 0. kstat_open
 */

  if (!kc) {
    kc = kstat_open();
    if (!kc) {
      perror("kstat_open ");
      exit(1);
    }
    changed = 1;
    kcid = kc->kc_chain_id;
  }

  /* keep doing it until no more changes */
 kcid_changed:

/*
 * 1.  kstat_chain_update
 */

  nkcid = kstat_chain_update(kc);
  if (nkcid) {  /* UPDKCID will abort if nkcid is -1, so no need to check */
    changed = 1;
    kcid = nkcid;
  }
  UPDKCID(nkcid,0);

  if (changed) {

/*
 * 2. get data addresses
 */

    ncpu = 0;
    for (ks = kc->kc_chain; ks; 
	 ks = ks->ks_next) {

      if (strncmp(ks->ks_name, "cpu_stat", 8) == 0) {
	nkcid = kstat_read(kc, ks, NULL);
	UPDKCID(nkcid, kcid);  /* if kcid changed, pointer might be invalid */

	cpu_ks[ncpu] = ks;
	ncpu++;
	 	 
	if (ncpu > ncpus) {
	  fprintf(stderr, "kstat finds %d CPUs, should be %d\n", ncpu, ncpus);
	  exit(1);
	}
      }
    }
    if (ncpu != ncpus) {
      fprintf(stderr, "kstat finds %d CPUs, should be %d\n", ncpu, ncpus);
      exit(1);
    }
    changed = 0;
  }


/*
 * 3. get data
 */

  for (i = 0; i < ncpus; i++) {
    nkcid = kstat_read(kc, cpu_ks[i], &cpu_stat[i]);
    UPDKCID(nkcid, kcid);  /* if kcid changed, pointer might be invalid */
  }

}



int doit () {
   static long cp_time[CPUSTATES];
   static long cp_old[CPUSTATES];
   static long cp_diff[CPUSTATES];
   int j,i;
   
   /* get the cp_time array */
   for (j = 0; j < CPUSTATES; j++)
     cp_time[j] = 0L;
   
   
   /* use kstat to upadte all processor information */
   kupdate();
   for (i = 0; i < ncpus; i++) {
      /* sum counters up to, but not including, wait state counter */
      for (j = 0; j < CPU_WAIT; j++)
	cp_time[j] += (long) cpu_stat[i].cpu_sysinfo.cpu[j];
      
      /* add in wait state breakdown counters */
      cp_time[CPUSTATE_IOWAIT] += (long) cpu_stat[i].cpu_sysinfo.wait[W_IO] +
	(long) cpu_stat[i].cpu_sysinfo.wait[W_PIO];
      cp_time[CPUSTATE_SWAP] += (long) cpu_stat[i].cpu_sysinfo.wait[W_SWAP];
   }
   
   
   /* convert cp_time counts to percentages */
   (void) percentages (CPUSTATES, cpu_states, cp_time, cp_old, cp_diff);
   
}


int main (int argc, char* argv[]){
   int i;
   int first=1;
   
   ncpus=atoi(argv[1]);
   poll_int=atoi(argv[2]);

   cpu_ks = (kstat_t **) malloc (ncpus * sizeof (kstat_t *));
   cpu_stat = (cpu_stat_t *) malloc (ncpus * sizeof (cpu_stat_t));	    	    
   
   while(1) {
      doit();
      
      if (first) { first=0; sleep(poll_int); doit(); }
      for (i=0; i<CPUSTATES; i++) {
	 /* if percentage is >= 1000, print it as 100% */
	 printf((cpu_states[i] >= 1000 ? "%4.0f%% %s\n" : "%4.1f%% %s\n"),
		((float)cpu_states[i])/10.,
		cpustatenames[i]);
	 fflush(stdout);
      }
      printf("--\n");
      sleep(poll_int);
   }

}

