[comp.sources.x] v03i103: xnetload -- multiple host xload program, Part01/01

argv@island.uu.net (Dan Heller) (05/08/89)

Submitted-by: Mike Berkley <jmberkley@watnext.waterloo.edu>
Posting-number: Volume 3, Issue 103
Archive-name: xnetload/part01

[ I compiled, but didn't run this (we don't use rwhod here).  Check the
  makefile before compiling or use the imakefile.  The shar given makes
  its own directory.   --argv ]

What follows was written by the author:

xnetload gives the system load for a list of remote machines by using
rwhod statistics.  The load for the local machine is also displayed,
but is calculated using the standard Load widget routines, (this means
that xnetload needs to be setgid kmem.)

xnetload was written mostly as a tool to learning how to use the
toolkit.  I was amazed at what the toolkit can do in not very many
lines of code.

Don't forget to read the man page (there's a caveat about
"*Load*update").  Send comments, bug reports, etc.  to
jmberkley@watnext.waterloo.edu.

---------------------------------------------------------------------------
# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
#
# Wrapped by watnext!jmberkley on Tue May  2 10:56:54 EDT 1989
# Contents:  xnetload/ xnetload/README xnetload/Imakefile xnetload/xnetload.c
#	xnetload/xnetload.1 xnetload/Makefile xnetload/AUTHOR
#	xnetload/patchlevel.h
 
echo mkdir - xnetload
mkdir xnetload
chmod u=rwx,g=rx,o=rx xnetload
 
echo x - xnetload/README
sed 's/^@//' > "xnetload/README" <<'@//E*O*F xnetload/README//'
README for xnetload
Mike Berkley, April 1989

xnetload gives the system load for a list of remote machines by using
rwhod statistics.  The load for the local machine is also displayed,
but is calculated using the standard Load widget routines, (this means
that xnetload needs to be setgid kmem.)

There's an option to turn off the local load, -nolocal, which would
allow you to run with xnetload non-setgid.  Alternatively, you can
compile xnetload with -DNOLOCAL (see the Imakefile and Makefile).

xnetload was developed on a uVax running Ultrix.  It also compiles and
runs on Suns (both SunOS 3.5 and 4) and Sequents running Dynix.  Of
course, there are some little glitches: the Makefile needs to be
altered and you may need to find a memcpy() somewhere.

xnetload was written mostly as a tool to learning how to use the
toolkit.  I was amazed at what the toolkit can do in not very many
lines of code.

Since this was an experiment/learning tool, I'm sure that I did not
follow all of the correct X guidelines.

The getload() routine is just my own kludge to read rwhod stats.  It
works, but it is replaceable.  Ideally, I would like to use some kind
of system call like Sun 4's RPC versions of rwho and rup command, then
there would not be any dependency on rwhod.  If you are living on a
system that does not use rwhod, or you would like a more efficient way
of determining system load, then you can replace getload() without
breaking the rest of the program.

** Note - if you define "*Load*update", but not  **
** "xnetload*remote*update", then xnetload will  **
** set both the remote and local update times to **
** the "*Load*update" value.  If this time is    **
** much less than the rwhod update period, then  **
** the remote load displays will look more like  **
** bar graphs.                                   **

Send comments, bug reports, etc. to jmberkley@watnext.waterloo.edu.


******************************************
* Mike Berkley, University of Waterloo   *
* PAMI Lab                               *
* jmberkley@watnext.waterloo.edu         *
* {utai,uunet}!watmath!watnext!jmberkley *
******************************************
@//E*O*F xnetload/README//
chmod u=rw,g=r,o=r xnetload/README
 
echo x - xnetload/Imakefile
sed 's/^@//' > "xnetload/Imakefile" <<'@//E*O*F xnetload/Imakefile//'
#
# Imakefile for xnetload
#
# Written by: Mike Berkley, jmberkley@watnext.waterloo.edu, 1989
#
# Permission to use, copy, modify and distribute (without charge) this
# software and its documentation is granted, provided that this comment
# is retained.

