#include "config.h"
#include "xsysstats.h"
#include "headers.h"

#ifdef __DGUX__
#define signed int
#endif

#ifndef HAVE_RSTAT

#ifndef HAVE_CALLRPC
#error "Neither rstat nor callrpc available"
#endif
#ifndef HAVE_XDR_STATSTIME
#error "Neither rstat nor xdr_statstime available"
#endif

int rstat(char *host, struct statstime *stats)
{
	return callrpc(host, RSTATPROG, RSTATVERS_TIME, RSTATPROC_STATS,
			(xdrproc_t)xdr_void, NULL, (xdrproc_t)xdr_statstime,
		       (char *) stats);
}
#endif /* HAVE_RSTAT */

#ifdef USE_NEW_RSTAT
#include <rpc/rpc.h>
int 
rstat_local(struct Host_Info *host, struct statstime *statval)
{
    enum clnt_stat stat;
    struct timeval timeout;
    int onetry=0;

    /* Reopen the connection.  We could probably come up with a smarter
     * method of backoff's, but trying to reopen ever time if it closed down
     * probably isn't too costly.
     */
reopen:
    if (host->client==NULL) {
	int val;
	val=open_host(host);
	if (val==-1) {
	    host->client=NULL;
	    return -1;
	}
    }
    if ((stat = clnt_call(host->client, RSTATPROC_STATS, (xdrproc_t)xdr_void,
			  NULL, (xdrproc_t) xdr_statstime, 
	      (char *) statval, timeout))!=RPC_SUCCESS) {
/* could perhaps do something more useful here */
	fprintf(stderr,"%s: clnt_call unsuccessfull, error %s\n", host->name, clnt_sperrno(stat));
	auth_destroy(host->client->cl_auth);
	clnt_destroy(host->client);
	host->client=NULL;
	/* It seems that we periodically loose the connection (not sure why -
	 * maybe things aren't guarentted to wrok long term)  If so, lets try
	 * to reopen, but only try once (otherwise, we could get in an endless
	 * loop.
	 */
    }
    return stat;
}
#else
int rstat_local(struct Host_Info *host, struct statstime *statval)
{
    return rstat(host->name, statval);
}
#endif

struct statstime	*our_stats;
static int *rstat_errors;
static int new_stat=0;

#ifdef USE_NEW_RSTAT
int open_host(struct Host_Info *host)
{
    struct timeval timeout;

    timeout.tv_sec=1;
    timeout.tv_usec=0;

#if 1
    if (!(host->client = clnt_create(host->name, RSTATPROG, RSTATVERS_TIME, "udp"))) {
/*		fprintf(stderr,"Could not open rpc connection to %s via udp\n", host->name);*/
	return -1;
    }
#else
    if (!(host->client = clnt_create_timed(host->name, RSTATPROG, RSTATVERS_TIME, "udp",&timeout))) {
		fprintf(stderr,"Could not open rpc connection to %s via udp\n", host->name);
	return -1;
    }
#endif
    if (!clnt_control(host->client, CLSET_TIMEOUT, (char*)&timeout)) {
/*	fprintf(stderr,"could not set default timeout for %s\n", host);*/
	auth_destroy(host->client->cl_auth);
	clnt_destroy(host->client);
	return -1;
    }
    if (!clnt_control(host->client, CLSET_RETRY_TIMEOUT, (char*)&timeout)) {
/*	fprintf(stderr,"could not set default retry timeout for %s\n", host);*/
	auth_destroy(host->client->cl_auth);
	clnt_destroy(host->client);
	return -1;
    }
    return 0;
}
#endif

/* set_first_values allocates the space to actually store the values,
 * add then reads in a first set into them.
 */
void set_first_values()
{
    int	i;

    our_stats = (struct statstime *)
	malloc(sizeof(struct statstime) * num_hosts * 2);
    rstat_errors = malloc(sizeof(int) * num_hosts*2);

    for (i=0; i<num_hosts; i++) {
#ifdef USE_NEW_RSTAT
	hosts[i].client=NULL;
	open_host(&hosts[i]);
#endif
	rstat_errors[i*2+new_stat] = rstat_local(&hosts[i], &our_stats[i*2 + new_stat]);
    }

    new_stat++;
    new_stat %=2;
}

