📜 ⬆️ ⬇️

Threads in OS X: How to get the CPU usage of all the threads in another program?

Good afternoon, dear habrovchane-makovody!

[Mac] OS X has a great built-in tool, the Activity Monitor, which easily shows the process memory and processor time. Well, this is very good, but sometimes you want a strange one. For example, see how many threads a process has (threads) and how many CPUs each of them eat. Here, the Activity Monitor cannot help us, alas, and the experienced Linux system will not find the procfs 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 accept the PID of the process at the input and give information about the CPU usage of each stream of this program (as well as the general usage) at the output.
')
We will write on pure C , 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 from our program to connect to some third-party, request its list of streams and get the properties of each stream. To do this, we need to use functions to work with tasks and their threads: task_for_pid() and task_threads() .

But not everything is so simple, alas. To use these functions, you need special rights for the program (let's call it threadmon , but this is not essential). As competent sources suggest, there was nothing required before Mac OS X 10.5, but then for security reasons, such restrictions were introduced. And this all means that we will need to sign our executable file with our certificate, and also before requesting our functions, request the user the right to execute them through the Security framework. Well, let's start from the beginning: write a function requesting user rights:

 #include <Security/Authorization.h> 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 for the taskport privilege necessary for successfully invoking task_for_pid() . Now we need at the beginning of the main() function to call acquireTaskportRight() and check the return value: 0 - everything is OK, otherwise - no privileges are 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 main() function:

 int main(int argc, char *argv[]) { if (argc != 2) { printf("Usage:\n %s <PID>\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 come to the most interesting. We will receive a task and all its streams from 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 up to the small: run through all the threads received and extract the necessary information 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. Little 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 plist:

 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>CFBundleDevelopmentRegion</key> <string>English</string> <key>CFBundleIdentifier</key> <string>com.silvansky.threadmon</string> <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundleName</key> <string>threadmon</string> <key>CFBundleVersion</key> <string>1.0</string> <key>SecTaskAccess</key> <array> <string>allowed</string> </array> </dict> </plist> 

Attention should be SecTaskAccess 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 for successful work. But it will not 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 the Developer ID and so on, but I will only briefly describe the situation: if you have a Developer ID certificate, then sign them boldly. If not, you can generate a self-signed certificate for codesign via Keychain . But my last method did not work, but this, as they say, is a problem in OS X 10.8, on earlier systems it should start.

But, again, you can not sign, if you are not too lazy to type sudo every time before running this utility. =)

Signing:

 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, well worth it! For the full source code, as usual, I send you to the githab .

PS: The article uses code snippets taken from the responses to StackOverflow and various personal blogs.
PPS: If you know a better way, you see obvious and implicit defects in the code - do not hesitate to write comments!

Source: https://habr.com/ru/post/152134/


All Articles