Standardize comment headers. Patch by Trevor McCort
[reactos.git] / reactos / ntoskrnl / dbg / profile.c
1 /* $Id:$
2 *
3 * COPYRIGHT: See COPYING in the top level directory
4 * PROJECT: ReactOS kernel
5 * FILE: ntoskrnl/dbg/profile.c
6 * PURPOSE: Kernel profiling
7 *
8 * PROGRAMMERS: Casper S. Hornstrup (chorns@users.sourceforge.net)
9 */
10
11 /* INCLUDES *****************************************************************/
12
13 #include <ntoskrnl.h>
14 #include "kdb.h"
15
16 #define NDEBUG
17 #include <internal/debug.h>
18
19 /* FUNCTIONS *****************************************************************/
20
21 #define PROFILE_SESSION_LENGTH 30 /* Session length in seconds */
22
23 typedef struct _PROFILE_DATABASE_ENTRY
24 {
25 ULONG_PTR Address;
26 } PROFILE_DATABASE_ENTRY, *PPROFILE_DATABASE_ENTRY;
27
28 #define PDE_BLOCK_ENTRIES ((PAGE_SIZE - (sizeof(LIST_ENTRY) + sizeof(ULONG))) / sizeof(PROFILE_DATABASE_ENTRY))
29
30 typedef struct _PROFILE_DATABASE_BLOCK
31 {
32 LIST_ENTRY ListEntry;
33 ULONG UsedEntries;
34 PROFILE_DATABASE_ENTRY Entries[PDE_BLOCK_ENTRIES];
35 } PROFILE_DATABASE_BLOCK, *PPROFILE_DATABASE_BLOCK;
36
37 typedef struct _PROFILE_DATABASE
38 {
39 LIST_ENTRY ListHead;
40 } PROFILE_DATABASE, *PPROFILE_DATABASE;
41
42 typedef struct _SAMPLE_GROUP_INFO
43 {
44 ULONG_PTR Address;
45 ULONG Count;
46 CHAR Description[128];
47 LIST_ENTRY ListEntry;
48 } SAMPLE_GROUP_INFO, *PSAMPLE_GROUP_INFO;
49
50 static volatile BOOLEAN KdbProfilingInitialized = FALSE;
51 static volatile BOOLEAN KdbProfilingEnabled = FALSE;
52 static volatile BOOLEAN KdbProfilingSuspended = FALSE;
53 static PPROFILE_DATABASE KdbProfileDatabase = NULL;
54 static KDPC KdbProfilerCollectorDpc;
55 static HANDLE KdbProfilerThreadHandle;
56 static CLIENT_ID KdbProfilerThreadCid;
57 static HANDLE KdbProfilerLogFile;
58 static KTIMER KdbProfilerTimer;
59 static KMUTEX KdbProfilerLock;
60 static BOOLEAN KdbEnableProfiler = FALSE;
61
62 VOID
63 KdbDeleteProfileDatabase(PPROFILE_DATABASE ProfileDatabase)
64 {
65 PLIST_ENTRY current = NULL;
66
67 current = RemoveHeadList(&ProfileDatabase->ListHead);
68 while (current != &ProfileDatabase->ListHead)
69 {
70 PPROFILE_DATABASE_BLOCK block = CONTAINING_RECORD(
71 current, PROFILE_DATABASE_BLOCK, ListEntry);
72 ExFreePool(block);
73 current = RemoveHeadList(&ProfileDatabase->ListHead);
74 }
75 }
76
77 VOID
78 KdbAddEntryToProfileDatabase(PPROFILE_DATABASE ProfileDatabase, ULONG_PTR Address)
79 {
80 PPROFILE_DATABASE_BLOCK block;
81
82 if (IsListEmpty(&ProfileDatabase->ListHead))
83 {
84 block = ExAllocatePool(NonPagedPool, sizeof(PROFILE_DATABASE_BLOCK));
85 ASSERT(block);
86 block->UsedEntries = 0;
87 InsertTailList(&ProfileDatabase->ListHead, &block->ListEntry);
88 block->Entries[block->UsedEntries++].Address = Address;
89 return;
90 }
91
92 block = CONTAINING_RECORD(ProfileDatabase->ListHead.Blink, PROFILE_DATABASE_BLOCK, ListEntry);
93 if (block->UsedEntries >= PDE_BLOCK_ENTRIES)
94 {
95 block = ExAllocatePool(NonPagedPool, sizeof(PROFILE_DATABASE_BLOCK));
96 ASSERT(block);
97 block->UsedEntries = 0;
98 InsertTailList(&ProfileDatabase->ListHead, &block->ListEntry);
99 }
100 block->Entries[block->UsedEntries++].Address = Address;
101 }
102
103 VOID INIT_FUNCTION
104 KdbInitProfiling()
105 {
106 KdbEnableProfiler = TRUE;
107 }
108
109 VOID INIT_FUNCTION
110 KdbInitProfiling2()
111 {
112 if (KdbEnableProfiler)
113 {
114 KdbEnableProfiling();
115 KdbProfilingInitialized = TRUE;
116 }
117 }
118
119 VOID
120 KdbSuspendProfiling()
121 {
122 KdbProfilingSuspended = TRUE;
123 }
124
125 VOID
126 KdbResumeProfiling()
127 {
128 KdbProfilingSuspended = FALSE;
129 }
130
131 BOOLEAN
132 KdbProfilerGetSymbolInfo(PVOID address, OUT PCH NameBuffer)
133 {
134 PLIST_ENTRY current_entry;
135 MODULE_TEXT_SECTION* current;
136 extern LIST_ENTRY ModuleTextListHead;
137 ULONG_PTR RelativeAddress;
138 NTSTATUS Status;
139 ULONG LineNumber;
140 CHAR FileName[256];
141 CHAR FunctionName[256];
142
143 current_entry = ModuleTextListHead.Flink;
144
145 while (current_entry != &ModuleTextListHead &&
146 current_entry != NULL)
147 {
148 current =
149 CONTAINING_RECORD(current_entry, MODULE_TEXT_SECTION, ListEntry);
150
151 if (address >= (PVOID)current->Base &&
152 address < (PVOID)(current->Base + current->Length))
153 {
154 RelativeAddress = (ULONG_PTR) address - current->Base;
155 Status = KdbSymGetAddressInformation(&current->SymbolInfo,
156 RelativeAddress,
157 &LineNumber,
158 FileName,
159 FunctionName);
160 if (NT_SUCCESS(Status))
161 {
162 sprintf(NameBuffer, "%s (%s)", FileName, FunctionName);
163 return(TRUE);
164 }
165 return(TRUE);
166 }
167 current_entry = current_entry->Flink;
168 }
169 return(FALSE);
170 }
171
172 PLIST_ENTRY
173 KdbProfilerLargestSampleGroup(PLIST_ENTRY SamplesListHead)
174 {
175 PLIST_ENTRY current;
176 PLIST_ENTRY largest;
177 ULONG count;
178
179 count = 0;
180 largest = SamplesListHead->Flink;
181 current = SamplesListHead->Flink;
182 while (current != SamplesListHead)
183 {
184 PSAMPLE_GROUP_INFO sgi = CONTAINING_RECORD(
185 current, SAMPLE_GROUP_INFO, ListEntry);
186
187 if (sgi->Count > count)
188 {
189 largest = current;
190 count = sgi->Count;
191 }
192
193 current = current->Flink;
194 }
195 if (count == 0)
196 {
197 return NULL;
198 }
199 return largest;
200 }
201
202 VOID
203 KdbProfilerWriteString(PCH String)
204 {
205 IO_STATUS_BLOCK Iosb;
206 NTSTATUS Status;
207 ULONG Length;
208
209 Length = strlen(String);
210 Status = NtWriteFile(KdbProfilerLogFile,
211 NULL,
212 NULL,
213 NULL,
214 &Iosb,
215 String,
216 Length,
217 NULL,
218 NULL);
219
220 if (!NT_SUCCESS(Status))
221 {
222 DPRINT1("NtWriteFile() failed with status 0x%.08x\n", Status);
223 }
224 }
225
226 NTSTATUS
227 KdbProfilerWriteSampleGroups(PLIST_ENTRY SamplesListHead)
228 {
229 CHAR Buffer[256];
230 PLIST_ENTRY current = NULL;
231 PLIST_ENTRY Largest;
232
233 KdbProfilerWriteString("\r\n\r\n");
234 KdbProfilerWriteString("Count Symbol\n");
235 KdbProfilerWriteString("--------------------------------------------------\r\n");
236
237 current = SamplesListHead->Flink;
238 while (current != SamplesListHead)
239 {
240 Largest = KdbProfilerLargestSampleGroup(SamplesListHead);
241 if (Largest != NULL)
242 {
243 PSAMPLE_GROUP_INFO sgi = CONTAINING_RECORD(
244 Largest, SAMPLE_GROUP_INFO, ListEntry);
245
246 //DbgPrint("%.08lu %s\n", sgi->Count, sgi->Description);
247
248 sprintf(Buffer, "%.08lu %s\r\n", sgi->Count, sgi->Description);
249 KdbProfilerWriteString(Buffer);
250
251 RemoveEntryList(Largest);
252 ExFreePool(sgi);
253 }
254 else
255 {
256 break;
257 }
258
259 current = SamplesListHead->Flink;
260 }
261
262 return STATUS_SUCCESS;
263 }
264
265 LONG STDCALL
266 KdbProfilerKeyCompare(IN PVOID Key1,
267 IN PVOID Key2)
268 {
269 int value = strcmp(Key1, Key2);
270
271 if (value == 0)
272 return 0;
273
274 return (value < 0) ? -1 : 1;
275 }
276
277
278 NTSTATUS
279 KdbProfilerAnalyzeSamples()
280 {
281 CHAR NameBuffer[512];
282 ULONG KeyLength;
283 PLIST_ENTRY current = NULL;
284 HASH_TABLE Hashtable;
285 LIST_ENTRY SamplesListHead;
286 ULONG Index;
287 ULONG_PTR Address;
288
289 if (!ExInitializeHashTable(&Hashtable, 17, KdbProfilerKeyCompare, TRUE))
290 {
291 DPRINT1("ExInitializeHashTable() failed.");
292 KEBUGCHECK(0);
293 }
294
295 InitializeListHead(&SamplesListHead);
296
297 current = RemoveHeadList(&KdbProfileDatabase->ListHead);
298 while (current != &KdbProfileDatabase->ListHead)
299 {
300 PPROFILE_DATABASE_BLOCK block;
301
302 block = CONTAINING_RECORD(current, PROFILE_DATABASE_BLOCK, ListEntry);
303
304 for (Index = 0; Index < block->UsedEntries; Index++)
305 {
306 PSAMPLE_GROUP_INFO sgi;
307 Address = block->Entries[Index].Address;
308 if (KdbProfilerGetSymbolInfo((PVOID) Address, (PCH) &NameBuffer))
309 {
310 }
311 else
312 {
313 sprintf(NameBuffer, "(0x%.08lx)", (ULONG) Address);
314 }
315
316 KeyLength = strlen(NameBuffer);
317 if (!ExSearchHashTable(&Hashtable, (PVOID) NameBuffer, KeyLength, (PVOID *) &sgi))
318 {
319 sgi = ExAllocatePool(NonPagedPool, sizeof(SAMPLE_GROUP_INFO));
320 ASSERT(sgi);
321 sgi->Address = Address;
322 sgi->Count = 1;
323 strcpy(sgi->Description, NameBuffer);
324 InsertTailList(&SamplesListHead, &sgi->ListEntry);
325 ExInsertHashTable(&Hashtable, sgi->Description, KeyLength, (PVOID) sgi);
326 }
327 else
328 {
329 sgi->Count++;
330 }
331 }
332
333 ExFreePool(block);
334
335 current = RemoveHeadList(&KdbProfileDatabase->ListHead);
336 }
337
338 KdbProfilerWriteSampleGroups(&SamplesListHead);
339
340 ExDeleteHashTable(&Hashtable);
341
342 KdbDeleteProfileDatabase(KdbProfileDatabase);
343
344 return STATUS_SUCCESS;
345 }
346
347 VOID STDCALL
348 KdbProfilerThreadMain(PVOID Context)
349 {
350 for (;;)
351 {
352 KeWaitForSingleObject(&KdbProfilerTimer, Executive, KernelMode, TRUE, NULL);
353
354 KeWaitForSingleObject(&KdbProfilerLock, Executive, KernelMode, FALSE, NULL);
355
356 KdbSuspendProfiling();
357
358 KdbProfilerAnalyzeSamples();
359
360 KdbResumeProfiling();
361
362 KeReleaseMutex(&KdbProfilerLock, FALSE);
363 }
364 }
365
366 VOID
367 KdbDisableProfiling()
368 {
369 if (KdbProfilingEnabled == TRUE)
370 {
371 /* FIXME: Implement */
372 #if 0
373 KdbProfilingEnabled = FALSE;
374 /* Stop timer */
375 /* Close file */
376 if (KdbProfileDatabase != NULL)
377 {
378 KdbDeleteProfileDatabase(KdbProfileDatabase);
379 ExFreePool(KdbProfileDatabase);
380 KdbProfileDatabase = NULL;
381 }
382 #endif
383 }
384 }
385
386 /*
387 * SystemArgument1 = EIP
388 */
389 static VOID STDCALL
390 KdbProfilerCollectorDpcRoutine(PKDPC Dpc, PVOID DeferredContext,
391 PVOID SystemArgument1, PVOID SystemArgument2)
392 {
393 ULONG_PTR address = (ULONG_PTR) SystemArgument1;
394
395 KdbAddEntryToProfileDatabase(KdbProfileDatabase, address);
396 }
397
398 VOID INIT_FUNCTION
399 KdbEnableProfiling()
400 {
401 if (KdbProfilingEnabled == FALSE)
402 {
403 NTSTATUS Status;
404 OBJECT_ATTRIBUTES ObjectAttributes;
405 UNICODE_STRING FileName;
406 IO_STATUS_BLOCK Iosb;
407 LARGE_INTEGER DueTime;
408
409 RtlInitUnicodeString(&FileName, L"\\SystemRoot\\profiler.log");
410 InitializeObjectAttributes(&ObjectAttributes,
411 &FileName,
412 0,
413 NULL,
414 NULL);
415
416 Status = NtCreateFile(&KdbProfilerLogFile,
417 FILE_ALL_ACCESS,
418 &ObjectAttributes,
419 &Iosb,
420 NULL,
421 FILE_ATTRIBUTE_NORMAL,
422 0,
423 FILE_SUPERSEDE,
424 FILE_WRITE_THROUGH | FILE_SYNCHRONOUS_IO_NONALERT,
425 NULL,
426 0);
427 if (!NT_SUCCESS(Status))
428 {
429 DPRINT1("Failed to create profiler log file\n");
430 return;
431 }
432
433 Status = PsCreateSystemThread(&KdbProfilerThreadHandle,
434 THREAD_ALL_ACCESS,
435 NULL,
436 NULL,
437 &KdbProfilerThreadCid,
438 KdbProfilerThreadMain,
439 NULL);
440 if (!NT_SUCCESS(Status))
441 {
442 DPRINT1("Failed to create profiler thread\n");
443 return;
444 }
445
446 KeInitializeMutex(&KdbProfilerLock, 0);
447
448 KdbProfileDatabase = ExAllocatePool(NonPagedPool, sizeof(PROFILE_DATABASE));
449 ASSERT(KdbProfileDatabase);
450 InitializeListHead(&KdbProfileDatabase->ListHead);
451 KeInitializeDpc(&KdbProfilerCollectorDpc, KdbProfilerCollectorDpcRoutine, NULL);
452
453 /* Initialize our periodic timer and its associated DPC object. When the timer
454 expires, the KdbProfilerSessionEndDpc deferred procedure call (DPC) is queued */
455 KeInitializeTimerEx(&KdbProfilerTimer, SynchronizationTimer);
456
457 /* Start the periodic timer with an initial and periodic
458 relative expiration time of PROFILE_SESSION_LENGTH seconds */
459 DueTime.QuadPart = -(LONGLONG) PROFILE_SESSION_LENGTH * 1000 * 10000;
460 KeSetTimerEx(&KdbProfilerTimer, DueTime, PROFILE_SESSION_LENGTH * 1000, NULL);
461
462 KdbProfilingEnabled = TRUE;
463 }
464 }
465
466 VOID
467 KdbProfileInterrupt(ULONG_PTR Address)
468 {
469 ASSERT(KeGetCurrentIrql() == PROFILE_LEVEL);
470
471 if (KdbProfilingInitialized != TRUE)
472 {
473 return;
474 }
475
476 if ((KdbProfilingEnabled) && (!KdbProfilingSuspended))
477 {
478 (BOOLEAN) KeInsertQueueDpc(&KdbProfilerCollectorDpc, (PVOID) Address, NULL);
479 }
480 }