ftp.nice.ch/peanuts/GeneralData/Documents/user-groups/rmNUG/dialupip2.0.tar.gz#/src/dialmon/dialmon.c

This is dialmon.c in view mode; [Download] [Up]

/*
**  Copyright (c) 1991 Bolt Beranek and Newman, Inc.
**  All rights reserved.
**
**  Redistribution and use in source and binary forms are permitted
**  provided that: (1) source distributions retain this entire copyright
**  notice and comment, and (2) distributions including binaries display
**  the following acknowledgement:  ``This product includes software
**  developed by Bolt Beranek and Newman, Inc. and CREN/CSNET'' in the
**  documentation or other materials provided with the distribution and in
**  all advertising materials mentioning features or use of this software.
**  Neither the name of Bolt Beranek and Newman nor CREN/CSNET may be used
**  to endorse or promote products derived from this software without
**  specific prior written permission.
**
**  THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
**  WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
**  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#include <stdio.h>
#include <ctype.h>
#include <curses.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <net/if.h>
#include <sys/dk.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/param.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <net/if_du.h>
#include "dialmon.h"

    /* Shut up, lint */
#define Refresh		(void)refresh
#define Wrefresh	(void)wrefresh
#define Wclear		(void)wclear
#define Wclrtoeol	(void)wclrtoeol
#define Wprintw		(void)wprintw
#define Wmove		(void)wmove

    /* Configuration data. */
#define MAXRECS		40	/* Max # of types of log records	*/
#define MAXPLOTS	49	/* # of plots per log record graph	*/
#define MAXWINDOWS	4	/* # of available windows		*/
#define HOSTNAMESIZE	25	/* Size of a host name			*/
#define MAXINTERVALS	1	/* intervals/average			*/
#define HOSTNAMELEN	128

    /* Window numbers */
#define W_GLOBAL	0	/* Site-wide statistics			*/
#define W_LINE		1	/* Active line statistics		*/
#define W_TOTAL		2	/* Active line totals			*/
#define W_HELP		3	/* Help function			*/

#define NOTSET		-1	/* When a yloc is not in use		*/
#define LINE_NOTCONF	'N'	/* When a serial line is not configured	*/
#define MAXREQUEST	25	/* Maximum size of a request		*/


    /* Fields in status structures, and statistics computed from them. */
#define R_CIPUP		 0
#define R_CIPLN		 1
#define R_COPUP		 2
#define R_COPLN		 3
#define R_CPSIP		 4
#define R_CPRIP		 5
#define R_CTPBUSY	 6
#define R_CTPIDLE	 7
#define R_CCHS		 8
#define R_CCHR		 9
#define R_gCHS		10	/* Global chars sent			*/
#define R_gCHR		11	/* Global chars received		*/
#define R_aCHS		12	/* Average chars sent			*/
#define R_aPKTS		13	/* Average pkts sent			*/
#define R_aCHPKTS	14	/* Average chars/pkt sent		*/
#define R_aCHR		15	/* Average chars received		*/
#define R_aPKTR		16	/* Average pkts received		*/
#define R_aCHPKTR	17	/* Average chars/pkt received		*/
#define R_aPCTESC	18	/* Average percent of esc chars sent	*/
#define R_STATE		19	/* State of the du_softc interface	*/
#define R_ESCR		20	/* number of esc chars received		*/
#define R_ESCS		21	/* number of esc chars sent		*/
#define R_IERROR	22
#define R_OERROR	23
#define R_gIERROR	24	/* Global				*/
#define R_gOERROR	25	/* Global				*/


    /* A dial log record. */
typedef struct _DIALLOGREC {
    int		yloc;		/* Where to graph packet counts with the
				 * corresponding attributes		*/
    int		Count;		/* new packet count			*/
    int		Old;		/* old count, so can update plots without
				 * redrawing entire plot		*/
    float	fCount;		/* New count packets as a float		*/
    int		Line;		/* line number, 0 for global stats	*/
    int		Window;		/* window to display this record in	*/
} DIALLOGREC;

    /* Averages */
typedef struct _AVERAGES {
    int		chs;		/* Characters sent			*/
    int		pkts;		/* Packets sent				*/
    int		chr;		/* Characters received			*/
    int		pktr;		/* Packets received			*/
    int		sescs;		/* Escape chars sent			*/
    int		rescs;		/* Escape chars received		*/
    int		ierror;		/* Input errors				*/
    int		oerror;		/* Output errors			*/
    int		sec;		/* Seconds between getting new data	*/
} AVERAGES;


static AVERAGES		Averages[MAX_NDU][MAXINTERVALS];
static char		HostName[HOSTNAMELEN];
static char		OtherHost[MAX_NDU][HOSTNAMESIZE];
static DIALSTATS	StatsCur;
static DIALSTATS	StatsOld;
static DIALSTATS	StatsReset;
static int		fd;		/* Descriptor connected to server */
static int		CurrLine;	/* Current serial line */
static int		freset;		/* Using reset totals? */
static int		CurrWind;	/* Current window */
static int		maxyloc;	/* largest y-local in graph window */
static int		newdata;	/* Index into Averages */
static int		cursed;		/* Curses set up? */
static WINDOW		*Wgraph;
static WINDOW		*Whelp;
static WINDOW		*Wprompt;
static WINDOW		*Wtitle;
static WINDOW		*Wtypes;

    /* Pointers to log records which hold 5 sec data, logical line number this
     * log record applies to and the y-position where this data is printed. */
static DIALLOGREC	*Records[MAXRECS];

    /* State of du_softc interface.  One message/bit. */
static char	*DUStateMessages[] = {
	"ESCAPED",
	"OACTIVE",
	"",
	"",
	"LWAITING",
	"LACTIVE",
	"LDOWN",
	"FAILCALL",
	"MONITORON"
};

    /* Type descriptions. */
static char *TypeDescriptions[] = {
    "Pkts received from IP",
    "Pkts from remote site",
    "Pkts passed to IP",
    "Pkts sent to remote site",
    "Pkts passed to IP",
    "Pkts received from IP",
    "Status of line",
    "Tty busy",
    "Percent tty idle",
    "Characters sent",
    "Characters received",
    "Characters sent",
    "Characters received",
    "Avg chars sent/sec",
    "Avg pkts sent/sec",
    "Avg chars/pkt sent",
    "Avg chars received/sec",
    "Avg pkts received/sec",
    "Avg chars/pkt received",
    "Avg chars received/interrupt",
    "Avg chars escaped",
    "Escape chars received",
    "State of interface",
    "Escape chars sent",
    "Line Input Errors",
    "Line Output Errors",
    "Global Input Errors",
    "Global Output Errors"
};


extern char	*progname;
extern char	*dip_release();

extern int		errno;
extern int		h_errno;
extern int		optind;
extern char		*ctime();
extern char		*malloc();
extern char		*optarg;
extern char		*strchr();
extern char		*strcpy();
extern char		*strerror();
extern char		*strncpy();
extern time_t		time();
extern unsigned int	sleep();
extern void		bcopy();
extern void		exit();



