Для контроля производительности в ОС Windows мне нужна программа, которая может сообщать как пользовательское, так и ядерное время для произвольного процесса. В системах POSIX стандартная утилита time
отлично работает, так как она сообщает часы настенных часов, время пользователя и время ядра.Как получить ручки для всех дочерних процессов текущего процесса в Windows?
Для Windows такая утилита по умолчанию отсутствует. Я огляделся и нашел по крайней мере три альтернативы. Как я объясню ниже, ни один из них не соответствует моим потребностям.
- от Windows SDK (не помню, какая именно версия). Он больше не распространяется, не поддерживается или не гарантируется для работы на современных системах. Я не смог его проверить.
- Cygwin's
time
. Почти идентичен POSIX-аналогу с аналогичным форматированием вывода. timep.exe
от Johnson (John) Hart, доступен в source code и двоичных файлах для его книги «Системное программирование Windows, 4-е издание». Это довольно простая утилита, которая использует WinAPI'sGetProcessTimes()
для получения тех же трех значений. Я подозреваю, что Cygwin'stime
не отличается в этом отношении.
Теперь проблема: 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
. Он должен суммировать время для данного процесса и всех его детей, внуков и т. Д., Рекурсивно. До сих пор я могу представить себе следующий подход:
CreateProcess()
и получить его PID (корень PID) и ручка; добавьте этот дескриптор в список- Перечислите все процессы в системе; для каждого процесса
- Сравните его ПИД с корневым ПИД-регулятором. Если он равен, получите PID и обработайте его, добавьте его в список дескрипторов.
- Для каждого нового PID, фазы сканирования повторите процесс, чтобы собрать больше детей ручка
- RECURSE вниз, пока нет новых ручек процесса не добавляются в список
- Подождите все собранные ручки из списка прекратить.
- Для каждой ручки, вызовите
GetProcessTimes()
и просуммировать их - 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);
Это или используется, когда можно получить иерархию процессов в Windows и, например, Sysinternals обработчик процесса (Sysinternals был куплен Microsoft) делает это. Но я думаю, что лучший подход - использовать [Windows ** job **] (https://msdn.microsoft.com/en-us/library/windows/desktop/ms684161%28v=vs.85%29.aspx ? е = 255 & MSPPError = -2147217396). Если у вас есть возможность изменить приложение, которое вы хотите измерить. –
@ Cheersandhth.-Alf: Не нужно менять приложение; дочерние процессы по умолчанию включены, а родительский процесс timep.exe - это тот, который создает задание. – MSalters
Win32/MSVC поддерживает 64-битные целые типы, даже если указатели имеют 32 бита. – MSalters