diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:00:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:00:29 +0000 |
commit | 52e25213825024b8bb446eb26b03bedc9d5c2103 (patch) | |
tree | da70bf44b2423f6f8e9a070c063fed79d190b489 /darwin | |
parent | Initial commit. (diff) | |
download | htop-upstream.tar.xz htop-upstream.zip |
Adding upstream version 3.2.2.upstream/3.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'darwin')
-rw-r--r-- | darwin/DarwinProcess.c | 478 | ||||
-rw-r--r-- | darwin/DarwinProcess.h | 46 | ||||
-rw-r--r-- | darwin/DarwinProcessList.c | 203 | ||||
-rw-r--r-- | darwin/DarwinProcessList.h | 39 | ||||
-rw-r--r-- | darwin/Platform.c | 437 | ||||
-rw-r--r-- | darwin/Platform.h | 127 | ||||
-rw-r--r-- | darwin/PlatformHelpers.c | 126 | ||||
-rw-r--r-- | darwin/PlatformHelpers.h | 40 | ||||
-rw-r--r-- | darwin/ProcessField.h | 18 |
9 files changed, 1514 insertions, 0 deletions
diff --git a/darwin/DarwinProcess.c b/darwin/DarwinProcess.c new file mode 100644 index 0000000..6027c25 --- /dev/null +++ b/darwin/DarwinProcess.c @@ -0,0 +1,478 @@ +/* +htop - DarwinProcess.c +(C) 2015 Hisham H. Muhammad +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "darwin/DarwinProcess.h" + +#include <libproc.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <mach/mach.h> +#include <sys/dirent.h> + +#include "CRT.h" +#include "Process.h" +#include "darwin/Platform.h" + + +const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = { + [0] = { .name = "", .title = NULL, .description = NULL, .flags = 0, }, + [PID] = { .name = "PID", .title = "PID", .description = "Process/thread ID", .flags = 0, .pidColumn = true, }, + [COMM] = { .name = "Command", .title = "Command ", .description = "Command line", .flags = 0, }, + [STATE] = { .name = "STATE", .title = "S ", .description = "Process state (S sleeping, R running, D disk, Z zombie, T traced, W paging)", .flags = 0, }, + [PPID] = { .name = "PPID", .title = "PPID", .description = "Parent process ID", .flags = 0, .pidColumn = true, }, + [PGRP] = { .name = "PGRP", .title = "PGRP", .description = "Process group ID", .flags = 0, .pidColumn = true, }, + [SESSION] = { .name = "SESSION", .title = "SID", .description = "Process's session ID", .flags = 0, .pidColumn = true, }, + [TTY] = { .name = "TTY", .title = "TTY ", .description = "Controlling terminal", .flags = PROCESS_FLAG_TTY, }, + [TPGID] = { .name = "TPGID", .title = "TPGID", .description = "Process ID of the fg process group of the controlling terminal", .flags = 0, .pidColumn = true, }, + [MINFLT] = { .name = "MINFLT", .title = " MINFLT ", .description = "Number of minor faults which have not required loading a memory page from disk", .flags = 0, .defaultSortDesc = true, }, + [MAJFLT] = { .name = "MAJFLT", .title = " MAJFLT ", .description = "Number of major faults which have required loading a memory page from disk", .flags = 0, .defaultSortDesc = true, }, + [PRIORITY] = { .name = "PRIORITY", .title = "PRI ", .description = "Kernel's internal priority for the process", .flags = 0, }, + [NICE] = { .name = "NICE", .title = " NI ", .description = "Nice value (the higher the value, the more it lets other processes take priority)", .flags = 0, }, + [STARTTIME] = { .name = "STARTTIME", .title = "START ", .description = "Time the process was started", .flags = 0, }, + [ELAPSED] = { .name = "ELAPSED", .title = "ELAPSED ", .description = "Time since the process was started", .flags = 0, }, + [PROCESSOR] = { .name = "PROCESSOR", .title = "CPU ", .description = "Id of the CPU the process last executed on", .flags = 0, }, + [M_VIRT] = { .name = "M_VIRT", .title = " VIRT ", .description = "Total program size in virtual memory", .flags = 0, .defaultSortDesc = true, }, + [M_RESIDENT] = { .name = "M_RESIDENT", .title = " RES ", .description = "Resident set size, size of the text and data sections, plus stack usage", .flags = 0, .defaultSortDesc = true, }, + [ST_UID] = { .name = "ST_UID", .title = "UID", .description = "User ID of the process owner", .flags = 0, }, + [PERCENT_CPU] = { .name = "PERCENT_CPU", .title = " CPU%", .description = "Percentage of the CPU time the process used in the last sampling", .flags = 0, .defaultSortDesc = true, .autoWidth = true, }, + [PERCENT_NORM_CPU] = { .name = "PERCENT_NORM_CPU", .title = "NCPU%", .description = "Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)", .flags = 0, .defaultSortDesc = true, .autoWidth = true, }, + [PERCENT_MEM] = { .name = "PERCENT_MEM", .title = "MEM% ", .description = "Percentage of the memory the process is using, based on resident memory size", .flags = 0, .defaultSortDesc = true, }, + [USER] = { .name = "USER", .title = "USER ", .description = "Username of the process owner (or user ID if name cannot be determined)", .flags = 0, }, + [TIME] = { .name = "TIME", .title = " TIME+ ", .description = "Total time the process has spent in user and system time", .flags = 0, .defaultSortDesc = true, }, + [NLWP] = { .name = "NLWP", .title = "NLWP ", .description = "Number of threads in the process", .flags = 0, }, + [TGID] = { .name = "TGID", .title = "TGID", .description = "Thread group ID (i.e. process ID)", .flags = 0, .pidColumn = true, }, + [PROC_EXE] = { .name = "EXE", .title = "EXE ", .description = "Basename of exe of the process from /proc/[pid]/exe", .flags = 0, }, + [CWD] = { .name = "CWD", .title = "CWD ", .description = "The current working directory of the process", .flags = PROCESS_FLAG_CWD, }, + [TRANSLATED] = { .name = "TRANSLATED", .title = "T ", .description = "Translation info (T translated, N native)", .flags = 0, }, +}; + +Process* DarwinProcess_new(const 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; + this->translated = false; + + return &this->super; +} + +void Process_delete(Object* cast) { + DarwinProcess* this = (DarwinProcess*) cast; + Process_done(&this->super); + // free platform-specific fields here + free(this); +} + +static void DarwinProcess_writeField(const Process* this, RichString* str, ProcessField field) { + const DarwinProcess* dp = (const DarwinProcess*) this; + char buffer[256]; buffer[255] = '\0'; + int attr = CRT_colors[DEFAULT_COLOR]; + int n = sizeof(buffer) - 1; + switch (field) { + // add Platform-specific fields here + case TRANSLATED: xSnprintf(buffer, n, "%c ", dp->translated ? 'T' : 'N'); break; + default: + Process_writeField(this, str, field); + return; + } + RichString_appendWide(str, attr, buffer); +} + +static int DarwinProcess_compareByKey(const Process* v1, const Process* v2, ProcessField key) { + const DarwinProcess* p1 = (const DarwinProcess*)v1; + const DarwinProcess* p2 = (const DarwinProcess*)v2; + + switch (key) { + // add Platform-specific fields here + case TRANSLATED: + return SPACESHIP_NUMBER(p1->translated, p2->translated); + default: + return Process_compareByKey_Base(v1, v2, key); + } +} + +static void DarwinProcess_updateExe(pid_t pid, Process* proc) { + char path[PROC_PIDPATHINFO_MAXSIZE]; + + int r = proc_pidpath(pid, path, sizeof(path)); + if (r <= 0) + return; + + Process_updateExe(proc, path); +} + +static void DarwinProcess_updateCwd(pid_t pid, Process* proc) { + struct proc_vnodepathinfo vpi; + + int r = proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0, &vpi, sizeof(vpi)); + if (r <= 0) { + free(proc->procCwd); + proc->procCwd = NULL; + return; + } + + if (!vpi.pvi_cdir.vip_path[0]) { + free(proc->procCwd); + proc->procCwd = NULL; + return; + } + + free_and_xStrdup(&proc->procCwd, vpi.pvi_cdir.vip_path); +} + +static void DarwinProcess_updateCmdLine(const struct kinfo_proc* k, Process* proc) { + Process_updateComm(proc, k->kp_proc.p_comm); + + /* 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; + + /* 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*)malloc(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; + + int end = 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 (end == 0) { + end = 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 (end == 0) { + end = np - sp; + } + + Process_updateCmdline(proc, sp, 0, end); + + /* Clean up. */ + free( procargs ); + + return; + +ERROR_B: + free( procargs ); + +ERROR_A: + Process_updateCmdline(proc, k->kp_proc.p_comm, 0, strlen(k->kp_proc.p_comm)); +} + +// Converts nanoseconds to hundredths of a second (centiseconds) as needed by the "time" field of the Process struct. +static long long int nanosecondsToCentiseconds(uint64_t nanoseconds) { + const uint64_t centiseconds_per_second = 100; + const uint64_t nanoseconds_per_second = 1e9; + return nanoseconds / nanoseconds_per_second * centiseconds_per_second; +} + +static char* DarwinProcess_getDevname(dev_t dev) { + if (dev == NODEV) { + return NULL; + } + char buf[sizeof("/dev/") + MAXNAMLEN]; + char* name = devname_r(dev, S_IFCHR, buf, MAXNAMLEN); + if (name) { + return xStrdup(name); + } + return NULL; +} + +void DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps, bool exists) { + DarwinProcess* dp = (DarwinProcess*)proc; + + const struct extern_proc* ep = &ps->kp_proc; + + /* UNSET HERE : + * + * processor + * user (set at ProcessList level) + * nlwp + * percent_cpu + * percent_mem + * m_virt + * m_resident + * minflt + * majflt + */ + + /* First, the "immutable" parts */ + if (!exists) { + /* Set the PID/PGID/etc. */ + proc->pid = ep->p_pid; + proc->ppid = ps->kp_eproc.e_ppid; + proc->pgrp = ps->kp_eproc.e_pgid; + proc->session = 0; /* TODO Get the session id */ + proc->tpgid = ps->kp_eproc.e_tpgid; + proc->tgid = proc->pid; + proc->isKernelThread = false; + proc->isUserlandThread = false; + dp->translated = ps->kp_proc.p_flag & P_TRANSLATED; + proc->tty_nr = ps->kp_eproc.e_tdev; + proc->tty_name = NULL; + + proc->starttime_ctime = ep->p_starttime.tv_sec; + Process_fillStarttimeBuffer(proc); + + DarwinProcess_updateExe(ep->p_pid, proc); + DarwinProcess_updateCmdLine(ps, proc); + + if (proc->settings->ss->flags & PROCESS_FLAG_CWD) { + DarwinProcess_updateCwd(ep->p_pid, proc); + } + } + + if (proc->tty_name == NULL && (dev_t)proc->tty_nr != NODEV) { + /* The call to devname() is extremely expensive (due to lstat) + * and represents ~95% of htop's CPU usage when there is high + * process turnover. + * + * To mitigate this we only fetch TTY information if the TTY + * field is enabled in the settings. + */ + if (proc->settings->ss->flags & PROCESS_FLAG_TTY) { + proc->tty_name = DarwinProcess_getDevname(proc->tty_nr); + if (!proc->tty_name) { + /* devname failed: prevent us from calling it again */ + proc->tty_nr = NODEV; + } + } + } + + /* Mutable information */ + proc->nice = ep->p_nice; + proc->priority = ep->p_priority; + + proc->state = (ep->p_stat == SZOMB) ? ZOMBIE : UNKNOWN; + + /* Make sure the updated flag is set */ + proc->updated = true; +} + +void DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessList* dpl, double timeIntervalNS) { + struct proc_taskinfo pti; + + if (sizeof(pti) == proc_pidinfo(proc->super.pid, PROC_PIDTASKINFO, 0, &pti, sizeof(pti))) { + uint64_t total_existing_time_ns = proc->stime + proc->utime; + + uint64_t user_time_ns = Platform_machTicksToNanoseconds(pti.pti_total_user); + uint64_t system_time_ns = Platform_machTicksToNanoseconds(pti.pti_total_system); + + uint64_t total_current_time_ns = user_time_ns + system_time_ns; + + if (total_existing_time_ns && 1E-6 < timeIntervalNS) { + uint64_t total_time_diff_ns = total_current_time_ns - total_existing_time_ns; + proc->super.percent_cpu = ((double)total_time_diff_ns / timeIntervalNS) * 100.0; + } else { + proc->super.percent_cpu = 0.0; + } + Process_updateCPUFieldWidths(proc->super.percent_cpu); + + proc->super.time = nanosecondsToCentiseconds(total_current_time_ns); + proc->super.nlwp = pti.pti_threadnum; + proc->super.m_virt = pti.pti_virtual_size / ONE_K; + proc->super.m_resident = pti.pti_resident_size / ONE_K; + 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 = system_time_ns; + proc->utime = user_time_ns; + + dpl->super.kernelThreads += 0; /*pti.pti_threads_system;*/ + dpl->super.userlandThreads += pti.pti_threadnum; /*pti.pti_threads_user;*/ + dpl->super.totalTasks += pti.pti_threadnum; + dpl->super.runningTasks += 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 == ZOMBIE) { + 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); + + /* Taken from: https://github.com/apple/darwin-xnu/blob/2ff845c2e033bd0ff64b5b6aa6063a1f8f65aa32/osfmk/mach/thread_info.h#L129 */ + switch (run_state) { + case TH_STATE_RUNNING: proc->state = RUNNING; break; + case TH_STATE_STOPPED: proc->state = STOPPED; break; + case TH_STATE_WAITING: proc->state = WAITING; break; + case TH_STATE_UNINTERRUPTIBLE: proc->state = UNINTERRUPTIBLE_WAIT; break; + case TH_STATE_HALTED: proc->state = BLOCKED; break; + default: proc->state = UNKNOWN; + } +} + + +const ProcessClass DarwinProcess_class = { + .super = { + .extends = Class(Process), + .display = Process_display, + .delete = Process_delete, + .compare = Process_compare + }, + .writeField = DarwinProcess_writeField, + .compareByKey = DarwinProcess_compareByKey, +}; diff --git a/darwin/DarwinProcess.h b/darwin/DarwinProcess.h new file mode 100644 index 0000000..bd17974 --- /dev/null +++ b/darwin/DarwinProcess.h @@ -0,0 +1,46 @@ +#ifndef HEADER_DarwinProcess +#define HEADER_DarwinProcess +/* +htop - DarwinProcess.h +(C) 2015 Hisham H. Muhammad +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include <sys/sysctl.h> + +#include "Settings.h" +#include "darwin/DarwinProcessList.h" + + +#define PROCESS_FLAG_TTY 0x00000100 + +typedef struct DarwinProcess_ { + Process super; + + uint64_t utime; + uint64_t stime; + bool taskAccess; + bool translated; +} DarwinProcess; + +extern const ProcessClass DarwinProcess_class; + +extern const ProcessFieldData Process_fields[LAST_PROCESSFIELD]; + +Process* DarwinProcess_new(const Settings* settings); + +void Process_delete(Object* cast); + +void DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps, bool exists); + +void DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessList* dpl, double timeIntervalNS); + +/* + * 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); + +#endif diff --git a/darwin/DarwinProcessList.c b/darwin/DarwinProcessList.c new file mode 100644 index 0000000..dae588b --- /dev/null +++ b/darwin/DarwinProcessList.c @@ -0,0 +1,203 @@ +/* +htop - DarwinProcessList.c +(C) 2014 Hisham H. Muhammad +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "darwin/DarwinProcessList.h" + +#include <errno.h> +#include <libproc.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <utmpx.h> +#include <sys/mman.h> +#include <sys/sysctl.h> + +#include "CRT.h" +#include "ProcessList.h" +#include "darwin/DarwinProcess.h" +#include "darwin/Platform.h" +#include "darwin/PlatformHelpers.h" +#include "generic/openzfs_sysctl.h" +#include "zfs/ZfsArcStats.h" + + +static void ProcessList_getHostInfo(host_basic_info_data_t* p) { + mach_msg_type_number_t info_size = HOST_BASIC_INFO_COUNT; + + if (0 != host_info(mach_host_self(), HOST_BASIC_INFO, (host_info_t)p, &info_size)) { + CRT_fatalError("Unable to retrieve host info"); + } +} + +static void ProcessList_freeCPULoadInfo(processor_cpu_load_info_t* p) { + if (NULL != p && NULL != *p) { + if (0 != munmap(*p, vm_page_size)) { + CRT_fatalError("Unable to free old CPU load information"); + } + *p = NULL; + } +} + +static unsigned ProcessList_allocateCPULoadInfo(processor_cpu_load_info_t* p) { + mach_msg_type_number_t info_size = sizeof(processor_cpu_load_info_t); + unsigned cpu_count; + + // TODO Improving the accuracy of the load counts woule help a lot. + if (0 != host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &cpu_count, (processor_info_array_t*)p, &info_size)) { + CRT_fatalError("Unable to retrieve CPU info"); + } + + return cpu_count; +} + +static void ProcessList_getVMStats(vm_statistics_t p) { + mach_msg_type_number_t info_size = HOST_VM_INFO_COUNT; + + if (host_statistics(mach_host_self(), HOST_VM_INFO, (host_info_t)p, &info_size) != 0) { + CRT_fatalError("Unable to retrieve VM statistics"); + } +} + +static struct kinfo_proc* ProcessList_getKInfoProcs(size_t* count) { + int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 }; + struct kinfo_proc* processes = NULL; + + for (unsigned int retry = 0; retry < 4; retry++) { + size_t size = 0; + if (sysctl(mib, 4, NULL, &size, NULL, 0) < 0 || size == 0) { + CRT_fatalError("Unable to get size of kproc_infos"); + } + + size += 16 * retry * retry * sizeof(struct kinfo_proc); + processes = xRealloc(processes, size); + + if (sysctl(mib, 4, processes, &size, NULL, 0) == 0) { + *count = size / sizeof(struct kinfo_proc); + return processes; + } + + if (errno != ENOMEM) + break; + } + + CRT_fatalError("Unable to get kinfo_procs"); +} + +ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId) { + DarwinProcessList* this = xCalloc(1, sizeof(DarwinProcessList)); + + ProcessList_init(&this->super, Class(DarwinProcess), usersTable, dynamicMeters, dynamicColumns, pidMatchList, userId); + + /* Initialize the CPU information */ + this->super.activeCPUs = ProcessList_allocateCPULoadInfo(&this->prev_load); + // TODO: support offline CPUs and hot swapping + this->super.existingCPUs = this->super.activeCPUs; + ProcessList_getHostInfo(&this->host_info); + ProcessList_allocateCPULoadInfo(&this->curr_load); + + /* Initialize the VM statistics */ + ProcessList_getVMStats(&this->vm_stats); + + /* Initialize the ZFS kstats, if zfs.kext loaded */ + openzfs_sysctl_init(&this->zfs); + openzfs_sysctl_updateArcStats(&this->zfs); + + this->super.kernelThreads = 0; + this->super.userlandThreads = 0; + this->super.totalTasks = 0; + this->super.runningTasks = 0; + + return &this->super; +} + +void ProcessList_delete(ProcessList* this) { + ProcessList_done(this); + free(this); +} + +void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate) { + DarwinProcessList* dpl = (DarwinProcessList*)super; + bool preExisting = true; + struct kinfo_proc* ps; + size_t count; + DarwinProcess* proc; + + /* Update the global data (CPU times and VM stats) */ + ProcessList_freeCPULoadInfo(&dpl->prev_load); + dpl->prev_load = dpl->curr_load; + ProcessList_allocateCPULoadInfo(&dpl->curr_load); + ProcessList_getVMStats(&dpl->vm_stats); + openzfs_sysctl_updateArcStats(&dpl->zfs); + + // in pause mode only gather global data for meters (CPU/memory/...) + if (pauseProcessUpdate) { + return; + } + + /* Get the time difference */ + dpl->global_diff = 0; + for (unsigned int i = 0; i < dpl->super.existingCPUs; ++i) { + for (size_t j = 0; j < CPU_STATE_MAX; ++j) { + dpl->global_diff += dpl->curr_load[i].cpu_ticks[j] - dpl->prev_load[i].cpu_ticks[j]; + } + } + + const double time_interval_ns = Platform_schedulerTicksToNanoseconds(dpl->global_diff) / (double) dpl->super.activeCPUs; + + /* Clear the thread counts */ + super->kernelThreads = 0; + super->userlandThreads = 0; + super->totalTasks = 0; + super->runningTasks = 0; + + /* We use kinfo_procs for initial data since : + * + * 1) They always succeed. + * 2) The contain the basic information. + * + * We attempt to fill-in additional information with libproc. + */ + ps = ProcessList_getKInfoProcs(&count); + + for (size_t i = 0; i < count; ++i) { + proc = (DarwinProcess*)ProcessList_getProcess(super, ps[i].kp_proc.p_pid, &preExisting, DarwinProcess_new); + + DarwinProcess_setFromKInfoProc(&proc->super, &ps[i], preExisting); + DarwinProcess_setFromLibprocPidinfo(proc, dpl, time_interval_ns); + + if (proc->super.st_uid != ps[i].kp_eproc.e_ucred.cr_uid) { + proc->super.st_uid = ps[i].kp_eproc.e_ucred.cr_uid; + proc->super.user = UsersTable_getRef(super->usersTable, proc->super.st_uid); + } + + // Disabled for High Sierra due to bug in macOS High Sierra + bool isScanThreadSupported = !Platform_KernelVersionIsBetween((KernelVersion) {17, 0, 0}, (KernelVersion) {17, 5, 0}); + + if (isScanThreadSupported) { + DarwinProcess_scanThreads(proc); + } + + super->totalTasks += 1; + + if (!preExisting) { + ProcessList_add(super, &proc->super); + } + } + + free(ps); +} + +bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id) { + assert(id < super->existingCPUs); + + // TODO: support offline CPUs and hot swapping + (void) super; (void) id; + + return true; +} diff --git a/darwin/DarwinProcessList.h b/darwin/DarwinProcessList.h new file mode 100644 index 0000000..393e656 --- /dev/null +++ b/darwin/DarwinProcessList.h @@ -0,0 +1,39 @@ +#ifndef HEADER_DarwinProcessList +#define HEADER_DarwinProcessList +/* +htop - DarwinProcessList.h +(C) 2014 Hisham H. Muhammad +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include <mach/mach_host.h> +#include <sys/sysctl.h> + +#include "ProcessList.h" +#include "zfs/ZfsArcStats.h" + + +typedef struct DarwinProcessList_ { + ProcessList super; + + host_basic_info_data_t host_info; + vm_statistics_data_t vm_stats; + processor_cpu_load_info_t prev_load; + processor_cpu_load_info_t curr_load; + uint64_t kernel_threads; + uint64_t user_threads; + uint64_t global_diff; + + ZfsArcStats zfs; +} DarwinProcessList; + +ProcessList* ProcessList_new(UsersTable* usersTable, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* pidMatchList, uid_t userId); + +void ProcessList_delete(ProcessList* this); + +void ProcessList_goThroughEntries(ProcessList* super, bool pauseProcessUpdate); + +bool ProcessList_isCPUonline(const ProcessList* super, unsigned int id); + +#endif diff --git a/darwin/Platform.c b/darwin/Platform.c new file mode 100644 index 0000000..332752b --- /dev/null +++ b/darwin/Platform.c @@ -0,0 +1,437 @@ +/* +htop - darwin/Platform.c +(C) 2014 Hisham H. Muhammad +(C) 2015 David C. Hunt +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "config.h" // IWYU pragma: keep + +#include "darwin/Platform.h" + +#include <errno.h> +#include <math.h> +#include <stdlib.h> +#include <unistd.h> +#include <CoreFoundation/CFString.h> +#include <CoreFoundation/CoreFoundation.h> +#include <IOKit/ps/IOPowerSources.h> +#include <IOKit/ps/IOPSKeys.h> + +#include "ClockMeter.h" +#include "CPUMeter.h" +#include "CRT.h" +#include "DateMeter.h" +#include "DateTimeMeter.h" +#include "HostnameMeter.h" +#include "LoadAverageMeter.h" +#include "Macros.h" +#include "MemoryMeter.h" +#include "MemorySwapMeter.h" +#include "ProcessLocksScreen.h" +#include "SwapMeter.h" +#include "SysArchMeter.h" +#include "TasksMeter.h" +#include "UptimeMeter.h" +#include "darwin/DarwinProcessList.h" +#include "darwin/PlatformHelpers.h" +#include "zfs/ZfsArcMeter.h" +#include "zfs/ZfsCompressedArcMeter.h" + +#ifdef HAVE_HOST_GET_CLOCK_SERVICE +#include <mach/clock.h> +#include <mach/mach.h> +#endif + +#ifdef HAVE_MACH_MACH_TIME_H +#include <mach/mach_time.h> +#endif + + +const ScreenDefaults Platform_defaultScreens[] = { + { + .name = "Main", + .columns = "PID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command", + .sortKey = "PERCENT_CPU", + }, +}; + +const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens); + +const SignalItem Platform_signals[] = { + { .name = " 0 Cancel", .number = 0 }, + { .name = " 1 SIGHUP", .number = 1 }, + { .name = " 2 SIGINT", .number = 2 }, + { .name = " 3 SIGQUIT", .number = 3 }, + { .name = " 4 SIGILL", .number = 4 }, + { .name = " 5 SIGTRAP", .number = 5 }, + { .name = " 6 SIGABRT", .number = 6 }, + { .name = " 6 SIGIOT", .number = 6 }, + { .name = " 7 SIGEMT", .number = 7 }, + { .name = " 8 SIGFPE", .number = 8 }, + { .name = " 9 SIGKILL", .number = 9 }, + { .name = "10 SIGBUS", .number = 10 }, + { .name = "11 SIGSEGV", .number = 11 }, + { .name = "12 SIGSYS", .number = 12 }, + { .name = "13 SIGPIPE", .number = 13 }, + { .name = "14 SIGALRM", .number = 14 }, + { .name = "15 SIGTERM", .number = 15 }, + { .name = "16 SIGURG", .number = 16 }, + { .name = "17 SIGSTOP", .number = 17 }, + { .name = "18 SIGTSTP", .number = 18 }, + { .name = "19 SIGCONT", .number = 19 }, + { .name = "20 SIGCHLD", .number = 20 }, + { .name = "21 SIGTTIN", .number = 21 }, + { .name = "22 SIGTTOU", .number = 22 }, + { .name = "23 SIGIO", .number = 23 }, + { .name = "24 SIGXCPU", .number = 24 }, + { .name = "25 SIGXFSZ", .number = 25 }, + { .name = "26 SIGVTALRM", .number = 26 }, + { .name = "27 SIGPROF", .number = 27 }, + { .name = "28 SIGWINCH", .number = 28 }, + { .name = "29 SIGINFO", .number = 29 }, + { .name = "30 SIGUSR1", .number = 30 }, + { .name = "31 SIGUSR2", .number = 31 }, +}; + +const unsigned int Platform_numberOfSignals = ARRAYSIZE(Platform_signals); + +const MeterClass* const Platform_meterTypes[] = { + &CPUMeter_class, + &ClockMeter_class, + &DateMeter_class, + &DateTimeMeter_class, + &LoadAverageMeter_class, + &LoadMeter_class, + &MemoryMeter_class, + &SwapMeter_class, + &MemorySwapMeter_class, + &TasksMeter_class, + &BatteryMeter_class, + &HostnameMeter_class, + &SysArchMeter_class, + &UptimeMeter_class, + &AllCPUsMeter_class, + &AllCPUs2Meter_class, + &AllCPUs4Meter_class, + &AllCPUs8Meter_class, + &LeftCPUsMeter_class, + &RightCPUsMeter_class, + &LeftCPUs2Meter_class, + &RightCPUs2Meter_class, + &LeftCPUs4Meter_class, + &RightCPUs4Meter_class, + &LeftCPUs8Meter_class, + &RightCPUs8Meter_class, + &ZfsArcMeter_class, + &ZfsCompressedArcMeter_class, + &BlankMeter_class, + NULL +}; + +static double Platform_nanosecondsPerMachTick = 1.0; + +static double Platform_nanosecondsPerSchedulerTick = -1; + +bool Platform_init(void) { + Platform_nanosecondsPerMachTick = Platform_calculateNanosecondsPerMachTick(); + + // Determine the number of scheduler clock ticks per second + errno = 0; + long scheduler_ticks_per_sec = sysconf(_SC_CLK_TCK); + + if (errno || scheduler_ticks_per_sec < 1) { + CRT_fatalError("Unable to retrieve clock tick rate"); + } + + const double nanos_per_sec = 1e9; + Platform_nanosecondsPerSchedulerTick = nanos_per_sec / scheduler_ticks_per_sec; + + return true; +} + +// Converts ticks in the Mach "timebase" to nanoseconds. +// See `mach_timebase_info`, as used to define the `Platform_nanosecondsPerMachTick` constant. +uint64_t Platform_machTicksToNanoseconds(uint64_t mach_ticks) { + return (uint64_t) ((double) mach_ticks * Platform_nanosecondsPerMachTick); +} + +// Converts "scheduler ticks" to nanoseconds. +// See `sysconf(_SC_CLK_TCK)`, as used to define the `Platform_nanosecondsPerSchedulerTick` constant. +double Platform_schedulerTicksToNanoseconds(const double scheduler_ticks) { + return scheduler_ticks * Platform_nanosecondsPerSchedulerTick; +} + +void Platform_done(void) { + /* no platform-specific cleanup needed */ +} + +void Platform_setBindings(Htop_Action* keys) { + /* no platform-specific key bindings */ + (void) keys; +} + +int Platform_getUptime(void) { + struct timeval bootTime, currTime; + int mib[2] = { CTL_KERN, KERN_BOOTTIME }; + size_t size = sizeof(bootTime); + + int err = sysctl(mib, 2, &bootTime, &size, NULL, 0); + if (err) { + return -1; + } + gettimeofday(&currTime, NULL); + + return (int) difftime(currTime.tv_sec, bootTime.tv_sec); +} + +void Platform_getLoadAverage(double* one, double* five, double* fifteen) { + double results[3]; + + if (3 == getloadavg(results, 3)) { + *one = results[0]; + *five = results[1]; + *fifteen = results[2]; + } else { + *one = 0; + *five = 0; + *fifteen = 0; + } +} + +int Platform_getMaxPid(void) { + /* http://opensource.apple.com/source/xnu/xnu-2782.1.97/bsd/sys/proc_internal.hh */ + return 99999; +} + +static double Platform_setCPUAverageValues(Meter* mtr) { + const ProcessList* dpl = mtr->pl; + unsigned int activeCPUs = dpl->activeCPUs; + double sumNice = 0.0; + double sumNormal = 0.0; + double sumKernel = 0.0; + double sumPercent = 0.0; + for (unsigned int i = 1; i <= dpl->existingCPUs; i++) { + sumPercent += Platform_setCPUValues(mtr, i); + sumNice += mtr->values[CPU_METER_NICE]; + sumNormal += mtr->values[CPU_METER_NORMAL]; + sumKernel += mtr->values[CPU_METER_KERNEL]; + } + mtr->values[CPU_METER_NICE] = sumNice / activeCPUs; + mtr->values[CPU_METER_NORMAL] = sumNormal / activeCPUs; + mtr->values[CPU_METER_KERNEL] = sumKernel / activeCPUs; + return sumPercent / activeCPUs; +} + +double Platform_setCPUValues(Meter* mtr, unsigned int cpu) { + + if (cpu == 0) { + return Platform_setCPUAverageValues(mtr); + } + + const DarwinProcessList* dpl = (const DarwinProcessList*)mtr->pl; + const processor_cpu_load_info_t prev = &dpl->prev_load[cpu - 1]; + const processor_cpu_load_info_t curr = &dpl->curr_load[cpu - 1]; + double total = 0; + + /* Take the sums */ + for (size_t i = 0; i < CPU_STATE_MAX; ++i) { + total += (double)curr->cpu_ticks[i] - (double)prev->cpu_ticks[i]; + } + + mtr->values[CPU_METER_NICE] + = ((double)curr->cpu_ticks[CPU_STATE_NICE] - (double)prev->cpu_ticks[CPU_STATE_NICE]) * 100.0 / total; + mtr->values[CPU_METER_NORMAL] + = ((double)curr->cpu_ticks[CPU_STATE_USER] - (double)prev->cpu_ticks[CPU_STATE_USER]) * 100.0 / total; + mtr->values[CPU_METER_KERNEL] + = ((double)curr->cpu_ticks[CPU_STATE_SYSTEM] - (double)prev->cpu_ticks[CPU_STATE_SYSTEM]) * 100.0 / total; + + mtr->curItems = 3; + + /* Convert to percent and return */ + total = mtr->values[CPU_METER_NICE] + mtr->values[CPU_METER_NORMAL] + mtr->values[CPU_METER_KERNEL]; + + mtr->values[CPU_METER_FREQUENCY] = NAN; + mtr->values[CPU_METER_TEMPERATURE] = NAN; + + return CLAMP(total, 0.0, 100.0); +} + +void Platform_setMemoryValues(Meter* mtr) { + const DarwinProcessList* dpl = (const DarwinProcessList*)mtr->pl; + const struct vm_statistics* vm = &dpl->vm_stats; + double page_K = (double)vm_page_size / (double)1024; + + mtr->total = dpl->host_info.max_mem / 1024; + mtr->values[MEMORY_METER_USED] = (double)(vm->active_count + vm->wire_count) * page_K; + mtr->values[MEMORY_METER_BUFFERS] = (double)vm->purgeable_count * page_K; + // mtr->values[MEMORY_METER_SHARED] = "shared memory, like tmpfs and shm" + mtr->values[MEMORY_METER_CACHE] = (double)vm->inactive_count * page_K; + // mtr->values[MEMORY_METER_AVAILABLE] = "available memory" +} + +void Platform_setSwapValues(Meter* mtr) { + int mib[2] = {CTL_VM, VM_SWAPUSAGE}; + struct xsw_usage swapused; + size_t swlen = sizeof(swapused); + sysctl(mib, 2, &swapused, &swlen, NULL, 0); + + mtr->total = swapused.xsu_total / 1024; + mtr->values[SWAP_METER_USED] = swapused.xsu_used / 1024; +} + +void Platform_setZfsArcValues(Meter* this) { + const DarwinProcessList* dpl = (const DarwinProcessList*) this->pl; + + ZfsArcMeter_readStats(this, &(dpl->zfs)); +} + +void Platform_setZfsCompressedArcValues(Meter* this) { + const DarwinProcessList* dpl = (const DarwinProcessList*) this->pl; + + ZfsCompressedArcMeter_readStats(this, &(dpl->zfs)); +} + +char* Platform_getProcessEnv(pid_t pid) { + char* env = NULL; + + int argmax; + size_t bufsz = sizeof(argmax); + + int mib[3]; + mib[0] = CTL_KERN; + mib[1] = KERN_ARGMAX; + if (sysctl(mib, 2, &argmax, &bufsz, 0, 0) == 0) { + char* buf = xMalloc(argmax); + if (buf) { + mib[0] = CTL_KERN; + mib[1] = KERN_PROCARGS2; + mib[2] = pid; + bufsz = argmax; + if (sysctl(mib, 3, buf, &bufsz, 0, 0) == 0) { + if (bufsz > sizeof(int)) { + char *p = buf, *endp = buf + bufsz; + int argc = *(int*)(void*)p; + p += sizeof(int); + + // skip exe + p = strchr(p, 0) + 1; + + // skip padding + while (!*p && p < endp) + ++p; + + // skip argv + for (; argc-- && p < endp; p = strrchr(p, 0) + 1) + ; + + // skip padding + while (!*p && p < endp) + ++p; + + size_t size = endp - p; + env = xMalloc(size + 2); + memcpy(env, p, size); + env[size] = 0; + env[size + 1] = 0; + } + } + free(buf); + } + } + + return env; +} + +FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) { + (void)pid; + return NULL; +} + +bool Platform_getDiskIO(DiskIOData* data) { + // TODO + (void)data; + return false; +} + +bool Platform_getNetworkIO(NetworkIOData* data) { + // TODO + (void)data; + return false; +} + +void Platform_getBattery(double* percent, ACPresence* isOnAC) { + *percent = NAN; + *isOnAC = AC_ERROR; + + CFArrayRef list = NULL; + + CFTypeRef power_sources = IOPSCopyPowerSourcesInfo(); + if (!power_sources) + goto cleanup; + + list = IOPSCopyPowerSourcesList(power_sources); + if (!list) + goto cleanup; + + double cap_current = 0.0; + double cap_max = 0.0; + + /* Get the battery */ + for (int i = 0, len = CFArrayGetCount(list); i < len; ++i) { + CFDictionaryRef power_source = IOPSGetPowerSourceDescription(power_sources, CFArrayGetValueAtIndex(list, i)); /* GET rule */ + + if (!power_source) + continue; + + CFStringRef power_type = CFDictionaryGetValue(power_source, CFSTR(kIOPSTransportTypeKey)); /* GET rule */ + + if (kCFCompareEqualTo != CFStringCompare(power_type, CFSTR(kIOPSInternalType), 0)) + continue; + + /* Determine the AC state */ + CFStringRef power_state = CFDictionaryGetValue(power_source, CFSTR(kIOPSPowerSourceStateKey)); + + if (*isOnAC != AC_PRESENT) + *isOnAC = (kCFCompareEqualTo == CFStringCompare(power_state, CFSTR(kIOPSACPowerValue), 0)) ? AC_PRESENT : AC_ABSENT; + + /* Get the percentage remaining */ + double tmp; + CFNumberGetValue(CFDictionaryGetValue(power_source, CFSTR(kIOPSCurrentCapacityKey)), kCFNumberDoubleType, &tmp); + cap_current += tmp; + CFNumberGetValue(CFDictionaryGetValue(power_source, CFSTR(kIOPSMaxCapacityKey)), kCFNumberDoubleType, &tmp); + cap_max += tmp; + } + + if (cap_max > 0.0) + *percent = 100.0 * cap_current / cap_max; + +cleanup: + if (list) + CFRelease(list); + + if (power_sources) + CFRelease(power_sources); +} + +void Platform_gettime_monotonic(uint64_t* msec) { + +#ifdef HAVE_HOST_GET_CLOCK_SERVICE + + clock_serv_t cclock; + mach_timespec_t mts; + + host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock); + clock_get_time(cclock, &mts); + mach_port_deallocate(mach_task_self(), cclock); + + *msec = ((uint64_t)mts.tv_sec * 1000) + ((uint64_t)mts.tv_nsec / 1000000); + +#else + + Generic_gettime_monotonic(msec); + +#endif + +} diff --git a/darwin/Platform.h b/darwin/Platform.h new file mode 100644 index 0000000..6636207 --- /dev/null +++ b/darwin/Platform.h @@ -0,0 +1,127 @@ +#ifndef HEADER_Platform +#define HEADER_Platform +/* +htop - darwin/Platform.h +(C) 2014 Hisham H. Muhammad +(C) 2015 David C. Hunt +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include <stdbool.h> +#include <sys/types.h> + +#include "Action.h" +#include "BatteryMeter.h" +#include "CPUMeter.h" +#include "DiskIOMeter.h" +#include "Hashtable.h" +#include "NetworkIOMeter.h" +#include "ProcessLocksScreen.h" +#include "SignalsPanel.h" +#include "CommandLine.h" +#include "darwin/DarwinProcess.h" +#include "generic/gettime.h" +#include "generic/hostname.h" +#include "generic/uname.h" + + +extern const ScreenDefaults Platform_defaultScreens[]; + +extern const unsigned int Platform_numberOfDefaultScreens; + +extern const SignalItem Platform_signals[]; + +extern const unsigned int Platform_numberOfSignals; + +extern const MeterClass* const Platform_meterTypes[]; + +bool Platform_init(void); + +// Converts ticks in the Mach "timebase" to nanoseconds. +// See `mach_timebase_info`, as used to define the `Platform_nanosecondsPerMachTick` constant. +uint64_t Platform_machTicksToNanoseconds(uint64_t mach_ticks); + +// Converts "scheduler ticks" to nanoseconds. +// See `sysconf(_SC_CLK_TCK)`, as used to define the `Platform_nanosecondsPerSchedulerTick` constant. +double Platform_schedulerTicksToNanoseconds(const double scheduler_ticks); + +void Platform_done(void); + +void Platform_setBindings(Htop_Action* keys); + +int Platform_getUptime(void); + +void Platform_getLoadAverage(double* one, double* five, double* fifteen); + +int Platform_getMaxPid(void); + +double Platform_setCPUValues(Meter* mtr, unsigned int cpu); + +void Platform_setMemoryValues(Meter* mtr); + +void Platform_setSwapValues(Meter* mtr); + +void Platform_setZfsArcValues(Meter* this); + +void Platform_setZfsCompressedArcValues(Meter* this); + +char* Platform_getProcessEnv(pid_t pid); + +FileLocks_ProcessData* Platform_getProcessLocks(pid_t pid); + +bool Platform_getDiskIO(DiskIOData* data); + +bool Platform_getNetworkIO(NetworkIOData* data); + +void Platform_getBattery(double* percent, ACPresence* isOnAC); + +static inline void Platform_getHostname(char* buffer, size_t size) { + Generic_hostname(buffer, size); +} + +static inline void Platform_getRelease(char** string) { + *string = Generic_uname(); +} + +#define PLATFORM_LONG_OPTIONS + +static inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { } + +static inline CommandLineStatus Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) { + return STATUS_ERROR_EXIT; +} + +static inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) { + Generic_gettime_realtime(tv, msec); +} + +void Platform_gettime_monotonic(uint64_t* msec); + +static inline Hashtable* Platform_dynamicMeters(void) { + return NULL; +} + +static inline void Platform_dynamicMetersDone(ATTR_UNUSED Hashtable* table) { } + +static inline void Platform_dynamicMeterInit(ATTR_UNUSED Meter* meter) { } + +static inline void Platform_dynamicMeterUpdateValues(ATTR_UNUSED Meter* meter) { } + +static inline void Platform_dynamicMeterDisplay(ATTR_UNUSED const Meter* meter, ATTR_UNUSED RichString* out) { } + +static inline Hashtable* Platform_dynamicColumns(void) { + return NULL; +} + +static inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { } + +static inline const char* Platform_dynamicColumnInit(ATTR_UNUSED unsigned int key) { + return NULL; +} + +static inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) { + return false; +} + +#endif diff --git a/darwin/PlatformHelpers.c b/darwin/PlatformHelpers.c new file mode 100644 index 0000000..97f0741 --- /dev/null +++ b/darwin/PlatformHelpers.c @@ -0,0 +1,126 @@ +/* +htop - darwin/PlatformHelpers.c +(C) 2018 Pierre Malhaire, 2020-2021 htop dev team, 2021 Alexander Momchilov +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "darwin/PlatformHelpers.h" + +#include <errno.h> +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <sys/sysctl.h> + +#include "CRT.h" + +#ifdef HAVE_MACH_MACH_TIME_H +#include <mach/mach_time.h> +#endif + + +void Platform_GetKernelVersion(KernelVersion* k) { + static KernelVersion cachedKernelVersion; + + if (!cachedKernelVersion.major) { + // just in case it fails someday + cachedKernelVersion = (KernelVersion) { -1, -1, -1 }; + char str[256] = {0}; + size_t size = sizeof(str); + int ret = sysctlbyname("kern.osrelease", str, &size, NULL, 0); + if (ret == 0) { + sscanf(str, "%hd.%hd.%hd", &cachedKernelVersion.major, &cachedKernelVersion.minor, &cachedKernelVersion.patch); + } + } + memcpy(k, &cachedKernelVersion, sizeof(cachedKernelVersion)); +} + +int Platform_CompareKernelVersion(KernelVersion v) { + struct KernelVersion actualVersion; + Platform_GetKernelVersion(&actualVersion); + + if (actualVersion.major != v.major) { + return actualVersion.major - v.major; + } + if (actualVersion.minor != v.minor) { + return actualVersion.minor - v.minor; + } + if (actualVersion.patch != v.patch) { + return actualVersion.patch - v.patch; + } + + return 0; +} + +bool Platform_KernelVersionIsBetween(KernelVersion lowerBound, KernelVersion upperBound) { + return 0 <= Platform_CompareKernelVersion(lowerBound) + && Platform_CompareKernelVersion(upperBound) < 0; +} + +void Platform_getCPUBrandString(char* cpuBrandString, size_t cpuBrandStringSize) { + if (sysctlbyname("machdep.cpu.brand_string", cpuBrandString, &cpuBrandStringSize, NULL, 0) == -1) { + fprintf(stderr, + "WARN: Unable to determine the CPU brand string.\n" + "errno: %i, %s\n", errno, strerror(errno)); + + String_safeStrncpy(cpuBrandString, "UNKNOWN!", cpuBrandStringSize); + } +} + +// Adapted from https://developer.apple.com/documentation/apple-silicon/about-the-rosetta-translation-environment +bool Platform_isRunningTranslated(void) { + int ret = 0; + size_t size = sizeof(ret); + errno = 0; + if (sysctlbyname("sysctl.proc_translated", &ret, &size, NULL, 0) == -1) { + if (errno == ENOENT) + return false; + + fprintf(stderr, + "WARN: Could not determine if this process was running in a translation environment like Rosetta 2.\n" + "Assuming that we're not.\n" + "errno: %i, %s\n", errno, strerror(errno)); + + return false; + } + return ret; +} + +double Platform_calculateNanosecondsPerMachTick(void) { + // Check if we can determine the timebase used on this system. + // If the API is unavailable assume we get our timebase in nanoseconds. +#ifndef HAVE_MACH_TIMEBASE_INFO + return 1.0; +#else + mach_timebase_info_data_t info; + + /* WORKAROUND for `mach_timebase_info` giving incorrect values on M1 under Rosetta 2. + * rdar://FB9546856 https://openradar.appspot.com/radar?id=5055988478509056 + * + * We don't know exactly what feature/attribute of the M1 chip causes this mistake under Rosetta 2. + * Until we have more Apple ARM chips to compare against, the best we can do is special-case + * the "Apple M1" chip specifically when running under Rosetta 2. + */ + + size_t cpuBrandStringSize = 1024; + char cpuBrandString[cpuBrandStringSize]; + Platform_getCPUBrandString(cpuBrandString, cpuBrandStringSize); + + bool isRunningUnderRosetta2 = Platform_isRunningTranslated(); + + // Kernel version 20.0.0 is macOS 11.0 (Big Sur) + bool isBuggedVersion = Platform_KernelVersionIsBetween((KernelVersion) {20, 0, 0}, (KernelVersion) {999, 999, 999}); + + if (isRunningUnderRosetta2 && String_eq(cpuBrandString, "Apple M1") && isBuggedVersion) { + // In this case `mach_timebase_info` provides the wrong value, so we hard-code the correct factor, + // as determined from `mach_timebase_info` when the process running natively. + info = (mach_timebase_info_data_t) { .numer = 125, .denom = 3 }; + } else { + // No workarounds needed, use the OS-provided value. + mach_timebase_info(&info); + } + + return (double)info.numer / (double)info.denom; +#endif +} diff --git a/darwin/PlatformHelpers.h b/darwin/PlatformHelpers.h new file mode 100644 index 0000000..45aea1a --- /dev/null +++ b/darwin/PlatformHelpers.h @@ -0,0 +1,40 @@ +#ifndef HEADER_PlatformHelpers +#define HEADER_PlatformHelpers +/* +htop - darwin/PlatformHelpers.h +(C) 2018 Pierre Malhaire, 2020-2022 htop dev team, 2021 Alexander Momchilov +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include <stdbool.h> +#include <sys/types.h> + + +typedef struct KernelVersion { + short int major; + short int minor; + short int patch; +} KernelVersion; + +void Platform_GetKernelVersion(KernelVersion* k); + +/* compare the given os version with the one installed returns: +0 if equals the installed version +positive value if less than the installed version +negative value if more than the installed version +*/ +int Platform_CompareKernelVersion(KernelVersion v); + +// lowerBound <= currentVersion < upperBound +bool Platform_KernelVersionIsBetween(KernelVersion lowerBound, KernelVersion upperBound); + +double Platform_calculateNanosecondsPerMachTick(void); + +void Platform_getCPUBrandString(char* cpuBrandString, size_t cpuBrandStringSize); + +bool Platform_isRunningTranslated(void); + +double Platform_calculateNanosecondsPerMachTick(void); + +#endif diff --git a/darwin/ProcessField.h b/darwin/ProcessField.h new file mode 100644 index 0000000..05fbc3b --- /dev/null +++ b/darwin/ProcessField.h @@ -0,0 +1,18 @@ +#ifndef HEADER_DarwinProcessField +#define HEADER_DarwinProcessField +/* +htop - darwin/ProcessField.h +(C) 2020 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + + +#define PLATFORM_PROCESS_FIELDS \ + TRANSLATED = 100, \ + \ + DUMMY_BUMP_FIELD = CWD, \ + // End of list + + +#endif /* HEADER_DarwinProcessField */ |