2016-03-15 1 views
3

Для контроля производительности в ОС Windows мне нужна программа, которая может сообщать как пользовательское, так и ядерное время для произвольного процесса. В системах POSIX стандартная утилита time отлично работает, так как она сообщает часы настенных часов, время пользователя и время ядра.Как получить ручки для всех дочерних процессов текущего процесса в Windows?

Для Windows такая утилита по умолчанию отсутствует. Я огляделся и нашел по крайней мере три альтернативы. Как я объясню ниже, ни один из них не соответствует моим потребностям.

  1. от Windows SDK (не помню, какая именно версия). Он больше не распространяется, не поддерживается или не гарантируется для работы на современных системах. Я не смог его проверить.
  2. Cygwin's time. Почти идентичен POSIX-аналогу с аналогичным форматированием вывода.
  3. timep.exe от Johnson (John) Hart, доступен в source code и двоичных файлах для его книги «Системное программирование Windows, 4-е издание». Это довольно простая утилита, которая использует WinAPI's GetProcessTimes() для получения тех же трех значений. Я подозреваю, что Cygwin's time не отличается в этом отношении.

Теперь проблема: GetProcessTimes() сообщает только раз для PID непосредственно порождены timep, но не его дети. Это делает для меня time и timep.

My target EXE-приложение обычно генерируется через файл BAT, который вызывает еще один BAT-файл; оба НДТЫ предназначены для настройки функции окружающей среды или изменить параметры командной строки: сообщение о

timep.exe 
|  
+---wrapper.bat 
       | 
       +--- real-wrapper.bat 
            | 
            +--- application.exe 

Времен для wrapper.bat одиночку ничего не говорят о application.exe. Очевидно, что модели создания процессов POSIX (fork-exec) и Win32 (CreateProcess) очень разные, что делает мою задачу очень трудной для Windows.

Я хочу попытаться написать свой собственный вариант time. Он должен суммировать время для данного процесса и всех его детей, внуков и т. Д., Рекурсивно. До сих пор я могу представить себе следующий подход:

  1. CreateProcess() и получить его PID (корень PID) и ручка; добавьте этот дескриптор в список
  2. Перечислите все процессы в системе; для каждого процесса
    1. Сравните его ПИД с корневым ПИД-регулятором. Если он равен, получите PID и обработайте его, добавьте его в список дескрипторов.
    2. Для каждого нового PID, фазы сканирования повторите процесс, чтобы собрать больше детей ручка
    3. RECURSE вниз, пока нет новых ручек процесса не добавляются в список
  3. Подождите все собранные ручки из списка прекратить.
  4. Для каждой ручки, вызовите GetProcessTimes() и просуммировать их
  5. Report Результаты

Этот алгоритм плохо, потому что это колоритный - дети процессы могут быть созданы в конце жизни любого процесса, или же они могут прекратить прежде чем мы получим возможность получить их ручку. В обоих случаях сообщаемые результаты будут неверными.

Мой вопрос: Есть ли лучшее решение?


EDIT: Я был в состоянии достичь своей цели, используя Job Objects. Ниже приведен фрагмент кода, извлеченный из моего приложения, имеющий отношение к получению времени ядра и пользователя из процесса и всех его дочерних элементов. Надеюсь, это сэкономит время для кого-то.

Я тестировал его с Windows 8.1 x64 и VS 2015, но он должен быть обратно переносимым, по крайней мере, в Windows 7. Для 32-разрядных хостов (я не уверен) может потребоваться некоторое прошивка в отношении типов long long - Я не знаком с методами CL.EXE с ними на таких платформах.

#include <windows.h> 
#include <string> 
#include <cassert> 
#include <iostream> 
/* ... */ 

STARTUPINFO startUp; 
PROCESS_INFORMATION procInfo; 


/* Start program in paused state */ 
PROCESS_INFORMATION procInfo; 
if (!CreateProcess(NULL, CmdParams, NULL, NULL, TRUE, 
    CREATE_SUSPENDED | NORMAL_PRIORITY_CLASS, NULL, NULL, &startUp, &procInfo)) { 
    DWORD err = GetLastError(); 
    // TODO format error message 
    std::cerr << "Unable to start the process: " << err << std::endl; 
    return 1; 
} 

