/*
 * System dependent file for Linux.   Includes SMP support.
 *
 * Kumsup Lee <klee@ima.umn.edu>
 * John DiMarco <jdd@cs.toronto.edu>,  University of Toronto, CSLab
 * Greg Nakhimovsky <greg.nakhimovsky@sun.com> May 2004
 *   - fixed a bug in display_bars() for SMP
 *   - added Linux support for "-disk"
 * Greg Nakhimovsky <greg.nakhimovsky@sun.com> April 2006
 *   - added disk stats for the >= 2.6 Linux kernel using /proc/diskstats
 */

/* LINTLIBRARY */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/param.h>
#include <sys/types.h>

/* 
 * NR_CPUS in <linux/threads.h> sets the maximum number of CPUs
 * this version of Linux can handle.  CONFIG_SMP needs to be set first,
 * else NR_CPUS will be one. In old Linux versions, NR_CPUS used
 * to be in <nlist.h>.  We use this number to size the CPU state
 * data structures.
 */
#define CONFIG_SMP
#include <linux/threads.h>
/* if NR_CPUS is undefined or 1, need to set it to a more reasonable number */
#define DEFAULT_MAXCPUS 8
#ifndef NR_CPUS
#define NR_CPUS DEFAULT_MAXCPUS
#endif /* NR_CPUS */
#if NR_CPUS == 1 
#define NR_CPUS DEFAULT_MAXCPUS
#endif /* NR_CPUS */
#define CPUSTATES 4   /* idle, user, nice, system */
static long	cp_time[NR_CPUS+1][CPUSTATES];
static long	cp_old[NR_CPUS+1][CPUSTATES];
static int maxcpus=NR_CPUS;

/* 
 * DK_MAX_DISK in <linux/kernel_stat.h> sets the maximum number of disks
 * this kernel keeps statistics on. Unfortunately, including this
 * file with CONFIG_SMP set causes unwanted #include dependancies, so unset
 * it first.  Warning: this resets NR_CPUS to 1; use maxcpus.
 */
#undef CONFIG_SMP 
#include <linux/kernel_stat.h>
#ifndef DK_MAX_DISK
#define DK_MAX_DISK 16	/* pick a reasonable default if not set */
#endif /* DK_MAX_DISK */

#ifndef STATFILE
#define STATFILE "/proc/stat"
#define STATFILE_26 "/proc/diskstats"
#endif /* STATFILE */

#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN 64
#endif /* MAXHOSTNAMELEN */

/* 
 * MAXLINELEN is the maximum line length in STATFILE.  All the
 * disks are on one line in STATFILE, which thus could be very long if 
 * DK_MAX_DISK is large, so set it to a minimum size and bump it up if 
 * DK_MAX_DISK warrants.
 */
#define MAXLINELEN DK_MAX_DISK*128
#if MAXLINELEN < 1024
#define MAXLINELEN 1024
#endif /* MAXLINELEN < 1024 */

#define DISKSTATES 3  /* idle, io_reads, io_writes */

extern void shorten(/* char *hname */);
extern void draw_bar(/*int bar_num, int *states, int num_states*/);

extern int cpuflag;
extern int diskflag;

#define WHITESPACE "	 "
#define CPU "cpu"
#define DISK "disk_io:"

extern char *xmalloc(/* int nbytes */);

static char line[MAXLINELEN];

static int numcpus=0;
static int numdisks=0;
static FILE *proc_stat;
static FILE *proc_diskstats;
static unsigned int d; /* dummy */
static char devicename[10];
static int ret;
static unsigned int v_major, v_index;

/* Flag if we are running >= 2.6 kernel with disk stats in /proc/diskstats */
static int diskstats_26=0;
/*
 * Consider it a real disk if a line in /proc/diskstats:
 * 1) Has 14 fields, and
 * 2) Device name does not start with "ram", and
 * 3) Device name does not end with a digit (like "md0")
 */
#define REAL_DISK26 (ret == 14 && strncmp(devicename, "ram", 3) != 0 && \
                    isalpha(*(devicename+strlen(devicename)-1)))

/* Called by -version */
void
version()
{
	printf("Linux: maxcpu=%d, maxdisk=%d\n", maxcpus, DK_MAX_DISK);
}

