Skip to content

Commit cd027be

Browse files
committed
Bring back conversion of process CPU time on macOS (#1638)
1 parent e1db96a commit cd027be

File tree

4 files changed

+69
-2
lines changed

4 files changed

+69
-2
lines changed

configure.ac

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,8 @@ return ptr != &ch; /* Should be 0 (exit success) */
396396
CFLAGS=$htop_save_CFLAGS
397397

398398
if test "$my_htop_platform" = darwin; then
399+
AC_CHECK_FUNCS([mach_timebase_info])
400+
399401
AC_CHECK_FUNCS([host_statistics64], [
400402
AC_CHECK_TYPES([struct vm_statistics64], [], [], [[#include <mach/vm_statistics.h>]])
401403
], [])

darwin/DarwinProcess.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -372,8 +372,8 @@ void DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessTable
372372
const DarwinMachine* dhost = (const DarwinMachine*) proc->super.super.host;
373373

374374
uint64_t total_existing_time_ns = proc->stime + proc->utime;
375-
uint64_t user_time_ns = pti.pti_total_user;
376-
uint64_t system_time_ns = pti.pti_total_system;
375+
uint64_t user_time_ns = Platform_machTicksToNanoseconds(pti.pti_total_user);
376+
uint64_t system_time_ns = Platform_machTicksToNanoseconds(pti.pti_total_system);
377377
uint64_t total_current_time_ns = user_time_ns + system_time_ns;
378378

379379
if (total_existing_time_ns < total_current_time_ns) {

darwin/Platform.c

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,72 @@ const MeterClass* const Platform_meterTypes[] = {
151151
NULL
152152
};
153153

154+
static uint64_t Platform_nanosecondsPerMachTickNumer = 1;
155+
static uint64_t Platform_nanosecondsPerMachTickDenom = 1;
156+
154157
static double Platform_nanosecondsPerSchedulerTick = -1;
155158

156159
static mach_port_t iokit_port; // the mach port used to initiate communication with IOKit
157160

161+
static void Platform_calculateNanosecondsPerMachTick(uint64_t* numer, uint64_t* denom) {
162+
// Check if we can determine the timebase used on this system.
163+
164+
#ifdef __x86_64__
165+
/* WORKAROUND for `mach_timebase_info` giving incorrect values on M1 under Rosetta 2.
166+
* rdar://FB9546856 http://www.openradar.appspot.com/FB9546856
167+
*
168+
* We don't know exactly what feature/attribute of the M1 chip causes this mistake under Rosetta 2.
169+
* Until we have more Apple ARM chips to compare against, the best we can do is special-case
170+
* the "Apple M1" chip specifically when running under Rosetta 2.
171+
*
172+
* Rosetta 2 only supports x86-64, so skip this workaround when building for other architectures.
173+
*/
174+
175+
bool isRunningUnderRosetta2 = Platform_isRunningTranslated();
176+
177+
// Kernel versions >= 20.0.0 (macOS 11.0 AKA Big Sur) affected
178+
bool isBuggedVersion = 0 <= Platform_CompareKernelVersion((KernelVersion) {20, 0, 0});
179+
180+
if (isRunningUnderRosetta2 && isBuggedVersion) {
181+
// In this case `mach_timebase_info` provides the wrong value, so we hard-code the correct factor,
182+
// as determined from `mach_timebase_info` as if the process was running natively.
183+
*numer = 125;
184+
*denom = 3;
185+
return;
186+
}
187+
#endif
188+
189+
#ifdef HAVE_MACH_TIMEBASE_INFO
190+
mach_timebase_info_data_t info = { 0 };
191+
if (mach_timebase_info(&info) == KERN_SUCCESS) {
192+
*numer = info.numer;
193+
*denom = info.denom;
194+
return;
195+
}
196+
#endif
197+
198+
// No info on actual timebase found; assume timebase in nanoseconds.
199+
*numer = 1;
200+
*denom = 1;
201+
}
202+
203+
// Converts ticks in the Mach "timebase" to nanoseconds.
204+
// See `mach_timebase_info`, as used to define the `Platform_nanosecondsPerMachTick` constant.
205+
uint64_t Platform_machTicksToNanoseconds(uint64_t mach_ticks) {
206+
uint64_t ticks_quot = mach_ticks / Platform_nanosecondsPerMachTickDenom;
207+
uint64_t ticks_rem = mach_ticks % Platform_nanosecondsPerMachTickDenom;
208+
209+
uint64_t part1 = ticks_quot * Platform_nanosecondsPerMachTickNumer;
210+
211+
// When Platform_nanosecondsPerMachTickDenom * Platform_nanosecondsPerMachTickNumer is less than 2^64, ticks_rem *
212+
// Platform_nanosecondsPerMachTickNumer will be less than 2^64 as well, i.e. never overflows.
213+
uint64_t part2 = (ticks_rem * Platform_nanosecondsPerMachTickNumer) / Platform_nanosecondsPerMachTickDenom;
214+
215+
return part1 + part2;
216+
}
217+
158218
bool Platform_init(void) {
219+
Platform_calculateNanosecondsPerMachTick(&Platform_nanosecondsPerMachTickNumer, &Platform_nanosecondsPerMachTickDenom);
159220

160221
// Determine the number of scheduler clock ticks per second
161222
errno = 0;

darwin/Platform.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ extern const MeterClass* const Platform_meterTypes[];
3838

3939
bool Platform_init(void);
4040

41+
// Converts ticks in the Mach "timebase" to nanoseconds.
42+
// See `mach_timebase_info`, as used to define the `Platform_nanosecondsPerMachTick*` constants.
43+
uint64_t Platform_machTicksToNanoseconds(uint64_t mach_ticks);
44+
4145
// Converts "scheduler ticks" to nanoseconds.
4246
// See `sysconf(_SC_CLK_TCK)`, as used to define the `Platform_nanosecondsPerSchedulerTick` constant.
4347
double Platform_schedulerTicksToNanoseconds(const double scheduler_ticks);

0 commit comments

Comments
 (0)