diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 19:58:07 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 19:58:07 +0000 |
commit | 10eea2ab1bae2a8ec159d81c0446fd8061b33e2b (patch) | |
tree | e8270dd60ec096bee8157dbadf029e15ed584592 /darwin | |
parent | Initial commit. (diff) | |
download | htop-10eea2ab1bae2a8ec159d81c0446fd8061b33e2b.tar.xz htop-10eea2ab1bae2a8ec159d81c0446fd8061b33e2b.zip |
Adding upstream version 3.3.0.upstream/3.3.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | darwin/DarwinMachine.c | 119 | ||||
-rw-r--r-- | darwin/DarwinMachine.h | 28 | ||||
-rw-r--r-- | darwin/DarwinProcess.c | 492 | ||||
-rw-r--r-- | darwin/DarwinProcess.h | 46 | ||||
-rw-r--r-- | darwin/DarwinProcessTable.c | 126 | ||||
-rw-r--r-- | darwin/DarwinProcessTable.h | 22 | ||||
-rw-r--r-- | darwin/Platform.c | 609 | ||||
-rw-r--r-- | darwin/Platform.h | 141 | ||||
-rw-r--r-- | darwin/PlatformHelpers.c | 125 | ||||
-rw-r--r-- | darwin/PlatformHelpers.h | 40 | ||||
-rw-r--r-- | darwin/ProcessField.h | 18 |
11 files changed, 1766 insertions, 0 deletions
diff --git a/darwin/DarwinMachine.c b/darwin/DarwinMachine.c new file mode 100644 index 0000000..582d496 --- /dev/null +++ b/darwin/DarwinMachine.c @@ -0,0 +1,119 @@ +/* +htop - DarwinMachine.c +(C) 2014 Hisham H. Muhammad +(C) 2023 htop dev team +Released under the GNU GPLv2+, see the COPYING file +in the source distribution for its full text. +*/ + +#include "darwin/DarwinMachine.h" + +#include <errno.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/mman.h> +#include <sys/sysctl.h> + +#include "CRT.h" +#include "Machine.h" +#include "darwin/Platform.h" +#include "darwin/PlatformHelpers.h" +#include "generic/openzfs_sysctl.h" +#include "zfs/ZfsArcStats.h" + + +static void DarwinMachine_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 DarwinMachine_freeCPULoadInfo(processor_cpu_load_info_t* p) { + if (!p) + return; + + if (!*p) + return; + + if (0 != munmap(*p, vm_page_size)) { + CRT_fatalError("Unable to free old CPU load information"); + } + + *p = NULL; +} + +static unsigned DarwinMachine_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 would 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 DarwinMachine_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"); + } +} + +void Machine_scan(Machine* super) { + DarwinMachine* host = (DarwinMachine*) super; + + /* Update the global data (CPU times and VM stats) */ + DarwinMachine_freeCPULoadInfo(&host->prev_load); + host->prev_load = host->curr_load; + DarwinMachine_allocateCPULoadInfo(&host->curr_load); + DarwinMachine_getVMStats(&host->vm_stats); + openzfs_sysctl_updateArcStats(&host->zfs); +} + +Machine* Machine_new(UsersTable* usersTable, uid_t userId) { + DarwinMachine* this = xCalloc(1, sizeof(DarwinMachine)); + Machine* super = &this->super; + + Machine_init(super, usersTable, userId); + + /* Initialize the CPU information */ + super->activeCPUs = DarwinMachine_allocateCPULoadInfo(&this->prev_load); + super->existingCPUs = super->activeCPUs; + DarwinMachine_getHostInfo(&this->host_info); + DarwinMachine_allocateCPULoadInfo(&this->curr_load); + + /* Initialize the VM statistics */ + DarwinMachine_getVMStats(&this->vm_stats); + + /* Initialize the ZFS kstats, if zfs.kext loaded */ + openzfs_sysctl_init(&this->zfs); + openzfs_sysctl_updateArcStats(&this->zfs); + + return super; +} + +void Machine_delete(Machine* super) { + DarwinMachine* this = (DarwinMachine*) super; + + DarwinMachine_freeCPULoadInfo(&this->prev_load); + + Machine_done(super); + free(this); +} + +bool Machine_isCPUonline(const Machine* host, unsigned int id) { + assert(id < host->existingCPUs); + + // TODO: support offline CPUs and hot swapping + (void) host; (void) id; + + return true; +} diff --git a/darwin/DarwinMachine.h b/darwin/DarwinMachine.h new file mode 100644 index 0000000..3135b58 --- /dev/null +++ b/darwin/DarwinMachine.h @@ -0,0 +1,28 @@ +#ifndef HEADER_DarwinMachine +#define HEADER_DarwinMachine +/* +htop - DarwinMachine.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 "Machine.h" +#include "zfs/ZfsArcStats.h" + + +typedef struct DarwinMachine_ { + Machine 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; + + ZfsArcStats zfs; +} DarwinMachine; + +#endif diff --git a/darwin/DarwinProcess.c b/darwin/DarwinProcess.c new file mode 100644 index 0000000..1e315eb --- /dev/null +++ b/darwin/DarwinProcess.c @@ -0,0 +1,492 @@ +/* +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/DarwinMachine.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 Machine* host) { + DarwinProcess* this = xCalloc(1, sizeof(DarwinProcess)); + Object_setClass(this, Class(DarwinProcess)); + Process_init(&this->super, host); + + 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_rowWriteField(const Row* super, RichString* str, ProcessField field) { + const DarwinProcess* dp = (const DarwinProcess*) super; + + char buffer[256]; buffer[255] = '\0'; + int attr = CRT_colors[DEFAULT_COLOR]; + size_t 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(&dp->super, 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 Settings* settings = proc->super.host->settings; + + const struct extern_proc* ep = &ps->kp_proc; + + /* UNSET HERE : + * + * processor + * user (set at ProcessTable level) + * nlwp + * percent_cpu + * percent_mem + * m_virt + * m_resident + * minflt + * majflt + */ + + /* First, the "immutable" parts */ + if (!exists) { + /* Set the PID/PGID/etc. */ + Process_setPid(proc, ep->p_pid); + Process_setThreadGroup(proc, ep->p_pid); + Process_setParent(proc, 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->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 (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 (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->super.updated = true; +} + +void DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessTable* dpt, double timeIntervalNS) { + struct proc_taskinfo pti; + + if (sizeof(pti) == proc_pidinfo(Process_getPid(&proc->super), PROC_PIDTASKINFO, 0, &pti, sizeof(pti))) { + const DarwinMachine* dhost = (const DarwinMachine*) proc->super.super.host; + + 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)dhost->host_info.max_mem; + + proc->stime = system_time_ns; + proc->utime = user_time_ns; + + dpt->super.kernelThreads += 0; /*pti.pti_threads_system;*/ + dpt->super.userlandThreads += pti.pti_threadnum; /*pti.pti_threads_user;*/ + dpt->super.totalTasks += pti.pti_threadnum; + dpt->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(), Process_getPid(proc), &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 = { + .super = { + .extends = Class(Process), + .display = Row_display, + .delete = Process_delete, + .compare = Process_compare + }, + .isHighlighted = Process_rowIsHighlighted, + .isVisible = Process_rowIsVisible, + .matchesFilter = Process_rowMatchesFilter, + .compareByParent = Process_compareByParent, + .sortKeyString = Process_rowGetSortKey, + .writeField = DarwinProcess_rowWriteField + }, + .compareByKey = DarwinProcess_compareByKey +}; diff --git a/darwin/DarwinProcess.h b/darwin/DarwinProcess.h new file mode 100644 index 0000000..496b179 --- /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 "Machine.h" +#include "darwin/DarwinProcessTable.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 Machine* settings); + +void Process_delete(Object* cast); + +void DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps, bool exists); + +void DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessTable* dpt, 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/DarwinProcessTable.c b/darwin/DarwinProcessTable.c new file mode 100644 index 0000000..850b503 --- /dev/null +++ b/darwin/DarwinProcessTable.c @@ -0,0 +1,126 @@ +/* +htop - DarwinProcessTable.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/DarwinProcessTable.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 "ProcessTable.h" +#include "darwin/DarwinMachine.h" +#include "darwin/DarwinProcess.h" +#include "darwin/Platform.h" +#include "darwin/PlatformHelpers.h" +#include "generic/openzfs_sysctl.h" +#include "zfs/ZfsArcStats.h" + + +static struct kinfo_proc* ProcessTable_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"); +} + +ProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) { + DarwinProcessTable* this = xCalloc(1, sizeof(DarwinProcessTable)); + Object_setClass(this, Class(ProcessTable)); + + ProcessTable* super = &this->super; + ProcessTable_init(super, Class(DarwinProcess), host, pidMatchList); + + return super; +} + +void ProcessTable_delete(Object* cast) { + DarwinProcessTable* this = (DarwinProcessTable*) cast; + ProcessTable_done(&this->super); + free(this); +} + +void ProcessTable_goThroughEntries(ProcessTable* super) { + const Machine* host = super->super.host; + const DarwinMachine* dhost = (const DarwinMachine*) host; + DarwinProcessTable* dpt = (DarwinProcessTable*) super; + bool preExisting = true; + struct kinfo_proc* ps; + size_t count; + DarwinProcess* proc; + + /* Get the time difference */ + dpt->global_diff = 0; + for (unsigned int i = 0; i < host->existingCPUs; ++i) { + for (size_t j = 0; j < CPU_STATE_MAX; ++j) { + dpt->global_diff += dhost->curr_load[i].cpu_ticks[j] - dhost->prev_load[i].cpu_ticks[j]; + } + } + + const double time_interval_ns = Platform_schedulerTicksToNanoseconds(dpt->global_diff) / (double) host->activeCPUs; + + /* We use kinfo_procs for initial data since : + * + * 1) They always succeed. + * 2) They contain the basic information. + * + * We attempt to fill-in additional information with libproc. + */ + ps = ProcessTable_getKInfoProcs(&count); + + for (size_t i = 0; i < count; ++i) { + proc = (DarwinProcess*)ProcessTable_getProcess(super, ps[i].kp_proc.p_pid, &preExisting, DarwinProcess_new); + + DarwinProcess_setFromKInfoProc(&proc->super, &ps[i], preExisting); + DarwinProcess_setFromLibprocPidinfo(proc, dpt, 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(host->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) { + ProcessTable_add(super, &proc->super); + } + } + + free(ps); +} diff --git a/darwin/DarwinProcessTable.h b/darwin/DarwinProcessTable.h new file mode 100644 index 0000000..7467bfd --- /dev/null +++ b/darwin/DarwinProcessTable.h @@ -0,0 +1,22 @@ +#ifndef HEADER_DarwinProcessTable +#define HEADER_DarwinProcessTable +/* +htop - DarwinProcessTable.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 "ProcessTable.h" + + +typedef struct DarwinProcessTable_ { + ProcessTable super; + + uint64_t global_diff; +} DarwinProcessTable; + +#endif diff --git a/darwin/Platform.c b/darwin/Platform.c new file mode 100644 index 0000000..387910e --- /dev/null +++ b/darwin/Platform.c @@ -0,0 +1,609 @@ +/* +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 <net/if.h> +#include <net/if_types.h> +#include <net/route.h> +#include <sys/socket.h> +#include <sys/_types/_mach_port_t.h> + +#include <CoreFoundation/CFBase.h> +#include <CoreFoundation/CFDictionary.h> +#include <CoreFoundation/CFNumber.h> +#include <CoreFoundation/CFString.h> +#include <CoreFoundation/CoreFoundation.h> + +#include <IOKit/IOKitLib.h> +#include <IOKit/IOTypes.h> +#include <IOKit/ps/IOPowerSources.h> +#include <IOKit/ps/IOPSKeys.h> +#include <IOKit/storage/IOBlockStorageDriver.h> + +#include "ClockMeter.h" +#include "CPUMeter.h" +#include "CRT.h" +#include "DateMeter.h" +#include "DateTimeMeter.h" +#include "FileDescriptorMeter.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/DarwinMachine.h" +#include "darwin/PlatformHelpers.h" +#include "generic/fdstat_sysctl.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, + &DiskIOMeter_class, + &NetworkIOMeter_class, + &FileDescriptorMeter_class, + &BlankMeter_class, + NULL +}; + +static double Platform_nanosecondsPerMachTick = 1.0; + +static double Platform_nanosecondsPerSchedulerTick = -1; + +static bool iokit_available = false; +static mach_port_t iokit_port; // the mach port used to initiate communication with IOKit + +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; + + // Since macOS 12.0, IOMasterPort is deprecated, and one should use IOMainPort instead + #if defined(HAVE_DECL_IOMAINPORT) && HAVE_DECL_IOMAINPORT + if (!IOMainPort(bootstrap_port, &iokit_port)) { + iokit_available = true; + } + #elif defined(HAVE_DECL_IOMASTERPORT) && HAVE_DECL_IOMASTERPORT + if (!IOMasterPort(bootstrap_port, &iokit_port)) { + iokit_available = true; + } + #endif + + 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; + } +} + +pid_t 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 Machine* host = mtr->host; + unsigned int activeCPUs = host->activeCPUs; + double sumNice = 0.0; + double sumNormal = 0.0; + double sumKernel = 0.0; + double sumPercent = 0.0; + for (unsigned int i = 1; i <= host->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 DarwinMachine* dhost = (const DarwinMachine*) mtr->host; + const processor_cpu_load_info_t prev = &dhost->prev_load[cpu - 1]; + const processor_cpu_load_info_t curr = &dhost->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 DarwinMachine* dhost = (const DarwinMachine*) mtr->host; + const struct vm_statistics* vm = &dhost->vm_stats; + double page_K = (double)vm_page_size / (double)1024; + + mtr->total = dhost->host_info.max_mem / 1024; + mtr->values[MEMORY_METER_USED] = (double)(vm->active_count + vm->wire_count) * page_K; + // mtr->values[MEMORY_METER_SHARED] = "shared memory, like tmpfs and shm" + // mtr->values[MEMORY_METER_COMPRESSED] = "compressed memory, like zswap on linux" + mtr->values[MEMORY_METER_BUFFERS] = (double)vm->purgeable_count * page_K; + 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; + // mtr->values[SWAP_METER_CACHE] = "pages that are both in swap and RAM, like SwapCached on linux" + // mtr->values[SWAP_METER_FRONTSWAP] = "pages that are accounted to swap but stored elsewhere, like frontswap on linux" +} + +void Platform_setZfsArcValues(Meter* this) { + const DarwinMachine* dhost = (const DarwinMachine*) this->host; + + ZfsArcMeter_readStats(this, &dhost->zfs); +} + +void Platform_setZfsCompressedArcValues(Meter* this) { + const DarwinMachine* dhost = (const DarwinMachine*) this->host; + + ZfsCompressedArcMeter_readStats(this, &dhost->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; +} + +void Platform_getFileDescriptors(double* used, double* max) { + Generic_getFileDescriptors_sysctl(used, max); +} + +bool Platform_getDiskIO(DiskIOData* data) { + if (!iokit_available) + return false; + + io_iterator_t drive_list; + + /* Get the list of all drives */ + if (IOServiceGetMatchingServices(iokit_port, IOServiceMatching("IOBlockStorageDriver"), &drive_list)) + return false; + + unsigned long long int read_sum = 0, write_sum = 0, timeSpend_sum = 0; + + io_registry_entry_t drive; + while ((drive = IOIteratorNext(drive_list)) != 0) { + CFMutableDictionaryRef properties_tmp = NULL; + + /* Get the properties of this drive */ + if (IORegistryEntryCreateCFProperties(drive, &properties_tmp, kCFAllocatorDefault, 0)) { + IOObjectRelease(drive); + IOObjectRelease(drive_list); + return false; + } + + if (!properties_tmp) { + IOObjectRelease(drive); + continue; + } + + CFDictionaryRef properties = properties_tmp; + + /* Get the statistics of this drive */ + CFDictionaryRef statistics = (CFDictionaryRef) CFDictionaryGetValue(properties, CFSTR(kIOBlockStorageDriverStatisticsKey)); + + if (!statistics) { + CFRelease(properties); + IOObjectRelease(drive); + continue; + } + + CFNumberRef number; + unsigned long long int value; + + /* Get bytes read */ + number = (CFNumberRef) CFDictionaryGetValue(statistics, CFSTR(kIOBlockStorageDriverStatisticsBytesReadKey)); + if (number != 0) { + CFNumberGetValue(number, kCFNumberSInt64Type, &value); + read_sum += value; + } + + /* Get bytes written */ + number = (CFNumberRef) CFDictionaryGetValue(statistics, CFSTR(kIOBlockStorageDriverStatisticsBytesWrittenKey)); + if (number != 0) { + CFNumberGetValue(number, kCFNumberSInt64Type, &value); + write_sum += value; + } + + /* Get total read time (in ns) */ + number = (CFNumberRef) CFDictionaryGetValue(statistics, CFSTR(kIOBlockStorageDriverStatisticsTotalReadTimeKey)); + if (number != 0) { + CFNumberGetValue(number, kCFNumberSInt64Type, &value); + timeSpend_sum += value; + } + + /* Get total write time (in ns) */ + number = (CFNumberRef) CFDictionaryGetValue(statistics, CFSTR(kIOBlockStorageDriverStatisticsTotalWriteTimeKey)); + if (number != 0) { + CFNumberGetValue(number, kCFNumberSInt64Type, &value); + timeSpend_sum += value; + } + + CFRelease(properties); + IOObjectRelease(drive); + } + + data->totalBytesRead = read_sum; + data->totalBytesWritten = write_sum; + data->totalMsTimeSpend = timeSpend_sum / 1e6; /* Convert from ns to ms */ + + if (drive_list) + IOObjectRelease(drive_list); + + return true; +} + +/* Caution: Given that interfaces are dynamic, and it is not possible to get statistics on interfaces that no longer exist, + if some interface disappears between the time of two samples, the values of the second sample may be lower than those of + the first one. */ +bool Platform_getNetworkIO(NetworkIOData* data) { + int mib[6] = {CTL_NET, + PF_ROUTE, /* routing messages */ + 0, /* protocal number, currently always 0 */ + 0, /* select all address families */ + NET_RT_IFLIST2, /* interface list with addresses */ + 0}; + + for (size_t retry = 0; retry < 4; retry++) { + size_t len = 0; + + /* Determine len */ + if (sysctl(mib, ARRAYSIZE(mib), NULL, &len, NULL, 0) < 0 || len == 0) + return false; + + len += 16 * retry * retry * sizeof(struct if_msghdr2); + char *buf = xMalloc(len); + + if (sysctl(mib, ARRAYSIZE(mib), buf, &len, NULL, 0) < 0) { + free(buf); + if (errno == ENOMEM && retry < 3) + continue; + else + return false; + } + + uint64_t bytesReceived_sum = 0, packetsReceived_sum = 0, bytesTransmitted_sum = 0, packetsTransmitted_sum = 0; + + for (char *next = buf; next < buf + len;) { + void *tmp = (void*) next; + struct if_msghdr *ifm = (struct if_msghdr*) tmp; + + next += ifm->ifm_msglen; + + if (ifm->ifm_type != RTM_IFINFO2) + continue; + + struct if_msghdr2 *ifm2 = (struct if_msghdr2*) ifm; + + if (ifm2->ifm_data.ifi_type != IFT_LOOP) { /* do not count loopback traffic */ + bytesReceived_sum += ifm2->ifm_data.ifi_ibytes; + packetsReceived_sum += ifm2->ifm_data.ifi_ipackets; + bytesTransmitted_sum += ifm2->ifm_data.ifi_obytes; + packetsTransmitted_sum += ifm2->ifm_data.ifi_opackets; + } + } + + data->bytesReceived = bytesReceived_sum; + data->packetsReceived = packetsReceived_sum; + data->bytesTransmitted = bytesTransmitted_sum; + data->packetsTransmitted = packetsTransmitted_sum; + + free(buf); + } + + return true; +} + +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..f67db8f --- /dev/null +++ b/darwin/Platform.h @@ -0,0 +1,141 @@ +#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); + +pid_t 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); + +void Platform_getFileDescriptors(double* used, double* max); + +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_dynamicColumnName(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; +} + +static inline Hashtable* Platform_dynamicScreens(void) { + return NULL; +} + +static inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { } + +static inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { } + +static inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { } + +static inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { } + +#endif diff --git a/darwin/PlatformHelpers.c b/darwin/PlatformHelpers.c new file mode 100644 index 0000000..a4ea82b --- /dev/null +++ b/darwin/PlatformHelpers.c @@ -0,0 +1,125 @@ +/* +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. + */ + + char cpuBrandString[1024] = ""; + Platform_getCPUBrandString(cpuBrandString, sizeof(cpuBrandString)); + + 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 */ |