/*
 * Copyright (c) 2013 Red Hat.
 * Copyright (c) 1999 Silicon Graphics, Inc.  All Rights Reserved.
 * 
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "pmapi.h"
#include "impl.h"

/*
 * Outputs psinfo.psargs and top value.
 *
 * Want to periodically write to stdout
 * with the top processes for the following categories:
 *
 * cpuburn - rate converted proc.psusage.utime + stime
 * syscalls - rate converted proc.psusage.pu_sysc
 * reads - rate converted proc.psusage.bread + gbread 
 * writes - rate converted proc.psusage.bwrit + gbwrit
 * ctxswitch - rate converted proc.psusage.pu_vctx + proc.psusage.pu_ictx
 * residentsize - proc.psusage.rss
 * 
 */

#define MAX_PMID 15 /* gives enough space for all possible metrics in fetch */
char    *namelist[MAX_PMID];
pmID	pmidlist[MAX_PMID];
int	type_tab[MAX_PMID]; /* the types of the metrics */
int	valfmt_tab[MAX_PMID]; /* the types of the metrics */
int	num_pmid;
int	num_proc_pmid;

/* indexes into [MAX_PMID] arrays */
int	kernel_utime;
int	kernel_stime;
int	kernel_sysc;
int	kernel_ctx;
int	pu_utime;
int	pu_stime;
int	pu_sysc;
int	pu_bread;
int	pu_gbread;
int	pu_bwrit;
int	pu_gbwrit;
int	pu_rss;
int	pu_vctx;
int	pu_ictx;

char	*hostname;
struct timeval	delta = { 2, 0 };	/* default interval */
int	top = 5;
long	num_samples = -1; /* forever */
int	num_inst = 0;
int	*instances = NULL;
char	**instance_names = NULL;
char	*runtime = NULL;
int     zflag = 0;                  /* for -z */
char    *tz = NULL;         /* for -Z timezone */
char	pid_label_fmt[8];
char	pid_fmt[8];
char	*line_fmt = "%.79s";
int	pid_len = 0;
pmInDom proc_indom;

/* values of interest */
#define CPU_VAL 	0
#define SYSCALLS_VAL	1
#define CTX_VAL		2
#define WRITES_VAL	3
#define READS_VAL	4
#define RSS_VAL		5
#define USR_VAL		6
#define SYS_VAL		7
#define NUM_VALUES 	8

/* which values to calculate and print */
#define SHOW_CPU	(1<<CPU_VAL)
#define SHOW_SYSCALLS	(1<<SYSCALLS_VAL)
#define SHOW_CTX	(1<<CTX_VAL)
#define SHOW_WRITES	(1<<WRITES_VAL)
#define SHOW_READS	(1<<READS_VAL)
#define SHOW_RSS	(1<<RSS_VAL)
#define SHOW_USR	(1<<USR_VAL)
#define SHOW_SYS	(1<<SYS_VAL)

int	show_spec = SHOW_CPU|SHOW_SYSCALLS|SHOW_CTX|SHOW_WRITES|SHOW_READS|SHOW_RSS; /* default to show all */

/* copied from pmdbg.h */
typedef struct {
    char        *name;
    int         bit;
} show_map_t;
static show_map_t show_map[] = {
    { "CPU",   SHOW_CPU },
    { "SYSC",  SHOW_SYSCALLS },
    { "CTX",   SHOW_CTX },
    { "WRITE", SHOW_WRITES },
    { "READ",  SHOW_READS },
    { "RSS",   SHOW_RSS },
    { "USR",   SHOW_USR },
    { "SYS",   SHOW_SYS },
};
static int num_show = sizeof(show_map) / sizeof(show_map[0]);

typedef struct {
   int inst; /* index into instances and names */
   double values[NUM_VALUES];
} rate_entry_t;
rate_entry_t *rate_tab = NULL;

double global_rates[NUM_VALUES] = {0};
double sum_rates[NUM_VALUES] = {0};

typedef struct {
   char *val_fmt;
   char *label_fmt;
   char *label;
   char *units;
   char *fullname;
} format_entry_t;

/*
 * Warning - must match order of *_VAL macros
 */
