Добрый день, уважаемые хабровчане-маководы!В [Mac] OS X имеется замечательный встроенный инструмент — Activity Monitor, который легко покажет занимаемую процессом память и процессорное время. Что ж, это очень хорошо, но иногда хочется странного. Например, посмотреть, сколько у процесса потоков (threads) и сколько CPU кушает каждый из них. Тут уже Activity Monitor нам никак не может помочь, увы, а файловой системы
procfs здесь бывалый линуксоид не найдёт. Придётся решать эту проблему своими силами.Сегодня я поведаю вам о том, как написать маленькую консольную программку, которая будет на вход принимать PID процесса и на выходе давать информацию о CPU usage каждого потока этой программы (а так же общий usage).
Писать будем на чистом C, у нас будет всего один файл исходников, и я решил не использовать Xcode для такого мелкого проекта, пусть будет обычный Makefile.
Для начала немного теории. Нам надо из нашей программы подключиться к некоей сторонней, запросить её список потоков и получить свойства каждого потока. Для этого нам надо использовать функции для работы с задачами и их потоками:
task_for_pid() и task_threads().Но не всё так просто, увы. Для использования этих функций нужны особые права для программы (назовём её
threadmon, но это не принципиально). Как подсказывают компетентные источники, до версии Mac OS X 10.5 ничего не требовалось, но потом в целях безопасности были введены такие вот ограничения. А это всё значит, что нам надо будет подписать наш исполняемый файл своим сертификатом, а так же перед вызовом наших функций запросить у пользователя права на их исполнение через фреймворк Security. Что ж, начнём с начала: напишем функцию, запрашивающую права у пользователя:#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;
}
Собственно, данная функция запросит у пользователя права на привелегию taskport, необходимую для успешного вызова
task_for_pid(). Теперь нам надо в начале функции main() вызвать acquireTaskportRight() и проверить возвращаемое значение: 0 — всё ок, иначе — привелегии не получены. Что ж, пишем дальше. Пусть наша программа на вход получает pid процесса, для которого будем получать информацию. Пишем в функции main():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
}
Теперь переходим к самому интересному. Будем получать таск и все его потоки из 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;
}
Теперь дело за малым: пробежаться по всем полученным тредам и вытянуть из них нужную нам информацию:
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;
Что ж, мы получили вполне жизнеспособную программу, которую уже почти можно использовать. Маленький нюанс: прав у программы по прежнему нет. Не дали мы их. Нам нужно сделать ещё две вещи: добавить Info.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>
Внимание стоит обратить на последний ключ:
SecTaskAccess, именно он нам нужен. Вот теперь нам надо в Makefile внести изменения: добавлять наш плист во время линковки:LOPTS=-framework Security -framework CoreFoundation -sectcreate __TEXT __info_plist ./Info.plist
Ну вот, теперь почти всё, система поймёт, какие права нужны прог��амме для успешной работы. Но не даст их программе, пока мы её не подпишем нашим сертификатом разработчика.
Тут можно долго рассуждать о сертификатах и ключах, о Developer ID и прочем, но я лишь кратко опишу ситуацию: если у Вас есть Developer ID сертификат, то подписывайте им смело. Если же его нет, можете сгенерировать самоподписаный сертификат для codesign через Keychain. Но у меня последний способ не заработал, но это, как говорят, проблема в OS X 10.8, на более ранних системах должно завестись.
Но, опять же, можно и не подписывать, если Вам не лень каждый раз набирать
sudo перед запуском этой утилиты. =)Подписываем:
codesign -s "your-certificate-name" ./threadmon
Тестируем:
$ 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%
Ну что ж, вполне достойно! За полным исходным кодом, как обычно, отправляю Вас на гитхаб.
PS: В статье использованы фрагменты кода, взятые из ответов на StackOverflow и разнообразных личных блогов.
PPS: Если Вы знаете способ лучше, видите явные и неявные дефекты в коде — не стесняйтесь, пишите комменты!