Re: SUMMARY: top under Solaris 2.X

From: Robert Boucher (rob@asimov.uucp)
Date: Thu Mar 04 1993 - 21:24:06 CST


In article <1993Mar4.153229.5129@aai.com> marc@aai.com (Marc Cohen) writes:
>
>Back about mid-January I posted a query about running top under Solaris
>2.1/SunOS 5.1. I wasn't able to get it working initially but I have it
>working now and thought I'd share what I did. Sorry for the lateness
>and hope this info is still worthwhile.

Here is an updated version of Marc's m_svr4.c. I've taken his code
and massaged it somewhat. I was hoping to include the necessary
changes in the new release of top which should start beta testing any
time now... But if people are going to use it, might as well display
some valid information. Since I my home SVR4/386 box has been replaced
with a new SPARClassic, I'll probably create a new module just for
SunOS5.1 and leave m_svr4.c alone. For now, however, they live
together.

Things that still require to be fixed:

1. Load average is incorrect.

2. Weighted cpu and %cpu are also incorrect

3. assumes only one cpu, should handle the ncpus symbol.

On the other hand, the cpu state and memory status are
correct. They match what vmstat(1m) returns.

This is by no means a finished module, as the bug list above can
attest, however the original SVR4 code was returning bogus numbers for
memory, swap and cpu states. There are probably many more bugs, please
send mail if you find any.

To install, you'll need top-3.0 and then follow Marc's instructions.

>From top-3.0 README:
> The latest version of "top" is always available via anonymous FTP from
> the host "eecs.nwu.edu" in the directory "/pub/top". Additional modules
> will be made available in the directory "/pub/top/m".
  

--
Robert Boucher

Work: boucher@sofkin.ca Home: boucher%asimov@revcan.rct.ca Home: uunet.ca!revcan!asimov!boucher

PS/2: yesterday's hardware today

Marc's instructions:

>First I'd like to thank Robert Boucher for writing m_svr4.c and >David J. MacKenzie who sent me a copy of it. > >To build top I started with the top 3.0 distribution and Robert's >m_svr4.c (a copy is included below). I used the unbundled Sun C. > >To get it to build I had to: > >o remove /usr/ucb from my path so I wouldn't get Berkeley > compatibilty stuff when I compiled. >o run configure and choose "svr4" as the system type. >o edit the Makefile so the "CDEFS" and "LIBS" lines look like: > > CDEFS = -O -DPRIO_MIN=-20 -DPRIO_MAX=20 > LIBS = -lelf -lc -L/usr/ucblib -lucb > >o install it as setuid root so it can read stuff in /proc. > >-Marc >-----

/* * top - a top users display for Unix * * SYNOPSIS: For System V Release 4, Intel or Sparc CPU * * DESCRIPTION: * * The following must be added to CFLAGS in the Makefile: * [From <ucbinclude/sys/resource.h>] * -DPRIO_MIN=-20 -DPRIO_MAX=20 * * LIBS: -lelf -lc -L/usr/ucblib -lucb * * AUTHOR: Robert Boucher <boucher@sofkin.ca> */

#include <sys/types.h> #include <sys/param.h> #include <sys/signal.h> #include <sys/fault.h> #include <sys/sysinfo.h> #include <sys/sysmacros.h> #include <sys/syscall.h> #include <dirent.h> #include <sys/user.h> #include <sys/proc.h> #include <sys/procfs.h> #include <sys/vm.h> #include <sys/var.h> #ifdef sun #include <sys/cpuvar.h> #endif #include <sys/file.h> #include <dirent.h> #include <stdio.h> #include <nlist.h> #include <fcntl.h> #include <math.h> #include <vm/anon.h>

#include "top.h" #include "machine.h"

#ifndef FSCALE /* * [From /usr/ucbinclude/sys/param.h] * Scale factor for scaled integers used to count * %cpu time and load averages. */ #define FSHIFT 8 /* bits to right of fixed binary point */ #define FSCALE (1<<FSHIFT) #endif /* FSCALE */

/* get_process_info passes back a handle. This is what it looks like: */

struct handle { struct prpsinfo **next_proc;/* points to next valid proc pointer */ int remaining; /* number of pointers remaining */ };

#define CPUSTATES 5

/* declarations for load_avg */ typedef long load_avg; #define loaddouble(la) ((double)(la) / FSCALE)

/* define what weighted cpu is. */ #define percent_cpu(pp) ((double)(pp)->pr_cpu / FSCALE)