format_entry_t format_tab[] =
{ 
    {"%7.2f", "%7s", "CPU%", "", "CPU Utilization"},
    {"%9.0f", "%9s", "SYSCALLS", "sys/sec", "System Calls"},
    {"%9.0f", "%9s", "CTX", "ctx/sec", "Context Switches"},
    {"%8.0f", "%8s", "WRITES", "Kb/sec", "Writes"},
    {"%8.0f", "%8s", "READS", "Kb/sec", "Reads"},
    {"%9.0f", "%9s", "RSS", "Kb", "Resident Size"},
    {"%7.2f", "%7s", "USR%", "", "User CPU Utilization"},
    {"%7.2f", "%7s", "SYS%", "", "System CPU Utilization"},
};

/* value table */
typedef struct val_entry_t {
    int num;
    pmValue values[MAX_PMID][2];
} val_entry_t;
val_entry_t *val_tab = NULL;

/* index will use high end for globals */
/* parallels index with other MAX_PMID arrays */
pmValue global_val[MAX_PMID][2]; 

#ifndef min
#define min(x,y) (((x)<(y))?(x):(y))
#endif

void get_indom(void);

/* copied and modified from __pmParseDebug() from util.c */
int
parse_show_spec(const char *spec)
{
    int		val = 0;
    int		tmp;
    const char	*p;
    char	*pend;
    int		i;

    for (p = spec; *p; ) {
	tmp = (int)strtol(p, &pend, 10);
	if (tmp == -1)
	    /* special case ... -1 really means set all the bits! */
	    tmp = INT_MAX;
	if (*pend == '\0') {
	    val |= tmp;
	    break;
	}
	else if (*pend == ',') {
	    val |= tmp;
	    p = pend + 1;
	}
	else {
	    pend = strchr(p, ',');
	    if (pend != NULL)
		*pend = '\0';

	    for (i = 0; i < num_show; i++) {
		if (strcasecmp(p, show_map[i].name) == 0) {
		    val |= show_map[i].bit;
		    if (pend != NULL) {
			*pend = ',';
			p = pend + 1;
		    }
		    else
			p = "";		/* force termination of outer loop */
		    break;
		}
	    }

	    if (i == num_show) {
		if (pend != NULL)
		    *pend = ',';
		return PM_ERR_CONV;
	    }
	}
    }

    return val;
}

static char *
get_time(void)
{
    static char str[80];
    time_t now = time(NULL);
    struct tm *tm = localtime(&now);
    strftime(str, sizeof(str), "%c %Z", tm);
    return str;
}

__uint64_t
get_value(int metric, pmValue *value)
{
    int sts;
    pmAtomValue val;

    if ((sts = pmExtractValue(valfmt_tab[metric], value,
		  type_tab[metric], &val, PM_TYPE_U64)) < 0) {
	fprintf(stderr, "%s: Failed to extract metric value: %s\n", 
		pmProgname, pmErrStr(sts));
	exit(1);

    }
    return val.ull;
}

int 
cpuburn_cmp(const void* x, const void* y)
{
   rate_entry_t *c1 = (rate_entry_t *)x;
   rate_entry_t *c2 = (rate_entry_t *)y;

   if (c1->values[CPU_VAL] == c2->values[CPU_VAL])
	return 0;
   else if (c2->values[CPU_VAL] > c1->values[CPU_VAL])
	return 1;
   else
	return -1;
}

int 
sys_cmp(const void* x, const void* y)
{
   rate_entry_t *c1 = (rate_entry_t *)x;
   rate_entry_t *c2 = (rate_entry_t *)y;

   if (c1->values[SYS_VAL] == c2->values[SYS_VAL])
	return 0;
   else if (c2->values[SYS_VAL] > c1->values[SYS_VAL])
	return 1;
   else
	return -1;
}

int 
usr_cmp(const void* x, const void* y)
{
   rate_entry_t *c1 = (rate_entry_t *)x;
   rate_entry_t *c2 = (rate_entry_t *)y;

   if (c1->values[USR_VAL] == c2->values[USR_VAL])
	return 0;
   else if (c2->values[USR_VAL] > c1->values[USR_VAL])
	return 1;
   else
	return -1;
}

int 
syscall_cmp(const void* x, const void* y)
{
   rate_entry_t *c1 = (rate_entry_t *)x;
   rate_entry_t *c2 = (rate_entry_t *)y;

   if (c1->values[SYSCALLS_VAL] == c2->values[SYSCALLS_VAL])
	return 0;
   else if (c2->values[SYSCALLS_VAL] > c1->values[SYSCALLS_VAL])
	return 1;
   else
	return -1;
}

