/*
 * top - a top users display for Unix
 *
 * SYNOPSIS:  PowerPC running AIX 5.1 or higher
 *
 * DESCRIPTION:
 * This is the machine-dependent module for AIX 5.1 and higher (may work on
 * older releases too).  It is currently only tested on PowerPC
 * architectures.
 *
 * TERMCAP: -lcurses
 *
 * CFLAGS: -DORDER -DHAVE_GETOPT -DHAVE_STRERROR -DMAXPROCS=10240
 *
 * LIBS: -lperfstat
 *
 * AUTHOR:  Joep Vesseur <joep@fwi.uva.nl>
 *
 * PATCHES: Antoine Tabary <tabary@bruyeres.cea.fr>, Dan Nelson <dnelson@allantgroup.com>
 */

#define MAXPROCS 10240

#include "config.h"

#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <nlist.h>
#include <procinfo.h>
#include <sys/types.h>
#include <sys/proc.h>
#include <sys/sysinfo.h> 
#include <sys/sysconfig.h>
#include <pwd.h>
#include <errno.h>
#include <libperfstat.h>
#include "top.h"
#include "machine.h"
#include "utils.h"


#define PROCRESS(p) (((p)->pi_trss + (p)->pi_drss)*4)
#define PROCSIZE(p) (((p)->pi_tsize/1024+(p)->pi_dvm)*4)
#define PROCTIME(pi) (pi->pi_ru.ru_utime.tv_sec + pi->pi_ru.ru_stime.tv_sec)

#ifdef OLD
/*
 * structure definition taken from 'monitor' by Jussi Maki (jmaki@hut.fi)
 */
struct vmker {
    uint n0,n1,n2,n3,n4,n5,n6,n7,n8;
    uint totalmem;
    uint badmem; /* this is used in RS/6000 model 220 */
    uint freemem;
    uint n12;
    uint numperm;   /* this seems to keep other than text and data segment 
                       usage; name taken from /usr/lpp/bos/samples/vmtune.c */
    uint totalvmem,freevmem;
    uint n15, n16, n17, n18, n19;
};

#define KMEM "/dev/kmem"

/* Indices in the nlist array */
#define X_AVENRUN       0
#define X_SYSINFO       1
#define X_VMKER         2
#define X_V             3

static struct nlist nlst[] = {
    { "avenrun", 0, 0, 0, 0, 0 }, /* 0 */
    { "sysinfo", 0, 0, 0, 0, 0 }, /* 1 */
    { "vmker",   0, 0, 0, 0, 0 }, /* 2 */
    { "v",       0, 0, 0, 0, 0 }, /* 3 */
    {  NULL, 0, 0, 0, 0, 0 }
};

#endif

/* get_process_info returns handle. definition is here */
struct handle
{
	struct procentry64 **next_proc;
	int remaining;
};

/*
 *  These definitions control the format of the per-process area
 */
static char header[] =
  "   PID X        PRI NICE   SIZE   RES STATE   TIME   WCPU    CPU COMMAND";
/* 0123456   -- field to fill in starts at header+6 */
#define UNAME_START 7

#define Proc_format \
	"%6d %-8.8s %3d %4d %5d%c %4d%c %-5s %6s %5.2f%% %5.2f%% %.14s%s"


/* these are for detailing the process states */
int process_states[9];
char *procstatenames[] = {
    " none, ", " sleeping, ", " state2, ", " runnable, ",
    " idle, ", " zombie, ", " stopped, ", " running, ", " swapped, ",
    NULL
};

/* these are for detailing the cpu states */
int cpu_states[CPU_NTIMES];
char *cpustatenames[] = {
    "idle", "user", "kernel", "wait",
    NULL
};

/* these are for detailing the memory statistics */
long memory_stats[7];
char *memorynames[] = {
    "K total, ", "K buf, ", "K sys, ", "K free", NULL
};
#define M_REAL		0
#define M_BUFFERS	1
#define M_SYSTEM	2
#define M_REALFREE	3

long swap_stats[3];
char *swapnames[] = {
    "K total, ", "K free", NULL
};
#define M_VIRTUAL 0
#define M_VIRTFREE 1

