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