/*
**  Normal exit.
*/
static void
ext()
{
    (void)close(fd);
    if (cursed)
	endwin();
    (void)printf("\n");
    exit(0);
}

/*
**  Print error and exit.
*/
static void
fatal(text)
    char	*text;
{
    int		oerrno;

    oerrno = errno;
    (void)close(fd);
    if (cursed) {
	Wmove(stdscr, COLS - 1, LINES - 1);
	Refresh();
	endwin();
    }
    (void)fprintf(stderr, "\n\n%s, %s\n", text, strerror(oerrno));
    exit(1);
}




/*
**  Compute the average stats for a line.
*/
static void
computeavgs(i)
    int		i;
{
    int		j;
    float	*fp1;
    float	*fp2;
    float	time;
    AVERAGES	*aptr;

    /* Get the pointer to the new data. */
    aptr = &Averages[i][newdata];
    aptr->chs = Records[R_CCHS][i].Count;
    aptr->pkts = Records[R_CPSIP][i].Count;
    aptr->chr = Records[R_CCHR][i].Count;
    aptr->pktr = Records[R_CPRIP][i].Count;
    aptr->rescs = Records[R_ESCR][i].Count;
    aptr->sescs = Records[R_ESCS][i].Count;
    aptr->sec = StatsCur.when - StatsOld.when;
    aptr->ierror = Records[R_IERROR][i].Count;
    aptr->oerror = Records[R_OERROR][i].Count;

    /* Total over MAXINTERVALS intervals. */
    Records[R_aCHS][i].fCount = 0.0;
    Records[R_aPKTS][i].fCount = 0.0;
    Records[R_aCHR][i].fCount = 0.0;
    Records[R_aPKTR][i].fCount = 0.0;
    Records[R_aPCTESC][i].fCount = 0.0;
    for (time = 0.0, j = 0; j < MAXINTERVALS; j++) {
        aptr = &Averages[i][j];
        Records[R_aCHS][i].fCount += aptr->chs;
        Records[R_aPKTS][i].fCount += aptr->pkts;
        Records[R_aCHR][i].fCount += aptr->chr;
        Records[R_aPKTR][i].fCount += aptr->pktr;
	Records[R_aPCTESC][i].fCount += aptr->rescs;
	time += aptr->sec;
    }

    /* Compute average chars sent/packet. */
    fp1 = &Records[R_aCHS][i].fCount;
    fp2 = &Records[R_aPKTS][i].fCount;
    Records[R_aCHPKTS][i].fCount = *fp2 ? *fp1 / *fp2 : 0.0;

    if (time)
        *fp2 /= time;

    /* Factor in escape characters sent. */
    fp2 = &Records[R_aPCTESC][i].fCount;
    *fp2 = *fp1 ? (*fp2 / *fp1 ) * 100. : 0.0;

    if (time)
        *fp1 /= time;


    /* Computer average chars received/packet. */
    fp1 = &Records[R_aCHR][i].fCount;
    fp2 = &Records[R_aPKTR][i].fCount;
    Records[R_aCHPKTR][i].fCount = *fp2 ? *fp1 / *fp2 : 0.0;
    if (time) {
        *fp1 /= time;
        *fp2 /= time;
    }
}

/*
**  Compute the differences between the old and new values of the stats
**  structures and save the results in a log record.  Since not all
**  lines need have been configured on a host, set it to NOTCONF if
**  necessary.  The StatsReset variable holds all totals since the last
**  reset command.
*/
static void
computediffs()
{
    LINESTATS		*old;
    LINESTATS		*cur;
    LINESTATS		*res;
    int			i;
    struct hostent	*hp;
    unsigned long	*paddr;

    /* global stats */
    StatsReset.ipup +=
	Records[R_CIPUP]->Count = StatsCur.ipup - StatsOld.ipup;
    StatsOld.ipup = StatsCur.ipup;

    StatsReset.ipln +=
	Records[R_CIPLN]->Count = StatsCur.ipln - StatsOld.ipln;
    StatsOld.ipln = StatsCur.ipln;

    StatsReset.opup +=
	Records[R_COPUP]->Count = StatsCur.opup - StatsOld.opup;
    StatsOld.opup = StatsCur.opup;

    StatsReset.opln +=
	Records[R_COPLN]->Count = StatsCur.opln - StatsOld.opln;
    StatsOld.opln = StatsCur.opln;

    /* line stats */
    old = StatsOld.ln;
    cur = StatsCur.ln;
    res = StatsReset.ln;
    for (i = 0; i < StatsCur.ndu; i++, old++, res++) {
        if (cur->ln == i) {
            /* line i is configured and new data was sent */
	    Records[R_STATE][i].Count = cur->flags;
            res->cchr += Records[R_CCHR][i].Count = cur->cchr - old->cchr;
            old->cchr = cur->cchr;

            /* sum up characters received for global stats */
            Records[R_gCHR]->Count += Records[R_CCHR][i].Count;
            res->cchs += Records[R_CCHS][i].Count = cur->cchs - old->cchs;
            old->cchs = cur->cchs;

            /* sum up characters sent for global stats */
            Records[R_gCHS]->Count += Records[R_CCHS][i].Count;
            res->cpsip += Records[R_CPSIP][i].Count = cur->cpsip - old->cpsip;
            old->cpsip = cur->cpsip;
            res->cprip += Records[R_CPRIP][i].Count = cur->cprip - old->cprip;
            old->cprip = cur->cprip;

            res->ierror +=
		Records[R_IERROR][i].Count = cur->ierror - old->ierror;
            old->ierror = cur->ierror;
		Records[R_gIERROR]->Count += Records[R_IERROR][i].Count;

            res->oerror +=
		Records[R_OERROR][i].Count = cur->oerror - old->oerror;
            old->oerror = cur->oerror;
            Records[R_gOERROR]->Count += Records[R_OERROR][i].Count;

	    /* Handle wrap-around */
	    Records[R_CTPBUSY][i].fCount = cur->ctpbusy < old->ctpbusy
	    ? (float)((unsigned int)cur->ctpbusy - (unsigned int)old->ctpbusy)
	    : (float)(cur->ctpbusy - old->ctpbusy);
            res->ctpbusy += (int)Records[R_CTPBUSY][i].fCount;
            old->ctpbusy = cur->ctpbusy;

                /* Counter wrapped around. */
	    Records[R_CTPIDLE][i].fCount = cur->ctpidle < old->ctpidle
	    ? (unsigned int)cur->ctpidle - (unsigned int)old->ctpidle
	    : cur->ctpidle - old->ctpidle;

            res->ctpidle += (int)Records[R_CTPIDLE][i].fCount;
            old->ctpidle = cur->ctpidle;

	    res->resc += Records[R_ESCR][i].Count = cur->resc - old->resc;
	    old->resc = cur->resc;
	    res->sesc += Records[R_ESCS][i].Count = cur->sesc - old->sesc;
	    old->sesc = cur->sesc;

	    /* Get the nost on the other end of the line, if different. */
	    cur->dest.s_addr = ntohl(cur->dest.s_addr);
	    if (old->dest.s_addr != cur->dest.s_addr) {
                paddr = &cur->dest.s_addr;
	        if (*paddr) {
		    if (hp = gethostbyaddr(paddr, sizeof *paddr, AF_INET)) {
		        (void)strncpy(OtherHost[i], hp->h_name,
				sizeof OtherHost[i] - 1);
			OtherHost[i][HOSTNAMESIZE - 1] = '\0';
		    }
		    else
		        (void)strcpy(OtherHost[i], inet_ntoa(*paddr));
		}
		else
		    (void)strcpy(OtherHost[i], "UNINITIALIZED");
	        res->dest.s_addr = old->dest.s_addr = cur->dest.s_addr;
	    }

            res->ln = old->ln = cur->ln;
            cur->ln = LINE_NOTCONF;
            cur++;
	    computeavgs(i);
	}
        else {
	    /* line i not configured, no new data was sent */
            res->ln = old->ln = LINE_NOTCONF;
            if ((CurrWind == W_LINE || CurrWind == W_TOTAL) && CurrLine == i)
                LineNotConf(i);
	}
    }

    /* finished storing this set of data, increment for the next set */
    if (++newdata >= MAXINTERVALS)
        newdata = 0;

    /* save current time value */
    StatsOld.when = StatsCur.when;
}


