Process Hacker
etwstat.c
Go to the documentation of this file.
1 /*
2  * Process Hacker Extended Tools -
3  * ETW statistics collection
4  *
5  * Copyright (C) 2010-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 #include "exttools.h"
24 #include "etwmon.h"
25 
27  _In_opt_ PVOID Parameter,
28  _In_opt_ PVOID Context
29  );
30 
32  _In_opt_ PVOID Parameter,
33  _In_opt_ PVOID Context
34  );
35 
37  VOID
38  );
39 
40 static PH_CALLBACK_REGISTRATION EtpProcessesUpdatedCallbackRegistration;
41 static PH_CALLBACK_REGISTRATION EtpNetworkItemsUpdatedCallbackRegistration;
42 
47 
52 
57 
62 
63 PH_CIRCULAR_BUFFER_ULONG EtDiskReadHistory;
64 PH_CIRCULAR_BUFFER_ULONG EtDiskWriteHistory;
65 PH_CIRCULAR_BUFFER_ULONG EtNetworkReceiveHistory;
66 PH_CIRCULAR_BUFFER_ULONG EtNetworkSendHistory;
67 PH_CIRCULAR_BUFFER_ULONG EtMaxDiskHistory; // ID of max. disk usage process
68 PH_CIRCULAR_BUFFER_ULONG EtMaxNetworkHistory; // ID of max. network usage process
69 
72 
74  VOID
75  )
76 {
78 
79  if (EtEtwEnabled)
80  {
81  ULONG sampleCount;
82 
83  sampleCount = PhGetIntegerSetting(L"SampleCount");
84  PhInitializeCircularBuffer_ULONG(&EtDiskReadHistory, sampleCount);
85  PhInitializeCircularBuffer_ULONG(&EtDiskWriteHistory, sampleCount);
86  PhInitializeCircularBuffer_ULONG(&EtNetworkReceiveHistory, sampleCount);
87  PhInitializeCircularBuffer_ULONG(&EtNetworkSendHistory, sampleCount);
88  PhInitializeCircularBuffer_ULONG(&EtMaxDiskHistory, sampleCount);
89  PhInitializeCircularBuffer_ULONG(&EtMaxNetworkHistory, sampleCount);
90 
94  NULL,
95  &EtpProcessesUpdatedCallbackRegistration
96  );
100  NULL,
101  &EtpNetworkItemsUpdatedCallbackRegistration
102  );
103  }
104 }
105 
107  VOID
108  )
109 {
111 }
112 
114  _In_ PET_ETW_DISK_EVENT Event
115  )
116 {
117  PPH_PROCESS_ITEM processItem;
118  PET_PROCESS_BLOCK block;
119 
120  if (Event->Type == EtEtwDiskReadType)
121  {
122  EtpDiskReadRaw += Event->TransferSize;
123  EtDiskReadCount++;
124  }
125  else
126  {
127  EtpDiskWriteRaw += Event->TransferSize;
129  }
130 
131  if (processItem = PhReferenceProcessItem(Event->ClientId.UniqueProcess))
132  {
133  block = EtGetProcessBlock(processItem);
134 
135  if (Event->Type == EtEtwDiskReadType)
136  {
137  block->DiskReadRaw += Event->TransferSize;
138  block->DiskReadCount++;
139  }
140  else
141  {
142  block->DiskWriteRaw += Event->TransferSize;
143  block->DiskWriteCount++;
144  }
145 
146  PhDereferenceObject(processItem);
147  }
148 }
149 
151  _In_ PET_ETW_NETWORK_EVENT Event
152  )
153 {
154  PPH_PROCESS_ITEM processItem;
155  PET_PROCESS_BLOCK block;
156  PPH_NETWORK_ITEM networkItem;
157  PET_NETWORK_BLOCK networkBlock;
158 
159  if (Event->Type == EtEtwNetworkReceiveType)
160  {
161  EtpNetworkReceiveRaw += Event->TransferSize;
163  }
164  else
165  {
166  EtpNetworkSendRaw += Event->TransferSize;
168  }
169 
170  // Note: there is always the possibility of us receiving the event too early,
171  // before the process item or network item is created. So events may be lost.
172 
173  if (processItem = PhReferenceProcessItem(Event->ClientId.UniqueProcess))
174  {
175  block = EtGetProcessBlock(processItem);
176 
177  if (Event->Type == EtEtwNetworkReceiveType)
178  {
179  block->NetworkReceiveRaw += Event->TransferSize;
180  block->NetworkReceiveCount++;
181  }
182  else
183  {
184  block->NetworkSendRaw += Event->TransferSize;
185  block->NetworkSendCount++;
186  }
187 
188  PhDereferenceObject(processItem);
189  }
190 
191  if (networkItem = PhReferenceNetworkItem(
192  Event->ProtocolType,
193  &Event->LocalEndpoint,
194  &Event->RemoteEndpoint,
195  Event->ClientId.UniqueProcess
196  ))
197  {
198  networkBlock = EtGetNetworkBlock(networkItem);
199 
200  if (Event->Type == EtEtwNetworkReceiveType)
201  {
202  networkBlock->ReceiveRaw += Event->TransferSize;
203  networkBlock->ReceiveCount++;
204  }
205  else
206  {
207  networkBlock->SendRaw += Event->TransferSize;
208  networkBlock->SendCount++;
209  }
210 
211  PhDereferenceObject(networkItem);
212  }
213 }
214 
215 static VOID NTAPI ProcessesUpdatedCallback(
216  _In_opt_ PVOID Parameter,
217  _In_opt_ PVOID Context
218  )
219 {
220  static ULONG runCount = 0; // MUST keep in sync with runCount in process provider
221 
222  PLIST_ENTRY listEntry;
223  ULONG64 maxDiskValue = 0;
224  PET_PROCESS_BLOCK maxDiskBlock = NULL;
225  ULONG64 maxNetworkValue = 0;
226  PET_PROCESS_BLOCK maxNetworkBlock = NULL;
227 
228  // Since Windows 8, we no longer get the correct process/thread IDs in the
229  // event headers for disk events. We need to update our process information since
230  // etwmon uses our EtThreadIdToProcessId function.
231  if (WindowsVersion >= WINDOWS_8)
233 
234  // ETW is extremely lazy when it comes to flushing buffers, so we must do it
235  // manually.
237 
238  // Update global statistics.
239 
240  PhUpdateDelta(&EtDiskReadDelta, EtpDiskReadRaw);
241  PhUpdateDelta(&EtDiskWriteDelta, EtpDiskWriteRaw);
242  PhUpdateDelta(&EtNetworkReceiveDelta, EtpNetworkReceiveRaw);
243  PhUpdateDelta(&EtNetworkSendDelta, EtpNetworkSendRaw);
244 
245  PhUpdateDelta(&EtDiskReadCountDelta, EtDiskReadCount);
246  PhUpdateDelta(&EtDiskWriteCountDelta, EtDiskWriteCount);
247  PhUpdateDelta(&EtNetworkReceiveCountDelta, EtNetworkReceiveCount);
248  PhUpdateDelta(&EtNetworkSendCountDelta, EtNetworkSendCount);
249 
250  // Update per-process statistics.
251  // Note: no lock is needed because we only ever modify the list on this same thread.
252 
253  listEntry = EtProcessBlockListHead.Flink;
254 
255  while (listEntry != &EtProcessBlockListHead)
256  {
257  PET_PROCESS_BLOCK block;
258 
259  block = CONTAINING_RECORD(listEntry, ET_PROCESS_BLOCK, ListEntry);
260 
261  PhUpdateDelta(&block->DiskReadDelta, block->DiskReadCount);
262  PhUpdateDelta(&block->DiskReadRawDelta, block->DiskReadRaw);
269 
270  if (maxDiskValue < block->DiskReadRawDelta.Delta + block->DiskWriteRawDelta.Delta)
271  {
272  maxDiskValue = block->DiskReadRawDelta.Delta + block->DiskWriteRawDelta.Delta;
273  maxDiskBlock = block;
274  }
275 
276  if (maxNetworkValue < block->NetworkReceiveRawDelta.Delta + block->NetworkSendRawDelta.Delta)
277  {
278  maxNetworkValue = block->NetworkReceiveRawDelta.Delta + block->NetworkSendRawDelta.Delta;
279  maxNetworkBlock = block;
280  }
281 
282  listEntry = listEntry->Flink;
283  }
284 
285  // Update history buffers.
286 
287  if (runCount != 0)
288  {
289  PhAddItemCircularBuffer_ULONG(&EtDiskReadHistory, EtDiskReadDelta.Delta);
290  PhAddItemCircularBuffer_ULONG(&EtDiskWriteHistory, EtDiskWriteDelta.Delta);
291  PhAddItemCircularBuffer_ULONG(&EtNetworkReceiveHistory, EtNetworkReceiveDelta.Delta);
292  PhAddItemCircularBuffer_ULONG(&EtNetworkSendHistory, EtNetworkSendDelta.Delta);
293 
294  if (maxDiskBlock)
295  {
296  PhAddItemCircularBuffer_ULONG(&EtMaxDiskHistory, HandleToUlong(maxDiskBlock->ProcessItem->ProcessId));
298  }
299  else
300  {
301  PhAddItemCircularBuffer_ULONG(&EtMaxDiskHistory, 0);
302  }
303 
304  if (maxNetworkBlock)
305  {
306  PhAddItemCircularBuffer_ULONG(&EtMaxNetworkHistory, HandleToUlong(maxNetworkBlock->ProcessItem->ProcessId));
308  }
309  else
310  {
311  PhAddItemCircularBuffer_ULONG(&EtMaxNetworkHistory, 0);
312  }
313  }
314 
315  runCount++;
316 }
317 
318 static VOID NTAPI EtpInvalidateNetworkNode(
319  _In_ PVOID Parameter
320  )
321 {
322  PPH_NETWORK_ITEM networkItem = Parameter;
323  PPH_NETWORK_NODE networkNode;
324 
325  if (networkNode = PhFindNetworkNode(networkItem))
327 
328  PhDereferenceObject(networkItem);
329 }
330 
332  _In_opt_ PVOID Parameter,
333  _In_opt_ PVOID Context
334  )
335 {
336  PLIST_ENTRY listEntry;
337 
338  // ETW is flushed in the processes-updated callback above. This may cause us the network
339  // blocks to all fall one update interval behind, however.
340 
341  // Update per-connection statistics.
342  // Note: no lock is needed because we only ever modify the list on this same thread.
343 
344  listEntry = EtNetworkBlockListHead.Flink;
345 
346  while (listEntry != &EtNetworkBlockListHead)
347  {
348  PET_NETWORK_BLOCK block;
349  PH_UINT64_DELTA oldDeltas[4];
350 
351  block = CONTAINING_RECORD(listEntry, ET_NETWORK_BLOCK, ListEntry);
352 
353  memcpy(oldDeltas, block->Deltas, sizeof(block->Deltas));
354 
355  PhUpdateDelta(&block->ReceiveDelta, block->ReceiveCount);
356  PhUpdateDelta(&block->ReceiveRawDelta, block->ReceiveRaw);
357  PhUpdateDelta(&block->SendDelta, block->SendCount);
358  PhUpdateDelta(&block->SendRawDelta, block->SendRaw);
359 
360  if (memcmp(oldDeltas, block->Deltas, sizeof(block->Deltas)))
361  {
362  // Values have changed. Invalidate the network node.
364  ProcessHacker_Invoke(PhMainWndHandle, EtpInvalidateNetworkNode, block->NetworkItem);
365  }
366 
367  listEntry = listEntry->Flink;
368  }
369 }
370 
372  VOID
373  )
374 {
375  PhAcquireQueuedLockExclusive(&EtpProcessInformationLock);
376 
378  {
380  EtpProcessInformation = NULL;
381  }
382 
384 
385  PhReleaseQueuedLockExclusive(&EtpProcessInformationLock);
386 }
387 
389  _In_ HANDLE ThreadId
390  )
391 {
393  ULONG i;
394  HANDLE processId;
395 
397  return NULL;
398 
399  PhAcquireQueuedLockShared(&EtpProcessInformationLock);
400 
402 
403  do
404  {
405  for (i = 0; i < process->NumberOfThreads; i++)
406  {
407  if (process->Threads[i].ClientId.UniqueThread == ThreadId)
408  {
409  processId = process->UniqueProcessId;
410  PhReleaseQueuedLockShared(&EtpProcessInformationLock);
411 
412  return processId;
413  }
414  }
415  } while (process = PH_NEXT_PROCESS(process));
416 
417  PhReleaseQueuedLockShared(&EtpProcessInformationLock);
418 
419  return NULL;
420 }