/* monitor.c -- AIX 3 System monitor 
**
** Copyright (c) 1991, 1992 Jussi Maki. All Rights Reserved.
** NON-COMMERCIAL USE ALLOWED. YOU ARE FREE TO DISTRIBUTE
** THIS PROGRAM AND MODIFY IT AS LONG AS YOU KEEP ORIGINAL
** COPYRIGHTS.
**
** Author: Jussi Maki
**         Helsinki University of Technology
**         Computing Centre
**         Otakaari 1
**         SF-02150 ESPOO
**         FINLAND, EUROPE
**
** Phone:  +358-0-451 4317
** Telefax: +358-0-464 788
**
** Internet-mail: jmaki@hut.fi
**
** Compile: with Makefile
** Install: run either as root or make the program
**          owner root and program setuid.
**            chown root monitor
**            chmod u+s monitor
**
** History:
** created:  15.5.1991 v1.0
** modified: 21.5.1991 v1.0beta    added disk-io display
** modified: 24.5.1991 v1.01       added network interface (ifnet) display
**           29.5.1991 v1.01a dhc: added digit after network interface name 
**           30.5.1991 v1.02       created loadavgd-server
**                                 changed getloadavg() interface
**                                 to bsd-tahoe interface
** modified: 05.7.1991 v1.03       added signal handler
**           08.7.1991 v1.04beta   added top cputimeshower (ala top)
**           09.7.1991 v1.04b1     added putenv("LANG=En_US") for curses bug
**           10.7.1991 v1.04       release of v1.04 (changed behaviour of q)
**           14.7.1991             created ChangeLog-file
**           02.9.1991 v1.05       new version 
**           13.10.1991 v1.05.1    bug fix to vmstat display
**           11.05.1992 v1.06      changed putenv to setlocale, changed sleep to select, etc.
**                                 modifications to runnable for aix3.2
**
*/

#include <curses.h>
#include <stdio.h>
#include <signal.h>
#include <locale.h>

#include <sys/types.h>
#include <sys/select.h>
#include <unistd.h>
#include <fcntl.h>
#include <math.h>
#include <nlist.h>
#include <time.h>
#include <sys/time.h>

#include <sys/sysinfo.h>
#include <sys/vminfo.h>
#include <sys/iostat.h>
/* include files needed for ifnet-networkinterface */
#include <sys/socket.h>
#include <net/if.h>

#include "getloadavg.h"
#include "top.h"

/* vmker struct is kernelstruct (undocumented) */
/* vmker seems to hold some kernels virtual memeory variables */
struct vmker {
  uint n0,n1,n2,n3,n4,n5,n6,n7,n8;
  uint totalmem;
  uint n10;
  uint freemem;
  uint n12,n13;
  uint totalvmem,freevmem;
};

int dk_cnt;  /* number of disks in system  */

#define NUMBER_OF_KNSTRUCTS 5
#define NLIST_SYSINFO 0
#define NLIST_VMKER 1
#define NLIST_VMINFO 2
#define NLIST_IOSTAT 3
#define NLIST_IFNET 4
struct nlist kernelnames[] = {
    {"sysinfo", 0, 0, 0, 0, 0},
    {"vmker", 0, 0, 0, 0, 0},
    {"vmminfo", 0, 0, 0, 0, 0},
    {"iostat", 0, 0, 0, 0, 0},
    {"ifnet", 0, 0, 0, 0, 0},
    {NULL, 0, 0, 0, 0, 0},
    };
#define N_VALUE(index) (kernelnames[index].n_value)

int sleep_sec=10;

double realtime();

#define MAX_IFNETS 16
#define MAX_IPNAME 32

#define MONITOR_NAME "System monitor v1.06beta"

sighandler(signum)
int signum;
{
  switch (signum) {
  case SIGTSTP:
    nocbreak();
    endwin();
    kill(getpid(),SIGSTOP);
    /* returned here from sigstop */
    cbreak();
    initscr();
    clear();
    break;
  case SIGINT:
    nocbreak();
    endwin();
    exit(0);
    break;
  default: fprintf(stderr,"unknown signal %d\n",signum); fflush(stderr);
    nocbreak();
    endwin();
    exit(0);
  }
  signal(signum,sighandler);
}