char *state_abbrev[] = {
    NULL, NULL, NULL, NULL, "idle", "zomb", "stop", "run", "swap"
};

/* sorting orders. first is default */
char *ordernames[] = {
    "cpu", "size", "res", "time", "pri", NULL
};

/* compare routines */
int compare_cpu(), compare_size(), compare_res(), compare_time(), 
    compare_prio();

int (*proc_compares[])() = {
    compare_cpu,
    compare_size,
    compare_res,
    compare_time,
    compare_prio,
    NULL
};

/* useful externals */
long percentages(int cnt, int *out, long *new, long *old, long *diffs);
char *format_time(long seconds);

#ifdef OLD
/* useful globals */
int kmem;			/* file descriptor */

/* offsets in kernel */
static unsigned long avenrun_offset;
static unsigned long sysinfo_offset;
static unsigned long vmker_offset;
static unsigned long v_offset;
#endif

/* used for calculating cpu state percentages */
static long cp_time[CPU_NTIMES];
static long cp_old[CPU_NTIMES];
static long cp_diff[CPU_NTIMES];

/* the runqueue length is a cumulative value. keep old value */
long old_runque;

/* process info */
struct kernvars v_info;		/* to determine nprocs */
int nprocs;			/* maximum nr of procs in proctab */
int ncpus;			/* nr of cpus installed */

struct procentry64 *p_info;	/* needed for vm and ru info */
struct procentry64 **pref;	/* processes selected for display */
struct timeval64 *cpu_proc, *old_cpu_proc; /* total cpu used by each process */
int pref_len;			/* number of processes selected */

/* needed to calculate WCPU */
unsigned long curtime;

/* needed to calculate CPU */
struct timeval curtimeval;
struct timeval lasttimeval;

#ifdef OLD
int getkval(unsigned long offset, caddr_t ptr, int size, char *refstr);
#endif

void *xmalloc(long size)
{
	void *p = malloc(size);
	if (!p)
	{
		fprintf(stderr,"Could not allocate %ld bytes: %s\n", size, strerror(errno));
		exit(1);
	}
	return p;
}

/*
 * Initialize globals, get kernel offsets and stuff...
 */
int machine_init(statics)
    struct statics *statics;
{
#ifdef OLD
    if ((kmem = open(KMEM, O_RDONLY)) == -1) {
	perror(KMEM);
	return -1;
    }

    /* get kernel symbol offsets */
    if (knlist(nlst, 4, sizeof(struct nlist)) != 0) {
	perror("knlist");
	return -1;
    }
    avenrun_offset = nlst[X_AVENRUN].n_value;
    sysinfo_offset = nlst[X_SYSINFO].n_value;
    vmker_offset   = nlst[X_VMKER].n_value;
    v_offset       = nlst[X_V].n_value;

    getkval(v_offset, (caddr_t)&v_info, sizeof v_info, "v"); 
#else
	sysconfig(SYS_GETPARMS, &v_info, sizeof v_info);
#endif
    ncpus = v_info.v_ncpus;	/* number of cpus */

/* procentry64 is 4912 bytes, and PROCMASK(PIDMAX) is 262144.  That'd
   require 1.2gb for the p_info array, which is way overkill.  Raise
   MAXPROCS if you have more than 10240 active processes in the system.
*/

#if 0
    nprocs = PROCMASK(PIDMAX);
#else
    nprocs = MAXPROCS;
#endif

    cpu_proc = (struct timeval64 *)xmalloc(PROCMASK(PIDMAX) * sizeof (struct timeval64));
    old_cpu_proc = (struct timeval64 *)xmalloc(PROCMASK(PIDMAX) * sizeof (struct timeval64));
    p_info = (struct procentry64 *)xmalloc(nprocs * sizeof (struct procentry64));
    pref = (struct procentry64 **)xmalloc(nprocs * sizeof (struct procentry64 *));

    statics->procstate_names = procstatenames;
    statics->cpustate_names = cpustatenames;
    statics->memory_names = memorynames;
    statics->swap_names = swapnames;
    statics->order_names = ordernames;

    return(0);
}

char *format_header(uname_field)
    register char *uname_field;
{
    register char *ptr;

    ptr = header + UNAME_START;
    while (*uname_field != '\0')
    {
	*ptr++ = *uname_field++;
    }

    return(header);
}