#
# Since xnetload finds the local machine's load using the standard Load
# widget stuff, it needs to be able to read kmem, i.e.  setgid kmem.  If
# you do not want this, then define NOLOCAL
#        DEFINES = -O -DNOLOCAL

       INCLUDES = -I$(TOP) -I$(TOP)/X11

# Dynix doesn't have -L option on load, but I have left changing the
# XLIB specification up to individual sites.  Dynix and our Sun 3.5
# don't come with a memcpy, so you'll have to add in whichever library
# has memcpy.
/* LOCAL_LIBRARIES = $(XAWLIB) $(XMULIB) $(XTOOLLIB) $(XLIB) */
LOCAL_LIBRARIES = -L/usr/software/X11/lib -lXaw -lXmu -lXt -lX11

           SRCS = xnetload.c status.c

   INSTALLFLAGS = $(INSTKMEMFLAGS)

all: xnetload

SingleProgramTarget(xnetload,xnetload.o,,$(LOCAL_LIBRARIES))
@//E*O*F xnetload/Imakefile//
chmod u=rw,g=r,o=r xnetload/Imakefile
 
echo x - xnetload/xnetload.c
sed 's/^@//' > "xnetload/xnetload.c" <<'@//E*O*F xnetload/xnetload.c//'
/* 
 * xnetload.c: multiple xload, using rwho statistics
 *
 * Written by: Mike Berkley, jmberkley@watnext.waterloo.edu, 1989
 *
 * Permission to use, copy, modify and distribute (without charge) this
 * software and its documentation is granted, provided that this comment
 * is retained.
 *
 * The X Window system is a copyright of the Massachusetts
 * Institute of Technology
 *
 */

/*
 * xnetload uses the Load widget load proc to get the local
 * machine load.  This means that xnetload needs to be setgid
 * kmem.  If you do not want this, then define NOLOCAL when compiling.
 */

#include <stdio.h>
#include <ctype.h>
#include <netdb.h>
#include <X11/Xos.h>
#include <X11/Xlib.h>
#include <X11/IntrinsicP.h>
#include <X11/LoadP.h>
#include <X11/StringDefs.h>
#include <X11/Shell.h>
#include <X11/Form.h>
#include "patchlevel.h"


extern void GetStatus();         /* gets rwho load */
extern char *XtMalloc();
extern time_t time();
static Widget Toplevel, Formwin, Statuswin;

#define DEFREMUPDATE 60          /* remote update defaults to 60 seconds */
#define DOWNTIME     (DEFREMUPDATE * 5)
#define DOWNMSG      " DOWN"
#define RWHODHD      "/usr/spool/rwho/whod"

/* arguments to Form widget */
static Arg Formargs[] = {
    { XtNfromVert, (XtArgVal) NULL },       /* Chain vert from value widget */
    { XtNdefaultDistance, (XtArgVal) 1 },   /* default spacing */
    { XtNresizable, (XtArgVal) TRUE },      /* allow resize */
};

/* Arguments to load windows */
static Arg Statusargs[] = {
    { XtNfromVert, (XtArgVal) NULL },       /* Chain vert from .value wid */
    { XtNresizable, (XtArgVal) TRUE },      /* allow resize */
    { XtNlabel, (XtArgVal) NULL },          /* Load title */
    { XtNborderWidth, (XtArgVal) 0 },       /* width for each load wid  */
};

/* Application specific resources - turn off local load display */
static Boolean Localload;
static XtResource app_resources[] = {
	{ "localload", "LocalLoad", XtRBoolean, sizeof(Boolean),
		(Cardinal) &Localload, XtRString,
/* if NOLOCAL is defined, then no local is displayed, by default */
#ifdef NOLOCAL
"off"
#else
"on"
#endif
},
};

/* command line options for local load display */
/* If compiled NOLOCAL, then off is default, but on still allowed */
static XrmOptionDescRec app_options[] = {
#ifdef NOLOCAL
	{ "-local", "localload", XrmoptionNoArg, "on" },
#else
	{ "-nolocal", "localload", XrmoptionNoArg, "off" },
#endif
};

