[comp.sources.unix] v15i004: Page accounting aide for SysVrel3.1

rsalz@bbn.com (Rich Salz) (05/24/88)

Submitted-by: DJ Molny <ihnp4!chinet!djmolny>
Posting-number: Volume 15, Issue 4
Archive-name: pages


"Pages" notes the size and the number of pages in core for each
paging region in the system.  A typical process has three regions:
text (executable instructions), initialized data variables, and
uninitialized variables (called BSS).  Processes may also use
regions to connect to shared libraries, shared memory segments, etc.

Notes on portability:  "Pages" is highly dependent on the System V
Release 3.1 implementation, and it has only been tested on the
AT&T 3B2/400 computer.  "Pages" will probably run on all members of
the 3B2 family, but may run on the 3B15 as well.  As far as release
compatability goes, SVR3.0 is probably OK, SVR2.1 *may* work, and
releases prior to 2.1 didn't have paging.

#! /bin/sh
#
# -rw-r--r--   1 djmolny  ccom        4072 Feb 19 12:29 README
# -r--r--r--   1 djmolny  ccom       10688 Feb 19 12:26 pages.c
# -rw-r--r--   1 djmolny  ccom        1286 Feb 19 11:20 pages.1
# -rw-r--r--   1 djmolny  ccom         199 Feb 19 10:32 pages.mk
#
echo 'x - README'
if test -f README; then echo 'shar: not overwriting README'; else
sed 's/^X//' << '________This_Is_The_END________' > README
XAT&T's paging UNIX operating system provides two tools to
Xanalyze memory usage.  The sar(1) package collects statistics
Xabout the amount of free memory in the system, and the number of
Xpages and processes moved in and out from the disk swap area.
XThe ps(1) command's "-l" option shows each process' size in pages.
X(1 page = 2Kb.)
X
XUnfortunately, neither sar nor ps tells how many pages from each
Xprocess are in actually core, and how many have been saved to disk.
XTo fill this need, I wrote "pages".  "Pages" works a lot like ps(1).
XIt reads /unix to find kernel symbol addresses, then opens /dev/kmem
Xand rummages around to find the information it wants.  "Pages" saves
Xkernel symbol data in /etc/pg_data to speed up execution.  This file
Xis updated automatically whenever /unix is newer than /etc/pg_data.
X
X"Pages" notes the size and the number of pages in core for each
Xpaging region in the system.  A typical process has three regions:
Xtext (executable instructions), initialized data variables, and
Xuninitialized variables (called BSS).  Processes may also use
Xregions to connect to shared libraries, shared memory segments, etc.
X
XAfter each region has been analyzed, "pages" prints a table of
Xeach process and independent region it has encountered.  The table
Xshows the process id (if appropriate), the number of pages swapped
Xto disk, and total number of pages, and the process' name (if appropriate.)
X
XA typical run of "pages" (with no arguments) may look like this:
X
X1:	  PID  NSWAP/TOTL   COMMAND
X2:	SHARE      1   23   sh
X3:	11308      3    8   sh
X4:	18486     12  104   vi
X5:	18498     15   46   pages
X6:	18496      3    8   sh
X
XLine one is column headings.  The second line shows the shared text region
Xof the Bourne shell (indicated by the word "SHARE" in column 1.)  One of
Xits 23 pages is paged out to disk (or was never loaded in the first place.)
XThe third line is the combined data and BSS regions of a shell, with three
Xof the 8 pages on disk.  Since the data are not shared, the specific process
Xid is shown.  Line 6 gives the same information for another shell process.
X
XLine 4 shows that 12 of 104 pages of vi are on disk.  In this case, the
XTOTL column represents the combined text, data, and BSS sizes.  If another
Xuser was running vi, a "SHARE" line show the shared text region separately.
XLine 5 shows "pages" itself.
X
XBy defaults, "pages" only shows processes attached to your current tty.
XTo alter this behavior, the -e, -a, -t, and -u options may be used.
XThese options work the same as on "pages" as they do on "ps", and
Xare explained on the ps(1) man page.  In addition, the -q option may
Xbe used to get a quick look at the system's overall paging status.
X
X"Pages" must run as setuid bin so that it can write the /etc/pg_data file,
Xand as setgid sys so that it can read /dev/kmem.  The makefile supplied
Xwith this package sets ownership and uid/gid bits when the "install"
Xtarget is specified.
X
XNotes on portability:  "Pages" is highly dependent on the System V
XRelease 3.1 implementation, and it has only been tested on the
XAT&T 3B2/400 computer.  "Pages" will probably run on all members of
Xthe 3B2 family, but may run on the 3B15 as well.  As far as release
Xcompatability goes, SVR3.0 is probably OK, SVR2.1 *may* work, and
Xreleases prior to 2.1 didn't have paging.
X
XI had to read and compare /usr/include/sys files and wade through lots of
Xkernel dumps to find the algorithms for this program.  Anyone who wants
Xto port "pages" to a truly foreign implementation will have to do the same.
XI only have access to the 3B2/400, so you're on your own when it comes
Xto porting.  Sorry.
X
XEnhancements and bug reports (preferably with fixes) are welcome but
XI don't promise to incorporate them immediately.  Since netnews often
Xexpires before I get around to reading it, messages should be sent
Xvia E-mail to ihnp4!chinet!djmolny.
X
XThis program is placed in the public domain.  It may be distributed
Xand modified freely providing this notice is reproduced in its entirety.
XThis program may not be sold.  Author: DJ Molny, ccom consultants, inc.
________This_Is_The_END________
if test `wc -l < README` -ne 79; then
	echo 'shar: README was damaged during transit (should have been 79 bytes)'