void get_system_info(si)
    struct system_info *si;
{
#ifdef OLD
    long long load_avg[3];
    struct sysinfo64 s_info;
    struct vmker m_info;
#else
    perfstat_memory_total_t m_info1;
    perfstat_cpu_total_t s_info1;
#endif
    int i;
    int total = 0;

#ifdef OLD
    /* get the load avarage array */
    getkval(avenrun_offset, (caddr_t)load_avg, sizeof load_avg, "avenrun");

    /* get the sysinfo structure */
    getkval(sysinfo_offset, (caddr_t)&s_info, sizeof s_info, "sysinfo64");

    /* get vmker structure */
    getkval(vmker_offset, (caddr_t)&m_info, sizeof m_info, "vmker");
#else
    /* cpu stats */
    perfstat_cpu_total(NULL, &s_info1, sizeof s_info1, 1);

    /* memory stats */
    perfstat_memory_total(NULL, &m_info1, sizeof m_info1, 1);
#endif


#ifdef OLD
    /* convert load avarages to doubles */
    for (i = 0; i < 3; i++)
	si->load_avg[i] = (double)load_avg[i]/65536.0;

    /* calculate cpu state in percentages */
    for (i = 0; i < CPU_NTIMES; i++) {
	cp_old[i] = cp_time[i];
	cp_time[i] = s_info.cpu[i];
	cp_diff[i] = cp_time[i] - cp_old[i];
	total += cp_diff[i];
    }

#else
    /* convert load avarages to doubles */
    for (i = 0; i < 3; i++)
        si->load_avg[i] = (double)s_info1.loadavg[i]/(1<<SBITS);

    /* calculate cpu state in percentages */
    for (i = 0; i < CPU_NTIMES; i++) {
	cp_old[i] = cp_time[i];
	cp_time[i] = (	i==CPU_IDLE?s_info1.idle:
			i==CPU_USER?s_info1.user:
			i==CPU_KERNEL?s_info1.sys:
			i==CPU_WAIT?s_info1.wait:0);
	cp_diff[i] = cp_time[i] - cp_old[i];
	total += cp_diff[i];
    }
#endif
    for (i = 0; i < CPU_NTIMES; i++) {
        cpu_states[i] = 1000 * cp_diff[i] / total; 
    }

    /* calculate memory statistics, scale 4K pages */
#ifdef OLD
#define PAGE_TO_MB(a) ((a)*4/1024)
    memory_stats[M_TOTAL]    = PAGE_TO_MB(m_info.totalmem+m_info.totalvmem);
    memory_stats[M_REAL]     = PAGE_TO_MB(m_info.totalmem);
    memory_stats[M_REALFREE] = PAGE_TO_MB(m_info.freemem);
    memory_stats[M_BUFFERS]  = PAGE_TO_MB(m_info.numperm);
    swap_stats[M_VIRTUAL]  = PAGE_TO_MB(m_info.totalvmem);
    swap_stats[M_VIRTFREE] = PAGE_TO_MB(m_info.freevmem);
#else
#define PAGE_TO_KB(a) ((a)*4)
    memory_stats[M_REAL] = PAGE_TO_KB(m_info1.real_total);
    memory_stats[M_BUFFERS] = PAGE_TO_KB(m_info1.numperm);
#ifdef _AIXVERSION_520
    memory_stats[M_SYSTEM] = PAGE_TO_KB(m_info1.real_system);
#endif
    memory_stats[M_REALFREE] = PAGE_TO_KB(m_info1.real_free);
    swap_stats[M_VIRTUAL] = PAGE_TO_KB(m_info1.pgsp_total);
    swap_stats[M_VIRTFREE] = PAGE_TO_KB(m_info1.pgsp_free);
#endif

    /* runnable processes */
#ifdef OLD
    process_states[0] = s_info.runque - old_runque;
    old_runque = s_info.runque;
#else
    process_states[0] = s_info1.runque - old_runque;
    old_runque = s_info1.runque;
#endif

    si->cpustates = cpu_states;
    si->memory = memory_stats;
    si->swap = swap_stats;
}

static struct handle handle;