/* resources specific to the remote load displays */
/*
 * You're wondering why I'm doing this, right?  Well, the default update
 * for the Load widget is 5 seconds (I think), but if rwhod updates the
 * files every 60 seconds, then the load displays will look weird.  So, I
 * don't force the user to have a 60 second update, but I at least make
 * this the default.
 */
static int Remupdate = DEFREMUPDATE;
static XtResource rem_resources[] = {
	{ XtNupdate, XtCInterval, XtRInt, sizeof(int),
        (Cardinal) &Remupdate, XtRInt, (caddr_t) &Remupdate },
};

/* GetStatus() global vars */
static char *Label;
static Arg Labelarg;

/* getload includes */
/* Dynix doesn't have an rwhod.h file, so here's the struct for that system */
#ifdef sequent
struct  outmp {
    char  out_line[8];    /* tty name */
    char  out_name[8];    /* user id */
    long  out_time;       /* time on */
};
struct  whod {
    char  wd_vers;
    char  wd_type;
    char  wd_fill[2];
    int   wd_sendtime;
    int   wd_recvtime;
    char  wd_hostname[32];
    int   wd_loadav[3];
    int   wd_boottime;
    struct whoent {
        struct outmp    we_utmp;
        int             we_idle;
    } wd_we[1024 / sizeof (struct whoent)];
};
#else
#include <protocols/rwhod.h>
#endif

main(argc,argv)
int argc;
char *argv[];
{
    int currarg = 1;             /* arg counter to walk through hosts */
    char
      *hostname,                 /* raw host name */
      *labelname;                /* label, could be "machine DOWN" */
    struct hostent *host;        /* for gethostbyname() */

    /* The toolkit tries to open your display if you use -help */
    /* so let's do it the other way */
    if((argc > 1) && !strncmp(argv[1],"-h",2)) {
        usage();
        exit(1);
    }

    /* Initialize the top level */
    Toplevel = XtInitialize(*argv[0], "XNetload",
                            app_options, XtNumber(app_options), &argc, argv);
	/* Get local vs nolocal */
	XtGetApplicationResources( (Widget)Toplevel, (caddr_t)NULL,
							  app_resources, XtNumber(app_resources),
							  NULL, (Cardinal) 0);
      
    /* Create a Form widget to contain all of the Loads */
    Formwin =
      XtCreateManagedWidget("shell",
                            formWidgetClass,
                            Toplevel,
                            Formargs,
                            XtNumber(Formargs));

/*
 * Load widget reads kmem for local load.  Define NOLOCAL if you
 * don't want this.
 */
	if(Localload) {
        /* Get and set label name */
        labelname = XtMalloc(BUFSIZ*sizeof(char));
        gethostname(labelname,BUFSIZ);
        XtSetArg(Statusargs[XtNumber(Statusargs)-1],
                 XtNlabel,(XtArgVal)labelname);

        /* Create the local load widget */
        Statuswin = XtCreateManagedWidget("local",
                                          loadWidgetClass,
                                          Formwin,
                                          Statusargs,
                                          XtNumber(Statusargs));
    }

    /* Remote Loads */
    /* currarg initialized above */
    for(;currarg < argc;currarg++) {
        /* translate command arg to a hostname */
        host = gethostbyname(argv[currarg]);
        /* if host == NULL, then hostname is junk for some reason */
        if(host) {
            /* allocate memory for hostname */
            if(!(hostname=XtMalloc((strlen(host->h_name)+1)*sizeof(char)))) {
                perror("xnetload: Ran out of memory for name");
                exit(1);
            }
            /* allocate memory for name and downmsg for label */
            if(!(labelname=XtMalloc((strlen(host->h_name)
                                          +strlen(DOWNMSG)+1)*sizeof(char))
                      )) {
                perror("xnetload: Ran out of memory for labelname");
                exit(1);
            }
            /* set up chain to previous Load widget */
            XtSetArg(Statusargs[0],XtNfromVert, (XtArgVal)Statuswin);

            /* set label name */
            strcpy(labelname,host->h_name);
            XtSetArg(Statusargs[XtNumber(Statusargs)-1],
                     XtNlabel,(XtArgVal)labelname);

            /* create the remote load widget, taking into account */
            /* modified label and vertical chaining */
            Statuswin = XtCreateManagedWidget("remote",
                                              loadWidgetClass,
                                              Formwin,
                                              Statusargs,
                                              XtNumber(Statusargs));

			/* This makes sure of 60 second default, without forcing! */
			XtGetApplicationResources( (Widget)Statuswin, (caddr_t)NULL,
									  rem_resources, XtNumber(rem_resources),
									  NULL, (Cardinal) 0);

            /* Set up hostname, and load callback (GetStatus()) */
            strcpy(hostname,host->h_name);
            XtRemoveAllCallbacks(Statuswin, XtNgetLoadProc);
            XtAddCallback(Statuswin,XtNgetLoadProc,
                          GetStatus,(caddr_t)hostname);
        }
    }

    /* if there are no loads to display, then should stop   */
    /* Note, Statuswin is static, guaranteed to start at 0, */
    /* so it's non-zero only if a load widget was created   */
    if(!Statuswin) {
        fprintf(stderr,"xnetload: No machines to display\n");
        usage();
        exit(0);
    }

    /* draw the widgets, and look for new events */
    XtRealizeWidget(Toplevel);
    XtMainLoop();
}