/*
**  Convert fields in struct to host order
*/
FromNetworkOrder(sp)
    DIALSTATS	*sp;
{
    int		i;
    LINESTATS	*lsp;

    sp->when = ntohl((unsigned long)sp->when);
    for (i = 0; i < 3; i++)
	sp->avenrun[i] = ntohl(sp->avenrun[i]);
    for (i = 0; i < CPUSTATES; i++)
	sp->cputime[i] = ntohl(sp->cputime[i]);
    sp->ipup = ntohl(sp->ipup);
    sp->ipln = ntohl(sp->ipln);
    sp->opln = ntohl(sp->opln);
    sp->opup = ntohl(sp->opup);
    sp->ndu = ntohl(sp->ndu);
    for (lsp = sp->ln; lsp < &sp->ln[sp->ndu]; lsp++) {
	lsp->cchr = ntohl(lsp->cchr);
	lsp->cchs = ntohl(lsp->cchs);
	lsp->cpsip = ntohl(lsp->cpsip);
	lsp->cprip = ntohl(lsp->cprip);
	lsp->flags = ntohl(lsp->flags);
	lsp->ctpbusy = ntohl(lsp->ctpbusy);
	lsp->ctpidle = ntohl(lsp->ctpidle);
	lsp->sesc = ntohl(lsp->sesc);
	lsp->resc = ntohl(lsp->resc);
	lsp->dest.s_addr = ntohl(lsp->dest.s_addr);
	lsp->ierror = ntohl(lsp->ierror);
	lsp->oerror = ntohl(lsp->oerror);
	lsp->ln = ntohl(lsp->ln);
    }
}


/*
**  Compute tty percents per line.
*/
static void
computettypercent()
{
    float	ttysum;
    float	percent;
    int		i;
    DIALLOGREC	*busyp;
    DIALLOGREC	*idlep;

    busyp = Records[R_CTPBUSY];
    idlep = Records[R_CTPIDLE];
    for (i = 0; i < MAX_NDU; i++, busyp++, idlep++) {
        ttysum = busyp->fCount + idlep->fCount;
        if (ttysum == 0.0)
            continue;
        percent = (busyp->fCount / ttysum) * 100.0;
        busyp->fCount = percent;
        percent = (idlep->fCount / ttysum) * 100.0;
        idlep->fCount = percent;
    }
}


/*
**  Read data from the socket.
*/
static void
readsocket(fd)
    int			fd;
{
    char		*timep;
    char		*p;
    int			n;
    int			ntot;
    unsigned long	size;

    /* Find out how much to read. */
    if (read(fd, (caddr_t)&size, sizeof size) != sizeof size)
        fatal("Can't read size of stats");
    size = ntohl(size);

    /* Read in all the data. */
    for (ntot = 0, p = (char *)&StatsCur; ntot < size; ntot += n, p += n)
        if ((n = read(fd, p, size - ntot)) <= 0)
            fatal("Read from server failed");
    FromNetworkOrder(&StatsCur);

    /* Update the time. */
    timep = ctime(&StatsCur.when);
    if (p = strchr(timep, '\n'))
	*p = '\0';
    Wmove(Wtitle, 1, 36);
    Wprintw(Wtitle, "%s", timep);

    /* Update the status fields. */
    UpdateStatus();
    Wrefresh(Wtitle);

    /* Compute differences, update the records. */
    computediffs();
    computettypercent();

    /* Update the graphs for the displayed window */
    if (CurrWind == W_GLOBAL || CurrWind == W_LINE)
        UpdateGraphs();
    else if (CurrWind == W_TOTAL)
        UpdateTotals();
    Wrefresh(Wprompt);
}



/*
**  Process the request read from the terminal.
*/
static void
processrequest(buff)
    char	*buff;
{
    char	*p;
    int		arg;
    int		c;

    for (arg = NOTSET, p = buff; *p && isspace(*p); p++)
	;

    for (c = *p; *++p && isspace(*p); )
	;

    switch (c) {

    case 'g':
	globalstats();
	return;

    case 'q':
	ext();
	/* NOTREACHED */

    case 'l':
	if (isdigit(*p) && (arg = atoi(p)) < MAX_NDU) {
	    linestats(arg);
	    return;
	}

    case 't':
	if (*p == 'g') {
	    totalstats(NOTSET);
	    return;
	}
	if (isdigit(*p) && (arg = atoi(p)) < MAX_NDU) {
	    totalstats(arg);
	    return;
	}
	break;

    case 'r':
	switch (*p) {
	case 'n':
	    reset(FALSE);
	    return;
	case 'y':
	    reset(TRUE);
	    return;
	}
	break;
    }

    helpwindow();
}


/*
**  Read input from the user.
*/
readterminal()
{
    static char	buff[MAXREQUEST];
    static int	curpos;
    int		count;
    int		done;
    int		i;
    char	*p;

    /* Refresh the screen, read if there is any input. */
    Wrefresh(Wprompt);
    if (ioctl(0, FIONREAD, (caddr_t)&count) >= 0 && count <= 0)
	return;

    p = &buff[curpos];
    for (done = FALSE, i = 0; i < count && !done; i++) {
	switch (*p = wgetch(Wprompt)) {
	default:
	    if (p < &buff[sizeof buff - 1])
		p++;
	    break;
	case '\n':
	case '\r':
	    *p = '\0';
	    done = TRUE;
	    break;
	case '\177':
	case '\b':
	    if (p > buff)
		p--;
	    break;
	case '\f':
	    Wrefresh(curscr);
	    break;
	}
    }

    /* Redraw the line. */
    curpos = p - buff;
    Wclear(Wprompt);
    Wprintw(Wprompt, " ? ");
    for (p = buff, i = 0; i < curpos; i++, p++)
	Wprintw(Wprompt, "%c", *p);
    Wrefresh(Wprompt);

    /* If we got a full line, execute it. */
    if (done) {
	curpos = 0;
	processrequest(buff);
	Wclear(Wprompt);
	Wprintw(Wprompt, " ? ");
	Wrefresh(Wprompt);
    }
}