int 
ctx_cmp(const void* x, const void* y)
{
   rate_entry_t *c1 = (rate_entry_t *)x;
   rate_entry_t *c2 = (rate_entry_t *)y;

   if (c1->values[CTX_VAL] == c2->values[CTX_VAL])
	return 0;
   else if (c2->values[CTX_VAL] > c1->values[CTX_VAL])
	return 1;
   else
	return -1;
}

int 
write_cmp(const void* x, const void* y)
{
   rate_entry_t *c1 = (rate_entry_t *)x;
   rate_entry_t *c2 = (rate_entry_t *)y;

   if (c1->values[WRITES_VAL] == c2->values[WRITES_VAL])
	return 0;
   else if (c2->values[WRITES_VAL] > c1->values[WRITES_VAL])
	return 1;
   else
	return -1;
}

int 
read_cmp(const void* x, const void* y)
{
   rate_entry_t *c1 = (rate_entry_t *)x;
   rate_entry_t *c2 = (rate_entry_t *)y;

   if (c1->values[READS_VAL] == c2->values[READS_VAL])
	return 0;
   else if (c2->values[READS_VAL] > c1->values[READS_VAL])
	return 1;
   else
	return -1;
}

int 
rss_cmp(const void* x, const void* y)
{
   rate_entry_t *c1 = (rate_entry_t *)x;
   rate_entry_t *c2 = (rate_entry_t *)y;

   if (c1->values[RSS_VAL] == c2->values[RSS_VAL])
	return 0;
   else if (c2->values[RSS_VAL] > c1->values[RSS_VAL])
	return 1;
   else
	return -1;
}

/*
 * sum the rate values for the top entries
 */
void
calc_sum_rates(int num_entries, int val_type)
{
    int i;
    double sum = 0;

    for(i=0; i < min(top, num_entries); i++) {
	rate_entry_t *entry = &rate_tab[i];
	sum += entry->values[val_type];
    }

    sum_rates[val_type] = sum;
}

void
print_top(rate_entry_t *rate_tab, int num_entries, int val_type)
{
    static char line[2048];
    char *ptr;
    int i,j;
    double x;

    /* --- section heading --- */
    if (global_rates[val_type] <= 0) {
	printf("%s\n", format_tab[val_type].label);
    }
    else {
	x= 100*sum_rates[val_type]/global_rates[val_type];
	printf("%s - top %d processes account for %.0f%% of %s\n",
	       format_tab[val_type].label,
	       top, x>100?100:x,
	       format_tab[val_type].fullname);
    }

    /* --- fields heading --- */
    printf(pid_label_fmt, "PID");
    for(j=0; j < NUM_VALUES; j++) {
	if (show_spec & (1<<j)) {
	    format_entry_t *fmt = &format_tab[j];
	    printf(fmt->label_fmt, fmt->label);
	}
    } 
    printf(" CMD\n");

    /* units */
    printf(pid_label_fmt, " ");
    for(j=0; j < NUM_VALUES; j++) {
	if (show_spec & (1<<j)) {
	    format_entry_t *fmt = &format_tab[j];
	    printf(fmt->label_fmt, fmt->units);
	}
    } 
    printf("\n");


    /* --- values --- */
    for(i=0; i < min(top, num_entries); i++) {
	rate_entry_t *entry = &rate_tab[i];

	*line = '\0';
	ptr = line;

	/* pid */
	sprintf(ptr, pid_fmt, instances[entry->inst]);
	ptr += strlen(ptr);

	/* other values */
	for(j=0; j < NUM_VALUES; j++) {
	    if (show_spec & (1<<j)) {
		format_entry_t *fmt = &format_tab[j];
		sprintf(ptr, fmt->val_fmt, entry->values[j]);
		ptr += strlen(ptr);
	    }
	}

	/* cmd */
        /* skip over leading pid-name in instance name */
	sprintf(ptr, " %s", &instance_names[entry->inst][pid_len+1]);

	/* ensure line fits in certain width */
	printf(line_fmt, line);
	printf("\n");
    }

    printf("\n");
}