/*
 * Load callback function
 *
 */
void
GetStatus(w,closure,call_data)
Widget w;                        /* load widget */
caddr_t closure;                 /* name of host, char*  */
caddr_t call_data;               /* load, float*  */
{
    int load;                    /* current load for this machine */
    char fname[BUFSIZ];          /* name of rwhod file */

    /* get current label for widget */
    XtSetArg(Labelarg,XtNlabel,(XtArgVal)&Label);
    XtGetValues(w,&Labelarg,(Cardinal)1);

    sprintf(fname,"%s.%s",RWHODHD,(char *)closure);

    /* if load is less than zero, then machine is down or something */
    if((load = getload(fname)) < 0) {
        *(double *)call_data = 0.0;
        /* update label with DOWN message */
        if(!matchstr(Label,DOWNMSG))
          updatelabel(w,(char *)closure,DOWNMSG);
    }
    /* otherwise we found a good load, so use it */
    else {
        *(double *)call_data = (double)load/100.0;
        /* hate to do this everytime, but we have to check */
        if(matchstr(Label,DOWNMSG))
          updatelabel(w,(char *)closure,"");
        return;
    }
}

/*
 * look for pat in str - exact match, no wild cards here
 */
static int
matchstr(str,pat)
register char *str, *pat;
{
    while(str = index(str,*pat))
      if(! strcmp(str++,pat))
        return(TRUE);
    return(FALSE);
}

/*
 * replace label with str and msg
 */
static int
updatelabel(w,str,msg)
Widget w;
register char *str, *msg;
{
    XEvent Sendevent;

    sprintf(Label,"%s%s",str,msg);
    XtSetArg(Labelarg,XtNlabel, (XtArgVal)Label);
    XtSetValues(w,&Labelarg, (Cardinal)1);

    /* Load widget doesn't redraw when label is changed.  Force it! */
    Sendevent.xexpose.x = 0;
    Sendevent.xexpose.width = ((LoadWidget)w)->core.width;
    XClearWindow(XtDisplay(w),XtWindow(w));
    (*((LoadWidgetClass)&loadClassRec)->core_class.expose)
      (w, &Sendevent, (Region) NULL);
}

#define ISDOWN(now,wd) (((now) - (wd).wd_recvtime) > DOWNTIME)
/*
 * actual function to get the load
 * reads rwhod stats to find the load average of a machine
 */
