mark@jhereg.Jhereg.MN.ORG (Mark H. Colburn) (05/25/88)
This is a public domain version of the System V sar command for the 3b1. As far as I can tell it is a complete implementation, except for disk acounting (see the README file) of the System V version of sar. This code was written without use of the AT&T or Berkley sources, other than the man pages. For installation instructions, see the README file. -- Mark H. Colburn mark@jhereg.Jhereg.MN.ORG ..!ihnp4!chinet!jhereg!mark ------------------------------- CUT HERE ------------------------------- #! /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 shell archive." # Contents: README Makefile sar.c sadc.c sar.h sa1.SH sa2.SH sar.1 # sar.1m # Wrapped by mark@jhereg on Tue May 24 22:41:29 1988 PATH=/bin:/usr/bin:/usr/ucb ; export PATH if test -f README -a "${1}" != "-c" ; then echo shar: Will not over-write existing file \"README\" else echo shar: Extracting \"README\" \(3910 characters\) sed "s/^X//" >README <<'END_OF_README' X X This is a public domain sar(1) system activity package written for the 3b1 X version 3.51. This is a complete rewrite of the sar package, reverse X engineered from the man pages. It was not derived from any software, except X for the file format information in the sar.h header file which was exctracted X from the sar.1m manual page. It would most likely run on other machines and X other versions of UNIX with a little hacking. It does make use of a few 3b1 X only featurs, namely syslocal and ktune, but these could be hacked around. X X The source is copyrighted. Permission is given for unlimited copying and X modification for non-commercial use as long as the source (not binary only) X is distributed, and this file accompanies the distribution, and I am X given credit where credit is due. X X I thought of writting this when answering a question about using the X nlist(3c) functions to access /dev/kmem. It seemed pretty easy to do (which X was not true in the final analysis, but, hey...) and since the 3b1 does not X have a sar, I thought that I would write it. X X It seems a little silly that a sar command was not developed and/or X distributed with the 3b1. After all, they give you an easy way to X reconfigure the kernel, you would think that they would give you some way to X determine if the kernel actually *needs* reconfiguring. X X X XINSTALLATION X X Traditionally, sar is run by the user 'adm' (administrative user). This X is how the installation is currently set up. If you would like to have X sar owned by someone else, you will have to change the Makefile X accordingly. X X The sadc command is set-GIDed to group sys in order for it to read X /dev/kmem. The program does a setgid(getgid()) as soon as /dev/kmem is X open, so it should be secure. X X To install this package edit the Makefile to specify the correct place for X the finished binaries and manual pages to go, then su to root and type X 'make install' as root. The install procedure will create two new X directories and set the file ownerships and permissions as follows: X X /usr/lib/sa - hiding place of shell scripts and support programs X /usr/adm/sa - contains the outputs of the sar and sadc programs X X Once the software has been configured and installed, you will want to X add the following commands to your crontab file: X X 0 * * * 0,6 su adm -c "/usr/lib/sa/sa1" X 0 8-17 * * 1-5 su adm -c "/usr/lib/sa/sa1 1200 3" X 0 18-7 * * 1-5 su adm -c "/usr/lib/sa/sa1" X 59 23 * * * su adm -c "/usr/lib/sa/sa2 -A" X X These can be suitably editted if you are running a better version of cron X than the one that is distributed with the 3b1. You may wish to change X the /usr/lib/sa/sa2 command line paramenters. These parameters are X simply passed through to sar, so they can be anything which suits your X purpose. X X Then add the following line to your /etc/rc: X X su adm -c "/usr/lib/sa/sadc /usr/adm/sa/sa`date +%d`" X X This line should be put into the /etc/rc file verbatim so that the X command is run by adm. This writes out a reboot record to the daily X accounting file whenever /etc/rc is run. X X That's it. You're done. X X X XLIMITATIONS X X At this time, this package is a complete implementation of the System V X sar commend with the following limitations: X X * Sar disk reporting is referenced, but does not work. This is due to X the apparent lack of disk statistic recording within the kernel. If I X can find the data in the kernel somewhere, I will add it. Pointers X andybody? X X I will be working on this package on an ongoing basis to fix any bugs and X to additional functionality that is needed. If you have any fixes, X questions, comments or suggestions please e-mail, call or write to: X X X Mark H. Colburn mark@jhereg.mn.org X 76 Gant Circle, #F ...!ihnp4!chinet!jhereg!mark X Streamwood, IL 60107 X (312) 213-2852 END_OF_README if test 3910 -ne `wc -c <README`; then echo shar: \"README\" unpacked with wrong size! fi # end of overwriting check fi if test -f Makefile -a "${1}" != "-c" ; then echo shar: Will not over-write existing file \"Makefile\" else echo shar: Extracting \"Makefile\" \(1875 characters\) sed "s/^X//" >Makefile <<'END_OF_Makefile' X# X# Makefile for sar System Activity Reporting Package X# X# @(#)Makefile 1.7 4/24/88 Copyright Mark H. Colburn X# X# X Xinclude $(MAKEINC)/Makepre.h X X# X# Please change the following parameter to match your system: X# X# BINDIR - the directory where sar will eventually live. X# SARONR - the LOGNAME of the user who owns sar (should be adm) X# SARGRP - the group of the user who owns sar (should be adm) X# KMEMGRP - the group who has read access to /dev/kmem X# MANDIR - the directory where the man pages should be copied to X# PERMS - default permissions for binaries and directories X# XBINDIR = /usr/bin XSARONR = adm XSARGRP = adm XKMEMGRP = sys XMANDIR = /usr/man/man1 XPERM = 755 X X XCFLAGS = -O XLDFLAGS = -s XPROGRAMS = sadc sar sa1 sa2 XSOURCES = README Makefile sar.c sadc.c sar.h sa1.SH sa2.SH sar.1 sar.1m XMANPAGES = sar.1 sar.1m XOBJECTS = sar.o sadc.o XMATHLIB = -lm X Xall: $(PROGRAMS) X Xinstall: all X if [ ! -d /usr/adm/sa ]; then mkdir /usr/adm/sa; fi X if [ ! -d /usr/lib/sa ]; then mkdir /usr/lib/sa; fi X chmod $(PERM) /usr/adm/sa /usr/lib/sa X chown $(SARONR) /usr/adm/sa /usr/lib/sa/* /usr/lib/sa /usr/lib/sa/* X chgrp $(SARGRP) /usr/adm/sa /usr/lib/sa/* /usr/lib/sa /usr/lib/sa/* X for file in sadc sa1 sa2; do \ X cp $$file /usr/lib/sa; \ X chmod $(PERM) /usr/lib/sa/$$file; \ X chown $(SARONR) $$file; \ X chgrp $(SARONR) $$file; \ X done X chgrp $(KMEMGRP) /usr/lib/sa/sadc X chmod g+s /usr/lib/sa/sadc X cp sar $(BINDIR) X chmod $(PERM) $(BINDIR)/sar X cp $(MANPAGES) $(MANDIR) X Xclean: X rm -f $(OBJECTS) $(PROGRAMS) a.out core X Xclobber: clean X rm -f $(SOURCES) X Xshar: X shar $(SOURCES) > shar X Xsadc: sadc.o sar.h X $(LD) $(SHAREDLIB) $(LDFLAGS) sadc.o -o sadc X Xsar: sar.o sar.h X $(LD) $(SHAREDLIB) $(LDFLAGS) sar.o -o sar $(MATHLIB) X Xsa1: sa1.SH X cp sa1.SH sa1 X chmod a+x sa1 X Xsa2: sa2.SH X sed 's;%BINDIR%;$(BINDIR);g' sa2.SH > sa2 X chmod a+x sa2 X Xinclude $(MAKEINC)/Makepost.h END_OF_Makefile if test 1875 -ne `wc -c <Makefile`; then echo shar: \"Makefile\" unpacked with wrong size! fi # end of overwriting check fi if test -f sar.c -a "${1}" != "-c" ; then echo shar: Will not over-write existing file \"sar.c\" else echo shar: Extracting \"sar.c\" \(31357 characters\) sed "s/^X//" >sar.c <<'END_OF_sar.c' X/* X * sar - System Activity Report X * X * This routine reads the data files written by sadc(1m) and writes user X * readable reports. X * X * Author: Mark H. Colburn (mark@jhereg.mn.org) X */ X X#include <stdio.h> X#include <sys/utsname.h> X#include <sys/sysinfo.h> X#include <string.h> X#include <fcntl.h> X#include <ctype.h> X#include <nlist.h> X#include <time.h> X#include "sar.h" X X#ifndef lint Xstatic char *SccsID = "@(#)sar.c 1.9 3/16/88 Copyright Mark H. Colburn"; X#endif X X/* we need to declare all of these to initialize the function array */ X Xvoid print_cpu(), print_buffer(), print_disk(), print_tty(); Xvoid print_syscall(), print_swap(), print_file(), print_queues(); Xvoid print_status(), print_message(), print_paging(); Xvoid print_report(); X X#define END(v) (sizeof(v)/sizeof(v[0])) X#define DAYSECS 86399 X#define PRINT_CPU 0 X#define PRINT_DISK 1 X#define PRINT_QUEUES 2 X#define PRINT_BUFFER 3 X#define PRINT_SWAP 4 X#define PRINT_SYSCALL 5 X#define PRINT_FILE 6 X#define PRINT_TTY 7 X#define PRINT_STATUS 8 X#define PRINT_MESSAGE 9 X#define PRINT_PAGING 10 X X/* X * this array of structures keeps track of which reports need to be generated, X * the functions which need to be called to generate those reports, and the X * order in which the reports are printed. X */ X Xstruct { X void (*func) (); X int doit; X} reports[] = { X { print_cpu, 0 }, X { print_disk, 0 }, X { print_queues, 0 }, X { print_buffer, 0 }, X { print_swap, 0 }, X { print_syscall, 0 }, X { print_file, 0 }, X { print_tty, 0 }, X { print_status, 0 }, X { print_message, 0 }, X { print_paging, 0 } X}; X X Xstruct sa currsa; /* current system statistics for quantum */ Xstruct sa oldsa; /* system statistics for last quantum */ Xchar *myname; /* name of this program (argv[0]) */ Xint rptcnt = 0; /* number of reports selected */ Xint interval; /* interval to display samples */ Xint repeat = 0; /* number of iterations to run */ Xint delay = 0; /* time delay between data colletcions */ Xtime_t start_tm = 0; /* time to start display (in seconds) */ Xtime_t end_tm = DAYSECS; /* time to end display (in seconds) */ X Xextern int optind; Xextern char *optarg; Xextern int errno; Xvoid exit(); Xlong lseek(); Xtime_t time(); Xtime_t get_time(); Xvoid fatal(); Xvoid usage(); X X X/* X * main - main routine, processes arguments X */ X Xint Xmain(argc, argv) X int argc; X char *argv[]; X{ X struct utsname sysname; /* name of this system */ X struct tm *mytime; /* used to print time */ X time_t now; /* used to fetch time */ X char infnm[128]; /* name of input data file */ X char outfn[128]; /* name of output data file (-o) */ X int oflag = 0; /* used to assure command line sanity */ X int fflag = 0; X int c; X int i; X X myname = argv[0]; X strcpy(outfn, ""); X X /* get current system time */ X now = time((int *) 0); X mytime = localtime(&now); X X /* default input data file name */ X (void) sprintf(infnm, "/usr/adm/sa/sa%.2d", mytime->tm_mday); X X /* process command line arguments */ X while ((c = getopt(argc, argv, "ubdycwaqvmpAi:e:s:o:f:")) != EOF) { X rptcnt++; X switch (c) { X case 'u': X reports[PRINT_CPU].doit = 1; X break; X case 'b': X reports[PRINT_BUFFER].doit = 1; X break; X case 'd': X reports[PRINT_DISK].doit = 1; X break; X case 'y': X reports[PRINT_TTY].doit = 1; X break; X case 'c': X reports[PRINT_SYSCALL].doit = 1; X break; X case 'w': X reports[PRINT_SWAP].doit = 1; X break; X case 'a': X reports[PRINT_FILE].doit = 1; X break; X case 'q': X reports[PRINT_QUEUES].doit = 1; X break; X case 'v': X reports[PRINT_STATUS].doit = 1; X break; X case 'm': X reports[PRINT_MESSAGE].doit = 1; X break; X case 'p': X reports[PRINT_PAGING].doit = 1; X break; X case 'A': X for (i = PRINT_CPU; i <= PRINT_PAGING; i++) { X reports[i].doit = 1; X rptcnt++; X } X break; X case 's': X rptcnt--; X fflag++; X start_tm = get_time(optarg); X break; X case 'e': X rptcnt--; X fflag++; X end_tm = get_time(optarg); X break; X case 'i': X rptcnt--; X fflag++; X interval = atoi(optarg); X if (interval < 1 || interval > DAYSECS) { X fputs("interval<1 or >number of seconds in a day\n", stderr); X usage(); X } X break; X case 'o': X rptcnt--; X oflag++; X (void) strcpy(outfn, optarg); X break; X case 'f': X rptcnt--; X fflag++; X (void) strcpy(infnm, optarg); X break; X default: X usage(); X break; X } X } X X if (oflag && fflag) X usage(); X if (optind < argc) { X delay = atoi(argv[optind]); X if (++optind < argc) X repeat = atoi(argv[optind]); X else X repeat = 1; X if (fflag || ++optind < argc || delay < 0 || repeat < 0) X usage(); X repeat++; X } X X /* if no reports selected, print the cpu statistics by default */ X if (rptcnt == 0) { X reports[PRINT_CPU].doit = 1; X rptcnt++; X } X X /* turn off disk reporting, it is not supported by the kernel. sigh. */ X if (reports[PRINT_DISK].doit == 1) { X reports[PRINT_DISK].doit = 0; X --rptcnt; X } X X /* print the report header */ X (void) uname(&sysname); X (void) printf("\n%s %s %s %s %s %.2d/%.2d/%d\n", X sysname.sysname, sysname.nodename, sysname.release, sysname.version, X sysname.machine, mytime->tm_mon + 1, mytime->tm_mday, mytime->tm_year); X X X /* print the reports the user selected */ X print_report(infnm, outfn); X return (0); X} X X X/* X * print_report - print all of the statistics that the user has requested X * X */ X Xvoid Xprint_report(inname, outname) X char *inname; /* name of file to get statistics from */ X char *outname; /* name of file to write statistics to */ X{ X FILE *infp; /* input data file descriptor */ X int outfd; /* output file descriptor */ X int err; /* return code indication */ X int first; /* 1 if first time proccessing any report */ X char cmdbuf[64]; /* used to hold the command to sadc */ X register int i; X X /* X * check to see if we are getting our statistics from a file (count == 0) X * if we are getting our statistics from sadc in real-time. Once we X * figure this out, we can open the correct input file. X */ X X outfd = -1; X if (repeat == 0) { X if ((infp = fopen(inname, "r")) == NULL) X fatal(stderr, "Unable to open input file"); X } else { X /* -o filename was specified, open output file */ X if (strlen(outname) && X (outfd = open(outname, O_CREAT | O_WRONLY, 0666)) == -1) X fatal("unable to open output file"); X X /* getting data from sadc, fire off the command... */ X sprintf(cmdbuf, "/usr/lib/sa/sadc %d %d", delay, repeat); X if ((infp = popen(cmdbuf, "r")) == NULL) X fatal("unable to popen /usr/lib/sa/sadc"); X } X X i = 0; X do { X if (repeat || reports[i].doit) { X X putchar('\n'); X first = 1; X while (!feof(infp)) { X err = (fread((char *) &currsa, sizeof(currsa), 1, infp)); X if (ferror(infp) || feof(infp)) X err = 0; X if (err == 1 && outfd >= 0 && X write(outfd, (char *) &currsa, sizeof(currsa)) < 0) X fatal("unable to write to output file"); X if (err == 1 && currsa.ts == 0) { X err = reboot(infp); X if (err == 1 && outfd >= 0 && X write(outfd, (char *) &oldsa, sizeof(oldsa)) < 0) X fatal("unable to write to output file"); X continue; X } X if (err == 0 || check_time(currsa.ts)) { X if (repeat) { X /* if we are sampling, then go through all reports */ X for (i = 0; i < END(reports); i++) { X if (reports[i].doit) { X if (rptcnt == 1 && err == 0) X putchar('\n'); X (*reports[i].func) (err != 1); X } X } X if (rptcnt > 1) X putchar('\n'); X } else { X if (err == 0) X putchar('\n'); X (*reports[i].func) (err != 1); X } X first = 0; X } X oldsa = currsa; X } X if (repeat == 0) X (void) fseek(infp, 0L, 0); X } X } while (!repeat && ++i < END(reports)); X if (outfd >= 0 && close(outfd) < 0) X fatal("unable to close output file"); X if (repeat) X (void) pclose(infp); X else X (void) fclose(infp); X} X X X/* X * reboot - handle system resets X * X * System reset records are marked by a system time of 0. They are written by X * sadc under certain conditions. When a system reset is noted, a new 'epoch' X * record is written to the file so that they system can use that as a X * starting place for statistics since the reboot. X */ X Xint Xreboot(fp) X FILE *fp; /* input file pointer */ X{ X int err; X struct tm *mytime; X X err = fread((char *) &oldsa, sizeof(struct sa), 1, fp); X if (err == 1) { X mytime = localtime(&oldsa.ts); X (void) printf("%.2d:%.2d:%.2d ***System Reset***\n", X mytime->tm_hour, mytime->tm_min, mytime->tm_sec); X } X return (err); X} X X X/* X * fatal - print out the error message in str, along with the program name, X * to the user and abort X */ X Xvoid Xfatal(str) X char *str; /* text of error message to print */ X{ X char buff[BUFSIZ]; X X sprintf(buff, "%s: %s", myname, str); X perror(buff); X exit(1); X} X X X/* X * usage - print out a usage message to the user and abort X */ X Xvoid Xusage() X{ X fprintf(stderr, "Usage: %s [-ubdycwaqvmpA] [-o file] t [n]\n", X myname); X fprintf(stderr, " %s [-ubdycwaqvmpA] [-s time] [-e time] [-i sec] [-f file]\n", myname); X fputs("\ntime takes the form hh[:mm[:ss]]\n", stderr); X exit(1); X} X X X/* X * get_time - get a user supplied to and convert it to seconds X * X * This routine will get a time which the user typed in in the form X * hh[:mm[:ss]] and convert it to the curresponding number of seconds X * so that it may be used to compare times easily. X */ X Xtime_t Xget_time(str) X char *str; /* time string to dissect */ X{ X int i; X time_t hour, minute, second; X X hour = 0; X minute = 0; X second = 0; X i = sscanf(str, "%d:%d:%d", &hour, &minute, &second); X if (i < 1 || hour < 0 || hour > 23 || minute < 0 X || minute > 59 || second < 0 || second > 59) X usage(); X return (hour * 3600 + minute * 60 + second); X} X X X/* X * check_time - check time of current item to see if it should be displayed. X * X * This routine checks to see if the current item falls within bounds to print X * or not. The bounds are determined by the start time, the stop time and the X * interval as input by the user on the command line. X */ X Xint Xcheck_time(ct) X time_t ct; /* current item time */ X{ X struct tm *mytime; /* temporary time buffer */ X time_t tmp; X X mytime = localtime(&ct); X tmp = mytime->tm_hour * 3600 + mytime->tm_min * 60 + mytime->tm_sec; X if (tmp >= start_tm && tmp <= end_tm) { X start_tm += interval; X return (1); X } X return (0); X} X X X/* X * All of the following routines are used tp print out the statistics reports. X * In order to make coding of these functions easier, you will note that they X * all have the same structure, using basically the same variables although of X * different types as needed. Not all of the functions are as efficient as X * possible (i.e.: a number of fucntions use flowating point, when similar X * results could be obtained using integer-only math, but they would not be X * quite as accurate) due to this, but it does make it easier for these X * functions to be developed and maintained. X */ X X X/* X * print_cpu - print out cpu usage statistics X * X * The following statistics are given: X * X * %usr Amount of time system spent in user mode X * %sys Amount of time system spent in system (kernel) mode X * %wio Amount of time system spent waiting for i/o to complete X * %idle Amount of time system spent idle. X * average averages of statistics for all time quantums sampled X * X * All of these are given as percentages for the sample time quantum. Idle X * time is computed, not read from the system. Due to rounding errors, X * totalling all of these statistics may not always give 100. X */ X Xvoid Xprint_cpu(last) X int last; /* 1 of last quantum */ X{ X float mu[4]; /* usage counters for each quantum */ X static float au[4]; /* average usage counters */ X time_t total; /* total of mu[0-3] */ X static int count = 0; /* number of quantums sampled */ X struct tm *mytime; /* used to get quantum times */ X int i; X X mytime = localtime(&currsa.ts); X if (count == 0) { X printf("%.2d:%.2d:%.2d %%usr %%sys %%wio %%idle\n", X mytime->tm_hour, mytime->tm_min, mytime->tm_sec); X } else if (last) { X au[CPU_USER] /= count; X au[CPU_KERNAL] /= count; X au[CPU_WAIT] /= count; X printf("Average %7.0f %7.0f %7.0f %7.0f\n", X au[CPU_USER], au[CPU_KERNAL], au[CPU_WAIT], X ceil(100 - au[CPU_USER] - au[CPU_KERNAL] - au[CPU_WAIT])); X } else { X for (i = CPU_IDLE; i <= CPU_WAIT; i++) X mu[i] = currsa.si.cpu[i] - oldsa.si.cpu[i]; X total = mu[CPU_IDLE] + mu[CPU_USER] + mu[CPU_KERNAL] + mu[CPU_WAIT]; X for (i = CPU_USER; i <= CPU_WAIT; i++) { X mu[i] = (mu[i] * 100) / total; X au[i] += mu[i]; X } X printf("%.2d:%.2d:%.2d %7.0f %7.0f %7.0f %7.0f\n", X mytime->tm_hour, mytime->tm_min, mytime->tm_sec, X mu[CPU_USER], mu[CPU_KERNAL], mu[CPU_WAIT], X ceil(100 - mu[CPU_USER] - mu[CPU_KERNAL] - mu[CPU_WAIT])); X } X count++; X} X X/* X * print_disk - print disk statistics X */ X X/* ARGSUSED */ Xvoid Xprint_disk(last) X int last; X{ X X /* X * it appears as if the information which is required to display the disk X * statistics has not been linked into this kernel. I have looked in the X * include files and have found a reference to a structure which would X * contain these values, but it is conditionally included into one of the X * system headers. Attempting to find the variable (gdstat) in the X * kernel namespace does not turn up anything, so I assume that it was X * left out. X */ X} X X X/* X * print_swap - print swapping statistics X * X * The following statistics are given: X * X * spwin/s number of swap-ins per second X * bswin/s number of 512 byte blocks swapped in per second X * swpot/s number of swap-outs per second X * bswot/s number of 512 byte blocks swapped out per second X * pswch/s number of process switches per second X * average averages of statistics for all time quantums sampled X * X * The averages are computed for all time quantums sampled. X */ X Xvoid Xprint_swap(last) X int last; X{ X float mu[5]; X static float au[5]; X static int count = 0; X time_t timer; X struct tm *mytime; X int i; X X#define SWPIN 0 X#define BSWIN 1 X#define SWPOT 2 X#define BSWOT 3 X#define PSWCH 4 X X mytime = localtime(&currsa.ts); X if (count == 0) { X printf("%.2d:%.2d:%.2d spwin/s bswin/s swpot/s bswot/s pswch/s\n", X mytime->tm_hour, mytime->tm_min, mytime->tm_sec); X } else if (last) { X printf("Average %7.2f %7.1f %7.2f %7.1f %7.0f\n", X au[SWPIN] / count, au[BSWIN] / (count * BUFSIZ), X au[SWPOT] / count, au[BSWOT] / (count * BUFSIZ), X au[PSWCH] / count); X } else { X timer = currsa.ts - oldsa.ts; X mu[SWPIN] = (currsa.si.swapin - oldsa.si.swapin) / timer; X mu[SWPOT] = (currsa.si.swapout - oldsa.si.swapout) / timer; X mu[BSWIN] = (currsa.si.bswapin - oldsa.si.bswapin) / timer; X mu[BSWOT] = (currsa.si.bswapout - oldsa.si.bswapout) / timer; X mu[PSWCH] = (currsa.si.pswitch - oldsa.si.pswitch) / timer; X for (i = SWPIN; i <= PSWCH; i++) X au[i] += mu[i]; X printf("%.2d:%.2d:%.2d %7.2f %7.1f %7.2f %7.1f %7.0f\n", X mytime->tm_hour, mytime->tm_min, mytime->tm_sec, X mu[SWPIN], mu[BSWIN] / BUFSIZ, mu[SWPOT], X mu[BSWOT] / BUFSIZ, mu[PSWCH]); X } X count++; X} X X X/* X * print_buffer - print buffer usage statistics X * X * The following statistics are given: X * X * bread/s # of 512 byte blocks read from disk per second X * lread/s # of 512 byte blocks logically read per second X * %rcache percentage of blocks read from buffer cache X * bwrit/s # of 512 byte blocks written to disk per second X * lwrit/s # of 512 byte blocks logically written per second X * %wcache percentage of blocks written to buffer cache X * pread/s number of 512 byte blocks physically read per second X * pwrit/s number of 512 byte blocks physically written per second X * average averages of statistics for all time quantums sampled X * X * The lread and lwrit are logical reads or writes. These will be done to the X * buffer cache if the block is in the buffer cache. If the block is not in X * the buffer cache, then the system will go ahead and do the i/o from the X * disk. If the cache hits are not high enough (lread: ~90-100%, X * lwrite: ~70-100%) then the size of the buffer cache should be increased. X * X * The pread and pwrit statistics show the number of blocks that were written X * directly to the disk (the buffer cache was not used). These would occur if X * a program uses non-buffered i/o (e.g.: open, read, write, close using X * O_NDELAY). For optimal system performance, these should be kept to a X * minimum. X */ X Xvoid Xprint_buffer(last) X int last; X{ X float mu[6]; X static float au[6]; X static int count = 0; X time_t timer; X struct tm *mytime; X int i; X X#define BREAD 0 X#define LREAD 1 X#define BWRIT 2 X#define LWRIT 3 X#define PREAD 4 X#define PWRIT 5 X X mytime = localtime(&currsa.ts); X if (count == 0) { X printf("%.2d:%.2d:%.2d bread/s lread/s %%rcache bwrit/s lwrit/s %%wcache pread/s pwrit/s\n", X mytime->tm_hour, mytime->tm_min, mytime->tm_sec); X } else if (last) { X printf("Average %7.0f %7.0f %7.0f %7.0f %7.0f %7.0f %7.0f %7.0f\n", X au[BREAD] / count, au[LREAD] / count, X au[LREAD] == 0 ? 0 : (1.0 - (au[BREAD] / au[LREAD])) * 100, X au[BWRIT] / count, au[LWRIT] / count, X au[LWRIT] == 0 ? 0 : (1.0 - (au[BWRIT] / au[LWRIT])) * 100, X au[PREAD] / count, au[PWRIT] / count); X } else { X timer = currsa.ts - oldsa.ts; X mu[BREAD] = (currsa.si.bread - oldsa.si.bread) / timer; X mu[LREAD] = (currsa.si.lread - oldsa.si.lread) / timer; X mu[BWRIT] = (currsa.si.bwrite - oldsa.si.bwrite) / timer; X mu[LWRIT] = (currsa.si.lwrite - oldsa.si.lwrite) / timer; X mu[PREAD] = (currsa.si.phread - oldsa.si.phread) / timer; X mu[PWRIT] = (currsa.si.phwrite - oldsa.si.phwrite) / timer; X for (i = BREAD; i <= PWRIT; i++) X au[i] += mu[i]; X printf("%.2d:%.2d:%.2d %7.0f %7.0f %7.0f %7.0f %7.0f %7.0f %7.0f %7.0f\n", X mytime->tm_hour, mytime->tm_min, mytime->tm_sec, X mu[BREAD], mu[LREAD], X mu[LREAD] == 0 ? 0 : (1.0 - (mu[BREAD] / mu[LREAD])) * 100, X mu[BWRIT], mu[LWRIT], X mu[LWRIT] == 0 ? 0 : (1.0 - (mu[BWRIT] / mu[LWRIT])) * 100, X mu[PREAD], mu[PWRIT]); X } X count++; X} X X X/* X * print_tty - print tty statistics X * X * The following statistics are given: X * X * rawch/s # of raw characters (non-canonical) processed per second X * canch/s # of canonical characters processed per second X * outch/s # of characters output by system per second X * rcvin/s # of received character interrupts per second X * xmtin/s # of output character interrupts per second X * mdmin/s # of modem (tty) interrupts per second X * average averages of statistics for all time quantums sampled X * X * Generally, the mdmin/s number should be 0. Numbers higher than one tend to X * indicate modem trouble (if there is a modem attached to that port) or X * terminal trouble. X */ X Xvoid Xprint_tty(last) X int last; X{ X float mu[6]; X static float au[6]; X static int count = 0; X time_t timer; X struct tm *mytime; X int i; X X#define RAWCH 0 X#define CANCH 1 X#define OUTCH 2 X#define RCVIN 3 X#define XMTIN 4 X#define MDMIN 5 X X mytime = localtime(&currsa.ts); X if (count == 0) { X printf("%.2d:%.2d:%.2d rawch/s canch/s outch/s rcvin/s xmtin/s mdmin/s\n", X mytime->tm_hour, mytime->tm_min, mytime->tm_sec); X } else if (last) { X printf("Average %7.0f %7.0f %7.0f %7.0f %7.0f %7.0f\n", X mu[RAWCH] / count, mu[CANCH] / count, mu[OUTCH] / count, X mu[RCVIN] / count, mu[XMTIN] / count, mu[MDMIN] / count); X } else { X timer = currsa.ts - oldsa.ts; X mu[RAWCH] = (currsa.si.rawch - oldsa.si.rawch) / timer; X mu[CANCH] = (currsa.si.canch - oldsa.si.canch) / timer; X mu[OUTCH] = (currsa.si.outch - oldsa.si.outch) / timer; X mu[RCVIN] = (currsa.si.rcvint - oldsa.si.rcvint) / timer; X mu[XMTIN] = (currsa.si.xmtint - oldsa.si.xmtint) / timer; X mu[MDMIN] = (currsa.si.mdmint - oldsa.si.mdmint) / timer; X for (i = RAWCH; i <= MDMIN; i++) X au[i] += mu[i]; X printf("%.2d:%.2d:%.2d %7.0f %7.0f %7.0f %7.0f %7.0f %7.0f\n", X mytime->tm_hour, mytime->tm_min, mytime->tm_sec, X mu[RAWCH], mu[CANCH], mu[OUTCH], X mu[RCVIN], mu[XMTIN], mu[MDMIN]); X } X count++; X} X X X/* X * print_syscall - print system call statistics X * X * The following statistics are given: X * X * scall/s number of system calls per second X * sread/s number of system read calls per second X * swrit/s number of system write calls per second X * fork/s number of user fork calls per second X * exec/s number of user exec calls per second X * rchar/s number of characters read by system per second X * wchar/s number of characters written by system per second X * average averages of statistics for all time quantums sampled X * X * Only user generated fork and exec calls are logged. For example, exec's X * done by the shell are not included in these statistics, from what I can X * tell. Uucico can run up the rchar and wchar statistics in a big hurry. X */ X Xvoid Xprint_syscall(last) X int last; X{ X float mu[7]; X static float au[7]; X static int count = 0; X time_t timer; X struct tm *mytime; X int i; X X#define SCALL 0 X#define SREAD 1 X#define SWRIT 2 X#define FORK 3 X#define EXEC 4 X#define RCHAR 5 X#define WCHAR 6 X X mytime = localtime(&currsa.ts); X if (count == 0) { X printf("%.2d:%.2d:%.2d scall/s sread/s swrit/s fork/s exec/s rchar/s wchar/s\n", X mytime->tm_hour, mytime->tm_min, mytime->tm_sec); X } else if (last) { X printf("Average %7.0f %7.0f %7.0f %6.2f %6.2f %7.0f %7.0f\n", X au[SCALL] / count, au[SREAD] / count, X au[SWRIT] / count, au[FORK] / count, X au[EXEC] / count, au[RCHAR] / count, X au[WCHAR] / count); X } else { X timer = currsa.ts - oldsa.ts; X mu[SCALL] = (currsa.si.syscall - oldsa.si.syscall) / timer; X mu[SREAD] = (currsa.si.sysread - oldsa.si.sysread) / timer; X mu[SWRIT] = (currsa.si.syswrite - oldsa.si.syswrite) / timer; X mu[FORK] = (currsa.si.sysfork - oldsa.si.sysfork) / timer; X mu[EXEC] = (currsa.si.sysexec - oldsa.si.sysexec) / timer; X mu[RCHAR] = (currsa.si.readch - oldsa.si.readch) / timer; X mu[WCHAR] = (currsa.si.writech - oldsa.si.writech) / timer; X for (i = SCALL; i <= WCHAR; i++) X au[i] += mu[i]; X printf("%.2d:%.2d:%.2d %7.0f %7.0f %7.0f %6.2f %6.2f %7.0f %7.0f\n", X mytime->tm_hour, mytime->tm_min, mytime->tm_sec, X mu[SCALL], mu[SREAD], mu[SWRIT], mu[FORK], X mu[EXEC], mu[RCHAR], mu[WCHAR]); X } X count++; X} X X X/* X * print_file - print file access statistics X * X * The following statistics are given: X * X * iget/s number of iget calls per second X * namei/s number of namei calls per second X * dirbk number of dirbk calls per second X * average averages of statistics for all time quantums sampled X * X * These statistics represent the number of times the kernel routines iget(), X * namei() and dirbk() are called. The biggest hog of these statistics are X * large find(1) commands. X */ X Xvoid Xprint_file(last) X int last; X{ X float mu[3]; X static float au[3]; X static int count = 0; X time_t timer; X struct tm *mytime; X int i; X X#define IGET 0 X#define NAMEI 1 X#define DIRBK 2 X X mytime = localtime(&currsa.ts); X if (count == 0) { X printf("%.2d:%.2d:%.2d iget/s namei/s dirbk/s\n", X mytime->tm_hour, mytime->tm_min, mytime->tm_sec); X } else if (last) { X printf("Average %6.0f %7.0f %7.0f\n", X au[IGET] / count, au[NAMEI] / count, au[DIRBK] / count); X } else { X timer = currsa.ts - oldsa.ts; X mu[IGET] = (currsa.si.iget - oldsa.si.iget) / timer; X mu[NAMEI] = (currsa.si.namei - oldsa.si.namei) / timer; X mu[DIRBK] = (currsa.si.dirblk - oldsa.si.dirblk) / timer; X for (i = IGET; i <= DIRBK; i++) X au[i] += mu[i]; X printf("%.2d:%.2d:%.2d %6.0f %7.0f %7.0f\n", X mytime->tm_hour, mytime->tm_min, mytime->tm_sec, X mu[IGET], mu[NAMEI], mu[DIRBK]); X } X count++; X} X X X/* X * print_queues - print system queue statistics X * X * The following statistics are given: X * X * runq-sz run queue size (number of procs waiting to run) X * %runocc percentage of time run queue is occupied X * swpq-sz swap queue size (number of procs waiting to be swapped) X * %swpocc percentage of time swap queue is occupied X * average averages of statistics for all time quantums sampled X * X */ X Xvoid Xprint_queues(last) X int last; X{ X float mu[4]; X static float au[4]; X static int count = 0; X time_t timer; X struct tm *mytime; X int i; X X#define RUNSZ 0 X#define RUNOCC 1 X#define SWPSZ 2 X#define SWPOCC 3 X X mytime = localtime(&currsa.ts); X if (count == 0) { X printf("%.2d:%.2d:%.2d runq-sz %%runocc swpq-sz %%swpocc\n", X mytime->tm_hour, mytime->tm_min, mytime->tm_sec); X } else if (last) { X printf("Average %7.1f %7.0f %7.1f %7.0f\n", X au[RUNSZ] / count, au[RUNOCC] / count, X au[SWPSZ] / count, au[SWPOCC] / count); X } else { X timer = currsa.ts - oldsa.ts; X mu[RUNSZ] = (currsa.si.runque - oldsa.si.runque) / timer; X mu[RUNOCC] = (currsa.si.runocc - oldsa.si.runocc) / timer; X mu[SWPSZ] = (currsa.si.swpque - oldsa.si.swpque) / timer; X mu[SWPOCC] = (currsa.si.swpocc - oldsa.si.swpocc) / timer; X for (i = IGET; i <= DIRBK; i++) X au[i] += mu[i]; X printf("%.2d:%.2d:%.2d %7.1f %7.0f %7.1f %7.0f\n", X mytime->tm_hour, mytime->tm_min, mytime->tm_sec, X mu[RUNSZ], mu[RUNOCC], mu[SWPSZ], mu[SWPOCC]); X } X count++; X} X X X/* X * print_status - print system table status X * X * The following statistics are given: X * X * text-sz number of entries in text table / max size of text table X * proc-sz number of entries in proc table / max size of proc table X * inod-sz number of entries in inode table / max size of inode table X * file-sz number of entries in file table / max size of file table X * text-ov number of times text table has overflowed this quantum X * proc-ov number of times proc table has overflowed this quantum X * inod-ov number of times inode table has overflowed this quantum X * file-ov number of times file table has overflowed this quantum X * X * The size of the text, proc, inode and file tables can be configured by X * using ktune(7). Ktune is a nifty utility, but one does not know that the X * kernel is in need of reconfiguring unless one has the information produced X * by sar, which does not come with the 3b1. Hmmm. X * X * If there are overflows in any of the table, it may be time to up the size X * of the table. This should not happen at most sites: the defaults are X * generous. X */ X Xvoid Xprint_status(last) X int last; X{ X struct tm *mytime; X static int count = 0; X X mytime = localtime(&currsa.ts); X if (count == 0) { X printf("%.2d:%.2d:%.2d text-sz proc-sz inod-sz file-sz text-ov proc-ov inod-ov file-ov\n", X mytime->tm_hour, mytime->tm_min, mytime->tm_sec); X } else if (!last) { X printf("%.2d:%.2d:%.2d %3d/%3d %3d/%3d %3d/%3d %3d/%3d %7d %7d %7d %7d\n", X mytime->tm_hour, mytime->tm_min, mytime->tm_sec, X currsa.sztext, currsa.msztext, X currsa.szproc, currsa.mszproc, X currsa.szinode, currsa.mszinode, X currsa.szfile, currsa.mszfile, X currsa.textovf - oldsa.textovf, X currsa.procovf - oldsa.procovf, X currsa.inodeovf - oldsa.inodeovf, X currsa.fileovf - oldsa.fileovf); X } X count++; X} X X X/* X * print_message - print interprocess communications statistics X * X * The following statistics are given: X * X * msg/s number of message transactions per second X * sema/s number of semaphore transactions per second X * average averages of statistics for all time quantums sampled X * X * These statistics will always be zero unless you have an application using X * the message and semaphone interprocess communications facilities. Even X * then, on a system as small as the 3b1, these are most likely to be zero, X * unless heavy activity is occurring. X */ X Xvoid Xprint_message(last) X int last; X{ X float mu[2]; X static float au[2]; X static int count = 0; X time_t timer; X struct tm *mytime; X int i; X X#define MSG 0 X#define SEMA 1 X X mytime = localtime(&currsa.ts); X if (count == 0) { X printf("%.2d:%.2d:%.2d msg/s sema/s\n", X mytime->tm_hour, mytime->tm_min, mytime->tm_sec); X } else if (last) { X printf("Average %5.2f %6.2f\n", X au[MSG] / count, au[SEMA] / count); X } else { X timer = currsa.ts - oldsa.ts; X mu[MSG] = (currsa.si.msg - oldsa.si.msg) / timer; X mu[SEMA] = (currsa.si.sema - oldsa.si.sema) / timer; X for (i = MSG; i <= SEMA; i++) X au[i] += mu[i]; X printf("%.2d:%.2d:%.2d %5.2f %6.2f\n", X mytime->tm_hour, mytime->tm_min, mytime->tm_sec, X mu[MSG], mu[SEMA]); X } X count++; X} X X X/* X * print_paging - print paging statistics X * X * The following statistics are given: X * X * pagins number of demand pagins occurred X * pagouts number of demand pageouts occurred X * average averages of statistics for all time quantums sampled X * X * These statistics may be better given as pagins/s and pagouts/s, except that X * the numbers are so low as to be meaningless (i.e.: they usually came up as X * zero). Possibly pagins/m and pagouts/m (using minutes rather than X * seconds). I have never seen a sar with these reports so I am not sure X * execatly how these are usually reported. X */ X Xvoid Xprint_paging(last) X int last; X{ X long mu[2]; X static long au[2]; X static int count = 0; X struct tm *mytime; X int i; X X#define PGIN 0 X#define PGOUT 1 X X mytime = localtime(&currsa.ts); X if (count == 0) { X printf("%.2d:%.2d:%.2d pagins pagouts\n", X mytime->tm_hour, mytime->tm_min, mytime->tm_sec); X } else if (last) { X printf("Average %6d %7d\n", au[PGIN] / count, au[PGOUT] / count); X } else { X mu[PGIN] = (currsa.si.pgin - oldsa.si.pgin); X mu[PGOUT] = (currsa.si.pgout - oldsa.si.pgout); X for (i = PGIN; i <= PGOUT; i++) X au[i] += mu[i]; X printf("%.2d:%.2d:%.2d %6d %7d\n", X mytime->tm_hour, mytime->tm_min, mytime->tm_sec, X mu[PGIN], mu[PGOUT]); X } X count++; X} END_OF_sar.c if test 31357 -ne `wc -c <sar.c`; then echo shar: \"sar.c\" unpacked with wrong size! fi # end of overwriting check fi if test -f sadc.c -a "${1}" != "-c" ; then echo shar: Will not over-write existing file \"sadc.c\" else echo shar: Extracting \"sadc.c\" \(8040 characters\) sed "s/^X//" >sadc.c <<'END_OF_sadc.c' X/* X * sadc - System Activity Data Collector X * X * sadc is responsible for collecting the System Activity data from the kernel X * and writing it out to a file. If it has no arguments, then it writes out a X * special reboot block. X * X * Returns 0 to caller if succeded, 1 if failed X * X * Author: Mark H. Colburn (mark@jhereg.mn.org) X */ X X#include <stdio.h> X#include <time.h> X#include <nlist.h> X#include <fcntl.h> X#include <ctype.h> X#include <sys/sysinfo.h> X#include <sys/var.h> X#include <sys/syslocal.h> X#include <sys/file.h> X#include <sys/text.h> X#include <sys/inode.h> X#include "sar.h" X X#ifndef lint Xstatic char *SccsID = "@(#)sadc.c 1.8 3/16/88 Copyright Mark H. Colburn"; X#endif X X/* X * this array will contain that addresses of the various kernel data X * structures that we are interested in looking at. X */ Xstruct nlist sysadr[] = { X {"sysinfo", 0, 0, 0, 0, 0}, X {"syserr", 0, 0, 0, 0, 0}, X {"text", 0, 0, 0, 0, 0}, X {"file", 0, 0, 0, 0, 0}, X {"inode", 0, 0, 0, 0, 0}, X {"proc", 0, 0, 0, 0, 0}, X {"ffreelist", 0, 0, 0, 0, 0}, X {"ifreelist", 0, 0, 0, 0, 0}, X {"", 0, 0, 0, 0, 0} X}; X X#define infoadr (sysadr[0].n_value) X#define erradr (sysadr[1].n_value) X#define textadr (sysadr[2].n_value) X#define fileadr (sysadr[3].n_value) X#define inodadr (sysadr[4].n_value) X#define procadr (sysadr[5].n_value) X#define ffreeadr (sysadr[6].n_value) X#define ifreeadr (sysadr[7].n_value) X Xint kmem; /* file handle for /dev/kmem */ Xchar *myname; /* name of this program (argv[0]) */ X Xvoid kmemread(); Xvoid get_sa_info(); Xvoid usage(); Xvoid fatal(); Xvoid exit(); Xvoid perror(); Xtime_t time(); Xunsigned sleep(); Xlong lseek(); Xchar *memset(); Xextern int errno; X X X/* X * main - main processing program X */ X Xint Xmain(argc, argv) X int argc; X char *argv[]; X{ X int delay; /* seconds to delay between collections */ X int reps; /* number of times to repeat collections */ X int outfd; /* file descriptor to write write data to */ X int zerorec; /* 1 if should write out reboot, 0 if data */ X char *fname; /* filename of output file */ X void init(); /* initialization function */ X X delay = 0; X reps = 1; X zerorec = 1; X fname = (char *) NULL; X X myname = argv[0]; X argc--; X argv++; X if (argc >= 2) { X if (isdigit((*argv)[0]) && isdigit((*(argv + 1))[0])) { X delay = atoi(*argv++); X reps = atoi(*argv++); X argc -= 2; X zerorec = 0; X } else X usage(); X } X if (argc == 1) { X fname = *argv; X } else if (argc > 1) { X usage(); X } else { X outfd = 1; /* standard output */ X } X X init(); X do { X if (fname && X (outfd = open(fname, O_WRONLY | O_CREAT | O_APPEND, 0644)) == -1) { X fprintf(stderr, "Unable to open output file %s (%d)\n", X fname, errno); X exit(1); X } X get_sa_info(outfd, zerorec); X if (outfd != 1) X (void) close(outfd); X if (reps > 1 && delay > 0) X (void) sleep((unsigned) delay); X } while (--reps); X (void) close(kmem); X exit(0); X} X X X/* X * init - handle miscellaneous initializations X */ X Xvoid Xinit() X{ X /* open the kernel memory */ X if ((kmem = open("/dev/kmem", O_RDONLY)) == -1) X fatal("Can't open /dev/kmem"); X X /* get addresses of various kernel structures from /unix */ X if (nlist("/unix", sysadr) == -1) X fatal("Unable perform nlist on /unix"); X X /* get the "real" address for each of the tables */ X kmemread(inodadr, (char *) &inodadr, sizeof(inodadr)); X kmemread(ifreeadr, (char *) &ifreeadr, sizeof(ifreeadr)); X kmemread(textadr, (char *) &textadr, sizeof(textadr)); X kmemread(fileadr, (char *) &fileadr, sizeof(fileadr)); X kmemread(procadr, (char *) &procadr, sizeof(procadr)); X kmemread(ffreeadr, (char *) &ffreeadr, sizeof(ffreeadr)); X X /* turn of the setgid now that /dev/kmem is open */ X setgid(getgid()); X} X X X/* X * get_sa_info - get system activity counters from kernel X */ X Xvoid Xget_sa_info(outfd, zerorec) X int outfd; /* file descriptor to write output to */ X int zerorec; /* not 0 if should zero record */ X{ X long vaddr; /* address of kernel var structure */ X struct var v; /* kernel var structure */ X struct syserr saerr; /* system error counts */ X struct sa sadata; /* system activity data */ X X /* X * if we are just rebooting, then write out a zero record so that our X * place will be marked in the file. The next record will be taken as X * the zero base for the new accounting. X */ X X (void) memset((char *) &sadata, 0, sizeof(sadata)); X X if (zerorec && write(outfd, (char *) &sadata, sizeof(sadata)) == -1) X fatal("Unable to write output file"); X X /* X * read all of the information that we need for statistics out of the X * kernel space. For some of these items, it means that we will have to X * read the data twice, since they are only pointers to the real items. X */ X X vaddr = (long) syslocal(SYSL_KADDR, SLA_V); X kmemread(vaddr, (char *) &v, (long) sizeof(v)); X kmemread(infoadr, (char *) &sadata.si, sizeof(sadata.si)); X kmemread(erradr, (char *) &saerr, sizeof(saerr)); X X /* copy the max table sizes from the system var record... */ X sadata.mszinode = v.v_inode; X sadata.mszfile = v.v_file; X sadata.msztext = v.v_text; X sadata.mszproc = v.v_proc; X X /* compute the current table sizes */ X sadata.szinode = (ifreeadr - inodadr) / sizeof(struct inode); X sadata.szfile = (ffreeadr - fileadr) / sizeof(struct file); X sadata.szproc = ((long) v.ve_proc - procadr) / sizeof(struct proc); X sadata.sztext = cnt_txt(textadr, X (long) (((long) v.ve_text - textadr) / sizeof(struct text))); X X /* copy the overflow statistics from the syserr record... */ X sadata.inodeovf = saerr.inodeovf; X sadata.fileovf = saerr.fileovf; X sadata.textovf = saerr.textovf; X sadata.procovf = saerr.procovf; X X /* get the current time in the timestamp */ X sadata.ts = time((int *) 0); X X if (write(outfd, (char *) &sadata, sizeof(sadata)) == -1) X fatal("Unable to write output file\n"); X} X X X/* X * cnt_txt - count the number of active text table entries X * X * This is kind of a hack, but I could find no other way to get the size of X * the text table, therefore, we will read through the text table incrementing X * a counter each time we find an entry that does not have a size of zero. X * This is time consuming, since we have to read /dev/kmem for each text table X * entry. Oh, well... X */ X Xint Xcnt_txt(txtadr, txtcnt) X long txtadr; /* address of text table */ X long txtcnt; /* max number of text table entries */ X{ X register int i; /* loop counter */ X struct text text; /* text table entry for reads */ X long count; /* count of 'used' table entries */ X X count = 0; X for (i = 0; i < txtcnt; ++i) { X kmemread((long) &((struct text *) txtadr)[i], X (char *) &text, sizeof(text)); X if (text.x_size != 0) X count++; X } X return (count); X} X X X/* X * usage - print out a usage message to the user and abort X */ X Xvoid Xusage() X{ X fprintf(stderr, "Usage: %s [t n] [ofile]\n", myname); X exit(1); X} X X X/* X * fatal - print out the error message in str, along with the program name, X * to the user and abort X */ X Xvoid Xfatal(str) X char *str; /* text of error message to print */ X{ X char buff[BUFSIZ]; X X sprintf(buff, "%s: %s", myname, str); X perror(buff); X exit(1); X} X X X/* X * kmemread - read nbytes of data from /dev/kmem into dptr X */ X Xvoid Xkmemread(kmadr, dptr, nbytes) X long kmadr; /* kernel address to read data from */ X char *dptr; /* pointer to place to save data */ X unsigned nbytes; /* number of bytes to read */ X{ X if (lseek(kmem, kmadr, 0) < 0L || read(kmem, dptr, nbytes) != nbytes) X fatal("can't read /dev/kmem"); X} END_OF_sadc.c if test 8040 -ne `wc -c <sadc.c`; then echo shar: \"sadc.c\" unpacked with wrong size! fi # end of overwriting check fi if test -f sar.h -a "${1}" != "-c" ; then echo shar: Will not over-write existing file \"sar.h\" else echo shar: Extracting \"sar.h\" \(1425 characters\) sed "s/^X//" >sar.h <<'END_OF_sar.h' X/* X * sar.h - defintions for System Activity package X * X * Author: Mark H. Colburn (mark@jhereg.mn.org) X * X * @(#)sar.h 1.2 88/03/16 Copyright Mark H. Colburn X */ X X#include <sys/gdisk.h> X X/* X * Used to access elements of the devio array in the sa structure X */ X X#define IO_OPS 1 X#define IO_BCNT 1 X#define IO_ACT 2 X#define IO_RESP 3 X X/* X * the actual system account record that is written out by sadc. This X * record format is not compatible with other versions of sar. X */ X Xstruct sa { X struct sysinfo si; /* see /usr/include/sys/sysinfo.h */ X int szinode; /* # of entries in inode table */ X int szfile; /* # of entries in file table */ X int sztext; /* # of entries in text table */ X int szproc; /* # of entries in proc table */ X int mszinode; /* size of inode table */ X int mszfile; /* size of file table */ X int msztext; /* size of text table */ X int mszproc; /* size of proc table */ X long inodeovf; /* number of inode table overflows */ X long fileovf; /* number of file table overflows */ X long textovf; /* number of text table overflows */ X long procovf; /* number of proc table overflows */ X time_t ts; /* time stamp, in seconds */ X long devio[DISKS][4]; /* device info for DISKS disks */ X}; END_OF_sar.h if test 1425 -ne `wc -c <sar.h`; then echo shar: \"sar.h\" unpacked with wrong size! fi # end of overwriting check fi if test -f sa1.SH -a "${1}" != "-c" ; then echo shar: Will not over-write existing file \"sa1.SH\" else echo shar: Extracting \"sa1.SH\" \(396 characters\) sed "s/^X//" >sa1.SH <<'END_OF_sa1.SH' X# X# sa1 - shell script to drive sadc(1m) X# X# Usage: /usr/lib/sa/sa1 [t n] X# X# Author: Mark H. Colburn (mark@jhereg.mn.org) X# X# @(#)sa1.SH 1.3 3/16/88 Copyright Mark H. Colburn X# X Xif [ $# = 2 ] Xthen X delay=$1 X count=$2 X while [ $count -gt 0 ] X do X /usr/lib/sa/sadc 0 1 /usr/adm/sa/sa`date +%d` X sleep $delay X count=`expr $count - 1` X done Xelse X /usr/lib/sa/sadc 0 1 /usr/adm/sa/sa`date +%d` Xfi END_OF_sa1.SH if test 396 -ne `wc -c <sa1.SH`; then echo shar: \"sa1.SH\" unpacked with wrong size! fi # end of overwriting check fi if test -f sa2.SH -a "${1}" != "-c" ; then echo shar: Will not over-write existing file \"sa2.SH\" else echo shar: Extracting \"sa2.SH\" \(270 characters\) sed "s/^X//" >sa2.SH <<'END_OF_sa2.SH' X# X# sa2 - shell script to print daily sar reports X# X# Usage: /usr/lib/sa/sa2 X# X# Author: Mark H. Colburn (mark@jhereg.mn.org) X# X# @(#)sa2.SH 1.3 4/24/88 Copyright Mark H. Colburn X# X%BINDIR%/sar $* > /usr/adm/sa/sar`date +%d` Xfind /usr/adm/sa -mtime +7 -exec rm -f {} \; END_OF_sa2.SH if test 270 -ne `wc -c <sa2.SH`; then echo shar: \"sa2.SH\" unpacked with wrong size! fi # end of overwriting check fi if test -f sar.1 -a "${1}" != "-c" ; then echo shar: Will not over-write existing file \"sar.1\" else echo shar: Extracting \"sar.1\" \(8838 characters\) sed "s/^X//" >sar.1 <<'END_OF_sar.1' X.TH SAR 1 X.SH NAME Xsar \- system activity reporting package X.SH SYNOPSIS X.B sar X.RB [\-ubdycwaqvmpA] X.RB [\-s " time" ] X.RB [\-e " time" ] X.RB [\-i " secs" ] X.RB [\-f " file" ] X.sp X.B sar X.RB [\-ubdycwaqvmpA] X.RB [\-o time] Xt [ n ] X.SH DESCRIPTION X.IR Sar Xis a utility to monitor system activity on an ongoing basis. XTwo different command forms are supported: one for monitoring the system Xdata in real-time and the other for monitoring system activity on a Xregularly scheduled basis. X.PP XWhen using the first form of the command, X.IR sar gets it's data from a file. XIf no file name is specified with the X.B \-f Xflag, then the default the file, is X.BI /usr/adm/sa/sa dd\^, Xwhere X.IR dd Xstands for the current day of the month. XThe X.B \-s, X.B \-e Xand X.B \-i Xflags provide a means to bound the data displayed by sar. XThe X.B \-s Xand X.B \-e Xflag specifies a starting time and ending time for a report. XThe X.B \-i Xflag specifies that only records every X.I sec Xor more seconds apart will be printed on the report. X.PP XWhen using the second form of the command, X.IR sar Xwill sample the system usage data from the kernel X.B n Xtimes, pausing X.B t Xseconds between each sample. XThe X.B \-o flag Xallows a means for the sampled data to be saved to a file for later Xreview. X.PP XAverages are given for all statistics that can be logically averaged. XAverages are computed based on the activities of all time quantums sampled. XDue to round off errors, it is possible that hand calculating the averages Xmay produce slightly different results that those which are printed by X.B sar. X.PP XDifferent reports may be displayed by supplying arguments on the command line. XIf no arguments are given, then the cpu usage report is displayed by Xdefault. XThe command line options are as follows: X.PP X.PD 0 X.TP 5 X.B \-u XReport X.SM CPU Xutilization (the default): X.sp X.ta \w'XXXXXXXX\ \ 'u X.RS X.nf X%usr time system spent in user mode X%sys time system spent in system (kernel) mode X%wio time system spent waiting for i/o to complete X%idle time system spent idle. X.DT X.fi X.sp XAll of these statistics are given as percentages for the sample time quantum. XIdle time is computed, not read from the system. XDue to rounding errors, totalling all of these statistics may not always give Xexactly 100. X.sp X.RE X.TP X.B \-b XReport system buffer activity: X.sp X.ta \w'XXXXXXXX\ \ 'u X.RS X.nf Xbread/s # of 512 byte blocks read from disk Xlread/s # of 512 byte blocks read from buffer cache X%rcache percentage of blocks read from buffer cache Xbwrit/s # of 512 byte blocks written to disk Xlwrit/s # of 512 byte blocks written to buffer cache X%wcache percentage of blocks written to buffer cache Xpread/s number of 512 byte blocks physically read Xpwrit/s number of 512 byte blocks physically written X.DT X.fi X.sp XThe X.I lread Xand X.I lwrit Xare logical reads or writes done to or from the buffer cache. XIf the requested block is not in the buffer cache, then the system will go Xahead and do the I/O to or from the disk X(the X.I bread Xand X.I bwrit Xstatistics). XIf the cache hits are not high enough (lread: ~90-100%, lwrite: ~70-100%) Xthen the size of the buffer cache should be increased. X.sp XThe X.I pread Xand X.I pwrit Xstatistics show the number of blocks that were written Xdirectly to the disk via a raw devide. XRaw I/O does not use the buffer cache XThese would occur if a data is transfered to or from the disk Xusing a raw device such as /dev/rfp??? or if disk buffering is Xdisallowed X(e.g.: open, read, write, close using O_NDELAY). XFor optimal system performance, these should be kept to a minimum. X.sp XTheoretically, these statistics are not just for disk, but for any block Xformatted devices such as tape drives, etc. XMost 3b1s do not have any block formatted devices, other than disks, Xhowever. X.sp X.RE X.TP X.B \-d XReport disk usage activity: X.sp XThis is currently not implemented on the 3b1. X.sp X.TP X.B \-y XReport TTY device activity: X.sp X.ta \w'XXXXXXXX\ \ 'u X.RS X.nf Xrawch/s input character rate Xcanch/s canonical characters processed Xoutch/s characters output by system Xrcvin/s received character interrupts Xxmtin/s output character interrupts Xmdmin/s modem (tty) interrupts X.DT X.fi X.sp XGenerally, mdmin/s should be 0. XNumbers higher than one tend to indicate modem or terminal trouble. X.sp X.RE X.TP X.B \-c XReport system calls: X.sp X.ta \w'XXXXXXXX\ \ 'u X.RS X.nf Xiget/s number of iget calls Xnamei/s number of namei calls Xdirbk number of dirbk calls X.DT X.fi X.sp XThese statistics represent the number of times the kernel routines iget(), Xnamei() and dirbk() are called. XThe biggest hog of these statistics are large find(1) commands. X.sp X.RE X.TP X.B \-w XReport system swapping and switching activity: X.sp X.ta \w'XXXXXXXX\ \ 'u X.RS X.nf Xspwin/s number of swap-ins Xbswin/s number of 512 byte blocks swapped in Xswpot/s number of swap-outs Xbswot/s number of 512 byte blocks swapped out Xpswch/s number of process switches X.DT X.fi X.sp XThese statistics represent the number of blocks transfered between the Xmain memory and the swap area (secondary memory). X.ISwpin includes the number of blocks transfered for initially loading Xprograms, as well as for swapping activity. X.sp X.RE X.TP X.B \-a XReport use of file access routines: X.sp X.ta \w'XXXXXXXX\ \ 'u X.RS X.nf Xiget/s number of iget calls Xnamei/s number of namei calls Xdirbk number of dirbk calls X.DT X.fi X.sp XThese statistics represent the number of times the kernel routines X.I iget X(get a specified inode from disk and lock it in the inode table) X.I namei X(get the inode for a specified pathname) and X.I dirblk Xare called. XThe biggest hog of these statistics are large find(1) commands. X.sp X.RE X.TP X.B \-q XReport system queue statistics: X.sp X.ta \w'XXXXXXXX\ \ 'u X.RS X.nf Xrunq-sz average run queue size X%runocc percentage of time run queue is occupied Xswpq-sz averae swap queue size X%swpocc percentage of time swap queue is occupied X.DT X.fi X.sp XThe run queue is a queue of runnable processes which are currently in Xmemory, awaiting scheduling. XThe swap queue containes a list of processes which have been swapped out of Xmain memory and are currently residing on the swap device, but are otherwise Xready to run. X.sp X.RE X.TP X.B \-v XReport status of text, process, inode and file tables: X.sp X.ta \w'XXXXXXXX\ \ 'u X.RS X.nf Xtext-sz text table size / max size of text table Xproc-sz proc table size / max size of proc table Xinod-sz inode table size / max size of inode table Xfile-sz file table size / max size of file table Xtext-ov text table has overflow count Xproc-ov proc table has overflow count Xinod-ov inode table has overflow count Xfile-ov file table has overflow count X.DT X.fi X.sp XThe size of the text, proc, indoe and file tables are the only statistics Xwhich are a 'snapshot' of the statistic at the time that X.IR sadc "(1m)" Xran, rather than an average of the statistic over the time quantum. XThe overflow counts represent the number of times the table has overflowed Xduring the time quantum. X.sp XThe maximum size of the text, proc, inode and file tables can be configured Xby using X.IR ktune "(7)." XKtune is a nifty utility, but one does not know that the Xkernel is in need of reconfiguring unless one has the information produced Xby sar, which does not come with the 3b1. X.sp XIf there are overflows in any of the table, it may be time to up the size Xof the table. X.sp X.RE X.TP X.B \-m XReport interprocess communications activities: X.sp X.ta \w'XXXXXXXX\ \ 'u X.RS X.nf Xmsg/s number of IPC message transactions Xsema/s number of IPC semaphore transactions X.DT X.fi X.sp XThese statistics will always be zero unless you have an application using Xthe message and semaphone interprocess communications facilities. Even Xthen, on a system as small as the 3b1, these are most likely to be zero, Xunless heavy activity is occurring. X.sp X.RE X.TP X.B \-p XReport pagaing statistics: X.sp X.ta \w'XXXXXXXX\ \ 'u X.RS X.nf Xpagins number of demand pagins occurred Xpagouts number of demand pageouts occurred X.DT X.fi X.sp XThese statistics may be better given as pagins/s and pagouts/s, except that Xthe numbers are so low as to be meaningless (i.e.: they usually came up as Xzero). XPossibly pagins/m and pagouts/m (using minutes rather than seconds). XI have never seen a sar with these reports so I am not sureexecatly how Xthese are usually reported. X.sp X.RE X.TP X.B \-A XDisplay all statistics. XEquivalent to X.BR \-udqbwcayvmp . X.sp X.SH BUGS X.br XCurrently disk reporting is not supported by the AT&T 3B1 kernel. X.br XThe X.B t Xand X.B n Xarguments currently must be specified as the last items on the command Xline when using the second form of the command line. X.SH AUTHOR X.br X.ta \w'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\ \ 'u X.nf XMark H. Colburn mark@jhereg.mn.org X76 Gant Circle, Apt. F ...!ihnp4!chinet!jhereg!mark XStreamwood, IL 60107 +1 312 213 2852 X.DT X.fi X.sp X.SH FILES X.RI /usr/adm/sa/sa dd X.sp X.SH SEE ALSO Xsar(1M). END_OF_sar.1 if test 8838 -ne `wc -c <sar.1`; then echo shar: \"sar.1\" unpacked with wrong size! fi # end of overwriting check fi if test -f sar.1m -a "${1}" != "-c" ; then echo shar: Will not over-write existing file \"sar.1m\" else echo shar: Extracting \"sar.1m\" \(2594 characters\) sed "s/^X//" >sar.1m <<'END_OF_sar.1m' X.TH SAR 1M X.SH NAME Xsa1, sa2, sadc \- system activity reporting package X.SH SYNOPSIS X.B /usr/lib/sa/sadc X[t n]\ [ofile] X.sp X.B /usr/lib/sa/sa1 X[t n] X.sp X.B /usr/lib/sa/sa2 X.SH DESCRIPTION XThe routines provided here allow the system activity data provided Xby the operating system to be automatically saved at regular intervals Xand reviewed at the request of a user. X.P XA number of counters are kept internally by the operating system and are Xincremented as various system actions occur. XCounters are kept for XCPU utilization, buffer usage, TTY device activity, process switching, Xsystem-calls, file-access, queue activity, inter-process communications Xand demand paging. X.PP X.I Sadc X(the system activity data collector) and the two Xshell procedures, X.I sa1 Xand X.IR sa2 , Xare used to save the system activity counters so that they may be reviewed Xlater with the X.IR sar (1) Xcommand. X.PP X.IR Sadc Xreads the system activity information from /dev/kmem. XIf the optional X.I t Xand X.I n Xcounters are provided to X.IR sadc , Xthen X.IR sadc Xwill collect X.I n Xsets of data, waiting X.I t Xseconds between each data collection. X.IR Sadc Xwrite all its data in binary format to the file specified by X.I ofile Xor to standard output if no file is specified. XIf X.I t Xand X.I n Xare omitted, Xa special record is written to the file to indicate a system restart. XThe X.BR /etc/rc Xentry: X.RS X.sp X/usr/lib/sa/sadc /usr/adm/sa/sa\*`date +%d\*` X.sp X.RE Xwrites a "reset record" to the daily data file whenever the machine is Xreset. X.PP XThe shell script X.IR sa1 , Xcalls X.I sadc Xwith the appropriate parameters to save the system data Xin the daily system activity data file X.BI /usr/adm/sa/sa dd Xwhere X.I dd Xis the current day. XThe arguments X.I t Xand X.I n Xcauses X.I sa1 Xto invoke the X.I sadc Xprogram X.I n Xtimes at an interval of X.I t Xseconds, or once if the X.I t Xand X.I n Xarguments are not supplied. XThe entries in X.B crontab X(see X.IR cron (1M)): X.RS X.sp X0 \(** \(** \(** 0,6 /usr/lib/sa/sa1 X.br X0 8\-17 \(** \(** 1\-5 /usr/lib/sa/sa1 1200 3 X.br X0 18\-7 \(** \(** 1\-5 /usr/lib/sa/sa1 X.sp X.RE Xwill produce records every hour and every 20 minutes during working hours. X.PP XThe X.IR sa2 , Xshell script Xwrites a daily report of all the system activity which occured Xin the file X.BI /usr/adm/sa/sar dd. XThe command line arguments given to sa2 are the same as those given to X.B sar, Xthey are merely passed through to X.B sar. X.SH FILES X.RI /usr/adm/sa/sa "dd " Xdaily data file X.br X.RI /usr/adm/sa/sar "dd " X.br X/dev/kmem X.br X/unix Xdaily report file X.SH SEE ALSO Xcron(1M), Xsar(1), Xktune(7), X/usr/include/sys/sysinfo.h END_OF_sar.1m if test 2594 -ne `wc -c <sar.1m`; then echo shar: \"sar.1m\" unpacked with wrong size! fi # end of overwriting check fi echo shar: End of shell archive. exit 0 -- Mark H. Colburn mark@jhereg.Jhereg.MN.ORG ..!ihnp4!chinet!jhereg!mark