load average daemon for xenix redux

John F. Haugh II root at rpp386.cactus.org
Sun Nov 26 03:40:43 AEST 1989


this little program has been kicked around alt.sources a few
times and i thought i'd take a whack at it.

it reads the process table at one second intervals and computes
the load average from the status of the various processes.

the previous version worked just fine, except it counted
processes sleeping on FIFOs in the load average, as well as
counting itself.  on my system that gave a permanent load
average of 3+ with NO active processes ...

the fix was to ignore processes sleeping on disk i/o above
PZERO and to ignore the load average daemon itself.  that
immediately improved the numbers.

as a freebie i moved certain loop-invariant statements out
of the main loop and added code to put the process into the
background with hangup disabled.

so, without further fanfare, here it is ...
--------------------- cut here ------------------------
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	Makefile
#	lload.c
# This archive created: Sat Nov 25 10:35:26 1989
# By:	John F. Haugh II (River Parishes Programming, Plano TX)
export PATH; PATH=/bin:/usr/bin:$PATH
if test -f 'Makefile'
then
	echo shar: "will not over-write existing file 'Makefile'"
else
cat << \SHAR_EOF > 'Makefile'
SHELL = /bin/sh
#
# Load average daemon
#

# Xenix/386 flags
CFLAGS = -Ox -DM_XENIX
LIBS = -lx -lm
# Normal flags
# CFLAGS = -O
# LIBS = -lm

lload: lload.o
	cc -o lload lload.o $(LIBS)

install: lload
	cp lload /etc
	touch /etc/loadav
	chown sysinfo /etc/lload /etc/loadav
	chgrp sysinfo /etc/lload /etc/loadav
	chmod 4700 /etc/lload
	chmod 644 /etc/loadav

clean:
	rm -f lload.o core a.out

clobber: clean
	rm -f lload
SHAR_EOF
fi
if test -f 'lload.c'
then
	echo shar: "will not over-write existing file 'lload.c'"
else
cat << \SHAR_EOF > 'lload.c'
/*
 * Load Average deamon.
 *
 * The load average is updated every constant time interval, and the result
 * written to a file as 3 double values.
 *
 * The load average is for the last 1, 5 and 15 minutes (3 values).
 *
 * A second argument (-v) will set the program in a verbose mode, writing
 * the load average to the standard output and not to the file.
 *
 * Preferably start this process from the inittab. It needs special
 * priviledges to read from /dev/kmem.
 *
 * The following processes are regarded as "runnning":
 *   A process that has the SRUN status (simply running).
 *   A process that is being created (SIDLE).
 *   A process that is being swapped out (SXBRK).
 *   A process that waits for disk I/O.
 *
 * A process is regarded as waiting for disk I/O if it is SSLEEP and
 * has wchan set to a buf in the buffer pool and it has a priority
 * below PZERO.
 *
 * The sleep is implemented using poll on a stream device, not the
 * more usual sleep() call. Why ?
 * Because you do not want to wake up simultaneosly with other programs
 * doing sleep(), which might give wrong load average.
 * Of course, if you do not have the stream pipe device, use the normal
 * sleep().
 */



/*  For Xenix:  compile as follows:  cc -Ox lload.c -lm -lx -o lload   */
/*  For Xenix 286: compile as follows:
		cc -M2e -i -Ox lload.c -lm -lx -o lload		*/

/*  Modified for Xenix by Sanford Zelkovitz    XBBS   714-898-8634     */
/*  Modified for Xenix 286 by Bodo Rueskamp, <br at unido.uucp>  */
/*  Modified to ignore sleeps on FIFOs by John F. Haugh II, jfh at rpp386 */

#include <fcntl.h>
#include <signal.h>
#ifdef M_XENIX
#include <sys/a.out.h>
#else
#include <nlist.h>
#endif
#include <stdio.h>
#ifndef M_XENIX
#include <stropts.h>
#include <poll.h>
#endif
#include <math.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/buf.h>
#ifdef M_XENIX
#include <sys/page.h>
#else
#include <sys/immu.h>
#include <sys/region.h>
#endif
#include <sys/var.h>
#include <sys/proc.h>

/* #define DEBUG */	/* Will append all values in a debug file */

#define LOADAV 		"/etc/loadav"	/* Where to write the load avarge */
#define STREAM_PIPE	"/dev/spx"	/* Used for polling with timeout */

/*
 * You may or may not need the '_' in the following names.
 */

#define VAR_NAME "_v"
#ifdef M_I386
#define BUF_NAME "_pbuf"
#else
#define BUF_NAME "_buf"
#endif
#define PROC_NAME "_proc"

struct nlist nl[] = {
 {VAR_NAME},
 {BUF_NAME},
 {PROC_NAME},
 {0},
};

int loadfile;				/* file descr to result file */
struct proc *p;
int p_max = 0;
struct var v;
int kmem;
struct nlist *v_p, *proc_p, *buf_p;
int size;
int first_buf, last_buf;
int sleeping = 1;			/* Poll frequency in seconds */
int verbose = 0;

double av[3] = { 0.0, 0.0, 0.0 };	/* The loadaverage */
double apa[3];				/* Holding constants */