caddr_t get_process_info(si, sel, compare_index)
    struct system_info *si;
    struct process_select *sel;
    int compare_index;
{
    int i, nproc;
    int active_procs = 0, total_procs = 0;
    struct procentry64 *pp, **p_pref = pref;
    struct timeval64 *cpu_proc_temp;
    double timediff;
    pid_t procsindex = 0;

    si->procstates = process_states;

    curtime = time(0);
    lasttimeval = curtimeval;
    gettimeofday(&curtimeval, NULL);

    /* get the procentry64 structures of all running processes */
    nproc = getprocs64(p_info, sizeof (struct procentry64), NULL, 0, 
                       &procsindex, nprocs);
    if (nproc < 0) {
	perror("getprocs64");
	quit(1);
    }

    /* the swapper has no cmd-line attached */
    strcpy(p_info[0].pi_comm, "swapper");

    if (lasttimeval.tv_sec)
    {
        timediff = (curtimeval.tv_sec - lasttimeval.tv_sec) +
                   1.0*(curtimeval.tv_usec - lasttimeval.tv_usec) / uS_PER_SECOND;
    }

    /* The pi_cpu value is wildly inaccurate.  The maximum value is 120, but
       when the scheduling timer fires, the field is zeroed for all
       processes and ramps up over a short period of time.  Instead of using
       this weird number, manually calculate an accurate value from the
       rusage data.  Store this run's rusage in cpu_proc[pid], and subtract
       from old_cpu_proc.
    */
    for (pp = p_info, i = 0; i < nproc; pp++, i++) {
        pid_t pid = PROCMASK(pp->pi_pid);
        
        /* total system and user time into cpu_proc */
        cpu_proc[pid] = pp->pi_ru.ru_utime;
        cpu_proc[pid].tv_sec += pp->pi_ru.ru_stime.tv_sec;
        cpu_proc[pid].tv_usec += pp->pi_ru.ru_stime.tv_usec;
        if (cpu_proc[pid].tv_usec > NS_PER_SEC) {
            cpu_proc[pid].tv_sec++;
            cpu_proc[pid].tv_usec -= NS_PER_SEC;
        }

        /* If this process was around during the previous update, calculate
           a true %CPU.  If not, convert the kernel's cpu value from its
           120-max value to a 10000-max one.
        */ 
        if (old_cpu_proc[pid].tv_sec == 0 && old_cpu_proc[pid].tv_usec == 0)
            pp->pi_cpu = pp->pi_cpu * 10000 / 120;
        else
            pp->pi_cpu = ((cpu_proc[pid].tv_sec - old_cpu_proc[pid].tv_sec) +
                         1.0*(cpu_proc[pid].tv_usec - old_cpu_proc[pid].tv_usec) / NS_PER_SEC) / timediff * 10000;
    }
    
    /* remember our current values as old_cpu_proc, and zero out cpu_proc
       for the next update cycle */
    memset(old_cpu_proc, 0, sizeof(struct timeval64) * nprocs);
    cpu_proc_temp = cpu_proc;
    cpu_proc = old_cpu_proc;
    old_cpu_proc = cpu_proc_temp;

    memset(process_states, 0, sizeof process_states);

    /* build a list of pointers to processes to show. */
    for (pp = p_info, i = 0; i < nproc; pp++, i++) {

	/* AIX marks all runnable processes as ACTIVE. We want to know
	   which processes are sleeping, so check used cpu and adjust status
	   field accordingly
	 */
	if (pp->pi_state == SACTIVE && pp->pi_cpu == 0)
	    pp->pi_state = SIDL;

        if (pp->pi_state && (sel->system || ((pp->pi_flags & SKPROC) == 0))) {
	    total_procs++;
	    process_states[pp->pi_state]++;
	    if ( (pp->pi_state != SZOMB) &&
		(sel->idle || pp->pi_cpu != 0 || (pp->pi_state == SACTIVE))
		&& (sel->uid == -1 || pp->pi_uid == (uid_t)sel->uid)) {
                *p_pref++ = pp;
		active_procs++;
	    }
	}
    }   

    /* the pref array now holds pointers to the procentry64 structures in
     * the p_info array that were selected for display
     */

    /* sort if requested */
    if ( proc_compares[compare_index] != NULL)
	qsort((char *)pref, active_procs, sizeof (struct procentry64 *), 
	      proc_compares[compare_index]);
    
    si->last_pid = -1;		/* no way to figure out last used pid */
    si->p_total = total_procs;
    si->p_active = pref_len = active_procs;

    handle.next_proc = pref;
    handle.remaining = active_procs;

    return((caddr_t)&handle);
}

