/* Copyright 1985, Massachusetts Institute of Technology */

/*
 * X gateway performance monitor.
 */

/* TODO:
*   1) Now that we check new values against current max/min and update 
*      our concept of max/min, we should do the opposite as well, and notice
*      when the real max/min are too far from the stated max/min and adjust the
*      later by some about if some threshold percentage is expected.
*   2) Make another window with the to display the raw numbers
*   3) Hack displaying more than one network at a time
*   4) Figure out how to monitor more than on gateway from a host 
*/

/*
 *------------------------------------------------------------------
 *
 * $Source: /achilles/u1/jis/gw/cgw/tools/gwperfmon/RCS/gwperfmon.c,v $
 * $Revision: 1.4 $
 * $Date: 87/05/19 15:02:32 $
 * $State: Exp $
 * $Author: jon $
 * $Locker: jon $
 *
 * $Log:	gwperfmon.c,v $
 * Revision 1.4  87/05/19  15:02:32  jon
 * Fix error checking/reported for -n.
 * 
 * Revision 1.3  87/05/19  14:29:18  jon
 * Do max/min scale checking on each new data point rather than looping through
 * all points every N.  Do a collect_stats just before entering main loop to 
 * avoid have a longer first time interval.  Make some error messages actually
 * print.
 * 
 * Revision 1.2  87/05/18  15:19:23  jon
 * Adds -n net-index-number to select a given network to monitor
 * 
 * Revision 1.1  87/05/15  22:58:49  jon
 * Initial revision
 * 
 * 
 *------------------------------------------------------------------
 */

#ifndef lint
static char *rcsid_gwperfmon_c = "$Header: gwperfmon.c,v 1.4 87/05/19 15:02:32 jon Locked $";
#endif	lint

/*
 * Simple graphical performance monitor for C gateway data.
 */

#include <stdio.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#include <X/Xlib.h>
#include <sys/time.h>

#include <strings.h>

#include "types.h"
#include "status.h"

#define	USEC_INC	50000
#define	SEC_INC		1

#define NUM_VALS_PER	1000
struct statistic {
	int	min_val, max_val;
	int	value[NUM_VALS_PER];
	char	*label, *label2;
};

#define SECS_PER_TIME_TICK	10
static	char do_time[NUM_VALS_PER];
static	struct timeval current_time, saved_time;
static	struct timezone dummy_zone;

short gray_bits[16] = {
    0xaaaa, 0x5555, 0xaaaa, 0x5555,
    0xaaaa, 0x5555, 0xaaaa, 0x5555,
    0xaaaa, 0x5555, 0xaaaa, 0x5555,
    0xaaaa, 0x5555, 0xaaaa, 0x5555};

/*
 * The array stats always has valid info for stats[i], 0 <= i < num_stats.
 * For each valid stats[i], stats[i].value[j] is valid for 0 <= j < num_of_val.
 * The info for the k-th possible statistic of interest is recorded, if it is
 *   recorded at all, in stats[possible_stats[k]].
 */

#define	NO_STAT			-1
#define LOAD                    0
#define PACKETS_IN              1
#define PACKETS_OUT             2
#define BYTES_IN                3
#define BYTES_OUT               4
#define ERRORS_IN               5
#define ERRORS_OUT              6
#define OVERFLOW_IN             7
#define OVERFLOW_OUT            8
#define DISCARDS                9
#define NUM_POSSIBLE_STATS	10

static	int			possible_stats[NUM_POSSIBLE_STATS];
#define WANT_STAT(x)		(possible_stats[(x)] != NO_STAT)

#define	MAX_STATS		10

#define DEFAULT_BORDER_WIDTH	3
#define DEFAULT_POSITION	"=%dx%d-0+0"

static	struct statistic	stats[MAX_STATS];

static	struct timeval timeout = {
	SEC_INC,USEC_INC};
static	int num_stats, num_of_val = 0;
static	int graph_x_offset = 0;
WindowInfo	WInfo;

Window Win;
char Host[40];
char *font_name = "6x10";
int background;			    	    /* color of background */
int foreground;			    	    /* color of graph */
int highlight;				    /* color of text, scale */
FontInfo *finfo;			    /* font information needed */
int debug = 0;
#define max(a,b) (a>b ? a:b)

