[comp.mail.sendmail] xchk - the xferstats summarizer

dlee@pallas.athenanet.com (Doug Lee) (03/07/91)

[Follow-ups redirected to "poster"; this is in C and is not for sendmail, so
I'm not sure they belong in comp.lang.perl or comp.mail.sendmail.]

In article <1991Mar03.205441.20747@ariel.unm.edu> sfreed@ariel.unm.edu writes:
>In article <1991Mar3.042937.20935@tous.uucp>, rbt@tous.uucp (Robert B. Tate) writes:
> 
>-> Would you please send it to me also! I have been wanting something like
>-> this and haven't had time to look at doing it...
>
>Same here... sounds like time to post it!!

Ok, I've received two public and several private requests for this.  At the
risk of being flamed for posting source to the wrong group, I am posting it
here on the ground that this is the origin of the apparent need for it.

As no one has sent me a sendmail log (we don't have sendmail here), I have
not modified it to eat sendmail logs as I suggested.  Feel free to do so,
and/or to send me a log (I hope I don't get 100 logs! :-) ).  Also, a
package for syslog processing was posted in comp.mail.sendmail recently, but
I haven't found time to examine it yet.

I'm sending this as a shell archive.  The program itself consists of xchk.c and
xchk.h.  Sample input material can be found in test.in, and the corresponding
output is in test.out.  For completeness, I threw in a *very* simple Makefile
and a small README file.

Mail me if you have any questions or problems.  Otherwise, enjoy!


-----------CUT HERE-----------
echo x - Makefile
sed '/^X/s///' > Makefile << '/'
Xxchk: xchk.c xchk.h
X	cc -O xchk.c -o xchk
X	strip xchk
/
echo x - README
sed '/^X/s///' > README << '/'
XThis is xchk, the uucp file transfer report generator.
X
XUsage:  xchk [ file | - ], where
X    <file> is a uucp "xferstats" file.  If "-" is given, the standard input
X    will be used.  See xchk.h for the default filename.
X
XThe output table is ordered alphabetically by user id and then by system name.
XFor an example of the expected input file format and corresponding output,
Xsee the "test.in" and "test.out" files.  (Note that there may be a difference
Xof +-0.1 on reported "CPS" rates, depending on the default method of
Xrounding used by printf().)
X
X
X                              IMPLEMENTATION NOTES
X
XI wrote this program both as a useful utility and as a data structure
Xexercise.  Therefore, if you wish to modify it, the following information
Xmight be useful:
X
X    - The "Sumtable" structure, which is an orthogonal list, is described in
X      the comments as if it were a table with rows for each system and columns
X      for each user.
X    - Some fields in the "Xfer_t" structure are filled in by parse_line() but
X      are never used.  I thought there might be a use for them eventually,
X      such as using the process and job ids to find more information about
X    specific transfers from the uux/uuxqt logs.
X
X
XI tried to write this without using calls specific to certain environments.
XI have only tested it under SysV and PCDOS 3.30 (don't ask), however.  If
Xyou find anything which must be changed to make the program work on your
Xplatform, I would appreciate notification of what was necessary.
X
XThis program is hereby placed in the public domain.
X
X
XDoug Lee
X03/01/91
/
echo x - test.in
sed '/^X/s///' > test.in << '/'
Xuunet!dlee M (2/28-23:28:38) (C,22925,1) [ttyh01] -> 2996 / 1.790 secs, 1673 bytes/sec
Xuunet!dlee M (2/28-23:28:57) (C,22925,2) [ttyh01] -> 155 / 0.050 secs, 3100 bytes/sec
Xuunet!dlee M (2/28-23:29:02) (C,22925,3) [ttyh01] -> 401 / 0.210 secs, 1909 bytes/sec
Xuunet!dlee M (2/28-23:29:07) (C,22925,4) [ttyh01] -> 157 / 0.050 secs, 3140 bytes/sec
Xuunet!daemon M (2/28-23:29:25) (C,22925,5) [ttyh01] <- 1305 / 3.850 secs, 338 bytes/sec
Xuunet!daemon M (2/28-23:29:29) (C,22925,6) [ttyh01] <- 86 / 2.080 secs, 41 bytes/sec
Xuunet!daemon M (2/28-23:29:35) (C,22925,7) [ttyh01] <- 640 / 3.230 secs, 198 bytes/sec
Xuunet!daemon M (2/28-23:29:40) (C,22925,8) [ttyh01] <- 89 / 2.340 secs, 38 bytes/sec
Xuunet!daemon M (2/28-23:29:47) (C,22925,9) [ttyh01] <- 1732 / 4.530 secs, 382 bytes/sec
Xuunet!daemon M (2/28-23:29:51) (C,22925,10) [ttyh01] <- 87 / 2.120 secs, 41 bytes/sec
Xuunet!daemon M (2/28-23:29:57) (C,22925,11) [ttyh01] <- 1704 / 3.630 secs, 469 bytes/sec
Xuunet!daemon M (2/28-23:30:01) (C,22925,12) [ttyh01] <- 87 / 2.540 secs, 34 bytes/sec
Xuunet!daemon M (2/28-23:30:09) (C,22925,13) [ttyh01] <- 2750 / 5.610 secs, 490 bytes/sec
Xuunet!daemon M (2/28-23:30:13) (C,22925,14) [ttyh01] <- 81 / 2.140 secs, 37 bytes/sec
Xuunet!uucp M (2/28-23:30:19) (C,22925,15) [ttyh01] <- 1551 / 3.750 secs, 413 bytes/sec
Xuunet!uucp M (2/28-23:30:24) (C,22925,16) [ttyh01] <- 77 / 2.200 secs, 35 bytes/sec
Xuunet!daemon M (2/28-23:30:29) (C,22925,17) [ttyh01] <- 587 / 3.310 secs, 177 bytes/sec
Xuunet!daemon M (2/28-23:30:34) (C,22925,18) [ttyh01] <- 89 / 2.440 secs, 36 bytes/sec
Xuunet!daemon M (2/28-23:31:08) (C,22925,19) [ttyh01] <- 21159 / 32.130 secs, 658 bytes/sec
Xuunet!daemon M (2/28-23:31:12) (C,22925,20) [ttyh01] <- 100 / 2.220 secs, 45 bytes/sec
Xuunet!daemon M (2/28-23:32:45) (C,22925,21) [ttyh01] <- 62755 / 90.620 secs, 692 bytes/sec
Xuunet!daemon M (2/28-23:32:51) (C,22925,22) [ttyh01] <- 100 / 2.420 secs, 41 bytes/sec
Xbradley!dlee M (2/28-23:34:11) (C,22944,1) [ttyh01] -> 1883 / 1.160 secs, 1623 bytes/sec
Xbradley!dlee M (2/28-23:34:14) (C,22944,2) [ttyh01] -> 174 / 0.090 secs, 1933 bytes/sec
Xbradley!news M (2/28-23:35:35) (C,22944,3) [ttyh01] <- 65767 / 76.670 secs, 857 bytes/sec
Xbradley!news M (2/28-23:35:39) (C,22944,4) [ttyh01] <- 204 / 1.940 secs, 105 bytes/sec
Xbradley!news M (2/28-23:37:20) (C,22944,5) [ttyh01] <- 84510 / 99.270 secs, 851 bytes/sec
Xbradley!news M (2/28-23:37:25) (C,22944,6) [ttyh01] <- 204 / 1.940 secs, 105 bytes/sec
Xbradley!news M (2/28-23:43:17) (C,22944,7) [ttyh01] <- 307129 / 350.210 secs, 876 bytes/sec
Xbradley!news M (2/28-23:43:22) (C,22944,8) [ttyh01] <- 204 / 2.160 secs, 94 bytes/sec
Xbradley!news M (2/28-23:44:26) (C,22944,9) [ttyh01] <- 51840 / 61.360 secs, 844 bytes/sec
Xbradley!news M (2/28-23:44:30) (C,22944,10) [ttyh01] <- 204 / 1.950 secs, 104 bytes/sec
Xbradley!uucp M (3/1-0:52:12) (C,22944,103) [ttyh01] <- 454 / 2.020 secs, 224 bytes/sec
Xbradley!uucp M (3/1-0:52:15) (C,22944,104) [ttyh01] <- 220 / 1.900 secs, 115 bytes/sec
Xuunet!dlee M (3/1-2:27:20) (C,24410,1) [ttyh01] -> 910 / 0.580 secs, 1568 bytes/sec
Xuunet!dlee M (3/1-2:27:24) (C,24410,2) [ttyh01] -> 161 / 0.090 secs, 1788 bytes/sec
Xuunet!daemon M (3/1-2:27:37) (C,24410,3) [ttyh01] <- 6066 / 7.440 secs, 815 bytes/sec
Xuunet!daemon M (3/1-2:27:42) (C,24410,4) [ttyh01] <- 123 / 2.390 secs, 51 bytes/sec
Xhrmpc!howard S (3/1-11:23:03) (C,28104,1) [ttyh05] -> 136233 / 619.390 secs, 219 bytes/sec
/
echo x - test.out
sed '/^X/s///' > test.out << '/'
X(This covers log entries from 2/28-23:28:38 through 3/1-11:23:03)
X
X
X                             Incoming                   Outgoing
X  User      System   Nfiles   Size     Time     Nfiles   Size     Time     CPS
X
Xdaemon    uunet         18    99540  00:02:55       0        0  00:00:00  568.7
Xdlee      bradley        0        0  00:00:00       2     2057  00:00:01 1645.6
Xdlee      uunet          0        0  00:00:00       6     4780  00:00:03 1725.6
Xhoward    hrmpc          0        0  00:00:00       1   136233  00:10:19  219.9
Xnews      bradley        8   510062  00:09:56       0        0  00:00:00  856.5
Xuucp      bradley        2      674  00:00:04       0        0  00:00:00  171.9
Xuucp      uunet          2     1628  00:00:06       0        0  00:00:00  273.6
/
echo x - xchk.c
sed '/^X/s///' > xchk.c << '/'
X/*
X * This program digests the "xferstats" file and prints a summary of file
X * transfer activity.
X */
X
X
X#include <stdio.h>
X#include <string.h>  /* For strcmp() and strcpy() */
X#include "xchk.h"
X
Xmain(argc, argv)
X  int argc;
X  char **argv;
X{
X  FILE *sfile;  /* The xferstats file */
X  Sumtable_t *sumtable;  /* The cumulative data table */
X
X  if (argc > 2) {
X    fprintf(stderr, "Usage: %s [ file | - ]\n", pgm);
X    fprintf(stderr, "If no filename is given, %s will be used.\n", STATFILE);
X    exit(-1);
X  }
X
X  /* Make the base node for the table */
X#ifdef DEBUG
X  printf("Creating base node for table\n");
X#endif /* DEBUG */
X  sumtable = newnode();
X
X  /* Determine which file is to be read and open it if necessary */
X  ++argv, --argc;
X  if (argc && strcmp(*argv, "-") == 0) {
X    sfile =   stdin;
X  } else {
X    if (!argc) *argv = STATFILE;
X    if ((sfile = fopen(*argv, "r")) == NULL) {
X      fprintf(stderr, "Can't access ");
X      perror(*argv);
X      exit(1);
X    }
X  }
X
X  /* Process the file */
X#ifdef DEBUG
X  printf("Processing %s\n", *argv);
X#endif /* DEBUG */
X  (void) process(sfile, sumtable);
X#ifdef DEBUG
X  printf("Done; %d nodes and %d ids generated.\n", nodecnt, idcnt);
X#endif /* DEBUG */
X  fclose(sfile);
X
X  (void) print_report(sumtable);
X  exit(0);
X}
X
X
X/*
X * Read a file and accumulate its data in the sumtable
X */
Xvoid process(sfile, sumtable)
X  FILE *sfile;
X  Sumtable_t *sumtable;
X{
X  static Xfer_t xbuf;  /* The structure filled for each input line */
X  Sumtable_t *p;
X  int r;
X
X  lnum = 1;
X  while (r = parse_line(sfile, &xbuf)) {  /* Fill struct from next input line */
X    if (r < 0) {
X      fprintf(stderr, "Warning: unparsable line (%d) skipped\n", lnum);
X      ++lnum;
X      continue;
X    }
X    if (!*firstdate)
X      if (*(xbuf.when)) {
X        strcpy(firstdate, xbuf.when);  /* Save for report */
X#ifdef DEBUG
X        printf("  Starting date found: %s\n", firstdate);
X#endif /* DEBUG */
X      } else {
X        fprintf(stderr, "Warning: skipping null date field on line %d\n", lnum);
X      }
X    if ((p = getnode(sumtable, xbuf.sys, xbuf.user))) {
X      (void) copydata(&xbuf, p);
X    } else {
X      fprintf(stderr, "%s: Out of table space!\n", pgm);
X      fclose(sfile);
X      exit(2);
X    }
X    ++lnum;
X  }
X  if (*(xbuf.when)) {
X    lastdate = xbuf.when;  /* It won't be clobbered--we're done reading */
X#ifdef DEBUG
X    printf("  Last date found: %s\n", lastdate);
X#endif /* DEBUG */
X  } else {
X    fprintf(stderr, "Warning: last date not found\n");
X    lastdate = "";
X  }
X}
X
X
X/*
X * Read a line of input and use it to fill an Xfer_t structure
X */
Xint parse_line(sfile, buf)
X  FILE *sfile;
X  Xfer_t *buf;
X{
X  int r;
X  char dirbuf[3];
X  char line[256];
X  char *p;
X
X#ifdef DEBUG
X  printf("Line %d:\n", lnum);
X#endif /* DEBUG */
X  if (fgets(line, 256, sfile) == NULL) return 0;
X  for (p = line; *p; p++) ;
X  if (*(--p) != '\n') {
X    fprintf(stderr, "%s: parse_line: line %d too long (> %d)\n", pgm, lnum,
X        (p - line + 1));
X    return -1;
X  }
X
X  /* One *UGLY* sscanf() call here ... */
X  r = sscanf(line,
X  "%10[^!]!%30s %*[^(](%15[^)]) (%*c,%ld,%d) %*[^]]] %2s %ld / %lf secs, %lf",
X      buf->sys, buf->user, buf->when, &(buf->proc), &(buf->job), dirbuf,
X      &(buf->size), &(buf->seconds), &(buf->rate));
X  if (r < 9) {
X#ifdef DEBUG
X    printf("  sscanf returned %d\n", r);
X#endif /* DEBUG */
X    return -r;
X  }
X  if (strcmp(dirbuf, INBOUNDSTR) == 0) buf->direction = INBOUND;
X  else if (strcmp(dirbuf, OUTBOUNDSTR) == 0) buf->direction = OUTBOUND;
X  else {
X    fprintf(stderr,
X        "%s: Transfer direction %s not recognized; assuming inbound\n",
X        pgm);
X    buf->direction = INBOUND;
X  }
X  return 1;
X}
X
X
X/*
X * Create a new node for the Sumtable_t table.  This function WILL abort the
X * program if malloc() fails.
X */
XSumtable_t *newnode()
X{
X  Sumtable_t *p;
X  static char *empty = "";  /* All uninitialized id pointers point here */
X
X  if ((p = (Sumtable_t *)malloc(sizeof(Sumtable_t))) == (Sumtable_t *)0) {
X    fprintf(stderr, pgm);
X    perror(" (newnode): malloc() failed");
X  exit(2);
X  }
X
X  p->ids[BYSYS] = p->ids[BYUSER] = empty;
X  p->fcounts[INBOUND] = p->fcounts[OUTBOUND] = 0;
X  p->bytes[INBOUND] = p->bytes[OUTBOUND] = 0;
X  p->secs[INBOUND] = p->secs[OUTBOUND] = (double)0;
X  p->next[BYSYS] = p->next[BYUSER] = TABLE_EDGE;
X#ifdef DEBUG
X  nodecnt++;
X#endif /* DEBUG */
X  return p;
X}
X
X/*
X * Create a new system or user id.  (Only one string is stored for each id;
X * the Sumtable_t nodes only contain pointers.)  This function WILL abort the
X * program if malloc() fails.
X */
Xchar *newid(id)
X  char *id;
X{
X  char *s;
X
X  if ((s = (char *)malloc(MAXID)) == (char *)0) {
X    fprintf(stderr, pgm);
X    perror(" (newid): malloc() failed");
X    exit(2);
X  }
X  strcpy(s, id);
X#ifdef DEBUG
X  idcnt++;
X#endif /* DEBUG */
X  return s;
X}
X
X
X/*
X * Find a node of type Sumtable_t with ids[whichway] (sys or user) matching
X * the given id.  Returns 1 if found, with p pointing to the node.  If the
X * node cannot be found, returns 0 with p pointing to the node which should
X * precede the one sought in the given direction.
X */
Xint findnode(p, id, whichway)
X  Sumtable_t **p;
X  char *id;
X  int whichway;
X{
X  Sumtable_t *cur, *prev;
X  int found, done;
X  int r;
X
X#ifdef DEBUG
X  printf("    Seeking %s %s: ", (whichway == BYSYS)? "system": "user", id);
X#endif /* DEBUG */
X  cur = *p;
X  found = done = 0;
X  while (!done && cur != TABLE_EDGE) {
X    r = strcmp(cur->ids[whichway], id);
X    if (r < 0) {
X      prev = cur;
X      cur = cur->next[whichway];
X    } else if (r == 0) {
X      found++;
X      done++;
X    } else {
X      done++;
X    }
X  }
X  *p = (found ? cur : prev);
X#ifdef DEBUG
X  found? printf("found\n"): printf("stopped at %s\n", *(*p)->ids[whichway]?
X      (*p)->ids[whichway]: "<empty>");
X#endif /* DEBUG */
X  return found;
X}
X
X/*
X * Find the node with the given user and sys.  If it doesn't exist,
X * create it and any header nodes necessary for proper linking.
X */
XSumtable_t *getnode(top, sys, user)
X  Sumtable_t *top;
X  char *sys, *user;
X{
X  Sumtable_t *sptr, *uptr, *n, *tmp;
X  char *uidptr, *sidptr;  /* Pointers for user and system id strings */
X
X#ifdef DEBUG
X  printf("  Getting node %s,%s\n", sys, user);
X#endif /* DEBUG */
X  sptr = uptr = top;
X  if (findnode(&sptr, sys, BYSYS)) {  /* Scan down for system header */
X    if (findnode(&sptr, user, BYUSER)) return sptr; /* AHA!  Found the node. */
X    /* else *sptr is the node just left of where ours should go */
X    sidptr = sptr->ids[BYSYS];  /* We'll have to carry this over */
X  } else {  /* System header not found; create it */
X#ifdef DEBUG
X    printf("    Creating %s system header\n", sys);
X#endif /* DEBUG */
X    tmp = sptr->next[BYSYS];
X    sptr->next[BYSYS] = newnode();
X    sptr = sptr->next[BYSYS];
X    sptr->next[BYSYS] = tmp;
X    sidptr = newid(sys);
X    sptr->ids[BYSYS] = sidptr;
X  } /* sptr is now pointing to the node just left of where ours should be */
X
X  if (findnode(&uptr, user, BYUSER)) {
X    findnode(&uptr, sys, BYSYS);  /* Stops just above the missing node */
X    uidptr = uptr->ids[BYUSER];  /* Save to carry over to new node (n) */
X  } else {  /* Oops, no user header; we'll fix that ... */
X#ifdef DEBUG
X    printf("    Creating %s user header\n", user);
X#endif /* DEBUG */
X    tmp = uptr->next[BYUSER];
X    uptr->next[BYUSER] = newnode();
X    uptr = uptr->next[BYUSER];
X    uptr->next[BYUSER] = tmp;
X    uidptr = newid(user);
X    uptr->ids[BYUSER] = uidptr;
X  }  /* uptr now points to the node just above where ours should go */
X
X  /* Patch in the new node */
X#ifdef DEBUG
X  printf("    Creating node %s,%s\n", sidptr, uidptr);
X#endif /* DEBUG */
X  n = newnode();
X  tmp = sptr->next[BYUSER];
X  sptr->next[BYUSER] = n;
X  n->next[BYUSER] = tmp;
X
X  tmp = uptr->next[BYSYS];
X  uptr->next[BYSYS] = n;
X  n->next[BYSYS] = tmp;
X
X  n->ids[BYSYS] = sidptr;
X  n->ids[BYUSER] = uidptr;
X
X  return n;
X}
X
X
X/*
X * Add data from an Xfer_t structure to a Sumtable_t node
X */
Xvoid copydata(src, dest)
X  Xfer_t *src;
X  Sumtable_t *dest;
X{
X  int dir;
X
X#ifdef DEBUG
Xprintf("  Adding data to node %s,%s\n", dest->ids[BYSYS], dest->ids[BYUSER]);
X#endif /* DEBUG */
X  dir = (int)src->direction;
X  dest->fcounts[dir]++;
X  dest->bytes[dir] += src->size;
X  dest->secs[dir] += src->seconds;
X}
X
X
X/*
X * Convert a time in seconds to a string of the form hh:mm:ss.  NOTICE all
X * calls use the same string space!
X */
Xchar *cvttime(secs)
X  double secs;
X{
X  int h,m,s;
X  static char str[9];
X
X  h = secs / 3600;
X  secs -= h * 3600;
X  m = secs / 60;
X  s = (int)(secs + 0.5 - 60 * m);
X  sprintf(str, "%02d:%02d:%02d", h, m, s);
X  return str;
X}
X
X/*
X * Print the sumtable sorted by user by system.  Also prints an estimate of the
X * transfer rate in CPS for each user-system combination.  (This figure
X * combines both incoming and outgoing traffic.)
X */
Xvoid print_report(sumtable)
X  Sumtable_t *sumtable;
X{
X  Sumtable_t *p, *q;
X
X  /* I'll just assume nobody stole my base node here ... */
X  if (sumtable->next[BYSYS] == TABLE_EDGE) {  /* We only need to check one */
X    printf("Nothing to report.\n");
X    return;
X  }
X
X  if (*firstdate && *lastdate) {
X    printf("(This covers log entries from %s through %s)\n\n\n", firstdate,
X        lastdate);
X  } else {
X    printf("[dates missing]\n\n\n");
X  }
X
X  printf("%37s%27s\n", "Incoming", "Outgoing");
X  printf("  User      System  ");
X  printf(" Nfiles   Size     Time     Nfiles   Size     Time     CPS\n\n");
X  p = sumtable;
X  while ((p = p->next[BYUSER]) != TABLE_EDGE) {
X    q = p;
X    while ((q = q->next[BYSYS]) != TABLE_EDGE) {
X      printf("%-10s%-10s", q->ids[BYUSER], q->ids[BYSYS]);
X      printf("%6d%9ld%10s", q->fcounts[INBOUND], q->bytes[INBOUND],
X          cvttime(q->secs[INBOUND]));
X      printf("%8d%9ld%10s", q->fcounts[OUTBOUND], q->bytes[OUTBOUND],
X          cvttime(q->secs[OUTBOUND]));
X      printf("%7.1lf\n", (q->bytes[INBOUND] + q->bytes[OUTBOUND]) /
X          (q->secs[INBOUND] + q->secs[OUTBOUND]));
X    }
X  }
X}
/
echo x - xchk.h
sed '/^X/s///' > xchk.h << '/'
X/*
X * Header for xchk.
X */
X
X
X/* Defining DEBUG tends to produce *LOTS* of output! */
X/*#define DEBUG*/
X
X#define STATFILE "/usr/spool/uucp/.Admin/xferstats"  /* Default input source */
X#define INBOUNDSTR "<-"  /* String used to identify inbound files */
X#define OUTBOUNDSTR "->"  /* String used to identify outbound files */
X
X#define SMAXLEN 30  /* Max length of system name */
X#define UMAXLEN 10  /* Max length of user login ids (usually 8, but ...) */
X#define MAXID 30  /* Longest possible id string (>= max(SMAXLEN,UMAXLEN) */
X#define TABLE_EDGE (Sumtable_t *)0  /* A table null pointer */
X
X/* Data flow direction indicators */
X#define INBOUND 0
X#define OUTBOUND 1
X
X/* Table traversal direction indicators */
X#define BYSYS 0
X#define BYUSER 1
X
X/* The record structure for each transfer (some fields not currently used) */
Xtypedef struct {
X  char sys[SMAXLEN];  /* Remote system name */
X  char user[UMAXLEN];    /* User whose file was transferred */
X  char when[15];          /* When the transfer occurred */
X  long proc;              /* Process ID of the responsible ``uucico'' */
X  int job;                /* Job number (0 first) of transfer */
X  int direction;          /* Direction of transfer */
X  long size;              /* Size of transferred file */
X  double seconds;         /* Transfer time in seconds */
X  double rate;            /* Reported transfer rate (presumably size/seconds) */
X} Xfer_t;
X
X/* The data accumulation structure */
Xtypedef struct Sumtable {
X  char *ids[2];               /* System and user ids (BYSYS,BYUSER) */
X  int fcounts[2];                /* Numbers of transfers (INBOUND,OUTBOUND) */
X  long bytes[2];                 /* Total byte counts (INBOUND,OUTBOUND) */
X  double secs[2];                /* Time totals (INBOUND,OUTBOUND) */
X  struct Sumtable *next[2];      /* Orthogonal pointers (BYSYS,BYUSER) */
X} Sumtable_t;
X
X
Xchar *pgm = "xchk";  /* For error messages */
Xint lnum;  /* Current line number in input file */
Xchar firstdate[15] = "";  /* Date of first log entry */
Xchar *lastdate;  /* Date of last log entry (no space needed; see process() */
X#ifdef DEBUG
Xint nodecnt = 0;
Xint idcnt = 0;
X#endif /* DEBUG */
X
X
Xvoid process();
Xint parse_line();
XSumtable_t *newnode();
Xchar *newid();
Xint findnode();
XSumtable_t *getnode();
Xchar *cvttime();
Xvoid copydata(), print_report();
/
exit 0
-----------CUT HERE-----------

-- 
Doug Lee  (dlee@athenanet.com or {bradley,uunet}!pallas!dlee)