/* Called at the beginning to inquire how many bars are needed. */
int
num_bars()
{
	/* 
	 * Assume any line in /proc/stat beginning with cpuN, where N is a
	 * number, corresponds to a specific CPU.  If there are no such lines,
	 * this must be a uniprocessor -- look for the line beginning with 
	 * "cpu ".  If there isn't one of those, there's no recognizable 
	 * evidence of any cpu in /proc/stat, which means something is wrong.
	 */

	int foundcpu=0;  /* we found a line beginning with "cpu " */
	char *p;

	/* flag if we are running >= 2.6 Linux kernel; count disks */
	if(0!=(proc_diskstats=fopen(STATFILE_26, "r"))) {
	  diskstats_26 = 1;
	  if(diskflag) {
	    while(NULL!=fgets(line, sizeof(line)-1, proc_diskstats)){	
  	      ret = sscanf(line, "%d%d%s%d%d%d%d%d%d%d%d%d%d%d", 
		    &d, &d, devicename, &d, &d, &d, &d, &d, &d, &d, &d, &d, &d, &d);
	      if(REAL_DISK26)
		numdisks++;
	    }
	  }
	  (void)fclose(proc_diskstats);
	}

	/* count the number of CPUs mentioned in the stat file */
	if(0>(proc_stat=fopen(STATFILE, "r"))) {
		perror(STATFILE);
		return 0;
	}
	while(NULL!=fgets(line, sizeof(line)-1, proc_stat)){
		if(0==strncmp(line, CPU, strlen(CPU))){
			if(' '==line[strlen(CPU)]){
				/* line looks like "cpu ..." */
				foundcpu=1;
			} else if(isdigit(line[strlen(CPU)])){
				/* line looks like "cpuN ..." */
			        numcpus++;
			}
		}
		else if(diskflag && !diskstats_26 &&
		        0==strncmp(line, DISK, strlen(DISK))){
		  p = &line[strlen(DISK)];
		  while(NULL!=(p=strchr(p, ':'))) {
	      	    numdisks++;
		    p++;
		  }
		  goto DONE;
		}
	}
	DONE:
	(void)fclose(proc_stat);

	/* 
	 * If there is no line beginning with "cpuN" (eg. cpu0), then 
	 * this is a uniprocessor system.
	 */
	if(0==numcpus) {
		numcpus=foundcpu; 
	} 
#ifdef SHOWAVG
	  else {
		numcpus++;
	}
#endif /* SHOWAVG */
	if(numcpus>(maxcpus+1)) numcpus=maxcpus+1;
	if(numdisks>DK_MAX_DISK) numdisks=DK_MAX_DISK;
	return(numcpus+numdisks);
}

/*
 * Indicates how many levels each bar has.  For most machines, each bar will
 * have the same stuff.  But one can, for instance, display memory use on one
 * bar, processor levels on others, etc.
 */
void
bar_items(nbars, items)
int nbars;
int items[];    /* nbars items in this */
{
    int i, n=0;

    for(i = 0; i < numcpus; i++,n++)
        items[i] = CPUSTATES;

    if(diskflag){
        for(i = 0; i < numdisks; i++,n++){
            items[n] = DISKSTATES;
        }
    }

}


/* Called after num_bars to ask for the bar names */
/* ARGSUSED */
char **
label_bars(nbars)
{
    static char **names;
    int i;
    static char hname[MAXHOSTNAMELEN+1];
    char buf[MAXHOSTNAMELEN + 1 + 32];
    unsigned int done, pos;

    hname[MAXHOSTNAMELEN] = '\0';
    if (gethostname(hname, MAXHOSTNAMELEN) < 0) {
	perror("gethostname");
	*hname = '\0';
    }
    shorten(hname);
    names=(char **) xmalloc(nbars * sizeof(char *));
    for(i=0; i < numcpus; i++) {
#define CPUNAME "%s %d"

#ifdef SHOWAVG
	if ( i == 0 )
		(void) sprintf(buf, "CPU");
	else
		(void) sprintf(buf, CPUNAME, hname, i-1);
#else  /* !SHOWAVG */
	(void) sprintf(buf, CPUNAME, hname, i);
#endif /* !SHOWAVG */
    	names[i] = strcpy(xmalloc(strlen(buf) + 1), buf);
    }

    if(diskflag) {
      if(diskstats_26) {
	if((proc_diskstats=fopen(STATFILE_26, "r")) == NULL ) {
           perror(STATFILE_26);
	   exit(1);
	}
	i = numcpus;
	while(NULL!=fgets(line, sizeof(line)-1, proc_diskstats)){	
	  ret = sscanf(line, "%d%d%s%d%d%d%d%d%d%d%d%d%d%d", 
			&v_major, &v_index, devicename, 
			&d, &d, &d, &d, &d, &d, &d, &d, &d, &d, &d);
	  if(REAL_DISK26) {
            (void) sprintf(buf, "disk %s %d-%d", devicename, v_major, v_index);
            names[i++] = strcpy(xmalloc(strlen(buf) + 1), buf);
	  }
	}
	(void)fclose(proc_diskstats);
      }
      else {
	if((proc_stat=fopen(STATFILE,"r")) == NULL ) {
           perror(STATFILE);
	   exit(1);
	}
	/* Get the /proc/stat line with the current disk data */
	done = 0;
	while(!done && NULL!=fgets(line, sizeof(line)-1, proc_stat)){
          if(0==strncmp(line, DISK, strlen(DISK)))
          done = 1;
	}
	(void)fclose(proc_stat);
	pos = 9;
	for(i=numcpus; i < numcpus+numdisks; i++) {
          sscanf(line+pos, "(%u,%u):(%*u,%*u,%*u,%*u,%*u) ",
             &v_major, &v_index);
	  pos += strcspn(line + pos, " ") + 1;
          (void) sprintf(buf, "disk %d-%d", v_major, v_index);
          names[i] = strcpy(xmalloc(strlen(buf) + 1), buf);
      }
    }
  }
  return names;
}