u_long host, gethst();                                /* gateway */
int net_index = 0;
int net_interface_type = -1;
int gw_timeout ();

#define GWSTAT_PORT htons(25321)

typedef struct _gw_stats_ {
  unsl load;
  unsl packets_in;
  unsl packets_out;
  unsl bytes_in;
  unsl bytes_out;
  unsl errors_in;
  unsl errors_out;
  unsl overflow_in;
  unsl overflow_out;
  unsl discards;
} gw_stats;

static gw_stats gw_stats1;
static gw_stats gw_stats2;

static gw_stats *gw_stats_cur;
static gw_stats *gw_stats_prev;
static gw_stats *gw_stats_temp;

int gw_socket;
struct sockaddr	fsock;
int			fssiz = sizeof(fsock);
struct sockaddr_in	sin;

OpaqueFrame win;

#define FORALLPOSSIBLESTATS(stat)\
	for (stat = 0; stat < NUM_POSSIBLE_STATS; stat++)
#define FORALLSTATS(stat) for (stat = 0; stat < num_stats; stat++)

double  stat1();
int     maxfree;

char	*options[NUM_POSSIBLE_STATS+1] = {
        "load", "ipackets", "opackets", "ibytes", "obytes", 
	"ierrors", "oerrors", "ioverflows", "ooverflows", "discards",
	0  /* Terminator! */  };

short  arrow []=  {0x0000, 0x0020, 0x0070, 0x00f8, 0x01fc, 0x03fe, 0x0070,
		   0x0070, 0x0070, 0x0070, 0x0070, 0x0070, 0x0070, 0x0000};
short  mask []=   {0x0020, 0x0070, 0x00f8, 0x01fc, 0x03fe, 0x07ff, 0x07ff,
		   0x00f8, 0x00f8, 0x00f8, 0x00f8, 0x00f8, 0x00f8, 0x00f8};