/*
**  Allocate storage for global log records
*/
static void
AllocGStor(i, yloc)
    int		i;
    int		yloc;
{
    DIALLOGREC	*drp;

    if (yloc >= maxyloc)
        fatal("Screen too small (global stats)");
    if ((drp = (DIALLOGREC *)malloc(sizeof *drp)) == NULL)
        fatal("Can't malloc storage for global log record");
    drp->yloc = yloc;
    drp->Old = 0;
    drp->Count = 0;
    drp->fCount = 0.0;
    drp->Line = 0;
    drp->Window = W_GLOBAL;
    Records[i] = drp;
}


/*
**  Allocate storage for line log records
*/
static void
AllocLStor(i, yloc)
    int		i;
    int		yloc;
{
    DIALLOGREC	*drp;
    int		j;

    if (yloc >= maxyloc)
        fatal("Screen too small (line stats)");
    if ((drp = (DIALLOGREC *)malloc(MAX_NDU * sizeof *drp)) == NULL)
        fatal("Can't malloc storage for line log record");

    for (Records[i] = drp, j = 0; j < MAX_NDU; j++, drp++) {
        drp->yloc = yloc;
        drp->Old = 0;
        drp->Count = 0;
	drp->fCount = 0.0;
        drp->Line = j;
        drp->Window = W_LINE;
    }
}


/*
**  Initialize data structures.
*/
static void
initdata()
{
    int			i;
    int			j;
    LINESTATS		*old;
    LINESTATS		*cur;
    LINESTATS		*res;
    struct timeval	tv;
    AVERAGES		*aptr;

    CurrWind = NOTSET;
    CurrLine = NOTSET;
    freset = FALSE;
    newdata = 0;
    maxyloc = LINES - 5;	/* 4 lines at top, one for prompt at bottom */

    StatsCur.when = gettimeofday(&tv, (struct timezone *)NULL) < 0
		    ? 0 : tv.tv_sec;
    StatsCur.avenrun[0] = 0;
    StatsCur.avenrun[1] = 0;
    StatsCur.avenrun[2] = 0;

    for (i = 0; i < CPUSTATES; i++)
        StatsCur.cputime[i] = 0;

    StatsReset.ipup = StatsCur.ipup = StatsOld.ipup = 0;
    StatsReset.ipln = StatsCur.ipln = StatsOld.ipln = 0;
    StatsReset.opln = StatsCur.opln = StatsOld.opln = 0;
    StatsReset.opup = StatsCur.opup = StatsOld.opup = 0;

    old = StatsOld.ln;
    cur = StatsCur.ln;
    res = StatsReset.ln;
    for (i = 0; i < StatsCur.ndu; i++, cur++, old++, res++) {
	/* Assume all lines configured, if we found out it's not, then
	 * we will save the correct value in StatsOld. */
        cur->ln = LINE_NOTCONF;
        res->ln = old->ln = i;
	res->dest.s_addr = cur->dest.s_addr = old->dest.s_addr = 0L;
        res->cchr = cur->cchr = old->cchr = 0;
        res->cchs = cur->cchs = old->cchs = 0;
        res->cpsip = cur->cpsip = old->cpsip = 0;
        res->cprip = cur->cprip = old->cprip = 0;
        res->ierror = cur->ierror = old->ierror = 0;
        res->oerror = cur->oerror = old->oerror = 0;
        res->flags = cur->flags = old->flags = 0;
        res->ctpbusy = cur->ctpbusy = old->ctpbusy = 0;
        res->ctpidle = cur->ctpidle = old->ctpidle = 0;
        res->resc = cur->resc = old->resc = 0;
        res->sesc = cur->sesc = old->sesc = 0;

	/* Set up the averages table. */
	for (j = 0; j < MAXINTERVALS; j++) {
	    aptr = &Averages[i][j];
	    aptr->chs = 0;
	    aptr->pkts = 0;
	    aptr->chr = 0;
	    aptr->pktr = 0;
	    aptr->rescs = 0;
	    aptr->sescs = 0;
	    aptr->sec = 0;
	}

	(void)strcpy(OtherHost[i], "UNINITIALIZED");
    }

    /* Get storage for loc records, assign y locations. */
    i = 1;
    AllocGStor(R_CIPUP, i++);
    AllocGStor(R_CIPLN, i++);
    AllocGStor(R_COPUP, i++);
    AllocGStor(R_COPLN, i++);
    AllocGStor(R_gIERROR, i++);
    AllocGStor(R_gOERROR, i++);
    AllocGStor(R_gCHS, i);
    AllocGStor(R_gCHR, i++);

    /* Local storage.  Note that some share the same location. */
    i = 1;
    AllocLStor(R_CPSIP, i++);
    AllocLStor(R_CPRIP, i++);
    AllocLStor(R_CTPBUSY, i);
    AllocLStor(R_CTPIDLE, NOTSET);
    AllocLStor(R_aPCTESC, i++);
    AllocLStor(R_ESCS, NOTSET);
    AllocLStor(R_ESCR, NOTSET);
    AllocLStor(R_CCHS, i);
    AllocLStor(R_CCHR, i++);
    AllocLStor(R_aCHPKTS, i);
    AllocLStor(R_aCHPKTR, i++);
    AllocLStor(R_aCHS, i);
    AllocLStor(R_aCHR, i++);
    AllocLStor(R_aPKTS, i);
    AllocLStor(R_aPKTR, i++);
    AllocLStor(R_IERROR, i++);
    AllocLStor(R_OERROR, i++);
    AllocLStor(R_STATE, i++);
}



/*
**  Blank out a row of stars.
*/
removeplot(oldx, newx, yloc)
    int		oldx;
    int		newx;
    int		yloc;
{
    for (; oldx > newx; oldx--)
        mvwaddch(Wgraph, yloc, oldx - 1, ' ');
}


/*
**  Add a row of stars.
*/
addplot(oldx, newx, yloc)
    int		oldx;
    int		newx;
    int		yloc;
{
    while (++oldx <= newx)
        mvwaddch(Wgraph, yloc, oldx - 1, '*');
}


/*
 * globalstats -- set-up global statistics window for monitoring
 */