char fmt[128];		/* static area where result is built */

/* define what weighted cpu is. use definition of %CPU from 'man ps(1)' */
#define weighted_cpu(pp) (PROCTIME(pp) == 0 ? 0.0 : \
                        (((PROCTIME(pp)*100.0)/(curtime-pi->pi_start))))

char *format_next_process(handle, get_userid)
    caddr_t handle;
    char *(*get_userid)();
{
    register struct handle *hp;
    register struct procentry64 *pi;
    long cpu_time;
    int proc_size, proc_ress;
    char size_unit = 'K';
    char ress_unit = 'K';

    hp = (struct handle *)handle;
    if (hp->remaining == 0) {	/* safe guard */
	fmt[0] = '\0';
	return fmt;
    }
    pi = *(hp->next_proc++);
    hp->remaining--;

    cpu_time = PROCTIME(pi);

    /* we disply sizes up to 10M in KiloBytes, beyond 10M in MegaBytes */
    if ((proc_size = (pi->pi_tsize/1024+pi->pi_dvm)*4) > 10240) {
	proc_size /= 1024;
	size_unit = 'M';
    }
    if ((proc_ress = (pi->pi_trss + pi->pi_drss)*4) > 10240) {
	proc_ress /= 1024;
	ress_unit = 'M';
    }

    sprintf(fmt, Proc_format ,
            pi->pi_pid,					  /* PID */
            (*get_userid)(pi->pi_uid),			  /* login name */
	        pi->pi_nice,				  /* fixed or vari */
            getpriority(PRIO_PROCESS, pi->pi_pid),
            proc_size,					  /* size */
            size_unit,					  /* K or M */
            proc_ress,					  /* resident */
            ress_unit,					  /* K or M */
            state_abbrev[pi->pi_state],			  /* process state */
            format_time(cpu_time),			  /* time used */
            weighted_cpu(pi),	                          /* WCPU */
            pi->pi_cpu / 100.0,                     /* CPU */
            printable(pi->pi_comm),                       /* COMM */
            (pi->pi_flags & SKPROC) == 0 ? "" : " (sys)"  /* kernel process? */
	    );
    return(fmt);
}

#ifdef OLD
/*
 *  getkval(offset, ptr, size, refstr) - get a value out of the kernel.
 *	"offset" is the byte offset into the kernel for the desired value,
 *  	"ptr" points to a buffer into which the value is retrieved,
 *  	"size" is the size of the buffer (and the object to retrieve),
 *  	"refstr" is a reference string used when printing error meessages,
 *	    if "refstr" starts with a '!', then a failure on read will not
 *  	    be fatal (this may seem like a silly way to do things, but I
 *  	    really didn't want the overhead of another argument).
 *  	
 */
int getkval(offset, ptr, size, refstr)
    unsigned long offset;
    caddr_t ptr;
    int size;
    char *refstr;
{
    int upper_2gb = 0;

    /* reads above 2Gb are done by seeking to offset%2Gb, and supplying
     * 1 (opposed to 0) as fourth parameter to readx (see 'man kmem')
     */
    if (offset > 1<<31) {
	upper_2gb = 1;
	offset &= 0x7fffffff;
    }

    if (lseek(kmem, offset, SEEK_SET) != offset) {
	fprintf(stderr, "top: lseek failed\n");
	quit(2);
    }

    if (readx(kmem, ptr, size, upper_2gb) != size) {
	if (*refstr == '!')
	    return 0;
	else {
	    fprintf(stderr, "top: kvm_read for %s: %s\n", refstr,
		    sys_errlist[errno]);
	    quit(2);
	}
    }

    return 1 ;
}
#endif
    
/* comparison routine for qsort */
/*
 * The following code is taken from the solaris module and adjusted
 * for AIX -- JV .
 */

