I'm working on an app that contains a mini system monitoring utility. I would like to list the top CPU-using processes.
As Quinn “The Eskimo!” has repeatedly cautioned, relying on private frameworks is just begging for maintenance effort in the future. Ideally, I want to go through public headers/frameworks.
I've gone to great lengths to try to find this information myself, and at this point I'm just struggling. I detail my research below.
Any pointers in the right direction would be much appreciated!
Attempts
Libproc
First I looked at libproc. Using proc_pidinfo with PROC_PIDTHREADINFO, I'm able to get each thread of an app, with its associated CPU usage percentage. Summing these, I could get the total for an app.
Unfortunately, this has two downsides:
Listing a table of processes now takes O(proces_count) rather than just O(process_count), and causes way more syscalls to be made
It doesn't work for processes owned by other users. Perhaps running as root could alleviate that, but that would involve making a priviliedged helper akin to the existing sysmond that Activity Monitor.app uses. I'm a little scared of that, because I don't want to put my users at risk.
Sysctl
Using the keys [CTL_KERN, KERN_PROC, KERN_PROC_PID, someProcessID], I'm able to get a kinfo_proc instance. Accessing its .kp_proc.p_pctcpu looked really promising, but that value is always zero.
Digging deeper, I found the kernel code that fills this struct in (fill_user64_externproc). The assignment of p_pctcpu is in a conditional region, relying on the _PROC_HAS_SCHEDINFO_ flag. Disassembling the kernel on my mac, I could confirm that the assignment of that field never happens (thus _PROC_HAS_SCHEDINFO_ wasn't set during compilation, and the value will always stay zero)
Reverse engineering Activity Monitor.app
Activity Monitor.app makes proc_info and sysctl system calls, but from looking at the disassembly, it doesn't look like that's where its CPU figures come from. From what I can tell, it's using private functions from /usr/lib/libsysmon.dylib.
That's a user library which wraps an XPC connection to sysmond (/usr/libexec/sysmond), allowing you to create requests (sysmon_request_create), add specific attributes you want to retrieve (sysmon_request_add_attribute), and then functions to query that data out (sysmon_row_get_value).
Getting the data "striaght from the horses mouth" like this sounds ideal. But unfortunately, the only documentation/usage I can find of sysmond is from bug databases demonstrating a privilege escalation vulnerability lol. There are some partial reverse engineered header files floating around, but they're incomplete, and have the usual fragility/upkeep issues associated with using private APIs.
On one hand, I don't want to depend on a private API, because that takes a lot of time to reverse engineer, keep up with changes, etc. On the other, making my own similar privileged helper would be duplicating effort, and expose a bigger attack surface. Needless to say, I have no confidence in being able to make a safer privileged helper than Apple's engineers lol
Reverse engineering iStat Menus
Looks like they're using proc_pid_rusage .
However, I don't know how to convert the cpu_*_time fields of the resulting struct rusage_info_v4 to compute a "simple" percentage. Even if I came up with some formula that produces plausible looking results, I have no real guarantee it's correct or equivalent to what Activity Monitor shows.