void 
doit(void)
{
    int i, r, m, v, sts;
    static double now = 0, then = 0;
    double delta_d;
    static unsigned long num_times = 0;
    static pmResult *result[2];
    int num_entries;

    num_times++;

    if (num_times > 2) {
	pmFreeResult(result[0]);	

        /* if forever, ensure no wrapping */
        if (num_samples < 0)
	    num_times = 3;
    }

    if (num_times > 1) {
	result[0] = result[1];
    }

    get_indom();
    if ((sts = pmFetch(num_pmid, pmidlist, &result[1])) < 0) {
	for (i = 0; i < num_pmid; i++)
	    fprintf(stderr, "%s: pmFetch: %s\n", namelist[i], pmErrStr(sts));
	exit(1);
    }


    if (num_times > 1) {
        then = result[0]->timestamp.tv_sec + result[0]->timestamp.tv_usec/1000000;
        now = result[1]->timestamp.tv_sec + result[1]->timestamp.tv_usec/1000000;
	delta_d = now - then;

        if (result[1]->numpmid != num_pmid) {
	    fprintf(stderr, "%s: Failed to fetch all metrics (%d out of %d)\n", 
		    pmProgname, result[1]->numpmid, num_pmid);
	    exit(1);
	}
	
	/* --- build value table --- */

	for(i=0; i<num_inst; i++) {
	    val_tab[i].num = 0;
	}

	/* go thru each result */
	for(r=0; r<2; r++) {
	    /* go thru each metric */
	    for(m=0; m<num_pmid; m++) {
		pmValueSet *vset = result[r]->vset[m];
		if (vset->numval < 0) {
		    fprintf(stderr, "%s: Error when fetching metric %s: %s\n", 
			pmProgname, namelist[m], pmErrStr(vset->numval));
		    continue;
		}
		if (vset->numval == 0)
		    continue;
		valfmt_tab[m] = vset->valfmt;

		/* handle global metrics */
		if (((show_spec & (SHOW_CPU | SHOW_SYS)) && m == kernel_stime) || 
		    ((show_spec & (SHOW_CPU | SHOW_USR)) && m == kernel_utime) || 
		    ((show_spec & SHOW_SYSCALLS) && m == kernel_sysc) || 
		    ((show_spec & SHOW_CTX) && m == kernel_ctx)) {
		    if (vset->numval != 1) {
			fprintf(stderr, "%s: Wrong num of values for kernel metric: %d\n", 
			    pmProgname, vset->numval);
			exit(1);
		    }
		    global_val[m][r] = vset->vlist[0];	
		    continue;
		}

		/* handle proc-instance metrics */

		/* Find result inst
		 * Would be faster to start from where left off
		 * last time. Need to remember this for a metric/result.
		 */
		for(v=0; v<vset->numval; v++) {
		    pmValue val = vset->vlist[v];
		    /* go thru each possible inst */
		    for(i=0; i<num_inst; i++) {
			if (instances[i] == val.inst) {
			    val_tab[i].num++; /* needs to be zeroed at start */
			    val_tab[i].values[m][r] = val;
			    break;
			}
		    }
		}
	    }
	}
	
	/* --- calculate rates --- */

	/* handle global metrics */
	if (show_spec & SHOW_CPU) {
	    __uint64_t utime[2], stime[2];
	    double cpuburn_val;

	    utime[0] = get_value(kernel_utime, &global_val[kernel_utime][0]);
	    utime[1] = get_value(kernel_utime, &global_val[kernel_utime][1]);
	    stime[0] = get_value(kernel_stime, &global_val[kernel_stime][0]);
	    stime[1] = get_value(kernel_stime, &global_val[kernel_stime][1]);
	    
	    cpuburn_val = (utime[1] - utime[0] + stime[1] - stime[0]) / delta_d;
	    cpuburn_val /= 10; 
	    cpuburn_val = cpuburn_val > 100 ? 100 : cpuburn_val;
	    global_rates[CPU_VAL] = cpuburn_val;
	}


	if (show_spec & SHOW_SYSCALLS) {
	    __uint64_t sysc[2];

	    sysc[0] = get_value(kernel_sysc, &global_val[kernel_sysc][0]);
	    sysc[1] = get_value(kernel_sysc, &global_val[kernel_sysc][1]);
	    
	    global_rates[SYSCALLS_VAL] = (sysc[1] - sysc[0]) / delta_d;
	}

	if (show_spec & SHOW_CTX) {
	    __uint64_t ctx[2];

	    ctx[0] = get_value(kernel_ctx, &global_val[kernel_ctx][0]);
	    ctx[1] = get_value(kernel_ctx, &global_val[kernel_ctx][1]);
	    
	    global_rates[CTX_VAL] = (ctx[1] - ctx[0]) / delta_d;
	}

	if (show_spec & SHOW_SYS) {
	    __uint64_t stime[2];

	    stime[0] = get_value(kernel_stime, &global_val[kernel_stime][0]);
	    stime[1] = get_value(kernel_stime, &global_val[kernel_stime][1]);
	    
	    global_rates[SYS_VAL] = ((stime[1] - stime[0]) / delta_d) / 10;
	}

	if (show_spec & SHOW_USR) {
	    __uint64_t utime[2];

	    utime[0] = get_value(kernel_utime, &global_val[kernel_utime][0]);
	    utime[1] = get_value(kernel_utime, &global_val[kernel_utime][1]);
	    
	    global_rates[USR_VAL] = ((utime[1] - utime[0]) / delta_d) / 10;
	}


	/* handle proc-instance metrics */
	num_entries = 0;
	for(i=0; i<num_inst; i++) {
	    val_entry_t *vt = &val_tab[i];
	    rate_entry_t *rt;
	    if (vt->num < (num_proc_pmid*2)) {
		/* couldn't get instances for all proc metrics for both(*2) results */
		continue;
	    }
	    
	    rt = &rate_tab[num_entries];
	    rt->inst = i;

	    /* create cpuburn values */
	    if (show_spec & SHOW_CPU) {
		__uint64_t utime[2], stime[2];
		double cpuburn_val;

		utime[0] = get_value(pu_utime, &vt->values[pu_utime][0]);
		utime[1] = get_value(pu_utime, &vt->values[pu_utime][1]);
		stime[0] = get_value(pu_stime, &vt->values[pu_stime][0]);
		stime[1] = get_value(pu_stime, &vt->values[pu_stime][1]);
		
		cpuburn_val = (utime[1] - utime[0] + stime[1] - stime[0]) / delta_d;
		cpuburn_val /= 10; 
		cpuburn_val = cpuburn_val > 100 ? 100 : cpuburn_val;
		rt->values[CPU_VAL] = cpuburn_val;
	    }

	    /* create syscalls values */
	    if (show_spec & SHOW_SYSCALLS) {
		__uint64_t sysc[2];

		sysc[0] = get_value(pu_sysc, &vt->values[pu_sysc][0]);
		sysc[1] = get_value(pu_sysc, &vt->values[pu_sysc][1]);
		
		rt->values[SYSCALLS_VAL] = (sysc[1] - sysc[0]) / delta_d;
	    }

	    /* create ctx values */
	    if (show_spec & SHOW_CTX) {
		double ictx, vctx;

		vctx = get_value(pu_vctx, &vt->values[pu_vctx][1]) - 
		       get_value(pu_vctx, &vt->values[pu_vctx][0]);
		ictx = get_value(pu_ictx, &vt->values[pu_ictx][1]) - 
		       get_value(pu_ictx, &vt->values[pu_ictx][0]);
		
		rt->values[CTX_VAL] = (vctx + ictx) / delta_d;
	    }

	    /* create reads values */
	    if (show_spec & SHOW_READS) {
		double br, gbr;

		br = get_value(pu_bread, &vt->values[pu_bread][1]) - 
		     get_value(pu_bread, &vt->values[pu_bread][0]);
		gbr = get_value(pu_gbread, &vt->values[pu_gbread][1]) -
		      get_value(pu_gbread, &vt->values[pu_gbread][0]);
		
		rt->values[READS_VAL] = (gbr * 1024 * 1024 + br / 1024) / delta_d;
	    }

	    /* create writes values */
	    if (show_spec & SHOW_WRITES) {
		double bw, gbw;

		bw = get_value(pu_bwrit, &vt->values[pu_bwrit][1]) -
		     get_value(pu_bwrit, &vt->values[pu_bwrit][0]);
		gbw = get_value(pu_gbwrit, &vt->values[pu_gbwrit][1]) -
		      get_value(pu_gbwrit, &vt->values[pu_gbwrit][0]);
		
		rt->values[WRITES_VAL] = (gbw * 1024 * 1024 + bw / 1024) / delta_d;
	    }

	    /* create rss values */
	    if (show_spec & SHOW_RSS) {
		rt->values[RSS_VAL] = get_value(pu_rss, &vt->values[pu_rss][1]);
	    }

	    /* create sys cpu time values */
	    if (show_spec & SHOW_SYS) {
		__uint64_t stime[2];

		stime[0] = get_value(pu_stime, &vt->values[pu_stime][0]);
		stime[1] = get_value(pu_stime, &vt->values[pu_stime][1]);
		
		rt->values[SYS_VAL] = ((stime[1] - stime[0]) / delta_d) / 10;
		rt->values[SYS_VAL] = rt->values[SYS_VAL] > 100 ? 100 : rt->values[SYS_VAL];
	    }

	    /* create user cpu time values */
	    if (show_spec & SHOW_USR) {
		__uint64_t utime[2];

		utime[0] = get_value(pu_utime, &vt->values[pu_utime][0]);
		utime[1] = get_value(pu_utime, &vt->values[pu_utime][1]);
		
		rt->values[USR_VAL] = ((utime[1] - utime[0]) / delta_d) / 10;
		rt->values[USR_VAL] = rt->values[USR_VAL] > 100 ? 100 : rt->values[USR_VAL];
	    }


	    num_entries++;
	}

	/* --- report values --- */
	printf("HOST: %s\n", hostname);
	printf("DATE: %s\n\n", get_time());

	if (show_spec & SHOW_CPU) {
	    qsort(rate_tab, num_entries, sizeof(rate_entry_t), cpuburn_cmp);
	    calc_sum_rates(num_entries, CPU_VAL);
	    print_top(rate_tab, num_entries, CPU_VAL);
	}

	if (show_spec & SHOW_SYSCALLS) {
	    qsort(rate_tab, num_entries, sizeof(rate_entry_t), syscall_cmp);
	    calc_sum_rates(num_entries, SYSCALLS_VAL);
	    print_top(rate_tab, num_entries, SYSCALLS_VAL);
	}

	if (show_spec & SHOW_CTX) {
	    qsort(rate_tab, num_entries, sizeof(rate_entry_t), ctx_cmp);
	    calc_sum_rates(num_entries, CTX_VAL);
	    print_top(rate_tab, num_entries, CTX_VAL);
	}

	if (show_spec & SHOW_WRITES) {
	    qsort(rate_tab, num_entries, sizeof(rate_entry_t), write_cmp);
	    print_top(rate_tab, num_entries, WRITES_VAL);
	}

	if (show_spec & SHOW_READS) {
	    qsort(rate_tab, num_entries, sizeof(rate_entry_t), read_cmp);
	    print_top(rate_tab, num_entries, READS_VAL);
	}

	if (show_spec & SHOW_RSS) {
	    qsort(rate_tab, num_entries, sizeof(rate_entry_t), rss_cmp);
	    print_top(rate_tab, num_entries, RSS_VAL);
	}

	if (show_spec & SHOW_SYS) {
	    qsort(rate_tab, num_entries, sizeof(rate_entry_t), sys_cmp);
	    calc_sum_rates(num_entries, SYS_VAL);
	    print_top(rate_tab, num_entries, SYS_VAL);
	}

	if (show_spec & SHOW_USR) {
	    qsort(rate_tab, num_entries, sizeof(rate_entry_t), usr_cmp);
	    calc_sum_rates(num_entries, USR_VAL);
	    print_top(rate_tab, num_entries, USR_VAL);
	}
    }

    if (num_times-1 == num_samples) {
	exit(0);
    }
}


