/* * pg_top - a top PostgreSQL users display for Unix * * SYNOPSIS: Any Sun running SunOS 5.x (Solaris 2.x) * * DESCRIPTION: * This is the machine-dependent module for SunOS 5.x (Solaris 2). * There is some support for MP architectures. * This makes pg_top work on all revisions of SunOS 5 from 5.0 * through 5.9 (otherwise known as Solaris 9). It has not been * tested on SunOS 5.10. * * LIBS: -lelf -lkvm -lkstat * * CFLAGS: -DHAVE_GETOPT -DORDER -DHAVE_STRERROR -DUSE_SIZE_T * * * AUTHORS: Torsten Kasch * Robert Boucher * CONTRIBUTORS: Marc Cohen * Charles Hedrick * William L. Jones * Petri Kutvonen * Casper Dik * Tim Pugh * Mark Wong */ #define _KMEMUSER #include "config.h" #if (OSREV == 551) #undef OSREV #define OSREV 55 #endif /* kernels 5.4 and above track pctcpu in the proc structure, but the results are less than desirable, so we continue to pretend that they don't and just calculate it on our own */ #if (OSREV >= 54) /* #define PROC_HAS_PCTCPU */ #endif #define USE_NEW_PROC #if defined(USE_NEW_PROC) && OSREV >= 56 #define _STRUCTURED_PROC 1 #define prpsinfo psinfo #include #define pr_fill pr_nlwp /* These require an ANSI C compiler "Reisser cpp" doesn't like this */ #define pr_state pr_lwp.pr_state #define pr_oldpri pr_lwp.pr_oldpri #define pr_nice pr_lwp.pr_nice #define pr_pri pr_lwp.pr_pri #define pr_onpro pr_lwp.pr_onpro #define ZOMBIE(p) ((p)->pr_nlwp == 0) #define SIZE_K(p) (long)((p)->pr_size) #define RSS_K(p) (long)((p)->pr_rssize) #else #undef USE_NEW_PROC #define ZOMBIE(p) ((p)->pr_zomb) #define SIZE_K(p) (long)((p)->pr_bysize/1024) #define RSS_K(p) (long)((p)->pr_byrssize/1024) #define pr_onpro pr_filler[5] #endif #include "pg_top.h" #include "machine.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utils.h" #if OSREV >= 53 #define USE_KSTAT #endif #ifdef USE_KSTAT #include /* * Some kstats are fixed at 32 bits, these will be specified as ui32; some * are "natural" size (32 bit on 32 bit Solaris, 64 on 64 bit Solaris * we'll make those unsigned long) * Older Solaris doesn't define KSTAT_DATA_UINT32, those are always 32 bit. */ #ifndef KSTAT_DATA_UINT32 #define ui32 ul #endif #endif #define UNIX "/dev/ksyms" #define KMEM "/dev/kmem" #define PROCFS "/proc" #define CPUSTATES 5 #ifndef PRIO_MIN #define PRIO_MIN -20 #endif #ifndef PRIO_MAX #define PRIO_MAX 20 #endif #ifndef FSCALE #define FSHIFT 8 /* bits to right of fixed binary point */ #define FSCALE (1<: The following percent numbers are 16-bit * binary fractions [0 .. 1] with the binary point to the right of the * high-order bit (one == 0x8000) */ #define percent_cpu(pp) (((double)pp->pr_pctcpu)/0x8000*100) #define weighted_cpu(pp) (*(double *)dbl_align(pp->pr_filler)) #else #define percent_cpu(pp) (*(double *)dbl_align(&pp->pr_filler[0])) #define weighted_cpu(pp) (*(double *)dbl_align(&pp->pr_filler[2])) #endif /* definitions for indices in the nlist array */ #define X_V 0 #define X_MPID 1 #define X_ANONINFO 2 #define X_MAXMEM 3 #define X_FREEMEM 4 #define X_AVENRUN 5 #define X_CPU 6 #define X_NPROC 7 #define X_NCPUS 8 static struct nlist nlst[] = { {"v"}, /* 0 */ /* replaced by dynamic allocation */ {"mpid"}, /* 1 */ #if OSREV >= 56 /* this structure really has some extra fields, but the first three match */ {"k_anoninfo"}, /* 2 */ #else {"anoninfo"}, /* 2 */ #endif {"maxmem"}, /* 3 */ /* use sysconf */ {"freemem"}, /* 4 */ /* available from kstat >= 2.5 */ {"avenrun"}, /* 5 */ /* available from kstat */ {"cpu"}, /* 6 */ /* available from kstat */ {"nproc"}, /* 7 */ /* available from kstat */ {"ncpus"}, /* 8 */ /* available from kstat */ {0} }; static unsigned long avenrun_offset; static unsigned long mpid_offset; #ifdef USE_KSTAT static kstat_ctl_t *kc = NULL; static kid_t kcid = 0; #else static unsigned long *cpu_offset; #endif static unsigned long nproc_offset; static unsigned long freemem_offset; static unsigned long maxmem_offset; static unsigned long anoninfo_offset; static void reallocproc(int n); static int maxprocs = 0; /* 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 */ }; /* * Structure for keeping track of CPU times from last time around * the program. We keep these things in a hash table, which is * recreated at every cycle. */ struct oldproc { pid_t oldpid; double oldtime; double oldpct; }; int oldprocs; /* size of table */ #define HASH(x) ((x << 1) % oldprocs) /* * GCC assumes that all doubles are aligned. Unfortunately it * doesn't round up the structure size to be a multiple of 8. * Thus we'll get a coredump when going through array. The * following is a size rounded up to 8. */ #define PRPSINFOSIZE dbl_align(sizeof(struct prpsinfo)) /* * These definitions control the format of the per-process area */ #if OSREV >= 58 static char header[] = " PID X LWP PRI NICE SIZE RES STATE TIME CPU COMMAND"; /* 0123456 -- field to fill in starts at header+6 */ #define UNAME_START 7 #define Proc_format \ "%6d %-8.8s %3d %3d %4d %5s %5s %-6s %6s %5s%% %s" #else static char header[] = " PID X LWP PRI NICE SIZE RES STATE TIME CPU COMMAND"; /* 0123456 -- field to fill in starts at header+6 */ #define UNAME_START 6 #define Proc_format \ "%5d %-8.8s %3d %3d %4d %5s %5s %-6s %6s %5s%% %s" #endif /* process state names for the "STATE" column of the display */ char *state_abbrev[] = {"", "sleep", "run", "zombie", "stop", "start", "cpu", "swap"}; int process_states[8]; char *procstatenames[] = { "", " sleeping, ", " running, ", " zombie, ", " stopped, ", " starting, ", " on cpu, ", " swapped, ", NULL }; int64_t cpu_states[CPUSTATES]; char *cpustatenames[] = {"idle", "user", "kernel", "iowait", "swap", NULL}; #define CPUSTATE_IOWAIT 3 #define CPUSTATE_SWAP 4 /* these are for detailing the memory statistics */ long memory_stats[5]; char *memorynames[] = {"K phys mem, ", "K free mem, ", "K total swap, ", "K free swap", NULL}; #define MEMORY_TOTALMEM 0 #define MEMORY_FREEMEM 1 #define MEMORY_TOTALSWAP 2 #define MEMORY_FREESWAP 3 /* these are names given to allowed sorting orders -- first is default */ char *ordernames[] = {"cpu", "size", "res", "time", NULL}; char *ordernames_io[] = {NULL}; /* forward definitions for comparison functions */ int compare_cpu(); int compare_size(); int compare_res(); int compare_time(); int (*proc_compares[]) () = { compare_cpu, compare_size, compare_res, compare_time, NULL }; kvm_t *kd; static DIR *procdir; static int nproc; /* "cpucount" is used to store the value for the kernel variable "ncpus". But since actually defines a variable "ncpus" we need to use a different name here. --wnl */ static int cpucount; /* these are for keeping track of the proc array */ static struct prpsinfo *pbase; static struct prpsinfo **pref; static struct oldproc *oldbase; /* pagetok function is really a pointer to an appropriate function */ static int pageshift; static long (*p_pagetok) (); #define pagetok(size) ((*p_pagetok)(size)) /* useful externals */ extern char *myname; extern int check_nlist(); extern int gettimeofday(); extern void perror(); extern void getptable(struct prpsinfo *, PGresult *); extern void quit(); extern int nlist(); /* p_pagetok points to one of the following, depending on which direction data has to be shifted: */ long pagetok_none(long size) { return (size); } long pagetok_left(long size) { return (size << pageshift); } long pagetok_right(long size) { return (size >> pageshift); } /* * 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). * */ int getkval(unsigned long offset, int *ptr, int size, char *refstr) { #ifdef DEBUG dprintf("getkval(%08x, %08x, %d, %s)\n", offset, ptr, size, refstr); #endif /* DEBUG */ if (kvm_read(kd, offset, (char *) ptr, size) != size) { #ifdef DEBUG dprintf("getkval: read failed\n"); #endif /* DEBUG */ if (*refstr == '!') { return (0); } else { fprintf(stderr, "pg_top: kvm_read for %s: %s\n", refstr, strerror(errno)); quit(23); } } #ifdef DEBUG dprintf("getkval read %d (%08x)\n", *ptr); #endif /* DEBUG */ return (1); } int machine_init(struct statics * statics) { struct utmpx ut; struct utmpx *up; int i; char *p; #ifndef USE_KSTAT int offset; #endif /* * There's a buffer overflow bug in curses that can be exploited when we * run as root. By making sure that TERMINFO is set to something this bug * is avoided. This code thanks to Casper */ if ((p = getenv("TERMINFO")) == NULL || *p == '\0') { putenv("TERMINFO=/usr/share/lib/terminfo/"); } /* perform the kvm_open - suppress error here */ if ((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL)) == NULL) { /* save the error message: we may need it later */ p = strerror(errno); } #ifdef DEBUG dprintf("kvm_open: fd %d\n", kd); #endif /* DEBUG */ /* * turn off super group/user privs - but beware; we might want the privs * back later and we still have a fd to /dev/kmem open so we can't use * setgid()/setuid() as that would allow a debugger to attach to this * process. CD */ setegid(getgid()); seteuid(getuid()); /* super user not needed for NEW_PROC */ #ifdef USE_KSTAT if ((kc = kstat_open()) == NULL) { fprintf(stderr, "Unable to open kstat.\n"); return (-1); } kcid = kc->kc_chain_id; #ifdef DEBUG dprintf("kstat_open: chain %d\n", kcid); #endif /* DEBUG */ #endif /* fill in the statics information */ statics->procstate_names = procstatenames; statics->cpustate_names = cpustatenames; statics->memory_names = memorynames; statics->order_names = ordernames; statics->order_names_io = ordernames_io; statics->flags.fullcmds = 1; statics->flags.warmup = 1; /* get boot time */ ut.ut_type = BOOT_TIME; if ((up = getutxid(&ut)) != NULL) { statics->boottime = up->ut_tv.tv_sec; } endutxent(); /* if the kvm_open succeeded, get the nlist */ if (kd) { if (kvm_nlist(kd, nlst) < 0) { perror("kvm_nlist"); return (-1); } if (check_nlist(nlst) != 0) return (-1); } #ifndef USE_KSTAT /* * if KSTAT is not available to us and we can't open /dev/kmem, this is a * serious problem. */ else { /* Print the error message here */ (void) fprintf(stderr, "kvm_open: %s\n", p); return (-1); } #endif /* 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; freemem_offset = nlst[X_FREEMEM].n_value; maxmem_offset = nlst[X_MAXMEM].n_value; #ifndef USE_KSTAT (void) getkval(nlst[X_NCPUS].n_value, (int *) (&cpucount), sizeof(cpucount), "ncpus"); cpu_offset = (unsigned long *) malloc(cpucount * sizeof(unsigned long)); for (i = offset = 0; i < cpucount; offset += sizeof(unsigned long)) { (void) getkval(nlst[X_CPU].n_value + offset, (int *) (&cpu_offset[i]), sizeof(unsigned long), nlst[X_CPU].n_name); if (cpu_offset[i] != 0) i++; } #endif /* calculate pageshift value */ i = sysconf(_SC_PAGESIZE); pageshift = 0; while ((i >>= 1) > 0) { pageshift++; } /* calculate an amount to shift to K values */ /* remember that log base 2 of 1024 is 10 (i.e.: 2^10 = 1024) */ pageshift -= 10; /* * now determine which pageshift function is appropriate for the result * (have to because x << y is undefined for y < 0) */ if (pageshift > 0) { /* this is the most likely */ p_pagetok = pagetok_left; } else if (pageshift == 0) { p_pagetok = pagetok_none; } else { p_pagetok = pagetok_right; pageshift = -pageshift; } if (!(procdir = opendir(PROCFS))) { (void) fprintf(stderr, "Unable to open %s\n", PROCFS); return (-1); } if (chdir(PROCFS)) { /* handy for later on when we're reading it */ (void) fprintf(stderr, "Unable to chdir to %s\n", PROCFS); return (-1); } /* all done! */ return (0); } char * format_header(register char *uname_field) { register char *ptr; ptr = header + UNAME_START; while (*uname_field != '\0') *ptr++ = *uname_field++; return (header); } #ifdef USE_KSTAT long kstat_data_value_l(kstat_named_t * kn) { switch (kn->data_type) { case KSTAT_DATA_INT32: return ((long) (kn->value.i32)); case KSTAT_DATA_UINT32: return ((long) (kn->value.ui32)); case KSTAT_DATA_INT64: return ((long) (kn->value.i64)); case KSTAT_DATA_UINT64: return ((long) (kn->value.ui64)); } return 0; } int kstat_safe_retrieve(kstat_t ** ksp, char *module, int instance, char *name, void *buf) { kstat_t *ks; kid_t new_kcid; int changed; #ifdef DEBUG dprintf("kstat_safe_retrieve(%08x -> %08x, %s, %d, %s, %08x)\n", ksp, *ksp, module, instance, name, buf); #endif /* DEBUG */ ks = *ksp; do { changed = 0; /* if we dont already have the kstat, retrieve it */ if (ks == NULL) { if ((ks = kstat_lookup(kc, module, instance, name)) == NULL) { return (-1); } *ksp = ks; } /* attempt to read it */ new_kcid = kstat_read(kc, ks, buf); /* * chance for an infinite loop here if kstat_read keeps returning -1 */ /* if the chain changed, update it */ if (new_kcid != kcid) { #ifdef DEBUG dprintf("kstat_safe_retrieve: chain changed to %d...updating\n", new_kcid); #endif /* DEBUG */ changed = 1; kcid = kstat_chain_update(kc); } } while (changed); return (0); } /* * int kstat_safe_namematch(int num, kstat_t *ksp, char *name, void *buf) * * Safe scan of kstat chain for names starting with "name". Matches * are copied in to "ksp", and kstat_read is called on each match using * "buf" as a buffer of length "size". The actual number of records * found is returned. Up to "num" kstats are copied in to "ksp", but * no more. If any kstat_read indicates that the chain has changed, then * the whole process is restarted. */ int kstat_safe_namematch(int num, kstat_t ** ksparg, char *name, void *buf, int size) { kstat_t *ks; kstat_t **ksp; kid_t new_kcid; int namelen; int count; int changed; char *cbuf; #ifdef DEBUG dprintf("kstat_safe_namematch(%d, %08x, %s, %08x, %d)\n", num, ksp, name, buf, size); #endif /* DEBUG */ namelen = strlen(name); do { /* initialize before the scan */ cbuf = (char *) buf; ksp = ksparg; count = 0; changed = 0; /* scan the chain for matching kstats */ for (ks = kc->kc_chain; ks != NULL; ks = ks->ks_next) { if (strncmp(ks->ks_name, name, namelen) == 0) { /* this kstat matches: save it if there is room */ if (count++ < num) { /* read the kstat */ new_kcid = kstat_read(kc, ks, cbuf); /* if the chain changed, update it */ if (new_kcid != kcid) { #ifdef DEBUG dprintf("kstat_safe_namematch: chain changed to %d...updating\n", new_kcid); #endif /* DEBUG */ changed = 1; kcid = kstat_chain_update(kc); /* there's no sense in continuing the scan */ /* so break out of the for loop */ break; } /* move to the next buffers */ cbuf += size; *ksp++ = ks; } } } } while (changed); #ifdef DEBUG dprintf("kstat_safe_namematch returns %d\n", count); #endif /* DEBUG */ return count; } static kstat_t *ks_system_misc = NULL; #endif /* USE_KSTAT */ int get_avenrun(int avenrun[3]) { #ifdef USE_KSTAT int status; kstat_named_t *kn; #ifdef DEBUG dprintf("get_avenrun(%08x)\n", avenrun); #endif /* DEBUG */ if ((status = kstat_safe_retrieve(&ks_system_misc, "unix", 0, "system_misc", NULL)) == 0) { if ((kn = kstat_data_lookup(ks_system_misc, "avenrun_1min")) != NULL) { avenrun[0] = kn->value.ui32; } if ((kn = kstat_data_lookup(ks_system_misc, "avenrun_5min")) != NULL) { avenrun[1] = kn->value.ui32; } if ((kn = kstat_data_lookup(ks_system_misc, "avenrun_15min")) != NULL) { avenrun[2] = kn->value.ui32; } } #ifdef DEBUG dprintf("get_avenrun returns %d\n", status); #endif /* DEBUG */ return (status); #else /* !USE_KSTAT */ (void) getkval(avenrun_offset, (int *) avenrun, sizeof(int[3]), "avenrun"); return 0; #endif /* USE_KSTAT */ } int get_ncpus() { #ifdef USE_KSTAT kstat_named_t *kn; int ret = -1; if ((kn = kstat_data_lookup(ks_system_misc, "ncpus")) != NULL) { ret = (int) (kn->value.ui32); } return ret; #else int ret; (void) getkval(nlst[X_NCPUS].n_value, (int *) (&ret), sizeof(ret), "ncpus"); return ret; #endif } int get_nproc() { #ifdef USE_KSTAT kstat_named_t *kn; int ret = -1; if ((kn = kstat_data_lookup(ks_system_misc, "nproc")) != NULL) { ret = (int) (kn->value.ui32); } return ret; #else int ret; (void) getkval(nproc_offset, (int *) (&ret), sizeof(ret), "nproc"); return ret; #endif } int64_t (* get_cpustats(int *cnt, int64_t (*cp_stats)[CPUSTATES]))[CPUSTATES] { #ifdef USE_KSTAT static kstat_t **cpu_ks = NULL; static cpu_stat_t *cpu_stat = NULL; static unsigned int nelems = 0; cpu_stat_t *cpu_stat_p; int i, cpu_num; int64_t (*cp_stats_p)[CPUSTATES]; #ifdef DEBUG dprintf("get_cpustats(%d -> %d, %08x)\n", cnt, *cnt, cp_stats); #endif /* DEBUG */ while (nelems > 0 ? (cpu_num = kstat_safe_namematch(nelems, cpu_ks, "cpu_stat", cpu_stat, sizeof(cpu_stat_t))) > nelems : (cpu_num = get_ncpus()) > 0) { /* reallocate the arrays */ #ifdef DEBUG dprintf("realloc from %d to %d\n", nelems, cpu_num); #endif /* DEBUG */ nelems = cpu_num; if (cpu_ks != NULL) { free(cpu_ks); } cpu_ks = (kstat_t **) calloc(nelems, sizeof(kstat_t *)); if (cpu_stat != NULL) { free(cpu_stat); } cpu_stat = (cpu_stat_t *) malloc(nelems * sizeof(cpu_stat_t)); } /* do we have more cpus than our caller? */ if (cpu_num > *cnt) { /* yes, so realloc their array, too */ #ifdef DEBUG dprintf("realloc array from %d to %d\n", *cnt, cpu_num); #endif /* DEBUG */ *cnt = cpu_num; cp_stats = (int64_t (*)[CPUSTATES]) realloc(cp_stats, cpu_num * sizeof(int64_t) * CPUSTATES); } cpu_stat_p = cpu_stat; cp_stats_p = cp_stats; for (i = 0; i < cpu_num; i++) { #ifdef DEBUG dprintf("cpu %d %08x: idle %u, user %u, syscall %u\n", i, cpu_stat_p, cpu_stat_p->cpu_sysinfo.cpu[0], cpu_stat_p->cpu_sysinfo.cpu[1], cpu_stat_p->cpu_sysinfo.syscall); #endif /* DEBUG */ (*cp_stats_p)[CPU_IDLE] = (int64_t) cpu_stat_p->cpu_sysinfo.cpu[CPU_IDLE]; (*cp_stats_p)[CPU_USER] = (int64_t) cpu_stat_p->cpu_sysinfo.cpu[CPU_USER]; (*cp_stats_p)[CPU_KERNEL] = (int64_t) cpu_stat_p->cpu_sysinfo.cpu[CPU_KERNEL]; (*cp_stats_p)[CPUSTATE_IOWAIT] = (int64_t) cpu_stat_p->cpu_sysinfo.wait[W_IO] + cpu_stat_p->cpu_sysinfo.wait[W_PIO]; (*cp_stats_p)[CPUSTATE_SWAP] = (int64_t) cpu_stat_p->cpu_sysinfo.wait[W_SWAP]; cp_stats_p++; cpu_stat_p++; } cpucount = cpu_num; #ifdef DEBUG dprintf("get_cpustats sees %d cpus and returns %08x\n", cpucount, cp_stats); #endif /* DEBUG */ return (cp_stats); #else /* !USE_KSTAT */ int i; struct cpu cpu; int64_t (*cp_stats_p)[CPUSTATES]; /* do we have more cpus than our caller? */ if (cpucount > *cnt) { /* yes, so realloc their array, too */ #ifdef DEBUG dprintf("realloc array from %d to %d\n", *cnt, cpucount); #endif /* DEBUG */ *cnt = cpucount; cp_stats = (int64_t (*)[CPUSTATES]) realloc(cp_stats, cpucount * sizeof(int64_t) * CPUSTATES); } cp_stats_p = cp_stats; for (i = 0; i < cpucount; i++) { if (cpu_offset[i] != 0) { /* get struct cpu for this processor */ (void) getkval(cpu_offset[i], (int *) (&cpu), sizeof(struct cpu), "cpu"); (*cp_stats_p)[CPU_IDLE] = (int64_t) cpu.cpu_stat.cpu_sysinfo.cpu[CPU_IDLE]; (*cp_stats_p)[CPU_USER] = (int64_t) cpu.cpu_stat.cpu_sysinfo.cpu[CPU_USER]; (*cp_stats_p)[CPU_KERNEL] = (int64_t) cpu.cpu_stat.cpu_sysinfo.cpu[CPU_KERNEL]; (*cp_stats_p)[CPUSTATE_IOWAIT] = (int64_t) cpu.cpu_stat.cpu_sysinfo.wait[W_IO] + cpu.cpu_stat.cpu_sysinfo.wait[W_PIO]; (*cp_stats_p)[CPUSTATE_SWAP] = (int64_t) cpu.cpu_stat.cpu_sysinfo.wait[W_SWAP]; cp_stats_p++; } } return (cp_stats); #endif /* USE_KSTAT */ } /* * void get_meminfo(long *total, long *fr) * * Get information about the system's physical memory. Pass back values * for total available and amount of memory that is free (in kilobytes). * It returns 0 on success and -1 on any kind of failure. */ int get_meminfo(long *total, long *fr) { long freemem; static kstat_t *ks = NULL; kstat_named_t *kn; /* total comes from sysconf */ *total = pagetok(sysconf(_SC_PHYS_PAGES)); /* free comes from the kernel's freemem or from kstat */ /* * prefer kmem for this because kstat unix:0:system_pages can be slow on * systems with lots of memory */ if (kd) { (void) getkval(freemem_offset, (int *) (&freemem), sizeof(freemem), "freemem"); } else { #ifdef USE_KSTAT /* only need to grab kstat chain once */ if (ks == NULL) { ks = kstat_lookup(kc, "unix", 0, "system_pages"); } if (ks != NULL && kstat_read(kc, ks, 0) != -1 && (kn = kstat_data_lookup(ks, "freemem")) != NULL) { freemem = kstat_data_value_l(kn); } else { freemem = -1; } #else freemem = -1; #endif } *fr = freemem == -1 ? -1 : pagetok(freemem); return (0); } /* * void get_swapinfo(long *total, long *fr) * * Get information about the system's swap. Pass back values for * total swap available and amount of swap that is free (in kilobytes). * It returns 0 on success and -1 on any kind of failure. */ int get_swapinfo(long *total, long *fr) { register int cnt, i; register long t, f; struct swaptable *swt; struct swapent *ste; static char path[256]; /* preset values to 0 just in case we have to return early */ *total = 0; *fr = 0; /* get total number of swap entries */ if ((cnt = swapctl(SC_GETNSWP, 0)) == -1) { return (-1); } /* allocate enough space to hold count + n swapents */ swt = (struct swaptable *) malloc(sizeof(int) + cnt * sizeof(struct swapent)); if (swt == NULL) { return (-1); } swt->swt_n = cnt; /* * fill in ste_path pointers: we don't care about the paths, so we point * them all to the same buffer */ ste = &(swt->swt_ent[0]); i = cnt; while (--i >= 0) { ste++->ste_path = path; } /* grab all swap info */ if (swapctl(SC_LIST, swt) == -1) { return (-1); } /* walk thru the structs and sum up the fields */ t = f = 0; ste = &(swt->swt_ent[0]); i = cnt; while (--i >= 0) { /* dont count slots being deleted */ if (!(ste->ste_flags & ST_INDEL) && !(ste->ste_flags & ST_DOINGDEL)) { t += ste->ste_pages; f += ste->ste_free; } ste++; } /* fill in the results */ *total = pagetok(t); *fr = pagetok(f); free(swt); /* good to go */ return (0); } void get_system_info(struct system_info * si) { int avenrun[3]; static int64_t cp_time[CPUSTATES]; static int64_t cp_old[CPUSTATES]; static int64_t cp_diff[CPUSTATES]; static int64_t (*cp_stats)[CPUSTATES] = NULL; static int cpus; register int j, i; /* get important information */ get_avenrun(avenrun); /* get the cpu statistics arrays */ cp_stats = get_cpustats(&cpus, cp_stats); /* zero the cp_time array */ for (j = 0; j < CPUSTATES; j++) { cp_time[j] = 0; } /* sum stats in to a single array */ for (i = 0; i < cpus; i++) { for (j = 0; j < CPUSTATES; j++) { cp_time[j] += cp_stats[i][j]; } } /* convert cp_time counts to percentages */ (void) percentages(CPUSTATES, cpu_states, cp_time, cp_old, cp_diff); /* get mpid -- process id of last process */ if (kd) (void) getkval(mpid_offset, &(si->last_pid), sizeof(si->last_pid), "mpid"); else si->last_pid = -1; /* convert load averages to doubles */ for (i = 0; i < 3; i++) si->load_avg[i] = loaddouble(avenrun[i]); /* get physical memory data */ if (get_meminfo(&(memory_stats[MEMORY_TOTALMEM]), &(memory_stats[MEMORY_FREEMEM])) == -1) { memory_stats[MEMORY_TOTALMEM] = memory_stats[MEMORY_FREEMEM] = -1; } /* get swap data */ if (get_swapinfo(&(memory_stats[MEMORY_TOTALSWAP]), &(memory_stats[MEMORY_FREESWAP])) == -1) { memory_stats[MEMORY_TOTALSWAP] = memory_stats[MEMORY_FREESWAP] = -1; } /* set arrays and strings */ si->cpustates = cpu_states; si->memory = memory_stats; #ifdef DEBUG dprintf("get_system_info returns\n"); #endif /* DEBUG */ } static struct handle handle; static int show_fullcmd; caddr_t get_process_info( struct system_info * si, struct process_select * sel, int compare_index, char *conninfo, int mode) { 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; PGconn *pgconn; PGresult *pgresult = NULL; /* allocate enough space for twice our current needs */ nproc = 20; pgconn = connect_to_db(conninfo); if (pgconn != NULL) { pgresult = pg_processes(pgconn); nproc = PQntuples(pgresult); if (nproc > maxprocs) { reallocproc(2 * nproc); } /* read all the proc structures */ getptable(pbase, pgresult); PQclear(pgresult); PQfinish(pgconn); } else nproc = 0; /* 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_uid = sel->uid != -1; show_fullcmd = sel->fullcmd; /* count up process states and get pointers to interesting procs */ total_procs = 0; active_procs = 0; (void) memset(process_states, 0, sizeof(process_states)); prefp = pref; for (pp = pbase, i = 0; i < nproc; i++, pp = (struct prpsinfo *) ((char *) pp + PRPSINFOSIZE)) { /* * 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[(int) pp->pr_state]++; if ((!ZOMBIE(pp)) && (show_idle || percent_cpu(pp) || (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 */ qsort((char *) pref, active_procs, sizeof(struct prpsinfo *), proc_compares[compare_index]); /* 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[MAX_COLS]; /* static area where result is built */ char * format_next_io(caddr_t handle, char *(*get_userid) ()) { fmt[0] = '\0'; return (fmt); } char * format_next_process( caddr_t handle, char *(*get_userid) ()) { register struct prpsinfo *pp; struct handle *hp; register long cputime; register double pctcpu; char sb[10]; /* 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) / cpucount; if (pp->pr_state == SONPROC && cpucount > 1) sprintf(sb, "cpu/%-2d", pp->pr_onpro); /* XXX large #s may overflow * colums */ else *sb = '\0'; /* format this entry */ #ifdef HAVE_SNPRINTF snprintf(fmt, sizeof(fmt), #else sprintf(fmt, #endif Proc_format, (int) pp->pr_pid, (*get_userid) (pp->pr_uid), (u_short) pp->pr_fill < 999 ? (u_short) pp->pr_fill : 999, pp->pr_pri, pp->pr_nice - NZERO, format_k(SIZE_K(pp)), format_k(RSS_K(pp)), *sb ? sb : state_abbrev[(int) pp->pr_state], format_time(cputime), format_percent(pctcpu), printable(show_fullcmd ? pp->pr_psargs : 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(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); } /* comparison routines for qsort */ /* * There are currently four possible comparison routines. main selects * one of these by indexing in to the array proc_compares. * * Possible keys are defined as macros below. Currently these keys are * defined: percent cpu, cpu ticks, process 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. */ /* First, the possible comparison keys. These are defined in such a way that they can be merely listed in the source code to define the actual desired ordering. */ #define ORDERKEY_PCTCPU if (dresult = percent_cpu (p2) - percent_cpu (p1),\ (result = dresult > 0.0 ? 1 : dresult < 0.0 ? -1 : 0) == 0) #define ORDERKEY_CPTICKS if ((result = p2->pr_time.tv_sec - p1->pr_time.tv_sec) == 0) #define ORDERKEY_STATE if ((result = \ (long) (sorted_state[(int) p2->pr_state] - \ sorted_state[(int) p1->pr_state])) == 0) #define ORDERKEY_PRIO if ((result = p2->pr_oldpri - p1->pr_oldpri) == 0) #define ORDERKEY_RSSIZE if ((result = p2->pr_rssize - p1->pr_rssize) == 0) #define ORDERKEY_MEM if ((result = (p2->pr_size - p1->pr_size)) == 0) /* Now the array that maps process state to a weight */ 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) */ }; /* compare_cpu - the comparison function for sorting by cpu percentage */ int compare_cpu( struct prpsinfo ** pp1, struct prpsinfo ** pp2) { register struct prpsinfo *p1; register struct prpsinfo *p2; register long result; double dresult; /* remove one level of indirection */ p1 = *pp1; p2 = *pp2; ORDERKEY_PCTCPU ORDERKEY_CPTICKS ORDERKEY_STATE ORDERKEY_PRIO ORDERKEY_RSSIZE ORDERKEY_MEM ; return (result); } /* compare_size - the comparison function for sorting by total memory usage */ int compare_size( struct prpsinfo ** pp1, struct prpsinfo ** pp2) { register struct prpsinfo *p1; register struct prpsinfo *p2; register long result; double dresult; /* remove one level of indirection */ p1 = *pp1; p2 = *pp2; ORDERKEY_MEM ORDERKEY_RSSIZE ORDERKEY_PCTCPU ORDERKEY_CPTICKS ORDERKEY_STATE ORDERKEY_PRIO ; return (result); } /* compare_res - the comparison function for sorting by resident set size */ int compare_res( struct prpsinfo ** pp1, struct prpsinfo ** pp2) { register struct prpsinfo *p1; register struct prpsinfo *p2; register long result; double dresult; /* remove one level of indirection */ p1 = *pp1; p2 = *pp2; ORDERKEY_RSSIZE ORDERKEY_MEM ORDERKEY_PCTCPU ORDERKEY_CPTICKS ORDERKEY_STATE ORDERKEY_PRIO ; return (result); } /* compare_time - the comparison function for sorting by total cpu time */ int compare_time( struct prpsinfo ** pp1, struct prpsinfo ** pp2) { register struct prpsinfo *p1; register struct prpsinfo *p2; register long result; double dresult; /* remove one level of indirection */ p1 = *pp1; p2 = *pp2; ORDERKEY_CPTICKS ORDERKEY_PCTCPU ORDERKEY_STATE ORDERKEY_PRIO ORDERKEY_MEM ORDERKEY_RSSIZE ; 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 */ void getptable(struct prpsinfo * baseptr, PGresult * pgresult) { struct prpsinfo *currproc; /* pointer to current proc structure */ #ifndef USE_NEW_PROC struct prstatus prstatus; /* for additional information */ #endif int numprocs = 0; int i; struct oldproc *op; static struct timeval lasttime = {0, 0}; struct timeval thistime; double timediff; double alpha, beta; struct oldproc *endbase; gettimeofday(&thistime, NULL); /* * To avoid divides, we keep times in nanoseconds. This is scaled by 1e7 * rather than 1e9 so that when we divide we get percent. */ if (lasttime.tv_sec) timediff = ((double) thistime.tv_sec * 1.0e7 + ((double) thistime.tv_usec * 10.0)) - ((double) lasttime.tv_sec * 1.0e7 + ((double) lasttime.tv_usec * 10.0)); else timediff = 1.0e7; /* * constants for exponential average. avg = alpha * new + beta * avg The * goal is 50% decay in 30 sec. However if the sample period is greater * than 30 sec, there's not a lot we can do. */ if (timediff < 30.0e7) { alpha = 0.5 * (timediff / 30.0e7); beta = 1.0 - alpha; } else { alpha = 0.5; beta = 0.5; } endbase = oldbase + oldprocs; currproc = baseptr; /* before reading /proc files, turn on root privs */ /* (we don't care if this fails since it will be caught later) */ #ifndef USE_NEW_PROC seteuid(0); #endif for (i = 0; i < nproc; i++) { int fd; char buf[30]; char *procpid = PQgetvalue(pgresult, i, 0); #ifdef USE_NEW_PROC #ifdef HAVE_SNPRINTF snprintf(buf, sizeof(buf), "%s/%s/psinfo", PROCFS, procpid); #else sprintf(buf, "%s/psinfo", direntp->d_name); #endif if ((fd = open(buf, O_RDONLY)) < 0) continue; if (read(fd, currproc, sizeof(psinfo_t)) != sizeof(psinfo_t)) { (void) close(fd); continue; } #else if ((fd = open(direntp->d_name, O_RDONLY)) < 0) continue; if (ioctl(fd, PIOCPSINFO, currproc) < 0) { (void) close(fd); continue; } if (ioctl(fd, PIOCSTATUS, &prstatus) < 0) { /* not a show stopper -- just fill in the needed values */ currproc->pr_fill = 0; currproc->pr_onpro = 0; } else { /* copy over the values we need from prstatus */ currproc->pr_fill = (short) prstatus.pr_nlwp; currproc->pr_onpro = prstatus.pr_processor; } #endif /* * SVr4 doesn't keep track of CPU% in the kernel, so we have to do our * own. See if we've heard of this process before. If so, compute % * based on CPU since last time. NOTE: Solaris 2.4 and higher do * maintain CPU% in prpsinfo. */ op = oldbase + HASH(currproc->pr_pid); while (1) { if (op->oldpid == -1) /* not there */ break; if (op->oldpid == currproc->pr_pid) { /* found old data */ #ifndef PROC_HAS_PCTCPU percent_cpu(currproc) = ((currproc->pr_time.tv_sec * 1.0e9 + currproc->pr_time.tv_nsec) - op->oldtime) / timediff; #endif weighted_cpu(currproc) = op->oldpct * beta + percent_cpu(currproc) * alpha; break; } op++; /* try next entry in hash table */ if (op == endbase) /* table wrapped around */ op = oldbase; } /* Otherwise, it's new, so use all of its CPU time */ if (op->oldpid == -1) { #ifdef PROC_HAS_PCTCPU weighted_cpu(currproc) = percent_cpu(currproc); #else if (lasttime.tv_sec) { percent_cpu(currproc) = (currproc->pr_time.tv_sec * 1.0e9 + currproc->pr_time.tv_nsec) / timediff; weighted_cpu(currproc) = percent_cpu(currproc); } else { /* first screen -- no difference is possible */ percent_cpu(currproc) = 0.0; weighted_cpu(currproc) = 0.0; } #endif } numprocs++; currproc = (struct prpsinfo *) ((char *) currproc + PRPSINFOSIZE); (void) close(fd); /* Atypical place for growth */ if (numprocs >= maxprocs) { reallocproc(2 * numprocs); currproc = (struct prpsinfo *) ((char *) baseptr + PRPSINFOSIZE * numprocs); } } #ifndef USE_NEW_PROC /* turn off root privs */ seteuid(getuid()); #endif if (nproc != numprocs) nproc = numprocs; /* * Save current CPU time for next time around For the moment recreate the * hash table each time, as the code is easier that way. */ oldprocs = 2 * nproc; endbase = oldbase + oldprocs; for (op = oldbase; op < endbase; op++) op->oldpid = -1; for (i = 0, currproc = baseptr; i < nproc; i++, currproc = (struct prpsinfo *) ((char *) currproc + PRPSINFOSIZE)) { /* find an empty spot */ op = oldbase + HASH(currproc->pr_pid); while (1) { if (op->oldpid == -1) break; op++; if (op == endbase) op = oldbase; } op->oldpid = currproc->pr_pid; op->oldtime = (currproc->pr_time.tv_sec * 1.0e9 + currproc->pr_time.tv_nsec); op->oldpct = weighted_cpu(currproc); } lasttime = thistime; } /* * 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. */ uid_t proc_owner(pid_t pid) { register struct prpsinfo *p; int i; for (i = 0, p = pbase; i < nproc; i++, p = (struct prpsinfo *) ((char *) p + PRPSINFOSIZE)) { if (p->pr_pid == (pid_t) pid) return ((int) p->pr_uid); } return (-1); } /* older revisions don't supply a setpriority */ #if (OSREV < 55) int setpriority(int dummy, int who, int niceval) { int scale; int prio; pcinfo_t pcinfo; pcparms_t pcparms; tsparms_t *tsparms; strcpy(pcinfo.pc_clname, "TS"); if (priocntl(0, 0, PC_GETCID, (caddr_t) & pcinfo) == -1) return (-1); prio = niceval; if (prio > PRIO_MAX) prio = PRIO_MAX; else if (prio < PRIO_MIN) prio = PRIO_MIN; tsparms = (tsparms_t *) pcparms.pc_clparms; scale = ((tsinfo_t *) pcinfo.pc_clinfo)->ts_maxupri; tsparms->ts_uprilim = tsparms->ts_upri = -(scale * prio) / 20; pcparms.pc_cid = pcinfo.pc_cid; if (priocntl(P_PID, who, PC_SETPARMS, (caddr_t) & pcparms) == -1) return (-1); return (0); } #endif /* * When we reach a proc limit, we need to realloc the stuff. */ static void reallocproc(int n) { int bytes; struct oldproc *op, *endbase; if (n < maxprocs) return; #ifdef DEBUG dprintf("reallocproc(%d): reallocating from %d\n", n, maxprocs); #endif /* DEBUG */ maxprocs = n; /* allocate space for proc structure array and array of pointers */ bytes = maxprocs * PRPSINFOSIZE; pbase = (struct prpsinfo *) realloc(pbase, bytes); pref = (struct prpsinfo **) realloc(pref, maxprocs * sizeof(struct prpsinfo *)); oldbase = (struct oldproc *) realloc(oldbase, 2 * maxprocs * sizeof(struct oldproc)); /* Just in case ... */ if (pbase == (struct prpsinfo *) NULL || pref == (struct prpsinfo **) NULL || oldbase == (struct oldproc *) NULL) { fprintf(stderr, "%s: can't allocate sufficient memory\n", myname); quit(1); } /* * We're growing from 0 to some number, only then we need to init the * oldproc stuff */ if (!oldprocs) { oldprocs = 2 * maxprocs; endbase = oldbase + oldprocs; for (op = oldbase; op < endbase; op++) op->oldpid = -1; } } 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)); }