[Windbg Script] Extracting Performance Monitor counters from .NET application

Have you ever had a situation where you find yourself debugging a dump from ASP.NET when suddenly you notice you forgot to get the Performance Monitor log?

If sometimes you face this situation, I have great news for you: this script shows you some of the main .NET Performance Monitor counters. It gets the information from the ASP.NET process or dump being debugged.

This is one of the coolest scripts I have!

Take a look at these screenshots:

 

Source code for GET_PERFMON.TXT:

$$

$$ =============================================================================

$$ Get Performance Monitor counters from mscorsvr.dll or mscorwks.dll.

$$

$$ Compatibility: Win32/Win64 (This is a new version from 01/30/09)

$$

$$ Usage: $$>< to run the program.

$$

$$ Requirements: Public symbols.

$$

$$ If necessary change the filename below to include your path and filename.

$$ By default it uses the WinDbg path and the default file name is GET_PERFMON.TXT

$$

$$ Roberto Alexis Farah

$$ Blog: https://blogs.msdn.com/debuggingtoolbox/

$$

$$ All my scripts are provided "AS IS" with no warranties, and confer no rights.

$$ =============================================================================

$$

r @$t0 = 0;

r @$t1 = 0;

.block

{

as ${/v:ScriptName} MYSCRIPTS\\GET_PERFMON.TXT

}

.foreach(obj { lm1m })

{

.if((0 == $sicmp("${obj}", "mscorsvr")) | (0 == $sicmp("${obj}", "mscorwks")))

{

r @$t0 = ${obj}!PerfCounters::m_pPrivatePerf;

}

}

.if(0 == @$t0)

{

.printf /D "<b>\nThis is not a .NET application!\n</b>";

}

.else