main(argc, argv)
int argc;
char **argv;
{
	int	stat;
	struct 	timeval timeleft;
	char 	display[40];
	int 	Select_mask, select_mask = 0;
	int 	maxplus1, n;
	Cursor 	cursor;
	char 	*geometry = NULL;		/* location of window */
	char 	def[32];
	int 	reverse = 0;
	double 	update = -1.;
	double	atof();
	char 	*border_color;
	char 	*fore_color;
	char 	*back_color;
	char 	*high_color;
	Pixmap 	border_pixmap;
	char 	*option;
	int 	opt;
	int	i;
	int	minheight, minwidth;
	
	Color cdef;
	int border_width = DEFAULT_BORDER_WIDTH;

	display[0] = '\0';

	if ((option = XGetDefault(argv[0],"ReverseVideo")) != NULL )
		if (strcmp (option, "on") == 0)
			reverse = 1;
	if ((option = XGetDefault(argv[0],"BorderWidth")) != NULL)
		border_width = atoi(option);
	if ((option = XGetDefault(argv[0],"BodyFont")) != NULL)
		font_name = option;
	if ((border_color = XGetDefault(argv[0],"Border")) == NULL)
		border_color = XGetDefault(argv[0],"BorderColor");
	back_color = XGetDefault(argv[0],"Background");
	fore_color = XGetDefault(argv[0],"Foreground");
	high_color = XGetDefault(argv[0],"Highlight");
	if ((option = XGetDefault(argv[0],"Update")) != NULL)
		update = atof(option);

	/* Initialize stats */
	FORALLPOSSIBLESTATS(stat)
		possible_stats[stat] = NO_STAT;
	num_stats = 0;
	for (i = 1; i < argc; i++) {  	                /* Parse line */
		if (argv[i][0] == '=') {
			geometry = argv[i];
			continue;
		}
		if (index(argv[i], ':') != NULL) {	/* host:display */
			strncpy(display, argv[i], sizeof(display));
			continue;
		}
		if (strcmp(argv[i], "-rv") == 0 ||
		strcmp(argv[i], "-reverse") == 0) {	/* black on white */
			reverse = 1;
			continue;
		}
		if (strcmp(argv[i], "-fw") == 0 ||
		strcmp(argv[i], "-forward") == 0) {	/* white on black */
			reverse = 0;
			continue;
		}
		if (strcmp(argv[i], "-bw") == 0 ||
		strcmp(argv[i], "-border") == 0) {	/* border width */
			if (++i >= argc) usage();
			border_width = atoi(argv[i]);
			continue;
		}
		if (strcmp(argv[i], "-fn") == 0 ||
		strcmp(argv[i], "-font") == 0) {	/* host name font */
			if (++i >= argc) usage();
			font_name = argv[i];
			continue;
		}
		if (strcmp(argv[i], "-bd") == 0 ||
		strcmp(argv[i], "-color") == 0) {	/* border color */
			if (++i >= argc) usage();
			border_color = argv[i];
			continue;
		}
		if (strcmp(argv[i], "-fg") == 0 ||
		strcmp(argv[i], "-foreground") == 0) {  /* foreground color */
			if (++i >= argc) usage();
			fore_color = argv[i];
			continue;
		}
		if (strcmp(argv[i], "-bg") == 0 ||
		strcmp(argv[i], "-background") == 0) {  /* background color */
			if (++i >= argc) usage();
			back_color = argv[i];
			continue;
		}
		if (strcmp(argv[i], "-hl") == 0 ||
		strcmp(argv[i], "-highlight") == 0) {   /* highlight color */
			if (++i >= argc) usage();
			high_color = argv[i];
			continue;
		}
		if (strcmp(argv[i], "-u") == 0 ||
		strcmp(argv[i], "-update") == 0) {	/* update interval */
			if (++i >= argc) usage();
			update = atof(argv[i]);
			continue;
		}
		if (strcmp(argv[i], "-h") == 0 ||
		strcmp (argv[i], "-host") == 0 ||
		strcmp (argv[i], "-g") == 0 ||
		strcmp (argv[i], "-gw") == 0 ||
		strcmp (argv[i], "-gateway") == 0) {   /* gateway host */
		         if (++i >= argc) usage();
		         host = gethst (argv[i]);
			 strcpy (Host, argv[i]);
			 continue;
		}
		if (strcmp(argv[i], "-n") == 0) {
		  if (++i >= argc) usage ();
		    net_index = atoi(argv[i]);
		  continue;
		}
		opt = getcmd(argv[i], options);
		if (opt >= 0 && opt < NUM_POSSIBLE_STATS) {
			if (num_stats == MAX_STATS) {
				fprintf(stderr,
				    "MAX_STATS exceeded, please recompile!\n");
			}
			else possible_stats[opt] = num_stats++;
			continue;
		}
		usage();
	}

	if (host == 0) usage();
	gw_stats_cur = &gw_stats1;
	gw_stats_prev = &gw_stats2;
	init_gw_socket();
	collect_stats ();  /* do this so we get a timeout before trying to 
			      setup X */
	
	if (num_stats == 0)
		FORALLPOSSIBLESTATS(stat) {
			possible_stats[stat] = num_stats++;
			if (num_stats == MAX_STATS) break;
		}

	init_stat(LOAD, 10000, "Load", "");
	init_stat(PACKETS_IN, 150, "Input", "Packets");
	init_stat(PACKETS_OUT, 150, "Output", "Packets");
	init_stat(BYTES_IN, 50000, "Input", "Bytes");
	init_stat(BYTES_OUT, 50000, "Output", "Bytes");
	init_stat(ERRORS_IN, 10, "Input", "Errors");
	init_stat(ERRORS_OUT, 10, "Output", "Errors");
	init_stat(OVERFLOW_IN, 10, "Input", "Overflows");
	init_stat(OVERFLOW_OUT, 10, "Output", "Overflows");
	init_stat(DISCARDS, 10, "Discards", "");

	if (border_width < 0) border_width = DEFAULT_BORDER_WIDTH;
	if (update > .09) {
		timeout.tv_sec = update;
		timeout.tv_usec = (update - (double)((int)update)) * 1000000.;
	}
	if (!XOpenDisplay (display)){
 	    fprintf(stderr, "%s: Can't open display '%s'\n",
		    argv[0], XDisplayName(display));
	    exit(1);
	}
	if ((finfo = XOpenFont(font_name)) == NULL) {
		fprintf(stderr, "Can't load font %s!\n", font_name);
		exit(1);
	}

	FORALLSTATS(stat) {
		int s_width;
		s_width = XStringWidth (stats[stat].label, finfo, 0, 0);
		graph_x_offset = max(graph_x_offset, s_width);
		s_width = XStringWidth (stats[stat].label2, finfo, 0, 0);
		graph_x_offset = max(graph_x_offset, s_width);
	}
	graph_x_offset += 15;
	if(debug) fprintf(stderr, "graph_x_offset=%d\n", graph_x_offset);
	gettimeofday(&saved_time, &dummy_zone);

	if (border_color && DisplayCells() > 2 &&
    		XParseColor(border_color, &cdef) && XGetHardwareColor(&cdef))
		border_pixmap = XMakeTile(cdef.pixel);
	else if (border_color && strcmp(border_color, "black") == 0)
		border_pixmap = BlackPixmap;
	else if (border_color && strcmp(border_color, "white") == 0)
		border_pixmap = WhitePixmap;
	else	
    		border_pixmap = XMakePixmap (
			(Bitmap) XStoreBitmap (16, 16, gray_bits),
					BlackPixel, WhitePixel);



	if (back_color && DisplayCells() > 2 &&
	    XParseColor(back_color, &cdef) && XGetHardwareColor(&cdef)) {
		background = cdef.pixel;
	} else if (back_color && (strcmp(back_color, "white") == 0)) {
		background = WhitePixel;
		reverse = 0;
	} else if (back_color && (strcmp(back_color, "black") == 0)) {
		background = BlackPixel;
		reverse = 0;
	} else
	    background = BlackPixel;

	if (fore_color && DisplayCells() > 2 &&
	    XParseColor(fore_color, &cdef) && XGetHardwareColor(&cdef)) {
		foreground = cdef.pixel;
	} else if (fore_color && (strcmp(fore_color, "black") == 0)) {
		foreground = BlackPixel;
		reverse = 0;
	} else if (fore_color && (strcmp(fore_color, "white") == 0)) {
		foreground = WhitePixel;
		reverse = 0;
	} else
	    foreground = WhitePixel;

	if (high_color && DisplayCells() > 2 &&
	    XParseColor(high_color, &cdef) && XGetHardwareColor(&cdef)) {
		highlight = cdef.pixel;
	} else
	    highlight = foreground;

	if (reverse) {
		highlight = background;
		background = foreground;
		foreground = highlight;
	}
	win.bdrwidth = border_width;
	win.border = border_pixmap;
	win.background = XMakeTile(background);

	minheight = (finfo->height * 2 + 2) * num_stats;
	minwidth  = graph_x_offset + 100;
	sprintf(def, DEFAULT_POSITION, minwidth+100, 
		(finfo->height * 3 + 3) * num_stats);

	Win = XCreate ("Performance Monitor", argv[0], geometry, def, &win,
		minwidth, minheight);

	win.height -= 10;
	XMapWindow (Win);
	cursor = XCreateCursor (11, 14, arrow, mask, 5, 1, 1, 0, GXcopyInverted);
	XDefineCursor (Win, cursor);

	redisplay (Win);
	timeleft = timeout;
	Select_mask = 1<<dpyno();
	maxplus1 = 1+dpyno();
	XSelectInput(Win, KeyPressed | ExposeWindow | ExposeCopy);

	collect_stats (); /* do one close to the loop so the first set of 
			     numbers has close to the right time interval */

	while(1) {
		select_mask = Select_mask;
		if(debug) fprintf(stderr, "time=[%d,%d]\n",
		timeleft.tv_sec, timeleft.tv_usec);
		XFlush();
		if ((n = select(maxplus1, &select_mask, NULL, NULL, &timeleft))
		    < 0) exit(46);
		if(debug)
			fprintf(stderr,"selected n=%d mask=0x%x, time=[%d,%d]\n",
			n, select_mask, timeleft.tv_sec, timeleft.tv_usec);
		if (perf_mon_selected (Win, n, select_mask, &timeleft)
		    < 0) break;
	}
}

