931 lines
28 KiB
C
931 lines
28 KiB
C
|
/*++
|
||
|
|
||
|
Copyright (c) 1992 Microsoft Corporation
|
||
|
|
||
|
Module Name:
|
||
|
|
||
|
qlocks.c
|
||
|
|
||
|
Abstract:
|
||
|
|
||
|
WinDbg Extension Api
|
||
|
|
||
|
Author:
|
||
|
|
||
|
David N. Cutler (davec) 25-Sep-1999
|
||
|
|
||
|
Environment:
|
||
|
|
||
|
User Mode.
|
||
|
|
||
|
Revision History:
|
||
|
|
||
|
--*/
|
||
|
|
||
|
#include "precomp.h"
|
||
|
#pragma hdrstop
|
||
|
|
||
|
//
|
||
|
// Define queued lock data.
|
||
|
//
|
||
|
|
||
|
#define NUMBER_PROCESSORS 32
|
||
|
#define NUMBER_PROCESSORS_X86 32
|
||
|
#define NUMBER_PROCESSORS_IA64 64
|
||
|
|
||
|
#if (NUMBER_PROCESSORS_IA64 < MAXIMUM_PROCESSORS)
|
||
|
#error "Update NUMBER_PROCESSORS definition"
|
||
|
#endif
|
||
|
|
||
|
UCHAR Key[NUMBER_PROCESSORS];
|
||
|
|
||
|
#define KEY_CORRUPT 255
|
||
|
#define KEY_OWNER 254
|
||
|
#define KEY_NOTHING 253
|
||
|
|
||
|
typedef struct KSPIN_LOCK_QUEUE_READ {
|
||
|
ULONG64 Next;
|
||
|
ULONG64 Lock;
|
||
|
} KSPIN_LOCK_QUEUE_READ;
|
||
|
|
||
|
KSPIN_LOCK_QUEUE_READ LockQueue[NUMBER_PROCESSORS_IA64][LockQueueMaximumLock];
|
||
|
|
||
|
ULONG64 ProcessorBlock[NUMBER_PROCESSORS_IA64];
|
||
|
|
||
|
ULONG64 SpinLock[LockQueueMaximumLock];
|
||
|
|
||
|
typedef struct _LOCK_NAME {
|
||
|
KSPIN_LOCK_QUEUE_NUMBER Number;
|
||
|
PCHAR Name;
|
||
|
} LOCK_NAME, *PLOCK_NAME;
|
||
|
|
||
|
LOCK_NAME LockName[] = {
|
||
|
{ LockQueueDispatcherLock, "KE - Dispatcher " },
|
||
|
{ LockQueueContextSwapLock, "KE - Context Swap " },
|
||
|
{ LockQueuePfnLock, "MM - PFN " },
|
||
|
{ LockQueueSystemSpaceLock, "MM - System Space " },
|
||
|
{ LockQueueVacbLock, "CC - Vacb " },
|
||
|
{ LockQueueMasterLock, "CC - Master " },
|
||
|
{ LockQueueNonPagedPoolLock, "EX - NonPagedPool " },
|
||
|
{ LockQueueIoCancelLock, "IO - Cancel " },
|
||
|
{ LockQueueWorkQueueLock, "EX - WorkQueue " },
|
||
|
{ LockQueueIoVpbLock, "IO - Vpb " },
|
||
|
{ LockQueueIoDatabaseLock, "IO - Database " },
|
||
|
{ LockQueueIoCompletionLock, "IO - Completion " },
|
||
|
{ LockQueueNtfsStructLock, "NTFS - Struct " },
|
||
|
{ LockQueueAfdWorkQueueLock, "AFD - WorkQueue " },
|
||
|
{ LockQueueBcbLock, "CC - Bcb " },
|
||
|
{ LockQueueMaximumLock, NULL },
|
||
|
};
|
||
|
|
||
|
//
|
||
|
// Define forward referenced prototypes.
|
||
|
//
|
||
|
|
||
|
ULONG
|
||
|
ProcessorIndex (
|
||
|
ULONG64 LockAddress,
|
||
|
ULONG LockIndex
|
||
|
);
|
||
|
|
||
|
DECLARE_API( qlocks )
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
Dump kernel mode queued spinlock status.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
None.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
None.
|
||
|
|
||
|
--*/
|
||
|
|
||
|
{
|
||
|
|
||
|
BOOL Corrupt;
|
||
|
ULONG HighestProcessor;
|
||
|
ULONG Index;
|
||
|
KSPIN_LOCK_QUEUE_READ *LockOwner;
|
||
|
ULONG Last;
|
||
|
ULONG Loop;
|
||
|
ULONG64 MemoryAddress;
|
||
|
ULONG Number;
|
||
|
ULONG Result;
|
||
|
CHAR Sequence;
|
||
|
ULONG LockQueueOffset;
|
||
|
ULONG Processor;
|
||
|
ULONG PtrSize = DBG_PTR_SIZE;
|
||
|
ULONG SizeOfQ = GetTypeSize("nt!KSPIN_LOCK_QUEUE");
|
||
|
ULONG MaximumProcessors;
|
||
|
|
||
|
|
||
|
MaximumProcessors = (UCHAR) GetUlongValue("NT!KeNumberProcessors");
|
||
|
// IsPtr64() ? NUMBER_PROCESSORS_IA64 : NUMBER_PROCESSORS_X86;
|
||
|
|
||
|
//
|
||
|
// Get address of processor block array and read entire array.
|
||
|
//
|
||
|
|
||
|
MemoryAddress = GetExpression("nt!KiProcessorBlock");
|
||
|
if (MemoryAddress == 0) {
|
||
|
|
||
|
//
|
||
|
// Either the processor block address is zero or the processor
|
||
|
// block array could not be read.
|
||
|
//
|
||
|
|
||
|
dprintf("Unable to read processor block array\n");
|
||
|
return E_INVALIDARG;
|
||
|
}
|
||
|
HighestProcessor = 0;
|
||
|
for (Index = 0; Index < MaximumProcessors; Index++) {
|
||
|
|
||
|
if (!ReadPointer(MemoryAddress + Index*PtrSize, &ProcessorBlock[Index])) {
|
||
|
dprintf("Unable to read processor block array\n");
|
||
|
return E_INVALIDARG;
|
||
|
}
|
||
|
|
||
|
if (ProcessorBlock[Index] != 0) {
|
||
|
HighestProcessor = Index;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (GetFieldOffset("nt!KPRCB", "LockQueue", &LockQueueOffset)) {
|
||
|
dprintf("Unable to read KPRCB.LockQueue offset.\n");
|
||
|
return E_INVALIDARG;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Read the lock queue information for each processor.
|
||
|
//
|
||
|
|
||
|
for (Index = 0; Index < MaximumProcessors; Index += 1) {
|
||
|
RtlZeroMemory(&LockQueue[Index][0],
|
||
|
sizeof(KSPIN_LOCK_QUEUE_READ) * LockQueueMaximumLock);
|
||
|
|
||
|
if (ProcessorBlock[Index] != 0) {
|
||
|
ULONG j;
|
||
|
|
||
|
for (j=0; j< LockQueueMaximumLock; j++) {
|
||
|
if (GetFieldValue(ProcessorBlock[Index] + LockQueueOffset + j*SizeOfQ,
|
||
|
"nt!KSPIN_LOCK_QUEUE",
|
||
|
"Next",
|
||
|
LockQueue[Index][j].Next)) {
|
||
|
|
||
|
//
|
||
|
// Lock queue information could not be read for the respective
|
||
|
// processor.
|
||
|
//
|
||
|
|
||
|
dprintf("Unable to read lock queue information for processor %d @ %p\n",
|
||
|
Index, ProcessorBlock[Index]);
|
||
|
|
||
|
return E_INVALIDARG;
|
||
|
}
|
||
|
GetFieldValue(ProcessorBlock[Index] + LockQueueOffset + j*SizeOfQ,
|
||
|
"nt!KSPIN_LOCK_QUEUE",
|
||
|
"Lock",
|
||
|
LockQueue[Index][j].Lock);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Read the spin lock information for each queued lock.
|
||
|
//
|
||
|
|
||
|
for (Index = 0; Index < LockQueueMaximumLock; Index += 1) {
|
||
|
SpinLock[Index] = 0;
|
||
|
if (LockQueue[0][Index].Lock != 0) {
|
||
|
if (GetFieldValue(LockQueue[0][Index].Lock & ~(LOCK_QUEUE_WAIT | LOCK_QUEUE_OWNER),
|
||
|
"nt!PVOID", // KSPIN_LOCK == ULONG_PTR, this would sign-extens it
|
||
|
NULL,
|
||
|
SpinLock[Index])) {
|
||
|
|
||
|
//
|
||
|
// Spin lock information could not be read for the respective
|
||
|
// queued lock.
|
||
|
//
|
||
|
|
||
|
dprintf("Unable to read spin lock information for queued lock %d\n",
|
||
|
Index);
|
||
|
|
||
|
return E_INVALIDARG;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Verify that the kernel spin lock array is not corrupt. Each entry in
|
||
|
// this array should either be zero or contain the address of the correct
|
||
|
// lock queue entry in one of the processor control blocks.
|
||
|
//
|
||
|
|
||
|
Corrupt = FALSE;
|
||
|
for (Index = 0; Index < LockQueueMaximumLock && (LockName[Index].Name != NULL); Index += 1) {
|
||
|
if (SpinLock[Index] != 0) {
|
||
|
if (ProcessorIndex(SpinLock[Index], Index) == 0) {
|
||
|
Corrupt = TRUE;
|
||
|
dprintf("Kernel spin lock %s is corrupt.\n", LockName[Index].Name);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Verify that all lock queue entries are not corrupt. Each lock queue
|
||
|
// entry should either have a next field of NULL of contain the address
|
||
|
// of the correct lock queue entry in one of the processor control blocks.
|
||
|
//
|
||
|
|
||
|
for (Loop = 0; Loop < NUMBER_PROCESSORS; Loop += 1) {
|
||
|
for (Index = 0; Index < LockQueueMaximumLock; Index += 1) {
|
||
|
if (LockQueue[Loop][Index].Next != 0) {
|
||
|
if (ProcessorIndex(LockQueue[Loop][Index].Next, Index) == 0) {
|
||
|
Corrupt = TRUE;
|
||
|
dprintf("Lock entry %d for processor %d is corrupt\n",
|
||
|
Index,
|
||
|
Loop);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (Corrupt != FALSE) {
|
||
|
return E_INVALIDARG;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Output key information and headings.
|
||
|
//
|
||
|
|
||
|
dprintf("Key: O = Owner, 1-n = Wait order, blank = not owned/waiting, C = Corrupt\n\n");
|
||
|
dprintf(" Processor Number\n");
|
||
|
dprintf(" Lock Name ");
|
||
|
for (Index = 0; Index <= HighestProcessor; Index++) {
|
||
|
dprintf("%3d", Index);
|
||
|
}
|
||
|
dprintf("\n\n");
|
||
|
|
||
|
//
|
||
|
// Process each queued lock and output owner information.
|
||
|
//
|
||
|
|
||
|
for (Index = 0; Index < LockQueueMaximumLock && (LockName[Index].Name != NULL); Index += 1) {
|
||
|
|
||
|
if (Index != (ULONG) LockName[Index].Number) {
|
||
|
dprintf("ERROR: extension bug: name array does not match queued lock list!\n");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
dprintf("%s", LockName[Index].Name);
|
||
|
|
||
|
//
|
||
|
// If the lock is owned, then find the owner and any waiters. Output
|
||
|
// the owner and waiters in order.
|
||
|
//
|
||
|
// If the lock is not owned, then check the consistency of lock queue
|
||
|
// entries. They should all contain next pointer of NULL and both the
|
||
|
// owner and wait flags should be clear.
|
||
|
//
|
||
|
|
||
|
RtlFillMemory(&Key[0], NUMBER_PROCESSORS, KEY_NOTHING);
|
||
|
if (SpinLock[Index] != 0) {
|
||
|
LockOwner = NULL;
|
||
|
for (Loop = 0; Loop < NUMBER_PROCESSORS; Loop += 1) {
|
||
|
if (LockQueue[Loop][Index].Lock & LOCK_QUEUE_OWNER) {
|
||
|
LockOwner = &LockQueue[Loop][Index];
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// If the lock owner was not found, then assume that the kernel
|
||
|
// spin lock points to the owner and the owner bit has not been
|
||
|
// set yet. Otherwise, fill out the owner/wait key array.
|
||
|
//
|
||
|
|
||
|
if (LockOwner == NULL) {
|
||
|
Number = ProcessorIndex(SpinLock[Index], Index);
|
||
|
Key[Number - 1] = KEY_OWNER;
|
||
|
|
||
|
//
|
||
|
// The owner processor has been determined by the kernel
|
||
|
// spin lock address. Check to determine if any of the
|
||
|
// lock queue entries are corrupt and fill in the key
|
||
|
// array accordingly. A corrupt lock queue entry is one
|
||
|
// that has a non NULL next field or one of the owner or
|
||
|
// wait flags is set.
|
||
|
//
|
||
|
|
||
|
for (Loop = 0; Loop < NUMBER_PROCESSORS; Loop += 1) {
|
||
|
if ((LockQueue[Loop][Index].Next != 0) ||
|
||
|
(LockQueue[Loop][Index].Lock & (LOCK_QUEUE_WAIT | LOCK_QUEUE_OWNER))) {
|
||
|
Key[Loop] = KEY_CORRUPT;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
|
||
|
//
|
||
|
// The lock owner was found. Attempt to construct the wait
|
||
|
// chain.
|
||
|
//
|
||
|
|
||
|
Key[Loop] = KEY_OWNER;
|
||
|
Last = Loop;
|
||
|
Sequence = 0;
|
||
|
while (LockOwner->Next != 0) {
|
||
|
Number = ProcessorIndex(LockOwner->Next, Index);
|
||
|
if (Key[Number - 1] == KEY_NOTHING) {
|
||
|
Last = Number - 1;
|
||
|
Sequence += 1;
|
||
|
Key[Last] = Sequence;
|
||
|
LockOwner = &LockQueue[Last][Index];
|
||
|
|
||
|
} else {
|
||
|
|
||
|
//
|
||
|
// The wait chain loops back on itself. Mark the
|
||
|
// entry as corrupt and scan the other entries to
|
||
|
// detemine if they are also corrupt.
|
||
|
//
|
||
|
|
||
|
Key[Last] = KEY_CORRUPT;
|
||
|
for (Loop = 0; Loop < NUMBER_PROCESSORS; Loop += 1) {
|
||
|
if ((LockQueue[Loop][Index].Next != 0) ||
|
||
|
(LockQueue[Loop][Index].Lock & (LOCK_QUEUE_WAIT | LOCK_QUEUE_OWNER))) {
|
||
|
if (Key[Loop] == KEY_NOTHING) {
|
||
|
Key[Loop] = KEY_CORRUPT;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// If the lock owner next field is NULL, then the wait
|
||
|
// search ended normally. Check to determine if the kernel
|
||
|
// spin lock points to the last entry in the queue.
|
||
|
//
|
||
|
|
||
|
if (LockOwner->Next == 0) {
|
||
|
Number = ProcessorIndex(SpinLock[Index], Index);
|
||
|
if (Last != (Number - 1)) {
|
||
|
Sequence += 1;
|
||
|
Key[Number - 1] = Sequence;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
|
||
|
//
|
||
|
// The kernel spin lock is not owned. Check to determine if any
|
||
|
// of the lock queue entries are corrupt and fill in the key
|
||
|
// array accordingly. A corrupt entry is one that has a non NULL
|
||
|
// next field or one of the owner or wait flags is set.
|
||
|
//
|
||
|
|
||
|
for (Loop = 0; Loop < NUMBER_PROCESSORS; Loop += 1) {
|
||
|
if ((LockQueue[Loop][Index].Next != 0) ||
|
||
|
(LockQueue[Loop][Index].Lock & (LOCK_QUEUE_WAIT | LOCK_QUEUE_OWNER))) {
|
||
|
Key[Loop] = KEY_CORRUPT;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (Processor = 0; Processor <= HighestProcessor; Processor++) {
|
||
|
switch (Key[Processor]) {
|
||
|
case KEY_CORRUPT:
|
||
|
dprintf(" C");
|
||
|
break;
|
||
|
case KEY_OWNER:
|
||
|
dprintf(" O");
|
||
|
break;
|
||
|
case KEY_NOTHING:
|
||
|
dprintf(" ");
|
||
|
break;
|
||
|
default:
|
||
|
dprintf("%3d", Key[Processor]);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
dprintf("\n");
|
||
|
}
|
||
|
|
||
|
dprintf("\n");
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
ULONG
|
||
|
ProcessorIndex (
|
||
|
ULONG64 LockAddress,
|
||
|
ULONG LockIndex
|
||
|
)
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
This function computes the processor number of the respective processor
|
||
|
given a lock queue address and the lock queue index.
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
LockQueue - Supplies a lock queue address in target memory.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
Zero is returned if a matching processor is not found. Otherwise, the
|
||
|
processor number plus one is returned.
|
||
|
|
||
|
--*/
|
||
|
|
||
|
{
|
||
|
|
||
|
ULONG64 LockBase;
|
||
|
ULONG Loop;
|
||
|
ULONG SizeOfKprcb = GetTypeSize("nt!KPRCB");
|
||
|
ULONG SizeOfQ = GetTypeSize("nt!KSPIN_LOCK_QUEUE");
|
||
|
ULONG LockQueueOffset;
|
||
|
|
||
|
if (GetFieldOffset("nt!KPRCB", "LockQueue", &LockQueueOffset)) {
|
||
|
dprintf("Unable to read KPRCB type.\n");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Attempt to find the lock address in one of the processor control
|
||
|
// blocks.
|
||
|
//
|
||
|
|
||
|
for (Loop = 0; Loop < NUMBER_PROCESSORS; Loop += 1) {
|
||
|
if ((LockAddress >= ProcessorBlock[Loop]) &&
|
||
|
(LockAddress < ProcessorBlock[Loop] + SizeOfKprcb)) {
|
||
|
LockBase = ProcessorBlock[Loop] + LockQueueOffset;
|
||
|
if (LockAddress == (LockBase + SizeOfQ * LockIndex)) {
|
||
|
return Loop + 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
PUCHAR QueuedLockName[] = {
|
||
|
"DispatcherLock",
|
||
|
"ContextSwapLock",
|
||
|
"PfnLock",
|
||
|
"SystemSpaceLock",
|
||
|
"VacbLock",
|
||
|
"MasterLock",
|
||
|
"NonPagedPoolLock",
|
||
|
"IoCancelLock",
|
||
|
"WorkQueueLock",
|
||
|
"IoVpbLock",
|
||
|
"IoDatabaseLock",
|
||
|
"IoCompletionLock",
|
||
|
"NtfsStructLock",
|
||
|
"AfdWorkQueueLock",
|
||
|
"BcbLock"
|
||
|
};
|
||
|
|
||
|
DECLARE_API( qlockperf )
|
||
|
|
||
|
/*++
|
||
|
|
||
|
Routine Description:
|
||
|
|
||
|
Displays queued spin lock performance data (if present).
|
||
|
|
||
|
Arguments:
|
||
|
|
||
|
None.
|
||
|
|
||
|
Return Value:
|
||
|
|
||
|
None.
|
||
|
|
||
|
--*/
|
||
|
|
||
|
{
|
||
|
|
||
|
//
|
||
|
// The following structure is used to accumulate data about each
|
||
|
// acquire/release pair for a lock.
|
||
|
//
|
||
|
|
||
|
typedef struct {
|
||
|
union {
|
||
|
ULONGLONG Key;
|
||
|
struct {
|
||
|
ULONG_PTR Releaser;
|
||
|
ULONG_PTR Acquirer;
|
||
|
};
|
||
|
};
|
||
|
ULONGLONG Time;
|
||
|
ULONGLONG WaitTime;
|
||
|
ULONG Count;
|
||
|
ULONG Waiters;
|
||
|
ULONG Depth;
|
||
|
ULONG IncreasedDepth;
|
||
|
ULONG Clean;
|
||
|
} QLOCKDATA, *PQLOCKDATA;
|
||
|
|
||
|
//
|
||
|
// House keeping data for each lock.
|
||
|
//
|
||
|
|
||
|
typedef struct {
|
||
|
|
||
|
//
|
||
|
// The following fields are used to keep data from acquire
|
||
|
// to release.
|
||
|
//
|
||
|
|
||
|
ULONGLONG AcquireTime;
|
||
|
ULONGLONG WaitToAcquire;
|
||
|
ULONG_PTR AcquirePoint;
|
||
|
BOOLEAN Clean;
|
||
|
|
||
|
//
|
||
|
// Remaining fields accumulate global stats for this lock.
|
||
|
//
|
||
|
|
||
|
ULONG Count;
|
||
|
ULONG Pairs;
|
||
|
ULONG FailedTry;
|
||
|
UCHAR MaxDepth;
|
||
|
UCHAR PreviousDepth;
|
||
|
ULONG NoWait;
|
||
|
} QLOCKHOUSE, *PQLOCKHOUSE;
|
||
|
|
||
|
|
||
|
ULONG64 TargetHouse;
|
||
|
ULONG64 TargetLog;
|
||
|
PQLOCKHOUSE LockHome;
|
||
|
PQLOCKDATA LockData;
|
||
|
QLOCKDATA TempEntry;
|
||
|
ULONG LogEntrySize;
|
||
|
ULONG LogSize;
|
||
|
ULONG HouseEntrySize;
|
||
|
ULONG HouseSize;
|
||
|
ULONG NumberOfLocks;
|
||
|
ULONG LockIndex;
|
||
|
ULONG i, j;
|
||
|
ULONG MaxEntriesPerLock;
|
||
|
ULONG HighIndex;
|
||
|
ULONGLONG HighTime;
|
||
|
ULONGLONG TotalHoldTime;
|
||
|
ULONG PercentageHeld;
|
||
|
ULONG64 AcquiredAddress;
|
||
|
ULONG64 ReleasedAddress;
|
||
|
UCHAR AcquirePoint[80];
|
||
|
UCHAR ReleasePoint[80];
|
||
|
ULONG64 AcquireOffset;
|
||
|
ULONG64 ReleaseOffset;
|
||
|
BOOLEAN Verbose = FALSE;
|
||
|
BOOLEAN Columnar = FALSE;
|
||
|
BOOLEAN Interesting = FALSE;
|
||
|
ULONG LockLow, LockHigh;
|
||
|
|
||
|
//
|
||
|
// First, see if we can do anything useful.
|
||
|
//
|
||
|
// For the moment, this is x86 only.
|
||
|
//
|
||
|
|
||
|
if (TargetMachine != IMAGE_FILE_MACHINE_I386) {
|
||
|
dprintf("Sorry, don't know how to gather queued spinlock performance\n",
|
||
|
"data on anything but an x86.\n");
|
||
|
return E_INVALIDARG;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Parse arguments.
|
||
|
//
|
||
|
|
||
|
if (strstr(args, "?")) {
|
||
|
|
||
|
//
|
||
|
// Has asked for usage information. Give them the options
|
||
|
// and an explanation of the output.
|
||
|
//
|
||
|
|
||
|
dprintf("usage: qlockperf [-v] [n]\n"
|
||
|
" -v indicates verbose output (see below).\n"
|
||
|
" -c verbose columnar output.\n"
|
||
|
" -ci verbose columnar output, no totals.\n"
|
||
|
" n supplies the lock number (default is all)\n\n"
|
||
|
"Verbose output includes details of each lock acquire and\n"
|
||
|
"release pair. Two lines per pair.\n\n"
|
||
|
"Line 1: ppp A symbolic_address R symbolic_address\n"
|
||
|
" ppp percentage, this pair for this lock (overall)\n"
|
||
|
" A Acquire point\n"
|
||
|
" R Release point\n\n"
|
||
|
"Line 2:\n"
|
||
|
" HT Hold Time total (average)\n"
|
||
|
" This is the time from acquire to release.\n"
|
||
|
" WT Wait Time total (average)\n"
|
||
|
" This is the time waiting to acquire.\n"
|
||
|
" C Count\n"
|
||
|
" Number of times this pair occured.\n"
|
||
|
" CA Clean Acquires (percentage)\n"
|
||
|
" Number of times acquire did not wait.\n"
|
||
|
" WC Waiter Count\n"
|
||
|
" Number of processors waiting for this\n"
|
||
|
" lock at release.\n"
|
||
|
" avD Average number of waiters (at release).\n"
|
||
|
" ID Increased Depth\n"
|
||
|
" Number of times the queue length increased\n"
|
||
|
" while the lock was held in this pair.\n"
|
||
|
);
|
||
|
return E_INVALIDARG;
|
||
|
}
|
||
|
|
||
|
if (strstr(args, "-c")) {
|
||
|
Verbose = TRUE;
|
||
|
Columnar = TRUE;
|
||
|
}
|
||
|
|
||
|
if (strstr(args, "-ci")) {
|
||
|
Interesting = TRUE;
|
||
|
}
|
||
|
|
||
|
if (strstr(args, "-v")) {
|
||
|
Verbose = TRUE;
|
||
|
}
|
||
|
|
||
|
LockLow = 0;
|
||
|
LockHigh = 999;
|
||
|
|
||
|
for (i = 0; args[i]; i++) {
|
||
|
if ((args[i] >= '0') && (args[i] <= '9')) {
|
||
|
LockLow = (ULONG)GetExpression(&args[i]);
|
||
|
LockHigh = LockLow;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
TargetHouse = GetExpression("nt!KiQueuedSpinLockHouse");
|
||
|
|
||
|
//
|
||
|
// Checking for control C after the first operation that might
|
||
|
// cause symbol load in case the user has bad symbols and is
|
||
|
// trying to get out.
|
||
|
//
|
||
|
|
||
|
if (CheckControlC()) {
|
||
|
return E_ABORT;
|
||
|
}
|
||
|
|
||
|
TargetLog = GetExpression("nt!KiQueuedSpinLockLog");
|
||
|
LogEntrySize = GetTypeSize("nt!QLOCKDATA");
|
||
|
LogSize = GetTypeSize("nt!KiQueuedSpinLockLog");
|
||
|
HouseEntrySize = GetTypeSize("nt!QLOCKHOUSE");
|
||
|
HouseSize = GetTypeSize("nt!KiQueuedSpinLockHouse");
|
||
|
|
||
|
if (!(TargetHouse &&
|
||
|
TargetLog &&
|
||
|
LogEntrySize &&
|
||
|
LogSize &&
|
||
|
HouseEntrySize &&
|
||
|
HouseSize)) {
|
||
|
dprintf("Sorry, can't find required system data, perhaps this kernel\n",
|
||
|
"was not built with QLOCK_STAT_GATHER defined?\n");
|
||
|
return E_INVALIDARG;
|
||
|
}
|
||
|
|
||
|
if ((LogEntrySize != sizeof(QLOCKDATA)) ||
|
||
|
(HouseEntrySize != sizeof(QLOCKHOUSE))) {
|
||
|
dprintf("Structure sizes in the kernel and debugger extension don't\n",
|
||
|
"match. This extension needs to be rebuild to match the\n",
|
||
|
"running system.\n");
|
||
|
return E_INVALIDARG;
|
||
|
}
|
||
|
|
||
|
NumberOfLocks = HouseSize / HouseEntrySize;
|
||
|
MaxEntriesPerLock = LogSize / LogEntrySize / NumberOfLocks;
|
||
|
dprintf("Kernel build with %d PRCB queued spinlocks\n", NumberOfLocks);
|
||
|
dprintf("(maximum log entries per lock = %d)\n", MaxEntriesPerLock);
|
||
|
|
||
|
if (LockHigh >= NumberOfLocks) {
|
||
|
if (LockLow == LockHigh) {
|
||
|
dprintf("User requested lock %d, system has only %d locks, quitting.\n",
|
||
|
LockLow,
|
||
|
NumberOfLocks);
|
||
|
return E_INVALIDARG;
|
||
|
}
|
||
|
LockHigh = NumberOfLocks - 1;
|
||
|
}
|
||
|
|
||
|
if (NumberOfLocks > 16) {
|
||
|
|
||
|
//
|
||
|
// I don't believe it.
|
||
|
//
|
||
|
|
||
|
dprintf("The number of locks doesn't seem reasonable, giving up.\n");
|
||
|
return E_INVALIDARG;
|
||
|
}
|
||
|
|
||
|
if (CheckControlC()) {
|
||
|
return E_ABORT;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Allocate space to process the data for one lock at a time.
|
||
|
//
|
||
|
|
||
|
LockHome = LocalAlloc(LPTR, sizeof(*LockHome));
|
||
|
LockData = LocalAlloc(LPTR, sizeof(*LockData) * MaxEntriesPerLock);
|
||
|
|
||
|
if (!(LockHome && LockData)) {
|
||
|
dprintf("Couldn't allocate memory for local copies of kernel data.\n",
|
||
|
"unable to continue.\n");
|
||
|
goto outtahere;
|
||
|
}
|
||
|
|
||
|
for (LockIndex = LockLow; LockIndex <= LockHigh; LockIndex++) {
|
||
|
if ((!ReadMemory(TargetHouse + (LockIndex * sizeof(QLOCKHOUSE)),
|
||
|
LockHome,
|
||
|
sizeof(QLOCKHOUSE),
|
||
|
&i)) || (i < sizeof(QLOCKHOUSE))) {
|
||
|
dprintf("unable to read data for lock %d, quitting\n",
|
||
|
LockIndex);
|
||
|
return E_INVALIDARG;
|
||
|
}
|
||
|
|
||
|
if (CheckControlC()) {
|
||
|
goto outtahere;
|
||
|
}
|
||
|
|
||
|
if (LockHome->Pairs == 0) {
|
||
|
continue;
|
||
|
}
|
||
|
dprintf("\nLock %d %s\n", LockIndex, QueuedLockName[LockIndex]);
|
||
|
dprintf(" Acquires %d (%d pairs)\n", LockHome->Count, LockHome->Pairs);
|
||
|
dprintf(" Failed Tries %d\n", LockHome->FailedTry);
|
||
|
dprintf(" Maximum Depth (at release) %d\n", LockHome->MaxDepth);
|
||
|
dprintf(" No Waiters (at acquire) %d (%d%%)\n",
|
||
|
LockHome->NoWait,
|
||
|
LockHome->NoWait * 100 / LockHome->Count);
|
||
|
|
||
|
//
|
||
|
// Change the following to a parameter saying we want the
|
||
|
// details.
|
||
|
//
|
||
|
|
||
|
if (Verbose) {
|
||
|
ULONG Entries = LockHome->Pairs;
|
||
|
PQLOCKDATA Entry;
|
||
|
|
||
|
if ((!ReadMemory(TargetLog + (LockIndex * MaxEntriesPerLock * sizeof(QLOCKDATA)),
|
||
|
LockData,
|
||
|
Entries * sizeof(QLOCKDATA),
|
||
|
&i)) || (i < (Entries * sizeof(QLOCKDATA)))) {
|
||
|
dprintf("unable to read data for lock %d, quitting\n",
|
||
|
LockIndex);
|
||
|
return E_INVALIDARG;
|
||
|
}
|
||
|
|
||
|
if (CheckControlC()) {
|
||
|
goto outtahere;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Sort table into longest duration.
|
||
|
//
|
||
|
|
||
|
TotalHoldTime = 0;
|
||
|
for (i = 0; i < (Entries - 1); i++) {
|
||
|
HighTime = LockData[i].Time;
|
||
|
HighIndex = i;
|
||
|
for (j = i + 1; j < Entries; j++) {
|
||
|
if (LockData[j].Time > HighTime) {
|
||
|
HighIndex = j;
|
||
|
HighTime = LockData[j].Time;
|
||
|
}
|
||
|
}
|
||
|
if (HighIndex != i) {
|
||
|
|
||
|
//
|
||
|
// Swap entries.
|
||
|
//
|
||
|
|
||
|
TempEntry = LockData[i];
|
||
|
LockData[i] = LockData[HighIndex];
|
||
|
LockData[HighIndex] = TempEntry;
|
||
|
}
|
||
|
TotalHoldTime += LockData[i].Time;
|
||
|
}
|
||
|
TotalHoldTime += LockData[Entries-1].Time;
|
||
|
dprintf(" Total time held %I64ld\n");
|
||
|
|
||
|
//
|
||
|
// Print something!
|
||
|
//
|
||
|
|
||
|
if (Interesting) {
|
||
|
dprintf("\n Average Average Count %% Av. %%\n"
|
||
|
" %% Hold Wait 0w Dp Con\n");
|
||
|
} else if (Columnar) {
|
||
|
dprintf("\n Total Average Total Average Count Clean %% Waiters Av Increased %%\n"
|
||
|
" %% Hold Hold Wait Wait 0w Dp Con\n");
|
||
|
}
|
||
|
for (i = 0; i < Entries; i++) {
|
||
|
|
||
|
if (CheckControlC()) {
|
||
|
goto outtahere;
|
||
|
}
|
||
|
|
||
|
Entry = &LockData[i];
|
||
|
|
||
|
//
|
||
|
// Sign extend if necessary.
|
||
|
//
|
||
|
|
||
|
if (!IsPtr64()) {
|
||
|
AcquiredAddress = (ULONG64)(LONG64)(LONG)Entry->Acquirer;
|
||
|
ReleasedAddress = (ULONG64)(LONG64)(LONG)Entry->Releaser;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Lookup the symbolic addresses.
|
||
|
//
|
||
|
|
||
|
GetSymbol(AcquiredAddress, AcquirePoint, &AcquireOffset);
|
||
|
GetSymbol(ReleasedAddress, ReleasePoint, &ReleaseOffset);
|
||
|
|
||
|
PercentageHeld = (ULONG)(Entry->Time * 100 / TotalHoldTime);
|
||
|
|
||
|
if (Interesting) {
|
||
|
dprintf("%3d%9d%9d%10d%4d%3d%4d %s+0x%x %s+0x%x\n",
|
||
|
PercentageHeld,
|
||
|
(ULONG)(Entry->Time / Entry->Count),
|
||
|
(ULONG)(Entry->WaitTime / Entry->Count),
|
||
|
Entry->Count,
|
||
|
Entry->Clean * 100 / Entry->Count,
|
||
|
Entry->Depth / Entry->Count,
|
||
|
Entry->IncreasedDepth * 100 / Entry->Count,
|
||
|
AcquirePoint, (ULONG)AcquireOffset,
|
||
|
ReleasePoint, (ULONG)ReleaseOffset);
|
||
|
|
||
|
} else if (Columnar) {
|
||
|
dprintf("%3d %20I64ld%9d%20I64ld%9d",
|
||
|
PercentageHeld,
|
||
|
Entry->Time,
|
||
|
(ULONG)(Entry->Time / Entry->Count),
|
||
|
Entry->WaitTime,
|
||
|
(ULONG)(Entry->WaitTime / Entry->Count));
|
||
|
dprintf("%10d%10d%4d%10d%3d%10d%4d %s+0x%x %s+0x%x\n",
|
||
|
Entry->Count,
|
||
|
Entry->Clean,
|
||
|
Entry->Clean * 100 / Entry->Count,
|
||
|
Entry->Waiters,
|
||
|
Entry->Depth / Entry->Count,
|
||
|
Entry->IncreasedDepth,
|
||
|
Entry->IncreasedDepth * 100 / Entry->Count,
|
||
|
AcquirePoint, (ULONG)AcquireOffset,
|
||
|
ReleasePoint, (ULONG)ReleaseOffset);
|
||
|
|
||
|
} else {
|
||
|
dprintf("%3d A %s+0x%x R %s+0x%x\n",
|
||
|
PercentageHeld,
|
||
|
AcquirePoint, (ULONG)AcquireOffset,
|
||
|
ReleasePoint, (ULONG)ReleaseOffset);
|
||
|
dprintf(" HT %I64ld (av %I64ld), WT %I64ld (av %I64ld), C %d, CA %d (%d%%) WC %d, (avD %d) ID %d (%d%%)\n",
|
||
|
Entry->Time,
|
||
|
Entry->Time / Entry->Count,
|
||
|
Entry->WaitTime,
|
||
|
Entry->WaitTime / Entry->Count,
|
||
|
Entry->Count,
|
||
|
Entry->Clean,
|
||
|
Entry->Clean * 100 / Entry->Count,
|
||
|
Entry->Waiters,
|
||
|
Entry->Depth / Entry->Count,
|
||
|
Entry->IncreasedDepth,
|
||
|
Entry->IncreasedDepth * 100 / Entry->Count);
|
||
|
dprintf("\n");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
outtahere:
|
||
|
if (LockHome) {
|
||
|
LocalFree(LockHome);
|
||
|
}
|
||
|
if (LockData) {
|
||
|
LocalFree(LockData);
|
||
|
}
|
||
|
return S_OK;
|
||
|
}
|