fi
fi		; : end of overwriting check
echo 'x - pages.c'
if test -f pages.c; then echo 'shar: not overwriting pages.c'; else
sed 's/^X//' << '________This_Is_The_END________' > pages.c
X/*
X * pages - show process paging information
X *
X * Written by:	DJ Molny
X *		ccom consultants, inc.
X *		5963 Oakwood Dr.
X *		Lisle, IL 60532
X *		ihnp4!chinet!djmolny
X *
X * This program is placed in the public domain.  It may be distributed
X * and modified freely providing this notice is reproduced in its entirety.
X * This program may not be sold.
X *
X * The "pages" program is highly dependent on AT&T's implementation
X * of the 3B2 UNIX System V Release 3 operating system.  It might be
X * modified to other members of the AT&T 3B family, but porting to
X * other paging implementations of UNIX is probably impossible.
X *
X * Refer to the accompanying manual page for usage instructions and
X * the inevitable disclaimers.
X *
X */
X
X#include <stdio.h>
X#include <sys/types.h>
X#include <nlist.h>
X#include <fcntl.h>
X#include <pwd.h>
X#include <sys/stat.h>
X#include <sys/immu.h>
X#include <sys/psw.h>
X#include <sys/pcb.h>
X#include <sys/signal.h>
X#include <sys/fs/s5dir.h>
X#include <sys/user.h>
X#include <sys/region.h>
X#include <sys/param.h>
X#include <sys/proc.h>
X#include <sys/var.h>
X#include <sys/sysmacros.h>
X#include <sys/sys3b.h>
X
Xstatic char sccsid[] = "@(#)pages.c	1.11 2/19/88";
X
X#define	PNAME	"pages"
X#define DATAFILE "/etc/pg_data"
X
X#define	MAXREAD	(64 * 1024)		/* max bytes to read in one shot */
X
Xstruct nlist ksymbs[] = {
X	"proc",     (long) 0, (short) 0, (unsigned short) 0, (char) 0, (char) 0,
X	"v",        (long) 0, (short) 0, (unsigned short) 0, (char) 0, (char) 0,
X	"freemem",  (long) 0, (short) 0, (unsigned short) 0, (char) 0, (char) 0,
X	(char *) 0, (long) 0, (short) 0, (unsigned short) 0, (char) 0, (char) 0,
X};
X
Xint quick = 0;		/* quick mode (off by default)		*/
Xint ttys;		/* tty # to show (defaults to current tty)	*/
Xint tflag = 0;		/* tty # specified (off by default)		*/
Xint aflag = 0;		/* show all logged in users (off by default)	*/
Xint eflag = 0;		/* show all procs (off by default)		*/
Xint uids = -1;		/* user id # (defaults to unused)		*/
X
Xint memfd;
Xstruct var v;
X
Xstatic struct regblk {		/* pertinent info. for the regions we select */
X	unsigned long pid;
X	char cmd[DIRSIZ];
X	short size;
X	short incore;
X};
X
Xstruct regblk *reg_tab, *reg_cur;	/* ptrs to region table */
X
X#define Error(s)	{ perror(s); }
X
X
Xmain(argc, argv)
Xint argc;
Xchar **argv;
X{
X	extern char *optarg;
X	struct passwd *pwd, *getpwnam();
X	struct stat sbuf, sbuf2;
X	char ttynm[128];
X	char *malloc();
X	int datafd;
X	int i;
X
X	while ((i = getopt(argc, argv, "qaeu:t:")) != -1)
X	{
X		switch(i) {
X			case 'q':
X				quick++;
X				eflag++;
X				break;
X			case 'e':
X				eflag++;
X				break;
X			case 'a':
X				aflag++;
X				break;
X			case 'u':
X				if (!optarg) usage();
X				if (*optarg >= '0' && *optarg <= '9')
X					uids = atoi(optarg);
X				else {
X					pwd = getpwnam(optarg);
X					if (!pwd)
X					{
X					    fprintf(stderr,
X						"No such user: %s\n", optarg);
X					    usage();
X					}
X					uids = pwd->pw_uid;
X				}
X				break;
X			case 't':
X				if (!optarg) usage();
X				tflag++;
X				strcpy(ttynm, "/dev/");
X				if (*optarg >= '0' && *optarg <= '9')
X					strcat(ttynm, "tty");
X				strcat(ttynm, optarg);
X				if (stat(ttynm, &sbuf) == -1)
X				{
X					perror(ttynm);
X					exit(4);
X				}
X
X				ttys = sbuf.st_rdev;
X				break;
X			default:
X				usage();
X		}
X	}
X
X
X	/* for default case (no arguments), act as if
X	   we're invoked with our own tty specified */
X
X	if (!aflag && !eflag && !tflag && uids == -1)
X	{
X		tflag++;
X
X		/* check stdin, stdout, stderr for a file
X		   descriptor connected to a tty */
X
X		for (i = 0; i < 3; i++)
X			if (isatty(i))
X				break;
X
X		if (i == 3)
X		{
X			fprintf(stderr, "%s: can't find tty\n", PNAME);
X			exit(5);
X		}
X
X		/* stat the tty file */
X		if (fstat(i, &sbuf) == -1)
X		{
X			perror(PNAME);
X			exit(6);
X		}
X
X		/* and extract the internal device number */
X		ttys = sbuf.st_rdev;
X	}
X
X	/* open kernel memory */
X
X	if ((memfd = open("/dev/kmem", O_RDONLY)) == -1)
X	{
X		Error("/dev/kmem");
X		exit(7);
X	}
X
X	/* stat /unix to see if it's changed since
X	   we last wrote the data cache file */
X
X	if (stat("/unix", &sbuf) == -1)
X	{
X		Error("/unix");
X		exit(10);
X	}
X
X	/* if our cache file is gone, or if unix is newer than
X	   the cache file, do the lengthy nlist() call and re-write
X	   the cache */
X
X	if (stat(DATAFILE, &sbuf2) == -1 || sbuf.st_mtime > sbuf2.st_mtime)
X	{
X		if (nlist("/unix", ksymbs) == -1)	/* LONG library call */
X		{
X			Error("nlist");
X			exit(11);
X		}
X
X		/* create/truncate the cache file */
X
X		datafd = open(DATAFILE, O_CREAT | O_WRONLY | O_TRUNC, 0664);
X
X		if (datafd == -1)
X		{
X			Error(DATAFILE);
X			exit(12);
X		}
X
X		/* save the whole name list structure */
X		if (write(datafd, ksymbs, sizeof(ksymbs)) != sizeof(ksymbs))
X		{
X			Error(DATAFILE);
X			exit(13);
X		}
X
X		close(datafd);
X
X	} else {	/* cache is current, so read ksymbs from there */
X
X		datafd = open(DATAFILE, O_RDONLY);
X
X		if (datafd == -1)
X		{
X			Error(DATAFILE);
X			exit(14);
X		}
X
X		/* snarf in the whole array */
X		if (read(datafd, ksymbs, sizeof(ksymbs)) != sizeof(ksymbs))
X		{
X			Error(DATAFILE);
X			exit(16);
X		}
X
X		close(datafd);
X	}
X
X
X	/* seek to the "var" kernel structure (contains tunables) */
X	if (lseek(memfd, ksymbs[1].n_value, 0) == -1)
X	{
X		Error("memory lseek");
X		exit(17);
X	}
X
X	/* and read it */
X	if (read(memfd, &v, sizeof(v)) != sizeof(v))
X	{
X		Error("memory read");
X		exit(18);
X	}
X
X	/* allocate one regblk per region */
X	reg_tab = (struct regblk *) malloc(sizeof(struct regblk) * v.v_region);
X
X	if (reg_tab == NULL)	/* make sure malloc succeeded */
X	{
X		fprintf(stderr, "can't grab %d bytes\n",
X			sizeof(struct regblk) * v.v_region);
X		exit(19);
X	}
X
X	procloop();			/* loop through proc table */
X	showpage();			/* display the paging info */
X
X	return(0);
X}
X
X
Xprocloop()
X{
X	struct user u;
X	struct region r;
X	struct pregion pr;
X	struct proc p;
X	register struct proc *pp;
X	int incore, npages;
X	int i;
X
X	pp = &p;
X	reg_cur = reg_tab;	/* initialize region table ptr */
X
X	/* for each process... */
X	for (i=0; i < v.v_proc; i++)
X	{
X		/* seek to process table */
X		if (lseek(memfd, ksymbs[0].n_value + i * sizeof(p), 0) == -1)
X		{
X			Error("memory lseek");
X			exit(21);
X		}
X
X		if (read(memfd, &p, sizeof(p)) != sizeof(p))
X		{
X			Error("memory read");
X			exit(22);
X		}
X
X		if (!pp->p_stat)	/* if p_stat == 0, no proc in slot */
X			continue;
X
X		/* use handy sys3b call to read the u block */
X		/* (see what I mean about non-portable!?) */
X		if (sys3b(RDUBLK, pp->p_pid, &u, sizeof(u)) == -1)
X			continue;
X
X		/* if we're not showing just any process... */
X		if (!eflag)
X		{
X			if (aflag)	/* show all procs connected to ttys */
X			{
X				if (u.u_ttyp == 0) /* if no tty, ignore */
X					continue;
X			} else {	/* all procs, subject to tty # & uid */
X
X				/* skip proc if not the right tty */
X				if (tflag && (!u.u_ttyp || ttys != u.u_ttyd))
X					continue;
X
X				/* skip proc if not the right user id */
X				if (uids != -1 && uids != pp->p_uid)
X					continue;
X			}
X		}
X
X		incore = npages = 0;	/* init counters */
X
X		while(1)		/* zoom thru region table */
X		{
X			/* lseek may fail if conditions change during our
X			   run; therefore this is a non-fatal error */
X			if (lseek(memfd, pp->p_region, 0) != (int) pp->p_region)
X				break;
X
X			/* reads may also fail non-fatally */
X			if (read(memfd, &pr, sizeof(pr)) != sizeof(pr))
X				break;
X
X			/* if region ptr = 0, it's part of the chain */
X			if (!pr.p_reg)
X			{
X				/* log it */
X				logr((unsigned long) pp->p_pid, npages,
X							incore, u.u_comm);
X				break;
X			}
X
X			/* point to next spot in the chain */
X			pp->p_region++;
X
X			/* go to the region itself */
X			if (lseek(memfd, pr.p_reg, 0) != (int) pr.p_reg)
X				break;
X
X			/* and read it */
X			if (read(memfd, &r, sizeof(r)) != sizeof(r))
X				break;
X
X			/* if it's PRIVATE or only used in one place,
X			   count it against the current process */
X			if (r.r_type == RT_PRIVATE || r.r_refcnt == 1)
X			{
X				npages += r.r_pgsz;
X				incore += r.r_nvalid;
X			} else		/* it's shared, so just log it */
X				logs((unsigned long) pr.p_reg, r.r_pgsz,
X					r.r_nvalid, u.u_comm, pr.p_type);
X		}
X	}
X}
X
X
X/* log a shared region in the table */
X
Xlogs(addr, size, incore, cmd, ptype)
Xchar *addr;
Xint size, incore;
Xchar *cmd;
Xint ptype;
X{
X	register struct regblk *r;
X	char *cmdtype;
X
X	switch(ptype) {
X		case PT_TEXT:	/* for shared text, save the cmd name */
X			cmdtype = cmd;
X			break;
X		case PT_DATA:
X			cmdtype = "<DATA>";
X			break;
X		case PT_STACK:	
X			cmdtype = "<STACK>";
X			break;
X		case PT_SHMEM:
X			cmdtype = "<SHMEM>";
X			break;
X		case PT_DMM:
X			cmdtype = "<DMM>";
X			break;
X		case PT_LIBTXT:
X			cmdtype = "<LIBTXT>";
X			break;
X		case PT_LIBDAT:
X			cmdtype = "<LIBDAT>";
X			break;
X		default:
X			cmdtype = "<OTHER>";	/* punt */
X			break;
X		}
X
X	/* loop thru the region table, looking to see if this one
X	   has been entered before (to avoid dups) */
X
X	for (r = reg_tab; r < reg_cur; r++)
X		if (r->pid == (unsigned long) addr)
X			return;
X
X	/* a new region; enter it now */
X	r->pid = (unsigned long) addr;
X	strncpy(r->cmd, cmdtype, DIRSIZ);
X	r->size = size;
X	r->incore = incore;
X
X	reg_cur++;		/* bump table ptr */
X}
X
X
X
X/* log a region in the table */
X
Xlogr(pid, size, incore, cmd)
Xunsigned long pid;
Xint size, incore;
Xchar *cmd;
X{
X	register struct regblk *r = reg_cur;
X
X	r->pid = (unsigned long) pid;	/* save the process id */
X	strncpy(r->cmd, cmd, DIRSIZ);	/* save the cmd name */
X	r->size = size;			/* save the region size */
X	r->incore = incore;		/* save the # of pages in core */
X
X	reg_cur++;			/* bump table ptr */
X}
X
X
Xshowpage()
X{
X	if (quick)
X		show_q();
X	else
X		show_n();
X}
X
X
X/* quick mode */
Xshow_q()
X{
X	register struct regblk *r;
X	int swapped = 0;
X	int incore = 0;
X	int freemem;
X
X	/* zip thru region table, adding up pages in core & pages swapped */
X	for (r = reg_tab; r < reg_cur; r++)
X	{
X		if (!r->size)
X			continue;
X		incore += r->incore;
X		swapped += r->size - r->incore;
X	}
X
X	/* seek to freemem's address */
X	if (lseek(memfd, ksymbs[2].n_value, 0) == -1)
X	{
X		Error("memory lseek");
X		exit(23);
X	}
X
X	/* read freemem */
X	if (read(memfd, &freemem, sizeof(freemem)) != sizeof(freemem))
X	{
X		Error("memory read");
X		exit(24);
X	}
X
X	/* print statitistics */
X	printf("%4d pages free in core\n", freemem);
X	printf("%4d pages active in core\n", incore);
X	printf("%4d pages swapped on disk\n", swapped);
X}
X
X
X/* per-process display */
Xshow_n()
X{
X	register struct regblk *r;
X
X	printf("  PID  NSWAP/TOTL   COMMAND\n");
X
X	/* zip thru region table, printing processes & shared regions */
X	for (r = reg_tab; r < reg_cur; r++)
X	{
X		if (r->pid < 64*1024)
X			printf("%5d", r->pid);
X		else
X			printf("SHARE");
X
X		printf("   %4d %4d   %-.*s\n", r->size - r->incore,
X						r->size, DIRSIZ, r->cmd);
X	}
X}
X
X
X/* print canned error message */
Xusage()
X{
X	fprintf(stderr,
X		"usage: %s [-e] [-a] [-u user] [-t tty]\n", PNAME);
X	exit(99);
X}
________This_Is_The_END________
if test `wc -l < pages.c` -ne 522; then
	echo 'shar: pages.c was damaged during transit (should have been 522 bytes)'