/* This sets the scale of linked graphs to the same value.  The highest
 * scale value is used.  The graph argument that is passed is the graph
 * value that has just changed.  It may or may not be more efficient to wait
 * until all the graphs have changed scale before going through and 
 * synchronizing the other graphs to match.  It depends on the circumstances.
 * of rescaling.
 *
 * It is assumed that as part of the set up, multiple links are all set
 * to the first element.  That is, if graph 0 is cpu, and graph 3 links
 * to graph 0 (thus, graphs[3].link = 0) and graph 4 links to graph 3,
 * then graph[4].link = 0 (the graph that graph 3 is linked to.)  This
 * makes things much simpler here, and is very little cost to do at startup.
 *
 */
static void sync_scales(struct graph_info graph)
{
	int max=0,num_match=0,i;

	for (i=0; i<num_graphs; i++) 
	    if (graphs[i].link == graph.link) {
		num_match++;
		if (graphs[i].true_scale>max)
		    max=graphs[i].true_scale;
	    }
	/* If this graph is linked, clear the redraw flag, and only
	 * set it if we change the scale of any of the graphs.  If
	 * this graph is not linked, then certainly rescale.
	 */
	if (num_match>1) {
		windows[graph.window]->redraw_needed=FALSE;
		for (i=0; i<num_graphs; i++)
		    if (graphs[i].link == graph.link) {
		        if (graphs[i].scale!=max) {
			    windows[graphs[i].window]->redraw_needed=TRUE;
			    graphs[i].scale = max;
			}
		    }
	}
	else
	    windows[graph.window]->redraw_needed=TRUE;
}

