Sar(1m) for the 3b1
Mark H. Colburn
mark at jhereg.Jhereg.MN.ORG
Wed May 25 13:42:10 AEST 1988
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 at 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 at 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 at 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 at 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 at 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 at 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 at 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 at 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 at 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 at jhereg.Jhereg.MN.ORG
..!ihnp4!chinet!jhereg!mark
More information about the Unix-pc.sources
mailing list