/* * 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. */ /* * m_macosx.c * * AUTHOR: Andrew S. Townley * based on m_bsd44.c and m_next32.c * by Christos Zoulas and Tim Pugh * CREATED: Tue Aug 11 01:51:35 CDT 1998 * SYNOPSIS: MacOS X Server (Rhapsody Developer Release 2) * DESCRIPTION: * MacOS X Server (Rhapsody Developer Release 2) * * CFLAGS: -DHAVE_STRERROR * TERMCAP: none * MATH: none */ /* * normal stuff */ #include "config.h" #include #include #include #include "os.h" #include "top.h" #include "machine.h" #include "utils.h" /* * MacOS kernel stuff */ #include #include #include #include #include #include #include #include #define VMUNIX "/mach_kernel" #define MEM "/dev/mem" #define SWAP NULL #define NUM_AVERAGES 3 #define LOG1024 10 #define PP(pp, field) ((pp)->kp_proc . field) #define EP(pp, field) ((pp)->kp_eproc . field) #define VP(pp, field) ((pp)->kp_eproc.e_vm . field) #define MPP(mp, field) (PP((mp)->kproc, field)) #define MEP(mp, field) (EP((mp)->kproc, field)) #define MVP(mp, field) (VP((mp)->kproc, field)) #define TP(mp, field) ((mp)->task_info . field) #define RP(mp, field) ((mp)->thread_summary . field) /* define what weighted cpu is */ #define weighted_cpu(pct, s) (s == 0 ? 0.0 : \ ((pct) / (1.0 - exp(s * logcpu)))) /* what we consider to be process size: */ #ifdef notdef #define PROCSIZE(pp) (VP((pp), vm_tsize) + VP((pp), vm_dsize) + VP((pp), vm_ssize)) #endif #define PROCSIZE(pp) (EP(pp, e_xsize)) #define TASKSIZE(t) (TP(t, virtual_size) + TP(t, resident_size)) /* what we consider to be resident set size: */ #ifdef notdef #define RSSIZE(pp) (MVP((pp), vm_rssize)) #endif #define RSSIZE(pp) (MEP((pp), e_xrssize)) #define pctdouble(p) ((double)(p) / FSCALE) /* * globals */ static kvm_t *kd = NULL; static int nproc; static int onproc = -1; static int pref_len; static int maxmem; static char fmt[MAX_COLS]; static double logcpu = 1.0; /* process array stuff */ static struct kinfo_proc *kproc_list = NULL; static struct macos_proc *proc_list = NULL; static struct macos_proc **proc_ref = NULL; static int process_states[7]; static struct handle handle; /* * The mach information hopefully will not be necessary * when the kvm_* interfaces are supported completely. * * Since we're only concerned with task and thread info * for 'interesting' processes, we're going to only allocate * as many task and thread structures as needed. */ static struct task_basic_info *task_list = NULL; /* memory statistics */ static int pageshift = 0; static int pagesize = 0; #define pagetok(size) ((size) << pageshift) static int swappgsin = -1; static int swappgsout = -1; static vm_statistics_data_t vm_stats; static long memory_stats[7]; /* CPU state percentages */ host_cpu_load_info_data_t cpuload; static long cp_time[CPU_STATE_MAX]; static long cp_old[CPU_STATE_MAX]; static long cp_diff[CPU_STATE_MAX]; static int cpu_states[CPU_STATE_MAX]; /* * types */ typedef long pctcpu; //struct statics //{ // char **procstate_names; // char **cpustate_names; // char **memory_names; // char **order_names; //}; // //struct system_info //{ // int last_pid; // double load_avg[NUM_AVERAGES]; // int p_total; /* total # of processes */ // int p_active; /* number processes considered active */ // int *procstates; // int *cpustates; // int *memory; //}; // //struct process_select //{ // int idle; /* show idle processes */ // int system; /* show system processes */ // int uid; /* show only this uid (unless -1) */ // char *command; /* only this command (unless NULL) */ //}; /* * We need to declare a hybrid structure which will store all * of the stuff we care about for each process. */ struct macos_proc { struct kinfo_proc *kproc; task_t the_task; struct task_basic_info task_info; unsigned int thread_count; struct thread_basic_info thread_summary; }; struct handle { struct macos_proc **next_proc; int remaining; }; static char header[] = " PID X PRI THRD SIZE RES STATE TIME MEM CPU COMMAND"; /* 0123456 -- field to fill in starts at header+6 */ #define UNAME_START 6 #define Proc_format \ "%5d %-8.8s %3d %4d %5s %5s %-5s %6s %5.2f%% %5.2f%% %.16s" int proc_compare(const void *, const void *); /* * puke() * * This function is used to report errors to stderr. */ static void puke(const char* fmt, ...) { va_list args; va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); fputc('\n', stderr); fflush(stderr); } /* * kread() * * This function is a wrapper for the kvm_read() function * with the addition of a message parameter per kvm_open(). * * All other behavior is per kvm_read except the error reporting. */ static ssize_t kread(u_long addr, void *buf, size_t nbytes, const char *errstr) { ssize_t s = 0; s = kvm_read(kd, addr, buf, nbytes); if(s == -1) { puke("error: kvm_read() failed for '%s' (%s)\n", errstr, strerror(errno)); } return s; } /* * prototypes for functions which top needs */ char *printable(); /* * definitions for offsets */ #define X_NPROC 0 #define X_HZ 1 #define X_MAXMEM 2 #define NLIST_LAST 3 static struct nlist nlst[] = { { "_maxproc" }, /* 0 *** maximum processes */ { "_hz" }, /* 1 */ { "_mem_size" }, /* 2 */ { 0 } }; static char *procstates[] = { "", " starting, ", " running, ", " sleeping, ", " stopped, ", " zombie, ", " swapped ", NULL }; static char *cpustates[] = { "user", "system", "idle", "nice", NULL }; static char *state_abbrev[] = { "", "start", "run\0\0\0", "sleep", "stop", "zomb" }; static char *mach_state[] = { "", "R", "T", "S", "U", "H" }; static char *thread_state[] = { "", "run\0\0\0", "stop", "wait", "uwait", "halted", }; static char *flags_state[] = { "", "W", "I" }; static char *memnames[] = { "K Tot, ", "K Free, ", "K Act, ", "K Inact, ", "K Wired, ", "K in, ", "K out ", NULL }; /* * format_header() * * This function is used to add the username into the * header information. */ char *format_header(register char *uname_field) { register char *ptr; ptr = header + UNAME_START; while(*uname_field != '\0') *ptr++ = *uname_field++; return(header); } /* * format_next_process() * * This function actuall is responsible for the formatting of * each row which is displayed. */ char *format_next_process(caddr_t handle, char *(*getuserid)()) { register struct macos_proc *pp; register long cputime; register double pct; register int vsize; register int rsize; struct handle *hp; /* * we need to keep track of the next proc structure. */ hp = (struct handle*)handle; pp = *(hp->next_proc++); hp->remaining--; /* * get the process structure and take care of the cputime */ if((MPP(pp, p_flag) & P_INMEM) == 0) { /* we want to print swapped processes as */ char *comm = MPP(pp, p_comm); #define COMSIZ sizeof(MPP(pp, p_comm)) char buf[COMSIZ]; strncpy(buf, comm, COMSIZ); comm[0] = '<'; strncpy(&comm[1], buf, COMSIZ - 2); comm[COMSIZ - 2] = '\0'; strncat(comm, ">", COMSIZ - 1); comm[COMSIZ - 1] = '\0'; } /* * count the cpu time, but ignore the interrupts * * At the present time (DR2 8/1998), MacOS X doesn't * correctly report this information through the * kinfo_proc structure. We need to get it from the * task threads. * * cputime = PP(pp, p_rtime).tv_sec; */ cputime = RP(pp, user_time).seconds + RP(pp, system_time).seconds; /* * calculate the base cpu percentages * * Again, at the present time, MacOS X doesn't report * this information through the kinfo_proc. We need * to talk to the threads. */ // pct = pctdouble(PP(pp, p_pctcpu)); pct = (double)(RP(pp, cpu_usage))/TH_USAGE_SCALE; /* * format the entry */ /* * In the final version, I would expect this to work correctly, * but it seems that not all of the fields in the proc * structure are being used. * * For now, we'll attempt to get some of the things we need * from the mach task info. */ sprintf(fmt, Proc_format, MPP(pp, p_pid), (*getuserid)(MEP(pp, e_pcred.p_ruid)), // TP(pp, base_priority), 0, pp->thread_count, format_k(TASKSIZE(pp) / 1024), format_k(pagetok(RSSIZE(pp))), state_abbrev[(u_char)MPP(pp, p_stat)], format_time(cputime), 100.0 * TP(pp, resident_size) / maxmem, // 100.0 * weighted_cpu(pct, (RP(pp, user_time).seconds + RP(pp, system_time).seconds)), 100.0 * pct, printable(MPP(pp, p_comm))); return(fmt); } /* * get_process_info() * * This function returns information about the processes * on the system. */ caddr_t get_process_info(struct system_info *si, struct process_select *sel, int x) { register int i; register int total_procs; register int active_procs; register struct macos_proc **prefp; register struct macos_proc *pp; register struct kinfo_proc *pp2; register struct kinfo_proc **prefp2; register struct thread_basic_info *thread; /* * these are copied out of sel for speed */ int show_idle; int show_system; int show_uid; int show_command; kproc_list = kvm_getprocs(kd, KERN_PROC_ALL, 0, &nproc); if(nproc > onproc) { proc_list = (struct macos_proc*)realloc(proc_list, sizeof(struct macos_proc) * nproc); proc_ref = (struct macos_proc **)realloc(proc_ref, sizeof(struct macos_proc *) * (onproc = nproc)); } if(proc_ref == NULL || proc_list == NULL || kproc_list == NULL) { puke("error: out of memory (%s)", strerror(errno)); return(NULL); } /* * now, our task is to build the array of information we * need to function correctly. This involves setting a pointer * to each real kinfo_proc structure returned by kvm_getprocs() * in addition to getting the mach information for each of * those processes. */ for(pp2 = kproc_list, i = 0; i < nproc; pp2++, i++) { kern_return_t rc; u_int info_count = TASK_BASIC_INFO_COUNT; /* * first, we set the pointer to the reference in * the kproc list. */ proc_list[i].kproc = pp2; /* * then, we load all of the task info for the process */ if(PP(pp2, p_stat) != SZOMB) { rc = task_for_pid(mach_task_self(), PP(pp2, p_pid), &(proc_list[i].the_task)); if(rc != KERN_SUCCESS) { puke("error: get task info for pid %d failed with rc = %d", PP(pp2, p_pid), rc); } /* * load the task information */ rc = task_info(proc_list[i].the_task, TASK_BASIC_INFO, (task_info_t)&(proc_list[i].task_info), &info_count); if(rc != KERN_SUCCESS) { puke("error: couldn't get task info (%s); rc = %d", strerror(errno), rc); } /* * load the thread summary information */ load_thread_info(&proc_list[i]); } } /* 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; show_command = sel->command != NULL; /* count up process states and get pointers to interesting procs */ total_procs = 0; active_procs = 0; memset((char *)process_states, 0, sizeof(process_states)); prefp = proc_ref; for(pp = proc_list, i = 0; i < nproc; pp++, i++) { /* * Place pointers to each valid proc structure in * proc_ref[]. Process slots that are actually in use * have a non-zero status field. Processes with * P_SYSTEM set are system processes---these get * ignored unless show_sysprocs is set. */ if(MPP(pp, p_stat) != 0 && (show_system || ((MPP(pp, p_flag) & P_SYSTEM) == 0))) { total_procs++; process_states[(unsigned char) MPP(pp, p_stat)]++; if((MPP(pp, p_stat) != SZOMB) && (show_idle || (MPP(pp, p_pctcpu) != 0) || (MPP(pp, p_stat) == SRUN)) && (!show_uid || MEP(pp, e_pcred.p_ruid) == (uid_t)sel->uid)) { *prefp++ = pp; active_procs++; } } } /* * if requested, sort the "interesting" processes */ qsort((char *)proc_ref, active_procs, sizeof(struct macos_proc *), proc_compare); /* remember active and total counts */ si->p_total = total_procs; si->p_active = pref_len = active_procs; /* pass back a handle */ handle.next_proc = proc_ref; handle.remaining = active_procs; return((caddr_t)&handle); } /* * get_system_info() * * This function is responsible for geting the periodic * system information snapshot. */ void get_system_info(struct system_info *si) { register long total; register int i; unsigned int count = HOST_CPU_LOAD_INFO_COUNT; if (host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, (host_info_t)&cpuload, &count) == KERN_SUCCESS) { for (i = 0; i < CPU_STATE_MAX; i++) { cp_time[i] = cpuload.cpu_ticks[i]; } } #ifdef MAX_VERBOSE /* * print out the entries */ for(i = 0; i < CPU_STATE_MAX; i++) printf("cp_time[%d] = %d\n", i, cp_time[i]); fflush(stdout); #endif /* MAX_VERBOSE */ /* * get the load averages */ if(kvm_getloadavg(kd, si->load_avg, NUM_AVERAGES) == -1) { puke("error: kvm_getloadavg() failed (%s)", strerror(errno)); return; } #ifdef MAX_VERBOSE printf("%-30s%03.2f, %03.2f, %03.2f\n", "load averages:", si->load_avg[0], si->load_avg[1], si->load_avg[2]); #endif /* MAX_VERBOSE */ total = percentages(CPU_STATE_MAX, cpu_states, cp_time, cp_old, cp_diff); /* * get the memory statistics */ { kern_return_t status; count = HOST_VM_INFO_COUNT; status = host_statistics(mach_host_self(), HOST_VM_INFO, (host_info_t)&vm_stats, &count); if(status != KERN_SUCCESS) { puke("error: vm_statistics() failed (%s)", strerror(errno)); return; } /* * we already have the total memory, we just need * to get it in the right format. */ memory_stats[0] = pagetok(maxmem / pagesize); memory_stats[1] = pagetok(vm_stats.free_count); memory_stats[2] = pagetok(vm_stats.active_count); memory_stats[3] = pagetok(vm_stats.inactive_count); memory_stats[4] = pagetok(vm_stats.wire_count); if(swappgsin < 0) { memory_stats[5] = 1; memory_stats[6] = 1; } else { memory_stats[5] = pagetok(((vm_stats.pageins - swappgsin))); memory_stats[6] = pagetok(((vm_stats.pageouts - swappgsout))); } swappgsin = vm_stats.pageins; swappgsout = vm_stats.pageouts; } si->cpustates = cpu_states; si->memory = memory_stats; si->last_pid = -1; return; } /* * machine_init() * * This function is responsible for filling in the values of the * statics structure. */ int machine_init(struct statics *stat) { register int rc = 0; register int i = 0; size_t size; size = sizeof(maxmem); sysctlbyname("hw.physmem", &maxmem, &size, NULL, 0); size = sizeof(nproc); sysctlbyname("kern.maxproc", &nproc, &size, NULL, 0); #ifdef MAX_VERBOSE printf("%-30s%10d\n", "total system memory:", maxmem); #endif /* MAX_VERBOSE */ /* * calculate the pageshift from the system page size */ pagesize = getpagesize(); pageshift = 0; while((pagesize >>= 1) > 0) pageshift++; pageshift -= LOG1024; /* * fill in the statics information */ stat->procstate_names = procstates; stat->cpustate_names = cpustates; stat->memory_names = memnames; if ((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, "kvm_open")) == NULL) return -1; return(0); } /* 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 */ 1, /* ABANDONED (WAIT) */ 6, /* run */ 5, /* start */ 2, /* zombie */ 4 /* stop */ }; int proc_compare(const void *pp1, const void *pp2) { register struct macos_proc *p1; register struct macos_proc *p2; register int result; register pctcpu lresult; /* remove one level of indirection */ p1 = *(struct macos_proc **) pp1; p2 = *(struct macos_proc **) pp2; /* compare percent cpu (pctcpu) */ if ((lresult = RP(p2, cpu_usage) - RP(p1, cpu_usage)) == 0) { /* use cpticks to break the tie */ if ((result = MPP(p2, p_cpticks) - MPP(p1, p_cpticks)) == 0) { /* use process state to break the tie */ if ((result = sorted_state[(unsigned char) MPP(p2, p_stat)] - sorted_state[(unsigned char) MPP(p1, p_stat)]) == 0) { /* use priority to break the tie */ if ((result = MPP(p2, p_priority) - MPP(p1, p_priority)) == 0) { /* use resident set size (rssize) to break the tie */ if ((result = RSSIZE(p2) - RSSIZE(p1)) == 0) { /* use total memory to break the tie */ result = PROCSIZE(p2->kproc) - PROCSIZE(p1->kproc); } } } } } else { result = lresult < 0 ? -1 : 1; } return(result); } /* * 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; { register int cnt; register struct macos_proc **prefp; register struct macos_proc *pp; prefp = proc_ref; cnt = pref_len; while (--cnt >= 0) { pp = *prefp++; if (MPP(pp, p_pid) == (pid_t)pid) { return((int)MEP(pp, e_pcred.p_ruid)); } } return(-1); } /* * load_thread_info() * * This function will attempt to load the thread summary info * for a Mach task. The task is located as part of the macos_proc * structure. * * returns the kern_return_t value of any failed call or KERN_SUCCESS * if everything works. */ int load_thread_info(struct macos_proc *mp) { register kern_return_t rc = 0; register int i = 0; register int t_utime = 0; register int t_stime = 0; register int t_cpu = 0; register int t_state = 0; register task_t the_task = mp->the_task; thread_array_t thread_list = NULL; /* * We need to load all of the threads for the * given task so we can get the performance * data from them. */ mp->thread_count = 0; rc = task_threads(the_task, &thread_list, &(mp->thread_count)); if(rc != KERN_SUCCESS) { // puke("error: unable to load threads for task (%s); rc = %d", strerror(errno), rc); return(rc); } /* * now, for each of the threads, we need to sum the stats * so we can present the whole thing to the caller. */ for(i = 0; i < mp->thread_count; i++) { struct thread_basic_info t_info; unsigned int icount = THREAD_BASIC_INFO_COUNT; kern_return_t rc = 0; rc = thread_info(thread_list[i], THREAD_BASIC_INFO, (thread_info_t)&t_info, &icount); if(rc != KERN_SUCCESS) { puke("error: unable to load thread info for task (%s); rc = %d", strerror(errno), rc); return(rc); } t_utime += t_info.user_time.seconds; t_stime += t_info.system_time.seconds; t_cpu += t_info.cpu_usage; } vm_deallocate(mach_task_self(), (vm_address_t)thread_list, sizeof(thread_array_t)*(mp->thread_count)); /* * Now, we load the values in the structure above. */ RP(mp, user_time).seconds = t_utime; RP(mp, system_time).seconds = t_stime; RP(mp, cpu_usage) = t_cpu; return(KERN_SUCCESS); }