diff --git a/host/src/macos_host.zig b/host/src/macos_host.zig index 231c1f8d..4ff4eb78 100644 --- a/host/src/macos_host.zig +++ b/host/src/macos_host.zig @@ -1358,6 +1358,24 @@ test "runtime socket root is short, per-user, and data-root-specific" { try std.testing.expect(!std.mem.eql(u8, first, second)); } +test "process CPU samples report nanoseconds" { + var before: posix.timespec = undefined; + var after: posix.timespec = undefined; + var footprint: u64 = 0; + var cpu_time_ns: u64 = 0; + var threads: u32 = 0; + try std.testing.expectEqual(.SUCCESS, posix.errno(posix.system.clock_gettime(.PROCESS_CPUTIME_ID, &before))); + try std.testing.expectEqual(@as(c_int, 0), c.weaver_process_sample(posix.system.getpid(), &footprint, &cpu_time_ns, &threads)); + try std.testing.expectEqual(.SUCCESS, posix.errno(posix.system.clock_gettime(.PROCESS_CPUTIME_ID, &after))); + + const before_ns: u64 = @intCast(before.sec * std.time.ns_per_s + before.nsec); + const after_ns: u64 = @intCast(after.sec * std.time.ns_per_s + after.nsec); + // Darwin's process CPU clock sums getrusage's user and system times, + // each truncated to microseconds. Allow only that loss of precision. + try std.testing.expect(cpu_time_ns >= before_ns); + try std.testing.expect(cpu_time_ns <= after_ns + 2 * std.time.ns_per_us); +} + test "provider socket peer pid rejects a same-user hijacker pid" { var path_buffer: [96]u8 = undefined; const path = try std.fmt.bufPrint(&path_buffer, "/tmp/weaver-peer-test-{d}.sock", .{posix.system.getpid()}); diff --git a/host/src/macos_system.c b/host/src/macos_system.c index fb302ebf..27afe31f 100644 --- a/host/src/macos_system.c +++ b/host/src/macos_system.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -55,10 +56,15 @@ int weaver_process_sample(int32_t pid, uint64_t *physical_footprint, if (!physical_footprint || !cpu_time_ns || !threads) return -1; struct rusage_info_v4 usage; struct proc_taskinfo task; + mach_timebase_info_data_t timebase; if (proc_pid_rusage(pid, RUSAGE_INFO_V4, (rusage_info_t *)&usage) != 0) return -1; if (proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &task, sizeof(task)) != sizeof(task)) return -1; + if (mach_timebase_info(&timebase) != KERN_SUCCESS || timebase.denom == 0) return -1; *physical_footprint = usage.ri_phys_footprint; - *cpu_time_ns = usage.ri_user_time + usage.ri_system_time; + // proc_pid_rusage reports Mach absolute-time ticks, not nanoseconds. + // Widen before scaling so a long-running process cannot overflow the product. + const __uint128_t cpu_ticks = (__uint128_t)usage.ri_user_time + usage.ri_system_time; + *cpu_time_ns = (uint64_t)(cpu_ticks * timebase.numer / timebase.denom); *threads = task.pti_threadnum < 0 ? 0 : (uint32_t)task.pti_threadnum; return 0; }