int end_monitor=0;

extern int topflag_usersystemtime;

main(argc,argv)
int argc;
char **argv;
{
    struct sysinfo si1,si2;
    struct vminfo vm1,vm2;
    struct vmker vmk;
    struct dkstat *dk1,*dk2;
    double time1,time2;
    struct ifnet *ifnets1[MAX_IFNETS];
    struct ifnet *ifnets2[MAX_IFNETS];

    int ntop = 17; /* top procs to show by default */
    int top_nproc1,top_nproc2;
    double top_cpusum;
    int show_all=0,show_top=0;
    int columns,lines;

    setlocale(LC_NUMERIC,"C"); /* be sure that decimal-point is "." */
    getscreensize(&columns,&lines);
    ntop = lines - 7;
    if (columns > 94) topflag_usersystemtime=1; 
    if (columns == 0) columns=80;
    if (lines == 0) lines=24;

    signal(SIGTSTP,sighandler);
    signal(SIGINT,sighandler);
    signal(SIGQUIT,sighandler);

    clear_ifnets(ifnets1,MAX_IFNETS);
    clear_ifnets(ifnets2,MAX_IFNETS);
    while (argc>1) {
      if (argv[1][0] == '-') {
	switch(argv[1][1]) {
	case 's':
	  sleep_sec = atoi(argv[2]); argc--;argv++;
	  if (sleep_sec < 1) sleep_sec=1;
	  break;
	case 'a': /* -all */
	  show_all = 1;
	  show_top = 1;
	  break;
	case 't': /* -top [nproc] */
	  show_top = 1;
	  if (atoi(argv[2])>1) {
	    ntop=atoi(argv[2]);
	    argc--;argv++;
	  }
	  break;
	default:
	  fprintf(stderr,"Usage: monitor [-s sleep] [-top [nproc]] [-all]\n");
	  fprintf(stderr,"Monitor is a curses-based AIX3 system event monitor\n");
	  fprintf(stderr,"     -s sleep      set refresh time\n");
	  fprintf(stderr,"     -top [nproc]  show top processes sorted by cpu-usage\n");
	  fprintf(stderr,"     -all          show all variable (needs high window)\n");
	  exit(0);
	  break;
	}
      }
      argv++;argc--;
    }
	  
    if (knlist(kernelnames,NUMBER_OF_KNSTRUCTS,sizeof(struct nlist)) == -1){
	perror("knlist, entry not found");
    }

    initscr();
    clear();
    cbreak();

    /* 0--let get_dkstat allocate memeory, 1--do not allocate */
    get_dkstat(&dk1,0); 
    get_dkstat(&dk2,0);
    get_sys_vm_info(&si2,&vmk,&vm2);
    get_ifnet(ifnets2); /* gets and initalizes ifnets-list automativally */
    if (show_top) 
      top_nproc1 = top_getprocinfo(top_proc1,top_user1);
    time2=realtime();
    sleep(1);

    while (! end_monitor) {
	get_sys_vm_info(&si1,&vmk,&vm1);
	if (!(show_top&&!show_all)) {
	  get_dkstat(&dk1,1); get_ifnet(ifnets1);
	}
	if (show_top) {
	  top_nproc2 = top_getprocinfo(top_proc2,top_user2);
	  top_cpusum = top_calcsortinfo(top_proc1,top_user1,top_nproc1,
				  top_proc2,top_user2,top_nproc2,top_sortinfo);
	}
	time1=realtime();
	checkinput();
	if (show_top&&!show_all) {
	  print_summary(time1-time2,&si1,&si2,&vmk);
	} else {
	  print_sysinfo(time1-time2,&si1,&si2,&vmk,&vm1,&vm2);
	  print_ifnet(time1-time2,ifnets1,ifnets2);
	  print_dkstat(time1-time2,dk1,dk2);
	} 
	if (show_top) {
	  top_print(ntop,top_proc2,top_user2,top_sortinfo,top_nproc2,top_cpusum);
	}
	move(0,0);
	refresh();
	wait_input_or_sleep(sleep_sec);

	get_sys_vm_info(&si2,&vmk,&vm2);
	if (!(show_top&&!show_all)) {
	  get_dkstat(&dk2,1); get_ifnet(ifnets2);
	}
	if (show_top) {
	  top_nproc1 = top_getprocinfo(top_proc1,top_user1);
	  top_cpusum = top_calcsortinfo(top_proc2,top_user2,top_nproc2,
				      top_proc1,top_user1,top_nproc1,top_sortinfo);
	}
	time2=realtime();
	checkinput();
	if (show_top&&!show_all) {
	  print_summary(time2-time1,&si2,&si1,&vmk);
	} else {
	  print_sysinfo(time2-time1,&si2,&si1,&vmk,&vm2,&vm1);
	  print_ifnet(time2-time1,ifnets2,ifnets1);
	  print_dkstat(time2-time1,dk2,dk1);
	} 
	if (show_top) {
	  top_print(ntop,top_proc1,top_user1,top_sortinfo,top_nproc1,top_cpusum);
	}
	move(0,0);
	refresh();
	wait_input_or_sleep(sleep_sec);
    }
    endwin();
}