static void
print_usage(void)
{
    fprintf(stderr,
"Usage: %s [options]\n\
\n\
Options:\n\
  -h host	metrics source is PMCD on host\n\
  -m top	look at top m values\n\
  -p spec,...	print certain values e.g. cpu,sysc,rss\n\
  -s num	number of samples to output\n\
  -t interval   default reporting interval [default 2 seconds]\n\
  -w		wide output for command name\n\
  -Z timezone   set reporting timezone\n\
  -z            set reporting timezone to local time of metrics source\n\
where spec = cpu|sysc|ctx|read|write|rss|sys|usr\n",
	pmProgname);
}

void
parse_options(int argc, char *argv[])
{
    int		c;
    int		sts;
    int		errflag = 0;
    char	*opts = "D:h:m:p:s:t:wzZ:?";
    char	*p;
    char	*endnum;
    int		one_trip = 1;

    while ((c = getopt(argc, argv, opts)) != EOF) {
	switch (c) {
	    case 'w':   /* wide flag */
		line_fmt = "%.1024s";
		break;
	    case 'p':	/* show flag */
		if (one_trip) {
		    show_spec = 0; one_trip = 0;
		}
		sts = parse_show_spec(optarg);
		if (sts < 0) {
		    fprintf(stderr, "%s: unrecognized print flag specification (%s)\n",
			pmProgname, optarg);
		    errflag++;
		}
		else
		    show_spec |= sts;
		break;

	    case 'D':	/* debug flag */
		sts = __pmParseDebug(optarg);
		if (sts < 0) {
		    fprintf(stderr, "%s: unrecognized debug flag specification (%s)\n",
			pmProgname, optarg);
		    errflag++;
		}
		else
		    pmDebug |= sts;
		break;


	    case 'h':	/* contact PMCD on this hostname */
		hostname = optarg;
		break;
	
	    case 'm':   /* top m */
		top = (int)strtol(optarg, &endnum, 10);
		if (top <= 0) {
		    fprintf(stderr, "%s: -m requires a positive integer\n", pmProgname);
		    errflag++;	
		}
		break;

	    case 's':   /* number of samples to output */
		num_samples = (int)strtol(optarg, &endnum, 10);
		if (top <= 0) {
		    fprintf(stderr, "%s: -s requires a positive integer\n", pmProgname);
		    errflag++;	
		}
		break;

	    case 't':		/* change default logging interval */
		if (pmParseInterval(optarg, &delta, &p) < 0) {
		    fprintf(stderr, "%s: illegal -t argument\n", pmProgname);
		    fputs(p, stderr);
		    free(p);
		    errflag++;
		}
		break;

	    case 'z':       /* timezone from host */
		if (tz != NULL) {
		    fprintf(stderr, "%s: at most one of -Z and/or -z allowed\n", pmProgname);
		    errflag++;
		}
		zflag++;
		break;

	    case 'Z':       /* $TZ timezone */
		if (zflag) {
		    fprintf(stderr, "%s: at most one of -Z and/or -z allowed\n", pmProgname);
		    errflag++;
		}
		tz = optarg;
		break;


	    case '?':
		if (errflag == 0) {
		    print_usage();
		    exit(0);
		}
	}
    }

    while (optind < argc) {
        fprintf(stderr, "%s: Invalid argument: %s\n", pmProgname, argv[optind]);
        errflag++;
	optind++;
    }

    if (errflag) {
	print_usage();
	exit(1);
    }

}