{

.block

{

.block

{

as ${/v:GCCounters} .block

{

r @$t1 = poi(@$t0) + @$ptrsize;

.printf "\n.NET GC Counters\n\n";

.printf "GenCollection 0 = 0n%d\n", poi(@$t1);

.printf "GenCollection 1 = 0n%d\n", poi(@$t1+@$ptrsize);

.printf "GenCollection 2 = 0n%d\n", poi(@$t1+@$ptrsize*2);

.printf "PromotedMemory = 0n%d\n", poi(@$t1+@$ptrsize*3);

.printf "PromotedMemory 1 = 0n%d\n", poi(@$t1+@$ptrsize*4);

.printf "PromotedFinalizationMem 0 = 0n%d\n", poi(@$t1+@$ptrsize*5);

.printf "Process ID = 0n%d\n", poi(@$t1+@$ptrsize*6);

.printf "GenHeapSize 0 = 0n%d\n", poi(@$t1+@$ptrsize*7);

.printf "GenHeapSize 1 = 0n%d\n", poi(@$t1+@$ptrsize*8);

.printf "GenHeapSize 2 = 0n%d\n", poi(@$t1+@$ptrsize*9);

.printf "TotalCommittedBytes = 0n%d\n", poi(@$t1+@$ptrsize*0n10);

.printf "TotalReservedBytes = 0n%d\n", poi(@$t1+@$ptrsize*0n11);

.printf "LargeObjectSize = 0n%d\n", poi(@$t1+@$ptrsize*0n12);

.printf "SurviveFinalize = 0n%d\n", poi(@$t1+@$ptrsize*0n13);

.printf "Handles = 0n%d\n", poi(@$t1+@$ptrsize*0n14);

.printf "Alloc = 0x%x\n", poi(@$t1+@$ptrsize*0n15);

.printf "LargeAlloc = 0x%x\n", poi(@$t1+@$ptrsize*0n16);

.printf "InducedGCs = 0n%d\n", poi(@$t1+@$ptrsize*0n17);

.printf "TimeInGC = 0n%d\n", poi(@$t1+@$ptrsize*0n18);

.printf "TimeInGCBase = 0n%d\n", poi(@$t1+@$ptrsize*0n19);

.printf "PinnedObjects = 0n%d\n", poi(@$t1+@$ptrsize*0n20);

.printf "SinkBlocks = 0n%d\n\n", poi(@$t1+@$ptrsize*0n21);

}

}

.block

{

as ${/v:InteropCounters} .block

{

r @$t1 = poi(@$t0) + 0x74;

.printf "\n.NET Interop Counters\n\n";

.printf "CCW = 0n%d\n", poi(@$t1);

.printf "Stubs = 0n%d\n", poi(@$t1+0x4);

.printf "Marshalling = 0n%d\n", poi(@$t1+0x8);

.printf "TLBImports = 0n%d\n", poi(@$t1+0xc);

.printf "TLBExports = 0n%d\n\n", poi(@$t1+0x10);

}

}

.block

{

as ${/v:LoadingCounters} .block

{

r @$t1 = poi(@$t0) + 0x88;

.printf "\n.NET Loading Counters\n\n";

.printf "Current ClassesLoaded = 0n%d\n", poi(@$t1);

.printf "Total ClassesLoaded = 0n%d\n", poi(@$t1+0x4);

.printf "Current AppDomains = 0n%d\n", poi(@$t1+0x8);

.printf "Total AppDomains = 0n%d\n", poi(@$t1+0xc);

.printf "Current Assemblies = 0n%d\n", poi(@$t1+0x10);

.printf "Total Assemblies = 0n%d\n", poi(@$t1+0x14);

.printf "Time Loading = 0n%d\n", poi(@$t1+0x18);

.printf "AsmSearchLen = 0n%d\n", poi(@$t1+0x20);

.printf "Total LoadFailures = 0n%d\n", poi(@$t1+0x24);

.printf "LoaderHeapSize = 0n%d\n", poi(@$t1+0x28);

.printf "AppDomainsUnloaded = 0n%d\n\n", poi(@$t1+0x2c);

}

}

.block

{

as ${/v:ExceptionCounters} .block

{

r @$t1 = poi(@$t0) + 0xb8;

.printf "\n.NET Exception Counters\n\n";

.printf "Total Exceptions = 0n%d\n", poi(@$t1);

.printf "Filters Executed = 0n%d\n", poi(@$t1+0x4);

.printf "Finallys Executed = 0n%d\n", poi(@$t1+0x8);

.printf "ThrowToCatchStackDepth = 0n%d\n\n", poi(@$t1+0xc);

}

}

.block

{

as ${/v:LockAndThreadCounters} .block

{

r @$t1 = poi(@$t0) + 0xc8;

.printf "\n.NET Locks and Threads Counters\n\n";

.printf "Total Contention = 0n%d\n", poi(@$t1);

.printf "QueueLength Current = 0n%d\n", poi(@$t1+0x4);

.printf "QueueLength Total = 0n%d\n", poi(@$t1+0x8);

.printf "CurrentThreadsLogical = 0n%d\n", poi(@$t1+0xc);

.printf "CurrentThreadsPhysical = 0n%d\n", poi(@$t1+0x10);

.printf "RecognizedThreads Current = 0n%d\n", poi(@$t1+0x14);

.printf "RecognizedThreads Total = 0n%d\n\n", poi(@$t1+0x18);

}

}

.block

{

as ${/v:JITCounters} .block

{

r @$t1 = poi(@$t0) + 0xe4;

.printf "\n.NET JIT Counters\n\n";

.printf "Methods Jitted = 0n%d\n", poi(@$t1);

.printf "IL Jitted Current = 0n%d\n", poi(@$t1+0x4);

.printf "IL Jitted Total = 0n%d\n", poi(@$t1+0x8);

.printf "JIT Failures = 0n%d\n", poi(@$t1+0xc);

.printf "Time In JIT = 0n%d\n", poi(@$t1+0x10);

.printf "Time In JIT Base = 0n%d\n\n", poi(@$t1+0x14);

}

}

.block

{

as ${/v:SecurityCounters} .block

{

r @$t1 = poi(@$t0) + 0xfc;

.printf "\n.NET Security Counters\n\n";

.printf "Total RunTime Checks = 0n%d\n", poi(@$t1);

.printf "Time Authorize = 0n%d\n", poi(@$t1+0x4);

.printf "Link Checks = 0n%d\n", poi(@$t1+0xc);

.printf "Time RunTime Checks = 0n%d\n", poi(@$t1+0x10);

.printf "Time RunTime Checks Base = 0n%d\n", poi(@$t1+0x14);

.printf "Stack Walk Depth = 0n%d\n\n", poi(@$t1+0x18);

}

}

}

.printf /D "<link cmd=\"${GCCounters} ad ${/v:ScriptName};ad ${/v:GCCounters}; ad ${/v:InteropCounters};ad ${/v:LoadingCounters};ad ${/v:ExceptionCounters};ad ${/v:LockAndThreadCounters};ad ${/v:JITCounters};ad ${/v:SecurityCounters}; $$><${ScriptName}\"><b>.NET GC Counters</b></link>\n\n"

.printf /D "<link cmd=\"${InteropCounters} ad ${/v:ScriptName};ad ${/v:GCCounters}; ad ${/v:InteropCounters};ad ${/v:LoadingCounters};ad ${/v:ExceptionCounters};ad ${/v:LockAndThreadCounters};ad ${/v:JITCounters};ad ${/v:SecurityCounters}; $$><${ScriptName}\"><b>.NET Interop Counters</b></link>\n\n"

.printf /D "<link cmd=\"${LoadingCounters} ad ${/v:ScriptName}; ad ${/v:GCCounters}; ad ${/v:InteropCounters};ad ${/v:LoadingCounters};ad ${/v:ExceptionCounters};ad ${/v:LockAndThreadCounters};ad ${/v:JITCounters};ad ${/v:SecurityCounters}; $$><${ScriptName}\"><b>.NET Loading Counters</b></link>\n\n"

.printf /D "<link cmd=\"${ExceptionCounters} ad ${/v:ScriptName}; ad ${/v:GCCounters}; ad ${/v:InteropCounters};ad ${/v:LoadingCounters};ad ${/v:ExceptionCounters};ad ${/v:LockAndThreadCounters};ad ${/v:JITCounters};ad ${/v:SecurityCounters}; $$><${ScriptName}\"><b>.NET Exception Counters</b></link>\n\n"

.printf /D "<link cmd=\"${LockAndThreadCounters} ad ${/v:ScriptName}; ad ${/v:GCCounters}; ad ${/v:InteropCounters};ad ${/v:LoadingCounters};ad ${/v:ExceptionCounters};ad ${/v:LockAndThreadCounters};ad ${/v:JITCounters};ad ${/v:SecurityCounters}; $$><${ScriptName}\"><b>.NET LocksAndThreads Counters</b></link>\n\n"

.printf /D "<link cmd=\"${JITCounters} ad ${/v:ScriptName}; ad ${/v:GCCounters}; ad ${/v:InteropCounters};ad ${/v:LoadingCounters};ad ${/v:ExceptionCounters};ad ${/v:LockAndThreadCounters};ad ${/v:JITCounters};ad ${/v:SecurityCounters}; $$><${ScriptName}\"><b>.NET JIT Counters</b></link>\n\n"

.printf /D "<link cmd=\"${SecurityCounters} ad ${/v:ScriptName}; ad ${/v:GCCounters}; ad ${/v:InteropCounters};ad ${/v:LoadingCounters};ad ${/v:ExceptionCounters};ad ${/v:LockAndThreadCounters};ad ${/v:JITCounters};ad ${/v:SecurityCounters}; $$><${ScriptName}\"><b>.NET Security Counters</b></link>\n\n"

}

 

Read me.