getcmd(to_match, table)			/* Modified from ucb/lpr/lpc.c */
register char *to_match;
register char **table;
{
	register char *p, *q;
	int found, index, nmatches, longest;

	longest = nmatches = 0;
	found = index = -1;
	for (p = *table; p; p = *(++table)) {
		index++;
		for (q = to_match; *q == *p++; q++)
			if (*q == 0)		/* exact match? */
				return(index);
		if (!*q) {			/* the to_match was a prefix */
			if (q - to_match > longest) {
				longest = q - to_match;
				nmatches = 1;
				found = index;
			} 
			else if (q - to_match == longest)
				nmatches++;
		}
	}
	if (nmatches > 1)
		return(-1);
	return(found);
}

init_stat(index, maxval, label_1, label_2)
int index, maxval;
char *label_1, *label_2;
{
	if WANT_STAT(index) {
		index = possible_stats[index];
		stats[index].max_val = maxval;
		stats[index].label = label_1;
		stats[index].label2 = label_2;
	}
}

#define	TIMER_EXPIRED(timer)						\
(*timer && ((*timer)->tv_sec == 0) && ((*timer)->tv_usec == 0))

int perf_mon_selected(w, number, mask, timer)
int mask, number;
Window w;
struct	timeval *timer;
{
	if(number == 0) {	/*timer expired */
		collect_stats();
		if WANT_STAT(LOAD)
		  stats[possible_stats[LOAD]].value[num_of_val] = gw_stats_cur->load;
		if WANT_STAT(PACKETS_IN)
		  stats[possible_stats[PACKETS_IN]].value[num_of_val] =
		    gw_stats_cur->packets_in - gw_stats_prev->packets_in;
		if WANT_STAT(PACKETS_OUT)
		  stats[possible_stats[PACKETS_OUT]].value[num_of_val] = 
		    gw_stats_cur->packets_out - gw_stats_prev->packets_out;
		if WANT_STAT(BYTES_IN)
		  stats[possible_stats[BYTES_IN]].value[num_of_val] = 
		    (gw_stats_cur->bytes_in - gw_stats_prev->bytes_in);
		if WANT_STAT(BYTES_OUT)
		  stats[possible_stats[BYTES_OUT]].value[num_of_val] = 
		    (gw_stats_cur->bytes_out - gw_stats_prev->bytes_out);
		if WANT_STAT(ERRORS_IN)
		  stats[possible_stats[ERRORS_IN]].value[num_of_val] = 
		    gw_stats_cur->errors_in - gw_stats_prev->errors_in;
		if WANT_STAT(ERRORS_OUT)
		  stats[possible_stats[ERRORS_OUT]].value[num_of_val] = 
		    gw_stats_cur->errors_out - gw_stats_prev->errors_out;
		if WANT_STAT(OVERFLOW_IN)
		  stats[possible_stats[OVERFLOW_IN]].value[num_of_val] = 
		    gw_stats_cur->overflow_in - gw_stats_prev->overflow_in;
		if WANT_STAT(OVERFLOW_OUT)
		  stats[possible_stats[OVERFLOW_OUT]].value[num_of_val] = 
		    gw_stats_cur->overflow_out - gw_stats_prev->overflow_out;
		if WANT_STAT(DISCARDS)
		  stats[possible_stats[DISCARDS]].value[num_of_val] = 
		    gw_stats_cur->discards - gw_stats_prev->discards;

		gettimeofday(&current_time, &dummy_zone);
		if (current_time.tv_sec < saved_time.tv_sec) {
			/* Super-user must have set the clock back */
			saved_time = current_time;
			saved_time.tv_sec -= SECS_PER_TIME_TICK;
		}
		if (saved_time.tv_sec+SECS_PER_TIME_TICK <= current_time.tv_sec) {
			saved_time = current_time;
			do_time[num_of_val] = 1;
		} 
		else
			do_time[num_of_val] = 0;
		next_display(w);
	}
	if (mask & (1 << dpyno())){
		XEvent event;
		XEvent pevent;
		XExposeWindowEvent *exp_event;
		int key;
		if(!XPending()) return (-1); /* end of file on connection */
		while (XPending())
		  {
			XNextEvent (&event);
			switch (event.type) {
			case KeyPressed:
				if ((key = mapkey(((XKeyPressedEvent *)&event)->detail)) > 0)
					switch(key){
					case 'f': /* faster usec timeout */
						if (timeout.tv_usec >= USEC_INC)
							timeout.tv_usec -= USEC_INC;
						else {
							if (timeout.tv_sec >= SEC_INC) {
								timeout.tv_sec -= SEC_INC;
								timeout.tv_usec = 1000000-USEC_INC;
							}
						}
						break;
					case 's': /* slower usec timeout */
						if (timeout.tv_usec < 1000000-USEC_INC)
							timeout.tv_usec += USEC_INC;
						else {
							timeout.tv_usec = 0;
							timeout.tv_sec += 1;
						}
						break;
					case 'F': /* faster sec timeout */
						if (timeout.tv_sec >= SEC_INC)
							timeout.tv_sec -= SEC_INC;
						break;
					case 'S': /* slower sec timeout */
						timeout.tv_sec += SEC_INC;
						break;
					case 'R': /* reset */
						timeout.tv_sec = SEC_INC;
						timeout.tv_usec = USEC_INC;
						num_of_val = 0;
						redisplay(w);
						break;
					case 'h':
					case 'H':
					case '?': /* Help */
						printf("%s\n%s\n%s\n%s\n%s\n%s\n",
						"'s' slower usec timeout",
						"'f' faster usec timeout",
						"'S' slower sec timeout",
						"'F' faster sec timeout",
						"'R' reset timeout and display",
						"'q' or 'Q' quit");
						/*
						 * Don't reset timeout
						 */
						return(0);
					case 'q':
					case 'Q':
						return(-1);
					}	/* switch(key) */
				break;
			case ExposeWindow:
				XSync(0);
				while (XPending() != 0) {
					XPeekEvent (&pevent);
					if (pevent.type != ExposeWindow) break;
					XNextEvent(&event);
				}

				exp_event = (XExposeWindowEvent *) &event;
				win.x = exp_event->x;
				win.y = exp_event->y;
				win.width = exp_event->width;
				win.height = exp_event->height - 10;
				redisplay(w);
				break;
			default:
				break;
			}
		}
	}
	*timer = timeout;
	return(0);
}

