blob: f66a1637b51eb875e954d29f87ad74753396cc10 [file] [log] [blame] [raw]
/*
htop - darwin/DarwinProcess.c
(C) 2015 Hisham H. Muhammad
Released under the GNU GPL, see the COPYING file
in the source distribution for its full text.
*/
#include "Process.h"
#include "DarwinProcess.h"
#include "CRT.h"
#include <stdlib.h>
#include <libproc.h>
#include <string.h>
#include <mach/mach.h>
/*{
#include "Settings.h"
#include "DarwinProcessList.h"
#include <sys/sysctl.h>
typedef struct DarwinProcess_ {
Process super;
bool is_kernel_process;
uint64_t utime;
uint64_t stime;
bool taskAccess;
} DarwinProcess;
}*/
ProcessClass DarwinProcess_class = {
.super = {
.extends = Class(Process),
.display = Process_display,
.delete = Process_delete,
.compare = Process_compare
},
.writeField = Process_writeField,
};
DarwinProcess* DarwinProcess_new(Settings* settings) {
DarwinProcess* this = xCalloc(1, sizeof(DarwinProcess));
Object_setClass(this, Class(DarwinProcess));
Process_init(&this->super, settings);
this->utime = 0;
this->stime = 0;
this->taskAccess = true;
return this;
}
void Process_delete(Object* cast) {
DarwinProcess* this = (DarwinProcess*) cast;
Process_done(&this->super);
// free platform-specific fields here
free(this);
}
bool Process_isKernelProcess(const Process* this) {
return ((const DarwinProcess *)this)->is_kernel_process;
}
bool Process_isExtraThreadProcess(const Process* this) {
(void) this;
return false;
}
void DarwinProcess_setStartTime(Process *proc, const struct extern_proc *ep, time_t now) {
proc->starttime_ctime = ep->p_starttime.tv_sec;
}
char *DarwinProcess_getCmdLine(const struct kinfo_proc *k, int* basenameOffset) {
/* This function is from the old Mac version of htop. Originally from ps? */
int mib[3], argmax, nargs, c = 0;
size_t size;
char *procargs, *sp, *np, *cp, *retval;
/* Get the maximum process arguments size. */
mib[0] = CTL_KERN;
mib[1] = KERN_ARGMAX;
size = sizeof( argmax );
if ( sysctl( mib, 2, &argmax, &size, NULL, 0 ) == -1 ) {
goto ERROR_A;
}
/* Allocate space for the arguments. */
procargs = ( char * ) xMalloc( argmax );
if ( procargs == NULL ) {
goto ERROR_A;
}
/*
* Make a sysctl() call to get the raw argument space of the process.
* The layout is documented in start.s, which is part of the Csu
* project. In summary, it looks like:
*
* /---------------\ 0x00000000
* : :
* : :
* |---------------|
* | argc |
* |---------------|
* | arg[0] |
* |---------------|
* : :
* : :
* |---------------|
* | arg[argc - 1] |
* |---------------|
* | 0 |
* |---------------|
* | env[0] |
* |---------------|
* : :
* : :
* |---------------|
* | env[n] |
* |---------------|
* | 0 |
* |---------------| <-- Beginning of data returned by sysctl() is here.
* | argc |
* |---------------|
* | exec_path |
* |:::::::::::::::|
* | |
* | String area. |
* | |
* |---------------| <-- Top of stack.
* : :
* : :
* \---------------/ 0xffffffff
*/
mib[0] = CTL_KERN;
mib[1] = KERN_PROCARGS2;
mib[2] = k->kp_proc.p_pid;
size = ( size_t ) argmax;
if ( sysctl( mib, 3, procargs, &size, NULL, 0 ) == -1 ) {
goto ERROR_B;
}
memcpy( &nargs, procargs, sizeof( nargs ) );
cp = procargs + sizeof( nargs );
/* Skip the saved exec_path. */
for ( ; cp < &procargs[size]; cp++ ) {
if ( *cp == '\0' ) {
/* End of exec_path reached. */
break;
}
}
if ( cp == &procargs[size] ) {
goto ERROR_B;
}
/* Skip trailing '\0' characters. */
for ( ; cp < &procargs[size]; cp++ ) {
if ( *cp != '\0' ) {
/* Beginning of first argument reached. */
break;
}
}
if ( cp == &procargs[size] ) {
goto ERROR_B;
}
/* Save where the argv[0] string starts. */
sp = cp;
*basenameOffset = 0;
for ( np = NULL; c < nargs && cp < &procargs[size]; cp++ ) {
if ( *cp == '\0' ) {
c++;
if ( np != NULL ) {
/* Convert previous '\0'. */
*np = ' ';
}
/* Note location of current '\0'. */
np = cp;
if (*basenameOffset == 0) {
*basenameOffset = cp - sp;
}
}
}
/*
* sp points to the beginning of the arguments/environment string, and
* np should point to the '\0' terminator for the string.
*/
if ( np == NULL || np == sp ) {
/* Empty or unterminated string. */
goto ERROR_B;
}
if (*basenameOffset == 0) {
*basenameOffset = np - sp;
}
/* Make a copy of the string. */
retval = xStrdup(sp);
/* Clean up. */
free( procargs );
return retval;
ERROR_B:
free( procargs );
ERROR_A:
retval = xStrdup(k->kp_proc.p_comm);
*basenameOffset = strlen(retval);
return retval;
}
void DarwinProcess_setFromKInfoProc(Process *proc, const struct kinfo_proc *ps, time_t now, bool exists) {
const struct extern_proc *ep = &ps->kp_proc;
/* UNSET HERE :
*
* processor
* user (set at ProcessList level)
* nlwp
* percent_cpu
* percent_mem
* m_size
* m_resident
* minflt
* majflt
*/
if(exists) {
if(proc->ruid != ps->kp_eproc.e_pcred.p_ruid) proc->real_user = NULL;
if(proc->euid != ps->kp_eproc.e_ucred.cr_uid) proc->effective_user = NULL;
} else {
/* First, the "immutable" parts */
proc->tgid = proc->pid;
proc->session = 0; /* TODO Get the session id */
DarwinProcess_setStartTime(proc, ep, now);
proc->name = xStrdup(ps->kp_proc.p_comm);
proc->comm = DarwinProcess_getCmdLine(ps, &(proc->basenameOffset));
((DarwinProcess *)proc)->is_kernel_process = ep->p_flag & P_SYSTEM;
}
/* Mutable information */
proc->ppid = ps->kp_eproc.e_ppid;
proc->pgrp = ps->kp_eproc.e_pgid;
proc->tpgid = ps->kp_eproc.e_tpgid;
/* e_tdev = (major << 24) | (minor & 0xffffff) */
/* e_tdev == -1 for "no device" */
proc->tty_nr = ps->kp_eproc.e_tdev & 0xff; /* TODO tty_nr is unsigned */
proc->ruid = ps->kp_eproc.e_pcred.p_ruid;
proc->euid = ps->kp_eproc.e_ucred.cr_uid;
proc->nice = ep->p_nice;
proc->priority = ep->p_priority;
proc->state = (ep->p_stat == SZOMB) ? 'Z' : '?';
/* Make sure the updated flag is set */
proc->updated = true;
}
void DarwinProcess_setFromLibprocPidinfo(DarwinProcess *proc, DarwinProcessList *dpl) {
struct proc_taskinfo pti;
if(proc_pidinfo(proc->super.pid, PROC_PIDTASKINFO, 0, &pti, sizeof pti) != sizeof pti) return;
if(0 != proc->utime || 0 != proc->stime) {
uint64_t diff = (pti.pti_total_system - proc->stime)
+ (pti.pti_total_user - proc->utime);
proc->super.percent_cpu = (double)diff * (double)dpl->super.cpuCount
/ ((double)dpl->global_diff * 100000.0);
}
proc->super.time = (pti.pti_total_system + pti.pti_total_user) / 10000000;
proc->super.nlwp = pti.pti_threadnum;
proc->super.m_size = pti.pti_virtual_size / 1024 / CRT_page_size_kib;
proc->super.m_resident = pti.pti_resident_size / 1024 / CRT_page_size_kib;
proc->super.majflt = pti.pti_faults;
proc->super.percent_mem = (double)pti.pti_resident_size * 100.0
/ (double)dpl->host_info.max_mem;
proc->stime = pti.pti_total_system;
proc->utime = pti.pti_total_user;
dpl->super.thread_count += pti.pti_threadnum;
if(Process_isKernelProcess(&proc->super)) {
dpl->super.kernel_thread_count += pti.pti_threadnum;
}
dpl->super.running_thread_count += pti.pti_numrunning;
}
/*
* Scan threads for process state information.
* Based on: http://stackoverflow.com/questions/6788274/ios-mac-cpu-usage-for-thread
* and https://github.com/max-horvath/htop-osx/blob/e86692e869e30b0bc7264b3675d2a4014866ef46/ProcessList.c
*/
void DarwinProcess_scanThreads(DarwinProcess *dp) {
Process* proc = (Process*) dp;
kern_return_t ret;
if (!dp->taskAccess) {
return;
}
if (proc->state == 'Z') {
return;
}
task_t port;
ret = task_for_pid(mach_task_self(), proc->pid, &port);
if (ret != KERN_SUCCESS) {
dp->taskAccess = false;
return;
}
task_info_data_t tinfo;
mach_msg_type_number_t task_info_count = TASK_INFO_MAX;
ret = task_info(port, TASK_BASIC_INFO, (task_info_t) tinfo, &task_info_count);
if (ret != KERN_SUCCESS) {
dp->taskAccess = false;
return;
}
thread_array_t thread_list;
mach_msg_type_number_t thread_count;
ret = task_threads(port, &thread_list, &thread_count);
if (ret != KERN_SUCCESS) {
dp->taskAccess = false;
mach_port_deallocate(mach_task_self(), port);
return;
}
integer_t run_state = 999;
for (unsigned int i = 0; i < thread_count; i++) {
thread_info_data_t thinfo;
mach_msg_type_number_t thread_info_count = THREAD_BASIC_INFO_COUNT;
ret = thread_info(thread_list[i], THREAD_BASIC_INFO, (thread_info_t)thinfo, &thread_info_count);
if (ret == KERN_SUCCESS) {
thread_basic_info_t basic_info_th = (thread_basic_info_t) thinfo;
if (basic_info_th->run_state < run_state) {
run_state = basic_info_th->run_state;
}
mach_port_deallocate(mach_task_self(), thread_list[i]);
}
}
vm_deallocate(mach_task_self(), (vm_address_t) thread_list, sizeof(thread_port_array_t) * thread_count);
mach_port_deallocate(mach_task_self(), port);
switch (run_state) {
case TH_STATE_RUNNING:
proc->state = 'R';
break;
case TH_STATE_STOPPED:
proc->state = 'S';
break;
case TH_STATE_WAITING:
proc->state = 'W';
break;
case TH_STATE_UNINTERRUPTIBLE:
proc->state = 'U';
break;
case TH_STATE_HALTED:
proc->state = 'H';
break;
default:
proc->state = '?';
break;
}
}