Process Hacker
perfpage.c
Go to the documentation of this file.
1 /*
2  * Process Hacker .NET Tools -
3  * .NET Performance property page
4  *
5  * Copyright (C) 2011 wj32
6  *
7  * This file is part of Process Hacker.
8  *
9  * Process Hacker is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * Process Hacker is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with Process Hacker. If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 #define CINTERFACE
24 #define COBJMACROS
25 #include "dn.h"
26 #include "resource.h"
27 #include <windowsx.h>
28 #include <corpub.h>
29 
30 typedef struct _PERFPAGE_CONTEXT
31 {
32  HWND WindowHandle;
33  PPH_PROCESS_ITEM ProcessItem;
34  BOOLEAN Enabled;
35  PPH_STRING InstanceName;
37 
38 INT_PTR CALLBACK DotNetPerfPageDlgProc(
39  _In_ HWND hwndDlg,
40  _In_ UINT uMsg,
41  _In_ WPARAM wParam,
42  _In_ LPARAM lParam
43  );
44 
45 static GUID CLSID_CorpubPublish_I = { 0x047a9a40, 0x657e, 0x11d3, { 0x8d, 0x5b, 0x00, 0x10, 0x4b, 0x35, 0xe7, 0xef } };
46 static GUID IID_ICorPublish_I = { 0x9613a0e7, 0x5a68, 0x11d3, { 0x8f, 0x84, 0x00, 0xa0, 0xc9, 0xb4, 0xd5, 0x0c } };
47 
48 static PH_INITONCE DotNetObjectTypeInfoInitOnce = PH_INITONCE_INIT;
49 static PPERF_OBJECT_TYPE_INFO DotNetObjectTypeInfo = NULL;
50 static ULONG DotNetObjectTypeInfoCount = 0;
51 static PVOID PerfInfoTextData = NULL;
52 
54  _In_ PPH_PLUGIN_PROCESS_PROPCONTEXT PropContext
55  )
56 {
58  PropContext->PropContext,
60  );
61 }
62 
64  _In_ HANDLE ProcessId,
65  _Out_ ICorPublish **Publish
66  )
67 {
68  HRESULT result;
69  ULONG flags;
70  BOOLEAN clrV4;
71 
72  clrV4 = FALSE;
73 
74  if (NT_SUCCESS(PhGetProcessIsDotNetEx(ProcessId, NULL, 0, NULL, &flags)))
75  {
76  if (flags & PH_CLR_VERSION_4_ABOVE)
77  clrV4 = TRUE;
78  }
79 
80  // Using CoCreateInstance always seems to create a v2-compatible class, but not a v4-compatible one.
81  // For v4 we have to manually load the correct version of mscordbi.dll.
82 
83  if (clrV4)
84  {
85  static PH_INITONCE initOnce = PH_INITONCE_INIT;
86  static HMODULE mscordbiDllBase;
87 
88  if (PhBeginInitOnce(&initOnce))
89  {
90  PH_STRINGREF systemRootString;
91  PH_STRINGREF mscordbiPathString;
92  PPH_STRING mscordbiFileName;
93 
94  LoadLibrary(L"mscoree.dll");
95 
96  PhGetSystemRoot(&systemRootString);
97 #ifdef _WIN64
98  PhInitializeStringRef(&mscordbiPathString, L"\\Microsoft.NET\\Framework64\\v4.0.30319\\mscordbi.dll");
99 #else
100  PhInitializeStringRef(&mscordbiPathString, L"\\Microsoft.NET\\Framework\\v4.0.30319\\mscordbi.dll");
101 #endif
102 
103  mscordbiFileName = PhConcatStringRef2(&systemRootString, &mscordbiPathString);
104  mscordbiDllBase = LoadLibrary(mscordbiFileName->Buffer);
105  PhDereferenceObject(mscordbiFileName);
106 
107  PhEndInitOnce(&initOnce);
108  }
109 
110  if (mscordbiDllBase)
111  {
112  HRESULT (__stdcall *dllGetClassObject)(REFCLSID, REFIID, LPVOID *);
113 
114  dllGetClassObject = (PVOID)GetProcAddress(mscordbiDllBase, "DllGetClassObjectInternal");
115 
116  if (dllGetClassObject)
117  {
118  IClassFactory *factory;
119 
120  if (SUCCEEDED(dllGetClassObject(&CLSID_CorpubPublish_I, &IID_IClassFactory, &factory)))
121  {
122  result = IClassFactory_CreateInstance(factory, NULL, &IID_ICorPublish_I, Publish);
123  IClassFactory_Release(factory);
124 
125  return result;
126  }
127  }
128  }
129  }
130 
131  return CoCreateInstance(&CLSID_CorpubPublish_I, NULL, CLSCTX_INPROC_SERVER, &IID_ICorPublish_I, Publish);
132 }
133 
135  _In_ HANDLE ProcessId,
136  _Out_ ICorPublishProcess **PublishProcess
137  )
138 {
139  HRESULT result;
140  ICorPublish *publish;
141 
142  if (SUCCEEDED(result = CreateCorpubPublish(ProcessId, &publish)))
143  {
144  result = ICorPublish_GetProcess(publish, HandleToUlong(ProcessId), PublishProcess);
145  ICorPublish_Release(publish);
146  }
147 
148  return result;
149 }
150 
152  VOID
153  )
154 {
155  if (PhBeginInitOnce(&DotNetObjectTypeInfoInitOnce))
156  {
157  PH_STRINGREF nameList;
158 
159  // Use a pre-defined list because enumerating all object types is extremely slow.
161  &nameList,
162  L".NET CLR Data;"
163  L".NET CLR Exceptions;"
164  L".NET CLR Interop;"
165  L".NET CLR Jit;"
166  L".NET CLR Loading;"
167  L".NET CLR LocksAndThreads;"
168  L".NET CLR Memory;"
169  L".NET CLR Remoting;"
170  L".NET CLR Security"
171  );
172  GetPerfObjectTypeInfo2(&nameList, &DotNetObjectTypeInfo, &DotNetObjectTypeInfoCount, &PerfInfoTextData);
173 
174  PhEndInitOnce(&DotNetObjectTypeInfoInitOnce);
175  }
176 }
177 
179  _In_ HWND hwndDlg,
180  _In_ PPERFPAGE_CONTEXT Context
181  )
182 {
183  HWND appDomainsLv;
184  ICorPublishProcess *publishProcess;
185  ICorPublishAppDomainEnum *publishAppDomainEnum;
186  ICorPublishAppDomain *publishAppDomain;
187  ULONG returnCount;
188  ULONG appDomainNameCount;
189  WCHAR appDomainName[256];
190 
191  appDomainsLv = GetDlgItem(hwndDlg, IDC_APPDOMAINS);
192 
193  SendMessage(appDomainsLv, WM_SETREDRAW, FALSE, 0);
194  ListView_DeleteAllItems(appDomainsLv);
195 
196  if (SUCCEEDED(GetCorPublishProcess(Context->ProcessItem->ProcessId, &publishProcess)))
197  {
198  if (SUCCEEDED(ICorPublishProcess_EnumAppDomains(publishProcess, &publishAppDomainEnum)))
199  {
200  while (TRUE)
201  {
202  if (!SUCCEEDED(ICorPublishAppDomainEnum_Next(publishAppDomainEnum, 1, &publishAppDomain, &returnCount)))
203  break;
204  if (returnCount == 0)
205  break;
206 
207  if (SUCCEEDED(ICorPublishAppDomain_GetName(publishAppDomain, 256, &appDomainNameCount, appDomainName)))
208  {
209  PhAddListViewItem(appDomainsLv, MAXINT, appDomainName, NULL);
210  }
211 
212  ICorPublishAppDomain_Release(publishAppDomain);
213  }
214 
215  ICorPublishAppDomainEnum_Release(publishAppDomainEnum);
216  }
217 
218  ICorPublishProcess_Release(publishProcess);
219  }
220 
221  SendMessage(appDomainsLv, WM_SETREDRAW, TRUE, 0);
222 }
223 
225  _In_ HWND hwndDlg,
226  _In_ PPERFPAGE_CONTEXT Context
227  )
228 {
229  PPH_STRING selectedText;
230  ULONG i;
231 
232  selectedText = PhGetWindowText(GetDlgItem(hwndDlg, IDC_CATEGORIES));
233 
234  for (i = 0; i < DotNetObjectTypeInfoCount; i++)
235  {
236  if (PhEqualStringRef(&DotNetObjectTypeInfo[i].Name, &selectedText->sr, FALSE))
237  {
238  return &DotNetObjectTypeInfo[i];
239  }
240  }
241 
242  return NULL;
243 }
244 
246  _In_ HWND hwndDlg,
247  _In_ PPERFPAGE_CONTEXT Context,
248  _In_ BOOLEAN RefreshCategory
249  )
250 {
251  HWND countersLv;
252  PPERF_OBJECT_TYPE_INFO typeInfo;
253  WCHAR indexString[PH_INT32_STR_LEN_1];
254  PVOID textData;
255  PVOID data;
256  PPERF_DATA_BLOCK block;
257  ULONG i;
258  PPERF_OBJECT_TYPE objectType;
259  ULONG j;
260  PPERF_COUNTER_DEFINITION counter;
261  PPERF_INSTANCE_DEFINITION instance;
262 
263  countersLv = GetDlgItem(hwndDlg, IDC_COUNTERS);
264 
265  if (RefreshCategory)
266  ListView_DeleteAllItems(countersLv);
267 
268  typeInfo = GetSelectedObjectTypeInfo(hwndDlg, Context);
269 
270  if (!typeInfo)
271  return;
272 
273  if (PerfInfoTextData)
274  {
275  textData = PerfInfoTextData;
276  }
277  else
278  {
279  if (!QueryPerfInfoVariableSize(HKEY_PERFORMANCE_DATA, L"Counter 009", &textData, NULL))
280  return;
281  }
282 
283  PhPrintUInt32(indexString, typeInfo->NameIndex);
284 
285  if (!QueryPerfInfoVariableSize(HKEY_PERFORMANCE_DATA, indexString, &data, NULL))
286  {
287  PhFree(textData);
288  return;
289  }
290 
291  block = data;
292  objectType = (PPERF_OBJECT_TYPE)((PCHAR)block + block->HeaderLength);
293 
294  for (i = 0; i < block->NumObjectTypes; i++)
295  {
296  if (objectType->ObjectNameTitleIndex == typeInfo->NameIndex && objectType->NumInstances != PERF_NO_INSTANCES)
297  {
298  PPERF_COUNTER_BLOCK counterBlock = NULL;
299  BOOLEAN instanceFound = FALSE;
300 
301  // Find the instance that corresponds with the process.
302 
303  instance = (PPERF_INSTANCE_DEFINITION)((PCHAR)objectType + objectType->DefinitionLength);
304 
305  for (j = 0; j < (ULONG)objectType->NumInstances; j++)
306  {
307  PH_STRINGREF instanceName;
308 
309  if (instance->NameLength != 0)
310  {
311  instanceName.Buffer = (PWSTR)((PCHAR)instance + instance->NameOffset);
312  instanceName.Length = instance->NameLength - sizeof(WCHAR);
313 
314  counterBlock = (PPERF_COUNTER_BLOCK)((PCHAR)instance + instance->ByteLength);
315 
316  if (PhEqualStringRef(&instanceName, &Context->InstanceName->sr, TRUE))
317  {
318  instanceFound = TRUE;
319  break;
320  }
321  }
322 
323  instance = (PPERF_INSTANCE_DEFINITION)((PCHAR)counterBlock + counterBlock->ByteLength);
324  }
325 
326  // Get the counter values.
327 
328  counter = (PPERF_COUNTER_DEFINITION)((PCHAR)objectType + objectType->HeaderLength);
329 
330  for (j = 0; j < objectType->NumCounters; j++)
331  {
332  INT lvItemIndex = -1;
333 
334  if (
335  counter->CounterType != PERF_COUNTER_RAWCOUNT &&
336  counter->CounterType != PERF_COUNTER_LARGE_RAWCOUNT &&
337  counter->CounterType != PERF_RAW_FRACTION
338  )
339  {
340  goto EndOfLoop;
341  }
342 
343  if (RefreshCategory)
344  {
345  PWSTR counterName;
346 
347  counterName = FindPerfTextInTextData(textData, counter->CounterNameTitleIndex);
348 
349  if (counterName)
350  lvItemIndex = PhAddListViewItem(countersLv, MAXINT, counterName, (PVOID)counter->CounterNameTitleIndex);
351  }
352  else
353  {
354  lvItemIndex = PhFindListViewItemByParam(countersLv, -1, (PVOID)counter->CounterNameTitleIndex);
355  }
356 
357  if (lvItemIndex != -1 && instanceFound)
358  {
359  switch (counter->CounterType)
360  {
361  case PERF_COUNTER_RAWCOUNT:
362  {
363  PULONG value = (PULONG)((PCHAR)counterBlock + counter->CounterOffset);
364 
365  PhSetListViewSubItem(countersLv, lvItemIndex, 1, PhaFormatUInt64(*value, TRUE)->Buffer);
366  }
367  break;
368  case PERF_COUNTER_LARGE_RAWCOUNT:
369  {
370  PULONG64 value = (PULONG64)((PCHAR)counterBlock + counter->CounterOffset);
371 
372  PhSetListViewSubItem(countersLv, lvItemIndex, 1, PhaFormatUInt64(*value, TRUE)->Buffer);
373  }
374  break;
375  case PERF_RAW_FRACTION:
376  {
377  PULONG value = (PULONG)((PCHAR)counterBlock + counter->CounterOffset);
378  PPERF_COUNTER_DEFINITION denomCounter = (PPERF_COUNTER_DEFINITION)((PCHAR)counter + counter->ByteLength);
379  PULONG denomValue = (PULONG)((PCHAR)counterBlock + denomCounter->CounterOffset);
380  PH_FORMAT format;
381  WCHAR formatBuffer[10];
382 
383  if (*denomValue != 0)
384  {
385  PhInitFormatF(&format, (FLOAT)*value * 100 / (FLOAT)*denomValue, 2);
386 
387  if (PhFormatToBuffer(&format, 1, formatBuffer, sizeof(formatBuffer), NULL))
388  PhSetListViewSubItem(countersLv, lvItemIndex, 1, formatBuffer);
389  }
390  else
391  {
392  PhSetListViewSubItem(countersLv, lvItemIndex, 1, L"0.00");
393  }
394  }
395  break;
396  }
397  }
398 
399 EndOfLoop:
400  counter = (PPERF_COUNTER_DEFINITION)((PCHAR)counter + counter->ByteLength);
401  }
402  }
403 
404  objectType = (PPERF_OBJECT_TYPE)((PCHAR)objectType + objectType->TotalByteLength);
405  }
406 
407  PhFree(data);
408 
409  if (textData != PerfInfoTextData)
410  PhFree(textData);
411 }
412 
413 INT_PTR CALLBACK DotNetPerfPageDlgProc(
414  _In_ HWND hwndDlg,
415  _In_ UINT uMsg,
416  _In_ WPARAM wParam,
417  _In_ LPARAM lParam
418  )
419 {
420  LPPROPSHEETPAGE propSheetPage;
421  PPH_PROCESS_PROPPAGECONTEXT propPageContext;
422  PPH_PROCESS_ITEM processItem;
423  PPERFPAGE_CONTEXT context;
424 
425  if (PhPropPageDlgProcHeader(hwndDlg, uMsg, lParam, &propSheetPage, &propPageContext, &processItem))
426  {
427  context = propPageContext->Context;
428  }
429  else
430  {
431  return FALSE;
432  }
433 
434  switch (uMsg)
435  {
436  case WM_INITDIALOG:
437  {
438  ULONG_PTR indexOfLastDot;
439  HWND appDomainsLv;
440  HWND countersLv;
441  HWND categoriesHandle;
442  ULONG i;
443 
444  context = PhAllocate(sizeof(PERFPAGE_CONTEXT));
445  memset(context, 0, sizeof(PERFPAGE_CONTEXT));
446  propPageContext->Context = context;
447  context->WindowHandle = hwndDlg;
448  context->ProcessItem = processItem;
449  context->Enabled = TRUE;
450 
451  // The .NET counters remove the file name extension in the instance names, even if the
452  // extension is something other than ".exe".
453 
454  indexOfLastDot = PhFindLastCharInString(context->ProcessItem->ProcessName, 0, '.');
455 
456  if (indexOfLastDot != -1)
457  context->InstanceName = PhSubstring(context->ProcessItem->ProcessName, 0, indexOfLastDot);
458  else
459  PhSetReference(&context->InstanceName, context->ProcessItem->ProcessName);
460 
461  appDomainsLv = GetDlgItem(hwndDlg, IDC_APPDOMAINS);
462  PhSetListViewStyle(appDomainsLv, FALSE, TRUE);
463  PhSetControlTheme(appDomainsLv, L"explorer");
464  PhAddListViewColumn(appDomainsLv, 0, 0, 0, LVCFMT_LEFT, 300, L"Application domain");
465 
466  countersLv = GetDlgItem(hwndDlg, IDC_COUNTERS);
467  PhSetListViewStyle(countersLv, FALSE, TRUE);
468  PhSetControlTheme(countersLv, L"explorer");
469  PhAddListViewColumn(countersLv, 0, 0, 0, LVCFMT_LEFT, 250, L"Counter");
470  PhAddListViewColumn(countersLv, 1, 1, 1, LVCFMT_RIGHT, 140, L"Value");
471 
473  AddProcessAppDomains(hwndDlg, context);
474 
475  categoriesHandle = GetDlgItem(hwndDlg, IDC_CATEGORIES);
476 
477  for (i = 0; i < DotNetObjectTypeInfoCount; i++)
478  {
479  PPERF_OBJECT_TYPE_INFO info = &DotNetObjectTypeInfo[i];
480 
481  ComboBox_AddString(categoriesHandle, PhaCreateStringEx(info->Name.Buffer, info->Name.Length)->Buffer);
482  }
483 
484  ComboBox_SelectString(categoriesHandle, -1, L".NET CLR Memory"); // select a default item
485  UpdateCounterData(hwndDlg, context, TRUE);
486  SetTimer(hwndDlg, 1, 1000, NULL);
487  }
488  break;
489  case WM_DESTROY:
490  {
491  if (context->InstanceName)
492  PhDereferenceObject(context->InstanceName);
493 
494  PhFree(context);
495 
496  PhPropPageDlgProcDestroy(hwndDlg);
497  }
498  break;
499  case WM_SHOWWINDOW:
500  {
501  PPH_LAYOUT_ITEM dialogItem;
502 
503  if (dialogItem = PhBeginPropPageLayout(hwndDlg, propPageContext))
504  {
505  PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_APPDOMAINS), dialogItem, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT);
506  PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_CATEGORIES), dialogItem, PH_ANCHOR_LEFT | PH_ANCHOR_TOP | PH_ANCHOR_RIGHT);
507  PhAddPropPageLayoutItem(hwndDlg, GetDlgItem(hwndDlg, IDC_COUNTERS), dialogItem, PH_ANCHOR_ALL);
508  PhEndPropPageLayout(hwndDlg, propPageContext);
509  }
510  }
511  break;
512  case WM_COMMAND:
513  {
514  switch (LOWORD(wParam))
515  {
516  case IDC_CATEGORIES:
517  if (HIWORD(wParam) == CBN_SELCHANGE)
518  {
519  UpdateCounterData(hwndDlg, context, TRUE);
520  }
521  break;
522  }
523  }
524  break;
525  case WM_NOTIFY:
526  {
527  LPNMHDR header = (LPNMHDR)lParam;
528 
529  switch (header->code)
530  {
531  case PSN_SETACTIVE:
532  context->Enabled = TRUE;
533  break;
534  case PSN_KILLACTIVE:
535  context->Enabled = FALSE;
536  break;
537  }
538 
539  PhHandleListViewNotifyForCopy(lParam, GetDlgItem(hwndDlg, IDC_APPDOMAINS));
540  PhHandleListViewNotifyForCopy(lParam, GetDlgItem(hwndDlg, IDC_COUNTERS));
541  }
542  break;
543  case WM_TIMER:
544  {
545  if (wParam == 1 && context->Enabled)
546  {
547  UpdateCounterData(hwndDlg, context, FALSE);
548  }
549  }
550  break;
551  }
552 
553  return FALSE;
554 }