/*
 *  Called after the bars are created to perform any machine dependent
 *  initializations.
 */
/* ARGSUSED */
void
init_bars(nbars)
int nbars;
{
	char dummy[256];
	int i, j;

	if((proc_stat=fopen(STATFILE,"r")) == NULL ) {
		perror(STATFILE);
  		for( i=0; i < nbars; i++)
		    for(j=0;j<CPUSTATES;j++) cp_old[i][j] = 0;
	} else {
#ifndef SHOWAVG
	    if(nbars>1){
		/* skip the first ("cpu") line */
		   fscanf(proc_stat, "%s %ld %ld %ld %ld",
                       dummy, &cp_time[0][0], &cp_time[0][1], &cp_time[0][2],
                       &cp_time[0][3]);
		   /* There is no need to read the entire line here */
	    }
#endif /* SHOWAVG */
            for (i=0; i < nbars; i++) {
                fscanf(proc_stat, "%s %ld %ld %ld %ld \n",
		       dummy, &cp_old[i][0], &cp_old[i][1], &cp_old[i][2],
		       &cp_old[i][3]);
		   /* There is no need to read the entire line here */
	    }
	}
	(void)fclose(proc_stat);
}

/* ARGSUSED */
void
display_cpus(void)
{
	int	states[CPUSTATES];
	int	nstates;
	int	i, done;
	char dummy[256];
	extern void draw_bar(/*int bar_num, int *states, int num_states*/);

        if((proc_stat=fopen(STATFILE,"r")) == NULL ) {
                perror(STATFILE);
        } else {
#ifndef SHOWAVG
	    if(numcpus>1){
		/* skip the first ("cpu") line */
		   fscanf(proc_stat, "%s %ld %ld %ld %ld",
                       dummy, &cp_time[0][0], &cp_time[0][1], &cp_time[0][2],
                       &cp_time[0][3]);
		/* SMP bug fix: skip all chars before newline */
		while ((getc(proc_stat)) != '\n')
		  ;
	    }
#endif /* SHOWAVG */
            for (i=0; i < numcpus; i++)
	    {
                fscanf(proc_stat, "%s %ld %ld %ld %ld",
                       dummy, &cp_time[i][0], &cp_time[i][1], &cp_time[i][2],
                       &cp_time[i][3]);
                /* SMP bug fix: skip all chars before newline */
                while ((getc(proc_stat)) != '\n')
                  ;
	    }
	    if(diskflag && !diskstats_26) {
	        /* get the /proc/stat line with the current disk io data */
		done = 0;
	    	while(!done && NULL!=fgets(line, sizeof(line)-1, proc_stat)){
		  if(0==strncmp(line, DISK, strlen(DISK)))
		    done = 1;
		}
            }
        }
	(void)fclose(proc_stat);

	for (i=0; i < numcpus; i++) {
#define delta(cpustate) ((int) (cp_time[i][(cpustate)] - cp_old[i][(cpustate)]))

		nstates = 0;
		states[nstates++] = delta(3); /* IDLE */
		states[nstates++] = delta(0); /* USER */
		states[nstates++] = delta(1); /* NICE */
		states[nstates++] = delta(2); /* SYS  */
		draw_bar(i, states, nstates);
			cp_old[i][0] = cp_time[i][0];
			cp_old[i][1] = cp_time[i][1];
			cp_old[i][2] = cp_time[i][2];
			cp_old[i][3] = cp_time[i][3];
	}
}