collect_stats()
{
    int			cc,i;
    char		recvbuf[576];
    statpkt             *status;
    netinfo             *netip;

    /* send out a request */
    recvbuf[0] = 0;
    recvbuf[1] = 0;
    recvbuf[2] = 0;
    recvbuf[3] = 0;  /* could be used for request like restart/reload */

    sin.sin_addr = *(struct in_addr *)(&host);

    cc = sendto(gw_socket, &recvbuf[0], 4, 0, &sin, sizeof(sin));
    if (cc < 0) {
	perror("gwstat: sendto");
	exit(1);
    }

    alarm(5);
    cc = recvfrom(gw_socket, &recvbuf[0], sizeof(recvbuf), 0, &fsock, &fssiz);
    if (cc < 0) {  
      perror("gwstat: recvfrom");
      exit(1);
    }
    alarm(0);
    status = (statpkt *) recvbuf;
    /* should hack STAT_MAGIC and endian-ness here so this will work on
       the RT's */
    if (status->version != STAT_VERSION) {
      fprintf (stderr, "gwpefmon: unknown version (%d) status packet received.\n",
	      status->version);
      exit (99);
    }

    if (net_index > status->nnets - 1) {
      fprintf (stderr, "gwperfmon: bad network interface %d, there are only %d interfaces on %s\n", 
	      net_index, status->nnets, Host);
      exit(99);
    }
      
    /* bump a generation ... */
    gw_stats_temp = gw_stats_prev;
    gw_stats_prev = gw_stats_cur;
    gw_stats_cur = gw_stats_temp;

    gw_stats_cur->load = status->load1;

    for (i = 0, netip = (netinfo *) (status + 1); i < status->nnets; i++) {
      if (netip->netnum == net_index) {
	gw_stats_cur->packets_in = netip->pkti;
	gw_stats_cur->packets_out = netip->pkto;
	gw_stats_cur->bytes_in = netip->byti;
	gw_stats_cur->bytes_out = netip->byto;
	gw_stats_cur->errors_in = netip->ioin;
	gw_stats_cur->errors_out = netip->ioout;
	gw_stats_cur->overflow_in = netip->ovfin;
	gw_stats_cur->overflow_out = netip->ovfout;
	gw_stats_cur->discards = netip->disc;
	break;
      }
      netip = (netinfo *) ((byte *) (netip + 1) + netip->devlen);
    }
  }