globalstats()
{
    int		line;

    /* window already global, don't change it */
    if (CurrWind == W_GLOBAL)
        return;

    CurrWind = W_GLOBAL;
    Wclear(Whelp);
    Wclear(Wtypes);
    Wclear(Wgraph);
    Wrefresh(Whelp);

    /* print title and scale */
    line = 0;
    Wmove(Wtypes, line, 0);
    Wprintw(Wtypes, "GLOBAL STATISTICS:");
    Wmove(Wgraph, line++, 0);
    Wprintw(Wgraph, "1        10        20        30        40");

    InitDisplay(R_CIPUP, 0);
    InitDisplay(R_CIPLN, 0);
    InitDisplay(R_COPUP, 0);
    InitDisplay(R_COPLN, 0);
    InitDisplay(R_gCHS, 0);
    InitDisplay(R_gCHR, 0);
    InitDisplay(R_gIERROR,0);
    InitDisplay(R_gOERROR,0);
    Wrefresh(Wtypes);
    Wrefresh(Wgraph);
    UpdateGraphs();
}


/*
**  Set-up line statistics window for monitoring.
*/
linestats(ln)
    int		ln;
{
    int		line;

    if (CurrWind == W_LINE && CurrLine == ln)
        return;

    if (StatsOld.ln[ln].ln == LINE_NOTCONF) {
        LineNotConf(ln);
        return;
    }

    CurrWind = W_LINE;
    CurrLine = ln;
    Wclear(Whelp);
    Wclear(Wtypes);
    Wclear(Wgraph);
    Wrefresh(Whelp);

    line = 0;
    Wmove(Wtypes, line, 0);
    Wprintw(Wtypes, "LINE TO %.20s:", OtherHost[ln]);
    Wmove(Wgraph, line++, 0);
    Wprintw(Wgraph, "1        10        20        30        40");

    InitDisplay(R_CPSIP, ln);
    InitDisplay(R_CPRIP, ln);
    InitDisplay(R_CTPBUSY, ln);
    InitDisplay(R_aPCTESC, ln);
    InitDisplay(R_CCHS, ln);
    InitDisplay(R_CCHR, ln);
    InitDisplay(R_aCHPKTS, ln);
    InitDisplay(R_aCHPKTR, ln);
    InitDisplay(R_aCHS, ln);
    InitDisplay(R_aCHR, ln);
    InitDisplay(R_aPKTS, ln);
    InitDisplay(R_aPKTR, ln);
    InitDisplay(R_STATE, ln);
    InitDisplay(R_IERROR, ln);
    InitDisplay(R_OERROR, ln);
    Wrefresh(Wtypes);
    Wrefresh(Wgraph);
    UpdateGraphs();
}


/*
**  Set-up total statistics window for monitoring.
*/
totalstats(ln)
    int		ln;
{
    int		line;

    if (CurrWind == W_TOTAL && CurrLine == ln)
        return;

    if (ln != NOTSET && StatsOld.ln[ln].ln == LINE_NOTCONF) {
        LineNotConf(ln);
        return;
    }

    CurrWind = W_TOTAL;
    CurrLine = ln;
    Wclear(Whelp);
    Wclear(Wtypes);
    Wclear(Wgraph);
    Wrefresh(Whelp);

    /* Pint title and scale. */
    line = 0;
    Wmove(Wtypes, line, 0);

    if (ln == NOTSET)
        Wprintw(Wtypes, "GLOBAL TOTALS:");
    else
        Wprintw(Wtypes, "LINE TOTALS TO %.20s:", OtherHost[ln]);
    Wmove(Wgraph, line++, 0);
    Wprintw(Wgraph, " TOTAL COUNTS");

    if (CurrLine == NOTSET) {
	/* Global records. */
        InitDisplay(R_CIPUP, 0);
        InitDisplay(R_CIPLN, 0);
        InitDisplay(R_COPUP, 0);
        InitDisplay(R_COPLN, 0);
        InitDisplay(R_gCHS, 0);
        InitDisplay(R_gCHR, 0);
        InitDisplay(R_gIERROR,0);
        InitDisplay(R_gOERROR,0);
    }
    else  {
	/* Line records. */
        InitDisplay(R_CPSIP, ln);
        InitDisplay(R_CPRIP, ln);
        InitDisplay(R_CTPBUSY, ln);
        InitDisplay(R_aPCTESC, ln);
        InitDisplay(R_CCHS, ln);
        InitDisplay(R_CCHR, ln);
        InitDisplay(R_aCHPKTS, ln);
        InitDisplay(R_aCHPKTR, ln);
	InitDisplay(R_IERROR, ln);
	InitDisplay(R_OERROR, ln);
    }
    Wrefresh(Wtypes);
    Wrefresh(Wgraph);
    UpdateTotals();
}


/*
**  If flag is TRUE, reset totals to 0 and start accumulating, else use
**  real totals as sent from monitored site.
*/
reset(flag)
    int		flag;
{
    int		i;
    LINESTATS	*res;

    freset = flag;
    if (freset) {
        StatsReset.ipup = 0;
        StatsReset.ipln = 0;
        StatsReset.opup = 0;
        StatsReset.opln = 0;
        for (res = StatsReset.ln, i = 0; i < StatsCur.ndu; i++, res++) {
            res->cchr = 0;
            res->cchs = 0;
            res->cpsip = 0;
            res->cprip = 0;
            res->ierror = 0;
            res->oerror = 0;
            res->ctpbusy = 0;
            res->ctpidle = 0;
	    res->resc = 0;
	    res->sesc = 0;
	}
    }
    if (CurrWind == W_TOTAL)
        UpdateTotals();
}


/*
**  Help function, displays the windows that are available.
*/

helpwindow()
{
    int		line;

    CurrWind = W_HELP;
    Wclear(Whelp);
    Wclear(Wtypes);
    Wclear(Wgraph);
    Wrefresh(Wtypes);
    Wrefresh(Wgraph);

    /* Print title. */
    line = 0;
    Wmove(Whelp, line++, 0);
    Wprintw(Whelp, "COMMANDS:\n");
    Wprintw(Whelp, "  l#\t\tSerial line statistics for line #\n");
    Wprintw(Whelp, "  t# | tg\tTotal statistics for line # or g for global\n");
    Wprintw(Whelp, "  h or ?\tHelp\n");
    Wprintw(Whelp, "  ^L\t\tRedraw\n");
    Wprintw(Whelp, "  ry\t\tUse the reset totals\n");
    Wprintw(Whelp, "  rn\t\tUse totals since last reboot\n");
    Wprintw(Whelp, "  q\t\tQuit\n");
    Wrefresh(Whelp);
}


/*
**  Clear screen and print message if the line to be monitored is not
**  configured.
*/
LineNotConf(ln)
    int		ln;
{
    Wclear(Whelp);
    Wclear(Wgraph);
    Wclear(Wtypes);
    Wrefresh(Whelp);
    Wmove(Wtypes, 0, 0);
    Wprintw(Wtypes, "SERIAL LINE %d NOT CONFIGURED", ln);
    CurrWind = W_HELP;
    Wrefresh(Wtypes);
    Wrefresh(Wgraph);
}