/*
 * go thru show_spec and build up the namelist and
 * index variables into the array
 */
void
create_namelist(void)
{
    int i = 0; /* for each pmid */
    int j = 0; /* for global pmids */

    if (show_spec & SHOW_CPU || show_spec & SHOW_USR) {
	namelist[i] = "proc.psusage.utime";
	pu_utime = i;
	i++;
	namelist[i] = "kernel.all.cpu.user";
	kernel_utime = i;
	i++; j++;
    }
    if (show_spec & SHOW_CPU || show_spec & SHOW_SYS) {
	namelist[i] = "proc.psusage.stime";
	pu_stime = i;
	i++;
	namelist[i] = "kernel.all.cpu.sys";
	kernel_stime = i;
	i++; j++;
    }
    if (show_spec & SHOW_SYSCALLS) {
	namelist[i] = "proc.psusage.sysc";
	pu_sysc = i;
	i++;
	namelist[i] = "kernel.all.syscall";
	kernel_sysc = i;
	i++; j++;
    }
    if (show_spec & SHOW_CTX) {
	namelist[i] = "proc.psusage.ictx";
	pu_ictx = i;
	i++;
	namelist[i] = "proc.psusage.vctx";
	pu_vctx = i;
	i++;
	namelist[i] = "kernel.all.pswitch";
	kernel_ctx = i;
	i++; j++;
    }
    if (show_spec & SHOW_READS) {
	namelist[i] = "proc.psusage.bread";
	pu_bread = i;
	i++;
	namelist[i] = "proc.psusage.gbread";
	pu_gbread = i;
	i++;
    }
    if (show_spec & SHOW_WRITES) {
	namelist[i] = "proc.psusage.bwrit";
	pu_bwrit = i;
	i++;
	namelist[i] = "proc.psusage.gbwrit";
	pu_gbwrit = i;
	i++;
    }
    if (show_spec & SHOW_RSS) {
	namelist[i] = "proc.psusage.rss";
	pu_rss = i;
	i++;
    }

    num_pmid = i;
    num_proc_pmid = i - j;
}

