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