/*
**  Initialize one type of log record's data structure for the line to be
**  displayed and display the title.
*/
InitDisplay(t, l)
    int		t;
    int		l;
{
    DIALLOGREC	*drp;

    drp = &Records[t][l];
    drp->Old = 0;

    switch (t) {
    default:
	Wmove(Wtypes, drp->yloc, 0);
	Wprintw(Wtypes, "%s", TypeDescriptions[t]);
	mvwaddch(Wtypes, drp->yloc, 29, ':');
	break;
    case R_CTPBUSY:
    case R_CCHS:
    case R_gCHS:
    case R_aCHS:
    case R_aPKTS:
    case R_aCHPKTS:
	Wmove(Wtypes, drp->yloc, 0);
	Wprintw(Wtypes, "%s", TypeDescriptions[t]);
	break;
    case R_aPCTESC:
    case R_CCHR:
    case R_gCHR:
    case R_aCHR:
    case R_aPKTR:
    case R_aCHPKTR:
	Wmove(Wgraph, drp->yloc, 0);
	Wprintw(Wgraph, "%s", TypeDescriptions[t]);
	break;
    }
}


/*
**  Update the system status lines.
*/
UpdateStatus()
{
    float	pct[CPUSTATES];
    int		i;
    int		w;

    /* sum up the time spent in each cpu state and initialize pct */
    for (w = 0, i = 0; i < CPUSTATES; i++) {
        pct[i] = 0.0;
        w += StatsCur.cputime[i];
    }

    /* compute the percent of time spent in each cpu state */
    if (w)
        for (i = 0; i < CPUSTATES; i++)
            pct[i] = ((float)StatsCur.cputime[i] / (float)w) * 100.0;

    Wmove(Wtitle, 2, 26);
    Wprintw(Wtitle, "%6.2f", pct[0]);
    Wmove(Wtitle, 2, 42);
    Wprintw(Wtitle, "%6.2f", pct[2]);
    Wmove(Wtitle, 2, 56);
    Wprintw(Wtitle, "%6.2f", pct[1]);
    Wmove(Wtitle, 2, 70);
    Wprintw(Wtitle, "%6.2f", pct[3]);
    Wmove(Wtitle, 3, 37);
    Wprintw(Wtitle, "%5.2f", ((float)StatsCur.avenrun[0]) / 100.);
    Wmove(Wtitle, 3, 44);
    Wprintw(Wtitle, "%5.2f", ((float)StatsCur.avenrun[1]) / 100.);
    Wmove(Wtitle, 3, 51);
    Wprintw(Wtitle, "%5.2f", ((float)StatsCur.avenrun[2]) / 100.);
}


/*
**  Update the plots in the active window. Does not update the Old of
**  any log record not being displayed or that prints a floating point
**  number.
*/
UpdateGraphs()
{
    DIALLOGREC	*drp;

    if (CurrWind == W_GLOBAL) {
	/* Update all global plots */
        UpdatePlot(Records[R_CIPUP]);
        UpdatePlot(Records[R_CIPLN]);
        UpdatePlot(Records[R_COPUP]);
        UpdatePlot(Records[R_COPLN]);
        drp = Records[R_gCHS];
        PrintNum(Wtypes, (unsigned long)drp->Count, drp->yloc, 17);
        drp->Old = drp->Count;
        drp = Records[R_gCHR];
        PrintNum(Wgraph, (unsigned long)drp->Count, drp->yloc, 21);
        drp->Old = drp->Count;
        drp = Records[R_gIERROR];
        PrintNum(Wgraph, (unsigned long)drp->Count, drp->yloc, 0);
        drp->Old = drp->Count;
        drp = Records[R_gOERROR];
        PrintNum(Wgraph, (unsigned long)drp->Count, drp->yloc, 0);
        drp->Old = drp->Count;
    }
    else if (CurrWind == W_LINE) {
	/* Update the displayed line's stats */
        Wmove(Wtypes, 0, 8);
	Wclrtoeol(Wtypes);
	Wmove(Wtypes, 0, 8);
	Wprintw(Wtypes, "%-.20s", OtherHost[CurrLine]);
	mvwaddch(Wtypes, 0, 29, ':');
        UpdatePlot(&Records[R_CPSIP][CurrLine]);
        UpdatePlot(&Records[R_CPRIP][CurrLine]);
        drp = &Records[R_CTPBUSY][CurrLine];
        PrintPct(Wtypes, drp->fCount, drp->yloc, 19);
        drp = &Records[R_CCHS][CurrLine];
        PrintNum(Wtypes, (unsigned long)drp->Count, drp->yloc, 17);
        drp->Old = drp->Count;
        drp = &Records[R_aCHPKTS][CurrLine];
        PrintNumf(Wtypes, drp->fCount, drp->yloc, 19);
        drp = &Records[R_aCHS][CurrLine];
        PrintNumf(Wtypes, drp->fCount, drp->yloc, 19);
        drp = &Records[R_aPKTS][CurrLine];
        PrintNumf(Wtypes, drp->fCount, drp->yloc, 19);
        drp = &Records[R_aPCTESC][CurrLine];
        PrintPct(Wgraph, drp->fCount, drp->yloc, 28);
        drp = &Records[R_CCHR][CurrLine];
        PrintNum(Wgraph, (unsigned long)drp->Count, drp->yloc, 26);
        drp->Old = drp->Count;
        drp = &Records[R_aCHPKTR][CurrLine];
        PrintNumf(Wgraph, drp->fCount, drp->yloc, 28);
        drp = &Records[R_aCHR][CurrLine];
        PrintNumf(Wgraph, drp->fCount, drp->yloc, 28);
        drp = &Records[R_aPKTR][CurrLine];
        PrintNumf(Wgraph, drp->fCount, drp->yloc, 28);
	drp = &Records[R_STATE][CurrLine];
	PrintState(Wgraph, drp->Count, drp->yloc, 5);
	drp = &Records[R_IERROR][CurrLine];
	PrintNum(Wgraph, (unsigned long)drp->Count, drp->yloc, 0);
	drp = &Records[R_OERROR][CurrLine];
	PrintNum(Wgraph, (unsigned long)drp->Count, drp->yloc, 0);
    }
    Wrefresh(Wtypes);
    Wrefresh(Wgraph);
}