main(argc, argv)
    char **argv;
{
    int i, n, n_run, n_disk;
    int mypid = getpid();
#ifdef DEBUG
    int debug_fd;
    char buff[100];
    debug_fd = open("/tmp/loadavdebug", O_CREAT|O_WRONLY, 0600);
#endif

    if (argc == 2 && strcmp(argv[1], "-v") == 0) {
	verbose = 1;
	printf("Verbose\n");
    }
    kmem = open("/dev/kmem", O_RDONLY);
    if (kmem == -1) {
	perror("/dev/kmem");
	exit(1);
    }
    if (!verbose) {
	loadfile = open(LOADAV, O_RDWR|O_CREAT,0664);
	if (loadfile == -1) {
	    fprintf(stderr, "%s:", argv[0]);
	    perror(LOADAV);
	    exit(1);
	}
    }
#ifdef M_XENIX

    if (nlist("/xenix", nl) == -1) {
#else
    if (nlist("/unix", nl) == -1) {
#endif
	perror("nlist");
	exit(1);
    }
    for (i=0; nl[i].n_name; i++) {
#ifdef M_XENIX
	if(nl[i].n_name[0] == '\0')
		break;
#endif
#ifdef DEBUG
	fprintf(stderr, "nl[%d] = %s\n", i, nl[i].n_name);
#endif
	if (nl[i].n_value == 0) {
	    fprintf(stderr, "Could not get address for %s\n", nl[i].n_name);
	    exit(1);
	}
	if (strcmp(nl[i].n_name, VAR_NAME) == 0)
	    v_p = &nl[i];
	if (strcmp(nl[i].n_name, PROC_NAME) == 0)
	    proc_p = &nl[i];
	if (strcmp(nl[i].n_name, BUF_NAME) == 0)
	    buf_p = &nl[i];
    }
    /*
     * Setup the constants used for computing load average.
     */
    apa[0] = exp(-sleeping/60.0);
    apa[1] = exp(-sleeping/300.0);
    apa[2] = exp(-sleeping/900.0);

    /*
     * Read struct var for buffer addresses and process table info.
     */
    if (lseek(kmem, (long) v_p->n_value, 0) == -1) {
	perror("lseek v");
	exit(1);
    }
    if (read(kmem, &v, sizeof v) == -1) {
	perror("read v");
	exit(1);
    }

    /*
     * Setup the bounds of the system buffers
     */
    first_buf = buf_p->n_value;
    last_buf = first_buf + v.v_buf * sizeof (struct buf);

    /*
     * Setup the process table
     */
    p_max = v.v_proc;
    if (! (p = (struct proc *) malloc ((unsigned) (p_max * sizeof *p)))) {
	perror ("malloc p");
	exit (1);
    }

    /*
     * Put myself into the background
     */

    signal (SIGHUP, SIG_IGN);
    if (fork ())
	exit (0);

    setpgrp ();

    /*
     * Start looping
     */
    while(1) {
	/*
	 * Read the 'v' structure every time. It says how
	 * many procs are used.
	 */
	if (lseek(kmem, (long) v_p->n_value, 0) == -1) {
	    perror("lseek v");
	    exit(1);
	}
	if (read(kmem, &v, sizeof v) != sizeof v) {
	    perror("read v");
	    exit(1);
	}
	size = (struct proc *)v.ve_proc - (struct proc *)proc_p->n_value;
	if (lseek(kmem, (long) proc_p->n_value, 0) == -1) {
	    perror("lseek proc");
	    exit(1);
	}
	n = read(kmem, p, size * sizeof (struct proc));
	if (n != size * sizeof (struct proc)) {
	    if (n == -1) {
		perror("read procs");
		exit(1);
	    }
	    fprintf(stderr, "Could only read %d (%d) procs\n",
		    n, size);
	    size = n / sizeof (struct proc);
	}
	n_run = 0;
	n_disk = 0;
	for (i=0; i<size; i++) {
	    if (p[i].p_pid == mypid)
		continue;
	    if (p[i].p_stat == SRUN || p[i].p_stat == SIDL ||
		p[i].p_stat == SXBRK)
		n_run++;
	    else if (p[i].p_stat == SSLEEP &&
		     p[i].p_pri < PZERO &&
		     (unsigned int)p[i].p_wchan >= first_buf &&
		     (unsigned int)p[i].p_wchan < last_buf) {
		n_disk++;
	    }
	}
	/*
	 * Update the load average using a decay filter.
	 */
	for (i = 0; i < 3; i++)
	    av[i] = apa[i] * av[i] + (n_run + n_disk) * (1.0 - apa[i]);

	if (!verbose) {
	    if (lseek(loadfile, 0L, 0) == -1) {
		fprintf(stderr, "Couldn't seek in %s\n", LOADAV);
		exit(1);
	    }
	    if (write(loadfile, (char *)av, sizeof av) != sizeof av) {
		perror(argv[0]);
		exit(1);
	    }
	} else
	    printf("(%d %d) %f %f %f\n", n_run, n_disk,
		   av[0], av[1], av[2]);
#ifdef DEBUG
	sprintf(buff, "(%d %d) %4.2f\n", n_run, n_disk,
		   av[0]);
	write(debug_fd, buff, strlen(buff));
#endif
	(void)nap(sleeping * 1000L);
    }
}

#ifndef M_XENIX
/*
 * Use a stream pipe to implement a sleep.
 * We have a stream pipe for ourselves, so we know noone will write
 * on it.
 */
nap(milli) {
    static int fd = 0;
    static struct pollfd pollfd;

    if (fd == 0) {
	fd = open(STREAM_PIPE, 0);
	if (fd == -1) {
	    perror(STREAM_PIPE);
	    exit(1);
	}
	pollfd.fd = fd;
	pollfd.events = POLLIN;
    }
    if (poll(&pollfd, 1, milli) == -1) {
	perror("nap: poll");
	exit(1);
    }
    if (pollfd.revents != 0) {
	fprintf(stderr, "nap: poll: got something\n");
	exit(1);
    }
}
#endif
SHAR_EOF
fi
exit 0
#	End of shell archive



More information about the Alt.sources mailing list