fi
fi		; : end of overwriting check
echo 'x - pages.1'
if test -f pages.1; then echo 'shar: not overwriting pages.1'; else
sed 's/^X//' << '________This_Is_The_END________' > pages.1
X.TH PAGES 1 "2/19/88"
X.UC 4
X.SH NAME
Xpages \- show 3B2 process paging status
X.SH SYNOPSIS
X.B pages
X[-q]
X[-e]
X[-a]
X[-u user]
X[-t tty]
X.SH DESCRIPTION
X.I Pages
Xscans kernel memory, and displays the size and number of swapped-out
Xpages for each process.
XRegions used by multiple processes, such as shared text, shared libraries,
Xand conventional shared memory are also displayed.
X.LP
XThe -e option selects all processes and regions.
XThe -a option selects all processes with a controlling tty.
XThe -u and -t options may be used to select processes by
Xuser id or controlling tty, respectively.
XInvoking
X.I pages
Xwithout arguments is equivalent to using the
X.B -t
Xoption for your own terminal.
X(All of these options work like their corresponding
X.I ps
Xoptions.)
X.LP
XThe -q option displays a three-line summary of pages free in core,
Xpages in use in core, and pages swapped to disk.  Individual processes
Xare not displayed.
X.LP
XOn the 3B2, one page equals 2K bytes of memory.
X.SH "SEE ALSO"
Xps(1)
X.SH AUTHOR
XDJ Molny, ihnp4!chinet!djmolny
X.SH DISCLAIMERS
X.I Pages
Xis highly dependent on the AT&T 3B2 System V Release 3 UNIX implementation.
XIt has only been tested on the AT&T 3B2/400 running SVR3.1.
X.TP
X"UNIX" and "3B" are trademarks of AT&T.
X.SH BUGS
XBugs?  We don't write no stinking bugs!
________This_Is_The_END________
if test `wc -l < pages.1` -ne 48; then
	echo 'shar: pages.1 was damaged during transit (should have been 48 bytes)'
fi
fi		; : end of overwriting check
echo 'x - pages.mk'
if test -f pages.mk; then echo 'shar: not overwriting pages.mk'; else
sed 's/^X//' << '________This_Is_The_END________' > pages.mk
XBIN=/usr/local/bin
XCFLAGS=	-O
XLDFLAGS= -s
X
Xpages:	pages.o
X	$(CC) $(LDFLAGS) -o pages pages.o
X
Xinstall: pages
X	mv pages $(BIN)
X	chown bin $(BIN)/pages
X	chgrp sys $(BIN)/pages
X	chmod 6755 $(BIN)/pages
________This_Is_The_END________
if test `wc -l < pages.mk` -ne 12; then
	echo 'shar: pages.mk was damaged during transit (should have been 12 bytes)'
fi
fi		; : end of overwriting check
exit 0
-- 
Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.