[comp.sources.unix] REPOST v11i083: Watcher system monitor program, Part02/02

rsalz@uunet.UUCP (10/01/87)

Submitted-by: Kenneth Ingham <ingham@charon.unm.edu>
Posting-number: Volume 11, Issue 83
Archive-name: watcher/part02

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file".  To overwrite existing
# files, type "sh file -c".  You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g..  If this archive is complete, you
# will see the following message at the end:
#		"End of archive 2 (of 2)."
# Contents:  Docs/Paper Docs/watcher.1 control.y defs.h
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f Docs/Paper -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"Docs/Paper\"
else
echo shar: Extracting \"Docs/Paper\" \(13821 characters\)
sed "s/^X//" >Docs/Paper <<'END_OF_Docs/Paper'
X.TI Keeping watch over the flocks
X.TI by night (and day)
X.AU 7
XKenneth Ingham
XUniversity of New Mexico Computing Center
XDistributed Systems Group
X2701 Campus NE
XAlbuquerque, NM 87131
X(505) 277-8044
Xingham@charon.unm.edu  or ucbvax!unmvax!charon!ingham
X.AB
XOver the last several years, the number of machines maintained by the
XUniversity of New Mexico Computing Center has increased rapidly, yet
Xthe number of system managers monitoring these systems has remained
Xstatic.  Consequently, the system managers were faced with the task of
Xwatching more and more machines; since only one system manager is on
Xcall at any time (known affectionately as "DOC"), this soon proved to
Xbe an unacceptable situation.  Shell scripts running every six hours
Xgave some assistance; this was offset by the fact that the scripts
Xgenerated a great deal of output indicating normal system operation,
Xwhich the system manager still had to scan carefully for signs of
Xtrouble.  This paper describes \fIwatcher\fR, a flexible system monitor
Xwhich watches the system more closely than the human system manager
Xwhile generating less output for him to examine.
X.sp
XRunning more often than the above mentioned set of shell scripts,
X\fIwatcher\fR is able to keep closer tabs on the system; since it
Xdelivers only a list of potential problems, however, this extra
Xmonitoring produces \fIno\fR corresponding increase in the demand on
XDOC.  No problems slip by unnoticed in the more concise output,
Xleading to an improvement in overall system availability as well as the
Xmore effective utilization of the system manager's time.
X.BD
X.SE 0. Acknowledgments (I couldn't have done it without you)
XI would like to thank Leslie Gorsline for her assistance in the writing
Xof this paper.  Without her, this paper might not have been.  Also
Xthanks to the UNMCC distributed systems group for their comments that helped
Ximprove \fIwatcher\fR.
X.SE 1. Background (the problem)
XThe computing facilities offered by the University of New Mexico
XComputing Center (UNMCC) include three microvaxen, five large vaxen
X(780 or bigger), and a Sequent B8000.  In addition to these Unix/VMS
Xmachines, the UNMCC Distributed Systems Group (DSG) monitors a number
Xof the various microvaxen and sun workstations scattered across
Xcampus.  This duty falls to the DSG Programmer designated as "DOC", or
X"DSG On Call", who receives his beeper based on a monthly rotation
Xschedule.
X.sp
XIn the past, shell scripts running every six hours reported various
Xsystem statistics to DOC, who then scanned the output for signs of
Xpossible trouble.  The output of these shell scripts became
Xoverwhelming as the number of machines and potential problems grew;
Xcorresponding to this increase in output was an increase in the amount
Xof time that DOC had to spend reading this output.  In addition, most
Xof this output merely indicated normal system operation; potential
Xproblems were buried amongst non-problems.  Because of this, DOC could
Xoften waste a tremendous amount of time wading through system status
Xreports, time which can be better spent actually fixing system
Xproblems.
X.sp
XUnix is equipped with many powerful tools for program development, but
Xnone which simply watch the system for signs of trouble.  Programs like
X\fIps\fR and \fIdf\fR provide information regarding the current state
Xof the machine, yet it still remains DOC's responsibility to interpret
Xthis information and assess the health of the system at any given
Xtime.  This deficiency can be rectified by providing the
Xsystem with the capacity to determine its own state of health, advising
XDOC when it notices a problem which requires DOC's intervention.
X.SE 2. Design Goals (devising the solution)
XIn designing \fIwatcher\fR, the author closely examined just what DOC
Xdoes in monitoring the system; just how \fIdoes\fR DOC spot potential
Xtrouble in the DOC reports?  These reports consist of output from \fIdf
X-i\fR, \fIruptime\fR, \fIps -aux | sort\fR, and the tail of
X\fIcronlog\fR, which usually only changes in the middle of the night.
XIt was determined that DOC's task consisted primarily of scanning
Xvarious numbers in this output, deciding whether or not they had
Xexceeded an allowable maximum or minimum, or if the values had changed
Xtoo much from the last time the command was run, assuming the last
Xvalue is even remembered.  Getting a computer to do this is more
Xcomplicated than might seem at first glance, due to inconsistencies in
Xthe location of pertinent information between runs of these commands.
XFor instance, the process occupying the fifth line of \fIps -ax\fR
Xmight next time appear on the eighth line; similarly, \fIuptime\fR does
Xnot consistently put germane information in the same place on the line.
X.sp
XWhile flexibility is certainly a primary design consideration, it is
Xnot the whole story.  In order to improve DOC's effectiveness, the
Xprogram should run frequently, roughly every two or three hours,
Xcatching problems early (hopefully before they have affected
Xthe users).  Thus, the program should also be as silent as possible
Xexcept when it detects a potential problem; any advantage DOC gains in
Xusing \fIwatcher\fR would be eliminated if the program delivered an
Xexceedingly verbose status report every two hours.  \fIwatcher\fR's
Xproblem reports should be exact and concise, leading DOC immediately to
Xthe trouble.
X.sp
XThe problem of reducing the amount of output DOC must process can be
Xapproached in different ways, including the redesign of the current
Xshell scripts.  A simple \fIawk\fR script can watch the output from
X\fIdf\fR [1].  However, each command would require a custom tailored
X\fIawk\fR script to look at it.  This task grows more complicated as the
Xnumber of programs running increases.
XWhile a program could be written to
Xgenerate these \fIawk\fR scripts, this process is needlessly complex;
Xfor only a bit more work, an efficient C program such as
X\fIwatcher\fR can be developed.  
X.SE 3. Design (actual implementation of the solution)
XRun at intervals specified in \fIcrontab\fR, \fIwatcher\fR parses a
Xcontrol file (./\fIwatcherfile\fR by default)
Xwith a \fIyacc\fR generated parser, building a data structure
Xcontaining all of the information from the file.  The file contains the
Xlist of commands \fIwatcher\fR
Xshould run (the pipeline), output specifications
Xfor each command (the output format), and the guidelines used in
Xdetermining if something is amiss and should be reported to DOC (the
Xchange format).  A sample \fIwatcher\fR control file would look
Xsomething like this (comment lines begin with a '#'):
X.EX
X# Here is the pipeline and its alias:
X(df -i | /usr/ucb/tail +2) { df }
X# the output format; this is a column output format:
X	$1-9 device%k $41-42 spaceused%d $64-65 inodesused%d:
X# and the change format:
X		spaceused 15%;
X		spaceused 0 89;
X		inodesused 15%;
X		inodesused 0 49.
X
X# another command example:
X(/usr/ucb/ruptime | fgrep -f UnmHosts) { ruptime }
X# this is a relative output format
X	2 status%s 1 machine%k 7 loadav%d:
X# and another change format:
X		loadav 0 10;
X		status "up".
X.NX
XThe first entry causes \fIwatcher\fR to run the \fIdf\fR pipeline
Xlisted in parentheses.  When reporting problems, \fIwatcher\fR refers
Xto this command by the alias provided in the braces; if no alias
Xappears, \fIwatcher\fR uses the entire pipeline.  
X.sp
XThe output format
Xinstructs \fIwatcher\fR how to parse the output;
Xcolumn format, indicated in the output format by \fBnum-num\fR,
Xinstructs \fIwatcher\fR that the output should be parsed
Xby columns, while relative format, denoted by a single integer, shows
Xthat the output should be broken up by whitespaces.
XThrough the convention \fBname%type\fR, the output format also names each
Xfield, indicating whether the field is numeric, string, or
Xkeyword, specified by \fBd\fR, \fBs\fR, or \fBk\fR respectively.
XKeyword fields are
Xused to match up corresponding output lines between runs.  Thus
X.EX
X	41-42 spaceused%d
X.NX
Xindicates that this field, named \fBspaceused\fR, contains numeric 
Xinformation in columns 41-42, while
X.EX
X	2 status%s
X.NX
Xinforms \fIwatcher\fR that the second word (group of non-whitespace
Xcharacters) on the line is a string field named \fBstatus\fR.
XFor the \fIdf\fR example given above,
X.EX
XFilesystem    kbytes    used   avail  capacity   iused   ifree  %iused  Mounted on
X/dev/hp1f      52431   39763    7424    84%    6937    9447    42%   /develop
X.NX
X\fBdevice\fR would be \fI/dev/hp1f\fR, \fBspaceused\fR would be 84,
Xand \fBinodesused\fR would be 42.  Similarly, the output from the
X\fIruptime\fR example, which looks like this
X.EX
Xcharon        up 26+07:53,    17 users,  load 3.12, 2.90, 2.66
X.NX
Xwould be broken at the following places:
X.EX
Xcharon | up | 26+07:53, | 17 | users, | load | 3.12, | 2.90, | 2.66,
X.NX
Xassigning "up" to \fBstatus\fR, and 3.12 to \fBloadav\fR.
X.sp
XThe name field also appears in the change format, designating allowable
Xvalues for this field to have.  These values can be specified as 
Xsingle character strings in the case of string fields; in the case of
Xnumeric fields, the values take the form of either
Xpercentage or absolute changes, or a minimum and maximum which delineate
Xan acceptable range.
XThus
X.EX
X	inodesused 15%;
X	inodesused 0 49.
X.NX
Xsignifies that DOC should be notified if the field named \fBinodesused\fR
Xincreases by more than 15% from the last run, or if it is outside the
Xrange 0 to 49; similarly
X.EX
X	status "up";
X.NX
Xinforms \fIwatcher\fR to notify DOC if the \fBstatus\fR field contains
Xanything other than the word "up".
X.sp
XAs \fIwatcher\fR parses the output of a pipeline, it stores the
Xpertinent parts of the output in a history file (by
Xdefault, ./\fIwatcher.history\fR).
XThe next time \fIwatcher\fR runs, it reads this file to provide
Xcomparison values for the command.  If a command is new (i.e. it has no
Xpreviously-stored output in the history file), \fIwatcher\fR checks the
Xfields which require no previous data, such as min-max fields, while
Xstill storing \fIall\fR of the relevant information to the history file.
XThus, the next time the new command is run, it will be an \fIold\fR command,
Xand meaningful between-run comparisons can be made.
X.sp
XWhen \fIwatcher\fR
Xdetects no problems with the system, DOC receives an empty mail message
Xwith the subject "\fIhostname\fR had no problems at \fIdate\fR";
Xthis is to insure that \fImail\fR is running correctly.  
XWhen it notices a problem which should be brought to DOC's attention,
Xit mails the system problem report in a concise
Xformat, explaining what is wrong and why.  
XThus, rather than the megabytes of shell script output that DOC
Xused to receive and have to read,
Xhe merely sees this when he reads his mail:
X.EX
XMail version 5.2 6/21/85.  Type ? for help.
X"/usr/spool/mail/ingham": 5 messages 5 new
X N  1 root@charon.unm Sat Apr 11 16:00  8/212  "charon had no problems at Sat"
X N  2 root@ariel.unm Sat Apr 11 16:00  8/208  "ariel had no problems at Sat "
X N  3 root@geinah.unm Sat Apr 11 16:00  11/417 "System problem report for gei"
X N  4 root@izar.unm Sat Apr 11 16:00  8/204  "izar had no problems at Sat A"
X N  5 root@deimos.unm Sat Apr 11 16:00  8/212  "deimos had no problems at Sat"
X.NX
XThe letters indicating no problems can be immediately deleted, and DOC
Xcan turn his attention to the letter indicating a 
Xsystem problems.  A sample problem report
Xwould look something like this:
X.EX
Xdf has a max/min value out of range:
X/dev/hp0h     140488  111195   15244    91%   10145   28767    26%   /usr
Xwhere spaceused = 91.00; valid range 0.00 to 89.00.
XAlso it had inodesused change by more than 10%.
XPrevious value 20.00; current value 26.00.
X.NX
XNote that if a line has more than one indication of a problem, all
Xanomalies are included in the report.
XThis provides DOC with as much information as possible, allowing him
Xto determine the problem quickly and devise 
Xa rapid fix (hopefully before users know something is amiss).  
X.sp
X.SE 4. Results (how its helped us)
X\fIwatcher\fR's primary advantage lies in the reduction of DOC's work
Xload.  It has taken over the more menial aspects of monitoring a system,
Xtasks like reading and comparing numbers, 
Xgiving DOC more time to concentrate on bugs of a nature which
X\fIwatcher\fR isn't set up to monitor, such as problems in the
Xaccounting system.
XDOC is apprised of potential problems quickly, and in
Xsome cases can repair them in less time than simply
Xreading the shell script output
Xwould have taken.
X.sp
XThe ability to monitor changes between runs has also helped bring to our
Xattention some
Xproblems which were missed in the DOC reports.  For example,
Xdisk space on \fI/u2\fR on one of our machines jumped by more than 15%.  Since
Xthis jump did not force the total space used above 90%, at which point
XDOC would have investigated the filesystem, it is unlikely
Xthat DOC would have even noticed this sudden change.  The facility to
Xwatch for relative changes between runs enables DOC to catch problems in
Xtheir infancy, and fix problems such as filesystems filling up too
Xrapidly before they inconvenience the users.
X.sp
XSince the system manager specifies not only the commands \fIwatcher\fR will
Xexecute and the time lapse between successive runs, but also the
Xparameters which indicate system anomalies,
X\fIwatcher\fR can easily be seen as a very flexible, general system
Xmonitor.  Its use at UNM has provided an increase in the
Xproductivity of the system manager, which has led in turn to the
Xincrease in the reliability and availability of the systems at UNMCC.
X.SE 5. Availability (how to get one)
X\fIwatcher\fR will be sent to the moderator of mod.sources after the
Xconference is over.
X.SE 6. References (you might also find this interesting)
X.in +0.5i
X.ti -0.5i
X[1] Monitoring Free Disk Space, Rik Farrow, Wizard's Grabbag, \fIUnix
XWorld\fR, Vol. IV, no. 3, pp. 86-87.
X.in -0.5i
END_OF_Docs/Paper
if test 13821 -ne `wc -c <Docs/Paper`; then
    echo shar: \"Docs/Paper\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f Docs/watcher.1 -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"Docs/watcher.1\"
else
echo shar: Extracting \"Docs/watcher.1\" \(6175 characters\)
sed "s/^X//" >Docs/watcher.1 <<'END_OF_Docs/watcher.1'
X.de EX
X.nf
X.sp
X.in +0.5i
X..
X.de NX
X.sp
X.in -0.5i
X.ad
X.fi
X..
X.TH WATCHER 1 "April 10, 1987"
X.UC 6
X.ad
X.SH NAME
Xwatcher \- system monitoring program
X.SH SYNOPSIS
X.B watcher
X[ \-p] [ \-v ]
X[ \-h histfile ] [ \-f controlfile ]
X.SH DESCRIPTION
X.I Watcher
Xis a program to watch the system, reporting only when it finds something
Xamiss.
X.I Watcher
Xreads commands from
X.I controlfile
Xto determine what to watch, the output format of the commands it is to
Xrun, and the acceptable limits for the output of those commands.
XIf no
X.B \-f
Xoption is present, the program looks first for `watcherfile',
Xthen `Watcherfile' to use as the input.
X.PP
XThe
X.B \-h histfile
Xflag tells watcher what file to use as a history file for comparisons
Xbetween runs.  The default is `watcher.history'.
X.PP
XThe
X.B \-p
Xoption has
X.I watcher
Xpretty print the control file.  This is useful to make sure that watcher
Xis parsing the file the way expected, and to provide a prettier version
Xof the control file to use (i.e. it is of limited use).
X.PP
XThe
X.B \-v
Xoption tells watcher to be verbose as it is running.  It will print out
Xvarious information about where it is looking for the files that it
Xuses, the commands that it is executing, and the output from these
Xcommands.  This option is mainly of use when debugging control files or
Xdebugging watcher itself.
X.PP
X.I Watcherfile
Xcontains a sequence of entries that specify the commands to be executed,
Xthe output format of those commands, and what changes should be
Xreported.  The format of the control file is one or more of the
Xfollowing:
X.EX
X( <pipeline> ) { <alias> }
X	<output format> :
X	<change format>.
X.NX
XA <pipeline> is a series of commands joined together with pipes ('|').
XThis command is executed and the output parsed according to the output
Xformat specified.  It is then checked against the change format for
Xpotential problems.  An <alias> is optional; it is used when identifying
Xthe command in the report of problems encountered.  If there is no
Xalias, the entire pipeline is used.  The reason for using an alias is
Xto keep the report clean; the pipelines tend to be long and messy.
X.PP
XAn <output format> is either a column format or a relative format.  A
Xcolumn format is one or more of the following:
X.EX
X	<start> - <end> <name> % <type> 
X.NX
XWhere <start> is the first column containing the information to be compared and
X<end> is the last one.  <name> is the name of the field.  This name is
Xmatched with the names in the change format to identify where in the
Xoutput the appropriate information is.  <type> is either "d", "s", or
X"k" specifying numeric or string data, or a keyword which is used in
Xmatching output from the various programs between runs.
X.PP
XRelative formats are one or more of the following:
X.EX
X<field> <name> % <type>
X.NX
XWhere <field> is the field on the line (a field is defined as a sequence
Xof non-whitespace surrounded by whitespace).  <name> and <type> are the
Xsame as for above.
X.PP
XA change format consists of various names and what changes are
Xallowable.  Change format entries are separated by semicolons (';').  The
Xlist of change formats is terminated by a period ('.').  A semicolon
Xdoes not follow the last change format.
X.I Watcher
Xknows about 4 types of changes.  It can compare the output (numeric)
Xto the previous value and calculate the percentage change.  If the
Xchange is greater that a set amount, a message is generated.  The syntax
Xof this format is:
X.EX
X	<name> <value> % 
X.NX
Xwhere <name> is a name matching a name in the output format and <value
Xis the maximum percentage change which is allowed before a report is
Xissued.
X.PP
XVery similar to the percentage change is the absolute change.  The only
Xdifference is that a percentage is not calculated.  The difference is
Xcalculated and compared to the value given.  Values greater that what is
Xprovided are reported.  The syntax is:
X.EX
X	<name> <value>
X.NX
X.PP
XA maximum and minimum may be specified for numeric data also.  This is
Xuseful for only numeric data.  The format for this is:
X.EX
X	<name> <max> <min>
X.NX
X.PP
X.I Watcher
Xcan also watch for string values changing from a given value to any
Xother value.  This syntax is:
X.EX
X	<name> "<value>"
X.NX
X.PP
XA sample control file is provided below:
X.EX
X(df -i | /usr/ucb/tail +2) { df }
X	1-9 filesystem%k 41-42 spaceused%d 64-65 inodesused%d 1-9 device%k:
X		spaceused 15%;
X		spaceused 0 89;
X		inodesused 15%;
X		inodesused 0 49.
X(/usr/ucb/ruptime | fgrep -f UnmHosts) { ruptime }
X	2 status%s 1 machine%k 7 loadav%d:
X		loadav 0 10;
X		status "up".
X(ps -aux | fgrep -v -f Daemons | /usr/ucb/tail +2) { 'ps with no daemons' }
X	9-14 pid%k 16-19 percentcpu%d 42-45 cputime%d:
X		cputime 0 10.
X.NX
XNote that there is no order for the output format specifiers; the second
Xfield may be specified before the first.  
X.PP
XAll names are of arbitrary length, start with [a-zA-Z] and contain no
Xwhite space unless enclosed in tics ("'").
X.PP
XThe pipeline is executed by 
X.I popen(3),
Xwhich uses 
X.I sh(1)
Xto expand the command; therefore shell metacharacters may be used.
X.PP
XThe control file may have comments in it.  Comments are delimited by a #
Xon the left and a newline on the right.
X.SH FILES
X.nf
XWatcherfile or watcherfile	default control file.
Xwatcher.history			default file containing results of previous run.
X.SH AUTHOR
X.nf
XKenneth Ingham
XUniversity of New Mexico Computing Center
X2701 Campus NE
XAlbuquerque, NM, 87131
Xingham@charon.unm.edu
X.SH "SEE ALSO"
Xpopen(3), sh(1),
X.I Keeping Watch over the 
X.I Flocks by Night (and day)
Xby Kenneth Ingham, Summer 1987 Usenix proceedings.
X.SH DIAGNOSTICS
XFiles which can't be opened cause a message about which files couldn't
Xbe found and the program exits.
X.sp
XThere are various syntax errors when parsing the controlfile.  These
Xalso cause an exit.
X.sp
X.I Watcher
Xcomplains when output does not parse according to the format
Xprovided.  It will continue to look at the rest of the output.
X.SH BUGS
XSyntax errors in control file are not easy to find based on the error
Xmessages.
X.sp
XDoesn't warn when a string variable has been selected for a numeric
Xcomparison.
X.sp
XOnly notices changes which get bigger; things which suddenly
Xdrop will not be noticed.
END_OF_Docs/watcher.1
if test 6175 -ne `wc -c <Docs/watcher.1`; then
    echo shar: \"Docs/watcher.1\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f control.y -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"control.y\"
else
echo shar: Extracting \"control.y\" \(6895 characters\)
sed "s/^X//" >control.y <<'END_OF_control.y'
X/*
X   the grammer describing the control file for watcher.
X
X   Kenneth Ingham
X
X   Copyright (C) 1987 The University of New Mexico
X*/
X
X%token PIPELINE NUMBER STRING
X%start command
X
X%{
X#include "defs.h"
X#define SMAXMIN	struct max_min_st
X#define SOUTFMT	struct out_fmt_st
X#define SCOLOUT	struct col_out_st
X#define SRELOUT	struct rel_out_st
X#define SCMD	struct cmd_st
X#define SCH	struct change_fmt_st
Xextern char *strval, ostrval[], pipeline[];
Xextern int intval, ointval;
Xextern struct cmd_st *clist;
Xint out_type;
Xchar alias[MAX_STR];
Xunion out_fmt_u key;
X%}
X
X%%
X/*
X   THE PARSER DESCRIPTION (read aloud to the beginning of
X   _Also_sprach_Zarathustra_)
X
X   As things are discovered, they are put into the linked list structure
X   detailed in defs.h.  Some error checking is done, but the messages
X   leave a bit to be desired.  Yacc and I didn't get along too well in
X   figuring out how to handle errors.
X
X   Assuming that pointers will fit into YYSTYPE (int on 4.3 vax).
X   Should use yacc's union facility.
X*/
X   
Xcommand		: one_command
X			{
X				if (clist == NULL)
X					clist = (SCMD *) $1;
X			}
X		| command one_command
X			{ 
X				SCMD *p;
X
X				if (clist != NULL) {
X					for (p=clist; p->next!=NULL; p=p->next)
X						;
X					p->next = (SCMD *)$2;
X				}
X				else
X					clist = (SCMD *)$1;
X			}
X		| error '.'
X			{
X			fprintf(stderr, "Command error near ");
X			fprintf(stderr, "'%s'\n", pipeline);
X			fprintf(stderr,"Last string read was '%s'\n", strval);
X			}
X		;
X
Xone_command	: PIPELINE alias out_fmt ':' change_fmt '.'
X			{
X				SCMD *p;
X
X				p = (SCMD *) malloc(sizeof(SCMD));
X				if (p == NULL) {
X					fprintf(stderr,"malloc error\n");
X					exit(1);
X				}
X				p->pipeline = malloc((unsigned)strlen(pipeline)+1);
X				if (p->pipeline == NULL) {
X					fprintf(stderr,"malloc error\n");
X					exit(1);
X				}
X				(void) strcpy(p->pipeline, pipeline);
X
X				if (alias[0]) {
X					p->alias = malloc((unsigned)strlen(alias)+1);
X					if (p->alias == NULL) {
X						fprintf(stderr,"malloc error\n");
X						exit(1);
X					}
X					(void) strcpy(p->alias, alias);
X				}
X				else
X					p->alias = NULL;
X
X				p->change_fmt = (SCH *)$5;
X				p->next = NULL;
X				p->out_type = out_type;
X				if (out_type == RELATIVE) {
X					p->out_fmt.rel_fmt = (SRELOUT *)$3;
X					p->key.rel_fmt = key.rel_fmt;
X				}
X				else { /* it better be column */
X					p->out_fmt.col_fmt = (SCOLOUT *)$3;
X					p->key.col_fmt = key.col_fmt;
X				}
X
X				key.rel_fmt = NULL;
X				$$ = (int)p;
X			}
X		;
X
Xout_fmt		: rel_out_fmt
X			{ 
X				out_type = RELATIVE;
X				$$ = $1;
X			}
X		| col_out_fmt
X			{
X				out_type = COLUMN;
X				$$ = $1;
X			}
X		| error '.'
X			{
X				fprintf(stderr,"Output format error on ");
X				fprintf(stderr,"'%s'\n", pipeline);
X				fprintf(stderr,"Last string read was '%s'\n",
X					strval);
X			}
X		;
X
Xchange_fmt	: one_change_fmt
X		| change_fmt ';' one_change_fmt
X			{
X				SCH *p;
X
X				for (p=(SCH *)$1; p->next != NULL; p = p->next)
X					;
X				
X				p->next = (SCH *)$3;
X
X				$$ = (int)$1;
X			}
X		;
X
Xone_change_fmt	: pct_change_fmt
X			{ $$ = $1; } /* Is this default? */
X		| abs_change_fmt
X			{ $$ = $1; }
X		| max_min_fmt
X			{ $$ = $1; }
X		| str_change_fmt
X			{ $$ = $1; }
X		| error '.'
X			{
X			fprintf(stderr,"Unknown change format type ");
X			fprintf(stderr,"on '%s'\n", pipeline);
X			fprintf(stderr,"Last string read '%s'\n", strval);
X			}
X		;
X
Xrel_out_fmt	: one_rel_fmt
X		| rel_out_fmt  one_rel_fmt
X			{
X				SRELOUT *p;
X
X				for (p=(SRELOUT *)$1;p->next != NULL; p=p->next)
X					;
X				p->next = (SRELOUT *)$2;
X				$$ = (int)$1;
X			}
X		;
X
Xcol_out_fmt	: one_col_fmt
X		| col_out_fmt  one_col_fmt
X			{
X				SCOLOUT *p;
X
X				for (p=(SCOLOUT *)$1;p->next != NULL; p=p->next)
X					;
X				p->next = (SCOLOUT *)$2;
X				$$ = (int)$1;
X			}
X		/*
X		| error '.'
X			{
X			fprintf(stderr,"Column output format error\n");
X			fprintf(stderr,"Last string read '%s'\n", strval);
X			}
X		*/
X		;
X
Xone_rel_fmt	: NUMBER STRING '%' STRING
X			{
X			extern int parse_error;
X			SRELOUT *p;
X
X			p = (SRELOUT *) malloc(sizeof(SRELOUT));
X			p->name = malloc((unsigned)strlen(ostrval)+1);
X			(void) strcpy(p->name, ostrval);
X			p->field = intval;
X			p->next = NULL;
X
X			if (strval[1] != '\0') {
X				fprintf(stderr,"%s: Invalid type specifier '%s'.\n",
X					NAME, strval);
X				parse_error = True;
X				$$ = (int)p;
X			}
X
X			switch (*strval) {
X				case 'd':
X				case 'f':
X					p->type = NUM;
X					break;
X				case 's':
X					p->type = STRING;
X					break;
X				case 'k':
X					p->type = KEY;
X					key.rel_fmt = p;
X					break;
X				default:
X					fprintf(stderr,"%s: Invalid type specifier '%s'.\n",
X						NAME, strval);
X					parse_error = True;
X			}
X
X			$$ = (int) p;
X			}
X		;
X
Xone_col_fmt	: NUMBER '-' NUMBER STRING '%' STRING
X			{
X				extern int parse_error;
X				SCOLOUT *p;
X
X				p = (SCOLOUT *) malloc(sizeof(SCOLOUT));
X				p->name = malloc((unsigned)strlen(ostrval)+1);
X				(void) strcpy(p->name, ostrval);
X				p->start = ointval;
X				p->end = intval;
X				p->next = NULL;
X
X				if (ointval >= intval) {
X					fprintf(stderr,"%s: start %d larger than end %d!\n", NAME, ointval, intval);
X					parse_error = True;
X					$$ = (int)p;
X				}
X
X				if (strval[1] != '\0') {
X					fprintf(stderr,"%s: Invalid %s '%s'.\n",
X						NAME, "type specifier", strval);
X					parse_error = True;
X					$$ = (int)p;
X				}
X
X				switch (*strval) {
X					case 'd':
X					case 'f':
X						p->type = NUM;
X						break;
X					case 's':
X						p->type = STRING;
X						break;
X					case 'k':
X						p->type = KEY;
X						key.col_fmt = p;
X						break;
X					default:
X						fprintf(stderr,"%s: %s '%s'.\n",
X							NAME,
X							"Bad type specifier",
X							strval);
X						parse_error = True;
X				}
X				$$ = (int) p;
X			}
X		;
X
Xpct_change_fmt	: STRING NUMBER '%'
X			{
X				SCH *p;
X
X				p = (SCH *) malloc(sizeof(SCH));
X				p->name = malloc((unsigned)strlen(strval)+1);
X				(void) strcpy(p->name, strval);
X				p->fmt.percent = (float)intval / 100;
X				p->type = PERCENT;
X
X				$$ = (int) p;
X			}
X		;
X
Xabs_change_fmt	: STRING NUMBER
X			{
X			  SCH *p;
X
X			  p = (SCH *) malloc(sizeof(SCH));
X			  p->name = malloc((unsigned) strlen(strval)+1);
X			  (void) strcpy(p->name, strval);
X			  p->fmt.abs_amount = intval;
X			  p->type = ABSOLUTE;
X
X			  $$ = (int) p;
X			}
X		;
X
Xmax_min_fmt	: STRING NUMBER NUMBER
X			{
X			  SCH *p;
X
X			  p = (SCH *) malloc(sizeof(SCH));
X			  p->name = malloc((unsigned)strlen(strval)+1);
X			  (void) strcpy(p->name, strval);
X			  p->fmt.max_min.max = intval;
X			  p->fmt.max_min.min = ointval;
X			  p->type = MAX_MIN;
X
X			  $$ = (int) p;
X			}
X		;
X
Xstr_change_fmt	: STRING '"' STRING '"'
X			{ 
X			  SCH *p;
X
X			  p = (SCH *) malloc(sizeof(SCH));
X			  p->name = malloc((unsigned)strlen(ostrval)+1);
X			  (void) strcpy(p->name, ostrval);
X			  p->fmt.str_value = malloc((unsigned)strlen(strval)+1);
X			  (void) strcpy(p->fmt.str_value, strval);
X			  p->type = STRING;
X
X			  $$ = (int) p;
X			}
X		;
X
Xalias		: '{' STRING '}'
X			{ (void) strcpy(alias, strval); }
X		| empty 
X			{ alias[0] = '\0'; }
X		;
X
Xempty		: ;
END_OF_control.y
if test 6895 -ne `wc -c <control.y`; then
    echo shar: \"control.y\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f defs.h -a "${1}" != "-c" ; then 
  echo shar: Will not over-write existing file \"defs.h\"
else
echo shar: Extracting \"defs.h\" \(4706 characters\)
sed "s/^X//" >defs.h <<'END_OF_defs.h'
X/*
X  Structure definitions, included files needed and useful constants for
X  Watcher.
X
X  Kenneth Ingham
X
X  Copyright (C) 1987 The University of New Mexico
X*/
X
X#include <stdio.h>
X#include <ctype.h>
X
X#ifdef BSD
X#include <strings.h>
X#else
X#include <string.h>
X#endif
X
X#include <signal.h>
X#include "y.tab.h"
X
X#define False		0
X#define True		1
X#define NAME		"watcher"
X#define MAX_STR		256
X#define DEF_CONTROL	"watcherfile"
X#define DEF_CONTROL2	"Watcherfile"
X#define DEF_HISTFILE	"watcher.history"
X#define MAX_NAME	16
X#define MAX_VEC		100
X
X#define INT		1
X#define FLOAT		2
X#define PERCENT		3
X#define ABSOLUTE	4
X#define MAX_MIN		5
X#define RELATIVE	6
X#define COLUMN		7
X#define NUM		8 /* really needs to be replaced by int or float */
X#define KEY		9
X
X/* 
X   below lie the structure definitions for all of the linked lists used
X   in watcher.  Make piece with your god before venturing onward
X*/
X
X/*
X   what a column output fromat entry looks like:
X*/
Xstruct col_out_st {
X	char *name;			/* name of output field */
X	int start;			/* where it starts... */
X	int end;			/* ... and ends */
X	int type;			/* what type of data to find there */
X	struct col_out_st *next;	/* and the next in the list is... */
X};
X
X/* 
X   and a relative output format...
X*/
Xstruct rel_out_st {
X	char *name;			/* name of output field */
X	int field;			/* Which field to look in for it */
X	int type;			/* what type of data to find there */
X	struct rel_out_st *next;	/* and the next in the list */
X};
X
X/*
X   types of change formats; all joined in a union later.
X*/
Xstruct max_min_st {
X	float max;			/* max allowable value... */
X	float min;			/* ...and the min */
X};
X
Xunion fmt_u {
X	float percent;			/* percent change */
X	float abs_amount;		/* absolute change */
X	struct max_min_st max_min;	/* max and min */
X	char *str_value;		/* what the string should be */
X};
X
X/*
X   the actual structure describing how things are allowed to change.
X*/
Xstruct change_fmt_st {
X	char *name;			/* name of the field this pertains to */
X	int type;			/* what type of change */
X	union fmt_u fmt;		/* the format, depending on type */
X	struct change_fmt_st *next;	/* and of course the next in the list */
X};
X
X/*
X   for the various types of output formats:
X*/
Xunion out_fmt_u {
X	struct rel_out_st *rel_fmt;	/* a relative output format (fields) */
X	struct col_out_st *col_fmt;	/* or one that uses columns */
X};
X
X/* 
X   what we are all about: the command structure.  Here we bring it all
X   together and have something to work with (luckily the subroutines are
X   also set up in a similar heirarchy and we can pass various parts of
X   the linked lists to them and they don't care about the "upper" parts.
X*/
Xstruct cmd_st {
X	char *pipeline;			/* the pipeline to execute */
X	char *alias;			/* a name to use when refering to it */
X	int out_type;			/* what type of output format used */
X	union out_fmt_u out_fmt;	/* the output format */
X	union out_fmt_u key;		/* what to key on for btwn run cmps */
X	struct change_fmt_st *change_fmt;/*the things to watch for changes */
X	struct cmd_st *next;		/* and of course the next in the list */
X};
X
X/*
X   now we get into the structures for the history linked list...
X*/
X
X/*
X   a way of storing any data type:
X*/
Xunion all_u {
X	char *strval;
X	int intval;
X	float floatval;
X};
X
X/*
X   which is used here where we hold the value for a specified key value
X   from the previous output.
X*/
Xstruct val_st {
X	char *name;		/* output field name */
X	int type;		/* what type of data in the union */
X	union all_u val;	/* ... the data (wow!) */
X	struct val_st *next;	/* and where would we be without a next one? */
X};
X
X/* 
X   for each line in the output of a command, we grab the key and store
X   all of the values obtained from the line.  Here is how it is done.
X*/
Xstruct key_st {
X	char *key_value;	/* value for the key (could you guess?) */
X	struct val_st *vals;	/* the various interesting parts of the line */
X	struct key_st *next;	/* and of course the next one */
X};
X
X/*
X   inally we come to the reaon for all of the above structures.  This is
X   how previous commands are stored once they have been read in from the
X   history file.
X*/
Xstruct old_cmd_st {
X	char *pipeline;			/* the pipeline executed */
X	struct key_st *keys;		/* the keys and their useful parts */
X	struct old_cmd_st *next;	/* and the next pipeline... */
X};
X
X/*
X   way of converting between type stored in struct and char for a
X   person's use.  This works on the cmd_st.out_type values.
X*/
X#define TCHAR(i)	(i==STRING ? 's' : (i == KEY ? 'k' : 'd'))
X
Xchar *malloc();		/* really should switch back to malloc */
Xchar *get_rel_field(), *get_col_field();
Xdouble atof();
Xstruct old_cmd_st *find_prev_cmd();
X
X/* bezerkeley vs system v differences... */
X#ifdef SYSV
X#define index	strchr
X#endif
END_OF_defs.h
if test 4706 -ne `wc -c <defs.h`; then
    echo shar: \"defs.h\" unpacked with wrong size!
fi
# end of overwriting check
fi
echo shar: End of archive 2 \(of 2\).
cp /dev/null ark2isdone
MISSING=""
for I in 1 2 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked both archives.
    rm -f ark[1-9]isdone
else
    echo You still need to unpack the following archives:
    echo "        " ${MISSING}
fi
##  End of shell archive.
exit 0