Counting Page Faults in MacOS
tl;dr use task_info(), scroll down for the snippet.
Early on in Casey Muratori's Computer Enhance course, he covers using Windows' Performance Monitor to track the number of page faults for a process, and sets you to do the same for homework.
I know how to do this on Linux, but I got to keep my M1 Mac laptop after a previous job exploded and it's become by go-to ever since.
Neither the Activity Monitor, nor the built-in Instruments tool seem to have this information, and I was a little stumped.
It seemed like maybe you can use dtrace, an equivalent to Linux' strace, but with the added twist you need to boot into some recovery mode to disable a protection system to use it!
I did not like this idea, mostly because I didn't want to open my laptop lid and press the power button.
After some more sleuthing I found that top has this information.
It was at this point I realised I have no idea where top comes from, this tool I use all the time that is written by actual humans, but I couldn't find a name on it anywhere!
On Linux, it seems like it's part of something distributed as procps-ng, but after finding the source code it became obvious that that was not the top I was running.
As it turns out, Apple have their own version, which is open source, they even put it on GitHub.
Presumably there are actual humans at Apple, whose job it is to maintain write top, what fun!
With some digging around, and judicious use of ctags, I eventually worked out the information comes from task_info().
This is part of the task interface of Mac, inherited from the Mach kernel which… came from Carnegie Mellon, but is also used in GNU Hurd?
I don't know to be honest, I checked out at this point from the historical side.
The official documentation of this task stuff is useless, with the stuff in the archive being much more complete1.
Finally, after slapping the keyboard and including random .h files, we get:
#include <stdio.h> #include <mach/task.h> #include <mach/task_info.h> #include <mach/mach_types.h> #include <mach/mach_init.h> #include <mach/mach_error.h> #include <unistd.h> #include <stdlib.h> int main(int argc, char *argv[]) { /* First argument is the PID of the process to profile. */ if (argc != 2) { printf("The first and only argument should be the PID of the process to profile\n"); return 1; } int their_pid = atoi(argv[1]); if (their_pid == 0) { printf("First argument must be a numeric PID \n"); return 1; } /* Getting the port of the task to profile */ mach_port_t their_task = 0; kern_return_t kr = task_for_pid( mach_task_self(), their_pid, &their_task ); if( kr != KERN_SUCCESS ) { printf( "task_for_pid failed: %s\n", mach_error_string( kr ) ); return kr; } /* Querying the info for the task */ task_events_info_data_t info = {0}; mach_msg_type_number_t count = TASK_EVENTS_INFO_COUNT; for(;;){ kr = task_info(their_task, TASK_EVENTS_INFO, (task_info_t)&info, &count); if (kr != KERN_SUCCESS) { printf("task_info failed: %s\n", mach_error_string(kr)); return kr; } printf("Page faults: %d\n", info.faults); printf("Copy-on-write: %d\n", info.cow_faults); printf("Pageins: %d\n", info.pageins); printf("Syscalls (mach): %d\n", info.syscalls_mach); printf("Syscalls (unix): %d\n", info.syscalls_unix); printf("Context switches: %d\n", info.csw); printf("------------------------\n"); sleep(1); } return 0; }
You have to run the command with sudo (or probably gain permissions in some other way), but the good news is you don't need to disable the SIP protection thingy.
$ sudo ./a.out 75034 Page faults: 657263 Copy-on-write: 66 Pageins: 0 Syscalls (mach): 123 Syscalls (unix): 1355262 Context switches: 3065
And with a bit of Matthias Wandel inspired ASCII plotting2, I can even recreate the sawtooth pattern in page faults that Casey had.
: 0
: 0
##### : 5269
##############################: 39260
##############################: 38801
################# : 17824
: 0
##################### : 21077
##############################: 38950
##############################: 38953
##############################: 38985
##############################: 39056
##############################: 39013
###################### : 22703
: 0
: 971
Of course, if you want to know why we're doing this, you'll have to take the course! Join me, and send me an email if you do :) mailto:joshua@joshuao.com