void
get_indom(void)
{
    static int onetrip = 1;

    if (instances != NULL) 
	free(instances);
    if (instance_names != NULL)
	free(instance_names);

    if ((num_inst = pmGetInDom(proc_indom, &instances, &instance_names)) <= 0) {
	fprintf(stderr, "%s: Failed to find any proc instances : %s\n",
		pmProgname, pmErrStr(num_inst));
	exit(1);
    }

    
    /* process the instance names
     * - find how long a pid is 
     * - create pid label fmt based on pid length
     */
    if (onetrip) {
	char *str = instance_names[0];

	onetrip=0;

	/* get pid str up to first space */
	pid_len = 0;
	while (*str != '\0' && *str != ' ') {
	    pid_len++;
	    str++;
	}
	if (*str == '\0') {
	    /* shouldn't happen */
	    fprintf(stderr, "%s: Bad proc instance : %s\n",
		    pmProgname, instance_names[0]);
	    exit(1);
	}

	sprintf(pid_label_fmt, "%%%ds", pid_len);
	sprintf(pid_fmt, "%%%dd", pid_len);
    }


    rate_tab = realloc(rate_tab, sizeof(rate_entry_t)*num_inst);
    if (rate_tab == NULL) {
	fprintf(stderr, "%s: Failed to allocate rate table\n",
		pmProgname);
	exit(1);
    }

    val_tab = realloc(val_tab, sizeof(val_entry_t)*num_inst);
    if (val_tab == NULL) {
	fprintf(stderr, "%s: Failed to allocate value table\n",
		pmProgname);
	exit(1);
    }

    pmDelProfile(proc_indom, 0, NULL);
    pmAddProfile(proc_indom, num_inst, instances);
}

