/* * pg_top - a top PostgreSQL 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 * AUTHOR: adapted from Rich Holland * AUTHOR: adapted from Kevin Schmidt */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pg_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 ""; } 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 pg_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); } void get_io_info(struct io_info *io_info) { /* Not supported yet */ memset(io_info, 0, sizeof(*io_info)); } void get_disk_info(struct disk_info *disk_info, char *data_directory) { /* Not supported yet */ memset(disk_info, 0, sizeof(*disk_info)); }