/* wait for terminal input with secs-timeout */
wait_input_or_sleep(secs)
int secs;
{
  fd_set input_fd;
  struct timeval timeout;

  FD_ZERO(&input_fd);
  FD_SET(fileno(stdin),&input_fd);
  timeout.tv_sec=secs; timeout.tv_usec=0;
  select(fileno(stdin)+1,&input_fd,0,0,&timeout);
}

getscreensize(cols,rows)
int *cols,*rows;
{
  *cols = atoi(termdef(fileno(stdout),'c')); /* c like columns */
  *rows = atoi(termdef(fileno(stdout),'l')); /* l like lines */
}

double realtime()
{
  struct timeval tp;
  gettimeofday(&tp,0);
  return((double)tp.tv_sec+tp.tv_usec*1.0e-6);
}

quit_monitor()
{
  nocbreak();
  endwin();
  exit(0);
}

/* checkinput is the subroutine to handle user input */
checkinput()
{
  char inbuf[1024];
  int nbytes;
  int inbytes;

  if ((nbytes=bytesreadable(fileno(stdin))) > 0) {
    inbytes = read(fileno(stdin),inbuf,nbytes);
    if (inbuf[0]=='q' || inbytes == 0 || inbytes == -1) {
      quit_monitor();
    }
    if (inbuf[0]==12) { /* cltr-L ^L */
      clear();
    }
  }
}

int bytesreadable(in)
int in;
{
  static int bytes;
  ioctl(in,FIONREAD,&bytes);
  return(bytes);
}

/**********************************************************************/

#define BARLEN 72
#define SIDELTA(a) (si->a - si2->a)
#define VMDELTA(a) (vm->a - vm2->a)

print_sysinfo(refresh_time,si,si2,vmk,vm,vm2)
double refresh_time;
struct sysinfo *si,*si2;
struct vmker *vmk;
struct vminfo *vm,*vm2;
{
    double cpu_sum;
    double str_cpu_sum;
    double swp_proc;
    double runnable,runque_tmp, runocc_tmp;
    char bar[BARLEN];
    time_t time1;
    double loadv[3];
    char hostnm[128];
    int x,y;

    gethostname(hostnm,sizeof(hostnm));
    time1=time(0);
    move(0,0); printw("%s: %s",MONITOR_NAME,hostnm);
    move(0,50);printw(ctime(&time1));
    move(1,50);printw("Refresh: %5.2f s",refresh_time);
    cpu_sum = SIDELTA(cpu[CPU_IDLE]) + SIDELTA(cpu[CPU_USER]) 
      + SIDELTA(cpu[CPU_KERNEL]) + SIDELTA(cpu[CPU_WAIT]);
    str_cpu_sum = cpu_sum/(double)BARLEN;
    cpu_sum = cpu_sum/100.0;