#define weighted_cpu(pct, pp) ( ((pp)->pr_time.tv_sec) == 0 ? 0.0 : \ ((pp)->pr_cpu) / ((pp)->pr_time.tv_sec) )

#ifdef sun #undef ctob #define ctob(x) ((x) << 12) #endif

/* Convert clicks (kernel pages) to kbytes ... */ #define pagetok(size) ctob(size) >> LOG1024

/* definitions for indices in the nlist array */ #define X_AVENRUN 0 #define X_MPID 1 #ifndef sun #define X_SYSINFO 2 #else #define X_CPU 2 #endif #define X_V 3 #define X_NPROC 4 #define X_ANONINFO 5 #ifndef sun #define X_TOTAL 6 #else #define X_FREEMEM 6 #define X_MAXMEM 7 #define X_AVAILRMEM 8 #define X_SWAPFS_MINFREE 9 #endif

static struct nlist nlst[] = { {"avenrun"}, /* 0 */ {"mpid"}, /* 1 */ #ifndef sun {"sysinfo"}, /* 2 */ #else {"cpu"}, /* 2 */ #endif {"v"}, /* 3 */ {"nproc"}, /* 4 */ {"anoninfo"}, /* 5 */ #ifndef sun {"total"}, /* 6 */ #else {"freemem"}, /* 6 */ {"maxmem"}, /* 7 */ {"availrmem"}, /* 8 */ {"swapfs_minfree"}, /* 9 */ #endif {0} };

/* * These definitions control the format of the per-process area */

static char header[] = " PID X PRI NICE SIZE RES STATE TIME WCPU CPU COMMAND"; /* 0123456 -- field to fill in starts at header+6 */ #define UNAME_START 6

#define Proc_format \ "%5d %-8.8s %3d %4d%6dK %4dK %-5s%4d:%02d %3d.0%% %5.2f%% %.16s"

/* process state names for the "STATE" column of the display */ /* the extra nulls in the string "run" are for adding a slash and the processor number when needed */

char *state_abbrev[] = { "", "sleep", "run", "zombie", "stop", "start", "cpu", "swap" };

#ifdef sun #define UNIX "/kernel/unix" #else #define UNIX "/stand/unix" #endif #define KMEM "/dev/kmem" #define PROCFS "/proc"

static int kmem = -1; static int mem = -1;

#ifndef sun struct vmtotal total; #else int freemem; int maxmem; int availrmem; int swapfs_minfree; #endif

struct anoninfo anoninfo;

/* these are retrieved from the kernel in _init */

static int nproc;

/* these are offsets obtained via nlist and used in the get_ functions */

static unsigned long mpid_offset; static unsigned long nproc_offset; static unsigned long avenrun_offset; #ifndef sun static unsigned long total_offset; static unsigned long sysinfo_offset; #else static unsigned long freemem_offset; static unsigned long maxmem_offset; static unsigned long availrmem_offset; static unsigned long swapfs_minfree_offset; static unsigned long cpu_offset; #endif static unsigned long anoninfo_offset;

/* these are for calculating cpu state percentages */

static long cp_time[CPUSTATES]; static long cp_old[CPUSTATES]; static long cp_diff[CPUSTATES];

/* these are for detailing the process states */

int process_states[8]; char *procstatenames[] = { "", " sleeping, ", " running, ", " zombie, ", " stopped, ", " starting, ", " on cpu, ", " swapped, ", NULL };

/* these are for detailing the cpu states */

int cpu_states[CPUSTATES]; char *cpustatenames[] = { "idle", "user", "kernel", "wait", "swap", NULL };

/* these are for detailing the memory statistics */

int memory_stats[5]; char *memorynames[] = { "K real, ", "K active, ", "K free, ", "K swap, ", "K free swap", NULL };

/* these are for keeping track of the proc array */

static int bytes; static struct prpsinfo *pbase; static struct prpsinfo **pref;

/* useful externals */ extern int errno; extern char *sys_errlist[]; extern char *myname;

long lseek (); long percentages ();

machine_init (statics) struct statics *statics; { register int i; static struct var v;

/* open kernel memory */ if ((kmem = open (KMEM, 0)) < 0) { perror (KMEM); exit (20); }

/* get the list of symbols we want to access in the kernel */ if ((i = nlist (UNIX, nlst)) < 0) { fprintf (stderr, "%s: nlist failed\n", myname); return (-1); }

/* make sure they were all found */ if (i > 0 && check_nlist (nlst) > 0) { return (-1); }

/* get the symbol values out of kmem */ /* NPROC Tuning parameter for max number of processes */ (void) getkval (nlst[X_V].n_value, &v, sizeof (struct var), nlst[X_V].n_name); nproc = v.v_proc;

/* stash away certain offsets for later use */ mpid_offset = nlst[X_MPID].n_value; nproc_offset = nlst[X_NPROC].n_value; avenrun_offset = nlst[X_AVENRUN].n_value; anoninfo_offset = nlst[X_ANONINFO].n_value; #ifndef sun total_offset = nlst[X_TOTAL].n_value; sysinfo_offset = nlst[X_SYSINFO].n_value; #else freemem_offset = nlst[X_FREEMEM].n_value; maxmem_offset = nlst[X_MAXMEM].n_value; availrmem_offset = nlst[X_AVAILRMEM].n_value; swapfs_minfree_offset = nlst[X_SWAPFS_MINFREE].n_value; /* XXX This is very dangerous... cpu is actually an array[ncpus] of cpu */ /* Since my box only has one, I'll fix this later... */ (void) getkval (nlst[X_CPU].n_value, &cpu_offset, sizeof (cpu_offset), nlst[X_CPU].n_name); #endif

/* allocate space for proc structure array and array of pointers */ bytes = nproc * sizeof (struct prpsinfo); pbase = (struct prpsinfo *) malloc (bytes); pref = (struct prpsinfo **) malloc (nproc * sizeof (struct prpsinfo *));

/* Just in case ... */ if (pbase == (struct prpsinfo *) NULL || pref == (struct prpsinfo **) NULL) { fprintf (stderr, "%s: can't allocate sufficient memory\n", myname); return (-1); }

/* fill in the statics information */ statics->procstate_names = procstatenames; statics->cpustate_names = cpustatenames; statics->memory_names = memorynames;

/* all done! */ return (0); }

char * format_header (uname_field) register char *uname_field; { register char *ptr;

ptr = header + UNAME_START; while (*uname_field != '\0') { *ptr++ = *uname_field++; }

return (header); }

get_system_info (si) struct system_info *si; { load_avg avenrun[3]; #ifndef sun struct sysinfo sysinfo; #else struct cpu cpu; /* XXX ROB: Very bad, cpu is an array of ncpus */ #endif register int j;

/* get the cp_time array */ for (j = 0; j < CPUSTATES; j++) cp_time[j] = 0L; #ifndef sun (void) getkval (sysinfo_offset, &sysinfo, sizeof (struct sysinfo), "sysinfo"); for (j = 0; j < CPUSTATES; j++) cp_time[j] += (long) sysinfo.cpu[j]; #else (void) getkval (cpu_offset, &cpu, sizeof (struct cpu), "cpu"); for (j = 0; j < CPUSTATES; j++) cp_time[j] += (long) cpu.cpu_stat.cpu_sysinfo.cpu[j]; #endif

/* get load average array */ (void) getkval (avenrun_offset, (int *) avenrun, sizeof (avenrun), "avenrun");

/* get mpid -- process id of last process */ (void) getkval (mpid_offset, &(si->last_pid), sizeof (si->last_pid), "mpid");

/* convert load averages to doubles */ { register int i; register double *infoloadp; register load_avg *sysloadp;

infoloadp = si->load_avg; sysloadp = avenrun; for (i = 0; i < 3; i++) { *infoloadp++ = loaddouble (*sysloadp++); } }

/* convert cp_time counts to percentages */ (void) percentages (CPUSTATES, cpu_states, cp_time, cp_old, cp_diff);

#ifndef sun /* get total -- systemwide main memory usage structure */ (void) getkval (total_offset, (int *) (&total), sizeof (total), "total"); /* convert memory stats to Kbytes */ memory_stats[0] = pagetok (total.t_rm); memory_stats[1] = pagetok (total.t_arm); memory_stats[2] = pagetok (total.t_free); #else (void) getkval (freemem_offset, (int *) (&freemem), sizeof (freemem), "freemem"); (void) getkval (maxmem_offset, (int *) (&maxmem), sizeof (maxmem), "maxmem"); memory_stats[0] = pagetok (maxmem); memory_stats[1] = 0; memory_stats[2] = pagetok (freemem); #endif (void) getkval (anoninfo_offset, (int *) (&anoninfo), sizeof (anoninfo), "anoninfo"); #ifndef sun memory_stats[3] = pagetok (anoninfo.ani_max - anoninfo.ani_free); memory_stats[4] = pagetok (anoninfo.ani_max - anoninfo.ani_resv); #else (void) getkval (availrmem_offset, (int *) (&availrmem), sizeof (availrmem), "availrmem"); (void) getkval (swapfs_minfree_offset, (int *) (&swapfs_minfree), sizeof (swapfs_minfree), "swapfs_minfree"); memory_stats[3] = pagetok (anoninfo.ani_resv); memory_stats[4] = pagetok (MAX (anoninfo.ani_max - anoninfo.ani_resv, 0) + availrmem - swapfs_minfree); #endif

/* set arrays and strings */ si->cpustates = cpu_states; si->memory = memory_stats; }

static struct handle handle;

caddr_t get_process_info (si, sel, compare) struct system_info *si; struct process_select *sel; int (*compare) (); { register int i; register int total_procs; register int active_procs; register struct prpsinfo **prefp; register struct prpsinfo *pp;

/* these are copied out of sel for speed */ int show_idle; int show_system; int show_uid;

/* Get current number of processes */ (void) getkval (nproc_offset, (int *) (&nproc), sizeof (nproc), "nproc");

/* read all the proc structures */ getptable (pbase);

/* get a pointer to the states summary array */ si->procstates = process_states;

/* set up flags which define what we are going to select */ show_idle = sel->idle; show_system = sel->system; show_uid = sel->uid != -1;

/* count up process states and get pointers to interesting procs */ total_procs = 0; active_procs = 0; bzero ((char *) process_states, sizeof (process_states)); prefp = pref;

for (pp = pbase, i = 0; i < nproc; pp++, i++) { /* * Place pointers to each valid proc structure in pref[]. * Process slots that are actually in use have a non-zero * status field. Processes with SSYS set are system * processes---these get ignored unless show_sysprocs is set. */ if (pp->pr_state != 0 && (show_system || ((pp->pr_flag & SSYS) == 0))) { total_procs++; process_states[pp->pr_state]++; if ((!pp->pr_zomb) && (show_idle || (pp->pr_state == SRUN) || (pp->pr_state == SONPROC)) && (!show_uid || pp->pr_uid == (uid_t) sel->uid)) { *prefp++ = pp; active_procs++; } } }

/* if requested, sort the "interesting" processes */ if (compare != NULL) { qsort ((char *) pref, active_procs, sizeof (struct prpsinfo *), compare); }

/* remember active and total counts */ si->p_total = total_procs; si->p_active = active_procs;

/* pass back a handle */ handle.next_proc = pref; handle.remaining = active_procs; return ((caddr_t) & handle); }

char fmt[128]; /* static area where result is built */

char * format_next_process (handle, get_userid) caddr_t handle; char *(*get_userid) (); { register struct prpsinfo *pp; struct handle *hp; register long cputime; register double pctcpu;

/* find and remember the next proc structure */ hp = (struct handle *) handle; pp = *(hp->next_proc++); hp->remaining--;

/* get the cpu usage and calculate the cpu percentages */ cputime = pp->pr_time.tv_sec; pctcpu = percent_cpu (pp);

/* format this entry */ sprintf (fmt, Proc_format, pp->pr_pid, (*get_userid) (pp->pr_uid), pp->pr_pri - PZERO, pp->pr_nice - NZERO, #ifdef sun pp->pr_bysize / 1024, pp->pr_byrssize / 1024, #else pagetok (pp->pr_size), pagetok (pp->pr_rssize), #endif state_abbrev[pp->pr_state], cputime / 60l, cputime % 60l, (pp->pr_cpu & 0377), 100.0 * pctcpu, pp->pr_fname); /* pp->pr_fname ); */

/* return the result */ return (fmt); }

/* * check_nlist(nlst) - checks the nlist to see if any symbols were not * found. For every symbol that was not found, a one-line * message is printed to stderr. The routine returns the * number of symbols NOT found. */ int check_nlist (nlst) register struct nlist *nlst; { register int i;

/* check to see if we got ALL the symbols we requested */ /* this will write one line to stderr for every symbol not found */

i = 0; while (nlst->n_name != NULL) { if (nlst->n_type == 0) { /* this one wasn't found */ fprintf (stderr, "kernel: no symbol named `%s'\n", nlst->n_name); i = 1; } nlst++; } return (i); }

/* * getkval(offset, ptr, size, refstr) - get a value out of the kernel. * "offset" is the byte offset into the kernel for the desired value, * "ptr" points to a buffer into which the value is retrieved, * "size" is the size of the buffer (and the object to retrieve), * "refstr" is a reference string used when printing error meessages, * if "refstr" starts with a '!', then a failure on read will not * be fatal (this may seem like a silly way to do things, but I * really didn't want the overhead of another argument). * */ getkval (offset, ptr, size, refstr) unsigned long offset; int *ptr; int size; char *refstr; { if (lseek (kmem, (long) offset, 0) == -1) { if (*refstr == '!') { refstr++; } fprintf (stderr, "%s: lseek to %s: %s\n", KMEM, refstr, sys_errlist[errno]); quit (22); } if (read (kmem, (char *) ptr, size) == -1) { if (*refstr == '!') { /* we lost the race with the kernel, process isn't in memory */ return (0); } else { fprintf (stderr, "%s: reading %s: %s\n", KMEM, refstr, sys_errlist[errno]); quit (23); } } return (1); }

/* comparison routine for qsort */

/* * proc_compare - comparison function for "qsort" * Compares the resource consumption of two processes using five * distinct keys. The keys (in descending order of importance) are: * percent cpu, cpu ticks, state, resident set size, total virtual * memory usage. The process states are ordered as follows (from least * to most important): WAIT, zombie, sleep, stop, start, run. The * array declaration below maps a process state index into a number * that reflects this ordering. */

unsigned char sorted_state[] = { 0, /* not used */ 3, /* sleep */ 6, /* run */ 2, /* zombie */ 4, /* stop */ 5, /* start */ 7, /* run on a processor */ 1 /* being swapped (WAIT) */ };

proc_compare (pp1, pp2) struct prpsinfo **pp1; struct prpsinfo **pp2; { register struct prpsinfo *p1; register struct prpsinfo *p2; register long result;

/* remove one level of indirection */ p1 = *pp1; p2 = *pp2;

/* compare percent cpu (pctcpu) */ if ((result = (long) (p2->pr_cpu - p1->pr_cpu)) == 0) { /* use cpticks to break the tie */ if ((result = p2->pr_time.tv_sec - p1->pr_time.tv_sec) == 0) { /* use process state to break the tie */ if ((result = (long) (sorted_state[p2->pr_state] - sorted_state[p1->pr_state])) == 0) { /* use priority to break the tie */ if ((result = p2->pr_oldpri - p1->pr_oldpri) == 0) { /* use resident set size (rssize) to break the tie */ if ((result = p2->pr_rssize - p1->pr_rssize) == 0) { /* use total memory to break the tie */ result = (p2->pr_size - p1->pr_size); } } } } } return (result); }

/* get process table V.4 only has a linked list of processes so we want to follow that linked list, get all the process structures, and put them in our own table */ getptable (baseptr) struct prpsinfo *baseptr; { struct prpsinfo *currproc; /* pointer to current proc structure */ int numprocs = 0; DIR *procdir; struct dirent *direntp; int fd; char fname[40];

if ((procdir = opendir (PROCFS)) == NULL) { fprintf (stderr, "%s: Unable to open %s\n", myname, PROCFS); exit (1); }

while (((direntp = readdir (procdir)) != NULL) && (numprocs < nproc)) { if ((!strcmp (direntp->d_name, ".")) || (!strcmp (direntp->d_name, ".."))) { continue; } strcpy (fname, PROCFS); strcat (fname, "/"); strcat (fname, direntp->d_name); if ((fd = open (fname, O_RDONLY)) < 0) { /* fprintf(stderr,"%s: Unable to open %s\n",myname, direntp->d_name); */ close (fd); continue; } currproc = &baseptr[numprocs]; if (ioctl (fd, PIOCPSINFO, currproc) < 0) { /* fprintf(stderr,"%s: Unable to get info from %s\n", myname, direntp->d_name); */ close (fd); continue; } numprocs++; close (fd); } closedir (procdir); if (nproc != numprocs) nproc = numprocs; } -- Inet: boucher%asimov@revcan.rct.ca Usenet: uunet.ca!revcan!asimov!boucher

C++ = Increment C and use the old value



This archive was generated by hypermail 2.1.2 : Fri Sep 28 2001 - 23:07:33 CDT