2016-03-15 102 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。它创建一个作业对象,然后在其内部生成一个请求的进程。所有后来产生的孩子都留在同一个工作对象中,因此可以在以后进行统计。

相关问题