    move(2,0);
    printw("Sys %4.1lf%% Wait %4.1lf%% User %4.1lf%% Idle %4.1lf%%\n",
	   SIDELTA(cpu[CPU_KERNEL])/cpu_sum,
	   SIDELTA(cpu[CPU_WAIT])/cpu_sum,
	   SIDELTA(cpu[CPU_USER])/cpu_sum,
	   SIDELTA(cpu[CPU_IDLE])/cpu_sum);
    printw("0%%             25%%              50%%               75%%              100%%\n");
    bar[0]=0;
    strchgen(bar,'=',(int)(SIDELTA(cpu[CPU_KERNEL])/str_cpu_sum));
    strchgen(bar,'W',(int)(SIDELTA(cpu[CPU_WAIT])/str_cpu_sum));
    strchgen(bar,'>',(int)(SIDELTA(cpu[CPU_USER])/str_cpu_sum));
    strchgen(bar,'.',(int)(SIDELTA(cpu[CPU_IDLE])/str_cpu_sum));
    move(4,0); clrtoeol();
    move(4,0); printw("%s",bar);

    getloadavg(loadv,3);
    move(6,0);
    runque_tmp = (double) SIDELTA(runque);
    runocc_tmp = (double) SIDELTA(runocc);
    if(runocc_tmp==0.0)
      runnable=0.0;
    else
      runnable=runque_tmp/runocc_tmp-1.0;
    printw("Runnable processes %5.2lf load average: %5.2lf, %5.2lf, %5.2lf\n",
	  (runnable+1.0)*SIDELTA(runocc)/refresh_time, loadv[0],loadv[1],loadv[2]);

    if (SIDELTA(swpocc) == 0) swp_proc=0.0;
    else swp_proc=(double)SIDELTA(swpque)/SIDELTA(swpocc);
    printw("Swap-in processes   %4.2f\n",swp_proc);

    x=0;y=9;
    move(y+0,x);printw("Memory    Real     Virtual");
    move(y+1,x);printw("free    %5.1lf MB  %5.1lf MB",
	   vmk->freemem*4/1024.0,vmk->freevmem*4/1024.0);
    move(y+2,x);printw("total   %5.1lf MB  %5.1lf MB",
	   vmk->totalmem*4/1024.0,vmk->totalvmem*4/1024.0);

    x=32;y=8;
    move(y+0,x);printw("Paging (4kB)");
    move(y+1,x);printw("%5.1f pgfaults",VMDELTA(pgexct)/refresh_time);
    move(y+2,x);printw("%5.1f pgin",    VMDELTA(pageins)/refresh_time);
    move(y+3,x);printw("%5.1f pgout",   VMDELTA(pageouts)/refresh_time);
    move(y+4,x);printw("%5.1f pgsin",   VMDELTA(pgspgins)/refresh_time);
    move(y+5,x);printw("%5.1f pgsout",  VMDELTA(pgspgouts)/refresh_time);

    x=48;y=8;
    move(y+0,x);printw("Process events");
    move(y+1,x);printw("%5.0f pswitch", SIDELTA(pswitch)/refresh_time);
    move(y+2,x);printw("%5.0f syscall", SIDELTA(syscall)/refresh_time);
    move(y+3,x);printw("%5.0f read",    SIDELTA(sysread)/refresh_time);
    move(y+4,x);printw("%5.0f write",   SIDELTA(syswrite)/refresh_time);
    move(y+5,x);printw("%5.0f fork",    SIDELTA(sysfork)/refresh_time);
    move(y+6,x);printw("%5.0f exec",    SIDELTA(sysexec)/refresh_time);
    move(y+7,x);printw("%5.0f rcvint",  SIDELTA(rcvint)/refresh_time);
    move(y+8,x);printw("%5.0f xmtint",  SIDELTA(xmtint)/refresh_time);
    move(y+9,x);printw("%5.0f mdmint",  SIDELTA(mdmint)/refresh_time);
    
    x=62;y=8;
    move(y+0,x);printw("   File/TTY-IO");
    move(y+1,x);printw("%7d iget",     (int)(SIDELTA(iget)/refresh_time));
    move(y+2,x);printw("%7d namei",    (int)(SIDELTA(namei)/refresh_time));
    move(y+3,x);printw("%7d dirblk",   (int)(SIDELTA(dirblk)/refresh_time));
    move(y+4,x);printw("%7d readch",   (int)(SIDELTA(readch)/refresh_time));
    move(y+5,x);printw("%7d writech",  (int)(SIDELTA(writech)/refresh_time));
    move(y+6,x);printw("%7d ttyrawch", (int)(SIDELTA(rawch)/refresh_time));
    move(y+7,x);printw("%7d ttycanch", (int)(SIDELTA(canch)/refresh_time));
    move(y+8,x);printw("%7d ttyoutch", (int)(SIDELTA(outch)/refresh_time));
}