#define ORDERKEY_PCTCPU \
           if ((result = pi2->pi_cpu - pi1->pi_cpu) == 0)

#define ORDERKEY_CPTICKS \
           if ((result = PROCTIME(pi2) - PROCTIME(pi1)) == 0)

#define ORDERKEY_STATE \
           if ((result = sorted_state[pi2->pi_state]  \
                         - sorted_state[pi1->pi_state])  == 0)

/* Nice values directly reflect the process' priority, and are always >0 ;-) */
#define ORDERKEY_PRIO \
	   if ((result = pi1->pi_nice - pi2->pi_nice) == 0)
#define ORDERKEY_RSSIZE \
           if ((result = PROCRESS(pi2) - PROCRESS(pi1)) == 0)
#define ORDERKEY_MEM \
           if ((result = PROCSIZE(pi2) - PROCSIZE(pi1)) == 0)

static unsigned char sorted_state[] =
{
    0,	/* not used */
    0,
    0,
    0,
    3,	/* sleep */
    1,	/* zombie */
    4,	/* stop */
    6,	/* run */
    2,	/* swap */
};

/* compare_cpu - the comparison function for sorting by cpu percentage */

int
compare_cpu(ppi1, ppi2)
    struct procentry64 **ppi1;
    struct procentry64 **ppi2;
{
    register struct procentry64 *pi1 = *ppi1, *pi2 = *ppi2;
    register int result;

    ORDERKEY_PCTCPU
    ORDERKEY_CPTICKS
    ORDERKEY_STATE
    ORDERKEY_PRIO
    ORDERKEY_RSSIZE
    ORDERKEY_MEM
    ;

    return result;
}
    

/* compare_size - the comparison function for sorting by total memory usage */

int
compare_size(ppi1, ppi2)
    struct procentry64 **ppi1;
    struct procentry64 **ppi2;
{
    register struct procentry64 *pi1 = *ppi1, *pi2 = *ppi2;
    register int result;

    ORDERKEY_MEM
    ORDERKEY_RSSIZE
    ORDERKEY_PCTCPU
    ORDERKEY_CPTICKS
    ORDERKEY_STATE
    ORDERKEY_PRIO
    ;

    return result;
}
    

/* compare_res - the comparison function for sorting by resident set size */

int
compare_res(ppi1, ppi2)
    struct procentry64 **ppi1;
    struct procentry64 **ppi2;
{
    register struct procentry64 *pi1 = *ppi1, *pi2 = *ppi2;
    register int result;

    ORDERKEY_RSSIZE
    ORDERKEY_MEM
    ORDERKEY_PCTCPU
    ORDERKEY_CPTICKS
    ORDERKEY_STATE
    ORDERKEY_PRIO
    ;

    return result;
}
    

/* compare_time - the comparison function for sorting by total cpu time */

int
compare_time(ppi1, ppi2)
    struct procentry64 **ppi1;
    struct procentry64 **ppi2;
{
    register struct procentry64 *pi1 = *ppi1, *pi2 = *ppi2;
    register int result;

    ORDERKEY_CPTICKS
    ORDERKEY_PCTCPU
    ORDERKEY_STATE
    ORDERKEY_PRIO
    ORDERKEY_MEM
    ORDERKEY_RSSIZE
    ;

    return result;
}
    

/* compare_prio - the comparison function for sorting by cpu percentage */

int
compare_prio(ppi1, ppi2)
    struct procentry64 **ppi1;
    struct procentry64 **ppi2;
{
    register struct procentry64 *pi1 = *ppi1, *pi2 = *ppi2;
    register int result;

    ORDERKEY_PRIO
    ORDERKEY_PCTCPU
    ORDERKEY_CPTICKS
    ORDERKEY_STATE
    ORDERKEY_RSSIZE
    ORDERKEY_MEM
    ;

    return result;
}
    

int proc_owner(pid)
int pid;
{
   register struct procentry64 **prefp = pref;
   register int cnt = pref_len;

   while (--cnt >= 0) {
       if ((*prefp)->pi_pid == pid)
	   return (*prefp)->pi_uid;
       prefp++;
   }
   
   return(-1);
}
