/*
* Copyright (c) 1984 through 2008, William LeFebvre
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of William LeFebvre nor the names of other
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* top - a top users display for Unix
*
* SYNOPSIS: any hp9000 running hpux version 10.x
*
* DESCRIPTION:
* This is the machine-dependent module for HPUX 10/11 that uses pstat.
* It has been tested on HP/UX 10.01, 10.20, and 11.00. It is presumed
* to work also on 10.10.
* Idle processes are marked by being either runnable or having a %CPU
* of at least 0.1%. This fraction is defined by CPU_IDLE_THRESH and
* can be adjusted at compile time.
*
* CFLAGS: -DHAVE_GETOPT
*
* LIBS:
*
* AUTHOR: John Haxby <john_haxby@hp.com>
* AUTHOR: adapted from Rich Holland <holland@synopsys.com>
* AUTHOR: adapted from Kevin Schmidt <kevin@mcl.ucsb.edu>
*/
#include "config.h"
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <ctype.h>
#include <signal.h>
#include <nlist.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/pstat.h>
#include <sys/dk.h>
#include <sys/stat.h>
#include <sys/dirent.h>
#include "top.h"
#include "machine.h"
#include "utils.h"
/*
* The idle threshold (CPU_IDLE_THRESH) is an extension to the normal
* idle process check. Basically, we regard a process as idle if it is
* both asleep and using less that CPU_IDLE_THRESH percent cpu time. I
* believe this makes the "i" option more useful, but if you don't, add
* "-DCPU_IDLE_THRESH=0.0" to the CFLAGS.
*/
#ifndef CPU_IDLE_THRESH
#define CPU_IDLE_THRESH 0.1
#endif
# define P_RSSIZE(p) (p)->pst_rssize
# define P_TSIZE(p) (p)->pst_tsize
# define P_DSIZE(p) (p)->pst_dsize
# define P_SSIZE(p) (p)->pst_ssize
#define VMUNIX "/stand/vmunix"
#define KMEM "/dev/kmem"
#define MEM "/dev/mem"
#ifdef DOSWAP
#define SWAP "/dev/dmem"
#endif
/* what we consider to be process size: */
#define PROCSIZE(pp) (P_TSIZE(pp) + P_DSIZE(pp) + P_SSIZE(pp))
/* definitions for indices in the nlist array */
#define X_MPID 0
static struct nlist nlst[] = {
{ "mpid" },
{ 0 }
};
/*
* These definitions control the format of the per-process area
*/
static char header[] =
" TTY PID X PRI NICE SIZE RES STATE TIME CPU COMMAND";
/* 0123456789.12345 -- field to fill in starts at header+6 */
#define UNAME_START 15
#define Proc_format \
"%8.8s %5d %-8.8s %4d %4d %5s %5s %-5s %6s %5.2f%% %s"
/* process state names for the "STATE" column of the display */
char *state_abbrev[] =
{
"", "sleep", "run", "stop", "zomb", "trans", "start"
};
/* values that we stash away in _init and use in later routines */
static int kmem;
static struct pst_status *pst;
/* these are retrieved from the OS in _init */
static int nproc;
static int ncpu = 0;
/* these are offsets obtained via nlist and used in the get_ functions */
static unsigned long mpid_offset;
/* these are for calculating cpu state percentages */
static long cp_time[PST_MAX_CPUSTATES];
static long cp_old[PST_MAX_CPUSTATES];
static long cp_diff[PST_MAX_CPUSTATES];
/* these are for detailing the process states */
int process_states[7];
char *procstatenames[] = {
"", " sleeping, ", " running, ", " stopped, ", " zombie, ",
" trans, ", " starting, ",
NULL
};
/* these are for detailing the cpu states */
int cpu_states[PST_MAX_CPUSTATES];
char *cpustatenames[] = {
/* roll "swait" into "block" and "ssys" into "sys" */
"usr", "nice", "sys", "idle", "", "block", "\0swait", "intr", "\0ssys",
NULL
};
/* these are for detailing the memory statistics */
long memory_stats[8];
char *memorynames[] = {
"Real: ", "K act, ", "K tot ", "Virtual: ", "K act, ",
"K tot, ", "K free", NULL
};
/* these are for getting the memory statistics */
static int pageshift; /* log base 2 of the pagesize */
/* define pagetok in terms of pageshift */
#define pagetok(size) ((size) << pageshift)
/* Mapping TTY major/minor numbers is done through this structure */
struct ttymap {
dev_t dev;
char name [9];
};
static struct ttymap *ttynames = NULL;
static int nttys = 0;
static get_tty_names ();
/* 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.
*/
static unsigned char sorted_state[] =
{
0, /* not used */
3, /* sleep */
6, /* run */
4, /* stop */
2, /* zombie */
5, /* start */
1, /* other */
};
proc_compare(p1, p2)
struct pst_status *p1;
struct pst_status *p2;
{
int result;
float lresult;
/* compare percent cpu (pctcpu) */
if ((lresult = p2->pst_pctcpu - p1->pst_pctcpu) == 0)
{
/* use cpticks to break the tie */
if ((result = p2->pst_cpticks - p1->pst_cpticks) == 0)
{
/* use process state to break the tie */
if ((result = sorted_state[p2->pst_stat] -
sorted_state[p1->pst_stat]) == 0)
{
/* use priority to break the tie */
if ((result = p2->pst_pri - p1->pst_pri) == 0)
{
/* use resident set size (rssize) to break the tie */
if ((result = P_RSSIZE(p2) - P_RSSIZE(p1)) == 0)
{
/* use total memory to break the tie */
result = PROCSIZE(p2) - PROCSIZE(p1);
}
}
}
}
}
else
{
result = lresult < 0 ? -1 : 1;
}
return(result);
}
machine_init(statics)
struct statics *statics;
{
struct pst_static info;
int i = 0;
int pagesize;
/* If we can get mpid from the kernel, we'll use it, otherwise */
/* we'll guess from the most recently started proces */
if ((kmem = open (KMEM, O_RDONLY)) < 0 ||
(nlist (VMUNIX, nlst)) < 0 ||
(nlst[X_MPID].n_type) == 0)
mpid_offset = 0;
else
mpid_offset = nlst[X_MPID].n_value;
if (pstat_getstatic (&info, sizeof (info), 1, 0) < 0)
{
perror ("pstat_getstatic");
return -1;
}
/*
* Allocate space for the per-process structures (pst_status). To
* make life easier, simply allocate enough storage to hold all the
* process information at once. This won't normally be a problem
* since machines with lots of processes configured will also have
* lots of memory.
*/
nproc = info.max_proc;
pst = (struct pst_status *) malloc (nproc * sizeof (struct pst_status));
if (pst == NULL)
{
fprintf (stderr, "out of memory\n");
return -1;
}
/*
* Calculate pageshift -- the value needed to convert pages to Kbytes.
* This will usually be 2.
*/
pageshift = 0;
for (pagesize = info.page_size; pagesize > 1; pagesize >>= 1)
pageshift += 1;
pageshift -= LOG1024;
/* get tty name information */
i = 0;
get_tty_names ("/dev", &i);
/* 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)
char *uname_field;
{
char *ptr = header + UNAME_START;
while (*uname_field != '\0')
*ptr++ = *uname_field++;
return header;
}
void
get_system_info(si)
struct system_info *si;
{
static struct pst_dynamic dynamic;
int i, n;
long total;
pstat_getdynamic (&dynamic, sizeof (dynamic), 1, 0);
ncpu = dynamic.psd_proc_cnt; /* need this later */
/* Load average */
si->load_avg[0] = dynamic.psd_avg_1_min;
si->load_avg[1] = dynamic.psd_avg_5_min;
si->load_avg[2] = dynamic.psd_avg_15_min;
/*
* CPU times
* to avoid space problems, we roll SWAIT (kernel semaphore block)
* into BLOCK (spin lock block) and SSYS (kernel process) into SYS
* (system time) Ideally, all screens would be wider :-)
*/
dynamic.psd_cpu_time [CP_BLOCK] += dynamic.psd_cpu_time [CP_SWAIT];
dynamic.psd_cpu_time [CP_SWAIT] = 0;
dynamic.psd_cpu_time [CP_SYS] += dynamic.psd_cpu_time [CP_SSYS];
dynamic.psd_cpu_time [CP_SSYS] = 0;
for (i = 0; i < PST_MAX_CPUSTATES; i++)
cp_time [i] = dynamic.psd_cpu_time [i];
percentages(PST_MAX_CPUSTATES, cpu_states, cp_time, cp_old, cp_diff);
si->cpustates = cpu_states;
/*
* VM statistics
*/
memory_stats[0] = -1;
memory_stats[1] = pagetok (dynamic.psd_arm);
memory_stats[2] = pagetok (dynamic.psd_rm);
memory_stats[3] = -1;
memory_stats[4] = pagetok (dynamic.psd_avm);
memory_stats[5] = pagetok (dynamic.psd_vm);
memory_stats[6] = pagetok (dynamic.psd_free);
si->memory = memory_stats;
/*
* If we can get mpid from the kernel, then we will do so now.
* Otherwise we'll guess at mpid from the most recently started
* process time. Note that this requires us to get the pst array
* now rather than in get_process_info(). We rely on
* get_system_info() being called before get_system_info() for this
* to work reliably.
*/
for (i = 0; i < nproc; i++)
pst[i].pst_pid = -1;
n = pstat_getproc (pst, sizeof (*pst), nproc, 0);
if (kmem >= 0 && mpid_offset > 0)
(void) getkval(mpid_offset, &(si->last_pid), sizeof(si->last_pid), "mpid");
else
{
static int last_start_time = 0;
int pid = 0;
for (i = 0; i < n; i++)
{
if (last_start_time <= pst[i].pst_start)
{
last_start_time = pst[i].pst_start;
if (pid <= pst[i].pst_pid)
pid = pst[i].pst_pid;
}
}
if (pid != 0)
si->last_pid = pid;
}
}
caddr_t get_process_info(si, sel, compare_index)
struct system_info *si;
struct process_select *sel;
int compare_index;
{
static int handle;
int i, active, total;
/*
* Eliminate unwanted processes
* and tot up all the wanted processes by state
*/
for (i = 0; i < sizeof (process_states)/sizeof (process_states[0]); i++)
process_states [i] = 0;
for (total = 0, active = 0, i = 0; pst[i].pst_pid >= 0; i++)
{
int state = pst[i].pst_stat;
process_states [state] += 1;
total += 1;
if (!sel->system && (pst[i].pst_flag & PS_SYS))
{
pst[i].pst_stat = -1;
continue;
}
/*
* If we are eliminating idle processes, then a process is regarded
* as idle if it is in a short term sleep and not using much
* CPU, or stopped, or simple dead.
*/
if (!sel->idle
&& (state == PS_SLEEP || state == PS_STOP || state == PS_ZOMBIE)
&& (state != PS_SLEEP && pst[i].pst_pctcpu < CPU_IDLE_THRESH/100.0))
pst[i].pst_stat = -1;
if (sel->uid > 0 && sel->uid != pst[i].pst_uid)
pst[i].pst_stat = -1;
if (sel->command != NULL &&
strncmp (sel->command, pst[i].pst_ucomm, strlen (pst[i].pst_ucomm)) != 0)
pst[i].pst_stat = -1;
if (pst[i].pst_stat >= 0)
active += 1;
}
si->procstates = process_states;
si->p_total = total;
si->p_active = active;
qsort ((char *)pst, i, sizeof(*pst), proc_compare);
/* handle is simply an index into the process structures */
handle = 0;
return (caddr_t) &handle;
}
/*
* Find the terminal name associated with a particular
* major/minor number pair
*/
static char *term_name (term)
struct psdev *term;
{
dev_t dev;
int i;
if (term->psd_major == -1 && term->psd_minor == -1)
return "?";
dev = makedev (term->psd_major, term->psd_minor);
for (i = 0; i < nttys && ttynames[i].name[0] != '\0'; i++)
{
if (dev == ttynames[i].dev)
return ttynames[i].name;
}
return "<unk>";
}
char *format_next_process(handle, get_userid)
caddr_t handle;
char *(*get_userid)();
{
static char fmt[MAX_COLS]; /* static area where result is built */
char run [sizeof ("runNN")];
int idx;
struct pst_status *proc;
char *state;
int size;
register long cputime;
register double pct;
int where;
struct handle *hp;
struct timeval time;
struct timezone timezone;
/* sanity check */
if (handle == NULL)
return "";
idx = *((int *) handle);
while (idx < nproc && pst[idx].pst_stat < 0)
idx += 1;
if (idx >= nproc || pst[idx].pst_stat < 0)
return "";
proc = &pst[idx];
*((int *) handle) = idx+1;
/* set ucomm for system processes, although we shouldn't need to */
if (proc->pst_ucomm[0] == '\0')
{
if (proc->pst_pid == 0)
strcpy (proc->pst_ucomm, "Swapper");
else if (proc->pst_pid == 2)
strcpy (proc->pst_ucomm, "Pager");
}
size = proc->pst_tsize + proc->pst_dsize + proc->pst_ssize;
if (ncpu > 1 && proc->pst_stat == PS_RUN)
{
sprintf (run, "run%02d", proc->pst_procnum);
state = run;
}
else if (proc->pst_stat == PS_SLEEP)
{
switch (proc->pst_pri+PTIMESHARE) {
case PSWP: state = "SWP"; break; /* also PMEM */
case PRIRWLOCK: state = "RWLOCK"; break;
case PRIBETA: state = "BETA"; break;
case PRIALPHA: state = "ALPHA"; break;
case PRISYNC: state = "SYNC"; break;
case PINOD: state = "INOD"; break;
case PRIBIO: state = "BIO"; break;
case PLLIO: state = "LLIO"; break; /* also PRIUBA */
case PZERO: state = "ZERO"; break;
case PPIPE: state = "pipe"; break;
case PVFS: state = "vfs"; break;
case PWAIT: state = "wait"; break;
case PLOCK: state = "lock"; break;
case PSLEP: state = "slep"; break;
case PUSER: state = "user"; break;
default:
if (proc->pst_pri < PZERO-PTIMESHARE)
state = "SLEEP";
else
state = "sleep";
}
}
else
state = state_abbrev [proc->pst_stat];
/* format this entry */
sprintf(fmt,
Proc_format,
term_name (&proc->pst_term),
proc->pst_pid,
(*get_userid)(proc->pst_uid),
proc->pst_pri,
proc->pst_nice - NZERO,
format_k(size),
format_k(proc->pst_rssize),
state,
format_time(proc->pst_utime + proc->pst_stime),
100.0 * proc->pst_pctcpu,
printable(proc->pst_ucomm));
/* return the result */
return(fmt);
}
/*
* 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, SEEK_SET) == -1) {
if (*refstr == '!')
refstr++;
(void) fprintf(stderr, "%s: lseek to %s: %s\n", KMEM,
refstr, strerror(errno));
quit(23);
}
if (read(kmem, (char *) ptr, size) == -1) {
if (*refstr == '!')
return(0);
else {
(void) fprintf(stderr, "%s: reading %s: %s\n", KMEM,
refstr, strerror(errno));
quit(23);
}
}
return(1);
}
void (*signal(sig, func))()
int sig;
void (*func)();
{
struct sigaction act;
struct sigaction oact;
memset (&act, 0, sizeof (act));
act.sa_handler = func;
if (sigaction (sig, &act, &oact) < 0)
return BADSIG;
return oact.sa_handler;
}
/*
* proc_owner(pid) - returns the uid that owns process "pid", or -1 if
* the process does not exist.
* It is EXTREMLY IMPORTANT that this function work correctly.
* If top runs setuid root (as in SVR4), then this function
* is the only thing that stands in the way of a serious
* security problem. It validates requests for the "kill"
* and "renice" commands.
*/
int proc_owner(pid)
int pid;
{
int i;
for (i = 0; i < nproc; i++)
{
if (pst[i].pst_pid == pid)
return pst[i].pst_uid;
}
return -1;
}
static get_tty_names (dir, m)
char *dir;
int *m;
{
char name [MAXPATHLEN+1];
struct dirent **namelist;
int i, n;
if ((n = scandir (dir, &namelist, NULL, NULL)) < 0)
return;
if (ttynames == NULL)
{
nttys = n;
ttynames = malloc (n*sizeof (*ttynames));
}
else
{
nttys += n;
ttynames = realloc (ttynames, nttys*sizeof (*ttynames));
}
for (i = 0; i < n; i++)
{
struct stat statbuf;
char *str = namelist[i]->d_name;
if (*str == '.')
continue;
sprintf (name, "%s/%s", dir, str);
if (stat (name, &statbuf) < 0)
continue;
if (!isalpha (*str))
str = name + sizeof ("/dev");
if (S_ISCHR (statbuf.st_mode))
{
ttynames [*m].dev = statbuf.st_rdev;
strncpy (ttynames[*m].name, str, 8);
ttynames[*m].name[9] = '\0';
*m += 1;
}
else if (S_ISDIR (statbuf.st_mode))
get_tty_names (name, m);
}
if (*m < nttys)
ttynames[*m].name[0] = '\0';
free (namelist);
}