爲了在Windows操作系統上進行性能監視,我需要一個能夠報告任意進程的用戶和內核時間的程序。在POSIX系統上,標準time
實用程序非常好,因爲它報告掛鐘時間,用戶時間和內核時間。如何獲取Windows中當前進程的所有子進程的句柄?
對於Windows,默認情況下不存在此類實用程序。我環顧四周,發現至少有三種選擇。正如我在下面解釋的,它們都不符合我的需求。
timeit
從Windows SDK(無法回想什麼確切的版本)。它不再分佈,支持或保證在現代系統上工作。我無法測試它。- Cygwin's
time
。幾乎與具有相似輸出格式的POSIX副本相同。 timep.exe
約翰遜(約翰)哈特,可在source code和他的書「Windows系統編程,第4版」的二進制文件。這是一個非常簡單的工具,它使用WinAPI的GetProcessTimes()
來獲得相同的三個值。我懷疑Cygwin的time
在這方面沒有什麼不同。
現在的問題:GetProcessTimes()
僅報告時間爲PID通過timep
直接催生,但不是它的孩子。這使得time
和timep
對我無用。
我的目標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。如果相等,則獲取PID並處理它,並將其添加到句柄列表中。
- 對於每一個新的PID,重複過程中掃描階段收集更多的孩子處理
- 遞歸,直到沒有新的進程句柄被添加到列表中
- 等待列表中的所有收集的句柄終止。
- 對於每一個手柄,撥打
GetProcessTimes()
,總結起來 - 報告結果
這種算法是不好的,因爲它是活潑的 - 可在任何過程的生命末期創建子進程,也可以終止在我們有機會獲得他們的處理之前。在這兩種情況下,報告的結果時間都不正確。
我的問題是:有沒有更好的解決方案?
編輯:我能夠通過使用Job Objects達到我的目的。以下是從我的應用程序中提取的代碼片段,與從進程及其所有子進程獲取內核和用戶時間相關。希望這會爲某人節省一些時間。
我用Windows 8.1 x64和VS 2015測試了它,但它至少應該可以向後移植到Windows 7.對於long long
類型,32位主機可能需要一些擺弄(我不確定)我不熟悉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被微軟收購)做到了。但我認爲更好的方法是使用[Windows **作業**](https://msdn.microsoft.com/en-us/library/windows/desktop/ms684161%28v=vs.85%29.aspx ?F = 255&MSPPError = -2147217396)。如果您有能力更改要測量的應用程序。 –
@ Cheersandhth.-Alf:無需更改應用程序;子進程默認包含在內,並且父timep.exe進程是創建作業的進程。 – MSalters
即使指針爲32位,Win32/MSVC也支持64位整數類型。 – MSalters