From e266590813c4218133f187ab68d0fa99a60272fa Mon Sep 17 00:00:00 2001 From: Ondrej Baudys <53287664+obaudys@users.noreply.github.com> Date: Thu, 1 Sep 2022 16:09:52 +1000 Subject: [PATCH] Fix ps command CPU usage on Apple Silicon M1 macs. #4142 (#6457) * Fix ps command CPU usage on Apple Silicon M1 macs. #4142 The cpu user and system times returned my libproc are not in nanoseconds; they are in mach ticks units. This is not documented very well. The convert from mach ticks to ns, the kernel provides a timebase info function and datatype: https://developer.apple.com/documentation/driverkit/3433733-mach_timebase_info The commit makes the PS command work for me. * Cargo fmt of previous commit. * Clippy format suggestion Co-authored-by: Ondrej Baudys --- Cargo.lock | 10 ++++++++++ crates/nu-system/Cargo.toml | 1 + crates/nu-system/src/macos.rs | 23 ++++++++++++++++++++--- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b1ae71ccca..06bbfaf1f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2219,6 +2219,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" +[[package]] +name = "mach2" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d0d1830bcd151a6fc4aea1369af235b36c1528fe976b8ff678683c9995eade8" +dependencies = [ + "libc", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -2863,6 +2872,7 @@ dependencies = [ "errno", "libc", "libproc", + "mach2", "nix", "ntapi", "once_cell", diff --git a/crates/nu-system/Cargo.toml b/crates/nu-system/Cargo.toml index 804753696f..aabd50e472 100644 --- a/crates/nu-system/Cargo.toml +++ b/crates/nu-system/Cargo.toml @@ -26,6 +26,7 @@ procfs = "0.14.0" [target.'cfg(target_os = "macos")'.dependencies] libproc = "0.12.0" errno = "0.2" +mach2 = "0.4" [target.'cfg(target_os = "windows")'.dependencies] winapi = { version = "0.3.9", features = ["tlhelp32", "fileapi", "handleapi", "ifdef", "ioapiset", "minwindef", "pdh", "psapi", "synchapi", "sysinfoapi", "winbase", "winerror", "winioctl", "winnt", "oleauto", "wbemcli", "rpcdce", "combaseapi", "objidl", "powerbase", "netioapi", "lmcons", "lmaccess", "lmapibuf", "memoryapi", "shellapi", "std", "securitybaseapi"] } diff --git a/crates/nu-system/src/macos.rs b/crates/nu-system/src/macos.rs index 2532b51632..ad440d71b7 100644 --- a/crates/nu-system/src/macos.rs +++ b/crates/nu-system/src/macos.rs @@ -6,6 +6,7 @@ use libproc::libproc::pid_rusage::{pidrusage, RUsageInfoV2}; use libproc::libproc::proc_pid::{listpidinfo, listpids, pidinfo, ListThreads, ProcType}; use libproc::libproc::task_info::{TaskAllInfo, TaskInfo}; use libproc::libproc::thread_info::ThreadInfo; +use mach2::mach_time; use std::cmp; use std::ffi::OsStr; use std::path::{Path, PathBuf}; @@ -372,9 +373,10 @@ impl ProcessInfo { self.curr_task.ptinfo.pti_total_user + self.curr_task.ptinfo.pti_total_system; let prev_time = self.prev_task.ptinfo.pti_total_user + self.prev_task.ptinfo.pti_total_system; - let usage_ms = (curr_time - prev_time) / 1000000u64; - let interval_ms = self.interval.as_secs() * 1000 + u64::from(self.interval.subsec_millis()); - usage_ms as f64 * 100.0 / interval_ms as f64 + let usage_ticks = curr_time - prev_time; + let interval_us = self.interval.as_micros(); + let ticktime_us = mach_ticktime() / 1000.0; + usage_ticks as f64 * 100.0 * ticktime_us / interval_us as f64 } /// Memory size in number of bytes @@ -387,3 +389,18 @@ impl ProcessInfo { self.curr_task.ptinfo.pti_virtual_size } } + +/// The Macos kernel returns process times in mach ticks rather than nanoseconds. To get times in +/// nanoseconds, we need to multiply by the mach timebase, a fractional value reported by the +/// kernel. It is uncertain if the kernel returns the same value on each call to +/// mach_timebase_info; if it does, it may be worth reimplementing this as a lazy_static value. +fn mach_ticktime() -> f64 { + let mut timebase = mach_time::mach_timebase_info_data_t::default(); + let err = unsafe { mach_time::mach_timebase_info(&mut timebase) }; + if err == 0 { + timebase.numer as f64 / timebase.denom as f64 + } else { + // assume times are in nanoseconds then... + 1.0 + } +}