/*
**  Update the total counts being displayed
*/
UpdateTotals()
{
    int			i;
    int			yloc;
    LINESTATS		*lsp;
    DIALSTATS		*sp;
    float		ttysum;
    unsigned long	totchr;
    unsigned long	totchs;
    unsigned long	totierror;
    unsigned long	totoerror;

    sp = freset ? &StatsReset : &StatsOld;

    if (CurrLine == NOTSET) {
	/* Global totals */
        PrintNum(Wgraph, sp->ipup, Records[R_CIPUP]->yloc, 0);
        PrintNum(Wgraph, sp->ipln, Records[R_CIPLN]->yloc, 0);
        PrintNum(Wgraph, sp->opup, Records[R_COPUP]->yloc, 0);
        PrintNum(Wgraph, sp->opln, Records[R_COPLN]->yloc, 0);

        /* total chars received and sent and total silo overflows */
	totchs = 0;
	totchr = 0;
	totierror = 0;
	totoerror = 0;
        for (i = 0; i < StatsCur.ndu; i++) {
            totchs += sp->ln[i].cchs;
            totchr += sp->ln[i].cchr;
	    totierror += sp->ln[i].ierror;
	    totoerror += sp->ln[i].oerror;
	}
        PrintNum(Wtypes, totchs, Records[R_gCHS]->yloc, 17);
        PrintNum(Wgraph, totchr, Records[R_gCHR]->yloc, 21);
	PrintNum(Wgraph, totierror, Records[R_gIERROR]->yloc, 17);
	PrintNum(Wgraph, totoerror, Records[R_gOERROR]->yloc, 17);
    }
    else {
	/* Displayed line totals */
        Wmove(Wtypes, 0, 15);
	Wclrtoeol(Wtypes);
        Wmove(Wtypes, 0, 15);
	Wprintw(Wtypes, "%-.14s", OtherHost[CurrLine]);
	mvwaddch(Wtypes, 0, 29, ':');
        lsp = &sp->ln[CurrLine];
        PrintNum(Wgraph, lsp->cpsip, Records[R_CPSIP][CurrLine].yloc, 0);
        PrintNum(Wgraph, lsp->cprip, Records[R_CPRIP][CurrLine].yloc, 0);
        PrintNum(Wgraph, lsp->ierror, Records[R_IERROR][CurrLine].yloc, 0);
        PrintNum(Wgraph, lsp->oerror, Records[R_OERROR][CurrLine].yloc, 0);
        if (ttysum = (float)(lsp->ctpbusy + lsp->ctpidle))
	    PrintPct(Wtypes,
		((float)lsp->ctpbusy / (float)ttysum) * 100.0,
		Records[R_CTPBUSY][CurrLine].yloc, 19);
	else
	    PrintPct(Wtypes, 0.0, Records[R_CTPBUSY][CurrLine].yloc, 19);

	yloc = Records[R_aPCTESC][CurrLine].yloc;
	Wmove(Wgraph, yloc, 0);
	Wclrtoeol(Wgraph);
	Wmove(Wgraph, yloc, 0);
        if (lsp->cchs)
	    Wprintw(Wgraph, "%10.5f%%s %10.5f%%r",
	      ((float)lsp->sesc / (float)lsp->cchs) * 100.0,
	      ((float)lsp->resc / (float)lsp->cchr) * 100.0);
	else
	    Wprintw(Wgraph, "%10.5f%%", (float)lsp->cchs);

	PrintNum(Wtypes, lsp->cchs, Records[R_CCHS][CurrLine].yloc, 17);
        PrintNum(Wgraph, lsp->cchr, Records[R_CCHR][CurrLine].yloc, 26);
    }
    Wrefresh(Wtypes);
    Wrefresh(Wgraph);
}


/*
**  Print the number in the specified window at the specified location.
*/
PrintNum(wn, num, yloc, xloc)
    WINDOW		*wn;
    unsigned long	num;
    int			yloc;
    int			xloc;
{
    Wmove(wn, yloc, xloc);
    Wclrtoeol(wn);
    Wmove(wn, yloc, xloc);
    Wprintw(wn, "%10d", num);
}


/*
**  Print the float in the specified window at the specified location.
*/
PrintNumf(wn, num, yloc, xloc)
    WINDOW	*wn;
    float	num;
    int		yloc;
    int		xloc;
{
    Wmove(wn, yloc, xloc);
    Wclrtoeol(wn);
    Wmove(wn, yloc, xloc);
    Wprintw(wn, "%8.2f  ", num);
}


/*
**  Print the percent of time a tty is busy.
*/
PrintPct(wn, num, yloc, xloc)
    WINDOW	*wn;
    float	num;
    int		yloc;
    int		xloc;
{
    Wmove(wn, yloc, xloc);
    Wclrtoeol(wn);
    Wmove(wn, yloc, xloc);
    Wprintw(wn, "%8.2f %%", num);
}

PrintState(wn, state, yloc, xloc)
    WINDOW	*wn;
    int		state;
    int		yloc;
    int		xloc;
{
    int		i;
    int		j;

    Wmove(wn, yloc, xloc);
    Wclrtoeol(wn);
    Wmove(wn,yloc,xloc);
    for (i = 0, j = 1; i < 32 ; i++, j <<= 1)
	if (state & j)
	    Wprintw(wn, "%s ", DUStateMessages[i]);
}


/*
**  Update the plot for the log record passed in.
*/
UpdatePlot(drp)
    DIALLOGREC	*drp;
{
   if (drp->Count == drp->Old)
       return;

   if (drp->Count < 0) {
       Wmove(Wgraph, drp->yloc, 0);
       Wclrtoeol(Wgraph);
       drp->Count = 0;
       drp->Old = 0;
   }
   else if (drp->Count > MAXPLOTS && drp->Old > MAXPLOTS) {
       PrintNum(Wgraph, (unsigned long)drp->Count, drp->yloc, 0);
       drp->Old = drp->Count;
   }
   else if (drp->Count > MAXPLOTS && drp->Old <= MAXPLOTS) {
       PrintNum(Wgraph, (unsigned long)drp->Count, drp->yloc, 0);
       drp->Old = drp->Count;
   }
    else if (drp->Count <= MAXPLOTS && drp->Old > MAXPLOTS) {
       Wmove(Wgraph, drp->yloc, 0);
       Wclrtoeol(Wgraph);
       addplot(0, drp->Count, drp->yloc);
       drp->Old = drp->Count;
   }
   else if (drp->Count > drp->Old) {
       /* add to old graph */
       addplot(drp->Old, drp->Count, drp->yloc);
       drp->Old = drp->Count;
   }
   else if (drp->Count < drp->Old) {
       /* remove from old graph */
       removeplot(drp->Old, drp->Count, drp->yloc);
       drp->Old = drp->Count;
   }
}


/*
**  Look up a hostname, fill in the socket.  Return -1 on error.
*/
static int
hosttosocket(p, psin)
    char		*p;
    struct sockaddr_in	*psin;
{
    struct hostent	*hp;
    unsigned long	w;

    bzero((caddr_t)psin, sizeof *psin);
    if (isdigit(*p)) {
	if ((w = inet_addr(p)) == (unsigned long)-1)
	    return -1;
	psin->sin_addr.s_addr = w;
	psin->sin_family = AF_INET;
	return 0;
    }