static void
display_disk(int base)
{
  /* Uses disk_io in /proc/stat for 2.4 or /proc/diskstats for 2.6 kernel */
  /* Assume that disks may not be registered or unregistered dynamically */
  int i, idisk, pos=9;
  static unsigned int io_reads[DK_MAX_DISK][2], io_writes[DK_MAX_DISK][2];
  static unsigned int io_ops_max[DK_MAX_DISK];
  static unsigned int curr=0;
  static unsigned int doit[DK_MAX_DISK]; /* initialized to zeros */
  int states[DISKSTATES];

  /* Read disks I/O statistics */
  if(diskstats_26) {
    if((proc_diskstats=fopen(STATFILE_26, "r")) == NULL ) {
      perror(STATFILE_26);
      exit(1);
    }
    idisk = 0;
    while(NULL!=fgets(line, sizeof(line)-1, proc_diskstats)){
      ret = sscanf(line, "%d%d%s%d%d%d%d%d%d%d%d%d%d%d", 
              &v_major, &v_index, devicename, 
 	      &io_reads[idisk][curr], &d, &d, &d, &io_writes[idisk][curr],
	      &d, &d, &d, &d, &d, &d);
      if(REAL_DISK26) {
	states[1] = abs(io_reads[idisk][curr] - io_reads[idisk][!curr]);   /* current io_reads */
	states[2] = abs(io_writes[idisk][curr] - io_writes[idisk][!curr]); /* current io_writes */
	if(doit[idisk] && (states[1]+states[2]) > io_ops_max[idisk])
	  io_ops_max[idisk] = states[1]+states[2];
	states[0] = abs(io_ops_max[idisk] - states[1] - states[2]); /* max - current */
	if(states[0] == 0) /* to prevent garbage from showing */
	  states[0] = 1;
	if(!doit[idisk]) { /* show full "idle" for the first time */
	  states[0] = 1;
	  states[1] = 0;
	  states[2] = 0;
	  io_ops_max[idisk] = 1;
	}
	/*
	printf("DEBUG: disk %d,%d: Calling draw_bar() for disk %d with states: %d,%d,%d\n",
	  v_major, v_index, idisk, states[0], states[1], states[2]);
	*/
	draw_bar(numcpus+idisk, states, 3);
	doit[idisk] = 1;
	/* prepare for the next call */
	idisk++;
      }
    }
    (void)fclose(proc_diskstats);
    curr ^= 1;
  }
  else {
    for (i=numcpus; i < numcpus+numdisks; i++) {
      idisk = i - numcpus;
      sscanf(line+pos, "(%u,%u):(%*u,%u,%*u,%u,%*u) ",
	     &v_major, &v_index, &io_reads[idisk][curr], &io_writes[idisk][curr]);
      pos += strcspn(line + pos, " ") + 1;
      states[1] = abs(io_reads[idisk][curr] - io_reads[idisk][!curr]);   /* current io_reads */
      states[2] = abs(io_writes[idisk][curr] - io_writes[idisk][!curr]); /* current io_writes */
      if(doit[idisk] && (states[1]+states[2]) > io_ops_max[idisk])
	io_ops_max[idisk] = states[1]+states[2];
      states[0] = abs(io_ops_max[idisk] - states[1] - states[2]); /* max - current */
      if(states[0] == 0) /* to prevent garbage from showing */
	states[0] = 1;
      if(!doit[idisk]) { /* show full "idle" for the first time */
	states[0] = 1;
	states[1] = 0;
	states[2] = 0;
	io_ops_max[idisk] = 1;
      }
      /*
      printf("DEBUG: disk %d,%d: Calling draw_bar with %d,%d,%d\n",
	v_major, v_index, states[0], states[1], states[2]);
      */
      draw_bar(i, states, 3);
      doit[idisk] = 1;
    }
    curr ^= 1;  /* prepare for the next call */
  }
}

/* 
 *  This procedure gets called every interval to compute and display the
 *  bars. It should call draw_bar() with the bar number, the array of
 *  integer values to display in the bar, and the number of values in
 *  the array.
 */
/* ARGSUSED */
void
display_bars(int nbars)
{	
	if(cpuflag)
		display_cpus();
	if(diskflag)
		display_disk(numcpus);

}



syntax highlighted by Code2HTML, v. 0.9.1