Потоки в OS X: как получить CPU usage всех потоков в чужой программе?
В [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;
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; }
// 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; }
Теперь дело за малым: пробежаться по всем полученным тредам и вытянуть из них нужную нам информацию:
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 и подписать полученный бинарник!
Внимание стоит обратить на последний ключ: SecTaskAccess, именно он нам нужен. Вот теперь нам надо в Makefile внести изменения: добавлять наш плист во время линковки:
Ну вот, теперь почти всё, система поймёт, какие права нужны программе для успешной работы. Но не даст их программе, пока мы её не подпишем нашим сертификатом разработчика.
Тут можно долго рассуждать о сертификатах и ключах, о 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%