    if ((hp = gethostbyname(p)) == NULL) {
	(void)fprintf(stderr, "Can't get address of \"%s\":  ", p);
#ifndef	HOST_NOT_FOUND
	(void)fprintf(stderr, "Host not found\n");
#else
	switch (h_errno) {
	default:
	    (void)fprintf(stderr, "Unknown error: %d\n", h_errno);
	    break;
	case HOST_NOT_FOUND:
	    (void)fprintf(stderr, "Host not found\n");
	    break;
	case TRY_AGAIN:
	    (void)fprintf(stderr, "Try again later\n");
	    break;
	case NO_RECOVERY:
	    (void)fprintf(stderr, "No recovery possible\n");
	    break;
	case NO_ADDRESS:
	    (void)fprintf(stderr, "No IP address\n");
	    break;
	}
#endif	/* NOT_NOT_FOUND */
	return -1;
    }

    psin->sin_family = hp->h_addrtype;
    bcopy(hp->h_addr, (char *)&psin->sin_addr, hp->h_length);
    return 0;
}

/*
**  Print usage message and exit.
*/
static void
usage(p)
    char	*p;
{
    (void)fprintf(stderr, "Usage error:  %s\n", p);
    (void)fprintf(stderr, "Usage: %s [-g] [-l line] [-t g|line] [site]\n",
	    progname);
    exit(1);
}


main(argc, argv)
    int			argc;
    char		*argv[];
{
    char		*p;
    struct servent	*sp;
    struct sockaddr_in	sin;
    int			i;
    int			fildes[2];
    char		*timep;
    char		buff[90];
    fd_set		readers;
    int			win;
    int			arg;
    int			localpipe;

    /* Set defaults. */
    win = W_GLOBAL;
    arg = NOTSET;
    localpipe = 0;

    /* Get options. */
    while ((i = getopt(argc, argv, "gl:pt:")) != EOF)
	switch (i) {
	default:
	    usage("Bad flag");
	    /* NOTREACHED */
	case 'g':		/* global stats, no arg */
	    win = W_GLOBAL;
	    arg = NOTSET;
	    break;
	case 'l':		/* line stats, next arg must be ln # */
	    if (!isdigit(*optarg) || (arg = atoi(optarg)) > MAX_NDU)
		usage("Bad number for -l");
	    win = W_LINE;
	    break;
	case 'p':
	    localpipe = 1;
	    break;
	case 't':		/* total stats, next arg must be */
	    if (*optarg == 'g') {
		win = W_TOTAL;
		arg = NOTSET;
	    }
	    else if (!isdigit(*optarg) || (arg = atoi(optarg)) >= MAX_NDU)
		usage("Bad number for -t");
	    win = W_TOTAL;
	    break;
	}
    argc -= optind;
    argv += optind;

    if (!localpipe) {
	switch (argc) {
	default:
	    usage("Wrong number of arguments");
	    /* NOTREACHED */
	case 0:
	    (void)gethostname(HostName, sizeof HostName);
	    break;
	case 1:
	    (void)strcpy(HostName, *argv);
	    break;
	}

	if ((sp = getservbyname(DIALMON_SERVICE, "tcp")) == NULL) {
	    (void)fprintf(stderr,
		    "Warning: %s/tcp unknown service; using port %d\n",
		    DIALMON_SERVICE, DIALMON_DEFAULT_PORT);
	    (void)sleep(2);
	}
    }
    else {
	if (argc)
	    usage("Wrong number of arguments");
	(void)gethostname(HostName, sizeof HostName);
    }

    /* Initialize screen */
    (void)initscr();
    (void)noecho();
    (void)nonl();
    (void)crmode();
    cursed++;
    if (LINES < 24 || COLS < 80)
	fatal("Screen too small");
    (void)signal(SIGINT, ext);
    Wtitle = newwin(4, 0, 0, 0);		/* 4 lines at top */
    Wgraph = newwin(maxyloc, COLS - 30, 4, 30);	/* 14 lines at center left*/
    Wtypes = newwin(maxyloc, 30, 4, 0);		/* 14 lines at center right */
    Wprompt = newwin(1, 0, LINES - 1, 0);
    Whelp = newwin(maxyloc, 0, 5, 0);
    Wmove(Wtitle, 0, 14);
    Wprintw(Wtitle, "%s Monitor: ", dip_release());
    Wmove(Wtitle, 1, 11);
    Wprintw(Wtitle, "Time of the last update: ");
    Wmove(Wtitle, 2, 5);
    Wprintw(Wtitle, "Cpu utilization USER:      %%, SYSTEM:      %%, ");
    Wprintw(Wtitle, "NICE:      %%, IDLE:      %%");
    Wmove(Wtitle, 3, 23);
    Wprintw(Wtitle, "Load average:      ,      ,      ");
    Wrefresh(Wtitle);

    /* This must be AFTER curses has been called, sigh. */
    initdata();

    switch (win) {
    default:
	helpwindow();
	break;
    case W_GLOBAL:
	globalstats();
	break;
    case W_LINE:
	linestats(arg);
	break;
    case W_TOTAL:
	totalstats(arg);
	break;
    }

    /* Connect to server */
    if (localpipe) {
	if (pipe(fildes) < 0)
	    fatal("Can't make pipe");
	switch (fork()) {
	case -1:
	    fatal("Can't fork");
	    /* NOTREACHED */
	case 0:
	    (void)close(fildes[0]);
	    (void)close(0);
	    (void)dup(fildes[1]);
	    (void)close(fildes[1]);
	    (void)execl(DIALMOND_PATH, "dialmond", (char *)NULL);
	    fatal(DIALMOND_PATH);
	    /* NOTREACHED */
	}
	(void)close(fildes[1]);
	fd = fildes[0];
    }
    else {
	if (hosttosocket(HostName, &sin) < 0) {
	    (void)sprintf(buff, "Unknown host %s", HostName);
	    fatal(buff);
	}
	if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	    fatal("Socket failed");
	sin.sin_port = sp ? sp->s_port : DIALMON_DEFAULT_PORT;
	if (connect(fd, (caddr_t)&sin, sizeof sin) < 0) {
	    (void)sprintf(buff, "Can't connect to port %d at %s",
		    ntohs(sin.sin_port), HostName);
	    fatal(buff);
	}
    }
    Wmove(Wtitle, 0, 36);
    Wprintw(Wtitle, "connected to %s", HostName);

    /* Print the current time. */
    timep = ctime(&StatsCur.when);
    if (p = strchr(timep, '\n'))
	*p = '\0';
    Wmove(Wtitle, 1, 36);
    Wprintw(Wtitle, "%s", timep);
    UpdateStatus();
    Wrefresh(Wtitle);

    /* Set up the prompt. */
    Wclear(Wprompt);
    Wprintw(Wprompt, " ? ");
    Wrefresh(Wprompt);

    /* read and plot responses */
    for ( ; ; ) {
	FD_ZERO(&readers);
	FD_SET(fd, &readers);
	FD_SET(0, &readers);
	if (select(fd + 1, &readers,
		(fd_set *)NULL, (fd_set *)NULL, (struct timeval *)NULL) > 0) {
	    if (FD_ISSET(fd, &readers))
		readsocket(fd);
	    if (FD_ISSET(0, &readers))
		readterminal();
	}
    }
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.