min (a, b)
	int a,b;
{
	return(a<b ? a:b);
}

#define	YORIGIN_FOR_STAT(num)	((((num)*win.height)/num_stats)+3)
#define	YMIDPOINT_FOR_STAT(num)	((((num)*win.height+win.height/2)/num_stats) + 5)
#define Y_FOR_STAT_VAL(stat, num_of_val)				\
	y_base - min(height_of_stat, (	\
		height_of_stat*(					\
		  stats[stat].value[num_of_val]-stats[stat].min_val)/(	\
		  stats[stat].max_val-stats[stat].min_val)))
#define First_Point(v, xv, yv) {v->x = xv; v->y = yv;\
		v++->flags = VertexDontDraw; }
#define Next_Point(v, xv, yv) {v->x = xv; v->y = yv;\
		v++->flags = VertexRelative | VertexDrawLastPoint; }

display_dividers(w, clear_first)
int clear_first;
Window w;
{
	register int	i, stat;
	register int lwidth = win.width - graph_x_offset;
	Vertex v[NUM_VALS_PER];
	register Vertex *vp;

	if(debug) fprintf(stderr, "num_of_val=%d\n", num_of_val);
	FORALLSTATS(stat) {
		register int y_org = YORIGIN_FOR_STAT(stat+1);
		vp = v;
		if (clear_first)
			XPixSet(w, graph_x_offset, y_org-2, lwidth, 5, background);
		/* Draw the horizontal line and then add the tick marks */
		XLine(w, graph_x_offset, y_org, win.width, y_org, 1, 1, 
		    foreground, GXcopy, ~0);
		for (i = 0; i < num_of_val; i++) {
			if (do_time[i]){
				First_Point(vp, graph_x_offset + i, y_org - 2);
				Next_Point(vp, 0, 4);
			}
		}
		if (vp != v)
			XDraw(w, v, vp-v, 1, 1, foreground, GXcopy, ~0);
	}
}