void set_values()
{
    int i,newstat,oldstat;
    static int num_ticks=0;

    num_ticks++;
    for (i=0; i<num_hosts; i++)
	rstat_errors[i*2 + new_stat] = 
	    rstat_local(&hosts[i], &our_stats[i*2 + new_stat]);

    new_stat++;
    new_stat %= 2;
    point_pos++;
    point_pos %= split_width;

    for (i=0; i<num_graphs; i++) {

	newstat = graphs[i].host_offset * 2 + ((1 + new_stat) % 2);
	oldstat = graphs[i].host_offset * 2 + new_stat;

	/* If we got an error this time, data is meaningless, so set values to
	 * 0.  If we got an error last time, we can get an accurate difference
	 * (entries in rstat structure will be zeroed), so also set values
	 * to zero.
	 */
	if (rstat_errors[graphs[i].host_offset*2]!=RPC_SUCCESS ||
	    rstat_errors[graphs[i].host_offset*2+1]!=RPC_SUCCESS) {
/*	    fprintf(stderr,"Have no valid data for host %s\n", hosts[graphs[i].host_offset].name);*/
	    points[i][point_pos]=0;
	}
	else { switch (graphs[i].type) {
	    case CPU:
	    case ICPU: {
		int	j[4],k;

		for (k=0; k<4; k++)
			j[k] = our_stats[newstat].cp_time[k] - 
			    our_stats[oldstat].cp_time[k];
		if (j[3] == 0) points[i][point_pos] = 100;
		else points[i][point_pos] = 100 - 
			(100 * j[3]) / (j[0]+j[1]+j[2]+j[3]);
		break;
	    }
	    case UCPU: {
		int     j[4],k;

		for (k=0; k<4; k++)
		    j[k] = our_stats[newstat].cp_time[k] -
			our_stats[oldstat].cp_time[k];

		    points[i][point_pos] = 100*j[0]/(j[0]+j[1]+j[2]+j[3]);
		    break;
	    }
	    case NICECPU: {
		int     j[4],k;

		for (k=0; k<4; k++)
		    j[k] = our_stats[newstat].cp_time[k] -
			our_stats[oldstat].cp_time[k];

		    points[i][point_pos] = 100*j[1]/(j[0]+j[1]+j[2]+j[3]);
		    break;
	    }
	    case SCPU: {
		int     j[4],k;

		for (k=0; k<4; k++)
		    j[k] = our_stats[newstat].cp_time[k] -
			our_stats[oldstat].cp_time[k];

		    points[i][point_pos] = 100*j[2]/(j[0]+j[1]+j[2]+j[3]);
		    break;
	    }

	    case IPACKETS:
		points[i][point_pos] = (our_stats[newstat].if_ipackets
			 - our_stats[oldstat].if_ipackets) / sleep_time;
		break;
	    case OPACKETS:
		points[i][point_pos] = (our_stats[newstat].if_opackets
			 - our_stats[oldstat].if_opackets) / sleep_time;
		break;
	    case PACKETS:
		points[i][point_pos] = (our_stats[newstat].if_ipackets +
			our_stats[newstat].if_opackets -
			our_stats[oldstat].if_opackets
			 - our_stats[oldstat].if_ipackets) / sleep_time;
		break;
	    case PAGE:
		    points[i][point_pos] = our_stats[newstat].v_pgpgin +
			our_stats[newstat].v_pgpgout  -
			our_stats[oldstat].v_pgpgin -
			our_stats[oldstat].v_pgpgout;
		    break;
	    case PAGEI:
		points[i][point_pos] = our_stats[newstat].v_pgpgin
			 - our_stats[oldstat].v_pgpgin;
		break;
	    case APAGEI:
		points[i][point_pos] = (our_stats[newstat].v_pgpgin
			 - our_stats[oldstat].v_pgpgin)/sleep_time;
		break;
	    case PAGEO:
		points[i][point_pos] = our_stats[newstat].v_pgpgout
			 - our_stats[oldstat].v_pgpgout;
		break;
	    case APAGEO:
		points[i][point_pos] = (our_stats[newstat].v_pgpgout
			 - our_stats[oldstat].v_pgpgout)/sleep_time;
		break;
	    case SWAP:
		points[i][point_pos] = our_stats[newstat].v_pswpin +
		    our_stats[newstat].v_pswpout -
		    our_stats[oldstat].v_pswpin -
		    our_stats[oldstat].v_pswpout;
		break;
	    case SWAPI:
		points[i][point_pos] = our_stats[newstat].v_pswpin
			 - our_stats[oldstat].v_pswpin;
		break;
	    case SWAPO:
		points[i][point_pos] = our_stats[newstat].v_pswpin
			 - our_stats[oldstat].v_pswpin;
		break;
	    case INT:
		points[i][point_pos] = ((signed)our_stats[newstat].v_intr
			 - (signed)our_stats[oldstat].v_intr)/sleep_time;
		if (points[i][point_pos] < 0)
			points[i][point_pos] = 0;
		break;
	    case DISK:
		points[i][point_pos] = (our_stats[newstat].dk_xfer[0]
			+our_stats[newstat].dk_xfer[1]
			+our_stats[newstat].dk_xfer[2]
			+our_stats[newstat].dk_xfer[3]
			-our_stats[oldstat].dk_xfer[0]
			-our_stats[oldstat].dk_xfer[1]
			-our_stats[oldstat].dk_xfer[2]
			-our_stats[oldstat].dk_xfer[3])
			/sleep_time;
		break;
	    case CONTEXT:
		points[i][point_pos] = (our_stats[newstat].v_swtch
			 - our_stats[oldstat].v_swtch)/sleep_time;
		break;

	    /* Load averages get handles a bit differently.  By default,
	     * dividing the avenrun[] by 256 gets you the load average.
	     * However, often load average is fairly low - under 5.
	     * This does not give very good resolution in decimal form.
	     * So it is multiplied by LOAD_FACTOR and then divided.
	     * For scale, the same thing is true.  When display the
	     * legend at the bottom of the screen, scale is then
	     * divided by LOAD_FACTOR.  If LOAD_FACTOR is 100, is
	     * effectively keeps two decimal points, making for
	     * a fairly fine graph.
	     * graphs[i].scale_mult has the same value as LOAD_FACTOR
	     * for the load average graphs.
	     */
	    case LOAD1:
		points[i][point_pos] =
		    (our_stats[newstat].avenrun[0] * LOAD_FACTOR) / FSCALE;
		break;
	    case LOAD5:
		points[i][point_pos] =
		    (our_stats[newstat].avenrun[1] * LOAD_FACTOR) / FSCALE;
		break;
	    case LOAD15:
		points[i][point_pos] =
		    (our_stats[newstat].avenrun[2] * LOAD_FACTOR) / FSCALE;
		break;
	    case COLL:
		points[i][point_pos] = our_stats[newstat].if_collisions
			 - our_stats[oldstat].if_collisions;
		break;
	    case ERRORS:
		points[i][point_pos] = our_stats[newstat].if_ierrors
			 - our_stats[oldstat].if_ierrors;
		break;
	    default:
		fprintf(stderr,"Unknown graph type: %d, graph %d\n",graphs[i].type, i);
	  }
	}
	if (points[i][point_pos]<0) points[i][point_pos]=0;
	graphs[i].running_avg += points[i][point_pos] -
	    graphs[i].running_avg;

	/* Check the max value.  If it's new, make a note to redraw the
	 * graph to reflect it (if max value display is on).
         */
	if (points[i][point_pos] > graphs[i].max_val) {
	    extern int show_max;

	    graphs[i].max_val = points[i][point_pos];
	    if (show_max)
		windows[graphs[i].window]->redraw_needed = TRUE;
	}


	/* Check to see if the graph needs to be downscaled.  Only actually
	 * attempt to do downscaling once every 10 ticks.  This is to
	 * prevent excessive runthroughs of the points array, finding
	 * the max value it holds.
	 * We do the check on true_scale.  IF graphs are synchronized,
	 * there is no point seeing if we can downscale when we know
	 * that we should, but another graph has a higher scale.
	 */
		
	if (!(num_ticks % DOWNSCALE_OCCURANCE) &&
	    graphs[i].running_avg<graphs[i].true_scale*graphs[i].scale_mult
	    && graphs[i].true_scale>graphs[i].min_scale) {

	    int	k,max=0;

	    for (k=0; k<split_width; k++)
		if (points[i][k]>max) max=points[i][k];
	    if (graphs[i].true_scale*graphs[i].scale_mult / 2 > max) {
		while (graphs[i].running_avg<graphs[i].true_scale * graphs[i].scale_mult &&
		    graphs[i].true_scale * graphs[i].scale_mult/2 > max &&
		    graphs[i].true_scale > graphs[i].min_scale)
			graphs[i].true_scale /=2;

		if (graphs[i].true_scale < graphs[i].min_scale)
			graphs[i].true_scale = graphs[i].min_scale;
		graphs[i].scale = graphs[i].true_scale;
		windows[graphs[i].window]->redraw_needed=TRUE;
		if (graphs[i].link != -1) sync_scales(graphs[i]);
	    }
	}

	if (points[i][point_pos]>graphs[i].true_scale*graphs[i].scale_mult &&
	    graphs[i].true_scale<graphs[i].max_scale) {
	    do {
		graphs[i].true_scale *= 2;
	    } while (points[i][point_pos]>=graphs[i].true_scale * graphs[i].scale_mult &&
		 graphs[i].true_scale < graphs[i].max_scale);
	    if (graphs[i].true_scale>graphs[i].max_scale)
		    graphs[i].true_scale = graphs[i].max_scale;
	    graphs[i].scale = graphs[i].true_scale;
	    windows[graphs[i].window]->redraw_needed=TRUE;
	    if (graphs[i].link != -1) sync_scales(graphs[i]);
	}
    }
}


syntax highlighted by Code2HTML, v. 0.9.1