print_summary(refresh_time,si,si2,vmk,vm,vm2)
double refresh_time;
struct sysinfo *si,*si2;
struct vmker *vmk;
struct vminfo *vm,*vm2;
{
  time_t time1;
  double loadv[3];
  char hostnm[128];
  double cpu_sum;

  /* hostname loadavg date-time */
  /* cpu-states */
  /* memory-states */
  gethostname(hostnm,sizeof(hostnm));
  getloadavg(loadv,3);
  time1=time(0);
  cpu_sum = SIDELTA(cpu[CPU_IDLE]) + SIDELTA(cpu[CPU_USER]) 
      + SIDELTA(cpu[CPU_KERNEL]) + SIDELTA(cpu[CPU_WAIT]);
  cpu_sum = cpu_sum/100.0;
  move(0,0); printw("%s",hostnm);
  move(0,17);printw("load averages: %5.2lf, %5.2lf, %5.2lf",loadv[0],loadv[1],loadv[2]);
  move(0,55);printw(ctime(&time1));
  move(1,0);
  printw("Cpu states:      %4.1lf%% user, %4.1lf%% system, %4.1lf%% wait, %4.1lf%% idle\n",
	   SIDELTA(cpu[CPU_USER])/cpu_sum,
	   SIDELTA(cpu[CPU_KERNEL])/cpu_sum,
	   SIDELTA(cpu[CPU_WAIT])/cpu_sum,
	   SIDELTA(cpu[CPU_IDLE])/cpu_sum);
  printw("Real memory:    %5.1lfM used, %5.1lfM free, %5.1lfM total\n",
	 (vmk->totalmem-vmk->freemem)*4/1024.0, vmk->freemem*4/1024.0,
	 vmk->totalmem*4/1024.0);
  printw("Virtual memory: %5.1lfM used, %5.1lfM free, %5.1lfM total\n",
	 (vmk->totalvmem-vmk->freevmem)*4/1024.0, vmk->freevmem*4/1024.0,
	 vmk->totalvmem*4/1024.0);
  printw("\n");
}

#define DKiDELTA(i,a)  ((dk1[i].a) - (dk2[i].a))

print_dkstat(refresh_time,dk1,dk2)
double refresh_time;
struct dkstat *dk1,*dk2;
{
    int i;
    int x,y;
    
    x=0;y=13;
    move(y+0,x);
    printw("DiskIO   read    write   busy");
    for (i=0; i<dk_cnt; i++) {
	move(y+1+i,x);
	printw("%-7s %5.1f %5.1f kB/s %3.0f%%",
	       dk1[i].diskname,
	       DKiDELTA(i,dk_rblks)*dk1[i].dk_bsize/1024.0/refresh_time,
	       DKiDELTA(i,dk_wblks)*dk1[i].dk_bsize/1024.0/refresh_time,
	       DKiDELTA(i,dk_time)/refresh_time);
    }
    move(y+9+i,x);
}

#define IFDELTA(i,a) ((if1[i]->a) - (if2[i]->a))

print_ifnet(refresh_time,if1,if2)
double refresh_time;
struct ifnet *if1[],*if2[];
{
    int i;
    int x,y;

    i=0;
    x=52;y=19;
    move(y+0,x); printw("Netw  read   write");
    while (if1[i]) {
	move(y+1+i,x);
	printw("%-2s%1d  %5.1f %5.1f kB/s",
	       if1[i]->if_name,
	       if1[i]->if_unit,
#ifdef AIXv3r1
	       IFDELTA(i,ifInOctets)/1024.0/refresh_time,
	       IFDELTA(i,ifOutOctets)/1024.0/refresh_time);
#else
	       IFDELTA(i,if_ibytes)/1024.0/refresh_time,
	       IFDELTA(i,if_obytes)/1024.0/refresh_time);
#endif
	i++;
    }
}

