Get GC Information in Silverlight


I had earlier written a post on getting GC information on Silverlight using ETW. With that we would have to write code to parse the ETW csv file.  In this post I am going to be using Perfmonitor to do this. This tools uses the same ETW under covers, but it does all the plumbing and gives a nice report , which is much easier to read.  Here are the reports

To demonstrate this I used the bing’s world leader search page and here is the url

http://www.bing.com/visualsearch?q=World+leaders&g=world_leaders&FORM=Z9GE74#

Steps to get the GC information are

  1. Start a cmd or powershell  as admin , this required to collect ETW tracing
  2. Browse the above mentioned webpage using IE
  3. Issue the command “PerfMonitor.exe /process:4180 start” where 4180 is the internet explorer’s process id
  4. Do the necessary actions
  5. Then issue “PerfMonitor.exe stop”
  6. The command to get the report “PerfMonitor.exe GCTime”. This will generate a report and open it in the browser

Perfmonitor is like xperf for managed code. This is non-intrusive and can collect some valuable information in production. This is an xcopy tool and does not need an install.

Get managed call-stacks in .NET for Registry access using ETW


I was recently debugging managed code which was accessing system registry implicitly because of an external dependent library.  So the first thing I asked was a Procmon log for of registry access. And then I also wanted look at the call-stacks for registry access, which Procmon does provide.

Here is a sample call-stack from Procmon for registry access. I am using linqpad as an example in this

ntoskrnl.exe    CmpCallCallBacks + 0x1c0    0xfffff80002c870d0
ntoskrnl.exe     ?? ::NNGAKEGL::`string’ + 0x4c81d    0xfffff80002c31116
ntoskrnl.exe    KiSystemServiceCopyEnd + 0×13    0xfffff800028d4853
ntdll.dll    ZwSetInformationKey + 0xa    0x779214aa
wow64.dll    wow64.dll + 0x2d252    0x7399d252
wow64.dll    wow64.dll + 0x1008f    0x7398008f
wow64.dll    wow64.dll + 0xcf87    0x7397cf87
wow64cpu.dll    TurboDispatchJumpAddressEnd + 0×24    0x7390276d
wow64.dll    wow64.dll + 0xd07e    0x7397d07e
wow64.dll    wow64.dll + 0xc549    0x7397c549
ntdll.dll    LdrpInitializeProcess + 0x17e2    0x779184c8
ntdll.dll     ?? ::FNODOBFM::`string’ + 0x2bea0    0×77917623
ntdll.dll    LdrInitializeThunk + 0xe    0x7790308e
ntdll.dll    NtOpenKey + 0×12    0x77acf9da
KernelBase.dll    OpenRegKey + 0×134    0x772f2ef3
KernelBase.dll    OpenAltSortsKey + 0×29    0x772de216
KernelBase.dll    IsValidLocale + 0×127    0x772e3271
clr.dll    clr.dll + 0xe4d5f    0x6be34d5f
clr.dll    clr.dll + 0xe4ba8    0x6be34ba8
mscorlib.ni.dll    mscorlib.ni.dll + 0x2ba2f1    0x697ba2f1
mscorlib.ni.dll    mscorlib.ni.dll + 0x2ba2c1    0x697ba2c1
mscorlib.ni.dll    mscorlib.ni.dll + 0x2b9faa    0x697b9faa
mscorlib.ni.dll    mscorlib.ni.dll + 0x2b9e0a    0x697b9e0a
mscorlib.ni.dll    mscorlib.ni.dll + 0x2ba15c    0x697ba15c
mscorlib.ni.dll    mscorlib.ni.dll + 0x2ba106    0x697ba106
mscorlib.ni.dll    mscorlib.ni.dll + 0x2b9db8    0x697b9db8
mscorlib.ni.dll    mscorlib.ni.dll + 0x2b9d34    0x697b9d34
clr.dll    clr.dll + 0x24a2a    0x6bd74a2a
clr.dll    clr.dll + 0×23153    0x6bd73153
clr.dll    clr.dll + 0x231cc    0x6bd731cc
clr.dll    clr.dll + 0x2323b    0x6bd7323b
clr.dll    clr.dll + 0×23415    0x6bd73415
clr.dll    clr.dll + 0x2355a    0x6bd7355a
clr.dll    clr.dll + 0×94844    0x6bde4844
<unknown>    0xb2420    0xb2420
mscorlib.ni.dll    mscorlib.ni.dll + 0×255870    0×69755870
mscorlib.ni.dll    mscorlib.ni.dll + 0x2557cd    0x697557cd
mscorlib.ni.dll    mscorlib.ni.dll + 0×255764    0×69755764
<unknown>    0x4e00d9    0x4e00d9
clr.dll    clr.dll + 0x21db    0x6bd521db
clr.dll    clr.dll + 0x24a2a    0x6bd74a2a
clr.dll    clr.dll + 0x24bcc    0x6bd74bcc
clr.dll    clr.dll + 0x24c01    0x6bd74c01
clr.dll    clr.dll + 0x24c21    0x6bd74c21
clr.dll    clr.dll + 0xece82    0x6be3ce82
clr.dll    clr.dll + 0xecf90    0x6be3cf90
clr.dll    clr.dll + 0xecda4    0x6be3cda4
clr.dll    clr.dll + 0xed199    0x6be3d199
clr.dll    clr.dll + 0xed09a    0x6be3d09a
clr.dll    clr.dll + 0x16af00    0x6bebaf00
mscoreei.dll    mscoreei.dll + 0x55ab    0x726355ab
mscoree.dll    mscoree.dll + 0x7f16    0x75077f16
mscoree.dll    mscoree.dll + 0x4de3    0x75074de3
ntdll.dll    __RtlUserThreadStart + 0×70    0x77ae9d72
ntdll.dll    _RtlUserThreadStart + 0x1b    0x77ae9d45