static int
getload(fname)
char *fname;
{
    int nbytes;      /* number of bytes read */
    int fd;          /* file descriptor */
    int counter;                 /* loop counter */
    time_t currtime;             /* current time */
    struct whod    wd;
    char buf[sizeof(struct whod)];

    (void) time(&currtime);

    /* Loop 3 times, looking for a non-zero load. */
    /* This keeps us from getting a zero load if rwhod is in */
    /* the process of doing something to the file.  This is */
    /* important, since we'll get strange looking zero gaps */
    /* in the graph if we don't try our best to get a non-zero load. */
    for(counter=0;counter<=3;counter++) {
        /* open and read from the file */
        if((fd = open(fname, O_RDONLY, 0)) < 0)
          /* we can't open file, so it's down or something */
          return(-1);

        nbytes = read(fd, buf, sizeof(struct whod));
        close(fd);
        /* if we got enough bytes, then copy it to wd */
        if(nbytes >= (sizeof (wd) - sizeof(wd.wd_we))) {

            /* NOT everbody has memcpy, but can't use strncpy() */
            memcpy(&wd,buf,(sizeof (wd) - sizeof(wd.wd_we)));

            /* ISDOWN macro, if machine is really down or faking */
            if(ISDOWN(currtime,wd))
              return(-1);
            else if(wd.wd_loadav[0] > 0)
              return(wd.wd_loadav[0]);
        }

        /* If we got to here, then we need to loop again */
        sleep(1);
    }
}

static int
usage()
{
    fprintf(stderr,"usage:\n xnetload [-nolocal] [machine] ...\n");
}
@//E*O*F xnetload/xnetload.c//
chmod u=rw,g=r,o=r xnetload/xnetload.c
 
echo x - xnetload/xnetload.1
sed 's/^@//' > "xnetload/xnetload.1" <<'@//E*O*F xnetload/xnetload.1//'
@.TH XNETLOAD l "April 1989" "X Version 11"
@.SH NAME
xnetload - display load averages from local network machines
@.SH SYNOPSIS
@.B xnetload
[-\fInolocal\fP] [\fImachine\fP] ...
@.SH DESCRIPTION
@.I xnetload 
uses the Athena Load widget and
@.B rwhod
statistics to display the
load average of a number of machines on a local network.
@.SH OPTIONS
@.PP
@.I xnetload
accepts all of the standard X Toolkit command line options along with the 
following additional options:
@.PP
@.TP 8
@.B \-nolocal
By default, the load for the local machine is displayed and the
load calculated using the
Load widget's standard LoadProc.  With this option,
@.I xnetload
will NOT display
the local machine's load unless the hostname is listed
on the command line.  When
@.B \-nolocal
is used,
@.I xnetload
will not try to read
@.I kmem.
@.PP
@.TP 8
@.B \-local
If
@.I xnetload
is compiled with
@.B \-DNOLOCAL,
then no local load will
be displayed by default.
@.B \-local
will turn the local load back on, and make
@.I xnetload
read
@.I kmem.
@.PP
@.TP 8
@.B [machine] ...
The load for each machine listed will be displayed.
@.SH RESOURCES
@.I xnetload
uses the standard toolkit resources.  These are some of the ones that
I find useful:
@.br
@.sp
!**** XLOAD ****
@.br
*Load*font:                          6x10
@.br
*Load*height:                        35
@.br
*Load*width:                         90
@.br
!**** XMULTIMETER ****
@.br
xnetload.geometry:                   -0+0
@.br
! Display the local machine load by reading kmem
@.br
xnetload.localload:                  on
@.br
! number of seconds between updates for local machine
@.br
xnetload*local*update:               15
@.br
! number of seconds between updates for remote machines
@.br
xnetload*remote*update:              40
@.sp
@.PP
Note - if you define "*Load*update", but not "xnetload*remote*update",
then
@.I xnetload
will set both the remote and local update times to the "*Load*update"
value.  If this time is much less than the
@.B rwhod
update period, then the remote load displays will look more like bar graphs.
@.SH SEE ALSO
X(1), xload(1), Athena Load widget
@.SH BUGS
@.I xnetload
uses the Load widget's normal load function for the local
system, therefore
@.I xnetload
should be setgid
@.I kmem.
If
@.I xnetload
cannot be setgid
@.I kmem,
then either compile with
@.B \-DNOLOCAL
or use the
@.B \-nolocal
option.
@.PP
@.I xnetload
will also take a width and height on the application
geometry specification, but sometimes the
@.I Form
widget will leave small gaps between the loads.
@.PP
A very wide
@.I xnetload
will often have wide margins.  I think the
@.I Form
widget is doing this.
@.SH AUTHOR
J. Michael Berkley
(jmberkley@watnext.waterloo.edu)
@//E*O*F xnetload/xnetload.1//
chmod u=rw,g=r,o=r xnetload/xnetload.1
 
