2016-03-15 101 views
3

爲了在Windows操作系統上進行性能監視,我需要一個能夠報告任意進程的用戶和內核時間的程序。在POSIX系統上,標準time實用程序非常好,因爲它報告掛鐘時間,用戶時間和內核時間。如何獲取Windows中當前進程的所有子進程的句柄?

對於Windows,默認情況下不存在此類實用程序。我環顧四周,發現至少有三種選擇。正如我在下面解釋的,它們都不符合我的需求。

  1. timeit從Windows SDK(無法回想什麼確切的版本)。它不再分佈,支持或保證在現代系統上工作。我無法測試它。
  2. Cygwin's time。幾乎與具有相似輸出格式的POSIX副本相同。
  3. timep.exe約翰遜(約翰)哈特,可在source code和他的書「Windows系統編程,第4版」的二進制文件。這是一個非常簡單的工具,它使用WinAPI的GetProcessTimes()來獲得相同的三個值。我懷疑Cygwin的time在這方面沒有什麼不同。

現在的問題:GetProcessTimes()僅報告時間爲PID通過timep直接催生,但不是它的孩子。這使得timetimep對我無用。

我的目標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和根PID。如果相等,則獲取PID並處理它,並將其添加到句柄列表中。
    2. 對於每一個新的PID,重複過程中掃描階段收集更多的孩子處理
    3. 遞歸,直到沒有新的進程句柄被添加到列表中
  3. 等待列表中的所有收集的句柄終止。
  4. 對於每一個手柄,撥打GetProcessTimes(),總結起來
  5. 報告結果

這種算法是不好的,因爲它是活潑的 - 可在任何過程的生命末期創建子進程,也可以終止在我們有機會獲得他們的處理之前。在這兩種情況下,報告的結果時間都不正確。

我的問題是:有沒有更好的解決方案?


編輯:我能夠通過使用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); 
+0

它是或曾經有可能獲得Windows中的流程層次結構, Sysinternals進程查看器(Sysinternals被微軟收購)做到了。但我認爲更好的方法是使用[Windows **作業**](https://msdn.microsoft.com/en-us/library/windows/desktop/ms684161%28v=vs.85%29.aspx ?F = 255&MSPPError = -2147217396)。如果您有能力更改要測量的應用程序。 –

+0

@ Cheersandhth.-Alf:無需更改應用程序;子進程默認包含在內,並且父timep.exe進程是創建作業的進程。 – MSalters

+0

即使指針爲32位,Win32/MSVC也支持64位整數類型。 – MSalters

回答

4

是的,從timep.exe創建一個作業,並使用job accounting。子進程(除非在自己的作業中創建)與他們的父進程共享作業。

這幾乎跳過你的步驟2-4

+0

使用工作對象是實現我的目標的正確途徑,謝謝!我將在下一個答案中發佈我的代碼片段,供那些可能需要解決相同任務的人員使用。 –

0

我已經打包爲這個問題的解決方案到適用於Windows的獨立程序調用chronos。它創建一個作業對象,然後在其內部生成一個請求的進程。所有後來產生的孩子都留在同一個工作對象中,因此可以在以後進行統計。

相關問題