redisplay(w)
Window w;
{
	register int height_of_stat, stat;

	XClear (w);
	display_dividers(w, 0);
	height_of_stat = YORIGIN_FOR_STAT(1) - YORIGIN_FOR_STAT(0) - 10;
	XTextMask (w, 0, 0, Host, strlen (Host), finfo->id, highlight);
	FORALLSTATS(stat) {
		register int y_origin_of_stat = YORIGIN_FOR_STAT(stat);
		int text_size;
		char temp[20];
		XTextMask (w, 0, YMIDPOINT_FOR_STAT(stat),
		    stats[stat].label, strlen (stats[stat].label), finfo->id,
		    highlight);
		XTextMask (w, 0, YMIDPOINT_FOR_STAT(stat)+10,
		    stats[stat].label2, strlen (stats[stat].label2), finfo->id, 
		    highlight);
		sprintf(temp, "%d", stats[stat].max_val);
		text_size = XStringWidth (temp, finfo, 0, 0);
		XTextMask (w, graph_x_offset-5-text_size, y_origin_of_stat+5,
			temp, strlen (temp), finfo->id, highlight);
		sprintf(temp, "%d", stats[stat].min_val);
		text_size = XStringWidth (temp, finfo, 0, 0);
		XTextMask (w, graph_x_offset-5-text_size,
		    y_origin_of_stat-1+height_of_stat, temp, strlen (temp),
		    finfo->id, highlight);
	}
	if (num_of_val > 0) FORALLSTATS(stat) 
		redisplay_stat_values(w, height_of_stat, stat, num_of_val);

}

redisplay_stat_values(w, height_of_stat, stat, stop_plus_one)
Window w;
int height_of_stat, stat, stop_plus_one;
{
	register int j, newY;
	Vertex v[NUM_VALS_PER];
	register Vertex *vp = v;
	int y_base = YORIGIN_FOR_STAT(stat+1)-5;
	newY = Y_FOR_STAT_VAL(stat, 0);
	First_Point(vp, graph_x_offset, newY);
	for (j = 1; j < stop_plus_one; ) {
		register int npts = 0, oldY = newY;
		do {
			newY = Y_FOR_STAT_VAL(stat, j);
			j++; 
			npts++;
		} 
		while ((oldY == newY) && (j < stop_plus_one));
		if (--npts)
			Next_Point(vp, npts, 0);
		Next_Point(vp, 1, newY - oldY);
	}
	if (vp != v)
		XDraw(w, v, vp-v, 1, 1, foreground, GXcopy, ~0);
}