echo x - xnetload/Makefile
sed 's/^@//' > "xnetload/Makefile" <<'@//E*O*F xnetload/Makefile//'
#
# Makefile for xnetload
#
# Written by: Mike Berkley, jmberkley@watnext.waterloo.edu, 1989
#
# Permission to use, copy, modify and distribute (without charge) this
# software and its documentation is granted, provided that this comment
# is retained.
#

# Since xnetload finds the local machine's load using the standard Load
# widget stuff, it needs to be able to read kmem, i.e.  setgid kmem.  If
# you do not want this, then define NOLOCAL
# CFLAGS = -O -DNOLOCAL
CFLAGS = -O

XINC = /usr/software/X11/include
XLIB = /usr/software/X11/lib

# Dynix doesn't have -L option on load, but I have left changing the
# XLIB specification up to individual sites.  Dynix and our Sun 3.5
# don't come with a memcpy, so you'll have to add in whichever library
# has memcpy.

INCLUDES = -I$(XINC)
LIBS = -L$(XLIB) -lXaw -lXmu -lXt -lX11

xnetload: xnetload.o
	$(CC) $(CFLAGS) -o xnetload xnetload.o $(LIBS)

@.c.o:
	$(CC) $(CFLAGS) -c $(INCLUDES) $<
@//E*O*F xnetload/Makefile//
chmod u=rw,g=r,o=r xnetload/Makefile
 
echo x - xnetload/AUTHOR
sed 's/^@//' > "xnetload/AUTHOR" <<'@//E*O*F xnetload/AUTHOR//'
xnetload was written by Mike Berkley, jmberkley@watnext.waterloo.edu

Permission to use, copy, modify and distribute (without charge) this
software and its documentation is granted, provided that this comment
is retained.
@//E*O*F xnetload/AUTHOR//
chmod u=rw,g=r,o=r xnetload/AUTHOR
 
echo x - xnetload/patchlevel.h
sed 's/^@//' > "xnetload/patchlevel.h" <<'@//E*O*F xnetload/patchlevel.h//'
#define PATCHLEVEL 0
@//E*O*F xnetload/patchlevel.h//
chmod u=rw,g=r,o=r xnetload/patchlevel.h
 
echo Inspecting for damage in transit...
temp=/tmp/shar$$; dtemp=/tmp/.shar$$
trap "rm -f $temp $dtemp; exit" 0 1 2 3 15
cat > $temp <<\!!!
      49     345    2160 README
      31     153    1023 Imakefile
     365    1371   11622 xnetload.c
     121     421    2644 xnetload.1
      32     157     941 Makefile
       5      29     222 AUTHOR
       1       3      21 patchlevel.h
     604    2479   18633 total
!!!
wc  xnetload/README xnetload/Imakefile xnetload/xnetload.c xnetload/xnetload.1 xnetload/Makefile xnetload/AUTHOR xnetload/patchlevel.h | sed 's=[^ ]*/==' | diff -b $temp - >$dtemp
if [ -s $dtemp ]
then echo "Ouch [diff of wc output]:" ; cat $dtemp
else echo "No problems found."
fi
exit 0