It is all unmanaged call-stacks , what I was looking for was managed call-stacks. The solution to this was ETW tracing. I use Perfmonitor from the BCL teams codeplex site for getting managed call-stacks.

Here are the steps to get managed call-stacks for registry access

PerfMonitor.exe /registry /stacks /lineNumbers start

Run the process and do the steps for registry access

PerfMonitor.exe /registry /stacks /lineNumbers stop
PerfMonitor.exe  /stacks /lineNumbers print

The above should produce a xml file which is by default named as PerfMonitorOutput.print.xml

And here is the managed call-stacks for registry access.

<Event MSec=  “3449.0880″ PID=”3356″ PName= “LINQPad” TID=”4536″ EventName=”RegistryOpen” Status=”0×00000000″ KeyHandle=”0×00000000″ ElapsedTime=”0″ KeyName=”\Registry\Machine\System\CurrentControlSet\Control\Nls\Locale\Alternate Sorts” Index=”0″ InitialTime=”1/3/1601 1:11:43 PM”>
<StackTrace>
<CodeAddress Address=”0xfffff80002c22003″ FullMethodName=”_NULL_IMPORT_DESCRIPTOR” ModuleName=”ntoskrnl”/>
<CodeAddress Address=”0xfffff80002bb5d3b” FullMethodName=”$$VProc_ImageExportDirectory” ModuleName=”ntoskrnl”/>
<CodeAddress Address=”0xfffff800028d4853″ FullMethodName=”KiDeliverApc” ModuleName=”ntoskrnl”/>
<CodeAddress Address=”0x77920e0a” FullMethodName=”ZwOpenKeyEx” ModuleName=”ntdll”/>
<CodeAddress Address=”0x7399d1f1″ ModuleName=”wow64″/>
<CodeAddress Address=”0x7398008f” ModuleName=”wow64″/>
<CodeAddress Address=”0x7397cf87″ ModuleName=”wow64″/>
<CodeAddress Address=”0x7390276d” ModuleName=”wow64cpu”/>
<CodeAddress Address=”0x7397d07e” ModuleName=”wow64″/>
<CodeAddress Address=”0x7397c549″ ModuleName=”wow64″/>
<CodeAddress Address=”0x779184c8″ FullMethodName=”LdrpInitializeProcess” ModuleName=”ntdll”/>
<CodeAddress Address=”0×77917623″ FullMethodName=” ?? ::FNODOBFM::`string&apos;” ModuleName=”ntdll”/>
<CodeAddress Address=”0x7790308e” FullMethodName=”LdrInitializeThunk” ModuleName=”ntdll”/>
<CodeAddress Address=”0x77acf9da” FullMethodName=”NtOpenKey” ModuleName=”ntdll”/>
<CodeAddress Address=”0x772f2ef3″ ModuleName=”KernelBase”/>
<CodeAddress Address=”0x772de216″ ModuleName=”KernelBase”/>
<CodeAddress Address=”0x772e3264″ ModuleName=”KernelBase”/>
<CodeAddress Address=”0x6be34d5f” ModuleName=”clr”/>
<CodeAddress Address=”0x6be34ba8″ ModuleName=”clr”/>
<CodeAddress Address=”0x697ba2f1″ FullMethodName=”System.Globalization.CultureData.InitCultureData()” ModuleName=”mscorlib”/>
<CodeAddress Address=”0x697ba2c1″ FullMethodName=”System.Globalization.CultureData.CreateCultureData(class System.String,bool)” ModuleName=”mscorlib”/>
<CodeAddress Address=”0x697b9faa” FullMethodName=”System.Globalization.CultureData.GetCultureData(class System.String,bool)” ModuleName=”mscorlib”/>
<CodeAddress Address=”0x697b9e0a” FullMethodName=”System.Globalization.CultureInfo..ctor(class System.String,bool)” ModuleName=”mscorlib”/>
<CodeAddress Address=”0x697ba15c” FullMethodName=”System.Globalization.CultureInfo.GetCultureByName(class System.String,bool)” ModuleName=”mscorlib”/>
<CodeAddress Address=”0x697ba106″ FullMethodName=”System.Globalization.CultureInfo.InitUserDefaultCulture()” ModuleName=”mscorlib”/>
<CodeAddress Address=”0x697b9db8″ FullMethodName=”System.Globalization.CultureInfo.Init()” ModuleName=”mscorlib”/>
<CodeAddress Address=”0x697b9d34″ FullMethodName=”System.Globalization.CultureInfo..cctor()” ModuleName=”mscorlib”/>
<CodeAddress Address=”0x6bd74a2a” ModuleName=”clr”/>
<CodeAddress Address=”0x6bd73153″ ModuleName=”clr”/>
<CodeAddress Address=”0x6bd731cc” ModuleName=”clr”/>
<CodeAddress Address=”0x6bd7323b” ModuleName=”clr”/>
<CodeAddress Address=”0x6bd73415″ ModuleName=”clr”/>
<CodeAddress Address=”0x6bd7355a” ModuleName=”clr”/>
<CodeAddress Address=”0x6bde4844″ ModuleName=”clr”/>
<CodeAddress Address=”0×122420″/>
<CodeAddress Address=”0×69755870″ FullMethodName=”System.Version.TryParseVersion(class System.String,value class VersionResult&amp;)” ModuleName=”mscorlib”/>
<CodeAddress Address=”0x697557cd” FullMethodName=”System.Version.Parse(class System.String)” ModuleName=”mscorlib”/>
<CodeAddress Address=”0×69755764″ FullMethodName=”System.Version..ctor(class System.String)” ModuleName=”mscorlib”/>
<CodeAddress Address=”0x1e00d9″ FullMethodName=”LINQPad.Loader.Main(class System.String[])” ModuleName=”LINQPad”/>
<CodeAddress Address=”0x6bd521db” ModuleName=”clr”/>
<CodeAddress Address=”0x6bd74a2a” ModuleName=”clr”/>
<CodeAddress Address=”0x6bd74bcc” ModuleName=”clr”/>
<CodeAddress Address=”0x6bd74c01″ ModuleName=”clr”/>
<CodeAddress Address=”0x6bd74c21″ ModuleName=”clr”/>
<CodeAddress Address=”0x6be3ce82″ ModuleName=”clr”/>
<CodeAddress Address=”0x6be3cf90″ ModuleName=”clr”/>
<CodeAddress Address=”0x6be3cda4″ ModuleName=”clr”/>
<CodeAddress Address=”0x6be3d199″ ModuleName=”clr”/>
<CodeAddress Address=”0x6be3d09a” ModuleName=”clr”/>
<CodeAddress Address=”0x6bebaf00″ ModuleName=”clr”/>
<CodeAddress Address=”0x726355ab” ModuleName=”mscoreei”/>
<CodeAddress Address=”0x75077f16″ ModuleName=”mscoree”/>
<CodeAddress Address=”0x75074de3″ ModuleName=”mscoree”/>
<CodeAddress Address=”0x77ae9d72″ FullMethodName=”__RtlUserThreadStart” ModuleName=”ntdll”/>
<CodeAddress Address=”0x77ae9d45″ FullMethodName=”_RtlUserThreadStart” ModuleName=”ntdll”/>
</StackTrace>
</Event>

Here I was  able to get the managed as well as native call-stacks for registry access by keyname.  And here is my list of posts on ETW

Get Managed and Native Call Stacks from Event Tracing for Windows (ETW)


Here is the TOC of series of posts on ETW

With the latest release of PerfMonitor from the CLR team now we can have Managed call stacks along with native code. How cool is that?  To get managed / native call stacks we need minimum of Vista, this is not available in XP.

Here is the sample source code I am going to be using to demonstrate the managed and the native call-stacks

using System;
using System.Linq;
namespace ConsoleApplication9 {
class Program {
private static void Main() {
Console.Read();
AllocateLOH();
}
private static void AllocateLOH() {
for (int i = 0; i < 10; i++) {
var x = new object[85000];
x.Count();
}
}
}
}

This is a very simple app that allocates large chunks of memory, which will get allocated in the LOH and frequent allocations in LOH causes high CPU. To identify this we would have to attach it to windbg and put a break-point on “WKS::gc_heap::allocate_large_object” and here I have blogged about the same

But with ETW we will not need Windbg, the ETW can do stack walks for the function calls and this makes it really great. To enable this feature first thing is to download the source and compile the code from codeplex . FYI if you are using x64 th tool would not work. It would work on wow64 , so compile it for x86.

After which run the command


PerfMonitor crawlableCLRStacks

This will take a while to complete, it is essentially setting the ngened images to debug attributes so we can get better stack walks. The next command is to start etw trace


PerfMonitor.exe  /lineNumbers /stacks /keyAny:0x1 start

So we are essentially instructing the tool to  get GC Information (/keyAny:0×1) along with stackwalk . The tool provides help which really useful.

After which I start the application and let the complete and then I stop the trace.


Perfmonitor.exe stop

And then dump the trace to a xml file


perfmonitor.exe print

This will dump out a huge file but what we are interested in Large Object Allocations on GC call stacks


<Event MSec=  "6940.8972" PID="365044" PName="ConsoleApplication9" TID="364352" EventName="GCAllocationTick" AllocationAmount="0x00053040" AllocationKind="Large" ClrInstanceID="15">

<StackTrace>

<CodeAddress Address="0x66669909" FullMethodName="ETW::SamplingLog::SendStackTrace" ModuleName="clr"/>

<CodeAddress Address="0x66669a8d" FullMethodName="EtwCallout" ModuleName="clr"/>

<CodeAddress Address="0x6669d870" FullMethodName="CoMofTemplate_qqh" ModuleName="clr"/>

<CodeAddress Address="0x666d5d44" ModuleName="clr"/>

<CodeAddress Address="0x6653ff5b" FullMethodName="WKS::gc_heap::allocate_more_space" ModuleName="clr"/>

<CodeAddress Address="0x66475d43" FullMethodName="WKS::gc_heap::allocate_large_object" ModuleName="clr"/>

<CodeAddress Address="0x666d61d8" FullMethodName="WKS::GCHeap::Alloc" ModuleName="clr"/>

<CodeAddress Address="0x666a39a7" ModuleName="clr"/>

<CodeAddress Address="0x664ef3d8" FullMethodName="AllocateArrayEx" ModuleName="clr"/>

<CodeAddress Address="0x664ef464" ModuleName="clr"/>

<CodeAddress Address="0x1f00ee" FullMethodName="ConsoleApplication9.Program.AllocateLOH()"/>

<CodeAddress Address="0x1f008e"/>

<CodeAddress Address="0x663f213b" FullMethodName="CallDescrWorker" ModuleName="clr"/>

<CodeAddress Address="0x66412c2f" FullMethodName="CallDescrWorkerWithHandler" ModuleName="clr"/>

<CodeAddress Address="0x66412dcc" FullMethodName="MethodDesc::CallDescr" ModuleName="clr"/>

<CodeAddress Address="0x66412e01" FullMethodName="MethodDesc::CallTargetWorker" ModuleName="clr"/>

<CodeAddress Address="0x66412e21" FullMethodName="MethodDescCallSite::Call_RetArgSlot" ModuleName="clr"/>

<CodeAddress Address="0x664c8872" FullMethodName="ClassLoader::RunMain" ModuleName="clr"/>

<CodeAddress Address="0x664c8980" FullMethodName="Assembly::ExecuteMainMethod" ModuleName="clr"/>

<CodeAddress Address="0x664c8794" FullMethodName="SystemDomain::ExecuteMainMethod" ModuleName="clr"/>

<CodeAddress Address="0x664ed6f5" FullMethodName="ExecuteEXE" ModuleName="clr"/>

<CodeAddress Address="0x664ed5f7" FullMethodName="_CorExeMainInternal" ModuleName="clr"/>

<CodeAddress Address="0x664e6db8" FullMethodName="_CorExeMain" ModuleName="clr"/>

<CodeAddress Address="0x704561f0" ModuleName="mscoreei"/>

<CodeAddress Address="0x706f7f16" FullMethodName="ShellShim__CorExeMain" ModuleName="mscoree"/>

<CodeAddress Address="0x706f4de3" FullMethodName="_CorExeMain_Exported" ModuleName="mscoree"/>

<CodeAddress Address="0x77bc9d72" FullMethodName="__RtlUserThreadStart" ModuleName="ntdll"/>

<CodeAddress Address="0x77bc9d45" FullMethodName="_RtlUserThreadStart" ModuleName="ntdll"/>

</StackTrace>

</Event

Here is a simple linq statement to extract the above information

 void Main()
 {
 var events = from e in XDocument.Load(@"c:\temp\test.xml").Descendants("Events").Descendants("Event").Take(5)
 where e.Attribute("EventName").Value == "GCAllocationTick" && e.Attribute("AllocationKind").Value == "Large"
 select new {Amount = Dec(e.Attribute("AllocationAmount").Value) ,
 Stack = e.Descendants("StackTrace").Descendants().Attributes("FullMethodName")};

var f = from e in events
 let stack = e.Stack.Select (s => s.Value )
 select new {AllocatedAmount = e.Amount,callstack = stack};
 }
 public static  int Dec(string hex)
 {
 var x1 = hex.Remove(0,3);
 return int.Parse(x1,System.Globalization.NumberStyles.AllowHexSpecifier);
 }
 

And here is the outcome from the linq code

IEnumerable<> (2 items)

AllocatedAmount callstack
340032
IEnumerable<String> (23 items)
ETW::SamplingLog::SendStackTrace
EtwCallout
CoMofTemplate_qqh
WKS::gc_heap::allocate_more_space
WKS::gc_heap::allocate_large_object
WKS::GCHeap::Alloc
AllocateArrayEx
ConsoleApplication9.Program.AllocateLOH()
CallDescrWorker
CallDescrWorkerWithHandler
MethodDesc::CallDescr
MethodDesc::CallTargetWorker
MethodDescCallSite::Call_RetArgSlot
ClassLoader::RunMain
Assembly::ExecuteMainMethod
SystemDomain::ExecuteMainMethod
ExecuteEXE
_CorExeMainInternal
_CorExeMain
ShellShim__CorExeMain
_CorExeMain_Exported
__RtlUserThreadStart
_RtlUserThreadStart
340032
IEnumerable<String> (23 items)
ETW::SamplingLog::SendStackTrace
EtwCallout
CoMofTemplate_qqh
WKS::gc_heap::allocate_more_space
WKS::gc_heap::allocate_large_object
WKS::GCHeap::Alloc
AllocateArrayEx
ConsoleApplication9.Program.AllocateLOH()
CallDescrWorker
CallDescrWorkerWithHandler
MethodDesc::CallDescr
MethodDesc::CallTargetWorker
MethodDescCallSite::Call_RetArgSlot
ClassLoader::RunMain
Assembly::ExecuteMainMethod
SystemDomain::ExecuteMainMethod
ExecuteEXE
_CorExeMainInternal
_CorExeMain
ShellShim__CorExeMain
_CorExeMain_Exported
__RtlUserThreadStart
_RtlUserThreadStart
680064

Wow now we can see every callstack from ntdll to CLR along with our code in callstack. How cool it is to see managed callstack and native in one single block. This wasn’t possible until now. Thanks to the CLR team. Just like I mentioned in my previous blog post we wouldn’t have to use Windbg to get this information. This is only tip of the iceberg , we can do lot of cool things with this.  I am sure Microsoft is working hard to integrate this within XPERF , but I am going to take a stab and see how I can display this data in a much more graphical way in my future posts. So next time someone complains of Performance issue in managed code you can probably ask them to just get on trace file , which will include everything to figure compared to getting  few memory dumps , perfmon counters , webserver log .

Exploring Event Tracing for Windows (ETW) within Task Parallel Library (TPL) using PerfMonitor


Like I mentioned in my previous post , Today I am going to exploring TPL ETW traces. Yes, I know we could use the built in VS.NET profiler, but in production environment we don’t have VS.NET and guess what even VS.NET parallel profiler uses only ETW to get those good looking graphs.

With the today’s release of source code from BCL, I am going to be using PerfMonitor to parse these traces and dump them out in xml file. TPL uses ‘self-manifests’ (The manifest is dumped as event data) we don’t need a man file to interpret it (since it is in the stream).   The TraceEvent code has the ability to decode these events using the manifest in the data stream.

The sample I am going to be using for tracing is MandelbrotFractals_CSharp which is from the PFX Team

I am starting the trace using xperf , on the TPL ETW provider


xperf -start clr -on 2e5dba47-a3d2-4d16-8ee0-6671ffdcd7b5 -f clrevents.etl

Started the application and then stopped the trace using


xperf -stop clr

I had to make few changes to the TraceEvent Code, these are minor . Especially the code didn’t handle Boolean and it was throwing an exception , anybody can make this simple fix.

To get the trace from the ETL to xml here is the command


PerfMonitor.exe /keepETL print clrevents.etl

In the above command we are instructing the exe to convert the etl to xml and not to delete the ETL file after doing it.

The above command output’s an xml file. Here is a sample output from the actual xml file.


<Event MSec= "24181.0409" PID="1635144" PName="(1635144)" TID="1634556" EventName="TaskStarted" ProviderName="TplEtwProvider" OriginatingTaskSchedulerID="1" OriginatingTaskID="0" TaskID="5"/>

<Event MSec= "24181.0734" PID="1635144" PName="(1635144)" TID="1634556" EventName="TaskScheduled" ProviderName="TplEtwProvider" OriginatingTaskSchedulerID="1" OriginatingTaskID="5" TaskID="6" CreatingTaskID="4" TaskCreationOptions="10500"/>

<Event MSec= "24181.0772" PID="1635144" PName="(1635144)" TID="1634556" EventName="ParallelFork" ProviderName="TplEtwProvider" OriginatingTaskManager="1" OriginatingTaskID="5" ForkJoinContextID="1"/>

<Event MSec= "24413.3543" PID="1635144" PName="(1635144)" TID="1634556" EventName="ParallelJoin" ProviderName="TplEtwProvider" OriginatingTaskSchedulerID="1" OriginatingTaskID="5" ForkJoinContextID="1"/>

<Event MSec= "24413.3624" PID="1635144" PName="(1635144)" TID="1634556" EventName="TaskScheduled" ProviderName="TplEtwProvider" OriginatingTaskSchedulerID="1" OriginatingTaskID="5" TaskID="7" CreatingTaskID="4" TaskCreationOptions="10500"/>

<Event MSec= "24413.3791" PID="1635144" PName="(1635144)" TID="1634556" EventName="TaskCompleted" ProviderName="TplEtwProvider" OriginatingTaskSchedulerID="1" OriginatingTaskID="0" TaskID="5" IsExceptional="False"/>

From the above we can see when the task was scheduled, which is the originating task and when did it complete.

This wouldn’t have been possible without ETW, even with Windbg we cannot these information. This can help us understand what is happening with in TPL , which will enable us to design better.

FYI, Perfmonitor is just an implementation of the TraceEvent API which converts the ETW data to xml. It gives us possibilities to extend this.

In the coming posts I will be exploring how ETW can literally replace debugger in Production environment.   Don’t get me wrong, I am very BIG fan of Windbg and have been using it to understand the OS and the CLR.

Thanks to Vance Morrison for letting me know about TraceEvent.

Undocumented Event Tracing for Windows (ETW) providers in .NET 4.0 (BCL)


Continuing with my explorations in ETW I happened notice that there are few undocumented ETW providers within the .NET 4.0 BCL. These are in the mscorlib.dll and they are

  1. System.Collections.Concurrent.CDSCollectionETWBCLProvider : This is used by the ConcurrentBag , ConcurrentDictionary and the ConcurrentStack
  2. System.Diagnostics.Eventing.FrameworkEventSource : This is used by System.Resources.ResourceManager
  3. System.Threading.CdsSyncEtwBCLProvider : This is used by SpinWait, SpinLock and Barrier
  4. System.Threading.TplEtwProvider  & System.Linq.Parallel.PlinqEtwProvider : This is already documented here

To get list of ETW providers on an application ,get pid of the application and then issue this command


logman query providers -pid 1853452

The outcome of the above command is

For example .from above result ,the provider 2e5dba47-a3d2-4d16-8ee0-6671ffdcd7b5 belongs to TplEtwProvider , it is not a registered with a man file. These providers that i have mentioned above are specifically useful in diagnosing parallel applications. In the forth coming posts I will demonstrate how we can leverage ETW in  understanding parallel applications performance.

Track down DLL loading in .NET using Event Tracing for Windows (ETW)


Sometime ago I was doing some performance work for start-up of an .NET application ,and it so happened the CLR Perf team had blogged specifically about this. One of critical pieces to start-up speed is loading only necessary Dll’s. And loading these Dll’s  on demand reduces the memory consumption, reduces look-up ,which could potentially save few hundred  milliseconds based on different configuration. I am going to be using the same  example as the CLR perf team had used. Here is the sample code.


using System.IO;
using System.Xml.Linq;

internal class Program
{
 private static void ProcessArgs(string[] args)
 {
 if (args.Length == 2 && args[0] == "text")
 File.WriteAllText(args[1], "Here is the data I am writing");
 else if (args.Length == 2 && args[0] == "xml")
 {
 var myXml = new XElement("data", "Here is the data I am writing");
 myXml.Save(args[1]);
 }
 }
 private static int Main(string[] args)
 {
 ProcessArgs(args);

 return 0;
 }
}

In this sample code we wouldn’t want to load System.Xml.Linq.dll to be loaded if the command line option was set to “text”. In the past to diagnose the load of System.Xml.Linq.dll I would have to attached the windbg and set a break-point on load of System.Xml.Linq using sxe:ld and then get the call-stack using !clrstack. With the ETW it is as simple starting and stopping a trace.


xperf -start clr -on e13c0d23-ccbc-4e12-931b-d9cc2eee27e4:0x400010dc:0xffffffff -f clrevents.etl

This is the clr guid for etw “e13c0d23-ccbc-4e12-931b-d9cc2eee27e4:0x400010dc” and the keyword is 0x400010dc, which is for Fusion,Loader,Jit and Stackwalk combined together. So we are essentially asking clr to log events for Jit,Loader, Fusion and as well as stack-walk information.

After issuing the command start the console app

EtwProvider.exe text text

Stop the trace

xperf -stop clr

And here is the sample output from the etl file

Opcode name MethodNameSpace MethodName MethodSignature
JittingStarted “Program” “ProcessArgs” “void (class System.String[])”
Walk [0x719c9909 : 0x719c9a8d]
AssemblyLoad “System.Xml.Linq; Version=4.0.0.0; Culture=neutral; PublicKeyToken=b77a5c561934e089″ 15
Walk [0x719c9909 : 0x719c9a8d]

The above table represents the ordered trace where the Jit compiler is trying to compile ProcessArgs method ,which is in-turn causing the System.Xml.Linq.dll to be loaded. The reason for doing this is because the Jit compiler is not aware of which code path would be taken and that’s the reason for loading the dll even though command line was text and not xml.

The idea behind this post is not show how the dll loading can be avoided, this has been already demonstrated in the CLR blog. I am trying to demonstrate how we can leverage ETW tracing , which is non-invasive and we don’t have to jump though hoops to get the information.

Case Study: Tracking .NET Exceptions with Event Tracing for Windows (ETW)


In the past I have debugged customers code ,where the code throws tons of exceptions. This is a huge performance problem. Tess has amazing post on why throwing ton of exceptions are bad. To debug this I had to resort to using Windbg and  getting call stacks of exceptions. The biggest issue was ,there were thousands of exceptions thrown from different points which made debugging extremely hard. I couldn’t just break-point based on exception type because the same type of exception were thrown from n different points. Windbg would  peg cpu for getting call stacks for each of these exceptions. I so wish I had ETW when I had to figure out those exceptions. FYI all these exception were handled and that made even harder.

Now with CLR 4.0 having ETW, this is so much easy to diagnose the same problem. Here is the sample code that I am going to use.

using System;
using System.Threading;
class Program {
 private void ProcessArgs() {
 for (int i = 0; i < 20; i++) {
 try {
 throw new ArgumentNullException(i.ToString());
 }
 catch (Exception e) {
 Console.WriteLine(e);
 }
 }
 }
 private static void Main(string[] args) {
 var p = new Program();
 ThreadPool.QueueUserWorkItem((x) => p.ProcessArgs());
 ThreadPool.QueueUserWorkItem((x) => p.ThrowNullReference());
 Console.Read();
 }
 private void ThrowNullReference() {
 for (int i = 0; i < 20; i++) {
 try {
 throw new NullReferenceException(i.ToString());
 }
 catch (Exception ex) {
 Console.WriteLine(ex);
 }
 }
 }
}

Here is the code to trace CLR exceptions

xperf -start clr -on e13c0d23-ccbc-4e12-931b-d9cc2eee27e4:0x00008000:5 -f clrevents.etl

After which I started the console application. Then stopped the etw and then dumped the contents to a csv file.

xperf -stop clr
xperf -i clrevents.etl -o clrexceptions.csv

Here is a sample exception trace from ETW

Microsoft-Windows-DotNETRuntime/Exception /Start ,    3856446,        "Unknown" (27776),      30268,   0, , , , , "System.ArgumentNullException", "Value cannot be null.", 0x00480270, 0x80004003, 16, 15

Attached the application to Windbg. With in the etw trace is the Instruction Pointer where the exception was raised . And for above example it was raised at 0×00480270.

Issued a command to disassemble the sourcecode at the specific instruction pointer within Windbg

!u  0x00480270 

And here is the output from the above command

0:007> !u  0×00480270
Normal JIT generated code
Program.ProcessArgs()
Begin 00480200, size ad

C:\Users\naveen\Documents\Visual Studio 2010\Projects\ConsoleApplication1\Program.cs @ 4:
00480200 55              push    ebp
00480201 8bec            mov     ebp,esp
00480203 57              push    edi
00480204 56              push    esi
00480205 53              push    ebx
00480206 83ec30          sub     esp,30h
00480209 8bf1            mov     esi,ecx
0048020b 8d7dd8          lea     edi,[ebp-28h]
0048020e b906000000      mov     ecx,6
00480213 33c0            xor     eax,eax
00480215 f3ab            rep stos dword ptr es:[edi]
00480217 8bce            mov     ecx,esi
00480219 33c0            xor     eax,eax
0048021b 8945e8          mov     dword ptr [ebp-18h],eax
0048021e 894ddc          mov     dword ptr [ebp-24h],ecx
00480221 833d3c31310000  cmp     dword ptr ds:[31313Ch],0
00480228 7405            je      0048022f
0048022a e8464be768      call    clr!JIT_DbgIsJustMyCode (692f4d75)
0048022f 33d2            xor     edx,edx
00480231 8955d0          mov     dword ptr [ebp-30h],edx
00480234 c745d400000000  mov     dword ptr [ebp-2Ch],0
0048023b 90              nop

C:\Users\naveen\Documents\Visual Studio 2010\Projects\ConsoleApplication1\Program.cs @ 5:
0048023c 33d2            xor     edx,edx
0048023e 8955d8          mov     dword ptr [ebp-28h],edx
00480241 90              nop
00480242 eb4d            jmp     00480291
00480244 90              nop

C:\Users\naveen\Documents\Visual Studio 2010\Projects\ConsoleApplication1\Program.cs @ 6:
00480245 90              nop

C:\Users\naveen\Documents\Visual Studio 2010\Projects\ConsoleApplication1\Program.cs @ 7:
00480246 8d4dd8          lea     ecx,[ebp-28h]
00480249 e882330568      call    mscorlib_ni+0x2635d0 (684d35d0) (System.Int32.ToString(), mdToken: 06000cd4)
0048024e 8945cc          mov     dword ptr [ebp-34h],eax
00480251 b98c475968      mov     ecx,offset mscorlib_ni+0x32478c (6859478c) (MT: System.ArgumentNullException)
00480256 e8c51de8ff      call    00302020 (JitHelp: CORINFO_HELP_NEWSFAST)
0048025b 8945c8          mov     dword ptr [ebp-38h],eax
0048025e 8b55cc          mov     edx,dword ptr [ebp-34h]
00480261 8b4dc8          mov     ecx,dword ptr [ebp-38h]
00480264 e8b7bbfc67      call    mscorlib_ni+0x1dbe20 (6844be20) (System.ArgumentNullException..ctor(System.String), mdToken: 06000795)
00480269 8b4dc8          mov     ecx,dword ptr [ebp-38h]
0048026c e835ffd668      call    clr!IL_Throw (691f01a6)

C:\Users\naveen\Documents\Visual Studio 2010\Projects\ConsoleApplication1\Program.cs @ 9:
00480271 8945c4          mov     dword ptr [ebp-3Ch],eax
00480274 8b45c4          mov     eax,dword ptr [ebp-3Ch]
00480277 8945d0          mov     dword ptr [ebp-30h],eax
0048027a 90              nop

C:\Users\naveen\Documents\Visual Studio 2010\Projects\ConsoleApplication1\Program.cs @ 10:
0048027b 8b4dd0          mov     ecx,dword ptr [ebp-30h]
0048027e e841416268      call    mscorlib_ni+0x8343c4 (68aa43c4) (System.Console.WriteLine(System.Object), mdToken: 06000918)
00480283 90              nop

C:\Users\naveen\Documents\Visual Studio 2010\Projects\ConsoleApplication1\Program.cs @ 11:
00480284 90              nop
00480285 e8ae1fbc68      call    clr!JIT_EndCatch (69042238)
0048028a eb00            jmp     0048028c
0048028c 90              nop

C:\Users\naveen\Documents\Visual Studio 2010\Projects\ConsoleApplication1\Program.cs @ 12:
0048028d 90              nop

C:\Users\naveen\Documents\Visual Studio 2010\Projects\ConsoleApplication1\Program.cs @ 5:
0048028e ff45d8          inc     dword ptr [ebp-28h]
00480291 837dd814        cmp     dword ptr [ebp-28h],14h
00480295 0f9cc0          setl    al
00480298 0fb6c0          movzx   eax,al
0048029b 8945d4          mov     dword ptr [ebp-2Ch],eax
0048029e 837dd400        cmp     dword ptr [ebp-2Ch],0
004802a2 75a0            jne     00480244

C:\Users\naveen\Documents\Visual Studio 2010\Projects\ConsoleApplication1\Program.cs @ 13:
004802a4 90              nop
004802a5 8d65f4          lea     esp,[ebp-0Ch]
004802a8 5b              pop     ebx
004802a9 5e              pop     esi
004802aa 5f              pop     edi
004802ab 5d              pop     ebp
004802ac c3              ret

Now we see the call stacks of where the exception was raised, even without hooking for exceptions within the debugger.  The key reason for doing this is, for this specific case study I could have got all the unique Instruction pointers from the trace and just take one memory dump.With this I could have managed to get the call stacks all the exceptions. This would be non-invasive, which would save us lot of time and effort.

With ETW we can get all the exception even the ones that were handled, along with Instruction Pointer which helps us trace the root cause of the issue.

With this in hand ,we could easily write a small automation tool to extract the Instruction Pointer from the trace and then disassemble the source code and get the call stack.

All of this can be done on .NET 4.0 (clr.dll) and Silverlight (coreclr.dll).

In my forth coming posts ,I will share few more cool things that can be done with ETW.

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: