<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>HTTP on Welcome to Christophe Nasarre's Blog</title><link>https://chrisnas.github.io/tags/http/</link><description>Recent content in HTTP on Welcome to Christophe Nasarre's Blog</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Thu, 13 Mar 2025 10:50:55 +0000</lastBuildDate><atom:link href="https://chrisnas.github.io/tags/http/index.xml" rel="self" type="application/rss+xml"/><item><title>How to monitor .NET applications startup</title><link>https://chrisnas.github.io/posts/2025-03-13_how-to-monitor-net/</link><pubDate>Thu, 13 Mar 2025 10:50:55 +0000</pubDate><guid>https://chrisnas.github.io/posts/2025-03-13_how-to-monitor-net/</guid><description>This episode explains how to monitor the startup of a .NET application and get insights about its lock and wait contentions duration</description><content:encoded><![CDATA[<hr>
<p>In <a href="/posts/2025-01-13_measuring-the-impact-of/">the previous article</a>, I presented what is needed (i.e. listen to <strong>WaitHandleWait</strong> events) to compute lock/wait durations and call stacks for <strong>Mutex</strong>, <strong>Semaphore</strong>, <strong>SemaphoreSlim</strong>, <strong>Manual</strong>/<strong>AutoResetEvent</strong>, <strong>ManualResetEventSlim</strong>, <strong>ReaderWriterLockSlim</strong> .NET synchronization constructs for a running process.</p>
<p>However, since the application is already running, some JIT-related events are missing, and some frames of the call stacks cannot be symbolized. Also, it would be great to monitor an application’s startup to see if it could be faster.</p>
<p>This post will detail how to monitor a .NET application since the very beginning of its life and the issues you might face.</p>
<h2 id="preparing-a-newnet-process-to-be-monitored">Preparing a new .NET process to be monitored</h2>
<p>From .NET 5, the <strong>dotnet-trace</strong> CLI tool allows you to <a href="https://github.com/dotnet/diagnostics/blob/main/documentation/dotnet-trace-instructions.md#using-dotnet-trace-to-launch-a-child-process-and-trace-it-from-startup">pass a command line to execute and trace it from startup</a>. In a <a href="https://medium.com/@ocoanet/tracing-allocations-with-eventpipe-part-3-tracing-without-dotnet-trace-7244bdb86e03">very interesting article</a>, Olivier Coanet presented the gory details about how to tell the .NET runtime to start an application in a pseudo-suspended mode as shown in the following diagram:</p>
<p><img loading="lazy" src="/posts/2025-03-13_how-to-monitor-net/1_9J_2x2R0n0nkuRTl6MDzZw.png"></p>
<p>The first step is to create a <strong>ReverseDiagnosticsServer</strong> instance with a specific port (i.e. <strong>dotnet-wait_1234</strong> in the diagram). Next, the process to monitor is spawned with the <strong>DOTNET_DiagnosticPorts</strong> environment variable set to the same port (i.e. <strong>dotnet-wait_1234</strong>). Look at the <a href="https://github.com/dotnet/diagnostics/blob/main/documentation/design-docs/ipc-protocol.md#diagnostic-ports">Diagnostics documentation</a> of the Diagnostic Ports with <strong>DOTNET_DiagnosticPorts</strong> environment variable for more details. The .NET runtime is the new process will listen to this port and… wait.</p>
<p>When the tool is ready, it sends a resume command via a <strong>DiagnosticsClient</strong>: from that point in time, the CLR executes the normal flow of actions to run the application and… you will receive all events without missing one!</p>
<h2 id="get-my-command-lineplease">Get my command line please</h2>
<p>Following the <a href="https://github.com/dotnet/diagnostics/blob/main/documentation/dotnet-trace-instructions.md#using-dotnet-trace-to-launch-a-child-process-and-trace-it-from-startup"><strong>dotnet-trace</strong> example</a>, my <a href="https://www.nuget.org/packages/dotnet-wait">dotnet-wait</a> tool accepts the command line of the child process in its final arguments that follow the** — **trigger. For example, dotnet-wait — dotnet foo.dll will start the program in foo.dll by using dotnet.exe. I’m reusing <a href="https://github.com/dotnet/diagnostics/blob/main/src/Tools/Common/ReversedServerHelpers/ReversedServerHelpers.cs#L37">the code in ReversedServerHelper.cs</a> to deal with arguments containing spaces:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">current</span> <span class="p">==</span> <span class="s">&#34;--&#34;</span><span class="p">)</span>  <span class="c1">// this is supposed to be the last one</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">i</span><span class="p">++;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">i</span> <span class="p">&lt;</span> <span class="n">args</span><span class="p">.</span><span class="n">Length</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">parameters</span><span class="p">.</span><span class="n">pathName</span> <span class="p">=</span> <span class="n">args</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// use the remaining arguments as the arguments for the child app to spawn</span>
</span></span><span class="line"><span class="cl">        <span class="n">i</span><span class="p">++;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">i</span> <span class="p">&lt;</span> <span class="n">args</span><span class="p">.</span><span class="n">Length</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">parameters</span><span class="p">.</span><span class="n">arguments</span> <span class="p">=</span> <span class="s">&#34;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">j</span> <span class="p">=</span> <span class="n">i</span><span class="p">;</span> <span class="n">j</span> <span class="p">&lt;</span> <span class="n">args</span><span class="p">.</span><span class="n">Length</span><span class="p">;</span> <span class="n">j</span><span class="p">++)</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="n">args</span><span class="p">[</span><span class="n">j</span><span class="p">].</span><span class="n">Contains</span><span class="p">(</span><span class="sc">&#39; &#39;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="n">parameters</span><span class="p">.</span><span class="n">arguments</span> <span class="p">+=</span> <span class="s">$&#34;\&#34;</span><span class="p">{</span><span class="n">args</span><span class="p">[</span><span class="n">j</span><span class="p">].</span><span class="n">Replace</span><span class="p">(</span><span class="s">&#34;\&#34;&#34;</span><span class="p">,</span> <span class="s">&#34;\\\&#34;&#34;</span><span class="p">)}</span><span class="err">\</span><span class="s">&#34;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">                <span class="k">else</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="n">parameters</span><span class="p">.</span><span class="n">arguments</span> <span class="p">+=</span> <span class="n">args</span><span class="p">[</span><span class="n">j</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="n">j</span> <span class="p">!=</span> <span class="n">args</span><span class="p">.</span><span class="n">Length</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="n">parameters</span><span class="p">.</span><span class="n">arguments</span> <span class="p">+=</span> <span class="s">&#34; &#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// no need to look for more arguments</span>
</span></span><span class="line"><span class="cl">        <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">throw</span> <span class="k">new</span> <span class="n">InvalidOperationException</span><span class="p">(</span><span class="s">$&#34;Missing path name value...&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The code to spawn the child process is simple:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="c1">// start the monitored app</span>
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">psi</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ProcessStartInfo</span><span class="p">(</span><span class="n">pathName</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(!</span><span class="kt">string</span><span class="p">.</span><span class="n">IsNullOrEmpty</span><span class="p">(</span><span class="n">arguments</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">psi</span><span class="p">.</span><span class="n">Arguments</span> <span class="p">=</span> <span class="n">arguments</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="n">psi</span><span class="p">.</span><span class="n">EnvironmentVariables</span><span class="p">[</span><span class="s">&#34;DOTNET_DiagnosticPorts&#34;</span><span class="p">]</span> <span class="p">=</span> <span class="n">port</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">psi</span><span class="p">.</span><span class="n">UseShellExecute</span> <span class="p">=</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">process</span> <span class="p">=</span> <span class="n">System</span><span class="p">.</span><span class="n">Diagnostics</span><span class="p">.</span><span class="n">Process</span><span class="p">.</span><span class="n">Start</span><span class="p">(</span><span class="n">psi</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Here is an example with the following prompt:</p>
<pre tabindex="0"><code>-- dotnet &#34;C:\CommandLineTest.dll&#34; one two &#34;t h r e e&#34; four &#39;five six&#39;
</code></pre><p>that generates the output (the test application is just listing its arguments):</p>
<pre tabindex="0"><code>dotnet-wait v1.0.0.0 - List wait duration
by Christophe Nasarre

Press ENTER to exit...
6 arguments
   1 | one
   2 | two
   3 | t h r e e
   4 | four
   5 | &#39;five
   6 | six&#39;
</code></pre><p>This test reminded me to never use simple quotes in prompts :^)</p>
<h2 id="its-myconsole">It’s my console!</h2>
<p>Once I implemented these steps, I immediately faced a very simple problem: my <strong>dotnet-wait</strong> tool and the test application are console applications. It means that they will share the same console for both input and output. For example, both are waiting for the RETURN key to (1) stop for the tool and (2) start for the test application: too bad for me because the tool will stop as soon the application starts…</p>
<p>Going back in time in my Windows memories, I remembered that the Win32 <a href="https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw?WT.mc_id=DT-MVP-5003325">CreateProcess</a> API accepts <a href="https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags?WT.mc_id=DT-MVP-5003325"><strong>CREATE_NEW_CONSOLE</strong> as creation flag</a> to automagically start the child process into its own new console. Unfortunately, it is not possible to pass this flag in .NET; maybe a limitation due to Linux support.</p>
<p>One simple solution could be to redirect the output of the tool or the application to a file: that would avoid mixing them in the console. Note that, by default, <strong>dotnet-trace</strong> discards output from the child process (by setting <strong>RedirectStandardOutput</strong>, <strong>RedirectStandardError</strong> and <strong>RedirectStandardInput</strong> <a href="https://github.com/dotnet/diagnostics/blob/main/src/Tools/Common/ReversedServerHelpers/ReversedServerHelpers.cs#L95">to false</a> and by <a href="https://github.com/dotnet/diagnostics/blob/main/src/Tools/Common/ReversedServerHelpers/ReversedServerHelpers.cs#L69">ignoring the error and output streams</a>) except if you pass <strong>— show-child-io</strong> on the command line. In this case, no output for <strong>dotnet-trace</strong>.</p>
<p>I decided to do the opposite for <strong>dotnet-wait</strong>: by default, you also get the child output but you can redirect the output of the tool to a file with <strong>-o <output path name></strong>. Still, this does not solve the input problem in case of common expected keys.</p>
<p>If you remember the interactions between the tool and the monitored application, the latter is suspended until <strong>DiagnosticsClient::ResumeRuntime</strong> is called. So, why not starting the tool that spawns the application in one console and another instance of the tool in a new console that will resume the application? This is exactly what my friend <a href="https://x.com/KooKiz">Kevin Gosse</a> imagined and how <strong>dotnet-wait</strong> works.</p>
<p><img loading="lazy" src="/posts/2025-03-13_how-to-monitor-net/1_u9iVEg9L5Z_DVpn6jtcM2w.png"></p>
<p>After the timeout that you give to <strong>diagnosticsServer.AcceptAsync(cancellation.Token)</strong> has elapsed, the runtime in the child process will display the following message:</p>
<pre tabindex="0"><code>The runtime has been configured to pause during startup and is awaiting a Diagnostics IPC ResumeStartup command from a Diagnostic Port.
DOTNET_DiagnosticPorts=&#34;dotnet-wait_34296&#34;
DOTNET_DefaultDiagnosticPortSuspend=0
</code></pre><p>And this is exactly what the <strong>-r 34296</strong> parameter will do!</p>
<p>You can now install <strong>dotnet wait</strong> and monitor the lock and wait contentions of your .NET9+ applications.</p>
]]></content:encoded></item><item><title>Measuring the impact of locks and waits on latency in your .NET apps</title><link>https://chrisnas.github.io/posts/2025-01-13_measuring-the-impact-of/</link><pubDate>Mon, 13 Jan 2025 15:31:03 +0000</pubDate><guid>https://chrisnas.github.io/posts/2025-01-13_measuring-the-impact-of/</guid><description>Monitor mutex, semaphore and event wait duration</description><content:encoded><![CDATA[<hr>
<h2 id="introduction">Introduction</h2>
<p>In an <a href="/posts/2018-09-28_monitor-finalizers-contention-threads/">old post</a>, I detailed how to use <strong>ContentionStart</strong> and <strong>ContentionStop</strong> events to measure the lock contentions duration for a .NET application. In a <a href="https://github.com/DataDog/dd-trace-dotnet/issues/5814">.NET 9 pull request</a>, a former Criteo’s colleague <a href="https://www.linkedin.com/in/gregoire-verdier">Grégoire Verdier</a> has added new events to be notified when wait time similar to lock contention is happening for Mutex, Semaphore, Manual/AutoResetEvent. Read <a href="https://techblog.criteo.com/a-perfview-alternative-in-webassembly-f6833820b699">his post</a> for more details about what he was trying to investigate.</p>
<p>With asynchronous and multi-threaded algorithms, it is essential to detect unexpected wait/locks in our applications. This post shows you how to leverage these events to measure the duration of these waits and get the call stack when the wait started:</p>
<p><img loading="lazy" src="/posts/2025-01-13_measuring-the-impact-of/1_OTO7qWO5aYvNXprhaPvRoA.png"></p>
<h2 id="new-waithandlewait-events">New WaitHandleWait events</h2>
<p>These new events are emitted by the <strong>Microsoft-Windows-DotNETRuntime</strong> CLR provider when you enable the <strong>WaitHandle</strong> (= 0x40000000000) keyword with <strong>Verbose</strong> verbosity. Each time <strong>WaitOne</strong> is called on a waitable object and this object is already owned, a <strong>WaitHandleWaitStart</strong> event is emitted. When the object is released, a <strong>WaitHandleWaitStop</strong> event is emitted.</p>
<p>For example, the following code:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">static</span> <span class="n">Mutex</span> <span class="n">mutex</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Mutex</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">static</span> <span class="k">void</span> <span class="n">Main</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">owningThread</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Thread</span><span class="p">(</span><span class="n">OwningThread</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">owningThread</span><span class="p">.</span><span class="n">Start</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">mutexThread</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Thread</span><span class="p">(</span><span class="n">MutexThread</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">mutexThread</span><span class="p">.</span><span class="n">Start</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">owningThread</span><span class="p">.</span><span class="n">Join</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">mutexThread</span><span class="p">.</span><span class="n">Join</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">static</span> <span class="k">void</span> <span class="n">OwningThread</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;    [{GetCurrentThreadId(), 8}] Start to hold resources&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&#34;___________________________________________&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">mutex</span><span class="p">.</span><span class="n">WaitOne</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">Thread</span><span class="p">.</span><span class="n">Sleep</span><span class="p">(</span><span class="m">3000</span><span class="p">);</span>  <span class="c1">// the wait should last ~3 seconds</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&#34;    Release resources&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">mutex</span><span class="p">.</span><span class="n">ReleaseMutex</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">static</span> <span class="k">void</span> <span class="n">MutexThread</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;    [{GetCurrentThreadId(), 8}] waiting for Mutex...&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">mutex</span><span class="p">.</span><span class="n">WaitOne</span><span class="p">();</span>  <span class="c1">// events are emitted in the implementation when a contention happens</span>
</span></span><span class="line"><span class="cl">    <span class="n">mutex</span><span class="p">.</span><span class="n">ReleaseMutex</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&#34;    &lt;-- Mutex&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>generates a Start and Stop events pair:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="m">125980</span> <span class="p">|</span> <span class="m">00000000</span><span class="p">-</span><span class="m">0000</span><span class="p">-</span><span class="m">0000</span><span class="p">-</span><span class="m">0000</span><span class="p">-</span><span class="m">000000000000</span> <span class="p">&gt;</span> <span class="k">event</span> <span class="m">301</span> <span class="n">__</span> <span class="p">[</span> <span class="m">1</span><span class="p">|</span> <span class="n">Start</span><span class="p">]</span> <span class="n">WaitHandleWait</span><span class="p">/</span><span class="n">Start</span>
</span></span><span class="line"><span class="cl"><span class="m">125980</span> <span class="p">|</span> <span class="m">00000000</span><span class="p">-</span><span class="m">0000</span><span class="p">-</span><span class="m">0000</span><span class="p">-</span><span class="m">0000</span><span class="p">-</span><span class="m">000000000000</span> <span class="p">&gt;</span> <span class="k">event</span> <span class="m">302</span> <span class="n">__</span> <span class="p">[</span> <span class="m">2</span><span class="p">|</span>  <span class="n">Stop</span><span class="p">]</span> <span class="n">WaitHandleWait</span><span class="p">/</span><span class="n">Stop</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>There is no associated activity ID so you rely on the fact that the same waiter thread (125980 in the previous example) is emitting for both events.</p>
<h2 id="listening-to-the-new-waitevents">Listening to the new Wait events</h2>
<p><a href="/posts/2024-11-13_implementing-dotnet-http-to/">As usual</a>, you should rely on the <a href="https://www.nuget.org/packages/Microsoft.Diagnostics.Tracing.TraceEvent/">TraceEvent nuget</a> to start an EventPipe session with an already running .NET application. The last version already contains the definition of the keyword:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="n">keywords</span> <span class="p">|=</span> <span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">WaitHandle</span><span class="p">;</span> <span class="c1">// .NET 9 WaitHandle kind of contention</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>and the C# events for Start and Stop:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="n">source</span><span class="p">.</span><span class="n">Clr</span><span class="p">.</span><span class="n">WaitHandleWaitStart</span> <span class="p">+=</span> <span class="n">OnWaitHandleWaitStart</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">source</span><span class="p">.</span><span class="n">Clr</span><span class="p">.</span><span class="n">WaitHandleWaitStop</span> <span class="p">+=</span> <span class="n">OnWaitHandleWaitStop</span><span class="p">;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The handler’s implementation is straightforward. The start of the wait is recorded for the current thread:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">OnWaitHandleWaitStart</span><span class="p">(</span><span class="n">WaitHandleWaitStartTraceData</span> <span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// get the contention info for the current thread</span>
</span></span><span class="line"><span class="cl">    <span class="n">ContentionInfo</span> <span class="n">info</span> <span class="p">=</span> <span class="n">_contentionStore</span><span class="p">.</span><span class="n">GetContentionInfo</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">ProcessID</span><span class="p">,</span> <span class="n">data</span><span class="p">.</span><span class="n">ThreadID</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">info</span> <span class="p">==</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// keep track of the wait start</span>
</span></span><span class="line"><span class="cl">    <span class="n">info</span><span class="p">.</span><span class="n">ContentionStartRelativeMSec</span> <span class="p">=</span> <span class="n">data</span><span class="p">.</span><span class="n">TimeStampRelativeMSec</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>When the wait ends, the duration is computed based on the recorded wait start because it is not provided in the payload <a href="https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/ClrEtwAll.man#L1788">like for ContentionStop</a>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">OnWaitHandleWaitStop</span><span class="p">(</span><span class="n">WaitHandleWaitStopTraceData</span> <span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">ContentionInfo</span> <span class="n">info</span> <span class="p">=</span> <span class="n">_contentionStore</span><span class="p">.</span><span class="n">GetContentionInfo</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">ProcessID</span><span class="p">,</span> <span class="n">data</span><span class="p">.</span><span class="n">ThreadID</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">info</span> <span class="p">==</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// unlucky case when we start to listen just after the WaitHandleStart event</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">info</span><span class="p">.</span><span class="n">ContentionStartRelativeMSec</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Too bad the duration is not provided in the payload like in ContentionStop...</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">contentionDurationMSec</span> <span class="p">=</span> <span class="n">data</span><span class="p">.</span><span class="n">TimeStampRelativeMSec</span> <span class="p">-</span> <span class="n">info</span><span class="p">.</span><span class="n">ContentionStartRelativeMSec</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">info</span><span class="p">.</span><span class="n">ContentionStartRelativeMSec</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">duration</span> <span class="p">=</span> <span class="n">TimeSpan</span><span class="p">.</span><span class="n">FromMilliseconds</span><span class="p">(</span><span class="n">contentionDurationMSec</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;{e.ThreadId,7} | {e.Duration.TotalMilliseconds} ms&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>This is nice but it would be more useful if we could get the call stack of long waits.</p>
<h2 id="call-stacks-with-eventpipe">Call stacks with EventPipe</h2>
<p>In a <a href="https://techblog.criteo.com/build-your-own-net-memory-profiler-in-c-call-stacks-2-2-1-f67b440a8cc">previous post</a>, I explained that it is possible to get the call stack when an event is emitted thanks to the <a href="https://learn.microsoft.com/en-us/dotnet/framework/performance/stack-etw-event?WT.mc_id=DT-MVP-5003325"><strong>ClrStackWalk</strong> event</a> that follows the event you are interested in. Unfortunately, this is not more the case for .NET 5+ that is using EventPipe instead of ETW.</p>
<p>As <a href="https://x.com/ocoanet">Olivier Coanet</a> presents in his <a href="https://medium.com/@ocoanet/tracing-allocations-with-eventpipe-part-2-reading-call-stacks-without-tracelog-4b0bfe4592aa">post</a>, you can get the call stack as an array of addresses from the hidden event record that is mapped by the <strong>TraceEvent</strong> parameter passed to each event handlers. This <a href="https://learn.microsoft.com/en-us/windows/win32/api/evntcons/ns-evntcons-event_record?WT.mc_id=DT-MVP-5003325"><strong>EVENT_RECORD</strong></a> structure contains a <strong>ExtendedData</strong> field that is an array of <a href="https://learn.microsoft.com/en-us/windows/win32/api/evntcons/ns-evntcons-event_header_extended_data_item?WT.mc_id=DT-MVP-5003325"><strong>EVENT_HEADER_EXTENDED_DATA_ITEM</strong></a>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">struct</span> <span class="nc">EVENT_HEADER_EXTENDED_DATA_ITEM</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">ushort</span> <span class="n">Reserved1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">ushort</span> <span class="n">ExtType</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">ushort</span> <span class="n">Reserved2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">ushort</span> <span class="n">DataSize</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">ulong</span> <span class="n">DataPtr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>If the <strong>ExtType</strong> value is <strong>EVENT_HEADER_EXT_TYPE_STACK_TRACE64</strong> (=6) then <strong>DataPtr</strong> points to a <a href="https://learn.microsoft.com/en-us/windows/win32/api/evntcons/ns-evntcons-event_extended_item_stack_trace64?%3FWT.mc_id=DT-MVP-5003325"><strong>EVENT_EXTENDED_ITEM_STACK_TRACE64</strong></a> structure:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">struct</span> <span class="nc">EVENT_EXTENDED_ITEM_STACK_TRACE64</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">ulong</span> <span class="n">MatchId</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">unsafe</span> <span class="k">fixed</span> <span class="kt">ulong</span> <span class="n">Address</span><span class="p">[</span><span class="m">1</span><span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>that contains an array of 64-bit addresses. The size of this array is given by <strong>DataSize — sizeof(ulong)</strong>.</p>
<p>For 32-bit applications, you will get <strong>EVENT_HEADER_EXT_TYPE_STACK_TRACE32</strong> (=5) as <strong>ExtType</strong> value and DataPtr will point to <a href="https://learn.microsoft.com/en-us/windows/win32/api/evntcons/ns-evntcons-event_extended_item_stack_trace32?WT.mc_id=DT-MVP-5003325"><strong>EVENT_EXTENDED_ITEM_STACK_TRACE32</strong></a>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">struct</span> <span class="nc">EVENT_EXTENDED_ITEM_STACK_TRACE32</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">ulong</span> <span class="n">MatchId</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">unsafe</span> <span class="k">fixed</span> <span class="kt">uint</span> <span class="n">Address</span><span class="p">[</span><span class="m">1</span><span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>that stores an array of 32-bit addresses.</p>
<p>Knowing that makes writing the code to get the call stacks as an array of 64-bit addresses (same with 32-bit applications for simplicity sake) pretty straightforward:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">static</span> <span class="n">EventPipeUnresolvedStack</span> <span class="n">ReadStackUsingUnsafeAccessor</span><span class="p">(</span><span class="n">TraceEvent</span> <span class="n">traceEvent</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">GetFromEventRecord</span><span class="p">(</span><span class="n">traceEvent</span><span class="p">.</span><span class="n">eventRecord</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="n">EventPipeUnresolvedStack</span> <span class="n">GetFromEventRecord</span><span class="p">(</span><span class="n">TraceEventNativeMethods</span><span class="p">.</span><span class="n">EVENT_RECORD</span><span class="p">*</span> <span class="n">eventRecord</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">eventRecord</span> <span class="p">==</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">extendedDataCount</span> <span class="p">=</span> <span class="n">eventRecord</span><span class="p">-&gt;</span><span class="n">ExtendedDataCount</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="kt">var</span> <span class="n">dataIndex</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">dataIndex</span> <span class="p">&lt;</span> <span class="n">extendedDataCount</span><span class="p">;</span> <span class="n">dataIndex</span><span class="p">++)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">extendedData</span> <span class="p">=</span> <span class="n">eventRecord</span><span class="p">-&gt;</span><span class="n">ExtendedData</span><span class="p">[</span><span class="n">dataIndex</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">extendedData</span><span class="p">.</span><span class="n">ExtType</span> <span class="p">==</span> <span class="n">TraceEventNativeMethods</span><span class="p">.</span><span class="n">EVENT_HEADER_EXT_TYPE_STACK_TRACE64</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">stackRecord</span> <span class="p">=</span> <span class="p">(</span><span class="n">TraceEventNativeMethods</span><span class="p">.</span><span class="n">EVENT_EXTENDED_ITEM_STACK_TRACE64</span><span class="p">*)</span><span class="n">extendedData</span><span class="p">.</span><span class="n">DataPtr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">addresses</span> <span class="p">=</span> <span class="p">&amp;</span><span class="n">stackRecord</span><span class="p">-&gt;</span><span class="n">Address</span><span class="p">[</span><span class="m">0</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">addressCount</span> <span class="p">=</span> <span class="p">(</span><span class="n">extendedData</span><span class="p">.</span><span class="n">DataSize</span> <span class="p">-</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">UInt64</span><span class="p">))</span> <span class="p">/</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">UInt64</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">addressCount</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">callStackAddresses</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">ulong</span><span class="p">[</span><span class="n">addressCount</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">            <span class="k">for</span> <span class="p">(</span><span class="kt">var</span> <span class="n">index</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">index</span> <span class="p">&lt;</span> <span class="n">addressCount</span><span class="p">;</span> <span class="n">index</span><span class="p">++)</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">callStackAddresses</span><span class="p">[</span><span class="n">index</span><span class="p">]</span> <span class="p">=</span> <span class="n">addresses</span><span class="p">[</span><span class="n">index</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="k">new</span> <span class="n">EventPipeUnresolvedStack</span><span class="p">(</span><span class="n">callStackAddresses</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">extendedData</span><span class="p">.</span><span class="n">ExtType</span> <span class="p">==</span> <span class="n">TraceEventNativeMethods</span><span class="p">.</span><span class="n">EVENT_HEADER_EXT_TYPE_STACK_TRACE32</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">stackRecord</span> <span class="p">=</span> <span class="p">(</span><span class="n">TraceEventNativeMethods</span><span class="p">.</span><span class="n">EVENT_EXTENDED_ITEM_STACK_TRACE32</span><span class="p">*)</span><span class="n">extendedData</span><span class="p">.</span><span class="n">DataPtr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">addresses</span> <span class="p">=</span> <span class="p">&amp;</span><span class="n">stackRecord</span><span class="p">-&gt;</span><span class="n">Address</span><span class="p">[</span><span class="m">0</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">addressCount</span> <span class="p">=</span> <span class="p">(</span><span class="n">extendedData</span><span class="p">.</span><span class="n">DataSize</span> <span class="p">-</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">UInt32</span><span class="p">))</span> <span class="p">/</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">UInt32</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">addressCount</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">callStackAddresses</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">ulong</span><span class="p">[</span><span class="n">addressCount</span><span class="p">];</span>  <span class="c1">// store the 32 addresses as 64 bit addresses</span>
</span></span><span class="line"><span class="cl">            <span class="k">for</span> <span class="p">(</span><span class="kt">var</span> <span class="n">index</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">index</span> <span class="p">&lt;</span> <span class="n">addressCount</span><span class="p">;</span> <span class="n">index</span><span class="p">++)</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">callStackAddresses</span><span class="p">[</span><span class="n">index</span><span class="p">]</span> <span class="p">=</span> <span class="n">addresses</span><span class="p">[</span><span class="n">index</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="k">new</span> <span class="n">EventPipeUnresolvedStack</span><span class="p">(</span><span class="n">callStackAddresses</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Note that the last version of TraceEvent nuget provides a public access to the <strong>eventRecord</strong> field so it is no more needed to use the <strong>UnsafeAccessor</strong> attribute used by Olivier.</p>
<h2 id="symbolize-the-call-stack-addresses">Symbolize the call stack addresses</h2>
<p>Address is good but the corresponding method name is better. I won’t repeat what I’ve already detailed in <a href="https://techblog.criteo.com/build-your-own-net-memory-profiler-in-c-call-stacks-2-2-2-ec9657eb17f9?source=friends_link&amp;sk=b34465f583867cb7dcf5bad6395bf151">an older post</a> that shows how to get the name of a native and managed name from an instruction pointer address. Instead, I want to pinpoint a big limitation of this solution to listen to CLR provider <strong>MethodLoadVerbose</strong>/<strong>MethodDCStartVerboseV2</strong> events. If the methods you are interested in are jitted BEFORE your tool attaches to the application, you will never get these events.</p>
<p>You could get the same mapping address span/method name via the other “<em>Microsoft-Windows-DotNETRuntimeRundown</em>” provider and its <strong>MethodDCEndVerbose</strong> event that contains the expected <strong>MethodStartAddress</strong>, <strong>MethodSize</strong> and <strong>MethodName</strong> in its <a href="https://github.com/dotnet/runtime/blob/d897415e02340a13dc1c5078c09937bdf7ec8a56/src/coreclr/vm/ClrEtwAll.man#L4864">payload</a>. But I need this information before the end of the application…</p>
<p>Looking at <a href="https://github.com/dotnet/docs/blob/main/docs/framework/performance/clr-etw-keywords-and-levels.md#keyword-combinations-for-symbol-resolution-for-the-rundown-provider">the documentation</a>, it seems that the rundown provider accepts the <strong>StartRundownKeyword</strong> value to emit the DCStart events when the provider is enabled! <a href="https://github.com/dotnet/runtime/issues/42378">Since .NET 9</a>, it is possible to pass the keywords you want (before, the default value did not contain <strong>StartRundownKeyword</strong>) when creating the EventPipe session</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="c1">//                V-- this is the default rundown keyword</span>
</span></span><span class="line"><span class="cl"><span class="n">rundownKeywords</span> <span class="p">=</span> <span class="m">0x80020139</span> <span class="p">|</span> <span class="p">(</span><span class="kt">long</span><span class="p">)</span><span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">StartEnumeration</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">config</span> <span class="p">=</span> <span class="k">new</span> <span class="n">EventPipeSessionConfiguration</span><span class="p">(</span><span class="n">GetProviders</span><span class="p">(),</span> <span class="m">256</span><span class="p">,</span> <span class="n">rundownKeywords</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="p">(</span><span class="kt">var</span> <span class="n">session</span> <span class="p">=</span> <span class="n">client</span><span class="p">.</span><span class="n">StartEventPipeSession</span><span class="p">(</span><span class="n">config</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">source</span> <span class="p">=</span> <span class="k">new</span> <span class="n">EventPipeEventSource</span><span class="p">(</span><span class="n">session</span><span class="p">.</span><span class="n">EventStream</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">RegisterListeners</span><span class="p">(</span><span class="n">source</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// this is a blocking call</span>
</span></span><span class="line"><span class="cl">    <span class="n">source</span><span class="p">.</span><span class="n">Process</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Note that you should not add the rundown provider to the list passed as parameter.</p>
<p>Unfortunately, there is <a href="https://github.com/dotnet/runtime/issues/42378">currently an issue in the runtime since September 2020</a> that pinpoints this exact problem. I even tried to create and close a session to get the DCStop events before recreating a new one, but I failed.</p>
<p>The next episode will talk about how it is possible to start a .NET application and get the events since its startup… with the problems that are happening.</p>
]]></content:encoded></item><item><title>Monitor HTTP redirects to reduce unexpected latency</title><link>https://chrisnas.github.io/posts/2024-12-13_monitor-http-redirects-to/</link><pubDate>Fri, 13 Dec 2024 14:21:41 +0000</pubDate><guid>https://chrisnas.github.io/posts/2024-12-13_monitor-http-redirects-to/</guid><description>This post details how redirection handling impacts dotnet-http tool implementation but also possibly your requests latency.</description><content:encoded><![CDATA[<hr>
<p>In the <a href="/posts/2024-11-13_implementing-dotnet-http-to/">previous post</a>, I detailed how I used the undocumented events from the BCL to create the <a href="https://www.nuget.org/packages/dotnet-http">dotnet-http CLI tool</a> to monitor your outgoing HTTP requests. After testing with older versions of .NET, I realized that the code needed to be updated and I’m sharing my findings in this post.</p>
<p>The main point is that url redirections could have a major impact on requests latency:</p>
<p><img loading="lazy" src="/posts/2024-12-13_monitor-http-redirects-to/1_w_2f8F9E6hGmoiGLBCMqfw.png"></p>
<h2 id="always-test-supported-versions">Always test supported versions…</h2>
<p>When I wrote the initial version of dotnet-http, I only tested it with .NET 8 and .NET 9 with limited formats of urls. Unfortunately, things went bad when I tried to monitor applications running on .NET 5 and .NET 6: no events are emitted by these versions of the BCL.</p>
<p>So, the next step was to test .NET 7 and the result was simple: crash! After investigating, I realized that some events I looked at in .NET 8 source code did not have the same payload in .NET 7; even no payload at all:</p>
<p><img loading="lazy" src="/posts/2024-12-13_monitor-http-redirects-to/1_2HJxVmyxQfzhCr1i7_dpWw.png"></p>
<p>Even though there is no version field in the events payload, it is easy to check its size such as the following:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">OnConnectionEstablished</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">     <span class="n">DateTime</span> <span class="n">timestamp</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">     <span class="kt">int</span> <span class="n">threadId</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">     <span class="n">Guid</span> <span class="n">activityId</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">     <span class="n">Guid</span> <span class="n">relatedActivityId</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">     <span class="kt">byte</span><span class="p">[]</span> <span class="n">eventData</span>
</span></span><span class="line"><span class="cl">     <span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl">     <span class="p">...</span>
</span></span><span class="line"><span class="cl">     <span class="n">EventSourcePayload</span> <span class="n">payload</span> <span class="p">=</span> <span class="k">new</span> <span class="n">EventSourcePayload</span><span class="p">(</span><span class="n">eventData</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">     <span class="kt">var</span> <span class="n">versionMajor</span> <span class="p">=</span> <span class="n">payload</span><span class="p">.</span><span class="n">GetByte</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">     <span class="kt">var</span> <span class="n">versionMinor</span> <span class="p">=</span> <span class="n">payload</span><span class="p">.</span><span class="n">GetByte</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">     <span class="c1">// in .NET 7, nothing else is available</span>
</span></span><span class="line"><span class="cl">     <span class="n">Int64</span> <span class="n">connectionId</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">     <span class="kt">var</span> <span class="n">scheme</span> <span class="p">=</span> <span class="s">&#34;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">     <span class="kt">var</span> <span class="n">host</span> <span class="p">=</span> <span class="s">&#34;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">     <span class="n">UInt32</span> <span class="n">port</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">     <span class="kt">var</span> <span class="n">path</span> <span class="p">=</span> <span class="s">&#34;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">     <span class="k">if</span> <span class="p">(</span><span class="n">eventData</span><span class="p">.</span><span class="n">Length</span> <span class="p">&gt;</span> <span class="m">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">     <span class="p">{</span>
</span></span><span class="line"><span class="cl">         <span class="n">connectionId</span> <span class="p">=</span> <span class="n">payload</span><span class="p">.</span><span class="n">GetInt64</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">         <span class="n">scheme</span> <span class="p">=</span> <span class="n">payload</span><span class="p">.</span><span class="n">GetString</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">         <span class="n">host</span> <span class="p">=</span> <span class="n">payload</span><span class="p">.</span><span class="n">GetString</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">         <span class="n">port</span> <span class="p">=</span> <span class="n">payload</span><span class="p">.</span><span class="n">GetUInt32</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">         <span class="n">path</span> <span class="p">=</span> <span class="n">payload</span><span class="p">.</span><span class="n">GetString</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">     <span class="p">}</span>
</span></span><span class="line"><span class="cl">     <span class="p">...</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Another difference is that one even is not even emitted in .NET 7:</p>
<p><img loading="lazy" src="/posts/2024-12-13_monitor-http-redirects-to/1_Iwpe5YVHJwyBnRFvHMTiQw.png"></p>
<h2 id="explain-what-a-redirection-isplease">Explain what a redirection is please!</h2>
<p>Because I based the code on this Redirect event, I needed to find another way to support .NET 7 even though I would not have a redirected url to display. But first, let’s see what I’m talking about in terms of HTTP communication.</p>
<p>When you try to get the content / status code behind a url, the code is following different phases with the related events:</p>
<ul>
<li>**Start
**RequestStart</li>
<li>**DNS resolution
**ResolutionStart
ResolutionStop/Fail</li>
<li>**Socket connection
**ConnectStart
ConnectStop</li>
<li>**Security hand check (HTTPS only)
**HandshakeStart
HandshakeStop/Failed</li>
<li>**Request/response
**RequestHeadersStart
RequestHeadersStop
ResponseHeadersStart
ResponseHeadersStop
Redirect (.NET 8+)
ResponseContentStart
ResponseContentStop</li>
<li>**Request stop
**RequestStop/Failed</li>
</ul>
<p>Based on the received url, a server can decide to answer that another url should be used instead. For example, if you call github with <strong>http://</strong> instead of <a href="https://,"><strong>https://</strong>,</a> such a redirection will happen. Without invasive tools such as Wireshark, these redirections are impossible to detect and could cause unnecessary delay.</p>
<p>From the client perspective, this can be detected in the <strong>ResponseHeadersStop</strong> event payload that provides a status code. If its value is 301, then it is a redirection. The other effect of a redirection is that the BCL code will change the flow of events because it needs to start over with the new url from step 2. to step 6. As you can see, instead of paying the cost of just one request, two are actually emitted and processed.</p>
<h2 id="impact-on-the-implementation">Impact on the implementation</h2>
<p>In addition to the payload size checks addition, my initial implementation was not properly handling the redirection because the values (timestamps and durations) where overridden by the events related to the second redirected url.</p>
<p>The new implementation is splitting the request details into two classes. A base class that contains common fields to both parts of the request in case of redirection:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">class</span> <span class="nc">HttpRequestInfoBase</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span>
</span></span><span class="line"><span class="cl">       <span class="kd">public</span> <span class="n">HttpRequestInfoBase</span><span class="p">(</span><span class="n">DateTime</span> <span class="n">timestamp</span><span class="p">,</span> <span class="kt">string</span> <span class="n">scheme</span><span class="p">,</span> <span class="kt">string</span> <span class="n">host</span><span class="p">,</span> <span class="kt">uint</span> <span class="n">port</span><span class="p">,</span> <span class="kt">string</span> <span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">       <span class="p">{</span>
</span></span><span class="line"><span class="cl">           <span class="n">StartTime</span> <span class="p">=</span> <span class="n">timestamp</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">           <span class="k">if</span> <span class="p">(</span><span class="n">scheme</span> <span class="p">==</span> <span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">           <span class="p">{</span>
</span></span><span class="line"><span class="cl">               <span class="n">Url</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">           <span class="p">}</span>
</span></span><span class="line"><span class="cl">           <span class="k">else</span>
</span></span><span class="line"><span class="cl">           <span class="p">{</span>
</span></span><span class="line"><span class="cl">               <span class="k">if</span> <span class="p">(</span><span class="n">port</span> <span class="p">!=</span> <span class="m">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">               <span class="p">{</span>
</span></span><span class="line"><span class="cl">                   <span class="n">Url</span> <span class="p">=</span> <span class="s">$&#34;{scheme}://{host}:{port}{path}&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">               <span class="p">}</span>
</span></span><span class="line"><span class="cl">               <span class="k">else</span>
</span></span><span class="line"><span class="cl">               <span class="p">{</span>
</span></span><span class="line"><span class="cl">                   <span class="n">Url</span> <span class="p">=</span> <span class="s">$&#34;{scheme}://{host}:{path}&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">               <span class="p">}</span>
</span></span><span class="line"><span class="cl">           <span class="p">}</span>
</span></span><span class="line"><span class="cl">       <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">       <span class="kd">public</span> <span class="kt">string</span> <span class="n">Url</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">       <span class="kd">public</span> <span class="n">DateTime</span> <span class="n">StartTime</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">       <span class="kd">public</span> <span class="n">DateTime</span> <span class="n">ReqRespStartTime</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">       <span class="kd">public</span> <span class="kt">double</span> <span class="n">ReqRespDuration</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">       <span class="c1">// DNS</span>
</span></span><span class="line"><span class="cl">       <span class="kd">public</span> <span class="kt">double</span> <span class="n">DnsWait</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">       <span class="kd">public</span> <span class="n">DateTime</span> <span class="n">DnsStartTime</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">       <span class="kd">public</span> <span class="kt">double</span> <span class="n">DnsDuration</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">       <span class="c1">// HTTPS</span>
</span></span><span class="line"><span class="cl">       <span class="kd">public</span> <span class="kt">double</span> <span class="n">HandshakeWait</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">       <span class="kd">public</span> <span class="n">DateTime</span> <span class="n">HandshakeStartTime</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">       <span class="kd">public</span> <span class="kt">double</span> <span class="n">HandshakeDuration</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">       <span class="c1">// socket connection</span>
</span></span><span class="line"><span class="cl">       <span class="kd">public</span> <span class="n">DateTime</span> <span class="n">SocketConnectionStartTime</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">       <span class="kd">public</span> <span class="kt">double</span> <span class="n">SocketWait</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">       <span class="kd">public</span> <span class="kt">double</span> <span class="n">SocketDuration</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">       <span class="kd">public</span> <span class="n">DateTime</span> <span class="n">QueueuingEndTime</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">       <span class="kd">public</span> <span class="kt">double</span> <span class="n">QueueingDuration</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">   <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The second one inherits from the base class and contains addition details; including the details of the redirected url if any:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">class</span> <span class="nc">HttpRequestInfo</span> <span class="p">:</span> <span class="n">HttpRequestInfoBase</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span>
</span></span><span class="line"><span class="cl">       <span class="kd">public</span> <span class="n">HttpRequestInfo</span><span class="p">(</span><span class="n">DateTime</span> <span class="n">timestamp</span><span class="p">,</span> <span class="kt">string</span> <span class="n">scheme</span><span class="p">,</span> <span class="kt">string</span> <span class="n">host</span><span class="p">,</span> <span class="kt">uint</span> <span class="n">port</span><span class="p">,</span> <span class="kt">string</span> <span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">           <span class="p">:</span>
</span></span><span class="line"><span class="cl">           <span class="k">base</span><span class="p">(</span><span class="n">timestamp</span><span class="p">,</span> <span class="n">scheme</span><span class="p">,</span> <span class="n">host</span><span class="p">,</span> <span class="n">port</span><span class="p">,</span> <span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">       <span class="p">{</span>
</span></span><span class="line"><span class="cl">       <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">       <span class="kd">public</span> <span class="n">HttpRequestInfoBase</span> <span class="n">Redirect</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">       <span class="kd">public</span> <span class="n">UInt32</span> <span class="n">StatusCode</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">       <span class="c1">// HTTPS</span>
</span></span><span class="line"><span class="cl">       <span class="kd">public</span> <span class="kt">string</span> <span class="n">HandshakeErrorMessage</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">       <span class="kd">public</span> <span class="kt">string</span> <span class="n">Error</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">   <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>A new instance of <strong>HttpRequestInfoBase</strong> is created when a 301 status code is received in <strong>HttpResponseHeaderStop</strong> handler:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">OnHttpResponseHeaderStop</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">HttpRequestStatusEventArgs</span> <span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// used to detect redirection in .NET 8+</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="n">StatusCode</span> <span class="p">!=</span> <span class="m">301</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// create a new request info for the redirected request</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// because .NET 7 does not emit a Redirect event, we need to create a new request info here</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// --&gt; it means that the redirect url will be empty in .NET 7</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">root</span> <span class="p">=</span> <span class="n">GetRoot</span><span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="n">ActivityId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">_requests</span><span class="p">.</span><span class="n">TryGetValue</span><span class="p">(</span><span class="n">root</span><span class="p">,</span> <span class="k">out</span> <span class="n">HttpRequestInfo</span> <span class="n">info</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">info</span><span class="p">.</span><span class="n">Redirect</span> <span class="p">=</span> <span class="k">new</span> <span class="n">HttpRequestInfoBase</span><span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="n">Timestamp</span><span class="p">,</span> <span class="s">&#34;&#34;</span><span class="p">,</span> <span class="s">&#34;&#34;</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="s">&#34;&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="c1">// if you really want to have the duration of both original request + redirected request,</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// then do the following:</span>
</span></span><span class="line"><span class="cl">            <span class="c1">//    info.ReqRespDuration = (e.Timestamp - info.ReqRespStartTime).TotalMilliseconds;</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// However, I prefer to show the duration of the redirected request only to more easily</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// compute the cost of the initial redirected request = total duration - other durations</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>For .NET 8+, the redirected url is provided to the <strong>Redirect</strong> handler and stored in the Url field of the instance created in the previous handler:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">OnHttpRedirect</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">HttpRedirectEventArgs</span> <span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// since this is an Info event, the activityID is the root</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">root</span> <span class="p">=</span> <span class="n">ActivityHelpers</span><span class="p">.</span><span class="n">ActivityPathString</span><span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="n">ActivityId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">_requests</span><span class="p">.</span><span class="n">TryGetValue</span><span class="p">(</span><span class="n">root</span><span class="p">,</span> <span class="k">out</span> <span class="n">HttpRequestInfo</span> <span class="n">info</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">info</span><span class="p">.</span><span class="n">Redirect</span><span class="p">.</span><span class="n">Url</span> <span class="p">=</span> <span class="n">e</span><span class="p">.</span><span class="n">RedirectUrl</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>In each handler of events that could be received for both initial and redirected requests,</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">OnHttpResponseContentStop</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">EventPipeBaseArgs</span> <span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span>
</span></span><span class="line"><span class="cl">       <span class="kt">var</span> <span class="n">root</span> <span class="p">=</span> <span class="n">GetRoot</span><span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="n">ActivityId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">       <span class="k">if</span> <span class="p">(!</span><span class="n">_requests</span><span class="p">.</span><span class="n">TryGetValue</span><span class="p">(</span><span class="n">root</span><span class="p">,</span> <span class="k">out</span> <span class="n">HttpRequestInfo</span> <span class="n">info</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">       <span class="p">{</span>
</span></span><span class="line"><span class="cl">           <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">       <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">       <span class="k">if</span> <span class="p">(</span><span class="n">info</span><span class="p">.</span><span class="n">Redirect</span> <span class="p">==</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">       <span class="p">{</span>
</span></span><span class="line"><span class="cl">           <span class="n">info</span><span class="p">.</span><span class="n">ReqRespDuration</span> <span class="p">=</span> <span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="n">Timestamp</span> <span class="p">-</span> <span class="n">info</span><span class="p">.</span><span class="n">ReqRespStartTime</span><span class="p">).</span><span class="n">TotalMilliseconds</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">       <span class="p">}</span>
</span></span><span class="line"><span class="cl">       <span class="k">else</span>
</span></span><span class="line"><span class="cl">       <span class="p">{</span>
</span></span><span class="line"><span class="cl">           <span class="n">info</span><span class="p">.</span><span class="n">Redirect</span><span class="p">.</span><span class="n">ReqRespDuration</span> <span class="p">=</span> <span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="n">Timestamp</span> <span class="p">-</span> <span class="n">info</span><span class="p">.</span><span class="n">Redirect</span><span class="p">.</span><span class="n">ReqRespStartTime</span><span class="p">).</span><span class="n">TotalMilliseconds</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">       <span class="p">}</span>
</span></span><span class="line"><span class="cl">   <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The wait time and durations are now computed as the events are received and aggregated at the end of the request by adding the value of both parts (initial and redirected if any:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kt">double</span> <span class="n">dnsDuration</span> <span class="p">=</span> <span class="n">info</span><span class="p">.</span><span class="n">DnsDuration</span> <span class="p">+</span> <span class="p">((</span><span class="n">info</span><span class="p">.</span><span class="n">Redirect</span> <span class="p">!=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">?</span> <span class="n">info</span><span class="p">.</span><span class="n">Redirect</span><span class="p">.</span><span class="n">DnsDuration</span> <span class="p">:</span> <span class="m">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">dnsDuration</span> <span class="p">&gt;</span> <span class="m">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">double</span> <span class="n">dnsWait</span> <span class="p">=</span> <span class="n">info</span><span class="p">.</span><span class="n">DnsWait</span> <span class="p">+</span> <span class="p">((</span><span class="n">info</span><span class="p">.</span><span class="n">Redirect</span> <span class="p">!=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">?</span> <span class="n">info</span><span class="p">.</span><span class="n">Redirect</span><span class="p">.</span><span class="n">DnsWait</span> <span class="p">:</span> <span class="m">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">Console</span><span class="p">.</span><span class="n">Write</span><span class="p">(</span><span class="s">$&#34;{dnsWait,9:F3} | {dnsDuration,9:F3} | &#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">Console</span><span class="p">.</span><span class="n">Write</span><span class="p">(</span><span class="s">$&#34;          |           | &#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>As a conclusion, you should try to monitor these redirections by using my <a href="https://www.nuget.org/packages/dotnet-http">dotnet-http</a> CLI tool. Feel free to download it or install it with the following command line: <strong>dotnet tool install -g dotnet-http <strong>or update to the latest version with</strong>: dotnet tool update -g dotnet-http</strong></p>
<p>You could also integrate some event listening code into your framework that simply handles the <strong>ResponseHeadersStop</strong>/<strong>Redirect</strong> events.</p>
]]></content:encoded></item><item><title>Implementing dotnet-http to monitor your HTTP requests</title><link>https://chrisnas.github.io/posts/2024-11-13_implementing-dotnet-http-to/</link><pubDate>Wed, 13 Nov 2024 13:18:37 +0000</pubDate><guid>https://chrisnas.github.io/posts/2024-11-13_implementing-dotnet-http-to/</guid><description>This episode shows how to listen to HTTP related events and compute the duration of DNS, socket and security phases</description><content:encoded><![CDATA[<hr>
<p>The <a href="/posts/2024-10-13_digging-into-the-undocumented/">previous episode</a> detailed how to find the events dealing with network requests that are emitted by the BCL classes with their undocumented payload. It is now time to see how to listen to them and extract valuable insights such as what is happening when an HTTP request is sent to a server as an example. This is how my new <a href="https://www.nuget.org/packages/dotnet-http">dotnet-http CLI tool</a> is implemented.</p>
<p>You are now able to see the cost of DNS, socket connection, security and redirection as shown in the following screenshot.</p>
<p><img loading="lazy" src="/posts/2024-11-13_implementing-dotnet-http-to/1_Xx8OW9oP34V6fVKKdxqZQA.png"></p>
<p>As you can see, the cost of an unexpected redirection could be high and hides security handshakes. In this example, using <a href="https://github.com/Maoni0"><strong>https</strong>://github.com/Maoni0</a> instead of <a href="http://github.com/Maoni0"><strong>http</strong>://github.com/Maoni0</a> divides by 2 the request duration!</p>
<p>Once the DNS checks are done, the socket connections are established, and the security handshakes are validated, the corresponding phases are no more needed for the next requests.</p>
<h2 id="listen-to-custom-eventsource-events">Listen to custom EventSource events</h2>
<p>As explained in previous posts, it is easy to listen to events emitted by .NET application thanks to the Microsoft TraceEvent nuget. You also need to use <strong>EventPipeClient</strong> from the <a href="https://www.nuget.org/packages/Microsoft.Diagnostics.NETCore.Client">Microsoft.Diagnostics.NETCore.Client</a> nuget to connect to the running application. In my case, I’m relying on an older version that supports both ETW and EventPipe:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">configuration</span> <span class="p">=</span> <span class="k">new</span> <span class="n">SessionConfiguration</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">circularBufferSizeMB</span><span class="p">:</span> <span class="m">2000</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">format</span><span class="p">:</span> <span class="n">EventPipeSerializationFormat</span><span class="p">.</span><span class="n">NetTrace</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">providers</span><span class="p">:</span> <span class="n">GetProviders</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">binaryReader</span> <span class="p">=</span> <span class="n">EventPipeClient</span><span class="p">.</span><span class="n">CollectTracing</span><span class="p">(</span><span class="n">_processId</span><span class="p">,</span> <span class="n">configuration</span><span class="p">,</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">sessionId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">EventPipeEventSource</span> <span class="n">source</span> <span class="p">=</span> <span class="k">new</span> <span class="n">EventPipeEventSource</span><span class="p">(</span><span class="n">binaryReader</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The configuration contains a list of providers that are emitting the events you are interested in with the right keyword and verbosity. Here is what is needed for the HTTP requests monitoring :</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="n">IReadOnlyCollection</span><span class="p">&lt;</span><span class="n">Provider</span><span class="p">&gt;</span> <span class="n">GetProviders</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">providers</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Provider</span><span class="p">[]</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">new</span> <span class="n">Provider</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">name</span><span class="p">:</span> <span class="s">&#34;System.Net.Http&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">keywords</span><span class="p">:</span> <span class="p">(</span><span class="kt">ulong</span><span class="p">)(</span><span class="m">1</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">eventLevel</span><span class="p">:</span> <span class="n">EventLevel</span><span class="p">.</span><span class="n">Verbose</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">new</span> <span class="n">Provider</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">name</span><span class="p">:</span> <span class="s">&#34;System.Net.Sockets&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">keywords</span><span class="p">:</span> <span class="p">(</span><span class="kt">ulong</span><span class="p">)(</span><span class="m">0xFFFFFFFF</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">eventLevel</span><span class="p">:</span> <span class="n">EventLevel</span><span class="p">.</span><span class="n">Verbose</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">new</span> <span class="n">Provider</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">name</span><span class="p">:</span> <span class="s">&#34;System.Net.NameResolution&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">keywords</span><span class="p">:</span> <span class="p">(</span><span class="kt">ulong</span><span class="p">)(</span><span class="m">0xFFFFFFFF</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">eventLevel</span><span class="p">:</span> <span class="n">EventLevel</span><span class="p">.</span><span class="n">Verbose</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">new</span> <span class="n">Provider</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">name</span><span class="p">:</span> <span class="s">&#34;System.Net.Security&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">keywords</span><span class="p">:</span> <span class="p">(</span><span class="kt">ulong</span><span class="p">)(</span><span class="m">0xFFFFFFFF</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">eventLevel</span><span class="p">:</span> <span class="n">EventLevel</span><span class="p">.</span><span class="n">Verbose</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">new</span> <span class="n">Provider</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">name</span><span class="p">:</span> <span class="s">&#34;System.Threading.Tasks.TplEventSource&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">keywords</span><span class="p">:</span> <span class="p">(</span><span class="kt">ulong</span><span class="p">)(</span><span class="m">0x80</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">eventLevel</span><span class="p">:</span> <span class="n">EventLevel</span><span class="p">.</span><span class="n">Verbose</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">providers</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>You might be surprised by the presence of the <strong>TplEventSource</strong> provider but it is required to get a correct <strong>ActivityID</strong>. This major subject is detailed later.</p>
<p>There are so many events to listen to that it is easier to listen to the source <strong>AllEvents</strong> C# event:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="n">source</span><span class="p">.</span><span class="n">AllEvents</span> <span class="p">+=</span> <span class="n">OnEvents</span><span class="p">;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>This global handler passes the events to <strong>ParseEvent</strong> that forwards the important data to handlers for each provider:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="n">Guid</span> <span class="n">NetSecurityEventSourceProviderGuid</span> <span class="p">=</span> <span class="n">Guid</span><span class="p">.</span><span class="n">Parse</span><span class="p">(</span><span class="s">&#34;7beee6b1-e3fa-5ddb-34be-1404ad0e2520&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="n">Guid</span> <span class="n">DnsEventSourceProviderGuid</span> <span class="p">=</span> <span class="n">Guid</span><span class="p">.</span><span class="n">Parse</span><span class="p">(</span><span class="s">&#34;4b326142-bfb5-5ed3-8585-7714181d14b0&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="n">Guid</span> <span class="n">SocketEventSourceProviderGuid</span> <span class="p">=</span> <span class="n">Guid</span><span class="p">.</span><span class="n">Parse</span><span class="p">(</span><span class="s">&#34;d5b2e7d4-b6ec-50ae-7cde-af89427ad21f&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="n">Guid</span> <span class="n">HttpEventSourceProviderGuid</span> <span class="p">=</span> <span class="n">Guid</span><span class="p">.</span><span class="n">Parse</span><span class="p">(</span><span class="s">&#34;d30b5633-7ef1-5485-b4e0-94979b102068&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">ParseEvent</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">DateTime</span> <span class="n">timestamp</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">threadId</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">Guid</span> <span class="n">activityId</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">Guid</span> <span class="n">relatedActivityId</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">Guid</span> <span class="n">providerGuid</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="kt">string</span> <span class="n">taskName</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">Int64</span> <span class="n">keywords</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">UInt16</span> <span class="n">id</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="kt">byte</span><span class="p">[]</span> <span class="n">eventData</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">providerGuid</span> <span class="p">==</span> <span class="n">NetSecurityEventSourceProviderGuid</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">HandleNetSecurityEvent</span><span class="p">(</span><span class="n">timestamp</span><span class="p">,</span> <span class="n">threadId</span><span class="p">,</span> <span class="n">activityId</span><span class="p">,</span> <span class="n">relatedActivityId</span><span class="p">,</span> <span class="n">id</span><span class="p">,</span> <span class="n">taskName</span><span class="p">,</span> <span class="n">eventData</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">providerGuid</span> <span class="p">==</span> <span class="n">DnsEventSourceProviderGuid</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">HandleDnsEvent</span><span class="p">(</span><span class="n">timestamp</span><span class="p">,</span> <span class="n">threadId</span><span class="p">,</span> <span class="n">activityId</span><span class="p">,</span> <span class="n">relatedActivityId</span><span class="p">,</span> <span class="n">id</span><span class="p">,</span> <span class="n">taskName</span><span class="p">,</span> <span class="n">eventData</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">providerGuid</span> <span class="p">==</span> <span class="n">SocketEventSourceProviderGuid</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">HandleSocketEvent</span><span class="p">(</span><span class="n">timestamp</span><span class="p">,</span> <span class="n">threadId</span><span class="p">,</span> <span class="n">activityId</span><span class="p">,</span> <span class="n">relatedActivityId</span><span class="p">,</span> <span class="n">id</span><span class="p">,</span> <span class="n">taskName</span><span class="p">,</span> <span class="n">eventData</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">providerGuid</span> <span class="p">==</span> <span class="n">HttpEventSourceProviderGuid</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">HandleHttpEvent</span><span class="p">(</span><span class="n">timestamp</span><span class="p">,</span> <span class="n">threadId</span><span class="p">,</span> <span class="n">activityId</span><span class="p">,</span> <span class="n">relatedActivityId</span><span class="p">,</span> <span class="n">id</span><span class="p">,</span> <span class="n">taskName</span><span class="p">,</span> <span class="n">eventData</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">WriteLogLine</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The next step is to handle each event based on its id:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">HandleNetSecurityEvent</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">DateTime</span> <span class="n">timestamp</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">threadId</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">Guid</span> <span class="n">activityId</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">Guid</span> <span class="n">relatedActivityId</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="kt">ushort</span> <span class="n">id</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="kt">string</span> <span class="n">taskName</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="kt">byte</span><span class="p">[]</span> <span class="n">eventData</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">switch</span> <span class="p">(</span><span class="n">id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="m">1</span><span class="p">:</span> <span class="c1">// HandshakeStart</span>
</span></span><span class="line"><span class="cl">            <span class="n">OnHandshakeStart</span><span class="p">(</span><span class="n">timestamp</span><span class="p">,</span> <span class="n">threadId</span><span class="p">,</span> <span class="n">activityId</span><span class="p">,</span> <span class="n">relatedActivityId</span><span class="p">,</span> <span class="n">eventData</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="m">2</span><span class="p">:</span> <span class="c1">// HandshakeStop</span>
</span></span><span class="line"><span class="cl">            <span class="n">OnHandshakeStop</span><span class="p">(</span><span class="n">timestamp</span><span class="p">,</span> <span class="n">threadId</span><span class="p">,</span> <span class="n">activityId</span><span class="p">,</span> <span class="n">relatedActivityId</span><span class="p">,</span> <span class="n">eventData</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="m">3</span><span class="p">:</span> <span class="c1">// HandshakeFailed</span>
</span></span><span class="line"><span class="cl">            <span class="n">OnHandshakeFailed</span><span class="p">(</span><span class="n">timestamp</span><span class="p">,</span> <span class="n">threadId</span><span class="p">,</span> <span class="n">activityId</span><span class="p">,</span> <span class="n">relatedActivityId</span><span class="p">,</span> <span class="n">eventData</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">default</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">WriteLogLine</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="extract-information-from-an-eventpayload">Extract information from an event payload</h2>
<p>The payload of each event has been detailed in <a href="/posts/2024-10-13_digging-into-the-undocumented/">the previous post</a>. For example, I need to extract the following fields from the <strong>RequestStart</strong> event payload:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">OnRequestStart</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">DateTime</span> <span class="n">timestamp</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">threadId</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">Guid</span> <span class="n">activityId</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">Guid</span> <span class="n">relatedActivityId</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="kt">byte</span><span class="p">[]</span> <span class="n">eventData</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">...</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// string scheme</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// string host</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// int port</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// string path</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// byte versionMajor</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// byte versionMinor</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// enum HttpVersionPolicy</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>I’ve implemented the <strong>EventSourcePayload</strong> class that provides strongly typed helpers to get the different fields one after the other:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">EventSourcePayload</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kt">byte</span><span class="p">[]</span> <span class="n">_payload</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kt">int</span> <span class="n">_pos</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">EventSourcePayload</span><span class="p">(</span><span class="kt">byte</span><span class="p">[]</span> <span class="n">payload</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">_payload</span> <span class="p">=</span> <span class="n">payload</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>It accepts the payload as the array of bytes received by each handler.</p>
<p>A string information is serialized as a list of UTF-16 Unicode characters; each one stored in 2 bytes:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="kt">string</span> <span class="n">GetString</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">StringBuilder</span> <span class="n">builder</span> <span class="p">=</span> <span class="k">new</span> <span class="n">StringBuilder</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="k">while</span> <span class="p">(</span><span class="n">_pos</span> <span class="p">&lt;</span> <span class="n">_payload</span><span class="p">.</span><span class="n">Length</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">characters</span> <span class="p">=</span> <span class="n">UnicodeEncoding</span><span class="p">.</span><span class="n">Unicode</span><span class="p">.</span><span class="n">GetString</span><span class="p">(</span><span class="n">_payload</span><span class="p">,</span> <span class="n">_pos</span><span class="p">,</span> <span class="m">2</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="n">_pos</span> <span class="p">+=</span> <span class="m">2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">characters</span> <span class="p">==</span> <span class="s">&#34;\0&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="n">builder</span><span class="p">.</span><span class="n">Append</span><span class="p">(</span><span class="n">characters</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">builder</span><span class="p">.</span><span class="n">ToString</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The current position in the array is incremented character by character up to the final ‘\0’.</p>
<p>The other helpers implementation is straightforward thanks to the <a href="https://learn.microsoft.com/en-us/dotnet/api/system.bitconverter?WT.mc_id=DT-MVP-5003325"><strong>BitConverter</strong> class</a>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="kt">byte</span> <span class="n">GetByte</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">_payload</span><span class="p">[</span><span class="n">_pos</span><span class="p">++];</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">UInt16</span> <span class="n">GetUnit16</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">UInt16</span> <span class="k">value</span> <span class="p">=</span> <span class="n">BitConverter</span><span class="p">.</span><span class="n">ToUInt16</span><span class="p">(</span><span class="n">_payload</span><span class="p">,</span> <span class="n">_pos</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">_pos</span> <span class="p">+=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">UInt16</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="k">value</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">UInt32</span> <span class="n">GetUInt32</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">UInt32</span> <span class="k">value</span> <span class="p">=</span> <span class="n">BitConverter</span><span class="p">.</span><span class="n">ToUInt32</span><span class="p">(</span><span class="n">_payload</span><span class="p">,</span> <span class="n">_pos</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">_pos</span> <span class="p">+=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">UInt32</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="k">value</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">UInt64</span> <span class="n">GetUInt64</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">UInt64</span> <span class="k">value</span> <span class="p">=</span> <span class="n">BitConverter</span><span class="p">.</span><span class="n">ToUInt64</span><span class="p">(</span><span class="n">_payload</span><span class="p">,</span> <span class="n">_pos</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">_pos</span> <span class="p">+=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">UInt64</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="k">value</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">Int64</span> <span class="n">GetInt64</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">Int64</span> <span class="k">value</span> <span class="p">=</span> <span class="n">BitConverter</span><span class="p">.</span><span class="n">ToInt64</span><span class="p">(</span><span class="n">_payload</span><span class="p">,</span> <span class="n">_pos</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">_pos</span> <span class="p">+=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">UInt64</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="k">value</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">double</span> <span class="n">GetDouble</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">double</span> <span class="k">value</span> <span class="p">=</span> <span class="n">BitConverter</span><span class="p">.</span><span class="n">ToDouble</span><span class="p">(</span><span class="n">_payload</span><span class="p">,</span> <span class="n">_pos</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">_pos</span> <span class="p">+=</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">double</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="k">value</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Again, the position in the array is incremented to reflect the size of the field being read.</p>
<p>If you look at the rest of the <strong>OnRequestStart</strong> handler, you will see how each field is extracted:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="n">EventSourcePayload</span> <span class="n">payload</span> <span class="p">=</span> <span class="k">new</span> <span class="n">EventSourcePayload</span><span class="p">(</span><span class="n">eventData</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">scheme</span> <span class="p">=</span> <span class="n">payload</span><span class="p">.</span><span class="n">GetString</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">host</span> <span class="p">=</span> <span class="n">payload</span><span class="p">.</span><span class="n">GetString</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">port</span> <span class="p">=</span> <span class="n">payload</span><span class="p">.</span><span class="n">GetUInt32</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">path</span> <span class="p">=</span> <span class="n">payload</span><span class="p">.</span><span class="n">GetString</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">versionMajor</span> <span class="p">=</span> <span class="n">payload</span><span class="p">.</span><span class="n">GetByte</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">versionMinor</span> <span class="p">=</span> <span class="n">payload</span><span class="p">.</span><span class="n">GetByte</span><span class="p">();</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="my-activity-or-not-my-activity-that-is-thequestion">My activity or not my activity: that is the question</h2>
<p>In the previous episode, I forgot (on purpose) to mention that the <strong>EventSource</strong> is keeping track of “activities” when emitting events:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">protected</span> <span class="kd">unsafe</span> <span class="k">void</span> <span class="n">WriteEventCore</span><span class="p">(</span><span class="kt">int</span> <span class="n">eventId</span><span class="p">,</span> <span class="kt">int</span> <span class="n">eventDataCount</span><span class="p">,</span> <span class="n">EventData</span><span class="p">*</span> <span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">WriteEventWithRelatedActivityIdCore</span><span class="p">(</span><span class="n">eventId</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="n">eventDataCount</span><span class="p">,</span> <span class="n">data</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <a href="https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventSource.cs#L1367"><strong>WriteEventWithRelatedActivityIdCore</strong></a> method looks at the event metadata and if its opcode is <strong>Start</strong> then a new activity is created; if it is <strong>Stop</strong> then the current activity ends:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="n">opcode</span> <span class="p">==</span> <span class="n">EventOpcode</span><span class="p">.</span><span class="n">Start</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">m_activityTracker</span><span class="p">.</span><span class="n">OnStart</span><span class="p">(</span><span class="n">m_name</span><span class="p">,</span> <span class="n">metadata</span><span class="p">.</span><span class="n">Name</span><span class="p">,</span> <span class="n">metadata</span><span class="p">.</span><span class="n">Descriptor</span><span class="p">.</span><span class="n">Task</span><span class="p">,</span> <span class="k">ref</span> <span class="n">activityId</span><span class="p">,</span> <span class="k">ref</span> <span class="n">relActivityId</span><span class="p">,</span> <span class="n">metadata</span><span class="p">.</span><span class="n">ActivityOptions</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">opcode</span> <span class="p">==</span> <span class="n">EventOpcode</span><span class="p">.</span><span class="n">Stop</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">m_activityTracker</span><span class="p">.</span><span class="n">OnStop</span><span class="p">(</span><span class="n">m_name</span><span class="p">,</span> <span class="n">metadata</span><span class="p">.</span><span class="n">Name</span><span class="p">,</span> <span class="n">metadata</span><span class="p">.</span><span class="n">Descriptor</span><span class="p">.</span><span class="n">Task</span><span class="p">,</span> <span class="k">ref</span> <span class="n">activityId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>These <a href="https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/ActivityTracker.cs#L45"><strong>OnStart</strong></a> and <a href="https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/ActivityTracker.cs#L127"><strong>OnStop</strong></a> methods are doing nothing if the <strong>TplEventSource</strong> is not enabled with <strong>Keywords.TasksFlowActivityIds</strong> (= 0x80) set. This explains the code in <strong>GetProviders</strong> listed earlier where this non-HTTP provider is enabled.</p>
<p>When a request is created, the current global count managed by the <strong>ActivityTracker</strong> is incremented and it becomes the id of the current activity. Note that there is a “root” identifier before any activity gets created corresponding to the current AppDomain ID; starting at 1. If you think of an HTTP request, after a <strong>RequestStart</strong> event, each phase starts a new activity with, for example, <strong>ResolutionStart</strong> or <strong>ConnectStart</strong>. Informative events are emitted with the current request activity such as <strong>Redirect</strong> or <strong>ConnectionEstablished</strong>.</p>
<p>Here is a simplified view of the events emitted for an HTTP request (without DNS nor security events):</p>
<pre tabindex="0"><code>Thread +-- Path &gt;------- ID ---- Opcode -- Event ----------------------------------
 78568 |    1/1 &gt;  event  1 __ [ 1| Start] RequestStart
       |
 32388 |  1/1/1 &gt;  event  1 __ [ 1| Start] ResolutionStart
       |
 32388 |  1/1/1 &gt;  event  2 __ [ 2|  Stop] ResolutionStop
       |
 32388 |  1/1/2 &gt;  event  1 __ [ 1| Start] ConnectStart
       |
 32388 |  1/1/2 &gt;  event  2 __ [ 2|  Stop] ConnectStop
       |
 32388 |    1/1 &gt;  event  4 __ [ 0|  Info] ConnectionEstablished
       |
 53324 |    1/1 &gt;  event  6 __ [ 0|  Info] RequestLeftQueue
       |
 53324 |  1/1/3 &gt;  event  7 __ [ 1| Start] RequestHeadersStart
       |
 53324 |  1/1/3 &gt;  event  8 __ [ 2|  Stop] RequestHeadersStop
       |
 68024 |  1/1/4 &gt;  event 11 __ [ 1| Start] ResponseHeadersStart
       |
 68024 |  1/1/4 &gt;  event 12 __ [ 2|  Stop] ResponseHeadersStop
       |
 68024 |  1/1/5 &gt;  event 13 __ [ 1| Start] ResponseContentStart
       |
 68024 |  1/1/5 &gt;  event 14 __ [ 2|  Stop] ResponseContentStop
       |
 68024 |    1/1 &gt;  event  2 __ [ 2|  Stop] RequestStop
  200 &lt;|
</code></pre><p>As you can see, different threads are emitting events associated to the same request. At the <strong>ActivityTracker</strong> level, an <a href="https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/ActivityTracker.cs#L285">ActivityInfo</a> is stored in an async local so that each thread has its own storage that will be propagated by the methods of the Task Parallel Library (a.k.a. TPL) from task to task. This is why the very asynchronous code of the HTTP client implementation can go back to the current activity from different threads.</p>
<p>The activities are encoded into the 16 bytes of a GUID. In fact, only the first 12 bytes are used, and the final 4 bytes contain a checksum that includes the current process ID. Since the activity identifiers in the path do, most of the time, have a small value, the encoding is using 4 bits, also known as a nibble, to encode each of them. There <a href="https://github.com/dotnet/runtime/issues/109078">was a bug</a> in <a href="https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/ActivityTracker.cs#L466">ActivityTracker.AddIdToGuid</a> that will be fixed in .NET 10. Unfortunately, the decoding code in Perfview <a href="https://github.com/microsoft/perfview/issues/2122">needs to take it into account</a> so the last activity is not lost.</p>
<h2 id="computing-the-different-durations">Computing the different durations</h2>
<p>With this infrastructure in place, it is now possible to extract the “root” activity path corresponding to an HTTP request during each phase and update the corresponding state that is used to output the details when a request ends:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">class</span> <span class="nc">HttpRequestInfo</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">HttpRequestInfo</span><span class="p">(</span><span class="n">DateTime</span> <span class="n">timestamp</span><span class="p">,</span> <span class="n">Guid</span> <span class="n">activityId</span><span class="p">,</span> <span class="kt">string</span> <span class="n">scheme</span><span class="p">,</span> <span class="kt">string</span> <span class="n">host</span><span class="p">,</span> <span class="kt">uint</span> <span class="n">port</span><span class="p">,</span> <span class="kt">string</span> <span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">Root</span> <span class="p">=</span> <span class="n">ActivityHelpers</span><span class="p">.</span><span class="n">ActivityPathString</span><span class="p">(</span><span class="n">activityId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">port</span> <span class="p">!=</span> <span class="m">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">Url</span> <span class="p">=</span> <span class="s">$&#34;{scheme}://{host}:{port}{path}&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">Url</span> <span class="p">=</span> <span class="s">$&#34;{scheme}://{host}:{path}&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="n">StartTime</span> <span class="p">=</span> <span class="n">timestamp</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">string</span> <span class="n">Root</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">string</span> <span class="n">Url</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">string</span> <span class="n">RedirectUrl</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">DateTime</span> <span class="n">StartTime</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">DateTime</span> <span class="n">ReqRespStartTime</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">double</span> <span class="n">ReqRespDuration</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">UInt32</span> <span class="n">StatusCode</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// DNS</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">DateTime</span> <span class="n">DnsStartTime</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">double</span> <span class="n">DnsDuration</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// HTTPS</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">DateTime</span> <span class="n">HandshakeStartTime</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">double</span> <span class="n">HandshakeDuration</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">string</span> <span class="n">HandshakeErrorMessage</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// socket connection</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">DateTime</span> <span class="n">SocketConnectionStartTime</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">double</span> <span class="n">SocketDuration</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>As you have seen earlier, the different phases of a request may be processed by different threads. It means that an available thread needs to be found in order to execute them. If the thread pool is busy, you can expect some wait time. This is shown in the different <strong>wait</strong> sections between the phases. They are easily computed using the timestamp of each event. Having long wait durations might be reduced by increasing the number of threads in the thread pool.</p>
<p>Feel free to use the new <strong>dotnet-http</strong> CLI tool available from nuget.org or via <strong>dotnet tool install -g dotnet-http</strong>.</p>
<p>The corresponding sources are available from my <a href="https://github.com/chrisnas/ClrEvents/tree/master/Events/dotnet-http">github repository</a> in case you would like to integrate this kind of analysis directly in your code or your monitoring pipeline.</p>
<p>Happy monitoring!</p>
]]></content:encoded></item><item><title>Digging into the undocumented .NET events from the BCL</title><link>https://chrisnas.github.io/posts/2024-10-13_digging-into-the-undocumented/</link><pubDate>Sun, 13 Oct 2024 12:12:37 +0000</pubDate><guid>https://chrisnas.github.io/posts/2024-10-13_digging-into-the-undocumented/</guid><description>This is the first episode of a new series describing how to listen to undocumented .NET events and focusing on monitoring HTTP requests</description><content:encoded><![CDATA[<hr>
<p>I’ve presented in depth the events emitted by the CLR <a href="https://github.com/chrisnas/ClrEvents">in many posts</a> to get insightful details about how the .NET runtime is working (lock contention, GC, allocations, …). Some .NET features are not implemented at the runtime level but at the Base Class Library (a.k.a. BCL) level. For example, if you are using <a href="https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?WT.mc_id=DT-MVP-5003325"><strong>HttpClient</strong></a>, you might want to measure how long it takes to get the response to your HTTP requests.</p>
<p>In this new series, I will describe how the BCL is using <a href="https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.tracing.eventsource?WT.mc_id=DT-MVP-5003325"><strong>EventSource</strong></a> to emit the events, how you can listen to them with TraceEvent; focusing on HTTP requests.</p>
<h2 id="where-do-these-events-comefrom">Where do these events come from?</h2>
<p>When you look for these BCL events in the documentation, you end up to this <a href="https://learn.microsoft.com/en-us/dotnet/core/diagnostics/well-known-event-providers#systemnethttp-provider?WT.mc_id=DT-MVP-5003325">well-known event provides in .NET</a> page and in the <a href="https://learn.microsoft.com/en-us/dotnet/core/diagnostics/well-known-event-providers#framework-libraries?WT.mc_id=DT-MVP-5003325">Framework libraries section</a>. It lists the providers with their name and the emitted events with their keyword and verbosity. However, there is no detail about their payload! For example, for the “System.Net.Http” provider, you know that the <strong>RequestStart</strong> informal event is emitted each time “an HTTP request has started”. But you don’t know the corresponding url… Let me tell you how to get these tiny details.</p>
<p>Within the BCL, the classes responsible for emitting events derive from <a href="https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.tracing.eventsource?WT.mc_id=DT-MVP-5003325"><strong>EventSource</strong></a> with the “<em>Telemetry</em>” suffix as a naming convention. They are decorated with an <strong>EventSource</strong> attribute to define their name (used as provider name by a listener) and their Guid that will be provided with each event. For example, here is <a href="https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Http/src/System/Net/Http/HttpTelemetry.cs">the declaration of the class</a> responsible for events related to sending HTTP requests:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="na">[EventSource(Name = &#34;System.Net.Http&#34;)]</span>
</span></span><span class="line"><span class="cl"><span class="kd">internal</span> <span class="kd">sealed</span> <span class="kd">partial</span> <span class="k">class</span> <span class="nc">HttpTelemetry</span> <span class="p">:</span> <span class="n">EventSource</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>If a Guid is not provided, one is automatically computed (see the <a href="https://github.com/microsoft/perfview/blob/main/src/TraceEvent/TraceEventSession.cs#L2854">corresponding code in Perfview</a>).</p>
<p>Next, public helper methods decorated with a <a href="https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.tracing.noneventattribute?view=net-8.0&amp;WT.mc_id=DT-MVP-5003325"><strong>NonEvent</strong> attribute</a> are provided to be used in the BCL code when it is needed to emit events. These methods are calling private methods decorated with an <a href="https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.tracing.eventattribute?WT.mc_id=DT-MVP-5003325"><strong>Event</strong> attribute</a> to define their unique ID and their verbosity level:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="na">[NonEvent]</span>
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">void</span> <span class="n">RequestStart</span><span class="p">(</span><span class="n">HttpRequestMessage</span> <span class="n">request</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">...</span>
</span></span><span class="line"><span class="cl">    <span class="n">RequestStart</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">request</span><span class="p">.</span><span class="n">RequestUri</span><span class="p">.</span><span class="n">Scheme</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">request</span><span class="p">.</span><span class="n">RequestUri</span><span class="p">.</span><span class="n">IdnHost</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">request</span><span class="p">.</span><span class="n">RequestUri</span><span class="p">.</span><span class="n">Port</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">request</span><span class="p">.</span><span class="n">RequestUri</span><span class="p">.</span><span class="n">PathAndQuery</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="kt">byte</span><span class="p">)</span><span class="n">request</span><span class="p">.</span><span class="n">Version</span><span class="p">.</span><span class="n">Major</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="kt">byte</span><span class="p">)</span><span class="n">request</span><span class="p">.</span><span class="n">Version</span><span class="p">.</span><span class="n">Minor</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">request</span><span class="p">.</span><span class="n">VersionPolicy</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> 
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">[Event(1, Level = EventLevel.Informational)]</span>
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">RequestStart</span><span class="p">(</span><span class="kt">string</span> <span class="n">scheme</span><span class="p">,</span> <span class="kt">string</span> <span class="n">host</span><span class="p">,</span> <span class="kt">int</span> <span class="n">port</span><span class="p">,</span> <span class="kt">string</span> <span class="n">pathAndQuery</span><span class="p">,</span> <span class="kt">byte</span> <span class="n">versionMajor</span><span class="p">,</span> <span class="kt">byte</span> <span class="n">versionMinor</span><span class="p">,</span> <span class="n">HttpVersionPolicy</span> <span class="n">versionPolicy</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">...</span>
</span></span><span class="line"><span class="cl">    <span class="n">WriteEvent</span><span class="p">(</span><span class="n">eventId</span><span class="p">:</span> <span class="m">1</span><span class="p">,</span> <span class="n">scheme</span><span class="p">,</span> <span class="n">host</span><span class="p">,</span> <span class="n">port</span><span class="p">,</span> <span class="n">pathAndQuery</span><span class="p">,</span> <span class="n">versionMajor</span><span class="p">,</span> <span class="n">versionMinor</span><span class="p">,</span> <span class="n">versionPolicy</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The different <strong>WriteEvent</strong> overloads are responsible for filling an array of <a href="https://github.com/dotnet/runtime/blob/main/src/libraries/System.Diagnostics.Tracing/ref/System.Diagnostics.Tracing.cs#L255"><strong>EventData</strong> elements</a> (each one contains a pointer to the data and its size) that is passed to <a href="https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventSource.cs#L1332"><strong>EventSource.WriteEventCore</strong></a>. This helper methods dispatches the event to EventPipe/ETW pipelines.</p>
<h2 id="document-the-undocumented">Document the undocumented</h2>
<p>Unlike CLR events, their payload is not explicitly described in a text file but, instead, you have to look at the implementation of the different <strong>WriteEvent</strong> overloads. The rest of this section provides the details of the sources I’ve looked at and the corresponding events payload.</p>
<h2 id="http">HTTP</h2>
<p>Sources: <a href="https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Http/src/System/Net/Http/HttpTelemetry.cs">https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Http/src/System/Net/Http/HttpTelemetry.cs</a>
Name: System.Net.Http
Guid: d30b5633–7ef1–5485-b4e0–94979b102068</p>
<p><img loading="lazy" src="/posts/2024-10-13_digging-into-the-undocumented/1_3M-B2oQSv9q67xj-wkb_HA.png"></p>
<h2 id="sockets">Sockets</h2>
<p>Sources: <a href="https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketsTelemetry.cs">https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketsTelemetry.cs</a></p>
<p>Name: System.Net.Sockets
Guid: d5b2e7d4-b6ec-50ae-7cde-af89427ad21f</p>
<p><img loading="lazy" src="/posts/2024-10-13_digging-into-the-undocumented/1_9NOqFY_5ZXR6toGqRE3IPg.png"></p>
<h2 id="dns">DNS</h2>
<p>Sources: <a href="https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionTelemetry.cs">https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.NameResolution/src/System/Net/NameResolutionTelemetry.cs</a></p>
<p>Name: System.Net.NameResolution
Guid: 4b326142-bfb5–5ed3–8585–7714181d14b0</p>
<p><img loading="lazy" src="/posts/2024-10-13_digging-into-the-undocumented/1_ZcftMvnViwYebVCW_q1nXg.png"></p>
<h2 id="network-security">Network Security</h2>
<p>Sources: <a href="https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Security/src/System/Net/Security/NetSecurityTelemetry.cs">https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Security/src/System/Net/Security/NetSecurityTelemetry.cs</a></p>
<p>Name: System.Net.Security
 Guid: 7beee6b1-e3fa-5ddb-34be-1404ad0e2520</p>
<p><img loading="lazy" src="/posts/2024-10-13_digging-into-the-undocumented/1_aNAxTUuPK3kNRcCbUnwN9A.png"></p>
<p>The next episode will describe how to listen to these events and extract their payload in C#.</p>
]]></content:encoded></item><item><title>Trigger your GCs with dotnet-fullgc!</title><link>https://chrisnas.github.io/posts/2024-05-22_trigger-your-gcs-with/</link><pubDate>Wed, 22 May 2024 18:03:17 +0000</pubDate><guid>https://chrisnas.github.io/posts/2024-05-22_trigger-your-gcs-with/</guid><description>This post explains how I wrote a CLI tool that triggers a GC in any .NET application</description><content:encoded><![CDATA[<hr>
<h2 id="introduction">Introduction</h2>
<p>If you have read Microsoft documentation, you probably know that it is not recommended to trigger a garbage collection in your application code. However, in some troubleshooting cases, you might want to trigger a GC. For example, you don’t want to wait for a full gen2 compacting GC to figure out if your application is really leaking memory. For web applications, you can imagine having a hidden HTTP end point that simply call <strong>GC.Collect</strong>. What if you could simply call a command line tool to trigger a GC in any .NET application? This is exactly what my new dotnet-fullgc CLI tool is doing!</p>
<h2 id="this-is-theway">This Is The Way</h2>
<p>If you have read a few of my past posts about <a href="/posts/2022-09-18_net-diagnostic-ipc-protocol/">the .NET diagnostics mechanisms</a>, you know that you can send commands to another .NET process via EventPipes. Well… there is no explicit command to trigger a GC.</p>
<p>I have also explained how you could listen to events emitted by the CLR by enabling a provider with a set of keywords with a verbosity corresponding to the events you are interested in. This is how dotnet-trace and Perfview are collecting these events. If you want to trigger a GC, you simply need to enable the <strong>Microsoft-Windows-DotNETRuntime</strong> provider with the <strong>GCHeapCollect</strong> (= 0x800000L) keyword and an informal verbosity. Yes: it is as simple as that, and it is also working for .NET Framework!</p>
<p>So, you could trigger a GC with dotnet-trace via the following command line:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">dotnet trace collect -p &lt;process id&gt; - providers Microsoft-Windows-DotNETRuntime:0x800000:4 - duration 00:00:01
</span></span></code></pre></td></tr></table>
</div>
</div><p>However, a .nettrace file would be generated and it is not possible to pass parameters (more on this later).</p>
<p>The rest of this post shows the C# code to obtain the same result. With <strong>Microsoft.Diagnostics.NETCore.Client</strong> and <strong>TraceEvent</strong>, you create a <strong>DiagnosticsClient</strong> with the ID of the process you are interested in:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">client</span> <span class="p">=</span> <span class="k">new</span> <span class="n">DiagnosticsClient</span><span class="p">(</span><span class="n">processId</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The next step is to start an EventPipe session with the right provider, keyword and verbosity:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">providers</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">EventPipeProvider</span><span class="p">&gt;()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">new</span> <span class="n">EventPipeProvider</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;Microsoft-Windows-DotNETRuntime&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">EventLevel</span><span class="p">.</span><span class="n">Informational</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="kt">long</span><span class="p">)</span><span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">GCHeapCollect</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">Arguments</span>  <span class="c1">// more on this later</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="p">(</span><span class="kt">var</span> <span class="n">session</span> <span class="p">=</span> <span class="n">client</span><span class="p">.</span><span class="n">StartEventPipeSession</span><span class="p">(</span><span class="n">providers</span><span class="p">,</span> <span class="kc">false</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The source must be processed in another thread to avoid blocking the main thread:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Task</span><span class="w"> </span><span class="n">streamTask</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Task</span><span class="p">.</span><span class="na">Run</span><span class="p">(()</span><span class="w"> </span><span class="o">=&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// without source to process, session.Stop() will not return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">var</span><span class="w"> </span><span class="n">source</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">EventPipeEventSource</span><span class="p">(</span><span class="n">session</span><span class="p">.</span><span class="na">EventStream</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">source</span><span class="p">.</span><span class="na">Process</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">});</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>The question to answer is how to stop the session: just create another task that waits for a second before stopping the session to exit from the <strong>Process</strong> call:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Task</span><span class="w"> </span><span class="n">inputTask</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Task</span><span class="p">.</span><span class="na">Run</span><span class="p">(()</span><span class="w"> </span><span class="o">=&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">Thread</span><span class="p">.</span><span class="na">Sleep</span><span class="p">(</span><span class="n">1000</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">session</span><span class="p">.</span><span class="na">Stop</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Task</span><span class="p">.</span><span class="na">WaitAny</span><span class="p">(</span><span class="n">streamTask</span><span class="p">,</span><span class="w"> </span><span class="n">inputTask</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>That’s all!</p>
<h2 id="pitfalls">Pitfalls</h2>
<p>Unfortunately, I faced a couple of issue during the implementation of the tool.</p>
<h2 id="what-is-yournumber">What is your number?</h2>
<p>If you look at the TraceEvent implementation, you could imagine that is it possible to pass a GC ID as a parameter to the .NET provider:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="c1">//</span>
</span></span><span class="line"><span class="cl"><span class="c1">// Summary:</span>
</span></span><span class="line"><span class="cl"><span class="c1">//     Triggers a GC. Can pass a 64 bit value that will be logged with the GC Start</span>
</span></span><span class="line"><span class="cl"><span class="c1">//     event so you know which GC you actually triggered.</span>
</span></span><span class="line"><span class="cl"><span class="n">GCHeapCollect</span> <span class="p">=</span> <span class="m">0x800000L</span><span class="p">,</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Unfortunately, this is not supported and the reasons are explained below.</p>
<p>If you check the .NET source code and look at <a href="https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/eventtrace.cpp#L2420">EtwCallbackCommon()</a>, you can indeed see that a numeric ID can be passed to <strong>ETW::GCLog::ForceGC(l64ClientSequenceNumber)</strong> and passed as ID in <strong>GCStart</strong> event instead of the one incrementally increased collection after collection.</p>
<p>At the diagnostics client level, you have the opportunity to pass a dictionary of key/value string pairs. This dictionary is used when defining the <strong>EventPipeProvider</strong> to be enabled:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">&gt;</span> <span class="n">arguments</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">&gt;();</span>
</span></span><span class="line"><span class="cl"><span class="n">arguments</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="s">&#34;Id&#34;</span><span class="p">,</span> <span class="s">&#34;42&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">providers</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">EventPipeProvider</span><span class="p">&gt;()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">new</span> <span class="n">EventPipeProvider</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;Microsoft-Windows-DotNETRuntime&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">EventLevel</span><span class="p">.</span><span class="n">Informational</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="kt">long</span><span class="p">)</span><span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">GCHeapCollect</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">arguments</span>
</span></span><span class="line"><span class="cl">        <span class="p">),</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <a href="https://github.com/dotnet/diagnostics/blob/main/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/EventPipeProvider.cs#L63">diagnostics client transforms</a> the dictionary into a string with key=value pairs separated by ‘;’ such as “Id=42;AnotherId=AnotherValue;…” and serializes it as is in the payload when sending a CollectTraces command.</p>
<p>The question is what identifier is expected by the CLR? To answer this question, you need to look at the <a href="https://github.com/dotnet/runtime/blob/main/src/native/eventpipe/ep-provider.c#L381">code of provider_invoke_callback</a>. The “key=value;…” string is stored into a buffer and each = and ; characters are transformed into \0. So, “Id=42” is transformed into Id\042\0.</p>
<p>The next step is done by <strong>ep_event_filter_desc_init()</strong> that uses that buffer to fill up the 3 fields of an <strong>EventFilterDescriptor</strong>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">uint64_t</span> <span class="n">ptr</span> <span class="o">=</span> <span class="n">address</span> <span class="n">of</span> <span class="n">the</span> <span class="n">buffer</span>
</span></span><span class="line"><span class="cl"><span class="kt">uint32_t</span> <span class="n">size</span> <span class="o">=</span> <span class="n">size</span> <span class="n">of</span> <span class="n">the</span> <span class="n">buffer</span> <span class="p">(</span><span class="o">=</span><span class="mi">6</span> <span class="k">for</span> <span class="n">id</span><span class="err">\</span><span class="mo">042</span><span class="err">\</span><span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kt">uint32_t</span> <span class="n">type</span> <span class="o">=</span> <span class="mi">0</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>And finally, EtwCallbackCommon receives the filterdata and tries the following to get the collection id:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">PEVENT_FILTER_DESCRIPTOR</span> <span class="n">FilterData</span> <span class="o">=</span> <span class="p">(</span><span class="n">PEVENT_FILTER_DESCRIPTOR</span><span class="p">)</span><span class="n">pFilterData</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">((</span><span class="n">FilterData</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">   <span class="p">(</span><span class="n">FilterData</span><span class="o">-&gt;</span><span class="n">Type</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">   <span class="p">(</span><span class="n">FilterData</span><span class="o">-&gt;</span><span class="n">Size</span> <span class="o">==</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">l64ClientSequenceNumber</span><span class="p">)))</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">l64ClientSequenceNumber</span> <span class="o">=</span> <span class="o">*</span><span class="p">(</span><span class="n">LONGLONG</span> <span class="o">*</span><span class="p">)</span> <span class="p">(</span><span class="n">FilterData</span><span class="o">-&gt;</span><span class="n">Ptr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>As you can see, it will fail because:</p>
<ul>
<li>the received type value is 0</li>
<li>the received size is not the size of a 64 bit number</li>
<li>the ptr field does not point to the value as 64 bit number (but as a string)</li>
</ul>
<p>An <a href="https://github.com/dotnet/runtime/issues/102572">issue has been filed</a> with a possible fix.</p>
<h2 id="only-one-collection-please">Only one collection please!</h2>
<p>When I test my dotnet-fullgc with dotnet-gcstats, I always see 2 collections!</p>
<p><img loading="lazy" src="/posts/2024-05-22_trigger-your-gcs-with/1_OjENjdzCIyyrPNUiGrQEVw.png"></p>
<p>We investigated with my colleague <a href="https://x.com/KooKiz">Kevin Gosse</a> and he <a href="https://github.com/dotnet/runtime/issues/99487">created an issue</a> for that. The <strong>EtwCallback()</strong> function is called whenever a session is enabled or disabled. Unfortunately, the call to <strong>ForceGC</strong> is made in both cases: so, when the session is stopped, a second garbage collection is triggered.</p>
<h2 id="next-steps">Next steps</h2>
<p>Feel free to install dotnet-fullgc on your machine with <strong>dotnet tool install -g dotnet-fullgc</strong>.</p>
<p>Next, use <strong>dotnet fullgc <process id></strong> to trigger two gen2 full garbage collections in your running .NET processes.</p>
]]></content:encoded></item><item><title>CLR events: go for the nettrace file format!</title><link>https://chrisnas.github.io/posts/2022-10-23_clr-events-go-for/</link><pubDate>Sun, 23 Oct 2022 16:55:52 +0000</pubDate><guid>https://chrisnas.github.io/posts/2022-10-23_clr-events-go-for/</guid><description>Let’s see how to listen to CLR event through EventPipe in C++. This episode explains how to start/stop a session with a running .NET app.</description><content:encoded><![CDATA[<hr>
<p>As shown in the <a href="/posts/2022-09-18_net-diagnostic-ipc-protocol/">previous post</a>, the processing of <strong>ProcessInfo</strong> diagnostic commands is easy because you send a request and read the different fields from the response. This is different if you want to receive events from the CLR via EventPipe. In C#, the <a href="https://www.nuget.org/packages/Microsoft.Diagnostics.Tracing.TraceEvent/">TraceEvent nuget package</a> wraps everything under a nice event handler based model as shown in many of my <a href="/posts/2018-07-26_grab-etw-session-providers/">previous posts</a>.</p>
<p>Behind the scene, a <strong>StartSession</strong> command is sent (more details about the parameters later) and the response contains the numeric ID of the session. Then, the events will be read from the IPC channel as a binary stream of data with the <a href="https://github.com/microsoft/perfview/blob/main/src/TraceEvent/EventPipe/EventPipeFormat.md">“nettrace“ file format</a>. The collection ends when the <strong>StopTracing</strong> command is sent.</p>
<p>The source code is available from <a href="https://github.com/chrisnas/ClrEvents/tree/master/Events/NativeEventListener">my github repository</a>.</p>
<h2 id="hidding-the-transport-layer-iipcendoint">Hidding the transport layer: IIpcEndoint</h2>
<p>Unlike the previous post, to send the command and read the response back from the CLR , I’m wrapping the transport layer with the <strong>IIpcEndpoint</strong> interface:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">IIpcEndpoint</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">virtual</span> <span class="kt">bool</span> <span class="n">Write</span><span class="p">(</span><span class="n">LPCVOID</span> <span class="n">buffer</span><span class="p">,</span> <span class="n">DWORD</span> <span class="n">bufferSize</span><span class="p">,</span> <span class="n">DWORD</span><span class="o">*</span> <span class="n">writtenBytes</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">virtual</span> <span class="kt">bool</span> <span class="nf">Read</span><span class="p">(</span><span class="n">LPVOID</span> <span class="n">buffer</span><span class="p">,</span> <span class="n">DWORD</span> <span class="n">bufferSize</span><span class="p">,</span> <span class="n">DWORD</span><span class="o">*</span> <span class="n">readBytes</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">virtual</span> <span class="kt">bool</span> <span class="nf">ReadByte</span><span class="p">(</span><span class="kt">uint8_t</span><span class="o">&amp;</span> <span class="n">byte</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">virtual</span> <span class="kt">bool</span> <span class="nf">ReadWord</span><span class="p">(</span><span class="kt">uint16_t</span><span class="o">&amp;</span> <span class="n">word</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">virtual</span> <span class="kt">bool</span> <span class="nf">ReadDWord</span><span class="p">(</span><span class="kt">uint32_t</span><span class="o">&amp;</span> <span class="n">dword</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">virtual</span> <span class="kt">bool</span> <span class="nf">ReadLong</span><span class="p">(</span><span class="kt">uint64_t</span><span class="o">&amp;</span> <span class="n">ulong</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">virtual</span> <span class="kt">bool</span> <span class="nf">Close</span><span class="p">()</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">virtual</span> <span class="o">~</span><span class="n">IIpcEndpoint</span><span class="p">()</span> <span class="o">=</span> <span class="k">default</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>It abstracts the write and read accesses to the underlying transport layer. In addition, the base class accepts a “recorder” that allows me to store what is received from the CLR into any kind of storage (today only a file-based recorder that helped a lot to reproduce specific situations without the need to have a running process to connect to):</p>
<p><img loading="lazy" src="/posts/2022-10-23_clr-events-go-for/1_ugj8AdZBJZv4qyi-VfTeog.png"></p>
<p>The <strong>PidEndpoint</strong> class accepts the process id of the running .NET application to monitor its CLR events and an optional recorder implementing the <strong>IIpcRecorder</strong> interface. The <strong>Create</strong> static factory implementation creates the expected named pipe on Windows (or the domain socket on Linux) and stores the handle into its <strong>_handle</strong> field:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">PidEndpoint</span><span class="o">*</span> <span class="n">PidEndpoint</span><span class="o">::</span><span class="n">CreateForWindows</span><span class="p">(</span><span class="kt">int</span> <span class="n">pid</span><span class="p">,</span> <span class="n">IIpcRecorder</span><span class="o">*</span> <span class="n">pRecorder</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">PidEndpoint</span><span class="o">*</span> <span class="n">pEndpoint</span> <span class="o">=</span> <span class="k">new</span> <span class="n">PidEndpoint</span><span class="p">(</span><span class="n">pRecorder</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// build the pipe name as described in the protocol
</span></span></span><span class="line"><span class="cl">    <span class="kt">wchar_t</span> <span class="n">pszPipeName</span><span class="p">[</span><span class="mi">256</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">nCharactersWritten</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">nCharactersWritten</span> <span class="o">=</span> <span class="n">wsprintf</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">pszPipeName</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="sa">L</span><span class="s">&#34;</span><span class="se">\\\\</span><span class="s">.</span><span class="se">\\</span><span class="s">pipe</span><span class="se">\\</span><span class="s">dotnet-diagnostic-%d&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">pid</span>
</span></span><span class="line"><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// check that CLR has created the diagnostics named pipe
</span></span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!::</span><span class="n">WaitNamedPipe</span><span class="p">(</span><span class="n">pszPipeName</span><span class="p">,</span> <span class="mi">200</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">auto</span> <span class="n">error</span> <span class="o">=</span> <span class="o">::</span><span class="n">GetLastError</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;Diagnostics named pipe is not available for process #&#34;</span> <span class="o">&lt;&lt;</span> <span class="n">pid</span> <span class="o">&lt;&lt;</span> <span class="s">&#34; (&#34;</span> <span class="o">&lt;&lt;</span> <span class="n">error</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;)&#34;</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="k">nullptr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// connect to the named pipe
</span></span></span><span class="line"><span class="cl">    <span class="n">HANDLE</span> <span class="n">hPipe</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">hPipe</span> <span class="o">=</span> <span class="o">::</span><span class="n">CreateFile</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">pszPipeName</span><span class="p">,</span>    <span class="c1">// pipe name
</span></span></span><span class="line"><span class="cl">        <span class="n">GENERIC_READ</span> <span class="o">|</span>  <span class="c1">// read and write access
</span></span></span><span class="line"><span class="cl">        <span class="n">GENERIC_WRITE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="mi">0</span><span class="p">,</span>              <span class="c1">// no sharing
</span></span></span><span class="line"><span class="cl">        <span class="nb">NULL</span><span class="p">,</span>           <span class="c1">// default security attributes
</span></span></span><span class="line"><span class="cl">        <span class="n">OPEN_EXISTING</span><span class="p">,</span>  <span class="c1">// opens existing pipe
</span></span></span><span class="line"><span class="cl">        <span class="mi">0</span><span class="p">,</span>              <span class="c1">// default attributes
</span></span></span><span class="line"><span class="cl">        <span class="nb">NULL</span><span class="p">);</span>          <span class="c1">// no template file
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">hPipe</span> <span class="o">==</span> <span class="n">INVALID_HANDLE_VALUE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;Impossible to connect to &#34;</span> <span class="o">&lt;&lt;</span> <span class="n">pszPipeName</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="k">nullptr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">pEndpoint</span><span class="o">-&gt;</span><span class="n">_handle</span> <span class="o">=</span> <span class="n">hPipe</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">pEndpoint</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The next step is to open a tracing session by sending the <strong>StartSession</strong> command.</p>
<h2 id="the-trace-diagnostic-commands">The Trace diagnostic commands</h2>
<p>Following the same object model provided by the <a href="https://www.nuget.org/packages/Microsoft.Diagnostics.NETCore.Client/">Microsoft.Diagnostics.NETCore.Client nuget</a>, my <strong>DiagnosticsClient</strong> class hides the transport layer. It also exposes high level functions such as <strong>OpenEventPipeSession</strong> to initiate a trace event session with the CLR:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="n">EventPipeSession</span><span class="o">*</span> <span class="nf">OpenEventPipeSession</span><span class="p">(</span><span class="kt">uint64_t</span> <span class="n">keywords</span><span class="p">,</span> <span class="n">EventVerbosityLevel</span> <span class="n">verbosity</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>If you remember from <strong>TraceEvent</strong>, you need a few parameters to create a session:</p>
<ul>
<li>size of circular buffers used by the CLR to cache events (same as Perfview, use 16 MB as default)</li>
<li>netttrace format (i.e. value of 1)</li>
<li>if rundown events are needed</li>
<li>a list of providers (“Microsoft-Windows-DotNETRuntime” for the CLR in my case)</li>
<li>keywords</li>
<li>verbosity level</li>
<li>possible arguments (none here)</li>
</ul>
<p>Here is the corresponding C++ description of the command type:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">const</span> <span class="kt">uint8_t</span> <span class="n">DotnetProviderMagicLength</span> <span class="o">=</span> <span class="mi">32</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">MagicProvider</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">wchar_t</span> <span class="n">Magic</span><span class="p">[</span><span class="n">DotnetProviderMagicLength</span><span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// 32 wchar_t (including \0)
</span></span></span><span class="line"><span class="cl"><span class="k">const</span> <span class="n">MagicProvider</span> <span class="n">DotnetProviderMagic</span> <span class="o">=</span> <span class="p">{</span> <span class="sa">L</span><span class="s">&#34;Microsoft-Windows-DotNETRuntime&#34;</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">const</span> <span class="kt">uint32_t</span> <span class="n">CircularBufferMBSize</span> <span class="o">=</span> <span class="mi">16</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">const</span> <span class="kt">uint32_t</span> <span class="n">NetTraceFormat</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cp">#pragma pack(1)
</span></span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="nl">StartSessionMessage</span> <span class="p">:</span> <span class="n">public</span> <span class="n">IpcHeader</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">CircularBufferMB</span><span class="p">;</span>  <span class="c1">// 16 MB
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">Format</span><span class="p">;</span>            <span class="c1">// 1 for NetTrace format
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint8_t</span> <span class="n">RequestRundown</span><span class="p">;</span>     <span class="c1">// 0 because don&#39;t want rundown
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// array of provider configuration
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">ProviderCount</span><span class="p">;</span>     <span class="c1">// 1 only: Microsoft-Windows-DotNETRuntime
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint64_t</span> <span class="n">Keywords</span><span class="p">;</span>          <span class="c1">// from EventKeyword
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">Verbosity</span><span class="p">;</span>         <span class="c1">// from EventPipeEventLevel
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">ProviderStringLen</span><span class="p">;</span> <span class="c1">// number of UTF16 characters = 32 (including last \0)
</span></span></span><span class="line"><span class="cl">    <span class="k">union</span>                       <span class="c1">// dotnet provider name
</span></span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">MagicProvider</span> <span class="n">_magic</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kt">uint8_t</span> <span class="n">Provider</span><span class="p">[</span><span class="mi">2</span> <span class="o">*</span> <span class="n">DotnetProviderMagicLength</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">Arguments</span><span class="p">;</span>         <span class="c1">// 0 for empty string (no argument)
</span></span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The code to fill up the command is straightforward:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">StartSessionMessage</span><span class="o">*</span> <span class="nf">CreateStartSessionMessage</span><span class="p">(</span><span class="kt">uint64_t</span> <span class="n">keywords</span><span class="p">,</span> <span class="n">EventVerbosityLevel</span> <span class="n">verbosity</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">auto</span> <span class="n">message</span> <span class="o">=</span> <span class="k">new</span> <span class="n">StartSessionMessage</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="o">::</span><span class="n">ZeroMemory</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">message</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="n">memcpy</span><span class="p">(</span><span class="n">message</span><span class="o">-&gt;</span><span class="n">Magic</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">DotnetIpcMagic_V1</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">message</span><span class="o">-&gt;</span><span class="n">Magic</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span><span class="o">-&gt;</span><span class="n">Size</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">StartSessionMessage</span><span class="p">);</span>  
</span></span><span class="line"><span class="cl">    <span class="n">message</span><span class="o">-&gt;</span><span class="n">CommandSet</span> <span class="o">=</span> <span class="p">(</span><span class="kt">uint8_t</span><span class="p">)</span><span class="n">DiagnosticServerCommandSet</span><span class="o">::</span><span class="n">EventPipe</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span><span class="o">-&gt;</span><span class="n">CommandId</span> <span class="o">=</span> <span class="p">(</span><span class="kt">uint8_t</span><span class="p">)</span><span class="n">EventPipeCommandId</span><span class="o">::</span><span class="n">CollectTracing2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span><span class="o">-&gt;</span><span class="n">Reserved</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span><span class="o">-&gt;</span><span class="n">CircularBufferMB</span> <span class="o">=</span> <span class="n">CircularBufferMBSize</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span><span class="o">-&gt;</span><span class="n">Format</span> <span class="o">=</span> <span class="n">NetTraceFormat</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span><span class="o">-&gt;</span><span class="n">RequestRundown</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span><span class="o">-&gt;</span><span class="n">ProviderCount</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span><span class="o">-&gt;</span><span class="n">Keywords</span> <span class="o">=</span> <span class="p">(</span><span class="kt">uint64_t</span><span class="p">)</span><span class="n">keywords</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span><span class="o">-&gt;</span><span class="n">Verbosity</span> <span class="o">=</span> <span class="p">(</span><span class="kt">uint32_t</span><span class="p">)</span><span class="n">verbosity</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span><span class="o">-&gt;</span><span class="n">ProviderStringLen</span> <span class="o">=</span> <span class="n">DotnetProviderMagicLength</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">memcpy</span><span class="p">(</span><span class="n">message</span><span class="o">-&gt;</span><span class="n">Provider</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">DotnetProviderMagic</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">message</span><span class="o">-&gt;</span><span class="n">Provider</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span><span class="o">-&gt;</span><span class="n">Arguments</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">message</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The provider list is defined with the <strong>ProviderCount</strong> field and string containing the list (only one here) follows the <strong>Verbosity</strong> field. To start the session, it is needed to send the <strong>StartSession</strong> message and read the session id from the response:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">bool</span> <span class="n">EventPipeStartRequest</span><span class="o">::</span><span class="n">Process</span><span class="p">(</span><span class="n">IIpcEndpoint</span><span class="o">*</span> <span class="n">pEndpoint</span><span class="p">,</span> <span class="kt">uint64_t</span> <span class="n">keywords</span><span class="p">,</span> <span class="n">EventVerbosityLevel</span> <span class="n">verbosity</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// send an StartSessionMessage and parse the response
</span></span></span><span class="line"><span class="cl">    <span class="n">StartSessionMessage</span><span class="o">*</span> <span class="n">pMessage</span> <span class="o">=</span> <span class="n">CreateStartSessionMessage</span><span class="p">(</span><span class="n">keywords</span><span class="p">,</span> <span class="n">verbosity</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">DWORD</span> <span class="n">writtenBytes</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">pEndpoint</span><span class="o">-&gt;</span><span class="n">Write</span><span class="p">(</span><span class="n">pMessage</span><span class="p">,</span> <span class="n">pMessage</span><span class="o">-&gt;</span><span class="n">Size</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">writtenBytes</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// analyze the response
</span></span></span><span class="line"><span class="cl">    <span class="n">IpcHeader</span> <span class="n">response</span> <span class="o">=</span> <span class="p">{};</span>
</span></span><span class="line"><span class="cl">    <span class="n">DWORD</span> <span class="n">bytesReadCount</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">pEndpoint</span><span class="o">-&gt;</span><span class="n">Read</span><span class="p">(</span><span class="o">&amp;</span><span class="n">response</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">response</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">bytesReadCount</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="n">CommandId</span> <span class="o">!=</span> <span class="p">(</span><span class="kt">uint8_t</span><span class="p">)</span><span class="n">DiagnosticServerResponseId</span><span class="o">::</span><span class="n">OK</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// get the session ID from the payload
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint16_t</span> <span class="n">payloadSize</span> <span class="o">=</span> <span class="n">response</span><span class="p">.</span><span class="n">Size</span> <span class="o">-</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">response</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">payloadSize</span> <span class="o">&lt;</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">uint64_t</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">pEndpoint</span><span class="o">-&gt;</span><span class="n">ReadLong</span><span class="p">(</span><span class="n">SessionId</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Once the <strong>StartSession</strong> command has been sent, the events corresponding to the given provider/keywords/verbosity (here the CLR runtime/gc+exception+contention/verbose)</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="k">auto</span> <span class="n">pSession</span> <span class="o">=</span> <span class="n">pClient</span><span class="o">-&gt;</span><span class="n">OpenEventPipeSession</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">EventKeyword</span><span class="o">::</span><span class="n">gc</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="n">EventKeyword</span><span class="o">::</span><span class="n">exception</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="n">EventKeyword</span><span class="o">::</span><span class="n">contention</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">EventVerbosityLevel</span><span class="o">::</span><span class="n">Verbose</span>  <span class="c1">// required for AllocationTick
</span></span></span><span class="line"><span class="cl">        <span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>will be read from the event pipe. Since this action will be synchronous, it is recommended to dedicate a thread to read and process the events:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">pSession</span> <span class="o">!=</span> <span class="k">nullptr</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">DWORD</span> <span class="n">tid</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">auto</span> <span class="n">hThread</span> <span class="o">=</span> <span class="o">::</span><span class="n">CreateThread</span><span class="p">(</span><span class="k">nullptr</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">ListenToEvents</span><span class="p">,</span> <span class="n">pSession</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">tid</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;Press ENTER to stop listening to events...</span><span class="se">\n\n</span><span class="s">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">line</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">std</span><span class="o">::</span><span class="n">getline</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">cin</span><span class="p">,</span> <span class="n">line</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;Stopping session</span><span class="se">\n\n</span><span class="s">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">pSession</span><span class="o">-&gt;</span><span class="n">Stop</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;Session stopped</span><span class="se">\n\n</span><span class="s">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// test if it works
</span></span></span><span class="line"><span class="cl">        <span class="o">::</span><span class="n">Sleep</span><span class="p">(</span><span class="mi">1000</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="o">::</span><span class="n">CloseHandle</span><span class="p">(</span><span class="n">hThread</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <strong>ListenToEvents</strong> callback executed by the new thread is “simply” listening to the event pipe of the session:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">DWORD</span> <span class="n">WINAPI</span> <span class="nf">ListenToEvents</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">pParam</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">EventPipeSession</span><span class="o">*</span> <span class="n">pSession</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="n">EventPipeSession</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">pParam</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">pSession</span><span class="o">-&gt;</span><span class="n">Listen</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Before describing how to read the events, it is important to understand how to stop the flow. First, inside the <strong>EventPipeSession</strong>, the internal loop that reads events needs to exit thanks to the <strong>_stopRequested</strong> boolean:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">bool</span> <span class="n">EventPipeSession</span><span class="o">::</span><span class="n">Stop</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">_stopRequested</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">_pid</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// it is neeeded to use a different ipc connection to stop the Session
</span></span></span><span class="line"><span class="cl">    <span class="n">DiagnosticsClient</span><span class="o">*</span> <span class="n">pStopClient</span> <span class="o">=</span> <span class="n">DiagnosticsClient</span><span class="o">::</span><span class="n">Create</span><span class="p">(</span><span class="n">_pid</span><span class="p">,</span> <span class="k">nullptr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">pStopClient</span><span class="o">-&gt;</span><span class="n">StopEventPipeSession</span><span class="p">(</span><span class="n">SessionId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">delete</span> <span class="n">pStopClient</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">true</span><span class="p">;</span> 
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>In addition, a message with <strong>StopTracing</strong> command id from the <strong>EventPipe</strong> command set needs to be sent to tell the CLR to stop sending the events. This message must be sent through a different IPC channel (hence the <strong>pStopClient</strong> variable used in the previous code. The <strong>StopEventPipeSession</strong> helper function uses the <strong>EventPipeStopRequest</strong> wrapper:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">bool</span> <span class="n">DiagnosticsClient</span><span class="o">::</span><span class="n">StopEventPipeSession</span><span class="p">(</span><span class="kt">uint64_t</span> <span class="n">sessionId</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">EventPipeStopRequest</span> <span class="n">request</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">request</span><span class="p">.</span><span class="n">Process</span><span class="p">(</span><span class="n">_pEndpoint</span><span class="p">,</span> <span class="n">sessionId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <strong>StopSession</strong> command accepts the session ID as single parameter:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#pragma pack(1)
</span></span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="nl">StopSessionMessage</span> <span class="p">:</span> <span class="n">public</span> <span class="n">IpcHeader</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint64_t</span> <span class="n">SessionId</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The processing of the stop request is to create such a message and send it through the IPC channel:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">StopSessionMessage</span><span class="o">*</span> <span class="nf">CreateStopMessage</span><span class="p">(</span><span class="kt">uint64_t</span> <span class="n">sessionId</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">StopSessionMessage</span><span class="o">*</span> <span class="n">message</span> <span class="o">=</span> <span class="k">new</span> <span class="n">StopSessionMessage</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="o">::</span><span class="n">ZeroMemory</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">message</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="n">memcpy</span><span class="p">(</span><span class="n">message</span><span class="o">-&gt;</span><span class="n">Magic</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">DotnetIpcMagic_V1</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">message</span><span class="o">-&gt;</span><span class="n">Magic</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span><span class="o">-&gt;</span><span class="n">Size</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">StopSessionMessage</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span><span class="o">-&gt;</span><span class="n">CommandSet</span> <span class="o">=</span> <span class="p">(</span><span class="kt">uint8_t</span><span class="p">)</span><span class="n">DiagnosticServerCommandSet</span><span class="o">::</span><span class="n">EventPipe</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span><span class="o">-&gt;</span><span class="n">CommandId</span> <span class="o">=</span> <span class="p">(</span><span class="kt">uint8_t</span><span class="p">)</span><span class="n">EventPipeCommandId</span><span class="o">::</span><span class="n">StopTracing</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span><span class="o">-&gt;</span><span class="n">Reserved</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span><span class="o">-&gt;</span><span class="n">SessionId</span> <span class="o">=</span> <span class="n">sessionId</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">message</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">bool</span> <span class="n">EventPipeStopRequest</span><span class="o">::</span><span class="n">Process</span><span class="p">(</span><span class="n">IIpcEndpoint</span><span class="o">*</span> <span class="n">pEndpoint</span><span class="p">,</span> <span class="kt">uint64_t</span> <span class="n">sessionId</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">StopSessionMessage</span><span class="o">*</span> <span class="n">pMessage</span> <span class="o">=</span> <span class="n">CreateStopMessage</span><span class="p">(</span><span class="n">sessionId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">DWORD</span> <span class="n">writtenBytes</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">pEndpoint</span><span class="o">-&gt;</span><span class="n">Write</span><span class="p">(</span><span class="n">pMessage</span><span class="p">,</span> <span class="n">pMessage</span><span class="o">-&gt;</span><span class="n">Size</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">writtenBytes</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">Error</span> <span class="o">=</span> <span class="o">::</span><span class="n">GetLastError</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;Error while sending EventPipe Stop message to the CLR: 0x&#34;</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">hex</span> <span class="o">&lt;&lt;</span> <span class="n">Error</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">dec</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">delete</span> <span class="n">pMessage</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="k">delete</span> <span class="n">pMessage</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="p">...</span> <span class="c1">// handle the response 
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>When the stop command is received by the CLR, the remaining “data” (more on this in the next episode) is sent through the first IPC channel before being closed. This is how the code knows that the session can stop listening to the EventPipe.</p>
<p>The next episode will start to parse the nettrace stream of events.</p>
<h2 id="resources">Resources</h2>
<ul>
<li><a href="/posts/2022-07-28_digging-into-the-clr/">Episode 1</a> — <em>Digging into the CLR Diagnostics IPC Protocol in C#</em></li>
<li><a href="/posts/2022-09-18_net-diagnostic-ipc-protocol/">Episode 2</a> — <em>.NET Diagnostic IPC protocol: the C++ way</em></li>
<li><a href="https://github.com/chrisnas/ClrEvents/tree/master/Events/NativeEventListener">Source code</a> for the C++ implementation of CLR events listener</li>
<li>Diagnostics IPC protocol <a href="https://github.com/dotnet/diagnostics/blob/main/documentation/design-docs/ipc-protocol.md">documentation</a></li>
<li>Microsoft.Diagnostics.NETCore.Client <a href="https://github.com/dotnet/diagnostics/tree/main/src/Microsoft.Diagnostics.NETCore.Client">source code</a></li>
</ul>
]]></content:encoded></item><item><title>Digging into the CLR Diagnostics IPC Protocol in C#</title><link>https://chrisnas.github.io/posts/2022-07-28_digging-into-the-clr/</link><pubDate>Thu, 28 Jul 2022 08:59:40 +0000</pubDate><guid>https://chrisnas.github.io/posts/2022-07-28_digging-into-the-clr/</guid><description>Learn how to directly connect to the .NET CLR and send diagnostics commands</description><content:encoded><![CDATA[<hr>
<h2 id="introduction">Introduction</h2>
<p>As I explained during <a href="https://www.youtube.com/watch?v=Jpoy3O6x-wM&amp;t=1530s">a DotNext conference session</a>, the .NET CLI tools such as <strong>dotnet-trace</strong>, <strong>dotnet-counter</strong> or <strong>dotnet-dump</strong> are communicating with the CLR thanks to Named Pipe on Windows and Domain Socket on Linux. Within the CLR, a <a href="https://github.com/dotnet/coreclr/blob/release/3.1/src/vm/diagnosticserver.cpp#24">diagnostic server thread</a> is responsible for answering requests. A communication protocol allows a tool to send <em>commands</em> and expect <em>responses</em>. This Diagnostic IPC Protocol is <a href="https://github.com/dotnet/diagnostics/blob/main/documentation/design-docs/ipc-protocol.md">pretty well documented</a> in the dotnet Diagnostics repository.</p>
<p>Before going into the protocol details, here is a list of the available commands and their effect:</p>
<p><img loading="lazy" src="/posts/2022-07-28_digging-into-the-clr/1_0LmzdTyId2oJIPkSac1EAA.png"></p>
<p>This series will detail how to communicate with a CLR using this protocol both in C# and in C++. Also note that processing CLR events thanks to EventPipe will also be covered.</p>
<h2 id="make-it-simple-use-microsoftdiagnosticsnetcoreclient-nuget">Make it simple: use Microsoft.Diagnostics.NETCore.Client nuget</h2>
<p>With <a href="https://www.nuget.org/packages/Microsoft.Diagnostics.Tracing.TraceEvent">TraceEvent nugget package</a>, Microsoft provided a great library to <a href="/posts/2018-07-26_grab-etw-session-providers/">easily listen to CLR events</a> in C#. If you want to easily send CLR diagnostic IPC protocol commands to a CLR in a .NET process, <a href="https://www.nuget.org/packages/Microsoft.Diagnostics.NETCore.Client/">Microsoft.Diagnostics.NETCore.Client nuget package</a> is for you. Remember that EventPipe is implemented by .NET Core and .NET 5+ (so no .NET Framework support)</p>
<p>The Swiss knife class <strong>DiagnosticsClient</strong> gives you access to most of the commands plus a way to list .NET processes as a bonus:</p>
<p><img loading="lazy" src="/posts/2022-07-28_digging-into-the-clr/1_lbYwy45LUJHmX-WsdrGJYw.png"></p>
<p>If you want to get the pid of all supported running .NET applications, call the static <strong>GetPublishedProcesses()</strong> method. Beware that the pid of your own application will also be included.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="k">void</span> <span class="n">ListProcesses</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">selfPid</span> <span class="p">=</span> <span class="n">Process</span><span class="p">.</span><span class="n">GetCurrentProcess</span><span class="p">().</span><span class="n">Id</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">pid</span> <span class="k">in</span> <span class="n">DiagnosticsClient</span><span class="p">.</span><span class="n">GetPublishedProcesses</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">process</span> <span class="p">=</span> <span class="n">Process</span><span class="p">.</span><span class="n">GetProcessById</span><span class="p">(</span><span class="n">pid</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;{pid,6}{GetSeparator(pid == selfPid)}{process.ProcessName}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Otherwise, create an instance passing the process ID of the .NET application you are interested in. With this object, call the method corresponding to the command you want to send. For example, the following code is calling <strong>GetProcessEnvironment()</strong> to list the environment variables:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="k">void</span> <span class="n">ListEnvironmentVariables</span><span class="p">(</span><span class="kt">int</span> <span class="n">pid</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// get environment variables via existing wrapper in DiagnosticsClient</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">client</span> <span class="p">=</span> <span class="k">new</span> <span class="n">DiagnosticsClient</span><span class="p">(</span><span class="n">pid</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">envVariables</span> <span class="p">=</span> <span class="n">client</span><span class="p">.</span><span class="n">GetProcessEnvironment</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">variable</span> <span class="k">in</span> <span class="n">envVariables</span><span class="p">.</span><span class="n">Keys</span><span class="p">.</span><span class="n">OrderBy</span><span class="p">(</span><span class="n">k</span> <span class="p">=&gt;</span> <span class="n">k</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;{variable,26} = {envVariables[variable]}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Note that the value “ExitCode=00000000” is associated to the “” (empty) key for reason unknown to me…</p>
<p>Even though the undocumented command to set an environment variable is available via the <strong>SetEnvironmentVariable()</strong> method, there is no helper method wrapping the <strong>ProcessInfo</strong> command. In fact, a <strong>GetProcessInfo()</strong> method exists but it is internal! The <strong>PidIpcEndpoint</strong> type in charge of the transport and the <strong>IpcMessage</strong>, <strong>IpcResponse</strong> and <strong>IpcClient</strong> types dealing with commands are also internal. It means that the nuget will not help if you need to send the <strong>ProcessInfo</strong> command.</p>
<h2 id="still-easy-use-microsoftdiagnosticsnetcoreclient-sourcecode">Still easy: use Microsoft.Diagnostics.NETCore.Client source code</h2>
<p>The .NET team spends some extra time testing, documenting, and verifying they are happy with the APIs in NetCore.Client before making them public, so sometimes you will see types that they used in their own tools that are still internal. But wait, if the CLI tools need some of these types, how will it work? Well…</p>
<p>The C# project corresponding to the MicrosoftDiagnostics.NETCore.Client assembly is part of the dotnet Diagnostic repository where the tools are implemented. If you look at <a href="https://github.com/dotnet/diagnostics/blob/main/src/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.csproj">the .csproj file</a>, you will see <strong>InternalsVisibleTo</strong> attributes to allow the tools to access the internal types:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">  <span class="nt">&lt;ItemGroup&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;InternalsVisibleTo</span> <span class="na">Include=</span><span class="s">&#34;dotnet-counters&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;InternalsVisibleTo</span> <span class="na">Include=</span><span class="s">&#34;dotnet-dsrouter&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;InternalsVisibleTo</span> <span class="na">Include=</span><span class="s">&#34;dotnet-monitor&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;InternalsVisibleTo</span> <span class="na">Include=</span><span class="s">&#34;dotnet-trace&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;InternalsVisibleTo</span> <span class="na">Include=</span><span class="s">&#34;Microsoft.Diagnostics.Monitoring&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;InternalsVisibleTo</span> <span class="na">Include=</span><span class="s">&#34;Microsoft.Diagnostics.Monitoring.EventPipe&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="c">&lt;!-- Temporary until Diagnostic Apis are finalized--&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;InternalsVisibleTo</span> <span class="na">Include=</span><span class="s">&#34;Microsoft.Diagnostics.Monitoring.WebApi&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;InternalsVisibleTo</span> <span class="na">Include=</span><span class="s">&#34;Microsoft.Diagnostics.NETCore.Client.UnitTests&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;/ItemGroup&gt;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The great thing about OSS is that you can compile your own fork to make these types public. Of course you will be on your own to support these custom builds of the library and it is possible there will be changes to the API before .NET makes it public.</p>
<p>So what you could do to use these internal types in your code is the following:</p>
<ul>
<li>copy the folder from the Diagnostics repository</li>
<li>add the name of your assembly that needs to access the internal types and members into the .csproj</li>
<li>replace the reference to the nuget package by a project reference to the copied project</li>
</ul>
<p>And now <strong>GetProcessInfo</strong> and the other internal types are public for you:</p>
<p><img loading="lazy" src="/posts/2022-07-28_digging-into-the-clr/1_RHI5XtwfR4iN2EI3S9o6bg.png"></p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="k">void</span> <span class="n">ListProcessInfo</span><span class="p">(</span><span class="kt">int</span> <span class="n">pid</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">client</span> <span class="p">=</span> <span class="k">new</span> <span class="n">DiagnosticsClient</span><span class="p">(</span><span class="n">pid</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">info</span> <span class="p">=</span> <span class="n">client</span><span class="p">.</span><span class="n">GetProcessInfo</span><span class="p">();</span>  <span class="c1">// this method is internal</span>
</span></span><span class="line"><span class="cl">    <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;              Command Line = {info.CommandLine}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;              Architecture = {info.ProcessArchitecture}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;      Entry point assembly = {info.ManagedEntrypointAssemblyName}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;               CLR Version = {info.ClrProductVersionString}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Note that during my tests, I was able to get a value for the <strong>ManagedEntrypointAssemblyName</strong> or <strong>ClrProductVersionString</strong> properties only with .NET 6+: the <strong>ProcessInfo2</strong> (0x404) command does not seem to be implemented in previous versions.</p>
<p>The next episode of the series will start to explain the EventPipe IPC protocol from a native C++ developer perspective.</p>
<h2 id="resources">Resources</h2>
<ul>
<li><a href="https://www.nuget.org/packages/Microsoft.Diagnostics.NETCore.Client/">Microsoft.Diagnostics.NETCore.Client nuget package</a></li>
<li><a href="https://www.nuget.org/packages/Microsoft.Diagnostics.Tracing.TraceEvent">TraceEvent nugget package</a></li>
<li>Diagnostics IPC protocol <a href="https://github.com/dotnet/diagnostics/blob/main/documentation/design-docs/ipc-protocol.md">documentation</a></li>
</ul>
]]></content:encoded></item><item><title>Troubleshooting CPU and exceptions issues with Datadog toolbox</title><link>https://chrisnas.github.io/posts/2022-06-09_troubleshooting-cpu-and-except/</link><pubDate>Thu, 09 Jun 2022 16:09:27 +0000</pubDate><guid>https://chrisnas.github.io/posts/2022-06-09_troubleshooting-cpu-and-except/</guid><description>Learn how to use Datadog CPU and exceptions profiling to troubleshoot well known Tess Ferrandez BuggyBits</description><content:encoded><![CDATA[<hr>
<h2 id="introduction">Introduction</h2>
<p>With the new 2.10 release of the Datadog .NET Tracer and Continuous Profiler available, it is time to update some investigation workflows <a href="/posts/2022-01-28_troubleshooting-net-performanc/">I already introduced</a>. New features have been added to help you diagnose performance issues in your .NET applications:</p>
<ul>
<li>Linux support!</li>
<li>Code Hotspots: allow you to automatically navigate from lengthy spans and requests to profiles</li>
<li>CPU profiling: pinpoint high CPU consuming methods</li>
<li>Exceptions profiling: identify exceptions distributions</li>
<li>Profile sequence: easily profile an application startup</li>
</ul>
<p>The goal of this post is to show you how all these features make your investigations easier. I would recommend reading <a href="/posts/2022-01-28_troubleshooting-net-performanc/">the previous post</a>; especially for the environment setup that I won’t repeat here.</p>
<h2 id="its-linux-showtime">It’s Linux showtime!</h2>
<p>The .NET Continuous Profiler is now available for Linux. The only limitation is the presence of glibc 2.18+ in the distribution; for example, CentOS 7 is not supported. Beyond that, we provide features parity between Linux and Windows.</p>
<p>In terms of installation, download the <a href="https://github.com/DataDog/dd-trace-dotnet/releases">.NET Tracer package</a> that supports your operating system and architecture. Go to <a href="https://docs.datadoghq.com/tracing/profiler/enabling/dotnet?tab=linux">the documentation</a> for the additional configuration steps.</p>
<h2 id="from-spans-toprofiles">From spans to profiles</h2>
<p>When analysing lengthy requests, you usually start from looking at the corresponding spans in the APM Traces part of the UI. It is now possible to view the corresponding profiles by clicking the “View Profile” button in the “Code Hotspots” tab:</p>
<p><img loading="lazy" src="/posts/2022-06-09_troubleshooting-cpu-and-except/1_uNg9te1UxHzFUzCCwJN3PQ.png"></p>
<p>Before digging into the profiling information, you are already able to see that more than half of the time is spent in <strong>Buffer._Memmove</strong> that is called by Buggybits <strong>ProductsController.Index</strong> method:</p>
<p><img loading="lazy" src="/posts/2022-06-09_troubleshooting-cpu-and-except/1_FjzJDKmiR40r7UYzjtV8HQ.png"></p>
<p>From the profile view, it is also possible to come back to the traces:</p>
<p><img loading="lazy" src="/posts/2022-06-09_troubleshooting-cpu-and-except/1_6M_WOoI8WOUxUukEti7TxQ.png"></p>
<p>Let’s see now what new features are available at the profiling side.</p>
<h2 id="cpu-profiling">CPU profiling</h2>
<p>The most demanded feature was the ability to analyse CPU consumption (a.k.a. CPU profiling). The idea is to be able to identify code that really consumes CPU usage and optimize it. This is particularly important in the context of cloud-based computing where what you pay is related to the consumed CPU.</p>
<p>In term of implementation, unlike Wall Time profiling, we look at the time spent by a thread on a CPU core and not the elapsed time since the last time we checked (every ~10ms). We also collect the call stack of a thread only if it is currently running on a core. Why? Because we want to only record call stacks corresponding to code paths that are consuming CPU. For example, ThreadPool threads are usually waiting (not interesting call stack) for a work item to process (interesting call stack).</p>
<p>In the <a href="/posts/2022-01-28_troubleshooting-net-performanc/">last blog post</a>, the <a href="https://github.com/DataDog/dd-trace-dotnet/blob/master/profiler/src/Demos/Samples.BuggyBits/Controllers/ProductsController.cs#L124">code responsible for lengthy requests</a> is doing too many string concatenations (look for += in the following code):</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="n">IActionResult</span> <span class="n">Index</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">sw</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Stopwatch</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">sw</span><span class="p">.</span><span class="n">Start</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">products</span> <span class="p">=</span> <span class="n">dataLayer</span><span class="p">.</span><span class="n">GetAllProducts</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">productsTable</span> <span class="p">=</span> <span class="s">&#34;&lt;table&gt;&lt;tr&gt;&lt;th&gt;Product Name&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;th&gt;Price&lt;/th&gt;&lt;/tr&gt;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">product</span> <span class="k">in</span> <span class="n">products</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">productsTable</span> <span class="p">+=</span> <span class="s">$&#34;&lt;tr&gt;&lt;td&gt;{product.ProductName}&lt;/td&gt;&lt;td&gt;{product.Description}&lt;/td&gt;&lt;td&gt;{product.Price}&lt;/td&gt;&lt;/tr&gt;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">productsTable</span> <span class="p">+=</span> <span class="s">&#34;&lt;/table&gt;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">sw</span><span class="p">.</span><span class="n">Stop</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">ViewData</span><span class="p">[</span><span class="s">&#34;ElapsedTimeInMs&#34;</span><span class="p">]</span> <span class="p">=</span> <span class="n">sw</span><span class="p">.</span><span class="n">ElapsedMilliseconds</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">ViewData</span><span class="p">[</span><span class="s">&#34;ProductsTable&#34;</span><span class="p">]</span> <span class="p">=</span> <span class="n">productsTable</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">View</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The Wall Time view was very explicit about <strong>ProductsController.Index()</strong> calling <strong>String.Concat()</strong> culprit:</p>
<p><img loading="lazy" src="/posts/2022-06-09_troubleshooting-cpu-and-except/1_ajUXPb0-ANMBllBPfU91oQ.png"></p>
<p>A simple solution is to <a href="https://github.com/DataDog/dd-trace-dotnet/blob/master/profiler/src/Demos/Samples.BuggyBits/Controllers/ProductsController.cs#L103">use a StringBuilder to optimize the concatenations</a>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="n">IActionResult</span> <span class="n">Builder</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">sw</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Stopwatch</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">sw</span><span class="p">.</span><span class="n">Start</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">products</span> <span class="p">=</span> <span class="n">dataLayer</span><span class="p">.</span><span class="n">GetAllProducts</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">productsTable</span> <span class="p">=</span> <span class="k">new</span> <span class="n">StringBuilder</span><span class="p">(</span><span class="m">1000</span> <span class="p">*</span> <span class="m">80</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">productsTable</span><span class="p">.</span><span class="n">Append</span><span class="p">(</span><span class="s">&#34;&lt;table&gt;&lt;tr&gt;&lt;th&gt;Product Name&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;th&gt;Price&lt;/th&gt;&lt;/tr&gt;&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">product</span> <span class="k">in</span> <span class="n">products</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">productsTable</span><span class="p">.</span><span class="n">Append</span><span class="p">(</span><span class="s">$&#34;&lt;tr&gt;&lt;td&gt;{product.ProductName}&lt;/td&gt;&lt;td&gt;{product.Description}&lt;/td&gt;&lt;td&gt;{product.Price}&lt;/td&gt;&lt;/tr&gt;&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">productsTable</span><span class="p">.</span><span class="n">Append</span><span class="p">(</span><span class="s">&#34;&lt;/table&gt;&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">sw</span><span class="p">.</span><span class="n">Stop</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">ViewData</span><span class="p">[</span><span class="s">&#34;ElapsedTimeInMs&#34;</span><span class="p">]</span> <span class="p">=</span> <span class="n">sw</span><span class="p">.</span><span class="n">ElapsedMilliseconds</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">ViewData</span><span class="p">[</span><span class="s">&#34;ProductsTable&#34;</span><span class="p">]</span> <span class="p">=</span> <span class="n">productsTable</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">View</span><span class="p">(</span><span class="s">&#34;Index&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The Wall Time view of the profile with the fix does not provide anything useful…</p>
<p><img loading="lazy" src="/posts/2022-06-09_troubleshooting-cpu-and-except/1_Mqpq2MVAkCUpA8Z62wRtYA.png"></p>
<p>If you want to go deeper, you need to look at the CPU consumption:</p>
<p><img loading="lazy" src="/posts/2022-06-09_troubleshooting-cpu-and-except/1_Ox4XRvFCIQozhGAeC-6kTQ.png"></p>
<p>The <strong>ProductController.Builder</strong> method is handling the request (like <strong>Index()</strong> in the String.Concat case) and calls <strong>DataLayer.GetAllProducts()</strong> where most of the CPU-related work is done.</p>
<p>Would it be interesting to continue optimizing the code? Notice that <strong>GetAllProducts()</strong> is “only” consuming 94ms and the other <strong>Number.</strong>* and <strong>String.Concat</strong> method around 350ms. So a gain might be neglectable compared to the total ~3 seconds CPU usage</p>
<p>Remember that you should not optimize for the sake of “optimizing”: you should have metrics that tell you when to start (too lengthy request processing) and when to stop.</p>
<h2 id="exceptions-profiling">Exceptions profiling</h2>
<p>In the .NET world, exceptions are at the center of errors handling. It is now possible to get a sampled view of the exceptions that happened during an application lifetime; by type:</p>
<p><img loading="lazy" src="/posts/2022-06-09_troubleshooting-cpu-and-except/1_uyUmkTTLlQjg1IhCBVoCcg.png"></p>
<p>and by message:</p>
<p><img loading="lazy" src="/posts/2022-06-09_troubleshooting-cpu-and-except/1_GRyr-g-jo8KXySpTRMTLpQ.png"></p>
<p>At the implementation level, the new exceptions profiler is notified by the CLR when an exception is thrown. Since in special cases (such as network issue or invalid parsed data for example), an application could trigger thousands of exceptions in a very short period, it is needed to sample them. Otherwise, the impact on performances would be severe; especially if the call stack needs to be rebuilt for each exception.</p>
<p>First, at least one exception per type is kept, ensuring that weird specific exceptions are not lost in the flow. Second, exceptions are sampled over time based on a fixed number of exceptions per profile and the rate of appearance. For knowing the exact number of exceptions, feel free to leverage the Runtime Metrics package as explained in the previous blog post.</p>
<h2 id="profiling-the-application-bootstrap">Profiling the application bootstrap</h2>
<p>In some situations, you are interested in analysing an application bootstrap. In Datadog APM Profile Search UI, it means finding the first profile of the given service execution. However, even with the date and time column, it is not obvious to find the right one:</p>
<p><img loading="lazy" src="/posts/2022-06-09_troubleshooting-cpu-and-except/1_KapOJXMlqOX2nflnyNB1ZA.png"></p>
<p>To help you find the initial profile of a service execution, a new “profile_seq” tag has been added to the HTTP request used to upload the profiles. It contains the count of generated profiles for a given execution of a service, starting from 0.</p>
<p>So now, in the Options of the profile list, add a “profile_seq” column:</p>
<p><img loading="lazy" src="/posts/2022-06-09_troubleshooting-cpu-and-except/1_BwnvwM5y6e0Xd20JXVL8XQ.png"></p>
<p>The first profile is then easily spottable with a 0 value:</p>
<p><img loading="lazy" src="/posts/2022-06-09_troubleshooting-cpu-and-except/1__8Kf93AIiGsBCjGZl_SoyA.png"></p>
<p>In the future, a more visual hint might be added to identify it without the need to add the column.</p>
<h2 id="major-implementation-refactoring">Major implementation refactoring</h2>
<p>Finally, our implementation has benefited from a large code refactoring. As the previous post explained, the generation of .pprof files and their upload was done in C#. This has been replaced by using a rust library shared amongst different profiler libraries (native, Ruby, .NET).</p>
<p>It means that you should not anymore see these frames in the application call stacks:</p>
<p><img loading="lazy" src="/posts/2022-06-09_troubleshooting-cpu-and-except/1_FHcvrh7PIKIrMimv1FrW6g.png"></p>
<p>It does not mean that one third of the processing has been removed! Just that no more C# code is running with performance gain. First, the managed implementation was allocating objects managed by the garbage collector; adding pressure that might trigger more collections. Second, with the native rust implementation, there is no need to duplicate data between the collecting native part of the continuous profiler and the managed code used to serialize it.</p>
<p>In addition, several optimizations have been done in the symbol’s resolution (i.e., type and method names) part of the code that also reduce memory consumption and CPU usage.</p>
<p>Happy profiling!</p>
<h2 id="references">References</h2>
<ul>
<li>Datadog Tracer &amp; Continuous Profiler <a href="https://github.com/DataDog/dd-trace-dotnet/releases/tag/v2.10.0">.msi Installer and Linux tar.gz</a></li>
<li><a href="https://docs.datadoghq.com/tracing/profiler/enabling/dotnet">Datadog Continuous Profiler documentation</a></li>
<li><a href="https://docs.datadoghq.com/tracing/setup_overview/setup/dotnet-framework/?tab=windows">Datadog Tracer documentation</a></li>
<li><a href="https://docs.datadoghq.com/tracing/runtime_metrics/dotnet/">Datadog Runtime metrics documentation</a></li>
<li><a href="https://twitter.com/TessFerrandez">Tess Ferrandez</a> repository for <a href="https://www.tessferrandez.com/blog/2008/02/04/debugging-demos-setup-instructions.html">BuggyBits labs</a></li>
</ul>
]]></content:encoded></item><item><title>Troubleshooting .NET performance issues with Datadog toolbox</title><link>https://chrisnas.github.io/posts/2022-01-28_troubleshooting-net-performanc/</link><pubDate>Fri, 28 Jan 2022 12:55:40 +0000</pubDate><guid>https://chrisnas.github.io/posts/2022-01-28_troubleshooting-net-performanc/</guid><description>How to troubleshoot well known Tess Ferrandez BuggyBits application with the Datadog toolbox including the new Continuous Profiler</description><content:encoded><![CDATA[<hr>
<h2 id="introduction">Introduction</h2>
<p>The beta of Datadog .NET Continuous Profiler is <a href="https://github.com/DataDog/dd-trace-dotnet/releases/tag/v2.1.1-profiler-beta1">available</a>!</p>
<p>This is a great opportunity to show how to use the different tools provided by Datadog to troubleshoot .NET applications facing performance issues. <a href="https://twitter.com/TessFerrandez">Tess Ferrandez</a> updated her famous BuggyBits application to .NET Core. Among the <a href="https://www.tessferrandez.com/blog/2008/02/04/debugging-demos-setup-instructions.html">different available scenarios</a>, let’s see how to investigate the <a href="https://www.tessferrandez.com/blog/2008/02/27/net-debugging-demos-lab-4-walkthrough.html">Lab 4 — High CPU Hang</a> with Datadog. It will be completely different from Tess way: no need to analyze memory dump anymore.</p>
<h2 id="setup-the-environment">Setup the environment</h2>
<p>First, you need to download and run the .msi from our Tracer repository: it will install both the Tracer and the Profiler. The former allows you, among other things, to see how long it takes to process ASP.NET Core requests. The latter is in Beta today and provides wall time duration of your threads (more on this later). Look at the corresponding documentations for the details of enabling <a href="https://docs.datadoghq.com/tracing/setup_overview/setup/dotnet-framework/?tab=windows">tracing</a> and <a href="https://docs.datadoghq.com/tracing/profiler/enabling/dotnet">profiling</a> once installed.</p>
<p>Next, ensure that <strong>.NET Runtime Metrics</strong> are installed for your organization:</p>
<p><img loading="lazy" src="/posts/2022-01-28_troubleshooting-net-performanc/1_87cPlnHNwMWqRpt4veVxkw.png"></p>
<p>Add <strong>DD_RUNTIME_METRICS_ENABLED=true</strong> environment variable for the application/service you want to monitor. Once enabled for your application, this package allows you to see the evolution of <a href="https://docs.datadoghq.com/tracing/runtime_metrics/dotnet/">important metrics</a> including some that you won’t find anywhere else such as GC pause time, thread contention time or count of exceptions per type.</p>
<p>Ensure that <a href="https://docs.datadoghq.com/developers/dogstatsd/?tab=hostagent#setup">DogstatsD is setup</a> for the Agent</p>
<p><img loading="lazy" src="/posts/2022-01-28_troubleshooting-net-performanc/1_V1sQz72I3ZwAgzjG6G-8eA.png"></p>
<h2 id="looking-at-thesymptoms">Looking at the symptoms</h2>
<p>In my example, the buggybits application is running under the <em>datadog.demos.buggybits</em> service name. This is how I can filter the related traces in the APM/Traces part of the Datadog portal:</p>
<p><img loading="lazy" src="/posts/2022-01-28_troubleshooting-net-performanc/1_IpoI4Qh-Gm0sbgPeM7u8Sg.png"></p>
<p>In this screenshot, the <strong>Products/Index</strong> requests duration is around 6 seconds; which is way too long!</p>
<p>When clicking such a request, the details panel provides the exact URL in the <strong>Tags</strong> tab:</p>
<p><img loading="lazy" src="/posts/2022-01-28_troubleshooting-net-performanc/1_rMphP6jfVQNQ7ZD9p5dr0g.png"></p>
<p>The <strong>Metrics</strong> tab shows CPU usage and other few metrics around the trace time:</p>
<p><img loading="lazy" src="/posts/2022-01-28_troubleshooting-net-performanc/1_zL8l7705sS_x-0zNhU6v6A.png"></p>
<p>So, in addition to having slow requests, the CPU usage seems to increase.</p>
<p>It is now time to go to the <em>.NET runtime metrics</em> dashboard and look at what is going on in more details. The first graph that shows up is the number of gen2 collections:</p>
<p><img loading="lazy" src="/posts/2022-01-28_troubleshooting-net-performanc/1_RUSWt8-h068RgOP76rSQSQ.png"></p>
<p>It means that during the 10 minutes test where very few requests are processed, almost 800 gen2 GC are happening every 10s (all runtime metrics are computed every 10 seconds).</p>
<p>The load test corresponding to these requests lasted 10 minutes between 4pm and 6+pm. Each time the requests were processed:</p>
<ul>
<li>the CPU usage increased</li>
<li>the number of gen2 collections increased</li>
<li>the duration of pauses due to garbage collections increased</li>
<li>the threads contention time increased</li>
</ul>
<p><img loading="lazy" src="/posts/2022-01-28_troubleshooting-net-performanc/1_iCzt6wCUu-yUef5btID_-g.png"></p>
<p>In addition to being slow, it seems that the code that processes <em>products/index</em> HTTP requests has also an impact on the CPU (i.e. on the overall application and machine performances).</p>
<p>It would be great if we could see the callstacks corresponding to this processing. This is exactly what the .NET Wall time Continuous Profiler is all about: looking at the duration of methods through a flamegraph representation.</p>
<h2 id="here-comes-theprofiler">Here comes the profiler</h2>
<p>Today, there is no direct way to jump from a trace to the profile containing the callstacks while the corresponding request was processed. We are currently working on this new feature called <em>Code Hotspots</em>.</p>
<p>However, it is easy to use the service name to filter the profiles and select the period of time from APM/Profile Search:</p>
<p><img loading="lazy" src="/posts/2022-01-28_troubleshooting-net-performanc/1_ft1EYQ2z0aXcHpxjgRZzgg.png"></p>
<p>When you click a one-minute profile (the callstacks are gathered and sent every minute), a panel appears with the <strong>Performance</strong> tab selected. It shows a framegraph on the left and a list on the right.</p>
<h2 id="getting-used-to-flamegraph">Getting used to flamegraph</h2>
<p>When you look at the wall time flamegraph, you see everything that happened during a single minute:</p>
<p><img loading="lazy" src="/posts/2022-01-28_troubleshooting-net-performanc/1__B5VXVtYIxx32h8wm-HNLA.png"></p>
<p>The previous screenshot highlights the groups of callstacks corresponding to the different threads of execution (from left to right):</p>
<ul>
<li>the <strong>Main()</strong> entry point of the application</li>
<li>the code in the CLR responsible for sending counters</li>
<li>the code in the Profiler in charge of generating and sending (the very thin spike) the profile every minute</li>
<li>the Tracer code</li>
<li>the code that listens to the CLR events to generate the runtime metrics</li>
<li>…and the application code that processes the requests!</li>
</ul>
<p>In the flamegraph, the width of each frame on a row represents the relative time during which the frame was found on a callstack. For example, in our tests, we have 4 threads simply calling <strong>Thread.Sleep</strong>; one for 10 seconds, one for 20 seconds, one for 30 seconds and a last one for 40 seconds. This is the expected result in a flamegraph (i.e. the widths are consistent with the 1/2/3/4 ratio):</p>
<p><img loading="lazy" src="/posts/2022-01-28_troubleshooting-net-performanc/1_mVT5PCAbJhkA2lrq3UbeCw.png"></p>
<p>This also applies to CPU-bound threads. For example, if 3 threads are computing the sum of numbers in a tight loop, this is the expected result (i.e. all <strong>OnCPUxxx</strong> have the same width)</p>
<p><img loading="lazy" src="/posts/2022-01-28_troubleshooting-net-performanc/1_byJpeSeYTLlJNgyLOBH73A.png"></p>
<p>These explanations should stop the fear that started to crawl inside your head about the “visible cost” of the Datadog Tracer and Profiler based on the previous screenshot. The large width of the Datadog threads frames is all about wall time, not CPU time: we are mostly sleeping or waiting but we don’t stop :^)</p>
<h2 id="investigate-the-performance-issue">Investigate the performance issue</h2>
<p>The next step is to focus on the stack frames corresponding to the request processing to better understand what is going on.</p>
<p>Basically, you would like to either remove a branch or keep only a branch. You simply have to move the mouse over a frame (i.e. <strong>ThreadPoolWorkQueue</strong> in the previous screenshot) and click the three dots that just appeared. Next, select <strong>Show From</strong> to keep only that branch in the flamegraph:</p>
<p><img loading="lazy" src="/posts/2022-01-28_troubleshooting-net-performanc/1_fq0_NeVQlii7qQS0gvBrRw.png"></p>
<p>Now, scroll-down into the flamegraph and the flow of execution corresponding to processing the <em>Products/Index</em> request becomes more visible:</p>
<p><img loading="lazy" src="/posts/2022-01-28_troubleshooting-net-performanc/1_TZHJO6vuO6VWar4FjVhlQQ.png"></p>
<p>It seems that the <strong>Index()</strong> method of the <strong>ProductsController</strong> is spending most of its time calling <strong>String.Concat()</strong>.</p>
<p>Let’s have a look at the <a href="https://github.com/TessFerrandez/BuggyBits/blob/main/src/BuggyBits/Controllers/ProductsController.cs#L18">source code</a>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="c1">// GET: Products</span>
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="n">IActionResult</span> <span class="n">Index</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">products</span> <span class="p">=</span> <span class="n">dataLayer</span><span class="p">.</span><span class="n">GetAllProducts</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">productsTable</span> <span class="p">=</span> <span class="s">&#34;&lt;table&gt;&lt;tr&gt;&lt;th&gt;Product Name&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;th&gt;Price&lt;/th&gt;&lt;/tr&gt;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">product</span> <span class="k">in</span> <span class="n">products</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">productsTable</span> <span class="p">+=</span> <span class="s">$&#34;&lt;tr&gt;&lt;td&gt;{product.ProductName}&lt;/td&gt;&lt;td&gt;{product.Description}&lt;/td&gt;&lt;td&gt;{product.Price}&lt;/td&gt;&lt;/tr&gt;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">productsTable</span> <span class="p">+=</span> <span class="s">&#34;&lt;/table&gt;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">ViewData</span><span class="p">[</span><span class="s">&#34;ProductsTable&#34;</span><span class="p">]</span> <span class="p">=</span> <span class="n">productsTable</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">View</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>But still no sign of <strong>String.Concat()</strong>… Well, this is because the C# compiler is hiding it from you with the <strong>+=</strong> syntaxic sugar. Let’s have a look at the decompiled code as shown by IlSpy (without the <strong>string.Concat</strong> transformation):</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="n">Microsoft</span><span class="p">.</span><span class="n">AspNetCore</span><span class="p">.</span><span class="n">Mvc</span><span class="p">.</span><span class="n">IActionResult</span> <span class="n">Index</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">products</span> <span class="p">=</span> <span class="n">dataLayer</span><span class="p">.</span><span class="n">GetAllProducts</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="kt">string</span> <span class="n">productsTable</span> <span class="p">=</span> <span class="s">&#34;&lt;table&gt;&lt;tr&gt;&lt;th&gt;Product Name&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;th&gt;Price&lt;/th&gt;&lt;/tr&gt;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">enumerator</span> <span class="p">=</span> <span class="n">products</span><span class="p">.</span><span class="n">GetEnumerator</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">while</span> <span class="p">(</span><span class="n">enumerator</span><span class="p">.</span><span class="n">MoveNext</span><span class="p">())</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">BuggyBits</span><span class="p">.</span><span class="n">Models</span><span class="p">.</span><span class="n">Product</span> <span class="n">product</span> <span class="p">=</span> <span class="n">enumerator</span><span class="p">.</span><span class="n">Current</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="kt">string</span><span class="p">[]</span> <span class="n">array</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">string</span><span class="p">[</span><span class="m">8</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">            <span class="n">array</span><span class="p">[</span><span class="m">0</span><span class="p">]</span> <span class="p">=</span> <span class="n">productsTable</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">array</span><span class="p">[</span><span class="m">1</span><span class="p">]</span> <span class="p">=</span> <span class="s">&#34;&lt;tr&gt;&lt;td&gt;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">array</span><span class="p">[</span><span class="m">2</span><span class="p">]</span> <span class="p">=</span> <span class="n">product</span><span class="p">.</span><span class="n">ProductName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">array</span><span class="p">[</span><span class="m">3</span><span class="p">]</span> <span class="p">=</span> <span class="s">&#34;&lt;/td&gt;&lt;td&gt;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">array</span><span class="p">[</span><span class="m">4</span><span class="p">]</span> <span class="p">=</span> <span class="n">product</span><span class="p">.</span><span class="n">Description</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">array</span><span class="p">[</span><span class="m">5</span><span class="p">]</span> <span class="p">=</span> <span class="s">&#34;&lt;/td&gt;&lt;td&gt;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">array</span><span class="p">[</span><span class="m">6</span><span class="p">]</span> <span class="p">=</span> <span class="n">product</span><span class="p">.</span><span class="n">Price</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">array</span><span class="p">[</span><span class="m">7</span><span class="p">]</span> <span class="p">=</span> <span class="s">&#34;&lt;/td&gt;&lt;/tr&gt;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">productsTable</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="n">Concat</span><span class="p">(</span><span class="n">array</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">finally</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="p">((</span><span class="n">System</span><span class="p">.</span><span class="n">IDisposable</span><span class="p">)</span><span class="n">enumerator</span><span class="p">).</span><span class="n">Dispose</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">productsTable</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="n">Concat</span><span class="p">(</span><span class="n">productsTable</span><span class="p">,</span> <span class="s">&#34;&lt;/table&gt;&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">base</span><span class="p">.</span><span class="n">ViewData</span><span class="p">[</span><span class="s">&#34;ProductsTable&#34;</span><span class="p">]</span> <span class="p">=</span> <span class="n">productsTable</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">View</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>So now we can see the call to <strong>string.Concat()</strong> at the end of the <strong>while</strong> loop iteration.</p>
<p>Behind the scene, since a string object is immutable, <strong>string.Concat()</strong> will create a new string each time it is called and the previous string referenced by <strong>productsTable</strong> is no more rooted and will put more pressure on the GC. If I’m telling you that <strong>datalayer.GetAllProducts()</strong> returns 10.000 products, it means that <strong>string.Concat</strong> gets called 10.000 times.</p>
<p>As the string grows, it will reach the 85000 bytes limit and start to be allocated in the LOH, adding more pressure on GC that will trigger gen2 collections; hence the high number of gen2 collections seen in the runtime metrics dashboard.</p>
<p>Note that if the native frames were visible in the flamegraph (by the way, let me know if this is a feature that would make sense to add), you would see the methods of the CLR responsible for the GC.</p>
<p>Look at <a href="https://www.tessferrandez.com/blog/2008/02/27/net-debugging-demos-lab-4-walkthrough.html">Tess Ferrandez post</a> for a possible solution to this expensive code pattern (i.e. calling <strong>string.Contact</strong> in a large tight loop)</p>
<h2 id="different-types-offilters">Different types of filters</h2>
<p>Before leaving, I would like to quicky talk about the list shown on the right hand-side of the UI.</p>
<p><img loading="lazy" src="/posts/2022-01-28_troubleshooting-net-performanc/1_S3wDYybXPZIbdB3fFdVong.png"></p>
<p>It allows you to see a wall time summary per method name, namespace (i.e. sum of methods from types in the same namespace, thread ID (i.e. internal Datadog unique ID), thread name or AppDomain Name.</p>
<p>First, the only methods listed here are “leaf” methods: they appear at the top of at least one callstack. If you would like to visually see some specific frames, you should use the filter box:</p>
<p><img loading="lazy" src="/posts/2022-01-28_troubleshooting-net-performanc/1_GTUUF548uj1JVUV3kLkUcA.png"></p>
<p>All other frames are faded out.</p>
<p>Second, the list is sorted with the largest wall time at the top: this could be an easy way to spot method “expensive” in term of CPU (i.e. will frequently be running so appear at the top of the stack). You simply need to skip the wait and sleep related methods like shown in the previous screenshot: <strong>String.Concat</strong> and <strong>Buffer._Memmove</strong> (used by <strong>string.Concat</strong>) were just in front of your eyes!</p>
<p>When you select an element of the list, the flamegraph is updated accordingly: only the callstacks containing this element will be visible (it could speed up the filtering process)</p>
<h2 id="references">References</h2>
<ul>
<li>Datadog Tracer &amp; Continuous Profiler <a href="https://github.com/DataDog/dd-trace-dotnet/releases/tag/v2.1.1-profiler-beta1">.msi Installer</a></li>
<li><a href="https://docs.datadoghq.com/tracing/profiler/enabling/dotnet">Datadog Continuous Profiler documentation</a></li>
<li><a href="https://docs.datadoghq.com/tracing/setup_overview/setup/dotnet-framework/?tab=windows">Datadog Tracer documentation</a></li>
<li><a href="https://docs.datadoghq.com/tracing/runtime_metrics/dotnet/">Datadog Runtime metrics documentation</a></li>
<li><a href="https://twitter.com/TessFerrandez">Tess Ferrandez</a> repository for <a href="https://www.tessferrandez.com/blog/2008/02/04/debugging-demos-setup-instructions.html">BuggyBits labs</a></li>
</ul>
]]></content:encoded></item><item><title>How to ease async callstacks analysis in Perfview</title><link>https://chrisnas.github.io/posts/2021-03-02_how-to-ease-async/</link><pubDate>Tue, 02 Mar 2021 09:35:37 +0000</pubDate><guid>https://chrisnas.github.io/posts/2021-03-02_how-to-ease-async/</guid><description>Our new post describes how to easily profile with Perfview and more interestingly, how to get human readable async callstacks!</description><content:encoded><![CDATA[<hr>
<p><img loading="lazy" src="/posts/2021-03-02_how-to-ease-async/1_ATK-AQ6S7ImVxHgw5iF_kQ.jpeg"></p>
<h2 id="introduction">Introduction</h2>
<p>In the <a href="/posts/2021-01-19_understanding-reversed-callsta/">previous post</a>, I described why you might get weird reversed callstacks in Visual Studio when analyzing or debugging async/await code. And if you are using Perfview to profile the same application, you should also get the same reverse continuation flow:</p>
<p><img loading="lazy" src="/posts/2021-03-02_how-to-ease-async/1_uI53gXuqEuy7tDlp7xw1mA.png"></p>
<p>The rest of the post describes how to easily profile with Perfview and more interestingly, how to leverage grouping/folding features to get much more readable asynchronous callstacks.</p>
<h2 id="perfview-101">Perfview 101</h2>
<p>Here are the different steps to get the previous tree-like representation of a profiling session results.</p>
<p>First, start a data collection by clicking the <strong>Start Collection</strong> button from the <strong>Collect | Collect</strong> dialog box and check <strong>Kernel Base</strong>, <strong>CPU Samples</strong>, and <strong>.NET</strong> boxes:</p>
<p><img loading="lazy" src="/posts/2021-03-02_how-to-ease-async/1_guqs5Mn098S85vqwRzZw6A.png"></p>
<p>Stop the collection when the application ends and double-click the <strong>CPU Stacks</strong> node :</p>
<p><img loading="lazy" src="/posts/2021-03-02_how-to-ease-async/1_Z703G2KzHaYJBgAc4cTG_A.png"></p>
<p>After selecting the application in the <strong>Select Process Window</strong></p>
<p><img loading="lazy" src="/posts/2021-03-02_how-to-ease-async/1_Ex2VB19zQfmciFRYkE9mFQ.png"></p>
<p>click the <strong>CallTree</strong> tab:</p>
<p><img loading="lazy" src="/posts/2021-03-02_how-to-ease-async/1_Dh99MrN5biZGS9O2lZDtKA.png"></p>
<p>Before entering the dreaded yellow/white CPU Stacks window, let’s spend some time detailing its vast toolbar in the following figure:</p>
<p><img loading="lazy" src="/posts/2021-03-02_how-to-ease-async/1_BOJ23oWq4qxmFQmjQhAzcw.png"></p>
<p>The most powerful elements are the *<strong>Pats</strong> combo-boxes. Each of them supports a “simple” matching pattern syntax for different purposes (don’t worry, you will see how to use them in many more examples later):</p>
<ul>
<li><strong>GroupPats</strong>: merge sibling matching frames into one.</li>
<li><strong>FoldPats</strong>: matching frames are folded into parent frame.</li>
<li><strong>IncPats</strong>: non matching frames are removed (used for process filtering for example).</li>
<li><strong>ExcPats</strong>: matching frames are excluded.</li>
</ul>
<p>Let’s see what we get with all combo-box set as empty for the <strong>CallTree</strong> tab:</p>
<p><img loading="lazy" src="/posts/2021-03-02_how-to-ease-async/1_IJx5TrYT-uxKeE5sn_s4XA.png"></p>
<p>For server applications, we are usually not interested in making any difference between threads so it would be nice to group all threads under a single <strong>AllThreads</strong> node. This is exactly what the first choice of the <strong>GroupPats</strong> combo-box provides:</p>
<p><img loading="lazy" src="/posts/2021-03-02_how-to-ease-async/1_VZDZWpBz8l3zHEow_ZUjoQ.png"></p>
<p>The effect is simple: all lines at the same level containing “Thread” in the text are merged into a new line with “AllThreads” as new text</p>
<p><img loading="lazy" src="/posts/2021-03-02_how-to-ease-async/1_-fds9I0vwxpOWjOw460wxA.png"></p>
<p>You can now get the same kind of tree based representation as in Visual Studio: the difference is that you need to open each node by clicking a checkbox (or right-click + <strong>Expand All</strong> to see the whole tree)</p>
<p>Most columns meaning are quite self-explicit except maybe the <strong>When</strong> column that provides the CPU usage graph over time in a “textual” way:</p>
<p><img loading="lazy" src="/posts/2021-03-02_how-to-ease-async/1_CyrGe04ny6Xx53BzoOqRRw.png"></p>
<p>The <strong>CallTree</strong> representation obviously displays the frames sorted by <strong>Inc%</strong> column.</p>
<h2 id="going-further-withperfview">Going further with Perfview</h2>
<p>When expanding the calltree, you usually get lost in the async/await implementation details. The following screenshot shows the signal/noise ratio for my simple test application where we don’t really care about the blue lines!</p>
<p><img loading="lazy" src="/posts/2021-03-02_how-to-ease-async/1_6CEg_Izw7pigwsMDCtAbiQ.png"></p>
<p>This is where the different Perfview combo-boxes are coming to the rescue. Some frames and their children are clearly not interesting such as the last two <strong>coreclr!ThePreStub</strong>. In that case, select the frame and copy the text from the status bar (yes: this is possible and so handy!)</p>
<p><img loading="lazy" src="/posts/2021-03-02_how-to-ease-async/1_U_-kLqVwQzl-yqlcTjj5-Q.png"></p>
<p>and paste it into the <strong>ExcPats</strong> combo-box</p>
<p><img loading="lazy" src="/posts/2021-03-02_how-to-ease-async/1_mIVQ1JJGFIcH9UxsifNF9g.png"></p>
<p>to make the corresponding frames disappear.</p>
<p>Unfortunately, you can’t do the same for the other Task-related frames because these <strong>ExcPats</strong> matching frames are completely removed with their children where your async method calls appear.</p>
<h2 id="folding-patterns-are-yourfriends">Folding patterns are your friends</h2>
<p>This time, the <strong>FoldPats</strong> combo-box will be your friend: each frame that maps one of its ; separated substring will disappear and its occurrence count will be added to its parent frame. Since all these Task-related frame do not appear a lot, the impact in the Inc/Exc columns of the parent frames should be minimal. After I used the following substrings:</p>
<blockquote>
<p>Tasks.Task+DelayPromise.CompleteTimedOut(;Tasks.Task.FinishContinuations(;Tasks.Task.RunOrQueueCompletionAction;Tasks.Task.RunContinuations(;Tasks.Task+DelayPromise+;Tasks.AwaitTaskContinuation.RunOrScheduleAction;Runtime.CompilerServices.AsyncTaskMethodBuilder`;CompilerServices.AsyncTaskMethodBuilder.Start(;CompilerServices.AsyncMethodBuilderCore.Start(;ExecutionContext.RunInternal(;CompilerServices.AsyncTaskMethodBuilder.SetResult(;Tasks.VoidTaskResult].TrySetResult;.TrySetResult(System.Threading.Tasks.;Tasks.Task.TrySetResult(</p>
</blockquote>
<p>the callstack was much more readable:</p>
<p><img loading="lazy" src="/posts/2021-03-02_how-to-ease-async/1_i5zr41nODbhMumppGDyHRw.png"></p>
<h2 id="morph-theframes">Morph the frames</h2>
<p>The final step is to transform the frames text into something more meaningful thanks to the <strong>GroupPats</strong> combo-box. At the beginning of this post, I picked the predefined <strong>[fold threads] Thread -&gt; AllThreads</strong> grouping pattern. The starting text between <strong>[]</strong> is used as a title by Perfview to allow the user to more easily figure out what its role is. The rest of the string defines how parts of each frame should match and be grouped. The corresponding contextual help has already been shown earlier when the toolbar was detailed.</p>
<p>Here, I don’t want to group all sibling frames into a group but rather morph the text into something more readable. The part before <strong>-&gt;</strong> or <strong>=&gt;</strong> is used as matching pattern to be replaced by the part after the sign. It is also possible to “extract” elements between <strong>{}</strong> from the matching pattern to be used to build the replacement string. Each matched element is identified as <strong>$1</strong>, <strong>$2</strong>,… based on its position in the pattern.</p>
<p>In my example, I would like to apply the following transformation:</p>
<p><img loading="lazy" src="/posts/2021-03-02_how-to-ease-async/1_7vPuOGJTB9pO9xboyvhfYw.png"></p>
<p>To write the pattern, you should focus on the separators (<strong>!</strong>, <strong>+&lt;</strong> and <strong>&gt;d__</strong> in this case):</p>
<p>{%}<strong>!</strong>{%}<strong>+&lt;</strong>{%}<strong>&gt;d__</strong>*</p>
<p>And the items to extract are what is left in between; identified as <strong>{%}</strong>.</p>
<p>The building of the replacement string is simply counting the matching item position (starting at 1):</p>
<p>(<strong>$1</strong>) <strong>$2</strong> ~~~async back to~~~ <strong>$3</strong>()</p>
<p>And here is the corresponding final result:</p>
<p><img loading="lazy" src="/posts/2021-03-02_how-to-ease-async/1_YxhSEYtt_O9wCCQyCRpZMA.png"></p>
<h2 id="dont-lose-yourxxxpats">Don’t lose your xxxPats!</h2>
<p>It is interesting to note that you could define your own patterns preset via the <strong>Preset</strong> menu item</p>
<p><img loading="lazy" src="/posts/2021-03-02_how-to-ease-async/1__Pje1UcZ0SSLM3po9E-YQw.png"></p>
<p>As you can see here, I have defined my own <strong>Criteo Arbitrage</strong> preset. If you want to reuse the content of <strong>GroupPats</strong>, <strong>FoldPats</strong>, and <strong>Fold%</strong> combo-boxes, click the <strong>Save As Preset</strong> (or even <strong>Set As Startup Preset</strong> to get them when you start Perfview) and pick a name</p>
<p><img loading="lazy" src="/posts/2021-03-02_how-to-ease-async/1_KZNIZSMPlil-cWsbA5GW9w.png"></p>
<p>Feel free to use the <strong>Manage Presets</strong> dialog for easier preset manipulation:</p>
<p><img loading="lazy" src="/posts/2021-03-02_how-to-ease-async/1_a9MDAqx0lbasO48canlt3Q.png"></p>
<p>I hope that, now, you better understand the value of Perfview to analyze complicated callstacks.</p>
<hr>
<p><strong>Read more from Christophe on our Medium blog!</strong></p>
<p><a href="https://medium.com/criteo-engineering/consul-streaming-whats-behind-it-6f44f77a5175"><strong>Consul Streaming: What’s behind it?</strong>
*Let’s look at new hidden feature for Consul large or very dynamic clusters of Consul 1.9: Streaming.*medium.com</a><a href="https://medium.com/criteo-engineering/consul-streaming-whats-behind-it-6f44f77a5175"></a></p>
<p><strong>Like what you are reading? Join us and make an impact!</strong></p>
<p><a href="http://careers.criteo.com"><strong>Careers at Criteo | Criteo jobs</strong>
*Find opportunities everywhere. ​Choose your next challenge. Find the job opportunities at Criteo in Product, research &amp;…*careers.criteo.com</a><a href="http://careers.criteo.com"></a></p>
]]></content:encoded></item><item><title>Understanding “reversed” callstacks in Visual Studio and Perfview with async/await code</title><link>https://chrisnas.github.io/posts/2021-01-19_understanding-reversed-callsta/</link><pubDate>Tue, 19 Jan 2021 17:16:08 +0000</pubDate><guid>https://chrisnas.github.io/posts/2021-01-19_understanding-reversed-callsta/</guid><description>This post explains why profilers like Visual Studio could display “reversed” callstacks when dealing with async/await code.</description><content:encoded><![CDATA[<hr>
<h2 id="introduction">Introduction</h2>
<p>With my colleague <a href="https://twitter.com/ezsilmar">Eugene</a>, we spent a long time analyzing performances of one of Criteo main applications with Perfview. The application is processing thousand of requests in an asynchronous pipeline full of <strong>async</strong>/**await **calls. During our research, we ended up with weird callstacks that looked kind of “reversed”. The goal of this post is to describe why this could happen (even in Visual Studio).</p>
<h2 id="lets-see-the-result-of-profiling-in-visualstudio">Let’s see the result of profiling in Visual Studio</h2>
<p>I wrote a simple .NET Core application that simulates a few <strong>async</strong>/**await **calls:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">static</span> <span class="kd">async</span> <span class="n">Task</span> <span class="n">Main</span><span class="p">(</span><span class="kt">string</span><span class="p">[]</span> <span class="n">args</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;pid = {Process.GetCurrentProcess().Id}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&#34;press ENTER to start...&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">Console</span><span class="p">.</span><span class="n">ReadLine</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">ComputeAsync</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&#34;press ENTER to exit...&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">Console</span><span class="p">.</span><span class="n">ReadLine</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="kd">async</span> <span class="n">Task</span> <span class="n">ComputeAsync</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">Task</span><span class="p">.</span><span class="n">WhenAll</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">Compute1</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="p">...</span>
</span></span><span class="line"><span class="cl">        <span class="n">Compute1</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="p">);</span> 
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>ComputeAsync</code> is starting a bunch of tasks that will await other **async **methods:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="kd">async</span> <span class="n">Task</span> <span class="n">Compute1</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">ConsumeCPU</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">Compute2</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">ConsumeCPUAfterCompute2</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="kd">async</span> <span class="n">Task</span> <span class="n">Compute2</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">ConsumeCPU</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">Compute3</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">ConsumeCPUAfterCompute3</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="kd">async</span> <span class="n">Task</span> <span class="n">Compute3</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">Task</span><span class="p">.</span><span class="n">Delay</span><span class="p">(</span><span class="m">1000</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">ConsumeCPUinCompute3</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&#34;DONE&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Unlike the <code>Compute1</code> and <code>Compute2</code> methods, the last <code>Compute3</code> is waiting 1 second before consuming some CPU with square root computation in <code>CompusumeCPUXXX</code> helpers:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="na">[MethodImpl(MethodImplOptions.NoInlining)]</span>
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="k">void</span> <span class="n">ConsumeCPUinCompute3</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">ConsumeCPU</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">[MethodImpl(MethodImplOptions.NoInlining)]</span>
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="k">void</span> <span class="n">ConsumeCPUAfterCompute3</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">ConsumeCPU</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">[MethodImpl(MethodImplOptions.NoInlining)]</span>
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="k">void</span> <span class="n">ConsumeCPUAfterCompute2</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">ConsumeCPU</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="k">void</span> <span class="n">ConsumeCPU</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="m">1000</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">j</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">j</span> <span class="p">&lt;</span> <span class="m">1000000</span><span class="p">;</span> <span class="n">j</span><span class="p">++)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">Math</span><span class="p">.</span><span class="n">Sqrt</span><span class="p">((</span><span class="kt">double</span><span class="p">)</span><span class="n">j</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>From Visual Studio, profile the CPU usage of this test program via <strong>Debug | Performance Profiler…</strong></p>
<p><img loading="lazy" src="/posts/2021-01-19_understanding-reversed-callsta/1_19VeO5ExRqIdORWkPalEsg.png"></p>
<p>In the summary result panel, click the <strong>Open Details…</strong> link</p>
<p><img loading="lazy" src="/posts/2021-01-19_understanding-reversed-callsta/1_mgUex0R8dO0KebQoOY3Dgw.png"></p>
<p>And pick the <strong>Call Tree</strong> view</p>
<p><img loading="lazy" src="/posts/2021-01-19_understanding-reversed-callsta/1_pHREftYmqPo6_o16Q9UIDg.png"></p>
<p>You should see two paths of execution:</p>
<p><img loading="lazy" src="/posts/2021-01-19_understanding-reversed-callsta/1_7bCbVvcXMaqyVnJGod52EA.png"></p>
<p>If you open the last one, you should see the expected chain of calls:</p>
<p><img loading="lazy" src="/posts/2021-01-19_understanding-reversed-callsta/1_UaWjYDCs6nJMrwpCN-TPmg.png"></p>
<p>… if the methods were synchronous; which is not the case. So Visual Studio did a great job in dealing with the implementation details of <strong>async</strong>/**await **to present a nice call stack.</p>
<p>However, if you open the first node, you get something more disturbing:</p>
<p><img loading="lazy" src="/posts/2021-01-19_understanding-reversed-callsta/1_32whbyr8dVBDlYx8Y37t7g.png"></p>
<p>… if you don’t know how <strong>async</strong>/**await **is implemented. My <code>Compute3</code> code is definitively not calling <code>Compute2</code> which is not calling <code>Compute1</code>! This is where Visual Studio smart frame/callstack reconstruction brings more confusion than anything else. So what’s going on?</p>
<h2 id="understanding-asyncawait-implementation">Understanding async/await implementation</h2>
<p>Unlike Visual Studio that is hiding real calls, you should be able to see what methods are really called when analyzing a memory dump with <strong>dotnet-dump</strong> and the <strong>pstacks</strong> command:</p>
<p><img loading="lazy" src="/posts/2021-01-19_understanding-reversed-callsta/1_jl7s91j1oVmCfOk-1FLx9Q.png"></p>
<p>If you follow the arrows from the bottom to the top, you should see the following synchronous (because as frame in thread callstacks) calls:</p>
<ul>
<li>a timer callback is calling <code>d__4.MoveNext()</code> : this corresponds to the end of the <code>Task.Delay</code> in<code>Compute3</code> method.</li>
<li><code>d__3.MoveNext()</code> gets called to continue the code after <code>await Compute3</code></li>
<li><code>d__.MoveNext()</code> gets called to continue the code after <code>await Compute2</code></li>
<li><code>ConsumeCPUAfterCompute2()</code> gets called as expected</li>
<li><code>ComputeCPU()</code>** **or <code>ConsumeCPUInCompute3()</code> get called as expected</li>
</ul>
<p>All the fancy methods names are due to “state machine” types that is generated by the C# compiler when you (1) define **async **methods that (2) await other **async **methods (or any “awaitable” object). Their role is to manage a “state machine” to execute code synchronously up to an <strong>await</strong> call, and again up to the next <strong>await</strong> call, and again and again until the method returns.</p>
<p><img loading="lazy" src="/posts/2021-01-19_understanding-reversed-callsta/1_z5p1oSFQhsBrFPMsqBYSuQ.png"></p>
<p>All these <code>d__*</code> types contains fields corresponding to each <strong>async</strong> method local variables and parameters if any. For example, here is what is generated for the <code>ComputeAsync</code>** **and <code>Compute1/2/3</code> <strong>async</strong> methods without any local or parameter:</p>
<p><img loading="lazy" src="/posts/2021-01-19_understanding-reversed-callsta/1_YjVkaPmjcdGN1SHKjPSX5w.png"></p>
<p>The integer <code>&lt;&gt;1__state</code> field keeps track of the “execution state” of the machine. For example, after the state machine is created in <code>Compute1</code>, this field is set to <strong>-1</strong>:</p>
<p><img loading="lazy" src="/posts/2021-01-19_understanding-reversed-callsta/1_WaPLv6vAl2h2vyQMZ4GdIw.png"></p>
<p>I don’t want to dig into the builder details but just let’s just say that the <code>MoveNext</code> method of the state machine <code>d__2</code> gets executed (by the same thread).</p>
<p>Before looking at the <code>MoveNext</code> implementation corresponding to the <code>Compute1</code> method (without exception handling), keep in mind that it has to :</p>
<ul>
<li>run all code up to an **await **call,</li>
<li>change the “execution state” (more on this later)</li>
<li>do some magic to execute that code in another thread (if needed — more on this later)</li>
<li>come back to continue the execution of the code after the <strong>await</strong> call</li>
<li>and do that up to the next <strong>await</strong> call again and again</li>
</ul>
<p><img loading="lazy" src="/posts/2021-01-19_understanding-reversed-callsta/1_Ip734O0vxHnhdDv3siEPWA.png"></p>
<p>Since <code>&lt;&gt;1__state</code> is -1, the first “synchronous” part of the code is executed (i.e. calling <code>ComsumeCPU</code> method).</p>
<p>The <code>Compute2</code> method is then called to get the corresponding awaitable object (here a <code>Task</code>). If the task runs immediately (i.e. no <strong>await</strong> call such as a simple <code>Task.FromResult()</code> in the <strong>async</strong> method), <code>IsCompleted()</code> will return <strong>true</strong> and the code after the <strong>await</strong> call will be run by the same thread. Yes it means that <strong>async</strong>/<strong>await</strong> calls could be run synchronously by the same thread: why creating a thread when it is not needed?</p>
<p>If the Task is passed to the ThreadPool to be executed by a worker thread, the <code>&lt;&gt;1__state</code> value is set to <strong>0</strong> (so the next time <code>MoveNext</code> is called, the next “synchronous” part (i.e. after the <strong>await</strong> call) will be executed). The code now calls <code>awaitUnsafeOnCompleted</code> to do its magic: adding a continuation to the <code>Compute2</code> task (the first <strong>awaiter</strong> parameter) so that <code>MoveNext</code> will be called on that same state machine (the second <strong>this</strong> parameter) when the task ends. The current thread then quietly returns.</p>
<p>So when the <code>Compute2</code> task ends, its continuation runs to call <code>MoveNext</code> this time with <code>&lt;&gt;1__state</code> as <strong>0</strong> so the last two lines are executed: <code>awaiter.GetResult()</code> returns immediately because the <code>Task</code> returned by <code>Compute2</code> already ended and the last <code>CinsumeCPUAfterCompute2</code> method is now called.</p>
<p>Here is a summary of what is happening:</p>
<ul>
<li>Each time you see an <strong>async</strong> method, the C# compiler is generating a dedicated state machine type with a <code>MoveNext</code> method that is responsible for executing your code synchronously between <strong>await</strong> calls</li>
<li>each time you see an <strong>await</strong> call, it means that a continuation will be added to the <code>Task</code> wrapping the <strong>async</strong> method to be executed. That continuation code will call the <code>MoveNext</code> method of the state machine of the calling method to execute the next piece of code up to its next <strong>await</strong> call.</li>
</ul>
<p><img loading="lazy" src="/posts/2021-01-19_understanding-reversed-callsta/1_Zf0qb_2Y8oKs9KO1Fli1rg.png"></p>
<p>This is why Visual Studio, trying to smartly match each async method state machine <code>MoveNext</code> frame to the method itself, shows reversed callstacks: the shown frames are the ones corresponding to the continuations after the <strong>await</strong> calls (in green in the previous figure).</p>
<p>Note that I described in more details how <strong>async</strong>/<strong>await</strong> is working and the action of <code>AwaitUnsageOnCompleted</code> during a <a href="https://youtu.be/8Ans2u4Bsi8?t=995">DotNext conference session</a> with <a href="https://twitter.com/KooKiz">Kevin</a> so feel free to watch the <a href="https://youtu.be/8Ans2u4Bsi8?t=995">recording at that particular time</a> if you want to go deeper.</p>
<p>The next post will describe what to do in Perfview to get more readable callstacks.</p>
<h3 id="stay-tuned">Stay tuned!</h3>
<hr>
<p><strong>Check out our latest posts on Medium:</strong></p>
<p><a href="https://medium.com/criteo-engineering/top-applications-of-graph-neural-networks-2021-c06ec82bfc18"><strong>Top Applications of Graph Neural Networks 2021</strong>
*GNNs have come a long way in academia. But do we have good applications of them in industry?*medium.com</a><a href="https://medium.com/criteo-engineering/top-applications-of-graph-neural-networks-2021-c06ec82bfc18"></a><a href="/posts/2020-12-08_build-your-own-net/"><strong>Build your own .NET CPU profiler in C#</strong>
*After describing memory allocation profiling it is now time to dig into the CPU sample profiling in C#!*medium.com</a></p>
<hr>
<p><strong>Join the crowd!</strong></p>
<p><a href="http://careers.criteo.com"><strong>Careers at Criteo | Criteo jobs</strong>
*Find opportunities everywhere. ​Choose your next challenge. *careers.criteo.com</a><a href="http://careers.criteo.com"></a></p>
]]></content:encoded></item><item><title>Build your own .NET CPU profiler in C#</title><link>https://chrisnas.github.io/posts/2020-12-08_build-your-own-net/</link><pubDate>Tue, 08 Dec 2020 10:27:37 +0000</pubDate><guid>https://chrisnas.github.io/posts/2020-12-08_build-your-own-net/</guid><description>After describing memory allocation profiling it is now time to dig into the CPU sample profiling in C#!</description><content:encoded><![CDATA[<hr>
<p>The last series was describing how to get details about your .NET application allocation patterns in C#.</p>
<ul>
<li><a href="/posts/2020-04-18_build-your-own-net/">Get a sampling of .NET application allocations</a></li>
<li><a href="/posts/2020-05-18_build-your-own-net/">A simple way to get the call stack</a></li>
<li><a href="/posts/2020-06-19_build-your-own-net/">Getting the call stack by hand</a></li>
</ul>
<p>It is now time to do the same but for the CPU consumption of your .NET applications.</p>
<h2 id="thanks-you-mr-windowskernel">Thanks you Mr Windows Kernel!</h2>
<p>Under Windows, the kernel ETW provider allows you to get notified every milli-second with the call stack of all threads running on a core. Without any surprise, it is easy with TraceEvent to listen to these events. As explained in an <a href="/posts/2018-07-26_grab-etw-session-providers/">old posts</a>, you simply need to create a session, enable providers and listen to the right event.</p>
<p>For sampled CPU profiling, I’m using the <code>TraceLogEventSource</code> to wrap the event source and automatically get the stack frames symbol resolution:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kt">string</span> <span class="n">sessionName</span> <span class="p">=</span> <span class="s">&#34;Cpu_Profiling_Session+&#34;</span> <span class="p">+</span> <span class="n">Guid</span><span class="p">.</span><span class="n">NewGuid</span><span class="p">().</span><span class="n">ToString</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="n">_session</span> <span class="p">=</span> <span class="k">new</span> <span class="n">TraceEventSession</span><span class="p">(</span><span class="n">sessionName</span><span class="p">,</span> <span class="n">TraceEventSessionOptions</span><span class="p">.</span><span class="n">Create</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(!</span><span class="n">EnableProviders</span><span class="p">(</span><span class="n">_session</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">_session</span><span class="p">.</span><span class="n">Dispose</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">_session</span> <span class="p">=</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">_profilingTask</span> <span class="p">=</span> <span class="n">Task</span><span class="p">.</span><span class="n">Factory</span><span class="p">.</span><span class="n">StartNew</span><span class="p">(()</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">using</span> <span class="p">(</span><span class="n">TraceLogEventSource</span> <span class="n">source</span> <span class="p">=</span> <span class="n">TraceLog</span><span class="p">.</span><span class="n">CreateFromTraceEventSession</span><span class="p">(</span><span class="n">_session</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// CPU sampling kernel events</span>
</span></span><span class="line"><span class="cl">        <span class="n">source</span><span class="p">.</span><span class="n">Kernel</span><span class="p">.</span><span class="n">PerfInfoSample</span> <span class="p">+=</span> <span class="p">(</span><span class="n">SampledProfileTraceData</span> <span class="n">data</span><span class="p">)</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="p">...</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// this call exits when the session is stopped</span>
</span></span><span class="line"><span class="cl">        <span class="n">source</span><span class="p">.</span><span class="n">Process</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>You need to enable three providers:</p>
<ul>
<li>Kernel: get the profiling event every milli-second and be notified when a dll gets loaded by a process to let TraceEvent manage the symbols</li>
<li>Clr: get JIT events describing managed method details</li>
<li>ClrRundown: get already JITted methods details</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">protected</span> <span class="kt">bool</span> <span class="n">EnableProviders</span><span class="p">(</span><span class="n">TraceEventSession</span> <span class="n">session</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">session</span><span class="p">.</span><span class="n">BufferSizeMB</span> <span class="p">=</span> <span class="m">256</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Note: it could fail if the user does not have the required privileges</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">success</span> <span class="p">=</span> <span class="n">session</span><span class="p">.</span><span class="n">EnableKernelProvider</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">KernelTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">ImageLoad</span> <span class="p">|</span>
</span></span><span class="line"><span class="cl">        <span class="n">KernelTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">Process</span> <span class="p">|</span>
</span></span><span class="line"><span class="cl">        <span class="n">KernelTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">Profile</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">stackCapture</span><span class="p">:</span> <span class="n">KernelTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">Profile</span>
</span></span><span class="line"><span class="cl">        <span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(!</span><span class="n">success</span><span class="p">)</span> <span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// this call always returns false  :^(</span>
</span></span><span class="line"><span class="cl">    <span class="n">session</span><span class="p">.</span><span class="n">EnableProvider</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">ProviderGuid</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">TraceEventLevel</span><span class="p">.</span><span class="n">Verbose</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="kt">ulong</span><span class="p">)(</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// events related to JITed methods</span>
</span></span><span class="line"><span class="cl">        <span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">Jit</span> <span class="p">|</span>                       <span class="c1">// Turning on JIT events is necessary to resolve JIT compiled code </span>
</span></span><span class="line"><span class="cl">        <span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">JittedMethodILToNativeMap</span> <span class="p">|</span> <span class="c1">// This is needed if you want line number information in the stacks</span>
</span></span><span class="line"><span class="cl">        <span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">Loader</span>                      <span class="c1">// You must include loader events as well to resolve JIT compiled code.</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// this provider will send events of already JITed methods</span>
</span></span><span class="line"><span class="cl">    <span class="n">session</span><span class="p">.</span><span class="n">EnableProvider</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ClrRundownTraceEventParser</span><span class="p">.</span><span class="n">ProviderGuid</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">TraceEventLevel</span><span class="p">.</span><span class="n">Verbose</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="kt">ulong</span><span class="p">)(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">Jit</span> <span class="p">|</span>              <span class="c1">// We need JIT events to be rundown to resolve method names</span>
</span></span><span class="line"><span class="cl">        <span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">JittedMethodILToNativeMap</span> <span class="p">|</span> <span class="c1">// This is needed if you want line number information in the stacks</span>
</span></span><span class="line"><span class="cl">        <span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">Loader</span> <span class="p">|</span>           <span class="c1">// As well as the module load events.  </span>
</span></span><span class="line"><span class="cl">        <span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">StartEnumeration</span>   <span class="c1">// This indicates to do the rundown now (at enable time)</span>
</span></span><span class="line"><span class="cl">        <span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The code to handle the event is really simple:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="n">source</span><span class="p">.</span><span class="n">Kernel</span><span class="p">.</span><span class="n">PerfInfoSample</span> <span class="p">+=</span> <span class="p">(</span><span class="n">SampledProfileTraceData</span> <span class="n">data</span><span class="p">)</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">ProcessID</span> <span class="p">!=</span> <span class="n">Pid</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">callstack</span> <span class="p">=</span> <span class="n">data</span><span class="p">.</span><span class="n">CallStack</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">callstack</span> <span class="p">==</span> <span class="kc">null</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">MergeCallStack</span><span class="p">(</span><span class="n">callstack</span><span class="p">,</span> <span class="n">Reader</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>I’m only interested in profiling a given process (hence the check on process id) and events with a call stack. The callstack is returned by the extension method <code>CallStack()</code> (see the <a href="/posts/2020-05-18_build-your-own-net/">previous post</a> for more details). The main processing is done by the <code>MergeCallStack()</code> method. But before looking at the only complicated part, it is time to discuss a useful tip.</p>
<h2 id="tip-use-etlxluke">Tip: use ETLx Luke!</h2>
<p>Like the previous posts about memory profiling, my goal is to demonstrate how to monitor applications as they run. However when you monitor an application CPU consumption, you would like to avoid any noisy neighbor that could highjack some cores. So minimizing the work of your profiling code is always a good idea. In addition, it could also be valuable to record the events and analyze them later. Microsoft <a href="https://github.com/microsoft/perfview/releases">Perfview</a> is the open source tool that I’m using the most to dig into CPU consumption. So the solution is to simply record the events and generate an .etlx file for Perfview.</p>
<p>The first code change is small: the session is created with a filename.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kt">string</span> <span class="n">sessionName</span> <span class="p">=</span> <span class="s">&#34;Cpu_Profiling_Session+&#34;</span> <span class="p">+</span> <span class="n">Guid</span><span class="p">.</span><span class="n">NewGuid</span><span class="p">().</span><span class="n">ToString</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="n">_session</span> <span class="p">=</span> <span class="k">new</span> <span class="n">TraceEventSession</span><span class="p">(</span><span class="n">sessionName</span><span class="p">,</span> <span class="n">_filename</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>I’m using a naming convention that contains the process ID I want to monitor so it will be easy to remember when I will analyze the recording in Perfview:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="n">profiler</span> <span class="p">=</span> <span class="k">new</span> <span class="n">EtlCpuSampleProfiler</span><span class="p">(</span><span class="s">$&#34;trace-{parameters.pid}.etl&#34;</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The second step to generate the .etlx file is a one liner:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">traceLog</span> <span class="p">=</span> <span class="n">TraceLog</span><span class="p">.</span><span class="n">OpenOrConvert</span><span class="p">(</span><span class="n">_filename</span><span class="p">,</span> <span class="k">new</span> <span class="n">TraceLogOptions</span><span class="p">()</span> <span class="p">{</span> <span class="n">ConversionLog</span> <span class="p">=</span> <span class="n">SymbolMessages</span> <span class="p">});</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <code>ConversionLog TraceLogOptions</code> property is expecting a <code>TextWriter</code> to log all possible messages related to symbols resolution.</p>
<p>The parsing of kernel profiling samples is done on the <code>TraceLog</code> in a more manual way by selecting the events based on <code>TaskGuid</code> corresponding to the kernel profiling task and the <code>OpCode</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="c1">// parse profiling kernel events</span>
</span></span><span class="line"><span class="cl"><span class="c1">// from https://github.com/microsoft/perfview/blob/master/src/TraceEvent/Samples/41_TraceLogMonitor.cs#L150</span>
</span></span><span class="line"><span class="cl"><span class="c1">// from https://docs.microsoft.com/en-us/windows/win32/etw/perfinfo</span>
</span></span><span class="line"><span class="cl"><span class="c1">// from https://github.com/microsoft/perfview/blob/master/src/TraceEvent/Parsers/KernelTraceEventParser.cs#L3128</span>
</span></span><span class="line"><span class="cl"><span class="c1">// and https://github.com/microsoft/perfview/blob/master/src/TraceEvent/Parsers/KernelTraceEventParser.cs#L2298</span>
</span></span><span class="line"><span class="cl"><span class="c1">//</span>
</span></span><span class="line"><span class="cl"><span class="n">Guid</span> <span class="n">perfInfoTaskGuid</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Guid</span><span class="p">(</span><span class="m">0xce1dbfb4</span><span class="p">,</span> <span class="m">0x137e</span><span class="p">,</span> <span class="m">0x4da6</span><span class="p">,</span> <span class="m">0x87</span><span class="p">,</span> <span class="m">0xb0</span><span class="p">,</span> <span class="m">0x3f</span><span class="p">,</span> <span class="m">0x59</span><span class="p">,</span> <span class="m">0xaa</span><span class="p">,</span> <span class="m">0x10</span><span class="p">,</span> <span class="m">0x2c</span><span class="p">,</span> <span class="m">0xbc</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="n">profileOpcode</span> <span class="p">=</span> <span class="m">46</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">data</span> <span class="k">in</span> <span class="n">traceLog</span><span class="p">.</span><span class="n">Events</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">ProcessID</span> <span class="p">!=</span> <span class="n">Pid</span><span class="p">)</span> <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">TaskGuid</span> <span class="p">!=</span> <span class="n">perfInfoTaskGuid</span><span class="p">)</span> <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">((</span><span class="kt">uint</span><span class="p">)</span><span class="n">data</span><span class="p">.</span><span class="n">Opcode</span> <span class="p">!=</span> <span class="n">profileOpcode</span><span class="p">)</span> <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">callstack</span> <span class="p">=</span> <span class="n">data</span><span class="p">.</span><span class="n">CallStack</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">callstack</span> <span class="p">==</span> <span class="kc">null</span><span class="p">)</span> <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">MergeCallStack</span><span class="p">(</span><span class="n">callstack</span><span class="p">,</span> <span class="n">Reader</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="how-to-merge-callstacks">How to “merge” call stacks</h2>
<p>In both live and file based implementations, I end up merging call stacks by calling the <code>MergeCallStack()</code> method. Instead of jumping directly into the C# code, I prefer to describe what I’m expecting from “merging“ call stacks.</p>
<p>If you think about what frames (i.e. method call) would appear at the beginning all these threads call stacks, it seems obvious that they should start with the same code: either the main thread startup, timer/thread pool initialization or custom thread bootstrap. In case of server applications, the same request processing calls would lead to specific handlers or controllers code. Each time a common group of frames appears in different call stacks, it would be more readable to see them as different branches starting from the same trunk like in Visual Studio Parallel Stack panel.</p>
<p><img loading="lazy" src="/posts/2020-12-08_build-your-own-net/1_Q6ry2HMPlwOTHGrhW0Avpg.png"></p>
<p>In order to build a “visual” representation, I have to count the number of time each frame appears at the same place in the recorded call stacks. My data structure looks like a tree where each node contains the current frame, the sampling count (as node or as leaf) and a list of different child frames corresponding to the different execution branches:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">MergedSymbolicStacks</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kt">int</span> <span class="n">_countAsNode</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kt">int</span> <span class="n">_countAsLeaf</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">ulong</span> <span class="n">Frame</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="kd">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">string</span> <span class="n">Symbol</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="kd">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">int</span> <span class="n">CountAsNode</span> <span class="p">=&gt;</span> <span class="n">_countAsNode</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">int</span> <span class="n">CountAsLeaf</span> <span class="p">=&gt;</span> <span class="n">_countAsLeaf</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">MergedSymbolicStacks</span><span class="p">&gt;</span> <span class="n">Stacks</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Each frame contains both the address and the method signature that have been extracted from the callstack retrieved from the events:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">protected</span> <span class="k">void</span> <span class="n">MergeCallStack</span><span class="p">(</span><span class="n">TraceCallStack</span> <span class="n">callStack</span><span class="p">,</span> <span class="n">SymbolReader</span> <span class="n">reader</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">currentFrame</span> <span class="p">=</span> <span class="n">callStack</span><span class="p">.</span><span class="n">Depth</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">frames</span> <span class="p">=</span> <span class="k">new</span> <span class="n">SymbolicFrame</span><span class="p">[</span><span class="n">callStack</span><span class="p">.</span><span class="n">Depth</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// the first element of callstack is the last frame: we need to iterate on each frame</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// up to the first one before adding them into the MergedSymbolicStack</span>
</span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="p">(</span><span class="n">callStack</span> <span class="p">!=</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">codeAddress</span> <span class="p">=</span> <span class="n">callStack</span><span class="p">.</span><span class="n">CodeAddress</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">codeAddress</span><span class="p">.</span><span class="n">Method</span> <span class="p">==</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">moduleFile</span> <span class="p">=</span> <span class="n">codeAddress</span><span class="p">.</span><span class="n">ModuleFile</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">moduleFile</span> <span class="p">!=</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="c1">// TODO: this seems to trigger extremely slow retrieval of symbols </span>
</span></span><span class="line"><span class="cl">                <span class="c1">//       through HTTP requests: see how to delay it AFTER the user</span>
</span></span><span class="line"><span class="cl">                <span class="c1">//       stops the profiling</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(!</span><span class="n">_missingSymbols</span><span class="p">.</span><span class="n">TryGetValue</span><span class="p">(</span><span class="n">moduleFile</span><span class="p">,</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">_</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="n">codeAddress</span><span class="p">.</span><span class="n">CodeAddresses</span><span class="p">.</span><span class="n">LookupSymbolsForModule</span><span class="p">(</span><span class="n">reader</span><span class="p">,</span> <span class="n">moduleFile</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                    <span class="k">if</span> <span class="p">(</span><span class="n">codeAddress</span><span class="p">.</span><span class="n">Method</span> <span class="p">==</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                    <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="n">_missingSymbols</span><span class="p">[</span><span class="n">moduleFile</span><span class="p">]</span> <span class="p">=</span> <span class="kc">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">}</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="n">frames</span><span class="p">[--</span><span class="n">currentFrame</span><span class="p">]</span> <span class="p">=</span> <span class="k">new</span> <span class="n">SymbolicFrame</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">codeAddress</span><span class="p">.</span><span class="n">Address</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">codeAddress</span><span class="p">.</span><span class="n">FullMethodName</span>
</span></span><span class="line"><span class="cl">            <span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">callStack</span> <span class="p">=</span> <span class="n">callStack</span><span class="p">.</span><span class="n">Caller</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">_stackCount</span><span class="p">++;</span>
</span></span><span class="line"><span class="cl">    <span class="n">_stacks</span><span class="p">.</span><span class="n">AddStack</span><span class="p">(</span><span class="n">frames</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <code>MergedSymbolicStack.AddStack()</code> method is doing the real merging. The idea of merging call stacks is to start from the bottom and if the frame has already been seen (at this position), increment its sampling count. If not, remember it before incrementing the count. Look at the next frame and do the same match/remember + increment up to the top of the stack.</p>
<p>Here is an animation of what it would look like on a piece of paper (like the one I wrote down before starting to write the C# implementation :^)</p>
<p><img loading="lazy" src="/posts/2020-12-08_build-your-own-net/1_31F18a8E4cGDevn4-V_pog.gif"></p>
<p>Here is the corresponding C# code to merge a stack (i.e. an array of frames)</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">void</span> <span class="n">AddStack</span><span class="p">(</span><span class="n">SymbolicFrame</span><span class="p">[]</span> <span class="n">frames</span><span class="p">,</span> <span class="kt">int</span> <span class="n">index</span> <span class="p">=</span> <span class="m">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">_countAsNode</span><span class="p">++;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">firstFrame</span> <span class="p">=</span> <span class="n">frames</span><span class="p">[</span><span class="n">index</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// search if the frame to add has already been seen</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">callstack</span> <span class="p">=</span> <span class="n">Stacks</span><span class="p">.</span><span class="n">FirstOrDefault</span><span class="p">(</span><span class="n">s</span> <span class="p">=&gt;</span> <span class="kt">string</span><span class="p">.</span><span class="n">CompareOrdinal</span><span class="p">(</span><span class="n">s</span><span class="p">.</span><span class="n">Symbol</span><span class="p">,</span> <span class="n">firstFrame</span><span class="p">.</span><span class="n">Symbol</span><span class="p">)</span> <span class="p">==</span> <span class="m">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// if not, we are starting a new branch</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">callstack</span> <span class="p">==</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">callstack</span> <span class="p">=</span> <span class="k">new</span> <span class="n">MergedSymbolicStacks</span><span class="p">(</span><span class="n">frames</span><span class="p">[</span><span class="n">index</span><span class="p">].</span><span class="n">Address</span><span class="p">,</span> <span class="n">frames</span><span class="p">[</span><span class="n">index</span><span class="p">].</span><span class="n">Symbol</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">Stacks</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="n">callstack</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// it was the last frame of the stack</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">index</span> <span class="p">==</span> <span class="n">frames</span><span class="p">.</span><span class="n">Length</span> <span class="p">-</span> <span class="m">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">callstack</span><span class="p">.</span><span class="n">_countAsLeaf</span><span class="p">++;</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">callstack</span><span class="p">.</span><span class="n">AddStack</span><span class="p">(</span><span class="n">frames</span><span class="p">,</span> <span class="n">index</span> <span class="p">+</span> <span class="m">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Last but not least, the constructors of the class reflect how to (1) create the root instance and (2) each node in the tree:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="n">MergedSymbolicStacks</span><span class="p">()</span> <span class="p">:</span> <span class="k">this</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// this will be the root of all stacks</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="n">MergedSymbolicStacks</span><span class="p">(</span><span class="kt">ulong</span> <span class="n">frame</span><span class="p">,</span> <span class="kt">string</span> <span class="n">symbol</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">Frame</span> <span class="p">=</span> <span class="n">frame</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">Symbol</span> <span class="p">=</span> <span class="n">symbol</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">_countAsNode</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">_countAsLeaf</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">Stacks</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">MergedSymbolicStacks</span><span class="p">&gt;();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The code to render the merged stack</p>
<p><img loading="lazy" src="/posts/2020-12-08_build-your-own-net/1_WIzDdkN_0nbUFiNnmKXe7A.png"></p>
<p>is not that complicated because everything is already in the tree of frames.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="k">void</span> <span class="n">RenderStack</span><span class="p">(</span><span class="n">MergedSymbolicStacks</span> <span class="n">stack</span><span class="p">,</span> <span class="n">IRenderer</span> <span class="n">visitor</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">isRoot</span><span class="p">,</span> <span class="kt">int</span> <span class="n">increment</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">alignment</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">string</span><span class="p">(</span><span class="sc">&#39; &#39;</span><span class="p">,</span> <span class="n">Padding</span> <span class="p">*</span> <span class="n">increment</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">padding</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">string</span><span class="p">(</span><span class="sc">&#39; &#39;</span><span class="p">,</span> <span class="n">Padding</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">currentFrame</span> <span class="p">=</span> <span class="n">stack</span><span class="p">.</span><span class="n">Frame</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// special root case</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">isRoot</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">visitor</span><span class="p">.</span><span class="n">WriteCount</span><span class="p">(</span><span class="s">$&#34;{Environment.NewLine}{alignment}{stack.CountAsNode, Padding} &#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">        <span class="n">visitor</span><span class="p">.</span><span class="n">WriteCount</span><span class="p">(</span><span class="s">$&#34;{Environment.NewLine}{alignment}{stack.CountAsLeaf + stack.CountAsNode, Padding} &#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">visitor</span><span class="p">.</span><span class="n">WriteMethod</span><span class="p">(</span><span class="n">stack</span><span class="p">.</span><span class="n">Symbol</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">childrenCount</span> <span class="p">=</span> <span class="n">stack</span><span class="p">.</span><span class="n">Stacks</span><span class="p">.</span><span class="n">Count</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">childrenCount</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">visitor</span><span class="p">.</span><span class="n">WriteFrameSeparator</span><span class="p">(</span><span class="s">&#34;&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">nextStackFrame</span> <span class="k">in</span> <span class="n">stack</span><span class="p">.</span><span class="n">Stacks</span><span class="p">.</span><span class="n">OrderByDescending</span><span class="p">(</span><span class="n">s</span> <span class="p">=&gt;</span> <span class="n">s</span><span class="p">.</span><span class="n">CountAsNode</span> <span class="p">+</span> <span class="n">s</span><span class="p">.</span><span class="n">CountAsLeaf</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// increment when more than 1 children</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">childIncrement</span> <span class="p">=</span> <span class="p">(</span><span class="n">childrenCount</span> <span class="p">==</span> <span class="m">1</span><span class="p">)</span> <span class="p">?</span> <span class="n">increment</span> <span class="p">:</span> <span class="n">increment</span> <span class="p">+</span> <span class="m">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">RenderStack</span><span class="p">(</span><span class="n">nextStackFrame</span><span class="p">,</span> <span class="n">visitor</span><span class="p">,</span> <span class="kc">false</span><span class="p">,</span> <span class="n">childIncrement</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">increment</span> <span class="p">!=</span> <span class="n">childIncrement</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">visitor</span><span class="p">.</span><span class="n">WriteFrameSeparator</span><span class="p">(</span><span class="s">$&#34;{Environment.NewLine}{alignment}{padding}{nextStackFrame.CountAsNode + nextStackFrame.CountAsLeaf, Padding} &#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="n">visitor</span><span class="p">.</span><span class="n">WriteFrameSeparator</span><span class="p">(</span><span class="s">$&#34;~~~~ &#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <code>IRenderer</code> interface implementations are simply changing foreground color depending on what kind of information to display:</p>
<p>I have used the same “Visitor” pattern for the <a href="https://github.com/chrisnas/DebuggingExtensions/tree/master/src/ParallelStacks.Runtime"><strong>pstack</strong></a> tool/extension for WinDBG.</p>
<h2 id="not-for-adminonly">Not for Admin only</h2>
<p>I always thought that I needed to be a member of the Administrator group and running elevated to be allowed to start a kernel profiling session. Well… This is in fact not the case! You have to dig into the documentation for <a href="https://docs.microsoft.com/en-us/windows/win32/etw/configuring-and-starting-a-systemtraceprovider-session">configuring and starting a <strong>SystemTraceProvider</strong> session</a> to read the following note:</p>
<p>If you want a non-administrators or a non-TCB process to be able to start a profiling trace session using the <code>SystemTraceProvider</code> on behalf of third party applications, then you need to grant the user profile privilege and then add this user to both the session <strong>GUID</strong> (created for the logger session) and the system trace provider <strong>GUID</strong> to enable the system trace provider. For more information, see the <a href="https://docs.microsoft.com/en-us/windows/desktop/api/Evntcons/nf-evntcons-eventaccesscontrol"><strong>EventAccessControl</strong></a> function.</p>
<p>Long story short, you need a user to be part of the <strong>Performance Log Users</strong> group (makes sense) or grant her the TRACELOG_ACCESS_REALTIME permission. Obviously, you need an administrator account to do both but this can be done once on a machine by your IT in a secure way.</p>
<p>I wrapped a managed implementation of the corresponding code to add the permission in a <code>ProfilingPermission</code> class that hides all the P/Invoke and weird marshalling stuff to the native Windows API. Simply pass a user name to <code>EnableProfileUser()</code> and it should work just fine.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">  1
</span><span class="lnt">  2
</span><span class="lnt">  3
</span><span class="lnt">  4
</span><span class="lnt">  5
</span><span class="lnt">  6
</span><span class="lnt">  7
</span><span class="lnt">  8
</span><span class="lnt">  9
</span><span class="lnt"> 10
</span><span class="lnt"> 11
</span><span class="lnt"> 12
</span><span class="lnt"> 13
</span><span class="lnt"> 14
</span><span class="lnt"> 15
</span><span class="lnt"> 16
</span><span class="lnt"> 17
</span><span class="lnt"> 18
</span><span class="lnt"> 19
</span><span class="lnt"> 20
</span><span class="lnt"> 21
</span><span class="lnt"> 22
</span><span class="lnt"> 23
</span><span class="lnt"> 24
</span><span class="lnt"> 25
</span><span class="lnt"> 26
</span><span class="lnt"> 27
</span><span class="lnt"> 28
</span><span class="lnt"> 29
</span><span class="lnt"> 30
</span><span class="lnt"> 31
</span><span class="lnt"> 32
</span><span class="lnt"> 33
</span><span class="lnt"> 34
</span><span class="lnt"> 35
</span><span class="lnt"> 36
</span><span class="lnt"> 37
</span><span class="lnt"> 38
</span><span class="lnt"> 39
</span><span class="lnt"> 40
</span><span class="lnt"> 41
</span><span class="lnt"> 42
</span><span class="lnt"> 43
</span><span class="lnt"> 44
</span><span class="lnt"> 45
</span><span class="lnt"> 46
</span><span class="lnt"> 47
</span><span class="lnt"> 48
</span><span class="lnt"> 49
</span><span class="lnt"> 50
</span><span class="lnt"> 51
</span><span class="lnt"> 52
</span><span class="lnt"> 53
</span><span class="lnt"> 54
</span><span class="lnt"> 55
</span><span class="lnt"> 56
</span><span class="lnt"> 57
</span><span class="lnt"> 58
</span><span class="lnt"> 59
</span><span class="lnt"> 60
</span><span class="lnt"> 61
</span><span class="lnt"> 62
</span><span class="lnt"> 63
</span><span class="lnt"> 64
</span><span class="lnt"> 65
</span><span class="lnt"> 66
</span><span class="lnt"> 67
</span><span class="lnt"> 68
</span><span class="lnt"> 69
</span><span class="lnt"> 70
</span><span class="lnt"> 71
</span><span class="lnt"> 72
</span><span class="lnt"> 73
</span><span class="lnt"> 74
</span><span class="lnt"> 75
</span><span class="lnt"> 76
</span><span class="lnt"> 77
</span><span class="lnt"> 78
</span><span class="lnt"> 79
</span><span class="lnt"> 80
</span><span class="lnt"> 81
</span><span class="lnt"> 82
</span><span class="lnt"> 83
</span><span class="lnt"> 84
</span><span class="lnt"> 85
</span><span class="lnt"> 86
</span><span class="lnt"> 87
</span><span class="lnt"> 88
</span><span class="lnt"> 89
</span><span class="lnt"> 90
</span><span class="lnt"> 91
</span><span class="lnt"> 92
</span><span class="lnt"> 93
</span><span class="lnt"> 94
</span><span class="lnt"> 95
</span><span class="lnt"> 96
</span><span class="lnt"> 97
</span><span class="lnt"> 98
</span><span class="lnt"> 99
</span><span class="lnt">100
</span><span class="lnt">101
</span><span class="lnt">102
</span><span class="lnt">103
</span><span class="lnt">104
</span><span class="lnt">105
</span><span class="lnt">106
</span><span class="lnt">107
</span><span class="lnt">108
</span><span class="lnt">109
</span><span class="lnt">110
</span><span class="lnt">111
</span><span class="lnt">112
</span><span class="lnt">113
</span><span class="lnt">114
</span><span class="lnt">115
</span><span class="lnt">116
</span><span class="lnt">117
</span><span class="lnt">118
</span><span class="lnt">119
</span><span class="lnt">120
</span><span class="lnt">121
</span><span class="lnt">122
</span><span class="lnt">123
</span><span class="lnt">124
</span><span class="lnt">125
</span><span class="lnt">126
</span><span class="lnt">127
</span><span class="lnt">128
</span><span class="lnt">129
</span><span class="lnt">130
</span><span class="lnt">131
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">static</span> <span class="k">class</span> <span class="nc">ProfilingPermission</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">const</span> <span class="kt">uint</span> <span class="n">TRACELOG_GUID_ENABLE</span> <span class="p">=</span> <span class="m">0x0080</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">const</span> <span class="kt">int</span> <span class="n">NO_ERROR</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>  <span class="c1">// ERROR_SUCCESS in C++</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">const</span> <span class="kt">int</span> <span class="n">ERROR_INSUFFICIENT_BUFFER</span> <span class="p">=</span> <span class="m">122</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// read https://docs.microsoft.com/en-us/windows/win32/etw/configuring-and-starting-a-systemtraceprovider-session </span>
</span></span><span class="line"><span class="cl">    <span class="c1">// for more details </span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="k">void</span> <span class="n">EnableProfilerUser</span><span class="p">(</span><span class="kt">string</span> <span class="n">accountName</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// Kernel provider from https://github.com/microsoft/perfview/blob/master/src/TraceEvent/Parsers/KernelTraceEventParser.cs#L43</span>
</span></span><span class="line"><span class="cl">        <span class="n">Guid</span> <span class="n">kernelProviderGuid</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Guid</span><span class="p">(</span><span class="s">&#34;{9e814aad-3204-11d2-9a82-006008a86939}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="kt">byte</span><span class="p">[]</span> <span class="n">sid</span> <span class="p">=</span> <span class="n">LookupSidByName</span><span class="p">(</span><span class="n">accountName</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// from https://docs.microsoft.com/en-us/windows/win32/etw/configuring-and-starting-a-systemtraceprovider-session</span>
</span></span><span class="line"><span class="cl">        <span class="kt">uint</span> <span class="n">operation</span> <span class="p">=</span> <span class="p">(</span><span class="kt">uint</span><span class="p">)</span><span class="n">EventSecurityOperation</span><span class="p">.</span><span class="n">EventSecurityAddDACL</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kt">uint</span> <span class="n">rights</span> <span class="p">=</span> <span class="n">TRACELOG_GUID_ENABLE</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kt">bool</span> <span class="n">allowOrDeny</span> <span class="p">=</span> <span class="p">(</span><span class="s">&#34;Allow&#34;</span> <span class="p">!=</span> <span class="kc">null</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="kt">uint</span> <span class="n">result</span> <span class="p">=</span> <span class="n">EventAccessControl</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="k">ref</span> <span class="n">kernelProviderGuid</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">operation</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">sid</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">rights</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">allowOrDeny</span>
</span></span><span class="line"><span class="cl">        <span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">result</span> <span class="p">!=</span> <span class="n">NO_ERROR</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">lastErrorMessage</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Win32Exception</span><span class="p">((</span><span class="kt">int</span><span class="p">)</span><span class="n">result</span><span class="p">).</span><span class="n">Message</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">throw</span> <span class="k">new</span> <span class="n">InvalidOperationException</span><span class="p">(</span><span class="s">$&#34;Failed to add ACL ({result.ToString()}) : {lastErrorMessage}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">static</span> <span class="kt">byte</span><span class="p">[]</span> <span class="n">LookupSidByName</span><span class="p">(</span><span class="kt">string</span> <span class="n">accountName</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">byte</span><span class="p">[]</span> <span class="n">sid</span> <span class="p">=</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kt">uint</span> <span class="n">cbSid</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">StringBuilder</span> <span class="n">referencedDomainName</span> <span class="p">=</span> <span class="k">new</span> <span class="n">StringBuilder</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="kt">uint</span> <span class="n">cchReferencedDomainName</span> <span class="p">=</span> <span class="p">(</span><span class="kt">uint</span><span class="p">)</span><span class="n">referencedDomainName</span><span class="p">.</span><span class="n">Capacity</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">SID_NAME_USE</span> <span class="n">sidUse</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kt">int</span> <span class="n">err</span> <span class="p">=</span> <span class="n">NO_ERROR</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(!</span><span class="n">LookupAccountName</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="n">accountName</span><span class="p">,</span> <span class="n">sid</span><span class="p">,</span> <span class="k">ref</span> <span class="n">cbSid</span><span class="p">,</span> <span class="n">referencedDomainName</span><span class="p">,</span> <span class="k">ref</span> <span class="n">cchReferencedDomainName</span><span class="p">,</span> <span class="k">out</span> <span class="n">sidUse</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">err</span> <span class="p">=</span> <span class="n">Marshal</span><span class="p">.</span><span class="n">GetLastWin32Error</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">err</span> <span class="p">==</span> <span class="n">ERROR_INSUFFICIENT_BUFFER</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">sid</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">byte</span><span class="p">[</span><span class="n">cbSid</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">                <span class="n">referencedDomainName</span><span class="p">.</span><span class="n">EnsureCapacity</span><span class="p">((</span><span class="kt">int</span><span class="p">)</span><span class="n">cchReferencedDomainName</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                <span class="n">err</span> <span class="p">=</span> <span class="n">NO_ERROR</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(!</span><span class="n">LookupAccountName</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="n">accountName</span><span class="p">,</span> <span class="n">sid</span><span class="p">,</span> <span class="k">ref</span> <span class="n">cbSid</span><span class="p">,</span> <span class="n">referencedDomainName</span><span class="p">,</span> <span class="k">ref</span> <span class="n">cchReferencedDomainName</span><span class="p">,</span> <span class="k">out</span> <span class="n">sidUse</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">                    <span class="n">err</span> <span class="p">=</span> <span class="n">Marshal</span><span class="p">.</span><span class="n">GetLastWin32Error</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">err</span> <span class="p">!=</span> <span class="n">NO_ERROR</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">lastErrorMessage</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Win32Exception</span><span class="p">(</span><span class="n">err</span><span class="p">).</span><span class="n">Message</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">throw</span> <span class="k">new</span> <span class="n">InvalidOperationException</span><span class="p">(</span><span class="s">$&#34;LookupAccountName fails ({err.ToString()}) : {lastErrorMessage}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// display the SID associated to the given user</span>
</span></span><span class="line"><span class="cl">        <span class="n">IntPtr</span> <span class="n">ptrSid</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(!</span><span class="n">ConvertSidToStringSid</span><span class="p">(</span><span class="n">sid</span><span class="p">,</span> <span class="k">out</span> <span class="n">ptrSid</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">err</span> <span class="p">=</span> <span class="n">Marshal</span><span class="p">.</span><span class="n">GetLastWin32Error</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">lastErrorMessage</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Win32Exception</span><span class="p">(</span><span class="n">err</span><span class="p">).</span><span class="n">Message</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;No SID string associated to user {accountName} ({err.ToString()}) : {lastErrorMessage}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kt">string</span> <span class="n">sidString</span> <span class="p">=</span> <span class="n">Marshal</span><span class="p">.</span><span class="n">PtrToStringAuto</span><span class="p">(</span><span class="n">ptrSid</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="n">ProfilingPermission</span><span class="p">.</span><span class="n">LocalFree</span><span class="p">(</span><span class="n">ptrSid</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;Account ({referencedDomainName}){accountName} mapped to {sidString}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">sid</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">    [DllImport(&#34;Sechost.dll&#34;, SetLastError = true)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">static</span> <span class="kd">extern</span> <span class="kt">uint</span> <span class="n">EventAccessControl</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="k">ref</span> <span class="n">Guid</span> <span class="n">providerGuid</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="kt">uint</span> <span class="n">operation</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="na">        [MarshalAs(UnmanagedType.LPArray)]</span> <span class="kt">byte</span><span class="p">[]</span> <span class="n">Sid</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="kt">uint</span> <span class="n">right</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="kt">bool</span> <span class="n">allowOrDeny</span> <span class="c1">// true means ALLOW</span>
</span></span><span class="line"><span class="cl">        <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">    [DllImport(&#34;kernel32.dll&#34;)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">static</span> <span class="kd">extern</span> <span class="n">IntPtr</span> <span class="n">LocalFree</span><span class="p">(</span><span class="n">IntPtr</span> <span class="n">hMem</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">    [DllImport(&#34;advapi32.dll&#34;, SetLastError = true)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">static</span> <span class="kd">extern</span> <span class="kt">bool</span> <span class="n">LookupAccountName</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="kt">string</span> <span class="n">systemName</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="kt">string</span> <span class="n">accountName</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="na">        [MarshalAs(UnmanagedType.LPArray)]</span> <span class="kt">byte</span><span class="p">[]</span> <span class="n">Sid</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="k">ref</span> <span class="kt">uint</span> <span class="n">cbSid</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">StringBuilder</span> <span class="n">referencedDomainName</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="k">ref</span> <span class="kt">uint</span> <span class="n">cchReferencedDomainName</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="k">out</span> <span class="n">SID_NAME_USE</span> <span class="n">nameUse</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">    [DllImport(&#34;advapi32.dll&#34;, CharSet = CharSet.Auto, SetLastError = true)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">static</span> <span class="kd">extern</span> <span class="kt">bool</span> <span class="n">ConvertSidToStringSid</span><span class="p">(</span>
</span></span><span class="line"><span class="cl"><span class="na">        [MarshalAs(UnmanagedType.LPArray)]</span> <span class="kt">byte</span><span class="p">[]</span> <span class="n">pSID</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="k">out</span> <span class="n">IntPtr</span> <span class="n">ptrSid</span><span class="p">);</span> <span class="c1">// can&#39;t be an out string because we need to explicitly call LocalFree on it;</span>
</span></span><span class="line"><span class="cl">                            <span class="c1">// the marshaller would call CoTaskMemFree in case of a string</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// from http://pinvoke.net/default.aspx/advapi32/LookupAccountName.html</span>
</span></span><span class="line"><span class="cl">    <span class="kd">enum</span> <span class="n">SID_NAME_USE</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">SidTypeUser</span> <span class="p">=</span> <span class="m">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">SidTypeGroup</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">SidTypeDomain</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">SidTypeAlias</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">SidTypeWellKnownGroup</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">SidTypeDeletedAccount</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">SidTypeInvalid</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">SidTypeUnknown</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">SidTypeComputer</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// from evntcons.h</span>
</span></span><span class="line"><span class="cl">    <span class="kd">enum</span> <span class="n">EventSecurityOperation</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">EventSecuritySetDACL</span> <span class="p">=</span> <span class="m">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">EventSecuritySetSACL</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">EventSecurityAddDACL</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">EventSecurityAddSACL</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">EventSecurityMax</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="c1">// EVENTSECURITYOPERATION</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>You are now ready to profile your application memory allocation patterns and CPU consumption!</p>
<hr>
<p><strong>Thanks for checking in with us again on our C# series. Like what you are reading? Head over to our latest blog posts on the topic:</strong></p>
<p><a href="https://medium.com/criteo-labs/build-your-own-net-memory-profiler-in-c-allocations-1-2-9c9f0c86cefd"><strong>Build your own .NET memory profiler in C#</strong>
*This post explains how to collect allocation details by writing your own memory profiler in C#.*medium.com</a><a href="https://medium.com/criteo-labs/build-your-own-net-memory-profiler-in-c-allocations-1-2-9c9f0c86cefd"></a><a href="/posts/2020-05-18_build-your-own-net/"><strong>Build your own .NET memory profiler in C# — call stacks (2/2–1)</strong>
*This post explains how to get the call stack corresponding to the allocations with CLR events.*medium.com</a></p>
<hr>
<p><strong>If you are interested in joining our team, check out our open positions and apply today!</strong></p>
<p><a href="http://careers.criteo.com"><strong>Careers at Criteo | Criteo jobs</strong>
*Find opportunities everywhere. ​Choose your next challenge. Find the job opportunities at Criteo in Product, research &amp;…*careers.criteo.com</a><a href="http://careers.criteo.com"></a></p>
]]></content:encoded></item><item><title>How to write your own commands in dotnet-dump (2/2)</title><link>https://chrisnas.github.io/posts/2020-11-09_how-to-write-commands/</link><pubDate>Mon, 09 Nov 2020 16:27:38 +0000</pubDate><guid>https://chrisnas.github.io/posts/2020-11-09_how-to-write-commands/</guid><description>This post describes the different steps, tips and tricks to write your own commands for dotnet-dump</description><content:encoded><![CDATA[<hr>
<p>In the <a href="/posts/2020-09-29_how-to-extend-dotnet/">previous post</a>, I presented the new commands that were added to dotnet-dump and how to use them. It is now time to show how to implement such a command.</p>
<p>But before jumping into the code, you should first ensure that you have a valid use case that the Diagnostics team is not currently working on. I recommend to create an issue in the Diagnostics repository to explain what is missing for which scenario and propose to implement the corresponding command.</p>
<h2 id="what-is-a-dotnet-dump-command">What is a dotnet-dump command?</h2>
<p>Here is the directory structure related to the dotnet-dump tool in the Diagnostics repository:</p>
<p><img loading="lazy" src="/posts/2020-11-09_how-to-write-commands/1_tlD3As6iHhwIWjL_FeyMSw.png"></p>
<p>The built binaries are generated under artifacts\bin\dotnet-dump&lt;Release or Debug&gt;\netcoreapp2.1 folder if you need to test them outside of Visual Studio.</p>
<p>The <strong>eng</strong> folder contains the <strong>versions.props</strong> file that lists the versions for nuget dependencies. In my case, I had to reference the ParallelStacks.Runtime nuget so I added the following line:<code>2.0.1</code></p>
<p>And in the <strong>dotnet-dump.csproj</strong>, this nuget is referenced with the same variable:
`</p>
<hr>
<p><strong>Missed the first part of the story? Read it here:</strong></p>
<p><a href="/posts/2020-09-29_how-to-extend-dotnet/"><strong>How to extend dotnet-dump (1/2) — What are the new commands?</strong>
*This first post describes the new commands, when to use them, and the git setup I used to implement them.*medium.com</a></p>
<p><strong>Want to work with Christophe or other teams? Check out our open positions:</strong></p>
<p><a href="http://careers.criteo.com"><strong>Careers at Criteo | Criteo jobs</strong>
*Find opportunities everywhere. ​Choose your next challenge. Find the job opportunities at Criteo in Product, research &amp;…*careers.criteo.com</a><a href="http://careers.criteo.com"></a></p>
]]></content:encoded></item><item><title>The .NET Core Journey at Criteo</title><link>https://chrisnas.github.io/posts/2020-07-31_the-net-core-journey/</link><pubDate>Fri, 31 Jul 2020 15:55:50 +0000</pubDate><guid>https://chrisnas.github.io/posts/2020-07-31_the-net-core-journey/</guid><description>This post shows the challenges we faced during the migration to .NET Core on containerized Linux for our main application.</description><content:encoded><![CDATA[<hr>
<p><img loading="lazy" src="/posts/2020-07-31_the-net-core-journey/1_WFbx_DPjik2EQydCiAahUA.jpeg"></p>
<h2 id="introduction">Introduction</h2>
<p>When I arrived at Criteo in late 2016, I joined the .NET Core “guild” (i.e. group of people from different teams dedicated to a specific topic). The first meeting I attended included Microsoft folks led by Scott Hunter (head of .NET program management) and including David Fowler (SignalR and ASP.NET Core). The goal for Criteo was simple: Moving a set of C# applications from Windows/.NET Framework to Linux/.NET Core. I guess that for Microsoft we were a customer with workloads that could be interesting to support with .NET Core. At that time, I did not realize how strong their commitment to work with us was. Our Open Source mindset was the selling point.</p>
<p>How complicated could it be? Well… this post will show you the challenges that we had to face to run, monitor and debug our applications.</p>
<hr>
<h2 id="try-it">Try it</h2>
<p>Once we got a build of all .NET Core assemblies (more on this in a forthcoming blog post), it was time to run a few applications. The first issues that we faced were related to missing features between .NET Framework and .NET Core. For example, we need cryptography support of <a href="https://github.com/dotnet/corefx/issues/4647">3DES and AES with cypher mode CFB</a> but it is (still) not available in .NET Core for Linux. Thanks to the Open Source status of .NET Core, we were able to <a href="https://github.com/criteo-forks/corefx/tree/aes_3des_cfb_mode_implementation_unix">add it to CoreFx</a>. However, since we did not implement it on MacOS/Windows as Microsoft requested for our change to be accepted as a Pull Request, we had to keep our Criteo-forked branch.</p>
<p>The second class of runtime problems we had to solve were due to differences between Windows and Linux but also with the “containerization” of the runtime environment. Let’s take two examples involving the .NET Garbage Collector. First, our containers were using Linux cgroups to manage quotas including memory and number of CPU cores usable by applications. However, at CLR startup, the GC was counting the <strong>total</strong> count of CPU cores to compute the number of heaps to allocate instead of the one defined at the cgroup level: We ended up with instant Out Of Memory automatic killing. This time our fix was done and merged in the CLR repository.</p>
<p>The second example is related to a GC optimization: During background generation 2 collections, the CLR threads working underneath are affinitized to each different CPU core to avoid locks. We were lucky enough to welcome <a href="https://twitter.com/@maoni0">Maoni Stephens</a> (Lead Dev on the GC) in our Paris office early 2018 to share our weird allocation patterns that impacted the GC. During her stay, she was kind enough to help us investigate a behavior on our servers: When <a href="https://docs.microsoft.com/en-us/sysinternals/downloads/process-explorer?WT.mc_id=DT-MVP-5003325">SysInternals ProcessExplorer</a> was running, the garbage collections were taking more time than usual. Maoni found out ProcessExplorer had an affinitized high priority thread conflicting with GC threads. During investigations related to longer response time on Linux compared to Windows. We realized that GC threads were not affinitized like it was the case on Windows and the issue was <a href="https://github.com/dotnet/coreclr/pull/24801">fixed by Jan Vorlicek</a>.</p>
<p><em>Here is our lesson: Sometimes fixes are merged into the official release and sometimes they are not. If your workloads are pushing .NET to its limits, you will probably have to build and manage your <em><a href="https://github.com/criteo-forks/coreclr"><em>own Core fork</em></a></em> and make it available to your deployments.</em></p>
<h2 id="monitor-it">Monitor it</h2>
<p>At Criteo, our Grafana dashboards measuring .NET Framework application health were based on metrics computed from Windows performance counters. Even without going to Linux, .NET Core is no more exposing performance counters so we had to entirely rebuild our metrics collection system!</p>
<p>Based on Microsoft feedbacks, we decided to listen to CLR events emitted via ETW on Windows and LTTng on Linux. In addition to work for both Operating Systems, these events are also providing accurate details about thread contention, exceptions and garbage collections not available with Performance counters. Please refer to our <a href="/posts/2019-10-17_how-to-expose-your/">series of blog posts</a> for more details and reusable code samples to integrate these events into your own systems.</p>
<p>Our first Linux metrics collection implementation was based on LTTng and we presented our journey during the <a href="https://www.youtube.com/watch?v=pMl9RM9h2eg&amp;list=PLuo4E47p5_7bfeZyYIyNYM-f-2tmr0neu&amp;index=6">Tracing Summit in 2017</a>. Microsoft already built <a href="https://github.com/microsoft/perfview/blob/master/documentation/TraceEvent/TraceEventLibrary.md">TraceEvent</a>, an assembly allowing .NET code to parse CLR events for both Windows and Linux. Unfortunately for us, the Linux part was only able to load traces files but we needed live session like on Windows where you can listen to events emitted by running applications. Since this code is Open Source, <a href="https://twitter.com/@GregoryLeocadie">Gregory</a> was able to add the <a href="https://github.com/microsoft/perfview/pull/340">live session feature</a> to TraceEvent.</p>
<p>With .NET Core 3.0 Microsoft provided a way to exchange events common to Linux and Windows called EventPipes. So… we moved our collection implementation from LTTng to EventPipe (look at our <a href="/posts/2019-10-17_how-to-expose-your/">blog series</a> and <a href="https://www.youtube.com/watch?v=Jpoy3O6x-wM">DotNext conference session</a> for more details and reusable code sample). With the new EventPipe implementation in the CLR came performance issues not seen by Microsoft. The reason is simple: Some of our applications are running hundreds of threads to process thousands of requests per second and allocate memory like crazy. In that kind of context, the CLR has a lot to do and so, has a lot of events to generate and emit via LTTng or EventPipes.</p>
<p><img loading="lazy" src="/posts/2020-07-31_the-net-core-journey/1_P_gRXkTbBaDLhsQSnWDJIQ.png"></p>
<p>The initial implementation was <a href="https://github.com/dotnet/runtime/issues/12204">lacking some</a> filtering and too many events were generated or expensive event payload was created even though the events were not emitted. Based on our feedback, the Microsoft Diagnostic team was very responsive and quickly fixed the problem.</p>
<p><em>Microsoft did not “just” move to Open Source, the teams are working deeply integrated with the issue/pull request model of GitHub. So don’t be shy and if you find a problem, create an issue with a detailed reproduction and even better, provide a pull request with the fix. Everyone in the community will benefit!</em></p>
<h2 id="run-it">Run it</h2>
<p>With these metrics, we started to investigate some performance differences (mostly response time) between Windows and containerized Linux.</p>
<p><img loading="lazy" src="/posts/2020-07-31_the-net-core-journey/1_h41QfdE5wVef3pD8DZ6twA.png"></p>
<p>We saw a huge performance difference on Linux: Both response time (x2) and scalability (timeout increase with QPS). Our team spent a lot of time to improve the situation up to the point where it was possible to send the applications to production.</p>
<p>In the new containerized environment we faced the same kind of <em>noisy neighbor</em> symptoms that we had with Process Explorer. If the CPU cores are not dedicated to a container (as it was for us at the beginning), this scenario happens a lot. So we updated the scheduling system to dedicate CPU cores to containers.</p>
<p>On a totally different area, we found out that the way .NET Core handles network I/O continuation had an impact on our main application. To give a bit of context, this application has to handle a lot of requests and is response-time driven. During the processing of a request, the current thread might have to send an HTTP request before continuing its processing. Since this is done asynchronously, the thread is now available to process more incoming requests and this is good for throughput. However, it means that when the inner HTTP request comes back, all available threads might be processing new incoming requests and it will take time to complete the old one. The net effect is to increase the median response time and this is not something we want!</p>
<p>The .NET Core implementation is relying on the .NET ThreadPool that shares its threads with all the async/await magic and the incoming requests processing (The .NET Framework implementation is using a totally different implementation based on I/O completion ports on Windows). To solve the issue, <a href="https://twitter.com/KooKiz">Kevin</a> <a href="https://github.com/criteo-forks/corefx/commit/dda2c4d80fd2d74b3dc7e0833e2a6794f1e290d3">implemented a custom thread pool</a> to handle network I/O and we keep on <a href="https://github.com/criteo-forks/corefx/commit/2acc917aef47798243cc221afc9b360c86ed60b7">optimizing it</a>. When you work on this kind of deep area of code-shared by so many different workloads, you realize that it is impossible to find the silver bullet.</p>
<h2 id="debug-it">Debug it</h2>
<p>What would you do if something would go wrong in an application? On Windows, with Visual Studio, we are able to remote debug a rogue application to set a breakpoint, look at fields and properties or even have a high-level view of what threads are doing with the ParallelStacks view. In the worst case, SysInternals <a href="https://docs.microsoft.com/en-us/visualstudio/debugger/remote-debugging-dotnet-core-linux-with-ssh?WT.mc_id=DT-MVP-5003325&amp;view=vs-2019">procdump </a>allows us to take a snapshot of the application and analyze it on our developer’s machine with WinDBG or Visual Studio.</p>
<p>In terms of remote debugging a Linux application, Microsoft provides an <a href="https://docs.microsoft.com/en-us/visualstudio/debugger/remote-debugging-dotnet-core-linux-with-ssh?WT.mc_id=DT-MVP-5003325">SSH-based solution</a> to attach to a running application. However, for security reasons, it is not allowed to run an SSH server in our Criteo containers. <em>The solution was to implement the communication protocol with VsDbg for Linux on top of WebSockets.</em></p>
<p><img loading="lazy" src="/posts/2020-07-31_the-net-core-journey/1_dBiRXngqIZIMqAyQQ1PryA.png"></p>
<p>Well… this was not enough. Hosting architecture (Marathon and Mesos in our case) ensures that applications in containers are running smoothly by sending requests to <em>health check</em> endpoints. If the application replies that everything is fine, then the container is safe. If the application does not answer as expected (including retries), then Marathon/Mesos kills the application and cleans up the container. Now think about what will happen if you set a breakpoint in the application and you dig into the data structures content in Visual Studio Watch/Quick Watch panels for a few minutes. Behind the scene, the debugger has to freeze all application threads, including the ones from the thread pool responsible to answer health checks. As you have probably guessed already, the debugging session will not end well.</p>
<p>This is why the previous figure shows an arrow between Marathon and the Remote Debugger which acts as a proxy for the application health check. When a debugging session starts (i.e. when the WebSockets code executes the protocol), the Remote Debugger knows that it should answer OK instead of calling the application endpoint that might never answer.</p>
<p>When remote debugging is not enough, how do you take a memory snapshot of the application? For example, if the health check does not answer after a series of retry, the Remote Debugger is calling the <a href="https://github.com/dotnet/runtime/blob/master/docs/design/coreclr/botr/xplat-minidump-generation.md">createdump tool</a> installed with the .NET Core runtime to generate a dump file. Again, since the memory dump creation of 40+ GB applications could take several minutes, the same health check proxy mechanism has been put in place.</p>
<p>Once the dump file is created, the remote debugger let Marathon kill the application. But wait! This is not enough because in that case, the container will be cleaned up and the disk storage will disappear. Not a problem, after a dump has been generated by createdump, the file is sent to a “Dump Navigator” application (one per data center). This application is providing a simple HTML user interface to get high-level details of the application state such as thread stacks or managed heap content.</p>
<p><img loading="lazy" src="/posts/2020-07-31_the-net-core-journey/1_jcwiOFsn6SN305A_f_vuxQ.png"></p>
<p>On Windows, we have built our own set of <a href="https://github.com/chrisnas/DebuggingExtensions/blob/master/Documentation/gsose.md">extension commands</a> that allow us to investigate memory, threadpool starvation, thread contention, or timer leak scenarios in a Windows memory dump with WinDBG as shown during this <a href="https://www.youtube.com/watch?v=biDJkJ4L_K8">NDC Oslo conference session</a>. Note that they are also <a href="https://github.com/kevingosse/LLDB-LoadManaged">usable with LLDB</a> on Linux. These commands are leveraging the <a href="https://github.com/microsoft/clrmd">ClrMD Microsoft library</a> that gives you access to a live process or a memory dump in C#. Thanks to the Linux support that has been added to this library by Microsoft developers, it was easy to reuse the code into our Dump Navigator application. I definitively recommend to look at the API provided by ClrMD to automate and build your own tools. The <a href="/posts/2019-12-31_getting-another-view-on/">long Criteo blog series</a> is a good start in addition to my <a href="https://www.youtube.com/watch?v=O8c5WwfbGFU">DotNext conference session</a>.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Even though some of our main applications moved to .NET Core running on containerized Linux with a large set of monitoring/debugging tools, the journey is not over. We are now testing the preview of .NET Core 5.0 (like we did for 3.0) to check if it supports Criteo specific needs. If this is not the case, we will figure out why and find solutions to integrate into the code. Same for the tools: I have started to <a href="https://github.com/dotnet/diagnostics/pull/1376">add our extension commands</a> to Microsoft dotnet-dump CLI tool used to analyze both Windows and Linux dumps.</p>
<p>At least we could say that we not only helped ourselves but also Microsoft to understand how far .NET Core could go and even the whole .NET Windows and Linux community. This is where Open Source shines!</p>
<hr>
<p><strong>Stay tuned for the next article in our mini-series. Don’t forget to head over to our previous articles of this journey:</strong></p>
<p><a href="https://medium.com/criteo-labs/migrating-arbitrage-to-apache-mesos-3f474179ec0b"><strong>Migrating Arbitrage to Apache Mesos</strong>
*Lessons learned from migrating our largest application to our container platform.*medium.com</a><a href="https://medium.com/criteo-labs/migrating-arbitrage-to-apache-mesos-3f474179ec0b"></a><a href="https://medium.com/criteo-labs/moving-net-to-linux-at-scale-d8ff49b42661"><strong>Moving .NET to Linux at Scale</strong>
*The story of a multi-year migration: How we changed Criteo’s whole foundation.*medium.com</a><a href="https://medium.com/criteo-labs/moving-net-to-linux-at-scale-d8ff49b42661"></a></p>
<hr>
<p><strong>Interested in joining the challenge? Head over to our career site!</strong></p>
<p><a href="https://careers.criteo.com/working-in-R&amp;D"><strong>Product, Research &amp; Development | Criteo Careers</strong>
careers.criteo.com</a><a href="https://careers.criteo.com/working-in-R&amp;D"></a></p>
]]></content:encoded></item><item><title>Build your own .NET memory profiler in C# — call stacks (2/2–2)</title><link>https://chrisnas.github.io/posts/2020-06-19_build-your-own-net/</link><pubDate>Fri, 19 Jun 2020 09:32:16 +0000</pubDate><guid>https://chrisnas.github.io/posts/2020-06-19_build-your-own-net/</guid><description>In this last episode I detail how to transform addresses from the stack into methods name and signature.</description><content:encoded><![CDATA[<hr>
<p>In the past two episodes of this series I have explained how to <a href="/posts/2020-04-18_build-your-own-net/">get a sampling of .NET application allocations</a> and <a href="/posts/2020-05-18_build-your-own-net/">one way to get the call stack</a> corresponding to the allocations; all with CLR events. In this last episode, I will detail how to transform addresses from the stack into methods name and possibly signature.</p>
<h2 id="from-managed-address-to-method-signature">From managed address to method signature</h2>
<p>In order to transform an address on the stack into a managed method name, you need to know where in memory (i.e. at which address) is stored the method JITted assembly code and what is its size:</p>
<p><img loading="lazy" src="/posts/2020-06-19_build-your-own-net/1_v73Nx1IxWIEQ3NzDZF0rsQ.png"></p>
<p>For each JITted method, the <code>MethodLoadVerbose</code>/<code>MethodDCStartVerboseV2</code> events are providing this information in addition to 3 properties to rebuild the full method name and signature (more on this later). I’m storing each method description as a <code>MethodInfo</code> into a <code>MethodStore</code> per process.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">PerProcessProfilingState</span> <span class="p">:</span> <span class="n">IDisposable</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">...</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">,</span> <span class="n">MethodStore</span><span class="p">&gt;</span> <span class="n">_methods</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">,</span> <span class="n">MethodStore</span><span class="p">&gt;();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">MethodStore</span> <span class="p">:</span> <span class="n">IDisposable</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// JITed methods information (start address + size + signature)</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">MethodInfo</span><span class="p">&gt;</span> <span class="n">_methods</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">...</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The only interesting part of the <code>MethodInfo</code> class is the computation of the full method name stored in the <code>_fullName</code> field:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">MethodInfo</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="kt">ulong</span> <span class="n">_startAddress</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="kt">int</span> <span class="n">_size</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="kt">string</span> <span class="n">_fullName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">...</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <code>ComputeFullName</code> helper merges together the 3 properties given by the <code>MethodxxxVerbose</code> events including special processing for constructors:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="kt">string</span> <span class="n">ComputeFullName</span><span class="p">(</span><span class="kt">ulong</span> <span class="n">startAddress</span><span class="p">,</span> <span class="kt">string</span> <span class="n">namespaceAndTypeName</span><span class="p">,</span> <span class="kt">string</span> <span class="n">name</span><span class="p">,</span> <span class="kt">string</span> <span class="n">signature</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">fullName</span> <span class="p">=</span> <span class="n">signature</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// constructor case: name = .ctor | namespaceAndTypeName = A.B.typeName | signature = ...  (parameters)</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// --&gt; A.B.typeName(parameters)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">name</span> <span class="p">==</span> <span class="s">&#34;.ctor&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="s">$&#34;{namespaceAndTypeName}{ExtractParameters(signature)}&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// general case: name = Foo | namespaceAndTypeName = A.B.typeName | signature = ...  (parameters)</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// --&gt; A.B.Foo(parameters)</span>
</span></span><span class="line"><span class="cl">    <span class="n">fullName</span> <span class="p">=</span> <span class="s">$&#34;{namespaceAndTypeName}.{name}{ExtractParameters(signature)}&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">fullName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="kt">string</span> <span class="n">ExtractTypeName</span><span class="p">(</span><span class="kt">string</span> <span class="n">namespaceAndTypeName</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">pos</span> <span class="p">=</span> <span class="n">namespaceAndTypeName</span><span class="p">.</span><span class="n">LastIndexOf</span><span class="p">(</span><span class="s">&#34;.&#34;</span><span class="p">,</span> <span class="n">StringComparison</span><span class="p">.</span><span class="n">Ordinal</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">pos</span> <span class="p">==</span> <span class="p">-</span><span class="m">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">namespaceAndTypeName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1">// skip the .</span>
</span></span><span class="line"><span class="cl">    <span class="n">pos</span><span class="p">++;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">namespaceAndTypeName</span><span class="p">.</span><span class="n">Substring</span><span class="p">(</span><span class="n">pos</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Only the parameters (not the return type) are extracted from the “return type SPACE SPACE (parameters)” signature format:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="kt">string</span> <span class="n">ExtractParameters</span><span class="p">(</span><span class="kt">string</span> <span class="n">signature</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">pos</span> <span class="p">=</span> <span class="n">signature</span><span class="p">.</span><span class="n">IndexOf</span><span class="p">(</span><span class="s">&#34;  (&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">pos</span> <span class="p">==</span> <span class="p">-</span><span class="m">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="s">&#34;(???)&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// skip double space</span>
</span></span><span class="line"><span class="cl">    <span class="n">pos</span> <span class="p">+=</span> <span class="m">2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">parameters</span> <span class="p">=</span> <span class="n">signature</span><span class="p">.</span><span class="n">Substring</span><span class="p">(</span><span class="n">pos</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">parameters</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>With the starting address and the size of each JITted methods, it is easy to find the one corresponding to a given address on the stack: look for the <code>MethodInfo</code> where this address could be between the start address and the start address + the code size:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="kt">string</span> <span class="n">GetFullName</span><span class="p">(</span><span class="kt">ulong</span> <span class="n">address</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">_cache</span><span class="p">.</span><span class="n">TryGetValue</span><span class="p">(</span><span class="n">address</span><span class="p">,</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">fullName</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">fullName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1">// look for managed methods</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">_methods</span><span class="p">.</span><span class="n">Count</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">method</span> <span class="p">=</span> <span class="n">_methods</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">((</span><span class="n">address</span> <span class="p">&gt;=</span> <span class="n">method</span><span class="p">.</span><span class="n">StartAddress</span><span class="p">)</span> <span class="p">&amp;&amp;</span> <span class="p">(</span><span class="n">address</span> <span class="p">&lt;</span> <span class="n">method</span><span class="p">.</span><span class="n">StartAddress</span> <span class="p">+</span> <span class="p">(</span><span class="kt">ulong</span><span class="p">)</span><span class="n">method</span><span class="p">.</span><span class="n">Size</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">fullName</span> <span class="p">=</span> <span class="n">method</span><span class="p">.</span><span class="n">FullName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">_cache</span><span class="p">[</span><span class="n">address</span><span class="p">]</span> <span class="p">=</span> <span class="n">fullName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">fullName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// look for native methods</span>
</span></span><span class="line"><span class="cl">    <span class="n">fullName</span> <span class="p">=</span> <span class="n">GetNativeMethodName</span><span class="p">(</span><span class="n">address</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">_cache</span><span class="p">[</span><span class="n">address</span><span class="p">]</span> <span class="p">=</span> <span class="n">fullName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">fullName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>For performance sake, the <code>_cache</code> dictionary property speeds up the process by keeping track of the address/full name mappings.</p>
<p>It is now time to look at the details of the <code>GetNativeMethodName</code> helper that takes care of the native functions scenario.</p>
<h2 id="the-native-part-of-the-symbolsstory">The native part of the symbols story</h2>
<p>Unlike for JITted methods, the CLR does not send events to describe native functions even for the CLR itself. Instead, you need to find a way to map a call stack address to a native function by yourself. Unlike Perfview, I will be using the <strong>dbghelp</strong> native API instead of <strong>DIA</strong> mostly because my scenario is to get the stacks while the applications are still running:</p>
<p><img loading="lazy" src="/posts/2020-06-19_build-your-own-net/1_hCkVFS_bkxBJUtq-pAywSQ.png"></p>
<p>After reading the <a href="https://docs.microsoft.com/en-us/archive/msdn-magazine/2002/march/under-the-hood-improved-error-reporting-with-dbghelp-5-1-apis?WT.mc_id=DT-MVP-5003325">march 2002 MSDN article about DBGHELP</a> by Matt Pietrek, the updated symbols <a href="https://docs.microsoft.com/en-us/windows/win32/debug/dbghelp-functions#symbol-handler?WT.mc_id=DT-MVP-5003325">related Microsoft Docs</a> and the dbghelp.h include a file from the Windows SDK, I wrote a C# wrapper around the dbghelp function needed to get a method name from an address in a process address space:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">internal</span> <span class="kd">static</span> <span class="k">class</span> <span class="nc">NativeDbgHelp</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// from C:\Program Files (x86)\Windows Kits\10\Debuggers\inc\dbghelp.h</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">const</span> <span class="kt">uint</span> <span class="n">SYMOPT_UNDNAME</span> <span class="p">=</span> <span class="m">0x00000002</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">const</span> <span class="kt">uint</span> <span class="n">SYMOPT_DEFERRED_LOADS</span> <span class="p">=</span> <span class="m">0x00000004</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">    [StructLayout(LayoutKind.Sequential)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="k">struct</span> <span class="nc">SYMBOL_INFO</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">uint</span> <span class="n">SizeOfStruct</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">uint</span> <span class="n">TypeIndex</span><span class="p">;</span>      <span class="c1">// Type Index of symbol</span>
</span></span><span class="line"><span class="cl">        <span class="kd">private</span> <span class="kt">ulong</span> <span class="n">Reserved1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kd">private</span> <span class="kt">ulong</span> <span class="n">Reserved2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">uint</span> <span class="n">Index</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">uint</span> <span class="n">Size</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">ulong</span> <span class="n">ModBase</span><span class="p">;</span>       <span class="c1">// Base Address of module containing this symbol</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">uint</span> <span class="n">Flags</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">ulong</span> <span class="n">Value</span><span class="p">;</span>         <span class="c1">// Value of symbol, ValuePresent should be 1</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">ulong</span> <span class="n">Address</span><span class="p">;</span>       <span class="c1">// Address of symbol including base address of module</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">uint</span> <span class="n">Register</span><span class="p">;</span>       <span class="c1">// register holding value or pointer to value</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">uint</span> <span class="n">Scope</span><span class="p">;</span>          <span class="c1">// scope of the symbol</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">uint</span> <span class="n">Tag</span><span class="p">;</span>            <span class="c1">// pdb classification</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">uint</span> <span class="n">NameLen</span><span class="p">;</span>        <span class="c1">// Actual length of name</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">uint</span> <span class="n">MaxNameLen</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="na">        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)]</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">string</span> <span class="n">Name</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">    [DllImport(&#34;dbghelp.dll&#34;, SetLastError = true)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">extern</span> <span class="kt">bool</span> <span class="n">SymInitialize</span><span class="p">(</span><span class="n">IntPtr</span> <span class="n">hProcess</span><span class="p">,</span> <span class="kt">string</span> <span class="n">userSearchPath</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">invadeProcess</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">    [DllImport(&#34;dbghelp.dll&#34;, SetLastError = true)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">extern</span> <span class="kt">uint</span> <span class="n">SymSetOptions</span><span class="p">(</span><span class="kt">uint</span> <span class="n">symOptions</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">    [DllImport(&#34;dbghelp.dll&#34;, SetLastError = true, CharSet = CharSet.Ansi)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">extern</span> <span class="kt">ulong</span> <span class="n">SymLoadModule64</span><span class="p">(</span><span class="n">IntPtr</span> <span class="n">hProcess</span><span class="p">,</span> <span class="n">IntPtr</span> <span class="n">hFile</span><span class="p">,</span> <span class="kt">string</span> <span class="n">imageName</span><span class="p">,</span> <span class="kt">string</span> <span class="n">moduleName</span><span class="p">,</span> <span class="kt">ulong</span> <span class="n">baseOfDll</span><span class="p">,</span> <span class="kt">uint</span> <span class="n">sizeOfDll</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// use ANSI version to ensure the right size of the structure </span>
</span></span><span class="line"><span class="cl">    <span class="c1">// read https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/ns-dbghelp-symbol_info</span>
</span></span><span class="line"><span class="cl"><span class="na">    [DllImport(&#34;dbghelp.dll&#34;, SetLastError = true, CharSet = CharSet.Ansi)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">extern</span> <span class="kt">bool</span> <span class="n">SymFromAddr</span><span class="p">(</span><span class="n">IntPtr</span> <span class="n">hProcess</span><span class="p">,</span> <span class="kt">ulong</span> <span class="n">address</span><span class="p">,</span> <span class="k">out</span> <span class="kt">ulong</span> <span class="n">displacement</span><span class="p">,</span> <span class="k">ref</span> <span class="n">SYMBOL_INFO</span> <span class="n">symbol</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">    [DllImport(&#34;dbghelp.dll&#34;, SetLastError = true)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">extern</span> <span class="kt">bool</span> <span class="n">SymCleanup</span><span class="p">(</span><span class="n">IntPtr</span> <span class="n">hProcess</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Note that you will need to download the dbghelp.dll (SymSrv.dll if needed) from <a href="https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk/?WT.mc_id=DT-MVP-5003325">the Windows SDK</a> and copy it next to your memory profiler binaries.</p>
<p>The usage of the dbghelp API is straightforward. First, for each new process, call <code>SymSetOptions</code>/<code>SymInitialize</code>** **with a handle of the process:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="kt">bool</span> <span class="n">SymInitialize</span><span class="p">(</span><span class="n">IntPtr</span> <span class="n">hProcess</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// read https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symsetoptions for more details</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// maybe SYMOPT_NO_PROMPTS and SYMOPT_FAIL_CRITICAL_ERRORS could be used</span>
</span></span><span class="line"><span class="cl">    <span class="n">NativeDbgHelp</span><span class="p">.</span><span class="n">SymSetOptions</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">NativeDbgHelp</span><span class="p">.</span><span class="n">SYMOPT_DEFERRED_LOADS</span> <span class="p">|</span>   <span class="c1">// performance optimization</span>
</span></span><span class="line"><span class="cl">        <span class="n">NativeDbgHelp</span><span class="p">.</span><span class="n">SYMOPT_UNDNAME</span>            <span class="c1">// C++ names are not mangled</span>
</span></span><span class="line"><span class="cl">        <span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-syminitialize</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// search path for symbols:</span>
</span></span><span class="line"><span class="cl">    <span class="c1">//   - The current working directory of the application</span>
</span></span><span class="line"><span class="cl">    <span class="c1">//   - The _NT_SYMBOL_PATH environment variable</span>
</span></span><span class="line"><span class="cl">    <span class="c1">//   - The _NT_ALTERNATE_SYMBOL_PATH environment variable</span>
</span></span><span class="line"><span class="cl">    <span class="c1">//</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// passing false as last parameter means that we will need to call SymLoadModule64 </span>
</span></span><span class="line"><span class="cl">    <span class="c1">// each time a module is loaded in the process</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">NativeDbgHelp</span><span class="p">.</span><span class="n">SymInitialize</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="kc">false</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="n">IntPtr</span> <span class="n">BindToProcess</span><span class="p">(</span><span class="kt">int</span> <span class="n">pid</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">_process</span> <span class="p">=</span> <span class="n">Process</span><span class="p">.</span><span class="n">GetProcessById</span><span class="p">(</span><span class="n">pid</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(!</span><span class="n">SymInitialize</span><span class="p">(</span><span class="n">_process</span><span class="p">.</span><span class="n">Handle</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">IntPtr</span><span class="p">.</span><span class="n">Zero</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">_process</span><span class="p">.</span><span class="n">Handle</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">catch</span> <span class="p">(</span><span class="n">Exception</span> <span class="n">x</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;Error while binding pid #{pid} to DbgHelp:&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="n">x</span><span class="p">.</span><span class="n">Message</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">IntPtr</span><span class="p">.</span><span class="n">Zero</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>In the case of protected processes, <code>Process.GetProcessById</code> might throw an exception. The <code>_hProcess</code> field storing the process handle will be cleaned up in the <code>IDisposible.Dispose</code> implementation of the <code>MethodStore</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">void</span> <span class="n">Dispose</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">_hProcess</span> <span class="p">==</span> <span class="n">IntPtr</span><span class="p">.</span><span class="n">Zero</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">_hProcess</span> <span class="p">=</span> <span class="n">IntPtr</span><span class="p">.</span><span class="n">Zero</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">_process</span><span class="p">.</span><span class="n">Dispose</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>After a process has been bound, each time one of its modules is loaded, <code>SymLoadModule64</code> must be called. You can be notified of such a loaded module by enabling the Kernel provider with the <code>ImageLoad</code> keyword.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="n">session</span><span class="p">.</span><span class="n">EnableKernelProvider</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">KernelTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">ImageLoad</span> <span class="p">|</span>
</span></span><span class="line"><span class="cl">    <span class="n">KernelTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">Process</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">KernelTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">None</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The handler attached to the <code>ImageLoaded</code> event will be called each time a dll gets loaded.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">SetupListeners</span><span class="p">(</span><span class="n">ETWTraceEventSource</span> <span class="n">source</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// get notified when a module is load to map the corresponding symbols</span>
</span></span><span class="line"><span class="cl">    <span class="n">source</span><span class="p">.</span><span class="n">Kernel</span><span class="p">.</span><span class="n">ImageLoad</span> <span class="p">+=</span> <span class="n">OnImageLoad</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">const</span> <span class="kt">int</span> <span class="n">ERROR_SUCCESS</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">OnImageLoad</span><span class="p">(</span><span class="n">ImageLoadTraceData</span> <span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">FilterOutEvent</span><span class="p">(</span><span class="n">data</span><span class="p">))</span> <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">GetProcessMethods</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">ProcessID</span><span class="p">).</span><span class="n">AddModule</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">FileName</span><span class="p">,</span> <span class="n">data</span><span class="p">.</span><span class="n">ImageBase</span><span class="p">,</span> <span class="n">data</span><span class="p">.</span><span class="n">ImageSize</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">void</span> <span class="n">AddModule</span><span class="p">(</span><span class="kt">string</span> <span class="n">filename</span><span class="p">,</span> <span class="kt">ulong</span> <span class="n">baseOfDll</span><span class="p">,</span> <span class="kt">int</span> <span class="n">sizeOfDll</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">baseAddress</span> <span class="p">=</span> <span class="n">NativeDbgHelp</span><span class="p">.</span><span class="n">SymLoadModule64</span><span class="p">(</span><span class="n">_hProcess</span><span class="p">,</span> <span class="n">IntPtr</span><span class="p">.</span><span class="n">Zero</span><span class="p">,</span> <span class="n">filename</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="n">baseOfDll</span><span class="p">,</span> <span class="p">(</span><span class="kt">uint</span><span class="p">)</span><span class="n">sizeOfDll</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">baseAddress</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// should work if the same module is added more than once</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">Marshal</span><span class="p">.</span><span class="n">GetLastWin32Error</span><span class="p">()</span> <span class="p">==</span> <span class="n">ERROR_SUCCESS</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;SymLoadModule64 failed for {filename}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Now everything is in place to get a native function name from an address on the stack:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="kt">string</span> <span class="n">GetNativeMethodName</span><span class="p">(</span><span class="kt">ulong</span> <span class="n">address</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">symbol</span> <span class="p">=</span> <span class="k">new</span> <span class="n">NativeDbgHelp</span><span class="p">.</span><span class="n">SYMBOL_INFO</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">symbol</span><span class="p">.</span><span class="n">MaxNameLen</span> <span class="p">=</span> <span class="m">1024</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">symbol</span><span class="p">.</span><span class="n">SizeOfStruct</span> <span class="p">=</span> <span class="p">(</span><span class="kt">uint</span><span class="p">)</span><span class="n">Marshal</span><span class="p">.</span><span class="n">SizeOf</span><span class="p">(</span><span class="n">symbol</span><span class="p">)</span> <span class="p">-</span> <span class="m">1024</span><span class="p">;</span>   <span class="c1">// char buffer is not counted</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// the ANSI version of SymFromAddr is called so each character is 1 byte long</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">NativeDbgHelp</span><span class="p">.</span><span class="n">SymFromAddr</span><span class="p">(</span><span class="n">_hProcess</span><span class="p">,</span> <span class="n">address</span><span class="p">,</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">displacement</span><span class="p">,</span> <span class="k">ref</span> <span class="n">symbol</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">buffer</span> <span class="p">=</span> <span class="k">new</span> <span class="n">StringBuilder</span><span class="p">(</span><span class="n">symbol</span><span class="p">.</span><span class="n">Name</span><span class="p">.</span><span class="n">Length</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// remove weird &#34;$##&#34; at the end of some symbols</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">pos</span> <span class="p">=</span> <span class="n">symbol</span><span class="p">.</span><span class="n">Name</span><span class="p">.</span><span class="n">LastIndexOf</span><span class="p">(</span><span class="s">&#34;$##&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">pos</span> <span class="p">==</span> <span class="p">-</span><span class="m">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">buffer</span><span class="p">.</span><span class="n">Append</span><span class="p">(</span><span class="n">symbol</span><span class="p">.</span><span class="n">Name</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span>
</span></span><span class="line"><span class="cl">            <span class="n">buffer</span><span class="p">.</span><span class="n">Append</span><span class="p">(</span><span class="n">symbol</span><span class="p">.</span><span class="n">Name</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="n">pos</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// add offset if any</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">displacement</span> <span class="p">!=</span> <span class="m">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">buffer</span><span class="p">.</span><span class="n">Append</span><span class="p">(</span><span class="s">$&#34;+0x{displacement}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">buffer</span><span class="p">.</span><span class="n">ToString</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// default value is the just the address in HEX</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="s">$&#34;0x{address:x}&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Note that I needed to remove some unexpected <strong>$##</strong> strings are the end of some symbols.</p>
<p><em><strong>This is the last episode of the series about building your own memory profiler in C#. In case you missed the first episodes, check them out on Medium:</strong></em></p>
<p><a href="https://medium.com/criteo-labs/build-your-own-net-memory-profiler-in-c-call-stacks-2-2-1-f67b440a8cc"><strong>Build your own .NET memory profiler in C# — call stacks (2/2–1)</strong>
*This post explains how to get the call stack corresponding to the allocations with CLR events.*medium.com</a><a href="https://medium.com/criteo-labs/build-your-own-net-memory-profiler-in-c-call-stacks-2-2-1-f67b440a8cc"></a><a href="/posts/2020-04-18_build-your-own-net/"><strong>Build your own .NET memory profiler in C#</strong>
*This post explains how to collect allocation details by writing your own memory profiler in C#.*medium.com</a></p>
<hr>
<h2 id="resources">Resources</h2>
<ul>
<li>Source code available <a href="https://github.com/chrisnas/ClrEvents">on Github</a>.</li>
<li>Download <a href="https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk/?WT.mc_id=DT-MVP-5003325">Debugging Tools for Windows</a> for dbghelp.dll and SymSrv.dll</li>
<li>Matt Pietrek <a href="https://docs.microsoft.com/en-us/archive/msdn-magazine/2002/march/under-the-hood-improved-error-reporting-with-dbghelp-5-1-apis?WT.mc_id=DT-MVP-5003325">article in MSDN Magazine</a> about DBGHELP</li>
<li>Dbghelp samples -<a href="http://www.debuginfo.com/examples/dbghelpexamples.html">http://www.debuginfo.com/examples/dbghelpexamples.html</a></li>
</ul>
<hr>
<p><strong>Join the crowd!</strong></p>
<p><a href="https://careers.criteo.com/"><strong>Careers at Criteo | Criteo jobs</strong>
*Find opportunities everywhere. ​Choose your next challenge. Find the job opportunities at Criteo in Product, research &amp;…*careers.criteo.com</a><a href="https://careers.criteo.com/"></a></p>
]]></content:encoded></item><item><title>Getting another view on thread stacks with ClrMD</title><link>https://chrisnas.github.io/posts/2019-12-31_getting-another-view-on/</link><pubDate>Tue, 31 Dec 2019 10:52:00 +0000</pubDate><guid>https://chrisnas.github.io/posts/2019-12-31_getting-another-view-on/</guid><description>This post of the series details how to look into your threads stack with ClrMD to get method calls, parameters and local variables.</description><content:encoded><![CDATA[<hr>
<p>This post of the series details how to look into your threads stack with ClrMD.</p>
<h2 id="introduction">Introduction</h2>
<p>It’s been a long time (see the resources at the end) since I’ve been discussing what ClrMD could bring to .NET developers/DevOps! My colleague <a href="https://twitter.com/KooKiz">Kevin</a> just wrote <a href="https://medium.com/@kevingosse/dumping-stack-objects-with-clrmd-c002dab4651b">an article about how to emulate SOS <strong>DumpStackObjects</strong></a> command both on Windows and Linux with ClrMD. This implementation lists the objects on the stack but without their values (like strings content for example) nor the stack frames corresponding to the method calls.</p>
<p>The rest of the post will show you, with ClrMD, how to get an higher view, closer to what the SOS <strong>ClrStack</strong> command could provide.</p>
<p>Let’s take this simple application as an example:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Program</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">static</span> <span class="k">void</span> <span class="n">Main</span><span class="p">(</span><span class="kt">string</span><span class="p">[]</span> <span class="n">args</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">Host</span> <span class="n">h</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Host</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="n">h</span><span class="p">.</span><span class="n">Base</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Host</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="k">void</span> <span class="n">Base</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">int</span> <span class="n">iValue</span> <span class="p">=</span> <span class="n">Guid</span><span class="p">.</span><span class="n">NewGuid</span><span class="p">().</span><span class="n">GetHashCode</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="kt">bool</span> <span class="n">bValue</span> <span class="p">=</span> <span class="p">(</span><span class="n">iValue</span> <span class="p">%</span> <span class="m">2</span><span class="p">)</span> <span class="p">==</span> <span class="m">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kt">string</span> <span class="n">parameter</span> <span class="p">=</span> <span class="n">Guid</span><span class="p">.</span><span class="n">NewGuid</span><span class="p">().</span><span class="n">ToString</span><span class="p">().</span><span class="n">Substring</span><span class="p">(</span><span class="m">0</span><span class="p">,</span><span class="m">1</span><span class="p">)</span> <span class="p">+</span> <span class="s">&#34;_1234567890&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="n">parameter</span> <span class="p">+</span> <span class="s">&#34; = &#34;</span> <span class="p">+</span> <span class="n">First</span><span class="p">(</span><span class="n">iValue</span><span class="p">,</span> <span class="n">bValue</span><span class="p">,</span> <span class="n">parameter</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kt">int</span> <span class="n">First</span><span class="p">(</span><span class="kt">int</span> <span class="n">iValue</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">bValue</span><span class="p">,</span> <span class="kt">string</span> <span class="n">parameter</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">Guid</span> <span class="n">guid</span> <span class="p">=</span> <span class="n">Guid</span><span class="p">.</span><span class="n">NewGuid</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">Second</span><span class="p">(</span><span class="n">bValue</span><span class="p">,</span> <span class="n">guid</span><span class="p">)</span> <span class="p">/</span> <span class="m">2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kt">int</span> <span class="n">Second</span><span class="p">(</span><span class="kt">bool</span> <span class="n">bValue</span><span class="p">,</span> <span class="n">Guid</span> <span class="n">guid</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">Third</span><span class="p">(</span><span class="n">guid</span><span class="p">).</span><span class="n">Length</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kt">string</span> <span class="n">Third</span><span class="p">(</span><span class="n">Guid</span> <span class="n">guid</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;call   procdump -ma {Process.GetCurrentProcess().Id}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">Console</span><span class="p">.</span><span class="n">ReadLine</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="s">$&#34;{guid.GetHashCode()}#{guid.GetHashCode()}#{guid.GetHashCode()}&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>As you can see, I’ve mixed value and reference types as parameters and local variables up to the call to the <code>Third</code> method that displays the <a href="https://docs.microsoft.com/en-us/sysinternals/downloads/procdump"><strong>procdump</strong></a> command line to execute in order to generate a memory dump of the process.</p>
<h2 id="use-windbg--sosluke">Use WinDBG + SOS Luke!</h2>
<p>When you open it with WinDBG and load SOS, here is the result of the <strong>dso</strong> command:</p>
<p><img loading="lazy" src="/posts/2019-12-31_getting-another-view-on/1_LJ5KZ3Vymkcla6eJ3sug6Q.png"></p>
<p>The <strong>clrstack</strong> command shows the stacked method calls:</p>
<p><img loading="lazy" src="/posts/2019-12-31_getting-another-view-on/1_0lB0DVD8gCrEPYYQBHIUKQ.png"></p>
<p>And if you use the <strong>-a</strong> parameter, you will get methods with their parameters and local variables (or <strong>-p</strong> for parameters only and <strong>-l</strong> for local variables only):</p>
<p><img loading="lazy" src="/posts/2019-12-31_getting-another-view-on/1_PMv0oK5BqBCBDrI_bF1g5w.png"></p>
<p>It is weird that SOS implementation does not give the type of both the parameters and locals. But wait! While researching for this post, I looked at the SOS implementation (now in the strike.cs file moved from the <strong>coreclr</strong> to the <strong>diagnostics</strong> repository) to <a href="https://github.com/dotnet/diagnostics/blob/master/src/SOS/Strike/strike.cpp#L13165">find this nice comment</a>:</p>
<p><img loading="lazy" src="/posts/2019-12-31_getting-another-view-on/1_Rg0WDui494pjE9ari7U0FA.png"></p>
<p>So I tried <strong>clrstack</strong> with <strong>-i</strong> and I got the types for parameters (and locals unlike what the comments implies):</p>
<p><img loading="lazy" src="/posts/2019-12-31_getting-another-view-on/1_nrTH9uu64zU2MLxM8whBjQ.png"></p>
<p>Even though <strong>clrstack</strong> supports the <strong>-all</strong> flag to dump the call stack of all managed threads, you might need to do your own automatic analysis on hundreds of threads and this is where ClrMD shines.</p>
<h2 id="merging-methods-and-parameterslocals">Merging methods and parameters/locals</h2>
<p>When I read Kevin’s post, I immediately thought about adding the method call on the stack based on the <a href="https://github.com/chrisnas/DebuggingExtensions/commit/4061a2c885241edd2bf964db9b7af94fc7dcd778">work I’ve done in March 2019</a> to implement the <a href="https://github.com/chrisnas/DebuggingExtensions/releases/download/1.6.3/pstacks1.3.zip">pstacks tool</a>. At that time, my goal was to aggregate the call stacks of a large number of threads in order to find out pattern of blocked threads, sharing the “same” call stacks. Visual Studio provides a great “Parallel Stacks” pane but I needed it for both Windows and Linux.</p>
<p>To list all the call stack with ClrMD, you <a href="https://github.com/chrisnas/DebuggingExtensions/blob/master/src/ParallelStacks.Runtime/ParallelStack.cs#L11">simply</a> enumerate the managed threads and for each one, its <code>StackTrace</code> property contains the list of <code>StackFrame</code> objects corresponding to each method call.</p>
<p><img loading="lazy" src="/posts/2019-12-31_getting-another-view-on/1__F2Z31l2DikZkOVEr6SSmQ.png"></p>
<p>The <code>StackPointer</code> property of each frame contains the address of the frame in the call stack, allowing a mapping of the method call with its parameters and locals:</p>
<p><img loading="lazy" src="/posts/2019-12-31_getting-another-view-on/1_HQVchR6XY_1E18Wr7k_8ow.png"></p>
<p>As always with stacks, lower addresses correspond to the last things added to the stack (i.e. last called method). While checking between what is shown by SOS, the parameters/locals addresses and frame stack pointers, you realize that all objects at an address in the stack equal or below the <code>StackPointer</code> of a frame are either parameters or local variables of the frame method.</p>
<p>Even better, for non static method, you can guess what is the <em><strong>this</strong></em>* *implicit parameter if the address is the same as the frame <code>StackPointer</code>; shown with the green <strong>=</strong> sign in the previous screenshot and prefixed by <strong>&gt;</strong> in Kevin’s updated code that merges the method calls to the parameters and locals:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="k">for</span> <span class="p">(</span><span class="kt">ulong</span> <span class="n">ptr</span> <span class="p">=</span> <span class="n">stackTop</span><span class="p">;</span> <span class="n">ptr</span> <span class="p">&lt;=</span> <span class="n">stackLimit</span><span class="p">;</span> <span class="n">ptr</span> <span class="p">+=</span> <span class="p">(</span><span class="kt">ulong</span><span class="p">)</span><span class="n">runtime</span><span class="p">.</span><span class="n">PointerSize</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// look for the frame corresponding to the current position in the stack</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">currentFrame</span><span class="p">.</span><span class="n">StackPointer</span> <span class="p">&lt;=</span> <span class="n">ptr</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="n">FormatFrame</span><span class="p">(</span><span class="n">currentFrame</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">nFrame</span><span class="p">++;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">nFrame</span> <span class="p">&lt;</span> <span class="n">frames</span><span class="p">.</span><span class="n">Count</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">currentFrame</span> <span class="p">=</span> <span class="n">frames</span><span class="p">[</span><span class="n">nFrame</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kt">ulong</span> <span class="n">obj</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(!</span><span class="n">runtime</span><span class="p">.</span><span class="n">ReadPointer</span><span class="p">(</span><span class="n">ptr</span><span class="p">,</span> <span class="k">out</span> <span class="n">obj</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(!</span><span class="n">IsInHeap</span><span class="p">(</span><span class="n">heap</span><span class="p">,</span> <span class="n">obj</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">type</span> <span class="p">=</span> <span class="n">heap</span><span class="p">.</span><span class="n">GetObjectType</span><span class="p">(</span><span class="n">obj</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">type</span> <span class="p">==</span> <span class="kc">null</span> <span class="p">||</span> <span class="n">type</span><span class="p">.</span><span class="n">IsFree</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// try to find implicit &#34;this&#34; parameter in case of non-static method</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">myFrame</span> <span class="p">=</span> <span class="p">(</span><span class="n">nFrame</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span> <span class="p">?</span> <span class="n">currentFrame</span> <span class="p">:</span> <span class="n">frames</span><span class="p">[</span><span class="n">nFrame</span><span class="p">-</span><span class="m">1</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">separator</span> <span class="p">=</span> <span class="p">((</span><span class="n">myFrame</span><span class="p">.</span><span class="n">StackPointer</span> <span class="p">==</span> <span class="n">ptr</span><span class="p">)</span> <span class="p">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">                 <span class="p">(</span><span class="n">myFrame</span><span class="p">.</span><span class="n">Method</span> <span class="p">!=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">                 <span class="p">(!</span><span class="n">myFrame</span><span class="p">.</span><span class="n">Method</span><span class="p">.</span><span class="n">IsStatic</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="p">?</span> <span class="s">&#34;&gt;&#34;</span> 
</span></span><span class="line"><span class="cl">        <span class="p">:</span> <span class="s">&#34; &#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">Console</span><span class="p">.</span><span class="n">Write</span><span class="p">(</span><span class="s">$&#34;{ptr:x16}   {separator} &#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">DumpObject</span><span class="p">(</span><span class="n">heap</span><span class="p">,</span> <span class="n">type</span><span class="p">,</span> <span class="p">(</span><span class="kt">ulong</span><span class="p">)</span><span class="n">obj</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <code>FormatFrame</code> helper method simply prefix static methods with <strong>#</strong> instead of <strong>|</strong> for instance methods:</p>
<p>Unfortunately, I did not find any way with ClrMD to make the difference between parameters and locals. Based on what you can see in SOS implementation <a href="https://github.com/dotnet/diagnostics/blob/master/src/SOS/Strike/strike.cpp#L12916">of this part</a> of the <strong>clrstack</strong> command, it relies on the <code>EnumerateArguments</code> and <code>EnumetateLocalVariables</code> methods of <code>ICorDebugILFrame</code> which is not exposed by ClrMD. There is another undocumented implementation based on <a href="https://github.com/dotnet/coreclr/blob/master/src/pal/prebuilt/inc/xclrdata.h#L6067">private interfaces</a> I could not leverage neither. For a larger discussion around stack walking in .NET, read <a href="https://mattwarren.org/2019/01/21/Stackwalking-in-the-.NET-Runtime/">this great post</a> by Matt Warren.</p>
<p>Also, without any explicit access to specific parameter or local, I did not find a way to get the value of primitive and value type instances stored on the stack. However, it is still possible to get them for boxed ones and reference type instances such as string for example.</p>
<h2 id="getting-instances-from-thestack">Getting instances from the stack</h2>
<p>In the last code excerpt, I did not describe the <code>DumpObject</code> helper method used to display an object on the stack. The implementation provided by Kevin was used to show the address and the type of the object:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;{objAddress:x16} {type.Name}&#34;</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The next step would be to display value for primitive types such as numbers, boolean, string and even array size:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="k">void</span> <span class="n">DumpObject</span><span class="p">(</span><span class="n">ClrHeap</span> <span class="n">heap</span><span class="p">,</span> <span class="n">ClrType</span> <span class="n">type</span><span class="p">,</span> <span class="kt">ulong</span> <span class="n">objAddress</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// get value for simple types</span>
</span></span><span class="line"><span class="cl">    <span class="kt">string</span> <span class="n">valueOrAddress</span> <span class="p">=</span> 
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">type</span><span class="p">.</span><span class="n">Name</span> <span class="p">==</span> <span class="s">&#34;System.Char&#34;</span><span class="p">)</span> <span class="p">?</span> <span class="s">$&#34;{type.GetValue(objAddress),16}&#34;</span> <span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">type</span><span class="p">.</span><span class="n">Name</span> <span class="p">==</span> <span class="s">&#34;System.String&#34;</span><span class="p">)</span> <span class="p">?</span> <span class="s">$&#34;{FormatString(type.GetValue(objAddress).ToString())}&#34;</span> <span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">type</span><span class="p">.</span><span class="n">Name</span> <span class="p">==</span> <span class="s">&#34;System.Bool&#34;</span><span class="p">)</span> <span class="p">?</span> <span class="s">$&#34;{FormatString(((bool)type.GetValue(objAddress)).ToString())}&#34;</span> <span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">type</span><span class="p">.</span><span class="n">Name</span> <span class="p">==</span> <span class="s">&#34;System.Byte&#34;</span><span class="p">)</span> <span class="p">?</span> <span class="s">$&#34;{FormatString(((byte)type.GetValue(objAddress)).ToString())}&#34;</span> <span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">type</span><span class="p">.</span><span class="n">Name</span> <span class="p">==</span> <span class="s">&#34;System.SByte&#34;</span><span class="p">)</span> <span class="p">?</span> <span class="s">$&#34;{FormatString(((sbyte)type.GetValue(objAddress)).ToString())}&#34;</span> <span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">type</span><span class="p">.</span><span class="n">Name</span> <span class="p">==</span> <span class="s">&#34;System.Decimal&#34;</span><span class="p">)</span> <span class="p">?</span> <span class="s">$&#34;{FormatString(((decimal)type.GetValue(objAddress)).ToString())}&#34;</span> <span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">type</span><span class="p">.</span><span class="n">Name</span> <span class="p">==</span> <span class="s">&#34;System.Double&#34;</span><span class="p">)</span> <span class="p">?</span> <span class="s">$&#34;{FormatString(((double)type.GetValue(objAddress)).ToString())}&#34;</span> <span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">type</span><span class="p">.</span><span class="n">Name</span> <span class="p">==</span> <span class="s">&#34;System.Single&#34;</span><span class="p">)</span> <span class="p">?</span> <span class="s">$&#34;{FormatString(((float)type.GetValue(objAddress)).ToString())}&#34;</span> <span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">type</span><span class="p">.</span><span class="n">Name</span> <span class="p">==</span> <span class="s">&#34;System.Int32&#34;</span><span class="p">)</span> <span class="p">?</span> <span class="s">$&#34;{FormatString(((int)type.GetValue(objAddress)).ToString())}&#34;</span> <span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">type</span><span class="p">.</span><span class="n">Name</span> <span class="p">==</span> <span class="s">&#34;System.SInt32&#34;</span><span class="p">)</span> <span class="p">?</span> <span class="s">$&#34;{FormatString(((uint)type.GetValue(objAddress)).ToString())}&#34;</span> <span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">type</span><span class="p">.</span><span class="n">Name</span> <span class="p">==</span> <span class="s">&#34;System.Int64&#34;</span><span class="p">)</span> <span class="p">?</span> <span class="s">$&#34;{FormatString(((long)type.GetValue(objAddress)).ToString())}&#34;</span> <span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">type</span><span class="p">.</span><span class="n">Name</span> <span class="p">==</span> <span class="s">&#34;System.SInt64&#34;</span><span class="p">)</span> <span class="p">?</span> <span class="s">$&#34;{FormatString(((ulong)type.GetValue(objAddress)).ToString())}&#34;</span> <span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">type</span><span class="p">.</span><span class="n">IsArray</span><span class="p">)</span> <span class="p">?</span> <span class="s">$&#34;{FormatString(GetArrayAsString(type, objAddress))}&#34;</span> <span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="s">$&#34;{objAddress:x16}&#34;</span><span class="p">;</span>  <span class="c1">// work also for IntPtr</span>
</span></span><span class="line"><span class="cl">    <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;{valueOrAddress} {type.Name}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Most of this code is based on the <code>GetValue</code> helper from <code>ClrType</code>: it returns the right “thing” for simple types. Look at ClrMD <a href="https://github.com/microsoft/clrmd/blob/c35e2115241e7dc6f9c835b4c59b9e396ec6471b/src/Microsoft.Diagnostics.Runtime/src/Implementation/ValueReader.cs#L33">implementation details</a> to get a better understanding of how the value is rebuilt.</p>
<p>The <code>GetArrayAsString</code> simply returns the number of elements in the array:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="kt">string</span> <span class="n">GetArrayAsString</span><span class="p">(</span><span class="n">ClrType</span> <span class="n">type</span><span class="p">,</span> <span class="kt">ulong</span> <span class="n">objAddress</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">elementCount</span> <span class="p">=</span> <span class="n">type</span><span class="p">.</span><span class="n">GetArrayLength</span><span class="p">(</span><span class="n">objAddress</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="s">$&#34;length = {elementCount}&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>And the call stack is now complete!</p>
<p><img loading="lazy" src="/posts/2019-12-31_getting-another-view-on/1_-N5vbQ2oPCAath_WSutxKA.png"></p>
<p>Note that you may even get more locals or parameters than with WinDBG+SOS but don’t ask me why…</p>
<p>For more advanced object formatting cases such as dumping structs or enumerating fields and their value, I would highly recommend to look at the related <a href="https://github.com/microsoft/dotnet-samples/blob/master/Microsoft.Diagnostics.Runtime/CLRMD/docs/TypesAndFields.md">ClrMD documentation page</a> (just replace <code>GCHeapType</code> by <code>ClrType</code> and you’ll be safe).</p>
<h2 id="resources">Resources</h2>
<ul>
<li><a href="https://medium.com/@kevingosse/dumping-stack-objects-with-clrmd-c002dab4651b">Dumping stack objects with ClrMD</a> by <a href="https://twitter.com/KooKiz">Kevin Gosse</a></li>
<li><a href="https://mattwarren.org/2019/01/21/Stackwalking-in-the-.NET-Runtime/">Stack walking in the .NET Runtime</a> by <a href="https://twitter.com/matthewwarren">Matt Warren</a></li>
<li>Part 1: <a href="http://labs.criteo.com/2017/02/going-beyond-sos-clrmd-part-1">Bootstrap ClrMD to load a dump</a>.</li>
<li>Part 2: <a href="http://labs.criteo.com/2017/03/clrmd-part-2-clrruntime-clrheap-traverse-managed-heap/">Find duplicated strings with ClrMD heap traversing</a>.</li>
<li>Part 3: <a href="http://labs.criteo.com/2017/04/clrmd-part-3-dea%E2%80%A6s-to-list-timers/">List timers by following static fields links</a>.</li>
<li>Part 4: <a href="http://labs.criteo.com/2017/05/clrmd-part-4-callbacks-called-timers/">Identify timers callback and other properties</a>.</li>
<li>Part 5: <a href="http://labs.criteo.com/2017/06/clrmd-part-5-how-to-use-clrmd-to-extend-sos-in-windbg/">Use ClrMD to extend SOS in WinDBG</a>.</li>
<li>Part 6: <a href="http://labs.criteo.com/2017/08/clrmd-part-6-manipulate-memory-structures-like-real-objects/">Manipulate memory structures like real objects</a>.</li>
<li>Part 7: <a href="http://labs.criteo.com/2017/08/clrmd-part-7-manipulate-nested-structs-using-dynamic/">Manipulate nested structs using dynamic</a>.</li>
<li>Part 8: <a href="http://labs.criteo.com/2017/11/clrmd-part-8-spelunking-inside-the-net-thread-pool/">Spelunking inside the .NET Thread Pool</a></li>
<li>Part 9: <a href="https://labs.criteo.com/2017/12/clrmd-part-9-deciphering-tasks-thread-pool-items/">Deciphering Tasks and Thread Pool items</a></li>
</ul>
]]></content:encoded></item><item><title>How to expose your custom counters in .NET Core</title><link>https://chrisnas.github.io/posts/2019-10-17_how-to-expose-your/</link><pubDate>Thu, 17 Oct 2019 12:42:17 +0000</pubDate><guid>https://chrisnas.github.io/posts/2019-10-17_how-to-expose-your/</guid><description>This post shows how to code your own counters: you’ll get the count and duration of ASP.NET requests processed with(out) GC as example.</description><content:encoded><![CDATA[<hr>
<p>This post of the series explains how to implement your own counters.</p>
<p>Part 1: <a href="/posts/2018-06-19_replace-net-performance-counters/">Replace .NET performance counters by CLR event tracing</a>.</p>
<p>Part 2: <a href="/posts/2018-07-26_grab-etw-session-providers/">Grab ETW Session, Providers and Events</a>.</p>
<p>Part 3: <a href="/posts/2018-09-28_monitor-finalizers-contention-threads/">CLR Threading events with TraceEvent</a>.</p>
<p>Part 4: <a href="/posts/2018-12-15_spying-on-net-garbage/">Spying on .NET Garbage Collector with TraceEvent</a>.</p>
<p>Part 5: <a href="/posts/2019-02-12_building-your-own-java/">Building your own Java GC logs in .NET</a></p>
<p>Part 6: <a href="/posts/2019-05-28_spying-on-net-garbage/">Spying on .NET Core Garbage Collector with .NET Core EventPipes</a></p>
<p>Part 7: <a href="/posts/2019-07-23_net-core-counters-internals/">.NET Core Counters internals: how to integrate counters in your monitoring pipeline</a></p>
<h2 id="introduction">Introduction</h2>
<p>The** EventPipe** counters are the .NET Core replacement for Windows performance counters. In the <a href="/posts/2019-07-23_net-core-counters-internals/">previous post</a>, I’ve explained how to listen to CLR event pipes to get the counter’s value over time both on Windows and Linux. This post shows you how easy it is to provide your counters via the same infrastructure.</p>
<p>The example I’m using is based on a real-world case we had to investigate at Criteo. We needed to correlate request duration with garbage collections, so we decided to add new metrics to our testing dashboard: number and duration of requests but split between those processed without being interrupted by a GC and the others.</p>
<p>For the sake of the ASP.NET Core code example, a <a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/write?WT.mc_id=DT-MVP-5003325?view=aspnetcore-3.0">dedicated middleware</a> is created: it simply measures the time spent to process a request and if the count of garbage collections has changed before and after the request is processed:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">RequestMetricsMiddleware</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="n">RequestDelegate</span> <span class="n">_next</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">RequestMetricsMiddleware</span><span class="p">(</span><span class="n">RequestDelegate</span> <span class="n">next</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">_next</span> <span class="p">=</span> <span class="n">next</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">async</span> <span class="n">Task</span> <span class="n">InvokeAsync</span><span class="p">(</span><span class="n">HttpContext</span> <span class="n">context</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// get the count of GCs before processing the request</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">collectionCountBeforeProcessingTheRequest</span> <span class="p">=</span> <span class="n">GetCurrentCollectionCount</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">sw</span> <span class="p">=</span> <span class="n">Stopwatch</span><span class="p">.</span><span class="n">StartNew</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">try</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// Call the next delegate/middleware in the pipeline</span>
</span></span><span class="line"><span class="cl">            <span class="k">await</span> <span class="n">_next</span><span class="p">(</span><span class="n">context</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">finally</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// compare the counter of GCs after processing the request</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// if the count changed, a garbage collection occurred during the processing </span>
</span></span><span class="line"><span class="cl">            <span class="c1">// and might have slowed it down and maybe reaching SLA limit: this could </span>
</span></span><span class="line"><span class="cl">            <span class="c1">// explain 9x-percentile in slow requests for example</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">GetCurrentCollectionCount</span><span class="p">()</span> <span class="p">-</span> <span class="n">collectionCountBeforeProcessingTheRequest</span> <span class="p">!=</span> <span class="m">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="c1">// update with collection metric</span>
</span></span><span class="line"><span class="cl">               <span class="n">RequestCountersEventSource</span><span class="p">.</span><span class="n">Instance</span><span class="p">.</span><span class="n">AddRequestWithGcDuration</span><span class="p">(</span><span class="n">sw</span><span class="p">.</span><span class="n">ElapsedMilliseconds</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="k">else</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="c1">// update without collection metric</span>
</span></span><span class="line"><span class="cl">                <span class="n">RequestCountersEventSource</span><span class="p">.</span><span class="n">Instance</span><span class="p">.</span><span class="n">AddRequestWithoutGcDuration</span><span class="p">(</span><span class="n">sw</span><span class="p">.</span><span class="n">ElapsedMilliseconds</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kt">int</span> <span class="n">GetCurrentCollectionCount</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">int</span> <span class="n">count</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">GC</span><span class="p">.</span><span class="n">MaxGeneration</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">count</span> <span class="p">+=</span> <span class="n">GC</span><span class="p">.</span><span class="n">CollectionCount</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">count</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The interesting part is in the <code>RequestCountersEventSource</code> implementation.</p>
<h2 id="use-an-eventsource-luke">Use an EventSource Luke!</h2>
<p>As explained in the previous post, an <code>EventSource</code> instance is used as the “server” part of the <strong>EventPipe</strong> communication channel. It exposes a name that is used to identify it, but more important to listen to it with <strong>dotnet-trace</strong>, <strong>dotnet-counters,</strong> or your own listener as the <em>provider</em> name.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="na">[EventSource(Name = RequestCountersEventSource.SourceName)]</span>
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">RequestCountersEventSource</span> <span class="p">:</span> <span class="n">EventSource</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// this name will be used as &#34;provider&#34; name with dotnet-counters</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ex: dotnet-counters monitor -p &lt;pid&gt; Sample.RequestCounters</span>
</span></span><span class="line"><span class="cl">    <span class="c1">//</span>
</span></span><span class="line"><span class="cl">    <span class="kd">const</span> <span class="kt">string</span> <span class="n">SourceName</span> <span class="p">=</span> <span class="s">&#34;Sample.RequestCounters&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">RequestCountersEventSource</span><span class="p">()</span> 
</span></span><span class="line"><span class="cl">        <span class="p">:</span> <span class="k">base</span><span class="p">(</span><span class="n">RequestCountersEventSource</span><span class="p">.</span><span class="n">SourceName</span><span class="p">,</span> <span class="n">EventSourceSettings</span><span class="p">.</span><span class="n">EtwSelfDescribingEventFormat</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// create the counters: they&#39;ll be bound to this event source + CounterGroup</span>
</span></span><span class="line"><span class="cl">        <span class="n">CreateCounters</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>This name is exposed via an <code>EventSourceAttribute</code> that decorates your <code>EventSource</code>-derived class (you could also pass it to the constructor). The counters are created in the constructor through the <code>CreateCounters</code> helper.</p>
<h2 id="pick-the-right-counterclass">Pick the right Counter class</h2>
<p>Before looking at the implementation of the <code>CreateCounters</code> method, you need to understand what kind of counters are available for you. In the previous post, I mentioned that the CLR was using <em>Mean</em> (that provides mean, max, and min values over the update interval) and <em>Sum</em> (to increment a single value) kinds of counters. Note that dotnet-counter will only show the mean value for <em>Mean</em> counters.
 
 In addition, the counters could either automatically poll the value from a callback (the method used by the CLR today), or your code could change a counter value by calling the <code>WriteMetric</code> method. The <code>EventCounter</code> class provides this helper and it does its best to compute the min/max/mean in <a href="https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/EventCounter.cs#L144">a lock-free way</a>.</p>
<p><img loading="lazy" src="/posts/2019-10-17_how-to-expose-your/1_8EnLvreQ7iS5gVkXhF8vfw.png"></p>
<p>The next question to answer is which one should you use.</p>
<p>In the case of the request with(out) GC example, I want to expose different metrics:</p>
<ul>
<li><em>Request count</em>: a <code>PollingCounter</code> will be used in addition to an int field incremented when a request is received.</li>
<li><em>Request count delta</em>: an <code>IncrementingCounter</code> associated with the same int value will provide the delta (i.e., number of requests processed during an interval)</li>
<li><em>Request with GC and without GC counts</em>: two <code>PollingCounter</code> instances based on two int fields incremented when a request with (or without respectively) GC are processed.</li>
<li><em>Duration of requests with and without GC</em>: two <code>EventCounter</code> instances updated when requests are processed.</li>
</ul>
<p>Here is the implementation of<code>CreateCounters</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">CreateCounters</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// the same request count can be used for two counters:</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// - raw request counter that will always increase</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// - increment counter that will automatically compute the delta</span>
</span></span><span class="line"><span class="cl">    <span class="c1">//   between the current value and the value when the counter</span>
</span></span><span class="line"><span class="cl">    <span class="c1">//   was previously sent</span>
</span></span><span class="line"><span class="cl">    <span class="n">_requestCount</span> <span class="p">??=</span> <span class="k">new</span> <span class="n">PollingCounter</span><span class="p">(</span><span class="s">&#34;request-count&#34;</span><span class="p">,</span> <span class="k">this</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">()</span> <span class="p">=&gt;</span> <span class="n">_requestCountValue</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="n">DisplayName</span> <span class="p">=</span> <span class="s">&#34;Requests count&#34;</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="n">_requestCountDelta</span> <span class="p">??=</span> <span class="k">new</span> <span class="n">IncrementingPollingCounter</span><span class="p">(</span><span class="s">&#34;request-count-delta&#34;</span><span class="p">,</span> <span class="k">this</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">()</span> <span class="p">=&gt;</span> <span class="n">_requestCountValue</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="n">DisplayName</span> <span class="p">=</span> <span class="s">&#34;New requests&#34;</span><span class="p">,</span> <span class="n">DisplayRateTimeScale</span> <span class="p">=</span> <span class="k">new</span> <span class="n">TimeSpan</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">1</span><span class="p">)</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// split the request counts between those for which a GC occured or not </span>
</span></span><span class="line"><span class="cl">    <span class="c1">// during their processing</span>
</span></span><span class="line"><span class="cl">    <span class="n">_noGcRequestCount</span> <span class="p">??=</span> <span class="k">new</span> <span class="n">PollingCounter</span><span class="p">(</span><span class="s">&#34;no-gc-request-count&#34;</span><span class="p">,</span> <span class="k">this</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">()</span> <span class="p">=&gt;</span> <span class="n">_noGcRequestCountValue</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="n">DisplayName</span> <span class="p">=</span> <span class="s">&#34;Requests (processed without GC) count&#34;</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="n">_withGcRequestCount</span> <span class="p">??=</span> <span class="k">new</span> <span class="n">PollingCounter</span><span class="p">(</span><span class="s">&#34;with-gc-request-count&#34;</span><span class="p">,</span> <span class="k">this</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">()</span> <span class="p">=&gt;</span> <span class="n">_withGcRequestsCountValue</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="n">DisplayName</span> <span class="p">=</span> <span class="s">&#34;Requests (processed during a GC) count&#34;</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// request duration counters (with or without GC happening during the processing)</span>
</span></span><span class="line"><span class="cl">    <span class="n">_noGcRequestDuration</span> <span class="p">??=</span> <span class="k">new</span> <span class="n">EventCounter</span><span class="p">(</span><span class="s">&#34;no-gc-request-duration&#34;</span><span class="p">,</span> <span class="k">this</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="n">DisplayName</span> <span class="p">=</span> <span class="s">&#34;Requests (processed without GC) duration in milli-seconds&#34;</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">_withGcRequestDuration</span> <span class="p">??=</span> <span class="k">new</span> <span class="n">EventCounter</span><span class="p">(</span><span class="s">&#34;with-gc-request-duration&#34;</span><span class="p">,</span> <span class="k">this</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="n">DisplayName</span> <span class="p">=</span> <span class="s">&#34;Requests (processed during a GC) duration in milli-seconds&#34;</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The request processing code of the ASP.NET Core middleware is relying on the following helper methods to update the counters:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">internal</span> <span class="k">void</span> <span class="n">AddRequestWithoutGcDuration</span><span class="p">(</span><span class="kt">long</span> <span class="n">elapsedMilliseconds</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">IncRequestCount</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">Interlocked</span><span class="p">.</span><span class="n">Increment</span><span class="p">(</span><span class="k">ref</span> <span class="n">_noGcRequestCountValue</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// compute min/max/mean</span>
</span></span><span class="line"><span class="cl">    <span class="n">_noGcRequestDuration</span><span class="p">?.</span><span class="n">WriteMetric</span><span class="p">(</span><span class="n">elapsedMilliseconds</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">internal</span> <span class="k">void</span> <span class="n">AddRequestWithGcDuration</span><span class="p">(</span><span class="kt">long</span> <span class="n">elapsedMilliseconds</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">IncRequestCount</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">Interlocked</span><span class="p">.</span><span class="n">Increment</span><span class="p">(</span><span class="k">ref</span> <span class="n">_withGcRequestsCountValue</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// compute min/max/mean</span>
</span></span><span class="line"><span class="cl">    <span class="n">_withGcRequestDuration</span><span class="p">?.</span><span class="n">WriteMetric</span><span class="p">(</span><span class="n">elapsedMilliseconds</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">IncRequestCount</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">Interlocked</span><span class="p">.</span><span class="n">Increment</span><span class="p">(</span><span class="k">ref</span> <span class="n">_requestCountValue</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>And that’s it!</p>
<h2 id="how-to-get-these-custom-counters">How to get these custom counters?</h2>
<p>The controller of the ASP.NET Core sample application is triggering (or not) garbage collections based on the parameters passed via the url:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="na">[Route(&#34;api/[controller]</span><span class="s">&#34;)]
</span></span></span><span class="line"><span class="cl"><span class="na">[ApiController]</span>
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">RequestController</span> <span class="p">:</span> <span class="n">ControllerBase</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// GET: api/Request/5</span>
</span></span><span class="line"><span class="cl"><span class="na">    [HttpGet(&#34;{id}&#34;)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">string</span> <span class="n">Get</span><span class="p">(</span><span class="kt">int</span> <span class="n">id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">id</span> <span class="p">==</span> <span class="p">-</span><span class="m">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="s">$&#34;pid = {Process.GetCurrentProcess().Id}&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">((</span><span class="n">id</span> <span class="p">&gt;=</span> <span class="m">0</span><span class="p">)</span> <span class="p">&amp;&amp;</span> <span class="p">(</span><span class="n">id</span> <span class="p">&lt;=</span> <span class="m">2</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">GC</span><span class="p">.</span><span class="n">Collect</span><span class="p">(</span><span class="n">id</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="s">$&#34;triggered GC {id}&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">id</span> <span class="p">&lt;=</span> <span class="m">10</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// trigger a given number of GCs up to 10</span>
</span></span><span class="line"><span class="cl">            <span class="n">TriggerGCs</span><span class="p">(</span><span class="n">id</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="s">$&#34;triggered {id} garbage collections&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="s">$&#34;value = {id}&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">void</span> <span class="n">TriggerGCs</span><span class="p">(</span><span class="kt">int</span> <span class="n">count</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">current</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">current</span> <span class="p">&lt;</span> <span class="n">count</span><span class="p">;</span> <span class="n">current</span><span class="p">++)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">GC</span><span class="p">.</span><span class="n">Collect</span><span class="p">(</span><span class="m">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>As explained earlier, it is possible to see the counter values with <strong>dotnet-counters</strong> by using the event source name as a provider with the following command line:</p>
<blockquote>
<p>dotnet counters monitor -p <pid> <strong>Sample.RequestCounters</strong></p>
</blockquote>
<p>Then if you trigger a few requests with and without GC, you should see the numbers change:</p>
<p><img loading="lazy" src="/posts/2019-10-17_how-to-expose-your/1_M-GjqpcH8BL4oG02sk3sEg.png"></p>
<p>The code available on <a href="https://github.com/chrisnas/ClrEvents">Github</a> has been updated to provide the middleware and the event source classes that demonstrate how to expose custom .NET Core counters.</p>
]]></content:encoded></item><item><title>.NET Core Counters internals: how to integrate counters in your monitoring pipeline</title><link>https://chrisnas.github.io/posts/2019-07-23_net-core-counters-internals/</link><pubDate>Tue, 23 Jul 2019 15:30:37 +0000</pubDate><guid>https://chrisnas.github.io/posts/2019-07-23_net-core-counters-internals/</guid><description>This post shows how to easily get .NET Core counters. Their internals are also detailed for a better understanding of usage/limits</description><content:encoded><![CDATA[<hr>
<p>This post of the series digs into the implementation details of the new .NET Core counters.</p>
<p>Part 1: <a href="/posts/2018-06-19_replace-net-performance-counters/">Replace .NET performance counters by CLR event tracing</a>.</p>
<p>Part 2: <a href="/posts/2018-07-26_grab-etw-session-providers/">Grab ETW Session, Providers and Events</a>.</p>
<p>Part 3: <a href="/posts/2018-09-28_monitor-finalizers-contention-threads/">CLR Threading events with TraceEvent</a>.</p>
<p>Part 4: <a href="/posts/2018-12-15_spying-on-net-garbage/">Spying on .NET Garbage Collector with TraceEvent</a>.</p>
<p>Part 5: <a href="/posts/2019-02-12_building-your-own-java/">Building your own Java GC logs in .NET</a></p>
<p>Part6: <a href="/posts/2019-05-28_spying-on-net-garbage/">Spying on .NET Core Garbage Collector with .NET Core EventPipes</a></p>
<h2 id="introduction">Introduction</h2>
<p>As explained in <a href="/posts/2018-12-06_in-process-clr-event/">a previous post</a>, <a href="/posts/2018-12-06_in-process-clr-event/">.NET Core 2.2 introduced</a> the <a href="https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.tracing.eventlistener?WT.mc_id=DT-MVP-5003325?view=netcore-2.2">EventListener class</a> to receive in-proc CLR events both on Windows and Linux. Starting with .NET Core 3.0 Preview 6, the <strong>EventPipe</strong>-based infrastructure makes it now possible to get these events from another process. The <a href="https://github.com/dotnet/diagnostics">diagnostics repository</a> contains the cross-platform tools leveraging this infrastructure:</p>
<ul>
<li><a href="https://github.com/dotnet/diagnostics/blob/master/documentation/dotnet-dump-instructions.md"><strong>dotnet-dump</strong></a>: take memory snapshot and allow analysis based on most SOS commands</li>
<li><a href="https://github.com/dotnet/diagnostics/blob/master/documentation/dotnet-trace-instructions.md"><strong>dotnet-trace</strong></a>: collect events emitted by the Core CLR and generate trace file to be analyzed with Perfview</li>
<li><a href="https://github.com/dotnet/diagnostics/blob/master/documentation/dotnet-counters-instructions.md"><strong>dotnet-counters</strong></a>: collect the metrics corresponding to some performance counters that used to be exposed by the .NET Framework</li>
</ul>
<p>At Criteo, our metrics are exposed in Grafana dashboards and it is interesting to figure out how the new counters are implemented and see how to fetch them via the <strong>EventPipe</strong> infrastructure. With this knowledge in hand, I’ve implemented helpers to let you get counters in less than 10 lines of code:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="n">_counterMonitor</span> <span class="p">=</span> <span class="k">new</span> <span class="n">CounterMonitor</span><span class="p">(</span><span class="n">_pid</span><span class="p">,</span> <span class="n">GetProviders</span><span class="p">());</span>
</span></span><span class="line"><span class="cl"><span class="n">_counterMonitor</span><span class="p">.</span><span class="n">CounterUpdate</span> <span class="p">+=</span> <span class="c1">// receive the value of one counter after the other</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">Task</span> <span class="n">monitorTask</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Task</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">_counterMonitor</span><span class="p">.</span><span class="n">Start</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="n">monitorTask</span><span class="p">.</span><span class="n">Start</span><span class="p">();</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>At the end of this post you will be able to very easily integrate any counter to your own monitoring pipeline!</p>
<h2 id="net-core-replacement-fornet-framework-performance-counters">.NET Core replacement for .NET Framework Performance Counters</h2>
<p>With .NET Core being cross-platform, performance counters were gone and, as explained in the previous posts of the series, CLR events were the only way to get metrics about how your .NET Core applications were behaving. However, with .NET Core 3.0, it is now possible to view a few metrics thanks to the <strong>dotnet-counters</strong> tool.</p>
<p>You can download and install the tools automatically if you have installed .NET Core SDK 2.1+. Microsoft is currently working to provide other ways to directly download the tools binaries without having to install the SDK or recompile the diagnostics repository.</p>
<p>Use the following command line to install dotnet-counters:
<code>dotnet tool install --global dotnet-counters --version 3.0.0-preview7.19365.2</code></p>
<p>Note that you need to have the same version both for the Core CLR runtime and for the tools because, as you will soon see, the monitoring and the monitored applications are communicating via a dedicated protocol (that have changed between previews) on top of a transport layer different between Windows and Linux.</p>
<p>After the installation, use the following command line <code>dotnet counters monitor -p </code> and you get a 1 second auto-refreshed view of counters.</p>
<p><img loading="lazy" src="/posts/2019-07-23_net-core-counters-internals/1_tzmG5E7_XphPKWYrK_YNzg.png"></p>
<p>These counters are exposed by the <em>System.Runtime</em> provider and are detailed with the <code>list</code> argument:</p>
<p><img loading="lazy" src="/posts/2019-07-23_net-core-counters-internals/1_vpUJf51QchDMQh9GPlWbRA.png"></p>
<p>This list is currently hard-coded in the <code>CreateKnownProviders</code> method. However, you are free to create your own provider and expose your application metrics as shown in <a href="https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.Tracing/documentation/EventCounterTutorial.md">this tutorial</a> (and in the next forthcoming post). In addition, if you are using ASP.NET Core, starting from Preview 7, then you could get a few counters from the “Microsoft.AspNetCore.Hosting” provider defined in <code>HostingEventSource.cs</code>.</p>
<h2 id="what-are-these-counters">What are these “counters”</h2>
<p>Even though it is nice to have a console-based cross-platform tool to see the values of counters change, what would be the cost to get them into your own monitoring pipeline? For example, at Criteo, we are pushing our metrics to Graphite in order to get nice Grafana dashboards. These graphical representations allow us to have a visual representation of the evolution of metrics over time. In addition, it is also possible to define alerts based on threshold for some metrics values (when CPU &gt; 85% for more than 5 seconds for example).</p>
<p>In a nutshell, dotnet-counters tool is listening to another application via <strong>EventPipe</strong>. Unlike .NET Framework performance counters that are polled by the monitoring application, the counters are pushed by the monitored .NET Core process.</p>
<p><img loading="lazy" src="/posts/2019-07-23_net-core-counters-internals/1_SuOEY89mW73PiMJkgHZsng.png"></p>
<p>In term of implementation, these counters are values that you could get via .NET internal or public APIs if you were running in-proc as shown <a href="https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/RuntimeEventSource.cs#L47">in RuntimeEventSource.cs</a>:</p>
<p><img loading="lazy" src="/posts/2019-07-23_net-core-counters-internals/1_NaWnrko0FZBfR1IPpzY0iw.png"></p>
<p>Unlike most of the events that previous posts of this series presented, counters are metrics that are computed by the CLR in the monitored application. They are supposed to provide a set of values changing over time in the monitored application without impacting the performance nor flooding the listener client. I highly recommend to take a look at <a href="https://github.com/dotnet/diagnostics/issues/346">this issue</a> for a deeper discussion about <strong>EventCounters</strong> compared to regular events.</p>
<p>As of Preview 7, two types of counters are used:</p>
<ul>
<li><em>Mean</em>: supposed to contain a mean of all values during the polling interval with its min and max values. However, based on <a href="https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/PollingCounter.cs#L70">the current implementation</a>, all contain only the current value.</li>
<li><em>Sum</em>: contains an increment between the previous value and the current one</li>
</ul>
<p><img loading="lazy" src="/posts/2019-07-23_net-core-counters-internals/1_Qxhek7OZy1N-wfDNdKecpQ.png"></p>
<p>The question is now to figure out how to get the values of the counters.</p>
<h2 id="how-to-receive-the-counters">How to receive the counters?</h2>
<p>Like the Perfview tool that relies on <strong>TraceEvent</strong> library, dotnet-counters uses an API exposed by <strong>Microsoft.Diagnostics.Tools.RuntimeClient</strong> assembly. Note that it is currently <a href="https://github.com/dotnet/diagnostics/issues/343">not (yet) available from nuget</a> so you need to recompile it with the <a href="https://github.com/dotnet/diagnostics/issues/343">diagnostics git repo</a>.</p>
<p>To receive counters, you need to create an <strong>EventPipe</strong> session that communicates via IPC (named pipes on Windows and domain sockets on Linux) with the CLR of the monitored process. Here is an excerpt of the <code>CounterMonitor.StartMonitoring</code> <a href="https://github.com/dotnet/diagnostics/blob/master/src/Tools/dotnet-counters/CounterMonitor.cs#L177">implementation</a> that connects and listens to counter events:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">configuration</span> <span class="p">=</span> <span class="k">new</span> <span class="n">SessionConfiguration</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">circularBufferSizeMB</span><span class="p">:</span> <span class="m">1000</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">outputPath</span><span class="p">:</span> <span class="s">&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">providers</span><span class="p">:</span> <span class="n">Trace</span><span class="p">.</span><span class="n">Extensions</span><span class="p">.</span><span class="n">ToProviders</span><span class="p">(</span><span class="n">providerString</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">binaryReader</span> <span class="p">=</span> <span class="n">EventPipeClient</span><span class="p">.</span><span class="n">CollectTracing</span><span class="p">(</span><span class="n">_processId</span><span class="p">,</span> <span class="n">configuration</span><span class="p">,</span> <span class="k">out</span> <span class="n">_sessionId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">EventPipeEventSource</span> <span class="n">source</span> <span class="p">=</span> <span class="k">new</span> <span class="n">EventPipeEventSource</span><span class="p">(</span><span class="n">binaryReader</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">source</span><span class="p">.</span><span class="n">Dynamic</span><span class="p">.</span><span class="n">All</span> <span class="p">+=</span> <span class="n">ProcessEvents</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">source</span><span class="p">.</span><span class="n">Process</span><span class="p">();</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The important method call is call is <code>EventPipeClient.CollectTracing()</code> that returns a <code>Stream</code> from which an <code>EventPipeEventSource</code> instance gets created. This class has been added to <strong>TraceEvent</strong> so you can now leverage the event parsing infrastructure on top of <strong>EventPipe</strong>! As shown in <a href="/posts/2018-07-26_grab-etw-session-providers/">a previous post</a>, it is easy to attach a listener to the source <code>All</code> .NET event and get notified each time an event is received after the <code>Process</code> method is called.</p>
<p>A few parameters are given to <code>CollectTracing</code> via the <code>SessionConfiguration</code> object: the size of the circular buffer used by the CLR and no file path because we want a live session. The last one is supposed to filter which providers and counters you would like to listen to: it expects a list of <code>Provider</code> instances. This struct <a href="https://github.com/dotnet/diagnostics/blob/master/src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/Provider.cs#L10">is created with a few parameters</a>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl">    <span class="kd">public</span> <span class="k">struct</span> <span class="nc">Provider</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="n">Provider</span><span class="p">(</span><span class="kt">string</span> <span class="n">name</span><span class="p">,</span> <span class="kt">ulong</span> <span class="n">keywords</span> <span class="p">=</span> <span class="kt">ulong</span><span class="p">.</span><span class="n">MaxValue</span><span class="p">,</span> 
</span></span><span class="line"><span class="cl">                        <span class="n">EventLevel</span> <span class="n">eventLevel</span> <span class="p">=</span> <span class="n">EventLevel</span><span class="p">.</span><span class="n">Verbose</span><span class="p">,</span> 
</span></span><span class="line"><span class="cl">                        <span class="kt">string</span> <span class="n">filterData</span> <span class="p">=</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span> <span class="p">...</span> <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>As we have already mentioned, the name of the provider is “<em>System.Runtime</em>” for the Core CLR counters. The keywords and event level are expected to have these max values. The filter data string starts with “<em>EventCounterIntervalSec=</em>” followed by the refresh interval in seconds. Internally, the CLR in the monitored application <a href="https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/CounterGroup.cs#L135">is creating a timer</a> with that frequency to push the counters via <strong>EventPipe</strong> (more on this later).</p>
<p>Here is a helper class to easily create your providers:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">CounterHelpers</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="n">Provider</span> <span class="n">MakeProvider</span><span class="p">(</span><span class="kt">string</span> <span class="n">name</span><span class="p">,</span> <span class="kt">int</span> <span class="n">refreshIntervalInSec</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">filterData</span> <span class="p">=</span> <span class="n">BuildFilterData</span><span class="p">(</span><span class="n">refreshIntervalInSec</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="k">new</span> <span class="n">Provider</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="m">0xFFFFFFFF</span><span class="p">,</span> <span class="n">EventLevel</span><span class="p">.</span><span class="n">Verbose</span><span class="p">,</span> <span class="n">filterData</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">static</span> <span class="kt">string</span> <span class="n">BuildFilterData</span><span class="p">(</span><span class="kt">int</span> <span class="n">refreshIntervalInSec</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">refreshIntervalInSec</span> <span class="p">&lt;</span> <span class="m">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">throw</span> <span class="k">new</span> <span class="n">ArgumentOutOfRangeException</span><span class="p">(</span><span class="n">nameof</span><span class="p">(</span><span class="n">refreshIntervalInSec</span><span class="p">),</span> <span class="s">$&#34;must be at least 1 second&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="s">$&#34;EventCounterIntervalSec={refreshIntervalInSec}&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Note that <code>dotnet-counters</code> allows you to pass a subset of the counters with the <em>System.Runtime[counter1,counter2,counter2]</em> syntax: events for all System.Runtime counters will be received but only these three will be displayed in the console.</p>
<h2 id="show-time-for-counterevents">Show time for counter events!</h2>
<p>Next, the important part of the job takes place in the <code>EventSourc.All</code> event listener. Each new counter value is received in the payload of an event named “<em>EventCounters</em>”.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">ProcessEvents</span><span class="p">(</span><span class="n">TraceEvent</span> <span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">EventName</span><span class="p">.</span><span class="n">Equals</span><span class="p">(</span><span class="s">&#34;EventCounters&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">IDictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="kt">object</span><span class="p">&gt;</span> <span class="n">countersPayload</span> <span class="p">=</span> <span class="p">(</span><span class="n">IDictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="kt">object</span><span class="p">&gt;)(</span><span class="n">data</span><span class="p">.</span><span class="n">PayloadValue</span><span class="p">(</span><span class="m">0</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">        <span class="n">IDictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="kt">object</span><span class="p">&gt;</span> <span class="n">kvPairs</span> <span class="p">=</span> <span class="p">(</span><span class="n">IDictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="kt">object</span><span class="p">&gt;)(</span><span class="n">countersPayload</span><span class="p">[</span><span class="s">&#34;Payload&#34;</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">name</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="n">Intern</span><span class="p">(</span><span class="n">kvPairs</span><span class="p">[</span><span class="s">&#34;Name&#34;</span><span class="p">].</span><span class="n">ToString</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">displayName</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="n">Intern</span><span class="p">(</span><span class="n">kvPairs</span><span class="p">[</span><span class="s">&#34;DisplayName&#34;</span><span class="p">].</span><span class="n">ToString</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">counterType</span> <span class="p">=</span> <span class="n">kvPairs</span><span class="p">[</span><span class="s">&#34;CounterType&#34;</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">counterType</span><span class="p">.</span><span class="n">Equals</span><span class="p">(</span><span class="s">&#34;Sum&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">OnSumCounter</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">displayName</span><span class="p">,</span> <span class="n">kvPairs</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">counterType</span><span class="p">.</span><span class="n">Equals</span><span class="p">(</span><span class="s">&#34;Mean&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">OnMeanCounter</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">displayName</span><span class="p">,</span> <span class="n">kvPairs</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">throw</span> <span class="k">new</span> <span class="n">InvalidOperationException</span><span class="p">(</span><span class="s">$&#34;Unsupported counter type &#39;{counterType}&#39;&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <code>Name</code> and <code>DisplayName</code> values are self-explanatory. The <em>Sum</em>/<em>Mean</em> type is retrieved from <code>CounterType</code>.</p>
<p>The value for each counter type is retrieved from the payload with “Increment” (<em>Sum</em> type) or “Mean” (*Mean *type) keys.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"> <span class="kd">private</span> <span class="k">void</span> <span class="n">OnSumCounter</span><span class="p">(</span><span class="kt">string</span> <span class="n">name</span><span class="p">,</span> <span class="kt">string</span> <span class="n">displayName</span><span class="p">,</span> <span class="n">IDictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="kt">object</span><span class="p">&gt;</span> <span class="n">kvPairs</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">double</span> <span class="k">value</span> <span class="p">=</span> <span class="kt">double</span><span class="p">.</span><span class="n">Parse</span><span class="p">(</span><span class="n">kvPairs</span><span class="p">[</span><span class="s">&#34;Increment&#34;</span><span class="p">].</span><span class="n">ToString</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// send the information to your metrics pipeline</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">OnMeanCounter</span><span class="p">(</span><span class="kt">string</span> <span class="n">name</span><span class="p">,</span> <span class="kt">string</span> <span class="n">displayName</span><span class="p">,</span> <span class="n">IDictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="kt">object</span><span class="p">&gt;</span> <span class="n">kvPairs</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">double</span> <span class="k">value</span> <span class="p">=</span> <span class="kt">double</span><span class="p">.</span><span class="n">Parse</span><span class="p">(</span><span class="n">kvPairs</span><span class="p">[</span><span class="s">&#34;Mean&#34;</span><span class="p">].</span><span class="n">ToString</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// send the information to your metrics pipeline</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <code>CounterMonitor</code> class has been added on my <a href="https://github.com/chrisnas/ClrEvents">Github</a> to expose a <code>CounterUpdate</code> C# event when a counter event is received:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">CounterMonitor</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">...</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="k">event</span> <span class="n">Action</span><span class="p">&lt;</span><span class="n">CounterEventArgs</span><span class="p">&gt;</span> <span class="n">CounterUpdate</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">void</span> <span class="n">OnSumCounter</span><span class="p">(</span><span class="kt">string</span> <span class="n">name</span><span class="p">,</span> <span class="kt">string</span> <span class="n">displayName</span><span class="p">,</span> <span class="n">IDictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="kt">object</span><span class="p">&gt;</span> <span class="n">kvPairs</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">double</span> <span class="k">value</span> <span class="p">=</span> <span class="kt">double</span><span class="p">.</span><span class="n">Parse</span><span class="p">(</span><span class="n">kvPairs</span><span class="p">[</span><span class="s">&#34;Increment&#34;</span><span class="p">].</span><span class="n">ToString</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// send the information to your metrics pipeline</span>
</span></span><span class="line"><span class="cl">        <span class="n">CounterUpdate</span><span class="p">(</span><span class="k">new</span> <span class="n">CounterEventArgs</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">displayName</span><span class="p">,</span> <span class="n">CounterType</span><span class="p">.</span><span class="n">Sum</span><span class="p">,</span> <span class="k">value</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">void</span> <span class="n">OnMeanCounter</span><span class="p">(</span><span class="kt">string</span> <span class="n">name</span><span class="p">,</span> <span class="kt">string</span> <span class="n">displayName</span><span class="p">,</span> <span class="n">IDictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="kt">object</span><span class="p">&gt;</span> <span class="n">kvPairs</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">double</span> <span class="k">value</span> <span class="p">=</span> <span class="kt">double</span><span class="p">.</span><span class="n">Parse</span><span class="p">(</span><span class="n">kvPairs</span><span class="p">[</span><span class="s">&#34;Mean&#34;</span><span class="p">].</span><span class="n">ToString</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// send the information to your metrics pipeline</span>
</span></span><span class="line"><span class="cl">        <span class="n">CounterUpdate</span><span class="p">(</span><span class="k">new</span> <span class="n">CounterEventArgs</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">displayName</span><span class="p">,</span> <span class="n">CounterType</span><span class="p">.</span><span class="n">Mean</span><span class="p">,</span> <span class="k">value</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The event argument contains the expected properties but other could be added if needed such as the timestamp for example:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">CounterEventArgs</span> <span class="p">:</span> <span class="n">EventArgs</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">internal</span> <span class="n">CounterEventArgs</span><span class="p">(</span><span class="kt">string</span> <span class="n">name</span><span class="p">,</span> <span class="kt">string</span> <span class="n">displayName</span><span class="p">,</span> <span class="n">CounterType</span> <span class="n">type</span><span class="p">,</span> <span class="kt">double</span> <span class="k">value</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">Counter</span> <span class="p">=</span> <span class="n">name</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">DisplayName</span> <span class="p">=</span> <span class="n">displayName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">Type</span> <span class="p">=</span> <span class="n">type</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">Value</span> <span class="p">=</span> <span class="k">value</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">string</span> <span class="n">Counter</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">string</span> <span class="n">DisplayName</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">CounterType</span> <span class="n">Type</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">double</span> <span class="n">Value</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">enum</span> <span class="n">CounterType</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">Sum</span> <span class="p">=</span> <span class="m">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">Mean</span> <span class="p">=</span> <span class="m">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="lets-show-somegraphs">Let’s show some graphs!</h2>
<p>With these helpers in hand, it is easy to integrate any counter to your monitoring pipeline. As an example, let’s see how to generate a .csv file used to create visual representations in Excel.</p>
<p><img loading="lazy" src="/posts/2019-07-23_net-core-counters-internals/1_e68YXXlCXrL6kY7HYs-Jow.png"></p>
<p>With a refresh rate of 1 second, one line containing the value of the CLR counters should be added to the .csv file every second. Since we get one event per counter, we need to know which is the “last” counter event sent by the CLR for a given 1 second counters push.</p>
<p>As mentioned earlier the <a href="https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/RuntimeEventSource.cs#L47">RuntimeEventSource</a> class defines the CLR counters. Each one is an instance of a type derived from the <code>DiagnoticCounter</code> class that <a href="https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/shared/System/Diagnostics/Tracing/DiagnosticCounter.cs#L45">associates its instances</a> to a <code>CounterGroup</code> also bound to the <code>RuntimeEventSource</code>. The <code>CounterGroup</code> class will setup a repeating timer responsible for creating the payload for its <code>DiagnosticCounter</code>-derived instances and ask the event source to send each to the monitoring application via <strong>EventPipe</strong>.</p>
<p><img loading="lazy" src="/posts/2019-07-23_net-core-counters-internals/1_U2SXMs1uV4x36fdjH7nKiA.png"></p>
<p>So we can rely on the order defined by the counters creation code in <code>RuntimeEventSource</code>: for a given push of counters, the name of the last one will be “<em>assembly-count</em>”. Beware that in a case of new counters (such as for ASP.NET Core), you would need to check what would be the last one of the counters series. Another way to work around would be to rely on the timestamps of each event but this could become flaky over time. It would have been great if a “<em>CounterSeries</em>”event containing the list of counter names would have been sent before any “<em>EventCounters</em>” of a series push (good idea for a pull request :^)</p>
<p>The <code>CsvCounterListener</code> class wraps the few lines of code needed to handle the events and add a line into the .csv file each time a series of counters is received:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">  1
</span><span class="lnt">  2
</span><span class="lnt">  3
</span><span class="lnt">  4
</span><span class="lnt">  5
</span><span class="lnt">  6
</span><span class="lnt">  7
</span><span class="lnt">  8
</span><span class="lnt">  9
</span><span class="lnt"> 10
</span><span class="lnt"> 11
</span><span class="lnt"> 12
</span><span class="lnt"> 13
</span><span class="lnt"> 14
</span><span class="lnt"> 15
</span><span class="lnt"> 16
</span><span class="lnt"> 17
</span><span class="lnt"> 18
</span><span class="lnt"> 19
</span><span class="lnt"> 20
</span><span class="lnt"> 21
</span><span class="lnt"> 22
</span><span class="lnt"> 23
</span><span class="lnt"> 24
</span><span class="lnt"> 25
</span><span class="lnt"> 26
</span><span class="lnt"> 27
</span><span class="lnt"> 28
</span><span class="lnt"> 29
</span><span class="lnt"> 30
</span><span class="lnt"> 31
</span><span class="lnt"> 32
</span><span class="lnt"> 33
</span><span class="lnt"> 34
</span><span class="lnt"> 35
</span><span class="lnt"> 36
</span><span class="lnt"> 37
</span><span class="lnt"> 38
</span><span class="lnt"> 39
</span><span class="lnt"> 40
</span><span class="lnt"> 41
</span><span class="lnt"> 42
</span><span class="lnt"> 43
</span><span class="lnt"> 44
</span><span class="lnt"> 45
</span><span class="lnt"> 46
</span><span class="lnt"> 47
</span><span class="lnt"> 48
</span><span class="lnt"> 49
</span><span class="lnt"> 50
</span><span class="lnt"> 51
</span><span class="lnt"> 52
</span><span class="lnt"> 53
</span><span class="lnt"> 54
</span><span class="lnt"> 55
</span><span class="lnt"> 56
</span><span class="lnt"> 57
</span><span class="lnt"> 58
</span><span class="lnt"> 59
</span><span class="lnt"> 60
</span><span class="lnt"> 61
</span><span class="lnt"> 62
</span><span class="lnt"> 63
</span><span class="lnt"> 64
</span><span class="lnt"> 65
</span><span class="lnt"> 66
</span><span class="lnt"> 67
</span><span class="lnt"> 68
</span><span class="lnt"> 69
</span><span class="lnt"> 70
</span><span class="lnt"> 71
</span><span class="lnt"> 72
</span><span class="lnt"> 73
</span><span class="lnt"> 74
</span><span class="lnt"> 75
</span><span class="lnt"> 76
</span><span class="lnt"> 77
</span><span class="lnt"> 78
</span><span class="lnt"> 79
</span><span class="lnt"> 80
</span><span class="lnt"> 81
</span><span class="lnt"> 82
</span><span class="lnt"> 83
</span><span class="lnt"> 84
</span><span class="lnt"> 85
</span><span class="lnt"> 86
</span><span class="lnt"> 87
</span><span class="lnt"> 88
</span><span class="lnt"> 89
</span><span class="lnt"> 90
</span><span class="lnt"> 91
</span><span class="lnt"> 92
</span><span class="lnt"> 93
</span><span class="lnt"> 94
</span><span class="lnt"> 95
</span><span class="lnt"> 96
</span><span class="lnt"> 97
</span><span class="lnt"> 98
</span><span class="lnt"> 99
</span><span class="lnt">100
</span><span class="lnt">101
</span><span class="lnt">102
</span><span class="lnt">103
</span><span class="lnt">104
</span><span class="lnt">105
</span><span class="lnt">106
</span><span class="lnt">107
</span><span class="lnt">108
</span><span class="lnt">109
</span><span class="lnt">110
</span><span class="lnt">111
</span><span class="lnt">112
</span><span class="lnt">113
</span><span class="lnt">114
</span><span class="lnt">115
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">CsvCounterListener</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="kt">string</span> <span class="n">_filename</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="kt">int</span> <span class="n">_pid</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="n">CounterMonitor</span> <span class="n">_counterMonitor</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="n">List</span><span class="p">&lt;(</span><span class="kt">string</span> <span class="n">name</span><span class="p">,</span> <span class="kt">double</span> <span class="k">value</span><span class="p">)&gt;</span> <span class="n">_countersValue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">CsvCounterListener</span><span class="p">(</span><span class="kt">string</span> <span class="n">filename</span><span class="p">,</span> <span class="kt">int</span> <span class="n">pid</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">_filename</span> <span class="p">=</span> <span class="n">filename</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">_pid</span> <span class="p">=</span> <span class="n">pid</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">_countersValue</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;(</span><span class="kt">string</span> <span class="n">name</span><span class="p">,</span> <span class="kt">double</span> <span class="k">value</span><span class="p">)&gt;();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="k">void</span> <span class="n">Start</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">_counterMonitor</span> <span class="p">!=</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">throw</span> <span class="k">new</span> <span class="n">InvalidOperationException</span><span class="p">(</span><span class="s">$&#34;Start can&#39;t be called multiple times&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">_counterMonitor</span> <span class="p">=</span> <span class="k">new</span> <span class="n">CounterMonitor</span><span class="p">(</span><span class="n">_pid</span><span class="p">,</span> <span class="n">GetProviders</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">        <span class="n">_counterMonitor</span><span class="p">.</span><span class="n">CounterUpdate</span> <span class="p">+=</span> <span class="n">OnCounterUpdate</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">Task</span> <span class="n">monitorTask</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Task</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">try</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">_counterMonitor</span><span class="p">.</span><span class="n">Start</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="k">catch</span> <span class="p">(</span><span class="n">Exception</span> <span class="n">x</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">Environment</span><span class="p">.</span><span class="n">FailFast</span><span class="p">(</span><span class="s">&#34;Error while listening to counters&#34;</span><span class="p">,</span> <span class="n">x</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">});</span>
</span></span><span class="line"><span class="cl">        <span class="n">monitorTask</span><span class="p">.</span><span class="n">Start</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">void</span> <span class="n">OnCounterUpdate</span><span class="p">(</span><span class="n">CounterEventArgs</span> <span class="n">args</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">_countersValue</span><span class="p">.</span><span class="n">Add</span><span class="p">((</span><span class="n">args</span><span class="p">.</span><span class="n">DisplayName</span><span class="p">,</span> <span class="n">args</span><span class="p">.</span><span class="n">Value</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// we know that the last CLR counter is &#34;assembly-count&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">args</span><span class="p">.</span><span class="n">Counter</span> <span class="p">==</span> <span class="s">&#34;assembly-count&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">SaveLine</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">            <span class="n">_countersValue</span><span class="p">.</span><span class="n">Clear</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kt">bool</span> <span class="n">isHeaderSaved</span> <span class="p">=</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">void</span> <span class="n">SaveLine</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(!</span><span class="n">isHeaderSaved</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">File</span><span class="p">.</span><span class="n">AppendAllText</span><span class="p">(</span><span class="n">_filename</span><span class="p">,</span> <span class="n">GetHeaderLine</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">            <span class="n">isHeaderSaved</span> <span class="p">=</span> <span class="kc">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">File</span><span class="p">.</span><span class="n">AppendAllText</span><span class="p">(</span><span class="n">_filename</span><span class="p">,</span> <span class="n">GetCurrentLine</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kt">string</span> <span class="n">GetHeaderLine</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">StringBuilder</span> <span class="n">buffer</span> <span class="p">=</span> <span class="k">new</span> <span class="n">StringBuilder</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">counter</span> <span class="k">in</span> <span class="n">_countersValue</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">buffer</span><span class="p">.</span><span class="n">AppendFormat</span><span class="p">(</span><span class="s">&#34;{0}\t&#34;</span><span class="p">,</span> <span class="n">counter</span><span class="p">.</span><span class="n">name</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// remove last tab</span>
</span></span><span class="line"><span class="cl">        <span class="n">buffer</span><span class="p">.</span><span class="n">Remove</span><span class="p">(</span><span class="n">buffer</span><span class="p">.</span><span class="n">Length</span> <span class="p">-</span> <span class="m">1</span><span class="p">,</span> <span class="m">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// add Windows-like new line because will be used in Excel</span>
</span></span><span class="line"><span class="cl">        <span class="n">buffer</span><span class="p">.</span><span class="n">Append</span><span class="p">(</span><span class="s">&#34;\r\n&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">buffer</span><span class="p">.</span><span class="n">ToString</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kt">string</span> <span class="n">GetCurrentLine</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">StringBuilder</span> <span class="n">buffer</span> <span class="p">=</span> <span class="k">new</span> <span class="n">StringBuilder</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">counter</span> <span class="k">in</span> <span class="n">_countersValue</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">buffer</span><span class="p">.</span><span class="n">AppendFormat</span><span class="p">(</span><span class="s">&#34;{0}\t&#34;</span><span class="p">,</span> <span class="n">counter</span><span class="p">.</span><span class="k">value</span><span class="p">.</span><span class="n">ToString</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// remove last tab</span>
</span></span><span class="line"><span class="cl">        <span class="n">buffer</span><span class="p">.</span><span class="n">Remove</span><span class="p">(</span><span class="n">buffer</span><span class="p">.</span><span class="n">Length</span> <span class="p">-</span> <span class="m">1</span><span class="p">,</span> <span class="m">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// add Windows-like new line because will be used in Excel</span>
</span></span><span class="line"><span class="cl">        <span class="n">buffer</span><span class="p">.</span><span class="n">Append</span><span class="p">(</span><span class="s">&#34;\r\n&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">buffer</span><span class="p">.</span><span class="n">ToString</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="k">void</span> <span class="n">Stop</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">_counterMonitor</span> <span class="p">==</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">throw</span> <span class="k">new</span> <span class="n">InvalidOperationException</span><span class="p">(</span><span class="s">$&#34;Stop can&#39;t be called before Start&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">_counterMonitor</span><span class="p">.</span><span class="n">Stop</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="n">_counterMonitor</span> <span class="p">=</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">_countersValue</span><span class="p">.</span><span class="n">Clear</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="n">IReadOnlyCollection</span><span class="p">&lt;</span><span class="n">Provider</span><span class="p">&gt;</span> <span class="n">GetProviders</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">providers</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">Provider</span><span class="p">&gt;();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// create default &#34;System.Runtime&#34; provider with a refresh every second</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">provider</span> <span class="p">=</span> <span class="n">CounterHelpers</span><span class="p">.</span><span class="n">MakeProvider</span><span class="p">(</span><span class="s">&#34;System.Runtime&#34;</span><span class="p">,</span> <span class="m">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">providers</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="n">provider</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">providers</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="whats-next">What’s next?</h2>
<p>You have seen how easy it is to be notified of CLR counters update. The integration to your own monitoring system should not be more complicated. However, you need to pay attention to the meaning of counter types between *Mean *and <em>Sum</em>. For example, the value you get for <strong>gen-0-count</strong> (<em>Sum</em>) counters is a difference between now and the previous computation. It means that you can’t have the “current” number of gen 0 collection at a given time.</p>
<p><img loading="lazy" src="/posts/2019-07-23_net-core-counters-internals/1_XKDMcfmyoXAVetidWvYmcA.png"></p>
<p>This is not a problem in the Excel example because you can “rebuild” a column that will contain the “current” count based on the previous value + the diff returned by the counter.</p>
<p><img loading="lazy" src="/posts/2019-07-23_net-core-counters-internals/1_3klYCATjxMGhjb_NRcrSZA.png"></p>
<p>Here is the resulting graph:</p>
<p><img loading="lazy" src="/posts/2019-07-23_net-core-counters-internals/1_a42c8gZoHWbMwKNBFSnsiA.png"></p>
<p>In other cases, you might need to feed your monitoring system with real count values and benefit from advanced charting such as non derivative computation to show a rate based on a series of values. At the end of the day, it is just a question of initial value from which rebuild a count. And if you think about it, you are often more interested in unexpected variations (i.e. differences returned by counters) when monitoring your application.</p>
<p>In addition to your business metrics, .NET Core Counters are usually enough to monitor the health of your applications. However, in order to investigate situations where counters value are showing weird results, you often need more details. For example spikes in garbage collections count might not be a problem if the pause time is not too long. Listening to specific CLR events as shown in previous posts of this series is a great way to unveil important metrics such as GC pause time, contentions duration or exception names without performance hit.</p>
<p>The code available on <a href="https://github.com/chrisnas/ClrEvents">Github</a> has been updated to provide the <code>CounterMonitor</code> and <code>CsvCounterListener</code> classes that demonstrates how to get .NET Core counters and generate .csv file usable in Excel.</p>
]]></content:encoded></item><item><title>Spying on .NET Garbage Collector with .NET Core EventPipes</title><link>https://chrisnas.github.io/posts/2019-05-28_spying-on-net-garbage/</link><pubDate>Tue, 28 May 2019 07:38:15 +0000</pubDate><guid>https://chrisnas.github.io/posts/2019-05-28_spying-on-net-garbage/</guid><description>This post shows how to use .NET Core EventPipes to build Garbage Collector logs. The emitted raw CLR events are described in details.</description><content:encoded><![CDATA[<hr>
<p>This post of the series shows how to generate GC logs in .NET Core with the new event pipes architecture and details the events emitted by the CLR during a collection.</p>
<p>Part 1: <a href="/posts/2018-06-19_replace-net-performance-counters/">Replace .NET performance counters by CLR event tracing</a>.</p>
<p>Part 2: <a href="/posts/2018-07-26_grab-etw-session-providers/">Grab ETW Session, Providers and Events</a>.</p>
<p>Part 3: CLR Threading events with TraceEvent.</p>
<p>Part 4: <a href="/posts/2018-12-15_spying-on-net-garbage/">Spying on .NET Garbage Collector with TraceEvent</a>.</p>
<p>Part 5: <a href="/posts/2019-02-12_building-your-own-java/">Building your own Java GC logs in .NET</a></p>
<h2 id="introduction">Introduction</h2>
<p>The previous episode of the series introduced the notion of “GC log”, well known in the Java world and how to implement it in .NET thanks to ETW and TraceEvent on Windows. This solution is easy but requires to create an ETW session (and to remember to close it)… and is also not supported on Linux. However, <a href="/posts/2018-12-06_in-process-clr-event/">.NET Core 2.2 introduced</a> the <a href="https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.tracing.eventlistener?WT.mc_id=DT-MVP-5003325?view=netcore-2.2">EventListener class</a> as the best way to receive CLR events both on Windows and Linux but only from inside the process itself. As of today, TraceEvent is not supporting live session with EventPipe/EventListener, only <a href="https://github.com/Microsoft/perfview/blob/master/src/TraceEvent/EventPipe/EventPipeEventSource.cs#L28">a file-based constructor is available</a>. This is unfortunate because it means that you can’t rely on the huge work done by TraceEvent to parse the CLR events; especially those related to garbage collections. The rest of the post will explain how to decipher raw events.</p>
<p>In addition, there is a bigger problem: the current .NET Core 2.2 implementation is <a href="https://github.com/dotnet/coreclr/issues/21380">not working for all CLR events</a>. Long story short, the <code>EventPipe</code> class relies on specific Thread Local Storage slot that is not set by GC background worker threads: the events are not emitted in that case. In addition, there is no per event timestamp information in 2.2. The implementation presented in this post relies on tests done with ETW traces and on the <a href="https://github.com/dotnet/coreclr/pull/21817">Pull Request</a> that fixes the issue for .NET Core 3.0, available in Preview 5.</p>
<h2 id="back-to-the-basics-what-events-are-emitted-by-thegc">Back to the basics: what events are emitted by the GC?</h2>
<p>The previous posts of the series were based on C# events raised by the TraceEvent parser with names different from the original CLR events and the <a href="https://docs.microsoft.com/en-us/dotnet/framework/performance/garbage-collection-etw-events?WT.mc_id=DT-MVP-5003325">corresponding Microsoft Docs</a>. When you implement your EventListener-derived class, each event is received as an <code>EventWrittenEventArgs</code> object in the <code>OnEventWritten</code> override. The <code>EventId</code> and <code>EventName</code> properties allow you to figure out which event is received. If you have worked with TraceEvent before, you might be using the <code>Opcode</code> property but even if a property with the same name exists in <code>EventWrittenEventArgs</code>, the value is completely different and should not be used.</p>
<p>The CLR is versioning the emitted events to be able to add information over time. For example, the <code>EventId</code> of the “GCStart” event is 1 but the <code>EventName</code> could be <em>GCStart</em>, <em>GCStart_V1</em> or <em>GCStart_V2</em> even though the Microsoft Docs seems to be <a href="https://docs.microsoft.com/en-us/dotnet/framework/performance/garbage-collection-etw-events#gcstart_v1_event?WT.mc_id=DT-MVP-5003325">stuck on version 1</a>. The following table lists the interesting GC events for .NET Core 2.2/3.0:</p>
<p><img loading="lazy" src="/posts/2019-05-28_spying-on-net-garbage/1_NFTfqDwPckWMA7Pjv9oX5A.png"></p>
<p>Look at <a href="https://docs.microsoft.com/en-us/dotnet/framework/performance/garbage-collection-etw-events?WT.mc_id=DT-MVP-5003325">the documentation related to each event</a>.</p>
<p>If you go back to <a href="/posts/2019-02-12_building-your-own-java/">this previous article</a> of the series, you notice that all details provided by the <code>TraceGC</code> argument are available except for the objects size before and after the collection. These values are embedded in the workload of the <em>GCPerHeapHistory</em> event by the GC code. Unfortunately, these details are not marshalled by the current <code>EventPipe</code> implementation to your <code>OnEventWritten</code> override (read <a href="https://github.com/dotnet/coreclr/issues/24506">https://github.com/dotnet/coreclr/issues/24506</a> for more details and when it will be fixed).</p>
<p>There is no strongly typed <code>EventArgs</code> per event and you need to know the name of the field you are interested in to get its index. From this index, you get its corresponding value from the <code>Payload</code> property of the received <code>EventWrittenArgs</code>. The following helper method is doing the heavy lifting for you:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="n">T</span> <span class="n">GetFieldValue</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;(</span><span class="n">EventWrittenEventArgs</span> <span class="n">e</span><span class="p">,</span> <span class="kt">string</span> <span class="n">fieldName</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// this is not very optimum in term of performance but should not be a problem</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">index</span> <span class="p">=</span> <span class="n">e</span><span class="p">.</span><span class="n">PayloadNames</span><span class="p">.</span><span class="n">IndexOf</span><span class="p">(</span><span class="n">fieldName</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">index</span> <span class="p">==</span> <span class="p">-</span><span class="m">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="k">default</span><span class="p">(</span><span class="n">T</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="n">T</span><span class="p">)</span> <span class="n">e</span><span class="p">.</span><span class="n">Payload</span><span class="p">[</span><span class="n">index</span><span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Now that all interesting events are known, it is time to figure out what is the sequence of events emitted during a garbage collection: a new line with the details should be added to the GC log file when the last event is received.</p>
<h2 id="what-is-the-exact-sequence-of-gcevents">What is the exact sequence of GC events</h2>
<p>So let’s go back to the main phases of a garbage collection with the related CLR events as shown in the following figure (with <a href="https://twitter.com/konradkokosa">Konrad Kokosa</a> courtesy from <a href="https://www.amazon.com/Pro-NET-Memory-Management-Performance/dp/148424026X">his book</a>)</p>
<p><img loading="lazy" src="/posts/2019-05-28_spying-on-net-garbage/1_pNJJ5L4IlEaOsH6aHzQ4tQ.png"></p>
<p>This is the expected events for the most complicated case: a background collection with possible foreground ephemeral (gen0 and gen1) collections while the GC threads are concurrently sweeping. However, it is not possible to rely on this specific order of events because the order changes, depending on workstation/background mode and generation 2/ephemeral. Each type of collection triggers events in different order as shown below:</p>
<h2 id="gen0gen1-and-gen-2-non-concurrent">Gen0/Gen1 and Gen 2 (non concurrent)</h2>
<p><img loading="lazy" src="/posts/2019-05-28_spying-on-net-garbage/1_wxICgleCgQgNQKQ7eBd8Zg.png"></p>
<h2 id="gen-2-background">Gen 2 (background)</h2>
<p><img loading="lazy" src="/posts/2019-05-28_spying-on-net-garbage/1_mV2osuRu1bwXSgppAvmhvQ.png"></p>
<p>Here is a more visual view of what could happen (dark blue is gen 2 and light blue are ephemeral gen0/1):</p>
<p><img loading="lazy" src="/posts/2019-05-28_spying-on-net-garbage/1_CJb-0oh4Z1vntA2JQpZsog.png"></p>
<h2 id="when-exactly-does-a-gcstart">When exactly does a GC start…</h2>
<p>The <strong>GCTriggered</strong> event notifies that a new collection will start except in the case of foreground ephemeral gen0/gen1 collections triggered during a background gen2. In that case, you could rely on the <strong>GCStart</strong> event and check if a background gen2 is running. This <strong>GCStart</strong> event provides the condemned generation in its <code>Depth</code> property. So I keep track of both the current background GC (if any) and the foreground GC (if any) in a <code>GCInfo</code> object:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">internal</span> <span class="k">class</span> <span class="nc">GCInfo</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// When a background garbage collection (BGC) is started,</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// other foreground garbage collection (FGC) for gen 0 and 1 could happen</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// before the original BGC ends</span>
</span></span><span class="line"><span class="cl">    <span class="c1">//</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">GCDetails</span> <span class="n">CurrentBGC</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// this could contain a FGC after a BGC has started</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// or a non-concurrent gen0/gen1/gen2 collection</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">GCDetails</span> <span class="n">GCInProgress</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <code>GCDetails</code> class keeps tracks of all the details gathered during a garbage collection:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">internal</span> <span class="k">class</span> <span class="nc">GCDetails</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">DateTime</span> <span class="n">TimeStamp</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">double</span> <span class="n">PauseDuration</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">int</span> <span class="n">Number</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">GCReason</span> <span class="n">Reason</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">GCType</span> <span class="n">Type</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">int</span> <span class="n">Generation</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">bool</span> <span class="n">IsCompacting</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">HeapDetails</span> <span class="n">Heaps</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <code>HeapDetails</code> stores the size of each generation after a collection:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">struct</span> <span class="nc">HeapDetails</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">long</span> <span class="n">Gen0Size</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">long</span> <span class="n">Gen1Size</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">long</span> <span class="n">Gen2Size</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">long</span> <span class="n">LOHSize</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <code>GCDetails</code> instance is created when the <strong>GCStart</strong> event is received:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">OnGcStart</span><span class="p">(</span><span class="n">EventWrittenEventArgs</span> <span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// This event is received after a collection is started</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">newGC</span> <span class="p">=</span> <span class="n">BuildGCDetails</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// If a BCG is already started, FGC (0/1) are possible and will finish before the BGC</span>
</span></span><span class="line"><span class="cl">    <span class="c1">//</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">GetFieldValue</span><span class="p">&lt;</span><span class="kt">uint</span><span class="p">&gt;(</span><span class="n">e</span><span class="p">,</span> <span class="s">&#34;Depth&#34;</span><span class="p">)</span> <span class="p">==</span> <span class="m">2</span><span class="p">)</span> <span class="p">&amp;&amp;</span> 
</span></span><span class="line"><span class="cl">        <span class="p">((</span><span class="n">GCType</span><span class="p">)</span><span class="n">GetFieldValue</span><span class="p">&lt;</span><span class="kt">uint</span><span class="p">&gt;(</span><span class="n">e</span><span class="p">,</span> <span class="s">&#34;Type&#34;</span><span class="p">)</span> <span class="p">==</span> <span class="n">GCType</span><span class="p">.</span><span class="n">BackgroundGC</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">_gcInfo</span><span class="p">.</span><span class="n">CurrentBGC</span> <span class="p">=</span> <span class="n">newGC</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">_gcInfo</span><span class="p">.</span><span class="n">GCInProgress</span> <span class="p">=</span> <span class="n">newGC</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// forthcoming expected events for gen 0/1 collections are GCGlobalHeapHistory then GCHeapStats</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="n">GCDetails</span> <span class="n">BuildGCDetails</span><span class="p">(</span><span class="n">EventWrittenEventArgs</span> <span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="k">new</span> <span class="n">GCDetails</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">Number</span> <span class="p">=</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">GetFieldValue</span><span class="p">&lt;</span><span class="kt">uint</span><span class="p">&gt;(</span><span class="n">e</span><span class="p">,</span> <span class="s">&#34;Count&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">Generation</span> <span class="p">=</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">GetFieldValue</span><span class="p">&lt;</span><span class="kt">uint</span><span class="p">&gt;(</span><span class="n">e</span><span class="p">,</span> <span class="s">&#34;Depth&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">Type</span> <span class="p">=</span> <span class="p">(</span><span class="n">GCType</span><span class="p">)</span><span class="n">GetFieldValue</span><span class="p">&lt;</span><span class="kt">uint</span><span class="p">&gt;(</span><span class="n">e</span><span class="p">,</span> <span class="s">&#34;Type&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="n">Reason</span> <span class="p">=</span> <span class="p">(</span><span class="n">GCReason</span><span class="p">)</span><span class="n">GetFieldValue</span><span class="p">&lt;</span><span class="kt">uint</span><span class="p">&gt;(</span><span class="n">e</span><span class="p">,</span> <span class="s">&#34;Reason&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>This is where it is important to remember if either a background or foreground GC is starting. In the former case, the <code>CurrentBGC</code> field is set and the <code>GCInProgress</code> field is set otherwise with a new <code>GCDetails</code> instance.</p>
<p>That way, when either of <strong>GCGlobalHistory</strong> or <strong>GCHeapStarts</strong> is received, it is easy to know what is the GC in progress; i.e. if a foreground GC is in progress, an event happens in its context (until the last one **GCHeapStats **that will clean the <code>GCInProcess</code> field):</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="n">GCDetails</span> <span class="n">GetCurrentGC</span><span class="p">(</span><span class="n">GCInfo</span> <span class="n">info</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">info</span><span class="p">.</span><span class="n">GCInProgress</span> <span class="p">!=</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">info</span><span class="p">.</span><span class="n">GCInProgress</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">info</span><span class="p">.</span><span class="n">CurrentBGC</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="-suspend-pause-application-threads-and-end-of-ephemeral-collections">… suspend, pause application threads and end of ephemeral collections</h2>
<p>The suspension and pause time are not that complicated to compute. The garbage collector code is relying on the <code>SuspendEE</code> and <code>RestartEE</code> methods provided by the .NET Execution Engine to suspend and restart the application threads respectively. Each of these methods emits a pair of <strong>GCxxxBegin</strong> and <strong>GCxxxEnd</strong> events. After <strong>GCSuspendEEBegin</strong> is emitted, the Execution Engine waits for the application threads to suspend their execution. When all threads are suspended, <strong>GCSuspendEEEnd</strong> gets emitted.</p>
<p>The <strong>GCRestartEEBegin</strong> event is emitted when the applications threads begin to resume their execution. When all application threads are resumed, <strong>GCRestartEEEnd</strong> gets emitted. The elapsed time between the <strong>GCSuspendEEEnd</strong> and <strong>GCRestartEEBegin</strong> events is counted as <em>suspension time</em>. However, for simplicity sake, my current implementation sums both the time spent by the Execution Engine to suspend the threads and the pause time due to the GC work.</p>
<p>The suspension start time is kept in <strong>GCInfo</strong>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="c1">// time when SuspendEEBegin is received for this process</span>
</span></span><span class="line"><span class="cl"><span class="c1">// --&gt; from here, all app threads will be suspended until RestartEEStop is received</span>
</span></span><span class="line"><span class="cl"><span class="c1">// Note that we don&#39;t know yet what will be the triggered GC</span>
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="n">DateTime</span><span class="p">?</span> <span class="n">SuspensionStart</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>It will be set when the <strong>GCSuspendEEBegin</strong> event is received:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">OnGcSuspendEEBegin</span><span class="p">(</span><span class="n">EventWrittenEventArgs</span> <span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// we don&#39;t know yet what will be the next GC corresponding to this suspension</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// so it is kept until next GCStart </span>
</span></span><span class="line"><span class="cl">    <span class="n">_gcInfo</span><span class="p">.</span><span class="n">SuspensionStart</span> <span class="p">=</span> <span class="n">e</span><span class="p">.</span><span class="n">TimeStamp</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>This implementation decision does not provide the same level of suspension details (no fine grain suspension time for inner foreground collections) as the one provided by the TraceEvent parsing.</p>
<p>The sibling <strong>GCRestartEEEnd</strong> event is used to (1) compute the total pause time and (2) detect when gen0/gen1/non concurrent gen2 collections end:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">OnGcRestartEEEnd</span><span class="p">(</span><span class="n">EventWrittenEventArgs</span> <span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">currentGC</span> <span class="p">=</span> <span class="n">GetCurrentGC</span><span class="p">(</span><span class="n">_gcInfo</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">currentGC</span> <span class="p">==</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// this should never happen, except if we are unlucky to have missed a GCStart event</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// compute suspension time</span>
</span></span><span class="line"><span class="cl">    <span class="kt">double</span> <span class="n">suspensionDuration</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">_gcInfo</span><span class="p">.</span><span class="n">SuspensionStart</span><span class="p">.</span><span class="n">HasValue</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">suspensionDuration</span> <span class="p">=</span> <span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="n">TimeStamp</span> <span class="p">-</span> <span class="n">_gcInfo</span><span class="p">.</span><span class="n">SuspensionStart</span><span class="p">.</span><span class="n">Value</span><span class="p">).</span><span class="n">TotalMilliseconds</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">_gcInfo</span><span class="p">.</span><span class="n">SuspensionStart</span> <span class="p">=</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// bad luck: a xxxBegin event has been missed</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">currentGC</span><span class="p">.</span><span class="n">PauseDuration</span> <span class="p">+=</span> <span class="n">suspensionDuration</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// could be the end of a gen0/gen1 or of a non concurrent gen2 GC</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">currentGC</span><span class="p">.</span><span class="n">Generation</span> <span class="p">&lt;</span> <span class="m">2</span><span class="p">)</span> <span class="p">||</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">currentGC</span><span class="p">.</span><span class="n">Type</span> <span class="p">==</span> <span class="n">GCType</span><span class="p">.</span><span class="n">NonConcurrentGC</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">GcEvents</span><span class="p">?.</span><span class="n">Invoke</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="n">BuildGcArgs</span><span class="p">(</span><span class="n">currentGC</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">        <span class="n">_gcInfo</span><span class="p">.</span><span class="n">GCInProgress</span> <span class="p">=</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// in case of background gen2, just need to sum the suspension time</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// --&gt; its end will be detected during GcGlobalHistory event</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="detect-other-collections-end-and-moredetails">Detect other collections end (and more details)</h2>
<p>As shown in the events workflow figure, the <strong>GCRestartEEBegin</strong>/<strong>GCRestartEEEnd</strong> duo of events are used to detect the end of non-concurrent gen0/1/2 collections. It is more complicated to detect the end of a gen2 background or inner ephemeral gen0/1 collections: <strong>GCGlobalHeapHistory</strong> for the former and <strong>GCHeapStats</strong> for the latter. However, these two events payload does not contain the piece of information to know if we are in a middle of a background gen 2 or not. With this details in mind, the code of the different event handlers is quite straightforward.</p>
<p>The generations size are retrieved from the <strong>GCHeapStat</strong> event:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="c1">// This event provides the size of each generation after the collection</span>
</span></span><span class="line"><span class="cl"><span class="c1">// Note: last event for non background GC (will be GCGlobalHeapHistory for background gen 2)</span>
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">OnGcHeapStats</span><span class="p">(</span><span class="n">EventWrittenEventArgs</span> <span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">currentGC</span> <span class="p">=</span> <span class="n">GetCurrentGC</span><span class="p">(</span><span class="n">_gcInfo</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">currentGC</span> <span class="p">==</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">currentGC</span><span class="p">.</span><span class="n">Heaps</span><span class="p">.</span><span class="n">Gen0Size</span> <span class="p">=</span> <span class="p">(</span><span class="kt">long</span><span class="p">)</span><span class="n">GetFieldValue</span><span class="p">&lt;</span><span class="kt">ulong</span><span class="p">&gt;(</span><span class="n">e</span><span class="p">,</span> <span class="s">&#34;GenerationSize0&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">currentGC</span><span class="p">.</span><span class="n">Heaps</span><span class="p">.</span><span class="n">Gen1Size</span> <span class="p">=</span> <span class="p">(</span><span class="kt">long</span><span class="p">)</span><span class="n">GetFieldValue</span><span class="p">&lt;</span><span class="kt">ulong</span><span class="p">&gt;(</span><span class="n">e</span><span class="p">,</span> <span class="s">&#34;GenerationSize1&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">currentGC</span><span class="p">.</span><span class="n">Heaps</span><span class="p">.</span><span class="n">Gen2Size</span> <span class="p">=</span> <span class="p">(</span><span class="kt">long</span><span class="p">)</span><span class="n">GetFieldValue</span><span class="p">&lt;</span><span class="kt">ulong</span><span class="p">&gt;(</span><span class="n">e</span><span class="p">,</span> <span class="s">&#34;GenerationSize2&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">currentGC</span><span class="p">.</span><span class="n">Heaps</span><span class="p">.</span><span class="n">LOHSize</span> <span class="p">=</span> <span class="p">(</span><span class="kt">long</span><span class="p">)</span><span class="n">GetFieldValue</span><span class="p">&lt;</span><span class="kt">ulong</span><span class="p">&gt;(</span><span class="n">e</span><span class="p">,</span> <span class="s">&#34;GenerationSize3&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// this is the last event for non background collections  during a background gen2 collections</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">_gcInfo</span><span class="p">.</span><span class="n">CurrentBGC</span> <span class="p">!=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">currentGC</span><span class="p">.</span><span class="n">Generation</span> <span class="p">&lt;</span> <span class="m">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">       <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">GcEvents</span><span class="p">?.</span><span class="n">Invoke</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="n">BuildGcArgs</span><span class="p">(</span><span class="n">currentGC</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">        <span class="n">_gcInfo</span><span class="p">.</span><span class="n">GCInProgress</span> <span class="p">=</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Remember this is the last event received for a gen0/gen1/foreground gen2 collection so I’m using it to clear the <code>GCInProgress</code> field: the next event will be for the current background gen2 if any (<code>CurrentBGC</code> field is not null) or a new collection.</p>
<p>As of today with Preview 5, the before/after generation sizes are not marshalled through event pipes (see the <a href="https://github.com/dotnet/coreclr/issues/24506">corresponding bug</a> for more details) so the **GCPerHeapHistory **event does not bring any value.</p>
<p>The last <strong>GCGlobalHeapHistory</strong> event of background gen 2 collection is also used to detect compaction:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="c1">// This event is used to figure out if a collection is compacting or not</span>
</span></span><span class="line"><span class="cl"><span class="c1">// Note: last event for background GC (will be GCHeapStats for ephemeral (0/1) and non concurrent gen 2 collections)</span>
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">OnGcGlobalHeapHistory</span><span class="p">(</span><span class="n">EventWrittenEventArgs</span> <span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">currentGC</span> <span class="p">=</span> <span class="n">GetCurrentGC</span><span class="p">(</span><span class="n">_gcInfo</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// check unexpected event (we should have received a GCStart first)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">currentGC</span> <span class="p">==</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">globalMask</span> <span class="p">=</span> <span class="n">GetFieldValue</span><span class="p">&lt;</span><span class="n">GCGlobalMechanisms</span><span class="p">&gt;(</span><span class="n">e</span><span class="p">,</span> <span class="s">&#34;GlobalMechanisms&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">currentGC</span><span class="p">.</span><span class="n">IsCompacting</span> <span class="p">=</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">globalMask</span> <span class="p">&amp;</span> <span class="n">GCGlobalMechanisms</span><span class="p">.</span><span class="n">Compaction</span><span class="p">)</span> <span class="p">==</span> <span class="n">GCGlobalMechanisms</span><span class="p">.</span><span class="n">Compaction</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// this is the last event for gen 2 background collections</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">((</span><span class="n">GetFieldValue</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">&gt;(</span><span class="n">e</span><span class="p">,</span> <span class="s">&#34;CondemnedGeneration&#34;</span><span class="p">)</span> <span class="p">==</span> <span class="m">2</span><span class="p">)</span> <span class="p">&amp;&amp;</span> <span class="p">(</span><span class="n">currentGC</span><span class="p">.</span><span class="n">Type</span> <span class="p">==</span> <span class="n">GCType</span><span class="p">.</span><span class="n">BackgroundGC</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// check unexpected generation mismatch</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">globalMask</span> <span class="p">=</span> <span class="p">(</span><span class="n">GCGlobalMechanisms</span><span class="p">)</span><span class="n">GetFieldValue</span><span class="p">&lt;</span><span class="kt">uint</span><span class="p">&gt;(</span><span class="n">e</span><span class="p">,</span> <span class="s">&#34;GlobalMechanisms&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">currentGC</span><span class="p">.</span><span class="n">IsCompacting</span> <span class="p">=</span>
</span></span><span class="line"><span class="cl">            <span class="p">(</span><span class="n">globalMask</span> <span class="p">&amp;</span> <span class="n">GCGlobalMechanisms</span><span class="p">.</span><span class="n">Compaction</span><span class="p">)</span> <span class="p">==</span> <span class="n">GCGlobalMechanisms</span><span class="p">.</span><span class="n">Compaction</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// this is the last event for gen 2 background collections</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">((</span><span class="n">GetFieldValue</span><span class="p">&lt;</span><span class="kt">uint</span><span class="p">&gt;(</span><span class="n">e</span><span class="p">,</span> <span class="s">&#34;CondemnedGeneration&#34;</span><span class="p">)</span> <span class="p">==</span> <span class="m">2</span><span class="p">)</span> <span class="p">&amp;&amp;</span> <span class="p">(</span><span class="n">currentGC</span><span class="p">.</span><span class="n">Type</span> <span class="p">==</span> <span class="n">GCType</span><span class="p">.</span><span class="n">BackgroundGC</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">GcEvents</span><span class="p">?.</span><span class="n">Invoke</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="n">BuildGcArgs</span><span class="p">(</span><span class="n">currentGC</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">            <span class="n">ClearCollections</span><span class="p">(</span><span class="n">_gcInfo</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>In case of a background gen 2, this is the last event so there should not be any collection in progress:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">ClearCollections</span><span class="p">(</span><span class="n">GCInfo</span> <span class="n">info</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">info</span><span class="p">.</span><span class="n">CurrentBGC</span> <span class="p">=</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">info</span><span class="p">.</span><span class="n">GCInProgress</span> <span class="p">=</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The next received event will start a new garbage collection cycle of events.</p>
<p>This post concludes the series about CLR events and how to use them to better understand how the runtime is behaving under the workloads of your applications. The code available on <a href="https://github.com/chrisnas/ClrEvents">Github</a> has been updated to provide the <code>EventListenerGcLog</code> class that uses the code demonstrated in this post to generate GC logs with event pipes.</p>
]]></content:encoded></item><item><title>Let’s debug the Core CLR with WinDBG!</title><link>https://chrisnas.github.io/posts/2019-04-04_let-debug-the-core/</link><pubDate>Thu, 04 Apr 2019 13:32:51 +0000</pubDate><guid>https://chrisnas.github.io/posts/2019-04-04_let-debug-the-core/</guid><description>This post of the series shows how you could easily debug the Core CLR in a real world case of insane thread contention duration.</description><content:encoded><![CDATA[<hr>
<p>This post of the series shows how we debugged the Core CLR to figure out insane contention duration.</p>
<p>Part 1: <a href="/posts/2018-06-19_replace-net-performance-counters/">Replace .NET performance counters by CLR event tracing</a>.</p>
<p>Part 2: <a href="/posts/2018-07-26_grab-etw-session-providers/">Grab ETW Session, Providers and Events</a>.</p>
<p>Part 3: CLR Threading events with TraceEvent.</p>
<p>Part 4: <a href="/posts/2018-12-15_spying-on-net-garbage/">Spying on .NET Garbage Collector with TraceEvent</a>.</p>
<p>Part 5: <a href="/posts/2019-02-12_building-your-own-java/">Building your own Java GC logs in .NET</a>.</p>
<h2 id="introduction">Introduction</h2>
<p>Long before migrating our .NET applications to Linux, our first step was to build a monitoring pipeline based on LTTng instead of ETW on Windows. To achieve this goal, the open source TraceEvent Nuget package needed to be updated in order to listen to LTTng live session (only a file based implementation was provided by Microsoft; mostly to allow Perfview to be able to open traces taken on Linux machines). This was a <a href="https://github.com/criteo-forks/perfview/pull/1">huge development task</a> that led sometimes to weird results. Among the metrics we wanted to monitor, the contention duration gave insane value such as thousands of minutes… per minute:</p>
<p><img loading="lazy" src="/posts/2019-04-04_let-debug-the-core/1_chjw_0ZlNI1GH6wc2tgNBg.png"></p>
<p>As shown in <a href="/posts/2018-09-28_monitor-finalizers-contention-threads/">a previous episode</a>, this duration is computed by comparing the time between the two events <strong>ContentStart</strong> and <strong>ContentionStop</strong>. What could be the possible reasons to get such insane values?</p>
<ol>
<li>
<p>A lot of small contentions are happening</p>
</li>
<li>
<p>A few very long contentions are happening</p>
</li>
</ol>
<p>As a first step, it would be great to be able to debug the Core CLR and figure out what call stacks end up to triggering these contention events. Unfortunately for us, the .NET debugging ecosystem on Linux is far from being as rich as on Windows. So this episode is detailing the steps to compile and debug the Core CLR on Windows with WinDBG.</p>
<h2 id="from-the-source-to-debugging-theruntime">From the source to debugging the runtime</h2>
<p>To better understand the implementation details in the CLR, we needed to find where the two events are emitted. In fact, during the CLR compilation, a lot of helpers are created based on the name of the event. In our case, <code>FireEtwContentionStart_V1</code> and <code>FireContentionStop</code> are the two helpers in charge. Both are called <a href="https://github.com/dotnet/coreclr/blob/master/src/vm/syncblk.cpp#L2993">in the <strong>AwareLock::EnterEpilogHelper</strong> function</a>.</p>
<p>As a Windows developer, I would like to debug the CLR code and set a breakpoint in the <code>EnterEpilogHelper</code> with Visual Studio to see what are the call stacks that end up to contention. However, I did not find a way to do it with Visual Studio. I turned to WinDBG and things gets “easier”… in a certain way.</p>
<p>Here are the different steps you need to follow before setting a breakpoint on any Core CLR function in WinDBG:</p>
<ul>
<li>Clone the Core CLR repository from <a href="https://github.com/dotnet/coreclr">https://github.com/dotnet/coreclr</a></li>
<li>Build it:</li>
<li>Get the Visual Studio, .NET Core SDK, CMake, Python, Powershell prerequisites from <a href="https://github.com/dotnet/coreclr/blob/master/Documentation/building/windows-instructions.md">the documentation</a></li>
<li>Goto the root folder and type <code>.\build -skiptests</code> to build a DEBUG version of the Core CLR</li>
<li>Leave your desk and go to lunch (ok… maybe just take a coffee break)</li>
</ul>
<ol start="3">
<li>When you go back, the result of the compilation should be available in the following folder:</li>
</ol>
<p>…\coreclr\bin\Product\Windows_NT.x64.debug.</p>
<ol start="4">
<li>the next step is to <a href="https://github.com/dotnet/coreclr/blob/master/Documentation/workflow/UsingYourBuild.md">use your custom Core CLR build</a> in the application:</li>
</ol>
<ul>
<li>the application must be <a href="https://docs.microsoft.com/en-us/dotnet/core/deploying/#self-contained-deployments-scd">self-contained</a> by adding <code>win-x64</code> (or linux-x64 for Linux) in a <code>PropertyGroup</code> section of the .csproj.</li>
<li>publish the application by running <code>dotnet publish</code> or from within Visual Studio</li>
</ul>
<p><img loading="lazy" src="/posts/2019-04-04_let-debug-the-core/1_Xvue5jy9443m9Zjih19cQw.png"></p>
<ul>
<li>Click the <em>Configure</em> link and select Debug configuration</li>
</ul>
<p><img loading="lazy" src="/posts/2019-04-04_let-debug-the-core/1_UDj6pcKSZutk3E7Kbvkfjw.png"></p>
<ul>
<li>after clicking <em>Save</em> and <em>Publish</em>, you should now have the result under the \bin\Debug\netcoreapp2.2\publish folder.</li>
<li>after clicking <em>Save</em> and <em>Publish</em>, you should now have the result under the \bin\Debug\netcoreapp2.2\publish folder.</li>
</ul>
<ol start="5">
<li>It is now time to copy the following files from the Core CLR output to your application publication folder:</li>
</ol>
<ul>
<li>coreclr.dll (for the native part of the CLR) and System.Private.CoreLib.dll (if the CLR C# code has been modified)</li>
<li>in the PDB subfolder, coreclr.pdb and System.Private.CoreLib.pdb</li>
<li>note that you might also need the sos.dll and mscordaccore.dll files for any investigation in WinDBG.</li>
</ul>
<p>If you wonder why the CoreFx repo is not rebuilt, the answer is simple: the contention related code is in the CoreCLR. Also, most of the managed “mscorlib” is defined in System.Private.CoreLib.dll that gets built with CoreCLR. The rest of the BCL is covered by CoreFX and not needed in this investigation.</p>
<h2 id="from-running-to-debugging-inwindbg">From running to debugging in WinDBG</h2>
<p>You should use <a href="https://github.com/dotnet/coreclr/blob/master/Documentation/workflow/UsingCoreRun.md">corerun.exe</a> instead of dotnet.exe to run an application with the debug version of the Core CLR you’ve just built.</p>
<p>Open up a command prompt in the <strong>coreclr\bin\Product\Windows_NT.x64.debug</strong> folder and type <code>corerun</code>** c:&lt;your path to the <strong>bin\Debug\netcoreapp2.2\publish</strong> folder of your application&gt;&lt;yourApp.dll&gt;**</p>
<p>You have to tell <code>corerun</code> where to find the CoreFx assemblies via the <code>CORE_LIBRARY</code> environment variable:</p>
<p><code>CORE_LIBRARIES=C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.2.0</code></p>
<p>If you forget about it, don’t be surprised if the application stops with <code>FileNoteFoundException</code> for a missing assembly (usually <strong>System.Runtime</strong>)…</p>
<p>If, like me, your applications are running with server mode GC, you know that it is set in the application .csproj file to end up into the runtimeconfig.json file. Unfortunately, this is not taken into account by <code>corerun</code> (yet?) and you need to set it (and if you need concurrent version too) explicitly through the following environment variables:</p>
<p><code>COMPlus_gcServer=1 COMPlus_gcConcurrent=1</code></p>
<p>From there, (<a href="https://www.microsoft.com/en-us/p/windbg-preview/9pgjgd53tn86">install WinDBG if not already done</a> and) start the debugger: click the <em>File</em> menu and select <em>Launch Executable (advanced)</em> to setup a debugging session:</p>
<p><img loading="lazy" src="/posts/2019-04-04_let-debug-the-core/1_ZyuzZ_qKw_bhpnCfq0WTDA.png"></p>
<p>The <em>Executable</em> text field points to the <strong>corerun.exe</strong> file generated during the compilation of the Core CLR. The same folder is used as <em>Start Directory</em> and the <em>Arguments</em> text field contains the full path of the application to debug. You could also attach to a running process but sometimes you need to access Core CLR data structures before any C#-compiled managed code of your application starts executing (to see how the garbage collector initializes for example).</p>
<p>As soon as you click the <em>Ok</em> button, the application starts but is almost immediately stopped by WinDBG</p>
<p><img loading="lazy" src="/posts/2019-04-04_let-debug-the-core/1_FFwwuUz6_xeF2AYBxn8K-A.png"></p>
<p>Don’t be scared by the last lines of the output: even through you read the word <strong>exception</strong>, this <code>int 3</code>** **assembly instruction tells you that a breakpoint has been set for you by WinDBG, has been hit when the application reached it and the application is now paused just before calling its entry point.</p>
<p>As you can see from the list of loaded modules, even though CoreRun.exe is there, no managed assembly (especially your application) has been loaded yet; not even the Core CLR itself! This means that you have to tell WinDBG to keep on executing the application until a point you would be interested in. To achieve that task, you will first need a quick tour of WinDBG user interface even though this post is not there to replace the <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debugging-using-windbg-preview">WinDBG online help</a> nor provide a detailed walkthrough.</p>
<p>The debugging section of the <em>Home</em> tab is not too different from what you get in Visual Studio:</p>
<p><img loading="lazy" src="/posts/2019-04-04_let-debug-the-core/1_h5nRlDCO_PXfonPtcVwFgg.png"></p>
<p>The icons are even easier to understand because their action is also displayed. If you want to see the current call stack, select the <em>View</em> tab and click the <strong>Stack</strong> icon:</p>
<p><img loading="lazy" src="/posts/2019-04-04_let-debug-the-core/1_UgBlXPkw57Gja-_ANnkfTw.png"></p>
<p>Like in Visual Studio, you are able to pin each panel wherever you want into the IDE</p>
<p><img loading="lazy" src="/posts/2019-04-04_let-debug-the-core/1_kqvC1tJsfN_lpaD6rclGjA.png"></p>
<p>The next step would be to set a breakpoint on the line of code you are interested in. But let’s be clear here: I’m talking about a line of code in a function exported by a native dll; not a line of C# code in a managed assembly. Remember that WinDBG is a native debugger and it debugs only native code. If you want to debug managed code with WinDBG, you need to use commands from the sos extension; but <a href="https://docs.microsoft.com/en-us/archive/blogs/tess/setting-breakpoints-in-net-code-using-bpmd?WT.mc_id=DT-MVP-5003325">this is another story</a>.</p>
<p>So let’s go back to the native world. Even though WinDBG does not have the notion of “solution” like Visual Studio provides, it is still possible to open a C++ file and set a breakpoint in it. Click <em>File |Open Source File</em> menu and go to your Core CLR github repo to select syncblk.cpp under the \src\vm folder. Look for <code>AwareLock::EnterEpilogHelper</code> with CTRL+F (yes: search is working in WinDBG) and go down to the call to the <code>FireEtwContentionStart_V1</code> helper. Setting a breakpoint on this line is as simple as pressing **F9 **like in Visual Studio. Press the <em>View</em> tab and click the <em>Breakpoint</em> button to see the result:</p>
<p><img loading="lazy" src="/posts/2019-04-04_let-debug-the-core/1_O1BnT-PQKdoFGo0NxyiOtw.png"></p>
<p>Since the dll in which the breakpoint is set is not loaded yet, you can’t see the details of the breakpoint.</p>
<p>There is a way to tell WinDBG to continue the execution of the application until a dll get’s loaded. For coreclr.dll, type the following command:</p>
<p><code>sxeld:coreclr</code></p>
<p>and type <strong>F5</strong> (or type <code>g</code> as a command or click the green triangle in the <em>Home</em> toolbar) to resume the execution of the application. The <em>Breakpoints</em> panel shows more details now:</p>
<p><img loading="lazy" src="/posts/2019-04-04_let-debug-the-core/1_SCPPNlsp-9Oqp0uk2LcTUw.png"></p>
<p>Press <strong>F5</strong> to resume the execution and the breakpoint should be triggered when the first contention happens.</p>
<h2 id="from-symbols-to-call-stacks-inwindbg">From symbols to call stacks in WinDBG</h2>
<p>Before digging into call stacks, I would like to show you one of the differences between native dlls and managed assemblies. As a .NET developer, you are used to Intellisense and strongly typed environment provided by the metadata stored in an assembly itself. For native dll, the story is different. Exported functions are visible with tools such as <a href="http://www.dependencywalker.com/">Dependency Walker</a> or <code>dumpbin /exports</code> from the SDK. If the dll exports symbols built by the C++ compiler, their name gets <em>mangled</em> to describe their signature. To get human readable symbols, you need the associated .pdb file. It will also be required to map a function address to its name in call stacks.</p>
<p>WinDBG allows you to browse these symbols with the <code>dt</code> command. For example, if you want to know all members defined by the <strong>AwareLock</strong> class, use the following command:</p>
<p><code>dt CoreClr!AwareLock::*</code></p>
<p>Like what was shown in the previous <em>Breakpoints</em> screenshot, the prefix of a name is the dll in which the symbol is defined. Next, use <code>!</code> as separator before the class name. Since Visual Studio is really slow to navigate the source code of the Core CLR or search in the thousands of include and C/C++ files, this is a very convenient way to navigate and learn its different parts. Don’t forget that the compilation could also inline functions (that won’t be visible in the symbols) and expand macros.</p>
<p>If you want to set a breakpoint on a function, use the <code>bp</code> command with the same syntax as <code>dt</code>. For example, the following command:</p>
<p><code>bp coreClr!AwareLock::EnterEpilogHelper</code></p>
<p>sets a breakpoint at the beginning of the function in which I already set a breakpoint.</p>
<p>This is the very basics of breakpoints in WinDBG. You are also able to define which actions to start when a breakpoint is hit. This is extremely powerful! For example, in the case of thread contention, you typically don’t want to stop the execution of the application because it will pause all threads and disturb the normal flow of execution that could lead to thread contention. Instead, you could ask WinDBG to dump the call stack leading to the function we are interested in and lets the execution resume with the following syntax:</p>
<p><code>bp coreClr!AwareLock::EnterEpilogHelper &quot;!clrstack; g&quot;</code></p>
<p>The commands to execute after the breakpoint is hit are defined between quotes. In this example, I’m using the <code>clrstack</code> command exported by the sos.dll extension (that must be previously loaded via <code>.loadby sos coreclr</code>) and once it is done, <code>g</code> resumes the execution.</p>
<h2 id="whats-next">What’s next?</h2>
<p>Due to automatic suspension of all threads when the <code>clrstack</code> command gets executed (before <code>g</code> resumes), the interactions between threads are not the same as normal execution outside of a debugger. I have even used <a href="https://github.com/criteo-forks/coreclr/commit/7394345097a78c7be3241939d357595ebad9b26a">some code available in DEBUG to dump the callstacks outside of a debugger</a> if the contention last more than a threshold. However, it was not possible to reproduce the problem on Windows.</p>
<p>In parallel on Linux, another colleague investigated another lead: some events may also be skipped by our LTTng implementation. Due to complicated event management, if a <strong>ContentionStop</strong> and <strong>ContentionStart</strong> are missed, a possible previous <strong>ContentionStart</strong> could be used by the next <strong>ContentionStop</strong> and the duration would be unrelated to the real contention that happened.</p>
<p>So there could be a simpler way to narrow down the issue: instead of relying on two events, why not simply compute the duration of the contention in the <code>AwareLock::EnterEpilogHelper</code> function and emit only one new event with the duration as payload? Well… this will be the topic of the next episode of this series.</p>
<h2 id="references">References</h2>
<p>Series of videos from the Defrag Tools show where <a href="https://twitter.com/maoni0">Maoni Stephens</a> explains how to debug the Garbage Collector for a better understanding of its arcana</p>
<ul>
<li><a href="https://channel9.msdn.com/Shows/Defrag-Tools/Defrag-Tools-33-CLR-GC-Part-1">https://channel9.msdn.com/Shows/Defrag-Tools/Defrag-Tools-33-CLR-GC-Part-1</a></li>
<li><a href="https://channel9.msdn.com/Shows/Defrag-Tools/Defrag-Tools-34-CLR-GC-Part-2">https://channel9.msdn.com/Shows/Defrag-Tools/Defrag-Tools-34-CLR-GC-Part-2</a></li>
<li><a href="https://channel9.msdn.com/Shows/Defrag-Tools/Defrag-Tools-35-CLR-GC-Part-3">https://channel9.msdn.com/Shows/Defrag-Tools/Defrag-Tools-35-CLR-GC-Part-3</a></li>
<li><a href="https://channel9.msdn.com/Shows/Defrag-Tools/Defrag-Tools-36-CLR-GC-Part-4">https://channel9.msdn.com/Shows/Defrag-Tools/Defrag-Tools-36-CLR-GC-Part-4</a></li>
</ul>
]]></content:encoded></item><item><title>Debugging Friday — Hunting down race condition</title><link>https://chrisnas.github.io/posts/2019-02-22_debugging-friday-hunting-down/</link><pubDate>Fri, 22 Feb 2019 09:25:50 +0000</pubDate><guid>https://chrisnas.github.io/posts/2019-02-22_debugging-friday-hunting-down/</guid><description>Another .NET investigation of our Debugging team to learn how to leverage tracepoint, object naming or parallel stacks in Visual Studio</description><content:encoded><![CDATA[<hr>
<h2 id="introduction">Introduction</h2>
<p>At Criteo, CLR metrics are collected by a service that listens to ETW events (<a href="/posts/2018-09-28_monitor-finalizers-contention-threads/">see the related series</a>). On a few servers, the metrics stopped being collected and we had to fix the problem <a href="/posts/2018-11-13_get-process-name-challenge/">by manually polling new and dead processes</a>. After deploying the new version, the same scenario started to happen: on some servers, the metrics were no more collected.</p>
<p>In an investigation, the first step is always trying to check the environment. In our case, on a server where the metrics collector is up and running, a dedicated ETW session should be created to listen to the CLR events.</p>
<p><img loading="lazy" src="/posts/2019-02-22_debugging-friday-hunting-down/1_BHlopgHd4ytq5YqbDWCdGQ.png"></p>
<p>The name given to the session allows us to easily detect if the session is present or not. In the case of a faulted server, the session was not present.</p>
<p>If you look at the code described in <a href="/posts/2018-07-26_grab-etw-session-providers/">a previous post</a>, it is not easy to guess why the session would be stopped by the metrics collector:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">ListenToEtw</span><span class="p">(</span><span class="n">TraceEventSession</span> <span class="n">etwSession</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// this call is blocking... until ewtSession.Stop is called (done in Dispose)</span>
</span></span><span class="line"><span class="cl">        <span class="n">etwSession</span><span class="p">.</span><span class="n">Source</span><span class="p">.</span><span class="n">Process</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">finally</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">etwSession</span><span class="p">.</span><span class="n">Dispose</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>A <code>TraceEventSession</code> is created and passed to a dedicated thread to process the events until <code>Stop</code> is called at the end of the application.</p>
<p>The second step of an investigation is trying to reproduce the issue in a controlled environment such as… my developer machine. I setup the Exception Settings of the debugger to stop on any managed exception:</p>
<p><img loading="lazy" src="/posts/2019-02-22_debugging-friday-hunting-down/1_AVxIk96bk6njBTlCAhbwwA.png"></p>
<p>That way, if something bad happens while I’m debugging the application, Visual Studio tells me exactly where the exception was thrown.</p>
<p>After starting and stopping applications monitored by the metrics collector a few times, an exception was thrown in the code responsible for mapping the application id and the component in charge of storing the metrics of this process. When looking at the code, it seems that there was a “timing” conflict between the code in charge of detecting new and dead processes (in a timer) and the code receiving the events from ETW (in the dedicated thread described earlier). A CLR event was received after the corresponding process was detected as being dead. The net effect of the uncaught exception was fast: the TraceEvent session stopped its execution and the <code>Process</code> method returned. Nothing special visible outside of a debugger with the right exception settings. This is a great scenario to understand why swallowing all exceptions is not a good pattern…</p>
<h2 id="still-notworking">Still not working</h2>
<p>The next step is to fix the code to handle the dead process case, build it and test it. Unfortunately, the new metrics collector, from time to time, does not seem to receive any CLR event. Even worse, this time the ETW session is still here as shown by <strong>logman -ets</strong>. Going back inside the Visual Studio debugger, everything is working fine: the TraceEvent session is created, its <code>Process</code> method called and blocked in a dedicated thread and… the events are received! It means that I’m not able to reproduce the problem while running under debugger control. Maybe the problem could come from the code responsible to filter out events sent by unmonitored applications.</p>
<p>So, I’m adding a Breakpoint in the method responsible for checking monitored process to ensure that there is no bug there (events could be received but skipped due to invalid process ID mapping for example):</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="kt">bool</span> <span class="n">IsMonitoredEvent</span><span class="p">(</span><span class="n">TraceEvent</span> <span class="n">traceEvent</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">isMonitored</span> <span class="p">=</span> <span class="n">traceEvent</span><span class="p">.</span><span class="n">ProcessID</span> <span class="p">==</span> <span class="n">_monitoredPid</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">isMonitored</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">NotifyProcessedEvent</span><span class="p">(</span><span class="n">traceEvent</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">isMonitored</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The breakpoint is hit and I’m able to validate that there is no problem in the mapping code</p>
<p>I can even check that the events I’m expecting are all received by asking Visual Studio to trace the event name with a tracepoint when <code>isMonitored</code> is true:</p>
<p><img loading="lazy" src="/posts/2019-02-22_debugging-friday-hunting-down/1_EQkz2rHq0AV_LxRdNcdWNw.png"></p>
<p>And I get all expected events in the Output Window. If some events were missing, it could have explained that metrics based on events series (such as contention duration) were not computed.</p>
<p>I’m now running the application outside of the debugger… and no event. Just to confirm that I’m not crazy, I decide to add traces in the source code:</p>
<p><img loading="lazy" src="/posts/2019-02-22_debugging-friday-hunting-down/1_oTwaR081Da5AVDUM-KEuCQ.png"></p>
<p>But how to get the output without an attached debugger? The trick is to start <a href="https://docs.microsoft.com/en-us/sysinternals/downloads/debugview?WT.mc_id=DT-MVP-5003325">SysInternals Debug View</a> and wait for the event names to appear: nothing. Even by moving the <code>Debug.WriteLine</code> call outside of the <code>if</code> block, no event is ever received, even from unmonitored processes.</p>
<h2 id="navigating-memory-by-stackframe">Navigating memory by stack frame</h2>
<p>Let’s summarize the investigation status:</p>
<ul>
<li>The metrics collector is working only when under the control of a debugger.</li>
<li>If the debugger is attached after it is started, the events are not received.</li>
</ul>
<p>I don’t know why but this kind of weird behaviors is always happening at Criteo on a Friday. So let’s start a joined debugging session with <a href="https://twitter.com/KooKiz">Kevin</a>, <a href="https://twitter.com/jpbempel">Jean-Philippe</a> and <a href="https://twitter.com/GregoryLeocadie">Gregory</a>!</p>
<p>To better understand what is the state of the metrics collector when the events are not received, the application is launched and the debugger is then attached to it. I open the <em>Parallel Stacks</em> panel and double-click the stack frame with a valuable context:</p>
<p><img loading="lazy" src="/posts/2019-02-22_debugging-friday-hunting-down/1_AvVFp0WGFB-NC2mKMyhlEQ.png"></p>
<p>In our case, it would be interesting to get a view on the state of the <code>ETWTraceEventSource</code> object used by TraceEvent to process the events. Even if you don’t have the source code, it is still possible for the debugger to get a view of the object used as implicit “this” pointer by the <code>ProcessOneFile</code> method. Summon the <em>Quick Watch</em> dialog (Shift-F9 with my old VC 6 keyboard shortcut) and type “this”</p>
<p><img loading="lazy" src="/posts/2019-02-22_debugging-friday-hunting-down/1_6NZx1wOEcPrl-Pwrgu5hAw.png"></p>
<p>Based on own understanding of how the <code>ETWTraceEventSource</code> is working, we know that registered event handlers are associated to entries in its <code>templates</code> field.</p>
<p><img loading="lazy" src="/posts/2019-02-22_debugging-friday-hunting-down/1_lqQjFitiYmDkPF5YZ3h41w.png"></p>
<p>Instead of the expected GC, thread pool, exception and contention events, only kernel related events are defined:</p>
<p><img loading="lazy" src="/posts/2019-02-22_debugging-friday-hunting-down/1_5XGoThkcMl2Y9g-tDAZ9kQ.png"></p>
<p>But breakpoints have been set and hit on the code that registers our own event handlers! Well… it was the case when we debugged the application from start. What if… the event source we are looking at now is not the one our code has registered its handlers to?</p>
<p>Without the address of the object available like in C++, it is complicated to easily check if two references actually point to the same object in memory. However, it is still possible to associate a numeric ID to an object with <em>Make Object ID</em>:</p>
<p><img loading="lazy" src="/posts/2019-02-22_debugging-friday-hunting-down/1_tnBH9jNGHP5l_edMokr0WA.png"></p>
<p>And its ID <strong>{$1}</strong> is now visible after the type name:</p>
<p><img loading="lazy" src="/posts/2019-02-22_debugging-friday-hunting-down/1_kt4s3zsCQFeNwu683WJNeQ.png"></p>
<p>To compare with the <code>ETWTraceEventSource</code> object manipulated by our own source code, double-click the right frame in <em>Parallel Stack</em>:</p>
<p><img loading="lazy" src="/posts/2019-02-22_debugging-friday-hunting-down/1_7XXfUT4DRHLUUPZVv2bBjA.png"></p>
<p>In the <code>ListenToEtw</code> method, <code>this</code> refers to our <code>SessionManager</code> in which the <code>ClrTraceEventParser</code> property references the <code>ETWTraceEventSource</code> object we believe we initialized with the right event handlers:</p>
<p><img loading="lazy" src="/posts/2019-02-22_debugging-friday-hunting-down/1_mrPFddjjiwTl9m-VvYJmyQ.png"></p>
<p>And… this object does not have any ID: it should be <strong>{$1}</strong>.</p>
<p>To confirm that we are not looking at the source object <strong>{$1}</strong> that TraceEvent uses to receive events, it is just a question of checking its <code>template</code> field:</p>
<p><img loading="lazy" src="/posts/2019-02-22_debugging-friday-hunting-down/1_3Du9EQ7dQE8giEiqc19Keg.png"></p>
<p>And here are the expected CLR events we are interested in!</p>
<h2 id="race-condition-again">Race condition… again</h2>
<p>So now the question is: how is it possible to have two instances of a TraceEvent internal class?</p>
<p>Here is the sequence of execution in our code:</p>
<ul>
<li>Thread 1 is creating a <code>TraceEventSession</code> object</li>
<li>Thread 1 starts Thread 2</li>
<li>Thread 1 accesses the <code>clrTraceEventParser</code> via <code>_currentSession?.Source.Clr</code></li>
</ul>
<p>and</p>
<ul>
<li>Thread 2 calls <code>etwSession.Source.Process()</code></li>
</ul>
<p>So the <code>Source</code> property getter could be called from two different threads. Unfortunately, <a href="https://github.com/Microsoft/perfview/blob/b19c4099ccf6b0037b12811f25c924a35af4a447/src/TraceEvent/TraceEventSession.cs#L1267">in the getter</a>, the source is lazily created in a non thread-safe way.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="n">ETWTraceEventSource</span> <span class="n">Source</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">   <span class="k">get</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="p">(</span><span class="n">m_source</span> <span class="p">==</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">         <span class="p">...</span> <span class="c1">// long code </span>
</span></span><span class="line"><span class="cl">         <span class="n">m_source</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ETWTraceEventSource</span><span class="p">(</span><span class="n">SessionName</span><span class="p">,</span> <span class="n">TraceEventSourceType</span><span class="p">.</span><span class="n">Session</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="k">return</span> <span class="n">m_source</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">   <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>When both threads enter the getter, <code>m_source</code> could be null and in that case, two <code>ETWTraceEventSource</code> objects are created and returned. One is used by TraceEvent to listen to events and the other by our code to register handlers to events that will never be received.</p>
<p>The fix is simply to force the initialization of the <code>Source</code> object in the first thread.</p>
<p>It is now a good time to go back home… to take a well deserved vacation!</p>
]]></content:encoded></item><item><title>Building your own Java-like GC logs in .NET</title><link>https://chrisnas.github.io/posts/2019-02-12_building-your-own-java/</link><pubDate>Tue, 12 Feb 2019 10:11:39 +0000</pubDate><guid>https://chrisnas.github.io/posts/2019-02-12_building-your-own-java/</guid><description>This post of the series focuses on logging each GC details in a file and how to leverage it during investigations.</description><content:encoded><![CDATA[<hr>
<p>This post of the series focuses on logging each GC details in a file and how to leverage it during investigations.</p>
<p>Part 1: <a href="/posts/2018-06-19_replace-net-performance-counters/">Replace .NET performance counters by CLR event tracing</a>.</p>
<p>Part 2: <a href="/posts/2018-07-26_grab-etw-session-providers/">Grab ETW Session, Providers and Events</a>.</p>
<p>Part 3: <a href="/posts/2018-09-28_monitor-finalizers-contention-threads/">Monitor Finalizers, contention and threads in your application</a>.</p>
<p>Part 4: <a href="/posts/2018-12-15_spying-on-net-garbage/">Spying on .NET Garbage Collector with TraceEvent</a>.</p>
<h2 id="introduction">Introduction</h2>
<p>I’m working in a team where we investigate issues in production: both for Java and .NET applications. This is a good opportunity to learn what are the features provided by Java that are missing in .NET. One of the features heavily discussed with my colleague <a href="https://twitter.com/jpbempel">Jean-Philippe</a> is called the <em>GC Log</em>. It is possible to start an application with parameters that tell the GC to save tons of details about each garbage collection in a file : the GC Log. Based on this file, it is possible to extract the reason of a collection, the times of the different phases including the suspension time. This is a great source of information during investigations… when you know how GC is working or by leveraging <a href="https://gceasy.io/">automatic report generation</a>.</p>
<p>In addition, you can also build your own UI to more easily understand what is going on and get a more visual representation of the situation.</p>
<p>In the short video above you can see the heap evolution during several days. Then, as this is an interactive HTML page you can zoom in an interesting period to have a more detailed view of the evolution between GCs.</p>
<p>Also for the pause time graph, you can follow the behavior of the GC with different kinds of pauses and associated phases. In this example, we have minor GCs happening and then an “initial mark” is triggered, followed by “final remark” and “cleanup” pauses. After an extra minor GC, we have a series of mixed GCs that is the result of what was planned by the GC after the marking phase.</p>
<p>In the .NET world, there is no such thing as a GC Log. However, as shown <a href="/posts/2018-12-15_spying-on-net-garbage/">in the previous post</a>, it is possible to use Perfview to analyze traces corresponding to collected CLR events. The GCStats view shows high level details in the “All GC Events” section. In addition to this HTML rendering, you can get access to the data itself in different formats</p>
<p><img loading="lazy" src="/posts/2019-02-12_building-your-own-java/1_q01Myz2Qqtad8URqchuJ3Q.png"></p>
<p>The more complete one is the Raw Data XML file that you could parse to extract the details you need. This is very close to a .NET GC Log but it is complicated to build an automated process to get it from a production machine.</p>
<p>It would be great if you could tell a .NET application to generate such a GC Log like in Java instead of relying on manual steps with Perfview (and more scripts on Linux). This post will show you how to achieve this goal!</p>
<h2 id="defining-the-goal-basic-gclog-implementation">Defining the goal: basic GcLog implementation</h2>
<p>In Java, you have to set on or off the GC log before the application starts and you can’t change it while it runs. Since I’m working with server applications, I would prefer to enable/disable the generation of a GC log file without having to stop and restart the application.</p>
<p>So I’ve defined the simple <strong>IGcLog</strong> interface:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">interface</span> <span class="nc">IGcLog</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">void</span> <span class="n">Start</span><span class="p">(</span><span class="kt">string</span> <span class="n">filename</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">void</span> <span class="n">Stop</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>In a dedicated <em>administration handler</em> (i.e. http endpoint of the application), the code could just use a class that implements this interface and call <code>Start </code>when the log is enabled and <code>Stop</code> when it is no more needed.</p>
<p>To make the implementation easier, I’ve written the following <code>GcLogBase</code> abstract class:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">abstract</span> <span class="k">class</span> <span class="nc">GcLogBase</span> <span class="p">:</span> <span class="n">IGcLog</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">protected</span> <span class="kt">string</span> <span class="n">Filename</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="n">StreamWriter</span> <span class="n">_fileWriter</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="k">void</span> <span class="n">Start</span><span class="p">(</span><span class="kt">string</span> <span class="n">filename</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="kt">string</span><span class="p">.</span><span class="n">IsNullOrEmpty</span><span class="p">(</span><span class="n">filename</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="k">throw</span> <span class="k">new</span> <span class="n">ArgumentNullException</span><span class="p">(</span><span class="n">nameof</span><span class="p">(</span><span class="n">filename</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">_fileWriter</span> <span class="p">!=</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">throw</span> <span class="k">new</span> <span class="n">InvalidOperationException</span><span class="p">(</span><span class="s">&#34;Start can&#39;t be called twice: Stop must be called first.&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">_fileWriter</span> <span class="p">=</span> <span class="k">new</span> <span class="n">StreamWriter</span><span class="p">(</span><span class="n">filename</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">Filename</span> <span class="p">=</span> <span class="n">filename</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">OnStart</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="k">void</span> <span class="n">Stop</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="kt">string</span><span class="p">.</span><span class="n">IsNullOrEmpty</span><span class="p">(</span><span class="n">Filename</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">OnStop</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="n">Filename</span> <span class="p">=</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">_fileWriter</span><span class="p">.</span><span class="n">Flush</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="n">_fileWriter</span><span class="p">.</span><span class="n">Dispose</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="n">_fileWriter</span> <span class="p">=</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">protected</span> <span class="kt">bool</span> <span class="n">WriteLine</span><span class="p">(</span><span class="kt">string</span> <span class="n">line</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">_fileWriter</span> <span class="p">==</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="kc">false</span><span class="p">;</span>   <span class="c1">// just in case the method is called AFTER Stop</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">_fileWriter</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="n">line</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">protected</span> <span class="kd">abstract</span> <span class="k">void</span> <span class="n">OnStart</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">protected</span> <span class="kd">abstract</span> <span class="k">void</span> <span class="n">OnStop</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Its main goal is to hide the file management by providing the <code>WriteLine</code> method that child classes would call to save the details of a garbage collection into a single line of text. The write operations are flushed when <code>Stop</code>is called. This combination allows asynchronous writes with low performance impact: don’t be scared if you don’t see the file size change because the <code>StreamWriter</code> class is caching write operations.</p>
<p>The next step is to implement <code>OnStart</code> and <code>OnStop</code> in a derived class to enable/disable GC details retrieval.</p>
<h2 id="how-to-get-the-gc-details-the-easyway">How to get the GC details: the easy way?</h2>
<p>As already discussed in the previous posts of the series, the CLR is emitting traces (via ETW on Windows and LTTng on Linux) that can be collected in C#. You have already seen <a href="/posts/2018-12-15_spying-on-net-garbage/">how TraceEvent could help</a> collecting and parsing GC traces from any application like what Perfview is doing. With TraceEvent, the <code>TraceGC</code><a href="https://github.com/Microsoft/perfview/blob/master/src/TraceEvent/Computers/TraceManagedProcess.cs#L1698"> instance </a>received when a garbage collection ends contains tons of information: it’s mapped to the <code>GarbageCollectionArgs</code><a href="https://github.com/chrisnas/ClrEvents/blob/master/src/ClrCounters/GarbageCollectionArgs.cs"> structure</a> that you get while listening to the <code>GarbageCollection</code><a href="https://github.com/chrisnas/ClrEvents/blob/master/src/ClrCounters/ClrEventsManager.cs#L24"> event</a> of my <code>ClrEventsManager</code> helper class. The only information to provide is the ID of the .NET process I’m interested in: that way, is it easy to filter the events for this process only.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="n">EtwGcLog</span> <span class="n">gcLog</span> <span class="p">=</span> <span class="n">EtwGcLog</span><span class="p">.</span><span class="n">GetProcessGcLog</span><span class="p">(</span><span class="n">pid</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">filename</span> <span class="p">=</span> <span class="n">GetUniqueFilename</span><span class="p">(</span><span class="n">pid</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">gcLog</span><span class="p">.</span><span class="n">Start</span><span class="p">(</span><span class="n">filename</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// in a simple Console application, wait for the user to press ENTER.</span>
</span></span><span class="line"><span class="cl"><span class="c1">// in a more realistic case, keep track of the EtwLog instance and </span>
</span></span><span class="line"><span class="cl"><span class="c1">// call Stop to end the processing of events when needed.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">gcLog</span><span class="p">.</span><span class="n">Stop</span><span class="p">();</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <strong>GetUniqueFilename</strong> method builds a filename based on the process ID and the time of the day:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="kt">string</span> <span class="n">GetUniqueFilename</span><span class="p">(</span><span class="kt">int</span> <span class="n">pid</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">now</span> <span class="p">=</span> <span class="n">DateTime</span><span class="p">.</span><span class="n">Now</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">string</span> <span class="n">filename</span> <span class="p">=</span> <span class="n">Path</span><span class="p">.</span><span class="n">Combine</span><span class="p">(</span><span class="n">Environment</span><span class="p">.</span><span class="n">CurrentDirectory</span><span class="p">,</span> 
</span></span><span class="line"><span class="cl">        <span class="s">$&#34;{pid.ToString()}_{now.Year}{now.Month}{now.Day}_{now.Hour}{now.Minute}{now.Second}.csv&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">filename</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <code>GetProcessGcLog</code> method is a factory-like helper to build an instance bound to the given process ID.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">static</span> <span class="n">EtwGcLog</span> <span class="n">GetProcessGcLog</span><span class="p">(</span><span class="kt">int</span> <span class="n">pid</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">EtwGcLog</span> <span class="n">gcLog</span> <span class="p">=</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">process</span> <span class="p">=</span> <span class="n">Process</span><span class="p">.</span><span class="n">GetProcessById</span><span class="p">(</span><span class="n">pid</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">process</span><span class="p">.</span><span class="n">Dispose</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">gcLog</span> <span class="p">=</span> <span class="k">new</span> <span class="n">EtwGcLog</span><span class="p">(</span><span class="n">pid</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">catch</span> <span class="p">(</span><span class="n">System</span><span class="p">.</span><span class="n">ArgumentException</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// there is no running process with the given pid</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">gcLog</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The implementation of <code>OnStart</code> and <code>OnStop</code> overrides is straightforward based <a href="https://medium.com/criteo-labs/spying-on-net-garbage-collector-with-traceevent-f49dc3117de">on the previous post</a>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">protected</span> <span class="kd">override</span> <span class="k">void</span> <span class="n">OnStart</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">string</span> <span class="n">sessionName</span> <span class="p">=</span> <span class="s">$&#34;GcLogEtwSession_{_pid.ToString()}_{Guid.NewGuid().ToString()}&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;Starting {sessionName}...\r\n&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">_userSession</span> <span class="p">=</span> <span class="k">new</span> <span class="n">TraceEventSession</span><span class="p">(</span><span class="n">sessionName</span><span class="p">,</span> <span class="n">TraceEventSessionOptions</span><span class="p">.</span><span class="n">Create</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">Task</span><span class="p">.</span><span class="n">Run</span><span class="p">(()</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// only want to receive GC event</span>
</span></span><span class="line"><span class="cl">        <span class="n">ClrEventsManager</span> <span class="n">manager</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ClrEventsManager</span><span class="p">(</span><span class="n">_userSession</span><span class="p">,</span> <span class="n">_pid</span><span class="p">,</span> <span class="n">EventFilter</span><span class="p">.</span><span class="n">GC</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">manager</span><span class="p">.</span><span class="n">GarbageCollection</span> <span class="p">+=</span> <span class="n">OnGarbageCollection</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// this is a blocking call until the session is disposed</span>
</span></span><span class="line"><span class="cl">        <span class="n">manager</span><span class="p">.</span><span class="n">ProcessEvents</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&#34;End of CLR event processing&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// add a header to the .csv file</span>
</span></span><span class="line"><span class="cl">    <span class="n">WriteLine</span><span class="p">(</span><span class="n">Header</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">protected</span> <span class="kd">override</span> <span class="k">void</span> <span class="n">OnStop</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// when the session is disposed, the call to ProcessEvents() returns</span>
</span></span><span class="line"><span class="cl">    <span class="n">_userSession</span><span class="p">.</span><span class="n">Dispose</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The created <code>TraceEventSession</code> is passed to the <code>ClrEventManager</code> with the process ID with a filter to receive only <strong>GarbageCollection</strong> event notifications. The <code>OnGarbageCollection</code> handler is super simple:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">OnGarbageCollection</span><span class="p">(</span><span class="kt">object</span> <span class="n">sender</span><span class="p">,</span> <span class="n">GarbageCollectionArgs</span> <span class="n">e</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">_line</span><span class="p">.</span><span class="n">Clear</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">_line</span><span class="p">.</span><span class="n">AppendFormat</span><span class="p">(</span><span class="s">&#34;{0},&#34;</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="n">StartRelativeMSec</span><span class="p">.</span><span class="n">ToString</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">    <span class="n">_line</span><span class="p">.</span><span class="n">AppendFormat</span><span class="p">(</span><span class="s">&#34;{0},&#34;</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="n">Number</span><span class="p">.</span><span class="n">ToString</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">    <span class="n">_line</span><span class="p">.</span><span class="n">AppendFormat</span><span class="p">(</span><span class="s">&#34;{0},&#34;</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="n">Generation</span><span class="p">.</span><span class="n">ToString</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">    <span class="n">_line</span><span class="p">.</span><span class="n">AppendFormat</span><span class="p">(</span><span class="s">&#34;{0},&#34;</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="n">Type</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">_line</span><span class="p">.</span><span class="n">AppendFormat</span><span class="p">(</span><span class="s">&#34;{0},&#34;</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="n">Reason</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">_line</span><span class="p">.</span><span class="n">AppendFormat</span><span class="p">(</span><span class="s">&#34;{0},&#34;</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="n">IsCompacting</span><span class="p">.</span><span class="n">ToString</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">    <span class="n">_line</span><span class="p">.</span><span class="n">AppendFormat</span><span class="p">(</span><span class="s">&#34;{0},&#34;</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="n">SuspensionDuration</span><span class="p">.</span><span class="n">ToString</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">    <span class="n">_line</span><span class="p">.</span><span class="n">AppendFormat</span><span class="p">(</span><span class="s">&#34;{0},&#34;</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="n">PauseDuration</span><span class="p">.</span><span class="n">ToString</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">    <span class="n">_line</span><span class="p">.</span><span class="n">AppendFormat</span><span class="p">(</span><span class="s">&#34;{0},&#34;</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="n">BGCFinalPauseDuration</span><span class="p">.</span><span class="n">ToString</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">    <span class="n">_line</span><span class="p">.</span><span class="n">AppendFormat</span><span class="p">(</span><span class="s">&#34;{0},&#34;</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="n">Gen0Size</span><span class="p">.</span><span class="n">ToString</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">    <span class="n">_line</span><span class="p">.</span><span class="n">AppendFormat</span><span class="p">(</span><span class="s">&#34;{0},&#34;</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="n">Gen1Size</span><span class="p">.</span><span class="n">ToString</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">    <span class="n">_line</span><span class="p">.</span><span class="n">AppendFormat</span><span class="p">(</span><span class="s">&#34;{0},&#34;</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="n">Gen2Size</span><span class="p">.</span><span class="n">ToString</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">    <span class="n">_line</span><span class="p">.</span><span class="n">AppendFormat</span><span class="p">(</span><span class="s">&#34;{0},&#34;</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="n">LOHSize</span><span class="p">.</span><span class="n">ToString</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">    <span class="n">_line</span><span class="p">.</span><span class="n">AppendFormat</span><span class="p">(</span><span class="s">&#34;{0},&#34;</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="n">ObjSizeBefore</span><span class="p">[</span><span class="m">0</span><span class="p">].</span><span class="n">ToString</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">    <span class="n">_line</span><span class="p">.</span><span class="n">AppendFormat</span><span class="p">(</span><span class="s">&#34;{0},&#34;</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="n">ObjSizeBefore</span><span class="p">[</span><span class="m">1</span><span class="p">].</span><span class="n">ToString</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">    <span class="n">_line</span><span class="p">.</span><span class="n">AppendFormat</span><span class="p">(</span><span class="s">&#34;{0},&#34;</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="n">ObjSizeBefore</span><span class="p">[</span><span class="m">2</span><span class="p">].</span><span class="n">ToString</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">    <span class="n">_line</span><span class="p">.</span><span class="n">AppendFormat</span><span class="p">(</span><span class="s">&#34;{0},&#34;</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="n">ObjSizeBefore</span><span class="p">[</span><span class="m">3</span><span class="p">].</span><span class="n">ToString</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">    <span class="n">_line</span><span class="p">.</span><span class="n">AppendFormat</span><span class="p">(</span><span class="s">&#34;{0},&#34;</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="n">ObjSizeAfter</span><span class="p">[</span><span class="m">0</span><span class="p">].</span><span class="n">ToString</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">    <span class="n">_line</span><span class="p">.</span><span class="n">AppendFormat</span><span class="p">(</span><span class="s">&#34;{0},&#34;</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="n">ObjSizeAfter</span><span class="p">[</span><span class="m">1</span><span class="p">].</span><span class="n">ToString</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">    <span class="n">_line</span><span class="p">.</span><span class="n">AppendFormat</span><span class="p">(</span><span class="s">&#34;{0},&#34;</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="n">ObjSizeAfter</span><span class="p">[</span><span class="m">2</span><span class="p">].</span><span class="n">ToString</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">    <span class="n">_line</span><span class="p">.</span><span class="n">AppendFormat</span><span class="p">(</span><span class="s">&#34;{0}&#34;</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span><span class="n">ObjSizeAfter</span><span class="p">[</span><span class="m">3</span><span class="p">].</span><span class="n">ToString</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">WriteLine</span><span class="p">(</span><span class="n">_line</span><span class="p">.</span><span class="n">ToString</span><span class="p">());</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Each garbage collection appears as a textual line with the following columns separated by a comma:</p>
<p><img loading="lazy" src="/posts/2019-02-12_building-your-own-java/1_NmJaV_9pZrRCe-Le44koGA.png"></p>
<p>The last twelve pieces of information require some explanation:</p>
<p>· **xxxBefore **: size of a generation before the collection; without free list</p>
<p>· **xxxAfter **: size of a generation after the collection; without free list</p>
<p>· **xxxSize **: size of a generation (including LOH) after the collection; including free list (i.e. fragmentation)</p>
<p>The computation of these sizes relies on inner fields of the <code>TraceGC</code> argument receives from TraceEvent. The <strong>xxxSize</strong> are grouped in the <strong>GenerationSize0/1/2/3</strong> fields of the <code>HeapStat</code> property. It is a little bit more complicated for the <strong>Before</strong>/<strong>After</strong> sizes. The Garbage Collector keeps track of these numbers in the <code>PerHeapHistories</code> field: an array of <code>GCPerHeapHistory</code> elements; one per heap (i.e. one per core for server GC). The next level is provided by the <code>GenData</code> field storing an array of <code>GCPerHeapHistoryGenData</code> elements; one per generation with LOH as the last index 3. So, to compute the size of each generation, it is needed to iterate on each heap:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="kt">long</span><span class="p">[]</span> <span class="n">GetGenerationSizes</span><span class="p">(</span><span class="n">TraceGC</span> <span class="n">gc</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">before</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">sizes</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">long</span><span class="p">[</span><span class="m">4</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">gc</span><span class="p">.</span><span class="n">PerHeapHistories</span> <span class="p">==</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">sizes</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">heap</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">heap</span> <span class="p">&lt;</span> <span class="n">gc</span><span class="p">.</span><span class="n">PerHeapHistories</span><span class="p">.</span><span class="n">Count</span><span class="p">;</span> <span class="n">heap</span><span class="p">++)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// LOH = 3</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">gen</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">gen</span> <span class="p">&lt;=</span> <span class="m">3</span><span class="p">;</span> <span class="n">gen</span><span class="p">++)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">sizes</span><span class="p">[</span><span class="n">gen</span><span class="p">]</span> <span class="p">+=</span> <span class="n">before</span> <span class="p">?</span> 
</span></span><span class="line"><span class="cl">                <span class="n">gc</span><span class="p">.</span><span class="n">PerHeapHistories</span><span class="p">[</span><span class="n">heap</span><span class="p">].</span><span class="n">GenData</span><span class="p">[</span><span class="n">gen</span><span class="p">].</span><span class="n">ObjSpaceBefore</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="n">gc</span><span class="p">.</span><span class="n">PerHeapHistories</span><span class="p">[</span><span class="n">heap</span><span class="p">].</span><span class="n">GenData</span><span class="p">[</span><span class="n">gen</span><span class="p">].</span><span class="n">ObjSizeAfter</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">sizes</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The code of the <code>GetGenerationSizes</code> helper method does that sum the value of either <code>ObjSpaceBefore</code> or <code>ObjSizeAfter</code>.</p>
<p>As you have probably noticed from the implementation, it is possible that the <code>PerHeapHistories</code> field is not filled up and all <strong>Before</strong>/<strong>After</strong> values are zero. This happens for a background gen2 collection. Also note that for gen0 and gen1 collection the value for gen2 and LOH is also 0 (make sense that gen2 and LOH do not change during such a collection).</p>
<h2 id="a-little-bit-ofui">A little bit of UI</h2>
<p>Now that a .csv file containing all garbage collections details is available, it is time to provide some UI on top of it such as the following for GC pauses:</p>
<p><img loading="lazy" src="/posts/2019-02-12_building-your-own-java/1_nd462AscBlWil52op5ObsA.png"></p>
<p>Let’s start what you can get for Excel champions:</p>
<ul>
<li><strong>Generation ratio</strong></li>
</ul>
<p><img loading="lazy" src="/posts/2019-02-12_building-your-own-java/1_CE3TCA5erE0a4k73kE9Khw.png"></p>
<ul>
<li><strong>Sizes of generations including Large Object Heap</strong></li>
</ul>
<p><img loading="lazy" src="/posts/2019-02-12_building-your-own-java/1_D1rIu1x65M0B9DfO_kvr6w.png"></p>
<ul>
<li><strong>Top 10 pauses (including suspension time comparison)</strong></li>
</ul>
<p><img loading="lazy" src="/posts/2019-02-12_building-your-own-java/1_-cXtvizxi1XFJAsKmuOaaA.png"></p>
<p>But you can get better interaction thanks to <a href="https://twitter.com/jpbempel">Jean-Philippe</a>. My colleague <a href="https://github.com/jpbempel/gclogs-analyzer">adapted his script for JVM to my .NET GC log .csv format</a>: it generates some nice zoomable HTML UI.</p>
<p>This short video above shows the heap evolution during ~20 minutes. Then, as this is an interactive HTML page you can focus on gen2 and LOH impact on memory consumption.</p>
<p>For the pause time graph on the same period, it is very easy to detect long pauses (even for gen0 collection) and zoom into smaller period to figure out the impact of different collections.</p>
<p>The code available on <a href="https://github.com/chrisnas/ClrEvents">Github</a> has been updated to make the <strong>EtwGcLog</strong> class available to you.</p>
]]></content:encoded></item><item><title>Fixing .NET middle-age crisis with Java ReferenceQueue and Cleaner</title><link>https://chrisnas.github.io/posts/2019-01-16_fixing-net-middle-age/</link><pubDate>Wed, 16 Jan 2019 15:07:01 +0000</pubDate><guid>https://chrisnas.github.io/posts/2019-01-16_fixing-net-middle-age/</guid><description>See how to implement Java patterns in C# to solve the “middle-age crisis“ issue with .NET garbage collector</description><content:encoded><![CDATA[<hr>
<p><img loading="lazy" src="/posts/2019-01-16_fixing-net-middle-age/1_Asj0272CtE0YBUC81pX_FA.png"></p>
<p>My colleague Kevin has just described <a href="https://medium.com/@kevingosse/implementing-java-referencequeue-and-phantomreference-in-c-827d7141b6e4">how to implement Java ReferenceQueue in C#</a> as a follow-up to <a href="http://tooslowexception.com/do-we-need-jvms-phantomreference-in-net/">Konrad Kokosa’s article</a> on this Java class. Among the different discussed features, one is still missing. This post will discuss how to deal with the “middle age crisis” scenario and control finalizer threading issues. I’m sure that my former Microsoft colleague <a href="https://twitter.com/sbovo">Sebastien</a> won’t be surprised by my interest in the subject.</p>
<p>When a class references both <code>IDisposable</code> instances and native resources, the usual C# pattern is to implement both <code>IDisposable</code> for explicit cleanup and a Finalizer to deal with developers who would have forgotten the explicit cleanup. This pattern might have a side effect when these classes are also referencing a large objects graph.</p>
<p>Let’s take a minute to describe how finalizers are managed by the CLR</p>
<p>This animation shows what happens at the end of a collection. The darkened objects are no more referenced and should be collected. B, G and H do not implement finalizers so that could be discarded. It is different for E, I and J because their classes implement a finalizer. First, a <em>Finalization</em> list was holding a “weak” reference to them since they were created. Then, at the end of a collection, these references are moved to the <em>FReacheable</em> queue and the collection ends. Later on, after the collection ends, the finalizer thread wakes up and calls the finalizer of all objects referenced by the <em>FReacheable</em> queue. This is the important part of the issue: it means that even though those objects weren’t referenced anymore, they couldn’t be collected nor their memory be reclaimed because the finalizer thread has not run yet. As they could not be reclaimed, they are promoted to the next generation just like other survivors. So if those objects were in generation 0, they now end up in generation 1, extending their lifetime. It’s even worse if they get promoted from generation 1 to generation 2, as the next gen 2 collection might happen only very far in the future. This artificially increases the memory consumption of the application.</p>
<p>To summarize, in case of business objects that hold a large references tree with also native resources, it would be great to be able to:</p>
<ul>
<li>Allow explicit cleanup resources with the <code>IDisposable</code> pattern</li>
<li>Discard the managed memory when the objects are collected</li>
<li>Automatically cleanup native resources AFTER they are collected</li>
<li>Have control on the thread that is cleaning up native resources</li>
</ul>
<h2 id="mix-a-phantom-with-idisposable">Mix a Phantom with IDisposable</h2>
<p>The requirement #3 seems impossible to fulfill: how to access to field of an object if its memory has been reclaimed? Maybe it is possible to cheat: what if these native resources usually held as <code>IntPtr</code> field would be copied when the object is still alive? That way, the cleanup code could be moved outside of the object itself. This is basically the <code>PhantomReference</code> Java idea implemented in C# by Kevin with his <code>PhantomObjectFinalizer</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">PhantomObjectFinalizer</span> <span class="p">:</span> <span class="n">PhantomReference</span><span class="p">&lt;</span><span class="n">LargeObject</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kt">int</span> <span class="n">_handle</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">PhantomObjectFinalizer</span><span class="p">(</span><span class="n">ReferenceQueue</span><span class="p">&lt;</span><span class="n">LargeObject</span><span class="p">&gt;</span> <span class="n">queue</span><span class="p">,</span> <span class="n">LargeObject</span> <span class="k">value</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">:</span> <span class="k">base</span><span class="p">(</span><span class="n">queue</span><span class="p">,</span> <span class="k">value</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">_handle</span> <span class="p">=</span> <span class="k">value</span><span class="p">.</span><span class="n">Handle</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="k">void</span> <span class="n">FinalizeResources</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&#34;I&#39;m cleaning handle &#34;</span> <span class="p">+</span> <span class="n">_handle</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Let’s make it generic in term of native payload:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">PhantomObjectFinalizer</span><span class="p">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="n">S</span><span class="p">&gt;</span> <span class="p">:</span> <span class="n">PhantomReference</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="k">where</span> <span class="n">T</span> <span class="p">:</span> <span class="k">class</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">S</span> <span class="n">State</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">PhantomObjectFinalizer</span><span class="p">(</span><span class="n">ReferenceQueue</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">queue</span><span class="p">,</span> <span class="n">T</span> <span class="k">value</span><span class="p">,</span> <span class="n">S</span> <span class="n">state</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">:</span> <span class="k">base</span><span class="p">(</span><span class="n">queue</span><span class="p">,</span> <span class="k">value</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">State</span> <span class="p">=</span> <span class="n">state</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Also note that the cleaning method has been removed due to the requirement #1: the <code>LargeObject</code>should be responsible for cleaning the resources because it will also implements <code>IDisposable</code>. The cleaning native part will obviously be shared with the <code>Dispose</code> method.</p>
<p>The <code>LargeObject</code> could be rewritten to use it and the first step is group native resources in a state:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">LargeObject</span> <span class="p">:</span> <span class="n">IDisposable</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="k">class</span> <span class="nc">NativeState</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">bool</span> <span class="n">_disposed</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="n">IntPtr</span> <span class="n">_handle1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="n">IntPtr</span> <span class="n">_handle2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="n">NativeState</span> <span class="n">_state</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Plus some heavy stuff that we don&#39;t want to keep around once the object </span>
</span></span><span class="line"><span class="cl">    <span class="c1">// is no more referenced</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">LargeObject</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">_state</span> <span class="p">=</span> <span class="k">new</span> <span class="n">NativeState</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">_handle1</span> <span class="p">=</span> <span class="p">(</span><span class="n">IntPtr</span><span class="p">)</span> <span class="p">(</span><span class="n">DateTime</span><span class="p">.</span><span class="n">Now</span><span class="p">.</span><span class="n">Ticks</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">_handle2</span> <span class="p">=</span> <span class="p">(</span><span class="n">IntPtr</span><span class="p">)</span> <span class="p">(</span><span class="n">DateTime</span><span class="p">.</span><span class="n">Now</span><span class="p">.</span><span class="n">Ticks</span> <span class="p">+</span> <span class="m">1</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// this is where we imagine using a PhantomObjectFinalizer </span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="k">void</span> <span class="n">Dispose</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">_state</span><span class="p">.</span><span class="n">_disposed</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">Cleanup</span><span class="p">(</span><span class="n">_state</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">CleanupIDisposableFields</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">void</span> <span class="n">CleanupIDisposableFields</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// call Dispose on all IDisposable fields</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">static</span> <span class="k">void</span> <span class="n">Cleanup</span><span class="p">(</span><span class="n">NativeState</span> <span class="n">state</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">state</span><span class="p">.</span><span class="n">_disposed</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">state</span><span class="p">.</span><span class="n">_disposed</span> <span class="p">=</span> <span class="kc">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;cleanup native resource {state._handle1.ToString()}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;cleanup native resource {state._handle2.ToString()}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">throw</span> <span class="k">new</span> <span class="n">InvalidOperationException</span><span class="p">(</span><span class="s">&#34;I messed up the cleaning...&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The native payload is stored in a <code>NativeState</code> object that also contains the <code>_disposed</code> <code>IDisposable</code>status. This is required to be able to know if the object has been disposed explicitly when the static <code>Cleanup</code> method is called. This implementation fulfills the requirement #1 even though the cleanup code is throwing an exception: we will have to see how to control it.</p>
<h2 id="introducing-the-cleaner-a-lajava">Introducing the Cleaner a la Java</h2>
<p>The next step is to focus on requirements #2 and #3: how to ensure that our <code>LargeObject</code> memory gets reclaimed by the garbage collector but still automatically cleanup the native resources? This scenario is handled by the <a href="https://docs.oracle.com/javase/9/docs/api/java/lang/ref/Cleaner.html">Cleaner class in Java</a> mentioned reading <a href="http://tooslowexception.com/do-we-need-jvms-phantomreference-in-net/">Konrad’s article</a> and that I have learnt to know better by discussing at length with <a href="https://twitter.com/jpbempel">Jean-Philippe</a>, our team Java internals expert.</p>
<p>You can register an object, a state and a callback that will be called when the object is no more referenced. It is a kind of secondary finalization mechanism.</p>
<p>Let’s see how I would like to use it in C#:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">LargeObject</span> <span class="p">:</span> <span class="n">IDisposable</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">static</span> <span class="k">readonly</span> <span class="n">Cleaner</span><span class="p">&lt;</span><span class="n">LargeObject</span><span class="p">,</span> <span class="n">NativeState</span><span class="p">&gt;</span> <span class="n">_cleaner</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">static</span> <span class="n">LargeObject</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">_cleaner</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Cleaner</span><span class="p">&lt;</span><span class="n">LargeObject</span><span class="p">,</span> <span class="n">NativeState</span><span class="p">&gt;(</span><span class="n">Cleanup</span><span class="p">,</span> <span class="n">OnError</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="n">NativeState</span> <span class="n">_state</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Plus some heavy stuff that we don&#39;t want to keep around once the object is no more referenced</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">LargeObject</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">_state</span> <span class="p">=</span> <span class="k">new</span> <span class="n">NativeState</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">_handle1</span> <span class="p">=</span> <span class="p">(</span><span class="n">IntPtr</span><span class="p">)</span> <span class="p">(</span><span class="n">DateTime</span><span class="p">.</span><span class="n">Now</span><span class="p">.</span><span class="n">Ticks</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">            <span class="n">_handle2</span> <span class="p">=</span> <span class="p">(</span><span class="n">IntPtr</span><span class="p">)</span> <span class="p">(</span><span class="n">DateTime</span><span class="p">.</span><span class="n">Now</span><span class="p">.</span><span class="n">Ticks</span> <span class="p">+</span> <span class="m">1</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">_cleaner</span><span class="p">.</span><span class="n">Track</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="n">_state</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>There will be a unique <code>Cleaner</code> object for all <code>LargeObject</code> instances. Each one will register itself and its native state in its constructor by calling the <code>Cleaner.Track</code> method.</p>
<p>The <code>Cleaner</code> instance receives two static callbacks:</p>
<ul>
<li><strong>Cleanup</strong>: this method will be called by the cleaner after a tracked <code>LargeObject</code> instance has been collected. As you can see, there is no need to change its initial implementation. It was static and receives the <code>NativeState</code> that stores the native state of a <code>LargeObject</code>. Since the <code>NativeState</code> type is a private inner class, the implementation details does not leak from <code>LargeObject</code> like it was the case with Kevin’s <code>PhantomObjectFinalizer</code> implementation.</li>
<li><strong>OnError</strong>: when an exception occurs during the cleanup (like my naïve implementation did by throwing an <code>InvalidOperationException</code>), the method gets called. This is a new feature compared to a .NET finalizer: you are notified if something goes wrong and you are able to log it. However, I would recommend to still exit the application like the default CLR behavior when a finalizer throws an exception.</li>
</ul>
<p>The <code>LargeObject</code> code is therefore responsible for cleaning both <code>IDisposable</code>and native resources: no need for its users to know the gory details.</p>
<p>The high-level API of the <code>Cleaner</code> class has been defined; it is now time to see how to implement it. If you have read <a href="https://medium.com/@kevingosse/implementing-java-referencequeue-and-phantomreference-in-c-827d7141b6e4">Kevin’s post</a>, the first step should be obvious: a <code>ReferenceQueue</code> will keep track of the <code>PhantomObjectFinalizer</code> bound to each “business object” like <code>LargeObject</code>. When the latter is collected, the phantom finalizer gets called to enqueue itself to the <code>ReferenceQueue</code>.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="cs">///   T is the type to finalize</span>
</span></span><span class="line"><span class="cl"><span class="cs">///   S contains the native state of T to be cleaned up</span>
</span></span><span class="line"><span class="cl"><span class="cs">/// This separation enforces the pattern where the &#34;native&#34; part stored by a managed type is kept in a State</span>
</span></span><span class="line"><span class="cl"><span class="cs">/// The state could even be used as is in T (kind of a SafeHandle holding several native resources)</span>
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">Cleaner</span><span class="p">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="n">S</span><span class="p">&gt;</span> 
</span></span><span class="line"><span class="cl">    <span class="k">where</span> <span class="n">T</span> <span class="p">:</span> <span class="k">class</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="n">ReferenceQueue</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">_queue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">Cleaner</span><span class="p">(</span><span class="n">Action</span><span class="p">&lt;</span><span class="n">S</span><span class="p">&gt;</span> <span class="n">onCleanup</span><span class="p">,</span> <span class="n">Action</span><span class="p">&lt;</span><span class="n">Exception</span><span class="p">&gt;</span> <span class="n">onError</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">_onCleanup</span> <span class="p">=</span> <span class="n">onCleanup</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">_onError</span> <span class="p">=</span> <span class="n">onError</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">_queue</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ReferenceQueue</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="p">...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="n">Action</span><span class="p">&lt;</span><span class="n">S</span><span class="p">&gt;</span> <span class="n">_onCleanup</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="n">Action</span><span class="p">&lt;</span><span class="n">Exception</span><span class="p">&gt;</span> <span class="n">_onError</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="p">...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="k">void</span> <span class="n">Track</span><span class="p">(</span><span class="n">T</span> <span class="k">value</span><span class="p">,</span> <span class="n">S</span> <span class="n">state</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">phantomReference</span> <span class="p">=</span> <span class="k">new</span> <span class="n">PhantomObjectFinalizer</span><span class="p">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="n">S</span><span class="p">&gt;(</span><span class="n">_queue</span><span class="p">,</span> <span class="k">value</span><span class="p">,</span> <span class="n">state</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="p">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>There is one big missing step: who will call the queue <code>Poll</code> method to get the finalized <code>PhantomObjectFinalizer</code> that contains the native state to cleanup?</p>
<h2 id="stay-in-control-of-the-cleanerjob">Stay in control of the cleaner job</h2>
<p>The simple implementation I’ve chosen is to create a dedicated thread that will poll the queue every period you want and call the cleanup callback. I did not want to add pressure on the <code>ThreadPool</code> that is shared with the application. If an exception is raised, the error callback will be called.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span><span class="lnt">57
</span><span class="lnt">58
</span><span class="lnt">59
</span><span class="lnt">60
</span><span class="lnt">61
</span><span class="lnt">62
</span><span class="lnt">63
</span><span class="lnt">64
</span><span class="lnt">65
</span><span class="lnt">66
</span><span class="lnt">67
</span><span class="lnt">68
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">Cleaner</span><span class="p">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="n">S</span><span class="p">&gt;</span> 
</span></span><span class="line"><span class="cl">    <span class="k">where</span> <span class="n">T</span> <span class="p">:</span> <span class="k">class</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">const</span> <span class="kt">int</span> <span class="n">DefaultCleanupPeriod</span> <span class="p">=</span> <span class="m">1000</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="n">Action</span><span class="p">&lt;</span><span class="n">S</span><span class="p">&gt;</span> <span class="n">_onCleanup</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="n">Action</span><span class="p">&lt;</span><span class="n">Exception</span><span class="p">&gt;</span> <span class="n">_onError</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="n">ReferenceQueue</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">_queue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="n">Thread</span> <span class="n">_cleanerThread</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="n">ManualResetEvent</span> <span class="n">_exitEvent</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="kt">int</span> <span class="n">_cleanupPeriod</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">Cleaner</span><span class="p">(</span><span class="n">Action</span><span class="p">&lt;</span><span class="n">S</span><span class="p">&gt;</span> <span class="n">onCleanup</span><span class="p">,</span> <span class="n">Action</span><span class="p">&lt;</span><span class="n">Exception</span><span class="p">&gt;</span> <span class="n">onError</span><span class="p">,</span> <span class="kt">int</span> <span class="n">cleanupPeriod</span> <span class="p">=</span> <span class="n">DefaultCleanupPeriod</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">_onCleanup</span> <span class="p">=</span> <span class="n">onCleanup</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">_onError</span> <span class="p">=</span> <span class="n">onError</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">_cleanupPeriod</span> <span class="p">=</span> <span class="n">cleanupPeriod</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">_queue</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ReferenceQueue</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">_exitEvent</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ManualResetEvent</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">_cleanerThread</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Thread</span><span class="p">(</span><span class="n">PeriodicCleanup</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">_cleanerThread</span><span class="p">.</span><span class="n">IsBackground</span> <span class="p">=</span> <span class="kc">true</span><span class="p">;</span> <span class="c1">// allow process exit even though the thread is still running</span>
</span></span><span class="line"><span class="cl">        <span class="n">_cleanerThread</span><span class="p">.</span><span class="n">Start</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">void</span> <span class="n">PeriodicCleanup</span><span class="p">(</span><span class="kt">object</span> <span class="n">parameter</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">Cleaner</span><span class="p">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="n">S</span><span class="p">&gt;</span> <span class="n">cleaner</span> <span class="p">=</span> <span class="p">(</span><span class="n">Cleaner</span><span class="p">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="n">S</span><span class="p">&gt;)</span> <span class="n">parameter</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">while</span> <span class="p">(</span><span class="kc">true</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">exit</span> <span class="p">=</span> <span class="n">_exitEvent</span><span class="p">.</span><span class="n">WaitOne</span><span class="p">(</span><span class="n">cleaner</span><span class="p">.</span><span class="n">_cleanupPeriod</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">exit</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="k">try</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">PhantomReference</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">reference</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="k">while</span> <span class="p">((</span><span class="n">reference</span> <span class="p">=</span> <span class="n">_queue</span><span class="p">.</span><span class="n">Poll</span><span class="p">())</span> <span class="p">!=</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="kt">var</span> <span class="n">finalizedReference</span> <span class="p">=</span> <span class="p">(</span><span class="n">PhantomObjectFinalizer</span><span class="p">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="n">S</span><span class="p">&gt;)</span> <span class="n">reference</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                    <span class="c1">// cleanup native fields</span>
</span></span><span class="line"><span class="cl">                    <span class="n">_onCleanup</span><span class="p">(</span><span class="n">finalizedReference</span><span class="p">.</span><span class="n">State</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="k">catch</span> <span class="p">(</span><span class="n">Exception</span> <span class="n">x</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">_onError</span><span class="p">(</span><span class="n">x</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="k">void</span> <span class="n">Track</span><span class="p">(</span><span class="n">T</span> <span class="k">value</span><span class="p">,</span> <span class="n">S</span> <span class="n">state</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">phantomReference</span> <span class="p">=</span> <span class="k">new</span> <span class="n">PhantomObjectFinalizer</span><span class="p">&lt;</span><span class="n">T</span><span class="p">,</span> <span class="n">S</span><span class="p">&gt;(</span><span class="n">_queue</span><span class="p">,</span> <span class="k">value</span><span class="p">,</span> <span class="n">state</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="k">void</span> <span class="n">Untrack</span><span class="p">(</span><span class="n">T</span> <span class="k">value</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">_queue</span><span class="p">.</span><span class="n">Untrack</span><span class="p">(</span><span class="k">value</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="k">void</span> <span class="n">Dispose</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">_exitEvent</span><span class="p">.</span><span class="n">Set</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Since I’ve created the thread as a background thread, it won’t block .NET to exit the process when the last foreground thread returns. However, you are free to follow the <code>IDisposable</code> pattern, and call <code>Dispose</code> to explicitly stop the cleaning thread at the right time of your application lifecycle.</p>
<p>In the <code>IDisposable</code>/finalizer pattern, the <code>GC</code> class provides the <code>SuppressFinalize</code> static method to remove an object when it has been explicitly disposed: that way, the object won’t go to the <em>FReacheable</em> queue nor be promoted into the next generation after it is collected. The Cleaner class provides the <code>Untrack</code> method to achieve the same effect: the object native payload won’t be cleaned up. I just had to update the <code>ReferenceQueue</code> to remove the object from the <code>ConditionalWeakTable</code> and remove the <code>PhantomReference</code> from the <em>FinalizationList</em>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">ReferenceQueue</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="k">where</span> <span class="n">T</span> <span class="p">:</span> <span class="k">class</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">internal</span> <span class="k">void</span> <span class="n">Track</span><span class="p">(</span><span class="n">T</span> <span class="k">value</span><span class="p">,</span> <span class="n">PhantomReference</span><span class="p">&lt;</span><span class="n">T</span><span class="p">&gt;</span> <span class="n">reference</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">_table</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="k">value</span><span class="p">,</span> <span class="n">reference</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">internal</span> <span class="k">void</span> <span class="n">Untrack</span><span class="p">(</span><span class="n">T</span> <span class="k">value</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">_table</span><span class="p">.</span><span class="n">TryGetValue</span><span class="p">(</span><span class="k">value</span><span class="p">,</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">reference</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">_table</span><span class="p">.</span><span class="n">Remove</span><span class="p">(</span><span class="k">value</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="n">GC</span><span class="p">.</span><span class="n">SuppressFinalize</span><span class="p">(</span><span class="n">reference</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The requirement #4 is now fulfilled. You are obviously free to pick another implementation more suitable to your needs than a thread-based periodic cleanup. I would like to mention that if the cleanup callback never returns, the effect is almost the same as in the case of a stuck finalizer: the native resources won’t be cleaned up anymore.</p>
<p>The following code shows how all this “complicated” code does not leak in a C# application:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Program</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">static</span> <span class="k">void</span> <span class="n">Main</span><span class="p">(</span><span class="kt">string</span><span class="p">[]</span> <span class="n">args</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">InnerScope</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">GC</span><span class="p">.</span><span class="n">Collect</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="n">GC</span><span class="p">.</span><span class="n">WaitForPendingFinalizers</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&#34;Done&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">Console</span><span class="p">.</span><span class="n">ReadLine</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// use the following code to explicitly stop the cleaner thread</span>
</span></span><span class="line"><span class="cl">        <span class="c1">//LargeObject.DisposeCleaner();</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// the code is working without this call because the ticking thread</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// is a background thread and won&#39;t stop the CLR to exit the process</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">static</span> <span class="k">void</span> <span class="n">InnerScope</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">largeObject</span> <span class="p">=</span> <span class="k">new</span> <span class="n">LargeObject</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// it is also possible to support explicit disposing</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// var largeObject2 = new LargeObject();</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// largeObject2.Dispose();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">GC</span><span class="p">.</span><span class="n">Collect</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="n">GC</span><span class="p">.</span><span class="n">WaitForPendingFinalizers</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">GC</span><span class="p">.</span><span class="n">KeepAlive</span><span class="p">(</span><span class="n">largeObject</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>And you get the expected output:</p>
<p><img loading="lazy" src="/posts/2019-01-16_fixing-net-middle-age/1_OOmONmrq6gaVEHbJeEnZTw.png"></p>
<p>Maybe <a href="https://twitter.com/konradkokosa">Konrad</a> will integrate a smarter Java <code>Cleaner</code>-like feature within the CLR itself or <a href="https://twitter.com/xoofx">Alexandre</a> in his new .NET ;^)</p>
]]></content:encoded></item><item><title>Spying on .NET Garbage Collector with TraceEvent</title><link>https://chrisnas.github.io/posts/2018-12-15_spying-on-net-garbage/</link><pubDate>Sat, 15 Dec 2018 11:08:01 +0000</pubDate><guid>https://chrisnas.github.io/posts/2018-12-15_spying-on-net-garbage/</guid><description>This post of the series focuses on CLR events related to garbage collection in .NET.</description><content:encoded><![CDATA[<hr>
<p>This post of the series focuses on CLR events related to garbage collection in .NET.</p>
<p>Part 1: <a href="/posts/2018-06-19_replace-net-performance-counters/">Replace .NET performance counters by CLR event tracing</a>.</p>
<p>Part 2: <a href="/posts/2018-07-26_grab-etw-session-providers/">Grab ETW Session, Providers and Events</a>.</p>
<p>Part 3: <a href="/posts/2018-09-28_monitor-finalizers-contention-threads/">CLR Threading events with TraceEvent</a>.</p>
<h2 id="introduction">Introduction</h2>
<p>The allocator and garbage collector components of the CLR may have a real impact on the performances of your application. The Book of the Runtime describes the allocator/collector design goals in the must read <a href="https://github.com/dotnet/coreclr/blob/master/Documentation/botr/garbage-collection.md">Garbage Collection Design page</a> written by Maoni Stephens, lead developer of the GC. In addition, Microsoft provides large <a href="https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/?WT.mc_id=DT-MVP-5003325">garbage collection documentation</a>. And if you want more details about .NET garbage collector, take a look at <a href="https://www.amazon.com/Pro-NET-Memory-Management-Performance/dp/148424026X">Pro .NET Memory Management</a> by <a href="https://twitter.com/konradkokosa">Konrad Kokosa</a>. In this post, I will focus on the events emitted by the CLR and how you could use them to better understand how your application is behaving, related to its memory consumption.</p>
<p>The impact on how your application behaves is mostly related to a couple of topics:</p>
<ul>
<li>*How many times and how long your threads get suspended during a collection
*Desktop applications and games provide fluent User Interfaces where glitches are less and less acceptable. In the opposite side of the spectrum, low latency server applications have short SLAs to answer each request. In both cases, applications cannot afford freezing for too long while the high priority GC threads are cleaning up the .NET heaps for background GCs or blocking non concurrent GCs.</li>
<li>*How much memory is dedicated to your process
*With the rise of containers and their quotas, your application needs to trim down its memory consumption. For example, with server GC enabled, the amount of memory used by your application could grows big (depending on the number of cores) before a gen 0 collection kicks in (read <a href="https://github.com/aspnet/AspNetCore/issues/3409">this discussion</a> about real world cases including StackOverflow web site and what are the possible solutions)
The memory pressure on the system is also taken into account by the GC and could lead to more collections being triggered (read Maoni Stephen blog post about <a href="https://devblogs.microsoft.com/dotnet/running-with-server-gc-in-a-small-container-scenario-part-0/?WT.mc_id=DT-MVP-5003325">how Windows jobs are taken into account by the GC and how to leverage them if needed</a>). It becomes more and more important to detect leaks and memory consumption spikes.</li>
</ul>
<p>In the previous post, you saw how to get the type name of instances being finalized. The CLR provides many more events related to memory management. They definitively help understand the interactions between this crucial part of .NET and your own code. In this article, you will see how to replace <a href="/posts/2018-06-19_replace-net-performance-counters//">the not always consistent performance counters</a> such as generation sizes or collection counts. More importantly, you will get very useful metrics information like the type of GC (foreground or background) and your application threads suspension time.</p>
<h2 id="sequences-of-events-during-garbage-collection-phases">Sequences of events during Garbage Collection phases</h2>
<p>Ephemeral collections (of generation 0 and 1) are called “stop-the-world”: your application threads will be frozen during the whole collection. For generation 2 background collections, it is a little bit more complicated. As shown in the following figure (with <a href="https://twitter.com/konradkokosa">Konrad Kokosa</a> courtesy from <a href="https://www.amazon.com/Pro-NET-Memory-Management-Performance/dp/148424026X">his book</a>)</p>
<p><img loading="lazy" src="/posts/2018-12-15_spying-on-net-garbage/1_INTuAJqcsWDbp8XM1ZtbMA.png"></p>
<p>The applications threads will be frozen during different phases:</p>
<ul>
<li>Initial internal step at the beginning of the collection,</li>
<li>At the end of the marking phase to reconcile the changes (allocations, references updates) done while background collection threads are running (also if compaction is needed). Look for documentation about card table usage to get more details,</li>
<li>If a compaction occurs.</li>
</ul>
<p>Please read <a href="https://devblogs.microsoft.com/premier-developer/understanding-different-gc-modes-with-concurrency-visualizer/?WT.mc_id=DT-MVP-5003325">Understanding different GC modes with Concurrency Visualizer</a> to go deeper and blog posts from <a href="http://mattwarren.org/2017/01/13/Analysing-Pause-times-in-the-.NET-GC/">Matt</a> <a href="http://mattwarren.org/2016/06/20/Visualising-the-dotNET-Garbage-Collector/">Warren</a> and <a href="https://devblogs.microsoft.com/dotnet/gc-etw-events-2/?WT.mc_id=DT-MVP-5003325">Maoni Stephens</a> about GC pauses.</p>
<h2 id="what-are-the-available-garbage-collections-metrics">What are the available garbage collections metrics?</h2>
<p>The <a href="https://github.com/Microsoft/perfview/blob/master/documentation/Downloading.md">Perfview tool</a> could help you analyze how many garbage collections occurred and for which reason. Select Run in the Collect menu and click the Run Command button.</p>
<p><img loading="lazy" src="/posts/2018-12-15_spying-on-net-garbage/1_3UbnAgXjKzwMZqbrHQw_0Q.png"></p>
<p>You could also trigger a collection after the application is started with Collect | Collect. When you want to stop collecting information, click the Stop Collection. When the .etl file gets generated, go to the GCStats node</p>
<p><img loading="lazy" src="/posts/2018-12-15_spying-on-net-garbage/1_fJ0jJD4GejWKNO_4kaXuuA.png"></p>
<p>Look for your application to get statistics related to garbage collections. The first <em><strong>GC Rollup By Generation</strong></em> table gives you high level metrics such as the number of collections per generation and the mean pause time:</p>
<p><img loading="lazy" src="/posts/2018-12-15_spying-on-net-garbage/1_ebel0SDOuULdvUjs63MmQw.png"></p>
<p>The next two sections list the collections with a pause time longer than 200ms before the section that lists all generation 2 collections:</p>
<p><img loading="lazy" src="/posts/2018-12-15_spying-on-net-garbage/1_8AoASNYsf0FYNAAJTqWJpQ.png"></p>
<p>The <em><strong>Suspend Msec</strong></em> columns gives you the time it took to suspend your application threads while <em><strong>Pause MSec</strong></em> counts the time during which your threads were actually suspended.</p>
<p>In addition to this, memory details such as the size of all generations after each collection are available:</p>
<p><img loading="lazy" src="/posts/2018-12-15_spying-on-net-garbage/1_mqZSpPC1ZEEsDkkpCxfrEw.png"></p>
<p>However, my goal is to get these details to feed monitoring dashboards <strong>as the application runs</strong>. I can’t use Perfview but I can still rely on the same CLR events.</p>
<h2 id="a-solution-for-runtimeplease">A solution for runtime please!</h2>
<p>Since version 2 of TraceEvent, there is an easy way to get already computed metrics about GC as <a href="https://devblogs.microsoft.com/dotnet/glad-part-2/?WT.mc_id=DT-MVP-5003325">described by Maoni Stephens</a>. It relies on the same code as Perfview for its <em>GCStats</em> window.</p>
<p>You only need to subscribe to two events; one when a GC starts and one when it ends:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">source</span> <span class="p">=</span> <span class="n">userSession</span><span class="p">.</span><span class="n">Source</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">source</span><span class="p">.</span><span class="n">NeedLoadedDotNetRuntimes</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="n">source</span><span class="p">.</span><span class="n">AddCallbackOnProcessStart</span><span class="p">((</span><span class="n">TraceProcess</span> <span class="n">proc</span><span class="p">)</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">proc</span><span class="p">.</span><span class="n">AddCallbackOnDotNetRuntimeLoad</span><span class="p">((</span><span class="n">TraceLoadedDotNetRuntime</span> <span class="n">runtime</span><span class="p">)</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">runtime</span><span class="p">.</span><span class="n">GCStart</span> <span class="p">+=</span> <span class="p">(</span><span class="n">TraceProcess</span> <span class="n">p</span><span class="p">,</span> <span class="n">TraceGC</span> <span class="n">gc</span><span class="p">)</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// a GC is starting</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">        <span class="n">runtime</span><span class="p">.</span><span class="n">GCEnd</span> <span class="p">+=</span> <span class="p">(</span><span class="n">TraceProcess</span> <span class="n">p</span><span class="p">,</span> <span class="n">TraceGC</span> <span class="n">gc</span><span class="p">)</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// a GC ends</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="p">});</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <strong>TraceGC</strong> class provides too many details beyond the scope of this post but here are the main fields that should be used in <strong>GCEnd</strong> event handler to monitor your applications:</p>
<p><img loading="lazy" src="/posts/2018-12-15_spying-on-net-garbage/1_DlGRChntSn43hNI6RPkjVw.png"></p>
<p>Note that the <strong>IsNotCompacting</strong> method <a href="https://github.com/Microsoft/perfview/issues/811">currently returns invalid value</a>.</p>
<h2 id="final-words">Final words</h2>
<p>I would like to mention one last event related to memory management. The <strong>GCAllocationTick</strong> CLR event (mapped by the <strong>ClrTraceEventParser.GCAllocationTick</strong> event) is emitted after ~100 KB has been allocated by your application. As you can infer from the <a href="https://docs.microsoft.com/en-us/dotnet/framework/performance/garbage-collection-etw-events#gcallocationtick_v2-event?WT.mc_id=DT-MVP-5003325">Microsoft documentation</a>, the field of the <strong>GCAllocationTickTraceData</strong> argument received by your handler provides the following properties:</p>
<p><img loading="lazy" src="/posts/2018-12-15_spying-on-net-garbage/1_MeqPtEj3AD86o5-1B5gb5A.png"></p>
<p>As you can see, listening to this <strong>GCAllocationTick</strong> event gives you a sampling of the allocations made in your application. This is not as precise as what a .NET profiler (relying on expensive <a href="https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/icorprofilercallback-objectallocated-method?WT.mc_id=DT-MVP-5003325">ObjectAllocated</a> and <a href="http://?WT.mc_id=DT-MVP-5003325">ObjectAllocatedByClass</a> <strong>ICorProfilerCallback</strong> hooks) would provide but it is much less intrusive. However, I would not recommend to systematically listen to this event in production, especially if your application is allocating GBs of memory per minute. Unlike what the documentation states, you need to set the verbosity to <strong>TraceEventLevel.Verbose</strong> (and not <strong>Informational</strong>) when you enable the CLR provider and this could impact your application performances due to the high number of emitted CLR events.</p>
<p>This event could be very helpful in case of unusual LOH allocations because you would get the type of the objects in the LOH almost each time (the 85.000 bytes threshold is close to the 100 KB trigger limit) or simply to have an hint on the most allocated types over time. Note that you won’t get the callstack leading to the allocations triggering the event. Instead, for memory leak or memory usage analysis, I would definitively recommend you to use Perfview. Vance Morrison has published a series of videos that detail <a href="https://channel9.msdn.com/Series/PerfView-Tutorial/PerfView-Tutorial-9-NET-Memory-Investigation-Basics-of-GC-Heap-Snapshots?WT.mc_id=DT-MVP-5003325">.NET memory investigations</a>, <a href="https://channel9.msdn.com/Series/PerfView-Tutorial/Tutorial-10-Investigating-NET-Heap-Memory-Leaks-Part1-Collecting-the-data?WT.mc_id=DT-MVP-5003325">collecting the data</a> and <a href="https://channel9.msdn.com/Series/PerfView-Tutorial/Tutorial-11-Investigating-NET-Heap-Memory-Leaks-Part2-Analyzing-the-data?WT.mc_id=DT-MVP-5003325">analyzing the data</a> with Perfview. You will also find a lot of detailed memory-related investigations guidelines in <a href="https://www.amazon.com/Pro-NET-Memory-Management-Performance/dp/148424026X">Konrad Kokosa’s book</a>.</p>
<p>You now have a complete view of the CLR events interesting to understand the different phases of a garbage collection and a few interactions (suspension) with the Execution Engine. Everything is in hands to replace the performance counters by CLR events: the metrics are more accurate and you get access to more information such as suspension time or contention time. The code presented during all episodes is available <a href="https://github.com/chrisnas/ClrEvents">on Github</a> with an easy to reuse <strong>ClrEventManager</strong> class that you could plug into your own applications or monitoring service!</p>
]]></content:encoded></item><item><title>In-process CLR event listeners with .NET Core 2.2</title><link>https://chrisnas.github.io/posts/2018-12-06_in-process-clr-event/</link><pubDate>Thu, 06 Dec 2018 13:52:09 +0000</pubDate><guid>https://chrisnas.github.io/posts/2018-12-06_in-process-clr-event/</guid><description>Write your own EventListener class</description><content:encoded><![CDATA[<hr>
<p><img loading="lazy" src="/posts/2018-12-06_in-process-clr-event/1_zc1BKfAHkpvrZlHPbUvuYA.png"></p>
<p>As the <a href="https://devblogs.microsoft.com/dotnet/announcing-net-core-2-2/?WT.mc_id=DT-MVP-5003325">.NET Core 2.2 blog post</a> introduced, it is now possible for a .NET Core application to listen to the events generated by the CLR that power it up. If you remember the <a href="/posts/2018-07-26_grab-etw-session-providers/">Grab ETW Session, Providers and Events</a> post, the CLR is emitting a lot of valuable events through ETW on Windows and LTTng on Linux. Thanks to <a href="https://www.nuget.org/packages/Microsoft.Diagnostics.Tracing.TraceEvent/">TraceEvent nuget package</a>, it is not that difficult to fetch these events at runtime on Windows, either in-process or out of process. However, it is much more complicated to achieve the same goal on Linux… With .NET Core 2.2, it is now super easy to listen to the events emitted by the CLR while your application is running: you simply need to implement a class that derives from <a href="https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.tracing.eventlistener?WT.mc_id=DT-MVP-5003325?view=netcore-2.2">System.Diagnostics.Tracing.EventListener</a> and create an instance of it. Nothing more.</p>
<p>This class exists since .NET Framework 4.5 and .NET Core 1.0 but it could only be used to listen events pushed by managed code. Since .NET Core 2.2, it can also be used to listen to native events pushed by the CLR. The usage is simple, even a little bit magical.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">sealed</span> <span class="k">class</span> <span class="nc">GcFinalizersEventListener</span> <span class="p">:</span> <span class="n">EventListener</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// from https://docs.microsoft.com/en-us/dotnet/framework/performance/garbage-collection-etw-events</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">const</span> <span class="kt">int</span> <span class="n">GC_KEYWORD</span> <span class="p">=</span>                 <span class="m">0x0000001</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">const</span> <span class="kt">int</span> <span class="n">TYPE_KEYWORD</span> <span class="p">=</span>               <span class="m">0x0080000</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">const</span> <span class="kt">int</span> <span class="n">GCHEAPANDTYPENAMES_KEYWORD</span> <span class="p">=</span> <span class="m">0x1000000</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">protected</span> <span class="kd">override</span> <span class="k">void</span> <span class="n">OnEventSourceCreated</span><span class="p">(</span><span class="n">EventSource</span> <span class="n">eventSource</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;{eventSource.Guid} | {eventSource.Name}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// look for .NET Garbage Collection events</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">eventSource</span><span class="p">.</span><span class="n">Name</span><span class="p">.</span><span class="n">Equals</span><span class="p">(</span><span class="s">&#34;Microsoft-Windows-DotNETRuntime&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">EnableEvents</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">eventSource</span><span class="p">,</span> 
</span></span><span class="line"><span class="cl">                <span class="n">EventLevel</span><span class="p">.</span><span class="n">Verbose</span><span class="p">,</span> 
</span></span><span class="line"><span class="cl">                <span class="p">(</span><span class="n">EventKeywords</span><span class="p">)</span> <span class="p">(</span><span class="n">GC_KEYWORD</span> <span class="p">|</span> <span class="n">GCHEAPANDTYPENAMES_KEYWORD</span> <span class="p">|</span> <span class="n">TYPE_KEYWORD</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// from https://blogs.msdn.microsoft.com/dotnet/2018/12/04/announcing-net-core-2-2/</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Called whenever an event is written.</span>
</span></span><span class="line"><span class="cl">    <span class="kd">protected</span> <span class="kd">override</span> <span class="k">void</span> <span class="n">OnEventWritten</span><span class="p">(</span><span class="n">EventWrittenEventArgs</span> <span class="n">eventData</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="p">...</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Program</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">static</span> <span class="k">void</span> <span class="n">Main</span><span class="p">(</span><span class="kt">string</span><span class="p">[]</span> <span class="n">args</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">GcFinalizersEventListener</span> <span class="n">listener</span> <span class="p">=</span> <span class="k">new</span> <span class="n">GcFinalizersEventListener</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&#34;\nPress ENTER to trigger a few finalizers...&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">Console</span><span class="p">.</span><span class="n">ReadLine</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="m">4</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">Thread</span> <span class="n">t</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Thread</span><span class="p">(()=&gt;</span> <span class="p">{});</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="n">GC</span><span class="p">.</span><span class="n">Collect</span><span class="p">(</span><span class="m">2</span><span class="p">,</span> <span class="n">GCCollectionMode</span><span class="p">.</span><span class="n">Forced</span><span class="p">,</span> <span class="kc">true</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&#34;\nPress ENTER to exit...&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">Console</span><span class="p">.</span><span class="n">ReadLine</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>First, you implement a class that derives from <code>EventListener</code> and override the following two methods:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl">   <span class="k">void</span> <span class="n">OnEventSourceCreated</span><span class="p">(</span><span class="n">EventSource</span> <span class="n">eventSource</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">   <span class="k">void</span> <span class="n">OnEventWritten</span><span class="p">(</span><span class="n">EventWrittenEventArgs</span> <span class="n">eventData</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>As soon as you new up an instance of your class, the <code>OnEventSourceCreated</code>override is called for each <em>event source</em> defined in the application. An event source, as its name implies, produces events. You can define your own in managed code if you wish. For the sake of this post, I will focus on listening to the <em><strong>Microsoft-Windows-DotNETRuntime</strong></em> event source. The <code>EventSource</code>instance passed to the <code>OnEventSourceCreated</code>method provides two interesting properties to let us identify the available sources. The following code :</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">protected</span> <span class="kd">override</span> <span class="k">void</span> <span class="n">OnEventSourceCreated</span><span class="p">(</span><span class="n">EventSource</span> <span class="n">eventSource</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;{eventSource.Guid} | {eventSource.Name}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>generates the following output :</p>
<pre tabindex="0"><code>5e5bb766-bbfc-5662-0548-1d44fad9bb56 | Microsoft-Windows-DotNETRuntime
 2e5dba47-a3d2-4d16-8ee0-6671ffdcd7b5 | System.Threading.Tasks.TplEventSource
 8e9f5090-2d75-4d03-8a81-e5afbf85daf1 | System.Diagnostics.Eventing.FrameworkEventSource
</code></pre><p>You can imagine that the first one is the source we are interested in listening to its events!</p>
<p>By default, there is no connection between the sources and your listeners: you need to enable the source by calling the <code>EnableEvents</code>method in your <code>OnEventSourceCreated</code>override. This <code>EventListener</code>method takes the following arguments:</p>
<ul>
<li><code>EventSource eventSource</code>: the event source you want to listen to</li>
<li><code>EventLevel level</code>: minimum verbosity level for the received events</li>
<li><code>EventKeywords matchAnyKeyword</code>: a keyword to filter on specific events</li>
</ul>
<p>The Microsoft Docs provides <a href="https://docs.microsoft.com/en-us/dotnet/framework/performance/clr-etw-keywords-and-levels?WT.mc_id=DT-MVP-5003325">the level and keywords</a> for <a href="https://docs.microsoft.com/en-us/dotnet/framework/performance/clr-etw-events?WT.mc_id=DT-MVP-5003325">each events</a> documented in the CLR. For the complete list, you take a look at <a href="https://github.com/dotnet/coreclr/blob/master/src/vm/ClrEtwAll.man">ClrETWAll.man in CoreClr source code</a> or in <code>ClrTraceEventParser</code> class of TraceEvent. In the sample code at the beginning of this post, I selected a group of keywords <code>GC_KEYWORD | GCHEAPANDTYPENAMES_KEYWORD | TYPE_KEYWORD</code> to receive only events related to the garbage collector and type information (<a href="/posts/2018-09-28_monitor-finalizers-contention-threads/">read this previous post for more details</a>).</p>
<p>Once the source has been paired to the listener, each time an event is emitted by the source with the right level and for the given keywords, the <code>OnEventWritten</code>override will get called. The <code>EventWrittenEventArgs</code>instance received as a parameter describes each event.</p>
<p><img loading="lazy" src="/posts/2018-12-06_in-process-clr-event/1_JPlKjHnKXY02YiWKrrWLMw.png"></p>
<p>The <code>Payload</code> contains the value of the different properties stored in a <code>ReadOnlyCollection</code> and the corresponding property names are provided via the <code>ReadOnlyCollection</code> <code>PayLoadNames</code>. The following code shows how to extract all properties values:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="c1">// from https://blogs.msdn.microsoft.com/dotnet/2018/12/04/announcing-net-core-2-2/</span>
</span></span><span class="line"><span class="cl"><span class="c1">// Called whenever an event is written.</span>
</span></span><span class="line"><span class="cl"><span class="kd">protected</span> <span class="kd">override</span> <span class="k">void</span> <span class="n">OnEventWritten</span><span class="p">(</span><span class="n">EventWrittenEventArgs</span> <span class="n">eventData</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Write the contents of the event to the console.</span>
</span></span><span class="line"><span class="cl">    <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;ThreadID = {eventData.OSThreadId} ID = {eventData.EventId} Name = {eventData.EventName}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">eventData</span><span class="p">.</span><span class="n">Payload</span><span class="p">.</span><span class="n">Count</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">string</span> <span class="n">payloadString</span> <span class="p">=</span> <span class="n">eventData</span><span class="p">.</span><span class="n">Payload</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="p">!=</span> <span class="kc">null</span> <span class="p">?</span> <span class="n">eventData</span><span class="p">.</span><span class="n">Payload</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">ToString</span><span class="p">()</span> <span class="p">:</span> <span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;    Name = \&#34;</span><span class="p">{</span><span class="n">eventData</span><span class="p">.</span><span class="n">PayloadNames</span><span class="p">[</span><span class="n">i</span><span class="p">]}</span><span class="err">\</span><span class="s">&#34; Value = \&#34;{payloadString}\&#34;&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&#34;\n&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Here is the kind of output you get for common garbage collector and finalizer events:</p>
<pre tabindex="0"><code>ThreadID = 17456 ID = 200 Name = IncreaseMemoryPressure
Name = &#34;BytesAllocated&#34; Value = &#34;1672&#34;
Name = &#34;ClrInstanceID&#34; Value = &#34;8&#34;
</code></pre><pre tabindex="0"><code>ThreadID = 17456 ID = 9 Name = GCSuspendEEBegin_V1
Name = &#34;Reason&#34; Value = &#34;1&#34;
Name = &#34;Count&#34; Value = &#34;0&#34;
Name = &#34;ClrInstanceID&#34; Value = &#34;8&#34;
</code></pre><pre tabindex="0"><code>ThreadID = 17456 ID = 8 Name = GCSuspendEEEnd_V1
Name = &#34;ClrInstanceID&#34; Value = &#34;8&#34;
</code></pre><pre tabindex="0"><code>ThreadID = 17456 ID = 35 Name = GCTriggered
Name = &#34;Reason&#34; Value = &#34;10&#34;
Name = &#34;ClrInstanceID&#34; Value = &#34;8&#34;
</code></pre><pre tabindex="0"><code>ThreadID = 17456 ID = 1 Name = GCStart_V2
Name = &#34;Count&#34; Value = &#34;1&#34;
Name = &#34;Depth&#34; Value = &#34;2&#34;
Name = &#34;Reason&#34; Value = &#34;10&#34;
Name = &#34;Type&#34; Value = &#34;0&#34;
Name = &#34;ClrInstanceID&#34; Value = &#34;8&#34;
Name = &#34;ClientSequenceNumber&#34; Value = &#34;0&#34;
</code></pre><pre tabindex="0"><code>ThreadID = 18860 ID = 29 Name = FinalizeObject
Name = &#34;TypeID&#34; Value = &#34;1210056592&#34;
Name = &#34;ObjectID&#34; Value = &#34;1371069040&#34;
Name = &#34;ClrInstanceID&#34; Value = &#34;8&#34;
</code></pre><pre tabindex="0"><code>ThreadID = 18860 ID = 15 Name = BulkType
Name = &#34;Count&#34; Value = &#34;1&#34;
Name = &#34;ClrInstanceID&#34; Value = &#34;8&#34;
</code></pre><p>The fact that there is no strongly typed event argument per event is not as good as what TraceEvent provides. In addition, after a few tests, it seems that the .NET Core 2.2 implementation <a href="https://github.com/dotnet/coreclr/issues/21380">is not complete</a>:</p>
<ul>
<li>GC events are not all received when in Server Mode</li>
<li>Properties are missing for BulkType event necessary to figure out finalizer type names</li>
</ul>
<p>However, with <code>EventListener</code>, Microsoft is giving us a very simple way to get valuable information, in-process, from the CLR while the application is running. A forthcoming blog post will show how to leverage this infrastructure to provide insights on how the garbage collection impacts an application.</p>
<p>Before leaving you building your own event listeners, you should know a couple of last details. Under the hood, the framework is <a href="https://github.com/dotnet/coreclr/blob/78570a239101f69200cfceab5e7527ca8cc312b8/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipeEventDispatcher.cs#L141">creating a dedicated thread</a> for you that will execute the two <code>OnXXX</code>methods of your <code>EventListener</code>-derived class. It means that your code should not block or spend to much time processing the events if you want to keep on receiving events at a regular pace.</p>
<p>This thread will last as long as one of your listeners still exists. When I say “exist”, I mean until you decide to dispose them. This is the way for you to tell the sources that you are no more interested in receiving events. When all your listeners are disposed, then the processing thread will exit.</p>
]]></content:encoded></item><item><title>[C#] Get-process-name challenge on a Friday afternoon</title><link>https://chrisnas.github.io/posts/2018-11-13_get-process-name-challenge/</link><pubDate>Tue, 13 Nov 2018 10:29:22 +0000</pubDate><guid>https://chrisnas.github.io/posts/2018-11-13_get-process-name-challenge/</guid><description>Unexpected CPU consumption</description><content:encoded><![CDATA[<hr>
<p><img loading="lazy" src="/posts/2018-11-13_get-process-name-challenge/1_CDn3N44B8tI1cCzL3G-Qbw.png"></p>
<h2 id="unexpected-cpu-consumption">Unexpected CPU consumption</h2>
<p>At Criteo, CLR metrics are collected by a service that listens to ETW events (<a href="/posts/2018-09-28_monitor-finalizers-contention-threads/">see the related series</a>). This metrics collector is given the process name of applications to monitor. Since applications could crash, be stopped or restarted, the metrics collector must be able to detect such an event. The previous implementation was using ETW kernel events (TraceEvent <code>ProcessStart </code>and <code>ProcessStop </code>events from <code>ETWTraceEventSource.Kernel</code>). However, in rare cases, it seems that a new application start was not detected and therefore the metrics were not collected for it.</p>
<p>An easy fix for this situation is to poll the list of running processes every second and detect which one is new or has left since the last time the list was polled. The implementation is straightforward: just call <code>Process.GetProcesses()</code> and get the process name from the <code>Process MainModule.FileName</code> property. After a few seconds testing this implementation on my laptop the fan started spinning: a quick look at Task Manager shows that the metrics collector is using ~10% CPU time!</p>
<p><img loading="lazy" src="/posts/2018-11-13_get-process-name-challenge/1_D9NeWlaSCi8aDtJms3LujA.png"></p>
<p>I’ve used these P/Invoked PSAPI functions 20 years ago but I don’t remember such an impact: for our monitoring service, we would like to keep the CPU impact below 1%.</p>
<h2 id="measure-measure-andprofile">Measure, measure… and profile</h2>
<p>This was a good opportunity to start profiling the metrics collector with dotTrace on a Friday afternoon!</p>
<p><code>NtProcessManager.GetModuleInfos</code> and <code>NtProcessManager.GetProcessIds</code> are at the methods top list of CPU consumption the worst offenders by far:</p>
<p><img loading="lazy" src="/posts/2018-11-13_get-process-name-challenge/1_bXsVZkRy-W_33QDcKFXWCA.png"></p>
<p>The callstack to reach <code>GetModuleInfos()</code> shows the following:</p>
<p><img loading="lazy" src="/posts/2018-11-13_get-process-name-challenge/1_rDChwSQuaJdUPbaJ7XDanw.png"></p>
<p>The <code>GetProcessPath()</code> method of the metrics collector is asking for the value of <code>Process.MainModule.FileName</code> property that ends up calling <code>GetModuleInfos</code>.</p>
<p>And the callstack for the <code>MainModule </code>getter execution shows the following:</p>
<p><img loading="lazy" src="/posts/2018-11-13_get-process-name-challenge/1_CDn3N44B8tI1cCzL3G-Qbw.png"></p>
<p>This call stack looks weird for two reason:</p>
<ul>
<li>Since the <code>Process </code>object exists, why is it needed to call <code>OpenProcess </code>again to get the main module?</li>
<li>Why would <code>OpenProcess </code>need to call <code>GetProcessIds </code>(i.e. get the list of running processes) since its id is already known?!</li>
</ul>
<p>Just take a look at the decompiled source code to get the answer:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">static</span> <span class="n">SafeProcessHandle</span> <span class="n">OpenProcess</span><span class="p">(</span><span class="kt">int</span> <span class="n">processId</span><span class="p">,</span> <span class="kt">int</span> <span class="n">access</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">throwIfExited</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">SafeProcessHandle</span> <span class="n">safeProcessHandle</span> <span class="p">=</span> <span class="n">NativeMethods</span><span class="p">.</span><span class="n">OpenProcess</span><span class="p">(</span><span class="n">access</span><span class="p">,</span> <span class="kc">false</span><span class="p">,</span> <span class="n">processId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="kt">int</span> <span class="n">lastWin32Error</span> <span class="p">=</span> <span class="n">Marshal</span><span class="p">.</span><span class="n">GetLastWin32Error</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(!</span><span class="n">safeProcessHandle</span><span class="p">.</span><span class="n">IsInvalid</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">safeProcessHandle</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  
</span></span><span class="line"><span class="cl">  <span class="c1">// error handling </span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">processId</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">throw</span> <span class="k">new</span> <span class="n">Win32Exception</span><span class="p">(</span><span class="m">5</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">ProcessManager</span><span class="p">.</span><span class="n">IsProcessRunning</span><span class="p">(</span><span class="n">processId</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="k">throw</span> <span class="k">new</span> <span class="n">Win32Exception</span><span class="p">(</span><span class="n">lastWin32Error</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">...</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>And <code>IsProcessRunning </code>calls <code>GetProcessIds</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">bool</span> <span class="n">IsProcessRunning</span><span class="p">(</span><span class="kt">int</span> <span class="n">processId</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">   <span class="k">return</span> <span class="n">IsProcessRunning</span><span class="p">(</span><span class="n">processId</span><span class="p">,</span> <span class="n">GetProcessIds</span><span class="p">());</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>It does not appear in the callstack most probably because it was inlined by the JIT.</p>
<p>So, the code of <code>ProcessManager.OpenProcess</code> calls the Win32 <code>OpenProcess</code> API to get… the handle of the process corresponding to the given id and desired access rights. From there, it is spending most of its CPU time dealing with error cases (i.e. when a process information cannot be accessed maybe due to access right limitation). We definitively don’t need all that in our case!</p>
<h2 id="what-next">What next?</h2>
<p>At that point of the investigation, my colleague <a href="https://twitter.com/KooKiz">Kevin</a> and I went to different directions. A few decades ago, I spent a lot of time digging into Windows internals using Win32 APIs and I remember that calling <code>PSAPI.GetModuleFilenameEx</code> with a pid and 0 as module handle should return the path name of the process (BTW, this is also what <code>GetModuleInfos </code>ends up calling but more on that later). So it should not be too complicated to P/Invoke this function from PSAPI.dll.</p>
<p>At the beginning of .NET programming, the <a href="https://pinvoke.net/">https://pinvoke.net/</a> web site was very useful to figure out the right syntax for a lot of APIs if you did not want to read the 1579 pages of the <a href="https://www.amazon.com/NET-COM-Complete-Interoperability-Guide-ebook/dp/B003AYZB7U">Complete Interoperability Guide</a>! The description of <code>GetModuleFileNameEx </code>is <a href="https://pinvoke.net/default.aspx/psapi/GetModuleFileNameEx.html">available</a>, and even come with a code sample.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="na">[DllImport(&#34;psapi.dll&#34;, BestFitMapping = false, CharSet = CharSet.Auto, SetLastError = true)]</span>
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="kd">extern</span> <span class="kt">int</span> <span class="n">GetModuleFileNameEx</span><span class="p">(</span><span class="n">SafeProcessHandle</span> <span class="n">processHandle</span><span class="p">,</span> <span class="n">IntPtr</span> <span class="n">moduleHandle</span><span class="p">,</span> <span class="n">StringBuilder</span> <span class="n">baseName</span><span class="p">,</span> <span class="kt">int</span> <span class="n">size</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Note that marshaling strings should always be done with care: the Win32 API is not always consistent when a pointer to a C-like string is supposed to be filled up by a function. An additional parameter is given to state the size of the buffer in which the characters of the string will be copied. In some cases, this parameter counts the number of characters and in some others, it counts the number of bytes available in the buffer. I let you imagine what a nightmare it was when you had to deal with ANSI/UNICODE strings. In the <code>GetModuleFileNameEx </code>case, the size parameter takes <a href="https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-getmodulefilenameexw?WT.mc_id=DT-MVP-5003325">the number of characters</a>.</p>
<p>If you take a look at the <code>NtProcessManager.GetModuleInfos</code> implementation, you find the following code in the implementation:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="n">StringBuilder</span> <span class="n">stringBuilder2</span> <span class="p">=</span> <span class="k">new</span> <span class="n">StringBuilder</span><span class="p">(</span><span class="m">1024</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="n">Microsoft</span><span class="p">.</span><span class="n">Win32</span><span class="p">.</span><span class="n">NativeMethods</span><span class="p">.</span><span class="n">GetModuleFileNameEx</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">safeProcessHandle</span><span class="p">,</span> 
</span></span><span class="line"><span class="cl">    <span class="k">new</span> <span class="n">HandleRef</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="n">handle</span><span class="p">),</span> 
</span></span><span class="line"><span class="cl">    <span class="n">stringBuilder2</span><span class="p">,</span> 
</span></span><span class="line"><span class="cl">    <span class="n">stringBuilder2</span><span class="p">.</span><span class="n">Capacity</span> <span class="p">*</span> <span class="m">2</span>
</span></span><span class="line"><span class="cl"> <span class="p">)</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Since the capacity of the <code>StringBuilder </code>is set to 1024, this code tells <code>GetModuleFileNameEx </code>that it is allowed to write up to 2048 characters. This looks like a bug… but hard to trigger with the <a href="https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file">usual 260 characters limitation for filenames</a>. However, if, one day, you decide to use the extended syntax with the “\?\” prefix syntax to create a looooong folder for your application, the bug will trigger an <code>AccessViolationException </code>beyond 1025 characters.</p>
<p><img loading="lazy" src="/posts/2018-11-13_get-process-name-challenge/1_GWNlpyBn9ykY2J6JVTTKNw.png"></p>
<p>Here is my safer implementation:</p>
<pre tabindex="0"><code>private readonly StringBuilder _baseNameBuilder = new StringBuilder(1024);
public static string GetProcessNameNative(Process p)
{
    _baseNameBuilder.Clear();
    if (GetModuleFileNameEx(p.SafeHandle, IntPtr.Zero, _baseNameBuilder, _baseNameBuilder.Capacity) == 0)
    {
        _baseNameBuilder.Append(&#34;???&#34;);
    }

    return _baseNameBuilder.ToString();
}
</code></pre><p>When I presented Kevin my oldies but goodies solution, he told me that he found a smarter solution. While I was digging into my memories, he kept decompiling the implementation of the <code>Process </code>class and realized that it contains a <code>processInfo</code> private field:</p>
<p><img loading="lazy" src="/posts/2018-11-13_get-process-name-challenge/1_ukveC2Y4V9Ls0ohF7Lk9Fw.png"></p>
<p>And its internal class exposes a public field called… <code>processName</code>: exactly what we needed!</p>
<p><img loading="lazy" src="/posts/2018-11-13_get-process-name-challenge/1_mAeJSykxpHEKbG25GTJSSQ.png"></p>
<p>So I was ready to implement a reflection-based solution like:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="n">Type</span> <span class="n">_processInfoType</span> <span class="p">=</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="n">FieldInfo</span> <span class="n">_processNameField</span> <span class="p">=</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">static</span> <span class="kt">string</span> <span class="n">GetProcessNameByReflection</span><span class="p">(</span><span class="n">Process</span> <span class="n">p</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">processInfoField</span> <span class="p">=</span> <span class="k">typeof</span><span class="p">(</span><span class="n">System</span><span class="p">.</span><span class="n">Diagnostics</span><span class="p">.</span><span class="n">Process</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="n">GetField</span><span class="p">(</span><span class="s">&#34;processInfo&#34;</span><span class="p">,</span> <span class="n">BindingFlags</span><span class="p">.</span><span class="n">Instance</span> <span class="p">|</span> <span class="n">BindingFlags</span><span class="p">.</span><span class="n">NonPublic</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">processInfo</span> <span class="p">=</span> <span class="n">processInfoField</span><span class="p">.</span><span class="n">GetValue</span><span class="p">(</span><span class="n">p</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">_processInfoType</span> <span class="p">==</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">_processInfoType</span> <span class="p">=</span> <span class="n">processInfo</span><span class="p">.</span><span class="n">GetType</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="n">_processNameField</span> <span class="p">=</span> <span class="n">_processInfoType</span><span class="p">.</span><span class="n">GetField</span><span class="p">(</span><span class="s">&#34;processName&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">_processNameField</span><span class="p">.</span><span class="n">GetValue</span><span class="p">(</span><span class="n">processInfo</span><span class="p">).</span><span class="n">ToString</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>And Kevin was able to give me a definitively smarter solution based on compiled expressions:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="n">Func</span><span class="p">&lt;</span><span class="n">Process</span><span class="p">,</span> <span class="kt">string</span><span class="p">&gt;</span> <span class="n">GetProcessNameAccessor</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">param</span> <span class="p">=</span> <span class="n">Expression</span><span class="p">.</span><span class="n">Parameter</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">Process</span><span class="p">),</span> <span class="s">&#34;arg&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">processInfoMember</span> <span class="p">=</span> <span class="n">Expression</span><span class="p">.</span><span class="n">Field</span><span class="p">(</span><span class="n">param</span><span class="p">,</span> <span class="s">&#34;processInfo&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">processNameMember</span> <span class="p">=</span> <span class="n">Expression</span><span class="p">.</span><span class="n">Field</span><span class="p">(</span><span class="n">processInfoMember</span><span class="p">,</span> <span class="s">&#34;processName&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">lambda</span> <span class="p">=</span> <span class="n">Expression</span><span class="p">.</span><span class="n">Lambda</span><span class="p">(</span><span class="k">typeof</span><span class="p">(</span><span class="n">Func</span><span class="p">&lt;</span><span class="n">Process</span><span class="p">,</span> <span class="kt">string</span><span class="p">&gt;),</span> <span class="n">processNameMember</span><span class="p">,</span> <span class="n">param</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="n">Func</span><span class="p">&lt;</span><span class="n">Process</span><span class="p">,</span> <span class="kt">string</span><span class="p">&gt;)</span><span class="n">lambda</span><span class="p">.</span><span class="n">Compile</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="k">readonly</span> <span class="n">Func</span><span class="p">&lt;</span><span class="n">Process</span><span class="p">,</span> <span class="kt">string</span><span class="p">&gt;</span> <span class="n">_getProcessNameFunc</span> <span class="p">=</span> <span class="n">GetProcessNameAccessor</span> <span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="kt">string</span> <span class="n">GetProcessNameByExpression</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">_getProcessNameFunc</span><span class="p">(</span><span class="n">_process</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>However, when I tested them on my laptop, I got null reference exceptions while it was working fine on Kevin’s machine… There was a tiny difference between us: I was calling <code>Process.GetProcessById</code> while Kevin was using <code>Process.GetProcesses</code> to get the <code>Process</code> instance. It looks like the implementation of both methods is not doing the same initialization. This kind of things happen when you are trying to use undocumented implementation details… Also note that the .NET Core implementation is different (<a href="https://github.com/dotnet/corefx/blob/e34fa6ac5fcc49be5cb22f46119c6d99219483b6/src/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Win32.cs">and does not contain the string length bug</a>).</p>
<h2 id="comparing-the-different-solutions">Comparing the different solutions</h2>
<p>So which solution should I pick for our metrics collector?</p>
<p>It’s time to do some benchmarking thanks to <a href="https://github.com/dotnet/BenchmarkDotNet">BenchmarkDotNet </a>and the results give different order of magnitude!</p>
<p><img loading="lazy" src="/posts/2018-11-13_get-process-name-challenge/1_yg7dKTQt9vBfkqix21GNlA.png"></p>
<p>The winner is without contest based on expressions and the worst one is… the initial implementation that does not even fall into the error case during our tests!</p>
<p>After updating the implementation with the compiled expression-based solution, the CPU usage of our metrics collector seems more reasonable:</p>
<p><img loading="lazy" src="/posts/2018-11-13_get-process-name-challenge/1_oyIUliDIkvCo8S3hCzLTRA.png"></p>
<p>It is now a good time to go back home… to write this article :^)</p>
]]></content:encoded></item></channel></rss>