next_display(w)
Window w;
{
	int stat, height_of_stat;
	int need_redisplay = 0;  /* if scale has changed need to redraw label,
				    max/min and all values, easiest to just do
				    a redisplay, this shouldn't be too 
				    frequent */

	height_of_stat = YORIGIN_FOR_STAT(1) - YORIGIN_FOR_STAT(0) - 10;
	FORALLSTATS(stat) {
		int newY, oldY;
		int y_base;
		/* first see if scale has changed */
		/* this will adapt max and min changes, but we should catch
		   cases when spikes go away and rescale max/min then.  (E.g.
		   {1, 3, 50, 2, 3} ought not to have max of 50 once the 50
		   gets shifted out */
		if (stats[stat].max_val < stats[stat].value[num_of_val]) {
		  stats[stat].max_val = stats[stat].value[num_of_val];
		  need_redisplay = 1;
		}
		if (stats[stat].min_val > stats[stat].value[num_of_val]) {
		  stats[stat].min_val = stats[stat].value[num_of_val];
		  need_redisplay = 1;
		}
		else {
		y_base = YORIGIN_FOR_STAT(stat+1)-5;
		newY = Y_FOR_STAT_VAL(stat, num_of_val);
		if (num_of_val == 0)
			oldY = newY;
		else
			oldY = Y_FOR_STAT_VAL(stat, num_of_val-1);
		XLine(w, graph_x_offset+num_of_val, oldY,
		    graph_x_offset+num_of_val+1, newY, 1, 1, foreground, 
		    GXcopy, ~0);
		if (do_time[num_of_val]) {
			y_base += 5;
			XLine(w, graph_x_offset+num_of_val, y_base-2,
			    graph_x_offset+num_of_val, y_base+2,
			    1, 1, foreground, GXcopy, ~0);
		      }
	      }
	      }

	if (++num_of_val >= NUM_VALS_PER ||
	    num_of_val >= win.width-graph_x_offset) {
		int num_shift_left = (win.width-graph_x_offset)/2;
		int width = (win.width-graph_x_offset) - num_shift_left;
		register int j;
		for (j = num_shift_left; j < num_of_val; j++)
			do_time[j-num_shift_left] = do_time[j];
		FORALLSTATS(stat) {
			register int ys = YORIGIN_FOR_STAT(stat)+5, nmax = 1, t;
			for (j = num_shift_left; j < num_of_val; j++) {
				t = stats[stat].value[j-num_shift_left] =
				    stats[stat].value[j];
			}
			if (!need_redisplay) {
			  XMoveArea(w, graph_x_offset+num_shift_left,
				    ys, graph_x_offset, ys, width, height_of_stat+2);
			  XPixSet(w, graph_x_offset+num_shift_left,
				  ys, width, height_of_stat+2, background);
			}
		}
		num_of_val -= num_shift_left+1;
		if (need_redisplay) {
			redisplay(w);
			need_redisplay = 0;
		      }
		else
			display_dividers(w, 1);
	      }
	
	if (need_redisplay)
	  redisplay (w);

      }



usage()
{
	fprintf(stderr,
		"Usage: gwperfmon [host:display] -host gateway -n netinterface# option option .....\n");
	exit(1);
}

u_long	gethst(hst)
char	*hst;
{
    struct hostent	*hstent;
    long		host;

    if ((*hst < '0') || (*hst > '9')) {
	if ((hstent = gethostbyname(hst)) == NULL) {
	    fprintf(stderr, "Don't know host %s\n", hst);
	    exit(1);
	}
	return(*(u_long *)(hstent->h_addr));
    } else {
	if ((host = inet_addr(hst)) == NULL) {
	    fprintf(stderr, "Don't know address %s\n", hst);
	    exit(1);
	}
	return(host);
    }
}

init_gw_socket ()
{
  
    gw_socket = socket(AF_INET, SOCK_DGRAM, 0);
    if (gw_socket < 0) {
	perror("gwperfmon: socket");
	exit(3);
    }
    bzero((char *)&sin, sizeof (sin));
    sin.sin_family = AF_INET;
    sin.sin_port = GWSTAT_PORT;
    if (bind(gw_socket, &sin, sizeof (sin)) < 0) {
	perror("gwperfmon: bind");
	exit(1);
      }

  signal(SIGALRM, gw_timeout);  /* used to time out recvfrom */
  }

gw_timeout()
{
  fprintf (stderr, "gwperfmon: timeout\n");
  exit (99);
}