int
main(int argc, char *argv[])
{
    pmDesc desc;
    int sts, i;

    __pmSetProgname(argv[0]);

    parse_options(argc, argv);

    create_namelist();

    if (hostname == NULL) {
	hostname = "local:";
    }

    if ((sts = pmNewContext(PM_CONTEXT_HOST, hostname)) < 0) {
	fprintf(stderr, "%s: Cannot connect to PMCD on host \"%s\": %s\n",
		pmProgname, hostname, pmErrStr(sts));
	exit(1);
    }

    if (zflag) {
        if ((sts = pmNewContextZone()) < 0) {
            fprintf(stderr, "%s: Cannot set context timezone: %s\n",
                pmProgname, pmErrStr(sts));
            exit(1);
        }
    }
    else if (tz != NULL) {
        if ((sts = pmNewZone(tz)) < 0) {
            fprintf(stderr, "%s: Cannot set timezone to \"%s\": %s\n",
                pmProgname, tz, pmErrStr(sts));
            exit(1);
        }
    }

    {
	char *tz;
	char buf[80];
	if (pmWhichZone(&tz) >= 0) {
	    sprintf(buf, "TZ=%s", tz);
	    putenv(buf);
	}
    }

    if ((sts = pmLookupName(num_pmid, namelist, pmidlist)) < 0) {
	fprintf(stderr, "%s: Failed to lookup metrics : %s\n",
		pmProgname, pmErrStr(sts));
	exit(1);
    }

    for(i=0; i<num_pmid; i++) {
	if ((sts = pmLookupDesc(pmidlist[i], &desc)) < 0) {
	    fprintf(stderr, "%s: Failed to lookup descriptor for metric \"%s\": %s\n",
		    pmProgname, namelist[i], pmErrStr(sts));
	    exit(1);
	}
	type_tab[i] = desc.type;	
	/* ASSUMES that the first metric will always be a proc metric */
	if (i == 0) {
	   proc_indom = desc.indom;
	} 
    }

    for(;;) {
	doit();
        sleep(delta.tv_sec);
    }

    return 0;
}
