Почему Process.TotalProcessorTime не работает для удаленных процессов
Dec 16, 2011 · CommentsВопрос из почты:
Скажите пожалуйста. Есть тут такой класс, верней конкретно одно из его свойств http://msdn.microsoft.com/en-us/library/system.diagnostics.process.totalprocessortime.aspx. Сказано, что его можно получить только локально. А почему? Из-за этого, собсно, не работает нормально командлет get-process, когда им пытаешься запросить процессы удаленной машины. Верней работает, но частично. Колонка CPU(s) в его вводе пустая.
Короткий ответ: по всей видимости, потому, что нужный счетчик не доступен через HKEY_PERFORMANCE_DATA.
Длинный ответ: с .NET я сталкиваюсь не очень часто. Ничего лучше, чем банально взять дизассемблер для IL (intermediate language) и посмотреть на код, мне в голову не пришло. Ildasm.exe входит в состав SDK, но есть и другие варианты, - например ReSharper.
Ildasm.exe c:\Windows\Microsoft.NET\Framework64\v4.0.30319\System.dll
IL достаточно прост, чтобы читать, не заглядывая в спецификацию. Основные моменты понятны и так, а разбираться с деталями мне как-то еще не требовалось. Интересующий нас метод очень прост:
.method public hidebysig specialname instance valuetype [mscorlib]System.TimeSpan
get_TotalProcessorTime() cil managed
{
// Code size 19 (0x13)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldc.i4.4
IL_0002: call instance void System.Diagnostics.Process::EnsureState(valuetype System.Diagnostics.Process/State)
IL_0007: ldarg.0
IL_0008: call instance class System.Diagnostics.ProcessThreadTimes System.Diagnostics.Process::GetProcessTimes()
IL_000d: callvirt instance valuetype [mscorlib]System.TimeSpan System.Diagnostics.ProcessThreadTimes::get_TotalProcessorTime()
IL_0012: ret
} // end of method Process::get_TotalProcessorTime
Вызов Process::GetProcessTimes() возвращает заполненную структуру ProcessThreadTimes, содержащую, кроме всего прочего, уже вычисленные значения user time и kernel time. Последующий вызов ProcessThreadTimes::get_TotalProcessorTime() просто складывает эти два значения и возвращает полученный результат.
Если взглянуть на Process::GetProcessTimes(), то видно, что тот просто вызывает Win32 функцию GetProcessTimes() и складывает полученные значения kernel и user time в возвращаемую структуру:
...
IL_0059: ldflda int64 System.Diagnostics.ProcessThreadTimes::create
IL_005e: ldloc.0
IL_005f: ldflda int64 System.Diagnostics.ProcessThreadTimes::exit
IL_0064: ldloc.0
IL_0065: ldflda int64 System.Diagnostics.ProcessThreadTimes::kernel
IL_006a: ldloc.0
IL_006b: ldflda int64 System.Diagnostics.ProcessThreadTimes::user
IL_0070: call bool Microsoft.Win32.NativeMethods::GetProcessTimes(class Microsoft.Win32.SafeHandles.SafeProcessHandle,
int64&,
int64&,
int64&,
int64&)
...
Функция GetProcessTimes() работает только для локальных процессов, так как идентифицирует процесс по переданному NT handle. Посмотрим теперь на какое-нибудь другое свойство, которое работает, в том числе, и для удаленных процессов. К примеру, на Process.HandleCount:
.method public hidebysig specialname instance int32
get_HandleCount() cil managed
{
// Code size 19 (0x13)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldc.i4.8
IL_0002: call instance void System.Diagnostics.Process::EnsureState(valuetype System.Diagnostics.Process/State)
IL_0007: ldarg.0
IL_0008: ldfld class System.Diagnostics.ProcessInfo System.Diagnostics.Process::processInfo
IL_000d: ldfld int32 System.Diagnostics.ProcessInfo::handleCount
IL_0012: ret
} // end of method Process::get_HandleCount
В этом случае значение берется из структуры ProcessInfo, которая, по всей видимости, заполняется методом EnsureState(). Последний, в свою очередь, делает несколько не относящихся к делу проверок и вызывает ProcessManager::GetProcessInfos(), чтобы получить желаемую структуру. Еще через пару уровней вложенности становится понятно, что информация об удаленных процессах добывается через класс NtProcessManager, который читает счетчики из HKEY_PERFORMANCE_DATA удаленной машины. Подсказки в коде, ведущие к этому заключению выглядят вот так:
.method private hidebysig static class System.Diagnostics.ProcessInfo[]
GetProcessInfos(class System.Diagnostics.PerformanceCounterLib 'library') cil managed
{
...
IL_000e: ldstr "230 232"
IL_0013: callvirt instance uint8[] System.Diagnostics.PerformanceCounterLib::GetPerformanceData(string)
IL_0018: stloc.1
Имя PerformanceCounterLib говорит само за себя. А константа 230 – это идентификатор объекта Process из HKEY_PERFORMANCE_DATA.
.locals init (class [mscorlib]System.Collections.Hashtable V_0,
class [mscorlib]System.Collections.ArrayList V_1,
valuetype [mscorlib]System.Runtime.InteropServices.GCHandle V_2,
native int V_3,
class Microsoft.Win32.NativeMethods/PERF_DATA_BLOCK V_4,
native int V_5,
class Microsoft.Win32.NativeMethods/PERF_INSTANCE_DEFINITION V_6,
class Microsoft.Win32.NativeMethods/PERF_COUNTER_BLOCK V_7,
int32 V_8,
class Microsoft.Win32.NativeMethods/PERF_OBJECT_TYPE V_9,
native int V_10,
native int V_11,
class [mscorlib]System.Collections.ArrayList V_12,
int32 V_13,
class Microsoft.Win32.NativeMethods/PERF_COUNTER_DEFINITION V_14,
string V_15,
class Microsoft.Win32.NativeMethods/PERF_COUNTER_DEFINITION[] V_16,
Структуры PERF_DATA_BLOCK, PERF_INSTANCE_DEFINITION и т.д. сразу напомнили содержимое winperf.h
Получается, что информация об удаленных процессах читается из HKEY_PERFORMANCE_DATA удаленной машины. Почему же нельзя точно также прочитать время, проведенное процессом в user и kernel mode? Видимо потому, что эти счетчики не доступны через HKEY_PERFORMANCE_DATA. Вот какие счётчики доступны для процесса:
230 Process
144 % Privileged Time
6 % Processor Time
142 % User Time
1410 Creating Process ID
684 Elapsed Time
952 Handle Count
784 ID Process
1424 IO Data Bytes/sec
1416 IO Data Operations/sec
1426 IO Other Bytes/sec
1418 IO Other Operations/sec
1420 IO Read Bytes/sec
1412 IO Read Operations/sec
1422 IO Write Bytes/sec
1414 IO Write Operations/sec
28 Page Faults/sec
182 Page File Bytes Peak
184 Page File Bytes
58 Pool Nonpaged Bytes
56 Pool Paged Bytes
682 Priority Base
186 Private Bytes
680 Thread Count
174 Virtual Bytes
172 Virtual Bytes Peak
180 Working Set
178 Working Set Peak
1478 Working Set - Private
Обратите внимание, что “Privileged Time” и “User Time” измеряются процентах. Т.е. считается текущее использование процессом CPU, а не общее время, проведенное процессом в обоих режимах.