/* generate 'len' characters 'ch' in the end of string 'str' */
strchgen(str,ch,len)
char *str;
char ch;
int len;
{
  while (*str != 0) str++;
  while (len) { len--; *str++ = ch; }
  *str = 0;
}


/****************************************************************************/
/* get_sys_vm_info(struct sysinfo *si,struct vker *vmk, struct vminfo *vm); */
/* get_dkstat(struct dkstat **dk, int initted);  */
/* get_ifnet(struct ifnet *ifnets[]); */
/****/

get_sys_vm_info(si,vmk,vm)
struct sysinfo *si;
struct vmker *vmk;
struct vminfo *vm;
{
    /*
    ** Get the system info structure from the running kernel.
    ** Get the kernel virtual memory vmker structure
    ** Get the kernel virtual memory info structure
    */
    getkmemdata(si,sizeof(struct sysinfo),N_VALUE(NLIST_SYSINFO));
    getkmemdata(vmk,sizeof(struct vmker),N_VALUE(NLIST_VMKER));
    getkmemdata(vm,sizeof(struct vminfo),N_VALUE(NLIST_VMINFO));
}


get_dkstat(dk,initted)
struct dkstat **dk;
int initted;
{
    struct iostat iostat;
    int i;

    getkmemdata(&iostat,sizeof(struct iostat),N_VALUE(NLIST_IOSTAT));
    if (!initted) {
	dk_cnt = iostat.dk_cnt;
	*dk = malloc(sizeof(struct dkstat)*iostat.dk_cnt);
    }
    getkmemdata(&(*dk)[0],sizeof(struct dkstat), iostat.dkstatp);
    for (i=1; i<dk_cnt; i++) {
	getkmemdata(&(*dk)[i],sizeof(struct dkstat), (*dk)[i-1].dknextp);
    }
}

clear_ifnets(ifnets,indexnum)
struct ifnet *ifnets[];
int indexnum;
{
  int i;

  for (i=0; i<indexnum; i++) {
    ifnets[i] = 0;
  }
}

get_ifnet(ifnets)
struct ifnet *ifnets[]; /* pointer to array of struct ifnet pointers */
{
    static int initted;
    static long ifnet_addr_first;
    static char *ifnets_names[MAX_IFNETS];
    char **ifnames;
    long ifnet_next;

    ifnames = ifnets_names;
    if (!initted) {
	initted=1;
	getkmemdata(&ifnet_addr_first,sizeof(long),N_VALUE(NLIST_IFNET));
    }
    ifnet_next = ifnet_addr_first;

    /* index to ifnets[0]-value is used, (*ifnets) could
       be used instead of it */
    while (ifnet_next) {
	if (!ifnets[0]) ifnets[0] = malloc(sizeof(struct ifnet));
	getkmemdata(ifnets[0],sizeof(struct ifnet),ifnet_next);
	if (!ifnames[0]) ifnames[0]=malloc(MAX_IPNAME);
	getkmemdata(ifnames[0],sizeof(MAX_IPNAME), ifnets[0]->if_name);
	ifnets[0]->if_name = ifnames[0];
	ifnet_next = ifnets[0]->if_next;
	ifnets++;  /* lets increment ifnets ** so that index 0 is always 
		      first one in list */
	ifnames++; /* same for ipnames */
    }
}


/*********************************************************************/
getkmemdata(buf,bufsize,n_value)
char *buf;      /* buffer for kernel data*/
int bufsize;    /* buffers size */
long n_value;    /* seek address to kernels data */
{
    static int fd;
    static initted = 0;
    /*
    ** Do stuff we only need to do once per invocation, like opening
    ** the kmem file and fetching the parts of the symbol table.
    */
    if (!initted) {
	initted = 1;
	fd = open("/dev/kmem", O_RDONLY);
	if (fd < 0) {
	    perror("kmem");
	    exit(1);
	}
    }
    /*
    ** Get the structure from the running kernel.
    */
    lseek(fd, n_value, SEEK_SET);
    read(fd, buf, bufsize);
}

/******* enf of monitor.c **********************************/