HANDLE hProc = procInfo.hProcess; 

/* Create job object and attach the process to it */ 
HANDLE hJob = CreateJobObject(NULL, NULL); // XXX no security attributes passed 
assert(hJob != NULL); 
int ret = AssignProcessToJobObject(hJob, hProc); 
assert(ret); 

/* Now run the process and allow it to spawn children */ 
ResumeThread(procInfo.hThread); 

/* Block until the process terminates */ 
if (WaitForSingleObject(hProc, INFINITE) != WAIT_OBJECT_0) { 
    DWORD err = GetLastError(); 
    // TODO format error message 
    std::cerr << "Failed waiting for process termination: " << err << std::endl; 
    return 1; 
} 

DWORD exitcode = 0; 
ret = GetExitCodeProcess(hProc, &exitcode); 
assert(ret); 

/* Calculate wallclock time in nanoseconds. 
    Ignore user and kernel times (third and fourth return parameters) */ 
FILETIME createTime, exitTime, unusedTime; 
ret = GetProcessTimes(hProc, &createTime, &exitTime, &unusedTime, &unusedTime); 
assert(ret); 

LONGLONG createTimeNs = (LONGLONG)createTime.dwHighDateTime << 32 | createTime.dwLowDateTime; 
LONGLONG exitTimeNs = (LONGLONG)exitTime.dwHighDateTime << 32 | exitTime.dwLowDateTime; 
LONGLONG wallclockTimeNs = exitTimeNs - createTimeNs; 

/* Get total user and kernel times for all processes of the job object */ 
JOBOBJECT_BASIC_ACCOUNTING_INFORMATION jobInfo; 
ret = QueryInformationJobObject(hJob, JobObjectBasicAccountingInformation, 
    &jobInfo, sizeof(jobInfo), NULL); 
assert(ret); 

if (jobInfo.ActiveProcesses != 0) { 
    std::cerr << "Warning: there are still " 
     << jobInfo.ActiveProcesses 
     << " alive children processes" << std::endl; 
    /* We may kill survived processes, if desired */ 
    TerminateJobObject(hJob, 127); 
} 

/* Get kernel and user times in nanoseconds */ 
LONGLONG kernelTimeNs = jobInfo.TotalKernelTime.QuadPart; 
LONGLONG userTimeNs = jobInfo.TotalUserTime.QuadPart; 

/* Clean up a bit */ 
CloseHandle(hProc); 
CloseHandle(hJob); 
+0

Это или используется, когда можно получить иерархию процессов в Windows и, например, Sysinternals обработчик процесса (Sysinternals был куплен Microsoft) делает это. Но я думаю, что лучший подход - использовать [Windows ** job **] (https://msdn.microsoft.com/en-us/library/windows/desktop/ms684161%28v=vs.85%29.aspx ? е = 255 & MSPPError = -2147217396). Если у вас есть возможность изменить приложение, которое вы хотите измерить. –

+0

@ Cheersandhth.-Alf: Не нужно менять приложение; дочерние процессы по умолчанию включены, а родительский процесс timep.exe - это тот, который создает задание. – MSalters

+0

Win32/MSVC поддерживает 64-битные целые типы, даже если указатели имеют 32 бита. – MSalters

ответ

4

Да, с timep.exe создать работу, и использовать job accounting. Процессы дочерних процессов (если они созданы на их собственных работах) совместно используют задание с их родительским процессом.

Это в значительной степени пропускаются ваши шаги 2-4

+0

Использование объекта работы было правильным решением для достижения моих целей, спасибо! В следующем ответе я отправлю свой фрагмент кода для тех, кому может понадобиться решить одну и ту же задачу. –

0

Я упаковал решение этой проблемы в отдельную программу для Windows, под названием chronos. Он создает объект задания, а затем создает в нем запрошенный процесс. Все дети, возникшие позже, остаются в одном и том же объекте работы и поэтому могут быть учтены позже.

Смежные вопросы