/*
* 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 <stdio.h>
#include <stdarg.h>
#include <errno.h>
#include "os.h"
#include "top.h"
#include "machine.h"
#include "utils.h"
/*
* MacOS kernel stuff
*/
#include <kvm.h>
#include <fcntl.h>
#include <sys/dkstat.h>
#include <sys/sysctl.h>
#include <mach/message.h>
#include <mach/vm_statistics.h>
#include <mach/mach.h>
#include <mach/host_info.h>
#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 <pname> */
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);
}