
Threads in OS X: how to get CPU usage of all threads in a foreign program?
- Tutorial

The [Mac] OS X has built a wonderful tool - Activity Monitor, which will easily take to memory and processor time. Well, this is very good, but sometimes you want something strange. For example, see how many threads have threads and how much CPU each of them eats. Here already, Activity Monitor cannot help us in any way, alas, but
procfs
an experienced Linuxoid will not find the file system here. We'll have to solve this problem on our own. Today I will tell you how to write a small console program that will take the PID of the process as an input and output information about the CPU usage of each thread of this program (as well as general usage).
We will write on a cleanC , we will have only one source file, and I decided not to use Xcode for such a small project, let it be a regular Makefile.
First, a little theory. We need to connect to a third-party program from our program, request its list of threads and get the properties of each thread. To do this, we need to use functions to work with tasks and their flows:
task_for_pid()
and task_threads()
. But not so simple, alas. To use these functions, special rights are required for the program (let's call it
threadmon
, but it doesn’t matter). According to competent sources, up to Mac OS X 10.5, nothing was required, but then, for security reasons, such restrictions were introduced. And all this means that we will need to sign our executable file with our certificate, and also, before calling our functions, request the user the right to execute them through the Security framework . Well, let's start from the beginning: we will write a function requesting user rights:#include
int acquireTaskportRight()
{
OSStatus stat;
AuthorizationItem taskport_item[] = {{"system.privilege.taskport:"}};
AuthorizationRights rights = {1, taskport_item}, *out_rights = NULL;
AuthorizationRef author;
AuthorizationFlags auth_flags = kAuthorizationFlagExtendRights | kAuthorizationFlagPreAuthorize | kAuthorizationFlagInteractionAllowed | ( 1 << 5);
stat = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, auth_flags, &author);
if (stat != errAuthorizationSuccess)
{
return 1;
}
stat = AuthorizationCopyRights(author, &rights, kAuthorizationEmptyEnvironment, auth_flags, &out_rights);
if (stat != errAuthorizationSuccess)
{
return 1;
}
return 0;
}
Actually, this function will ask the user for the privileges of the taskport privilege necessary for a successful call
task_for_pid()
. Now we need to main()
call acquireTaskportRight()
and check the return value at the beginning of the function : 0 - everything is ok, otherwise - privileges are not received. Well, we write further. Let our input program receive the pid of the process for which we will receive information. We write in the function main()
:int main(int argc, char *argv[])
{
if (argc != 2)
{
printf("Usage:\n %s \n", argv[0]);
return -1;
}
if (acquireTaskportRight())
{
printf("No rights granted by user or some error occured! Terminating.\n");
return -2;
}
char* end;
pid_t pid = strtol(argv[1], &end, 10);
if (*end)
{
printf("Error: invalid PID given: \"%s\", terminating.\n", argv[1]);
return -3;
}
printf("Starting threadmon for PID %d\n", pid);
// TODO: the rest
}
Now we pass to the most interesting. We will receive the task and all its threads from the pid:
task_t port;
kern_return_t kr = task_for_pid(mach_task_self(), pid, &port);
if (kr != KERN_SUCCESS)
{
printf("task_for_pid() returned %d, terminating.\n", kr);
return -4;
}
thread_array_t thread_list;
mach_msg_type_number_t thread_count;
thread_info_data_t thinfo;
mach_msg_type_number_t thread_info_count;
thread_basic_info_t basic_info_th;
// get threads in the task
kr = task_threads(port, &thread_list, &thread_count);
if (kr != KERN_SUCCESS)
{
printf("task_threads() returned %d, terminating.\n", kr);
return -5;
}
Now it’s the small thing: go over all the received threads and pull out the information we need from them:
long tot_cpu = 0;
int j;
for (j = 0; j < thread_count; j++)
{
thread_info_count = THREAD_INFO_MAX;
kr = thread_info(thread_list[j], THREAD_BASIC_INFO, (thread_info_t)thinfo, &thread_info_count);
if (kr != KERN_SUCCESS)
{
printf("Thread %d: Error %d\n", thread_list[j], kr);
continue;
}
basic_info_th = (thread_basic_info_t)thinfo;
if (!(basic_info_th->flags & TH_FLAGS_IDLE))
{
tot_cpu = tot_cpu + basic_info_th->cpu_usage;
printf("Thread %d: CPU %d%%\n", thread_list[j], basic_info_th->cpu_usage);
}
}
printf("---\nTotal: CPU %ld%%\n", tot_cpu);
return 0;
Well, we got a completely viable program that can almost be used. A small nuance: the program still has no rights. We did not give them. We need to do two more things: add Info.plist and sign the resulting binary!
Create something like this:
CFBundleDevelopmentRegion English CFBundleIdentifier com.silvansky.threadmon CFBundleInfoDictionaryVersion 6.0 CFBundleName threadmon CFBundleVersion 1.0 SecTaskAccess allowed
Attention should be paid to the last key:
SecTaskAccess
we need it. Now we need to make changes to the Makefile: add our list during linking:LOPTS=-framework Security -framework CoreFoundation -sectcreate __TEXT __info_plist ./Info.plist
Well, now almost everything, the system will understand what rights the program needs to work successfully. But he won’t give them to the program until we sign it with our developer certificate.
Here you can talk for a long time about certificates and keys, about Developer ID and more, but I will only briefly describe the situation: if you have a Developer ID certificate, then sign it boldly. If not, you can generate a self-signed certificate for codesign via Keychain . But my last method did not work for me, but this is said to be a problem in OS X 10.8, on earlier systems it should start.
But, again, you don’t have to sign it, if you are not too lazy to type each time
sudo
before running this utility. =) We sign:
codesign -s "your-certificate-name" ./threadmon
Testing:
$ ps -A | grep Xcode
775 ?? 617:02.82 /Applications/Xcode.app/Contents/MacOS/Xcode -psn_0_348245
73761 ttys005 0:00.00 grep Xcode
$ ./threadmon 775
Starting threadmon for PID 775
Thread 6147: CPU 55%
Thread 6403: CPU 0%
Thread 6659: CPU 0%
Thread 6915: CPU 0%
Thread 7171: CPU 0%
Thread 7427: CPU 0%
Thread 7683: CPU 0%
Thread 7939: CPU 0%
Thread 8195: CPU 0%
Thread 8451: CPU 0%
Thread 8707: CPU 0%
Thread 8963: CPU 0%
Thread 9219: CPU 0%
Thread 9475: CPU 0%
Thread 9731: CPU 0%
Thread 9987: CPU 0%
Thread 10243: CPU 0%
Thread 10499: CPU 0%
Thread 10755: CPU 0%
Thread 11011: CPU 0%
Thread 11267: CPU 0%
Thread 11523: CPU 22%
Thread 11779: CPU 7%
Thread 12035: CPU 32%
Thread 12291: CPU 46%
Thread 12547: CPU 14%
Thread 12803: CPU 0%
---
Total: CPU 176%
Well, quite worthy! For the full source code, as usual, send you to github .
PS: The code uses snippets taken from answers to StackOverflow and various personal blogs.
PPS: If you know the method better, you see explicit and implicit defects in the code - feel free to write comments!