<?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>Dotnet-Tools on Welcome to Christophe Nasarre's Blog</title><link>https://chrisnas.github.io/tags/dotnet-tools/</link><description>Recent content in Dotnet-Tools 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/dotnet-tools/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>Unexpected usage of EventSource or how to test statistical results in CLR pull request</title><link>https://chrisnas.github.io/posts/2024-09-13_unexpected-usage-of-eventsourc/</link><pubDate>Fri, 13 Sep 2024 14:25:30 +0000</pubDate><guid>https://chrisnas.github.io/posts/2024-09-13_unexpected-usage-of-eventsourc/</guid><description>Unexpected usage of EventSource</description><content:encoded><![CDATA[<hr>
<h2 id="testing-the-statistical-results">Testing the statistical results</h2>
<p>In parallel of the performance impact, it is important to validate the expected statistical distribution of the sampled allocations. Basically, I need to execute the same run of allocations multiple times in a row. Each run allocates the same number of instances of different types. For example, it is interesting to know if sampling instances of types with sizes proportional to a base value gives good results. Same question for totally different sized types or with Finalizers.</p>
<p>I would like to pass the number of runs to execute and a given scenario to a C# runner program and listen to the emitted events in another C# listener.</p>
<p>I’m facing 3 issues here:</p>
<ul>
<li>How many instances are allocated to validate the upscaling algorithm (sampled vs real count)</li>
<li>What are the types I want to focus on because I don’t want to hard code them in the listener application.</li>
<li>When does each run start?</li>
</ul>
<p>It would be great if I could send the answer to these questions via events so the listener would know at runtime. Well… This is exactly what a class inherited from <a href="https://learn.microsoft.com/en-us/dotnet/core/diagnostics/eventsource?WT.mc_id=DT-MVP-5003325"><strong>EventSource</strong></a> allows you to do!</p>
<p>In the runner application, I’ve defined the <strong>AllocationsRunEventSource</strong> that is decorated with the <strong>EventSource</strong> attribute to set its name that will be used as a provider name like <em>Microsoft-Windows-DotNETRuntime</em> for the .NET runtime 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></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;Allocations-Run&#34;)]</span>
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">AllocationsRunEventSource</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="kd">public</span> <span class="kd">static</span> <span class="k">readonly</span> <span class="n">AllocationsRunEventSource</span> <span class="n">Log</span> <span class="p">=</span> <span class="k">new</span> <span class="n">AllocationsRunEventSource</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">The</span> <span class="n">four</span> <span class="n">implemented</span> <span class="n">methods</span> <span class="n">are</span> <span class="n">defining</span> <span class="n">which</span> <span class="k">event</span> <span class="n">ID</span> <span class="k">for</span> <span class="n">which</span> <span class="n">verbosity</span> <span class="n">will</span> <span class="n">be</span> <span class="n">emitted</span> <span class="n">with</span> <span class="n">which</span> <span class="n">payload</span><span class="p">:</span>
</span></span><span class="line"><span class="cl"><span class="na">    [Event(600, Level = EventLevel.Informational)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="k">void</span> <span class="n">StartRun</span><span class="p">(</span><span class="kt">int</span> <span class="n">iterationsCount</span><span class="p">,</span> <span class="kt">int</span> <span class="n">allocationCount</span><span class="p">,</span> <span class="kt">string</span> <span class="n">listOfTypes</span><span class="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">600</span><span class="p">,</span> <span class="n">iterationsCount</span><span class="p">,</span> <span class="n">allocationCount</span><span class="p">,</span> <span class="n">listOfTypes</span><span class="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(601, Level = EventLevel.Informational)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="k">void</span> <span class="n">StopRun</span><span class="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">601</span><span class="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(602, Level = EventLevel.Informational)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="k">void</span> <span class="n">StartIteration</span><span class="p">(</span><span class="kt">int</span> <span class="n">iteration</span><span class="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">602</span><span class="p">,</span> <span class="n">iteration</span><span class="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(603, Level = EventLevel.Informational)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="k">void</span> <span class="n">StopIteration</span><span class="p">(</span><span class="kt">int</span> <span class="n">iteration</span><span class="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">603</span><span class="p">,</span> <span class="n">iteration</span><span class="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>To make the payload serialization and parsing easy, the list of types that will be allocated is passed as a string with the following format <em>allocatedTypes = “Object24;Object48;Object72;Object32;Object64;Object96”</em>.</p>
<p>The code of the runner calls these methods as expected at different moment of the execution:</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="n">AllocationsRunEventSource</span><span class="p">.</span><span class="n">Log</span><span class="p">.</span><span class="n">StartRun</span><span class="p">(</span><span class="n">iterations</span><span class="p">,</span> <span class="n">allocationsCount</span><span class="p">,</span> <span class="n">allocatedTypes</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">iterations</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">AllocationsRunEventSource</span><span class="p">.</span><span class="n">Log</span><span class="p">.</span><span class="n">StartIteration</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">     <span class="n">allocationsRun</span><span class="p">.</span><span class="n">Allocate</span><span class="p">(</span><span class="n">allocationsCount</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">     <span class="n">AllocationsRunEventSource</span><span class="p">.</span><span class="n">Log</span><span class="p">.</span><span class="n">StopIteration</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">AllocationsRunEventSource</span><span class="p">.</span><span class="n">Log</span><span class="p">.</span><span class="n">StopRun</span><span class="p">();</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Instead of recording the events with dotnet-trace, this time I’m using TraceEvent and Microsoft.Diagnostics.NETCore.Client to code a listener application. The code is very similar to <a href="/posts/2024-05-22_trigger-your-gcs-with/">what was presented</a> for my dotnet-fullgc CLI tool except that I’m enabling the <strong>AllocationsRun</strong> provider corresponding to the event source of the runner in addition to the .NET runtime one:</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">public</span> <span class="kd">static</span> <span class="k">void</span> <span class="n">PrintEventsLive</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="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">Verbose</span><span class="p">,</span> <span class="c1">// verbose is required for AllocationTick</span>
</span></span><span class="line"><span class="cl">                <span class="p">(</span><span class="kt">long</span><span class="p">)</span><span class="m">0x80000000001</span> <span class="c1">// new AllocationSamplingKeyword + GCKeyword</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;Allocations-Run&#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></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 custom events from that provider are received via the <strong>source.Dynamic.All</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><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="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><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><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></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">Task</span> <span class="n">streamTask</span> <span class="p">=</span> <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="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">_source</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">ClrTraceEventParser</span> <span class="n">clrParser</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ClrTraceEventParser</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">clrParser</span><span class="p">.</span><span class="n">GCAllocationTick</span> <span class="p">+=</span> <span class="n">OnAllocationTick</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">OnEvents</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>Because TraceEvent is not already aware of the new <strong>AllocationSampled</strong> event emitted by the PR code, it will also be received via the same <strong>OnEvent</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><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="kd">static</span> <span class="k">void</span> <span class="n">OnEvents</span><span class="p">(</span><span class="n">TraceEvent</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">if</span> <span class="p">(</span><span class="n">eventData</span><span class="p">.</span><span class="n">ID</span> <span class="p">==</span> <span class="p">(</span><span class="n">TraceEventID</span><span class="p">)</span><span class="m">303</span><span class="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">// AllocationSampled parsing </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">eventData</span><span class="p">.</span><span class="n">ID</span> <span class="p">==</span> <span class="p">(</span><span class="n">TraceEventID</span><span class="p">)</span><span class="m">600</span><span class="p">)</span>  <span class="c1">// Start run</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// keep track of the expected types and the number of allocated instances </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">eventData</span><span class="p">.</span><span class="n">ID</span> <span class="p">==</span> <span class="p">(</span><span class="n">TraceEventID</span><span class="p">)</span><span class="m">601</span><span class="p">)</span>  <span class="c1">// Stop run</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// show the results of the run</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">eventData</span><span class="p">.</span><span class="n">ID</span> <span class="p">==</span> <span class="p">(</span><span class="n">TraceEventID</span><span class="p">)</span><span class="m">602</span><span class="p">)</span>  <span class="c1">// Start an iteration in a run</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// reset for a new iteration</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">eventData</span><span class="p">.</span><span class="n">ID</span> <span class="p">==</span> <span class="p">(</span><span class="n">TraceEventID</span><span class="p">)</span><span class="m">603</span><span class="p">)</span>  <span class="c1">// Stop an iteration in a run</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// Show iteration results</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 class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The parsing of the payload of the run related events is done <a href="/posts/2024-08-13_tips-and-tricks-from/">the same way as for AllocationSampled</a> by a dedicated <strong>xxxData</strong> 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></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">AllocationsRunData</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">const</span> <span class="kt">int</span> <span class="n">EndOfStringCharLength</span> <span class="p">=</span> <span class="m">2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="n">TraceEvent</span> <span class="n">_payload</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">AllocationsRunData</span><span class="p">(</span><span class="n">TraceEvent</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></span><span class="line"><span class="cl">        <span class="n">ComputeFields</span><span 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">int</span> <span class="n">Iterations</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">Count</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">AllocatedTypes</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">ComputeFields</span><span class="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">offsetBeforeString</span> <span class="p">=</span> <span class="m">4</span> <span class="p">+</span> <span class="m">4</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">Span</span><span class="p">&lt;</span><span class="kt">byte</span><span class="p">&gt;</span> <span class="n">data</span> <span class="p">=</span> <span class="n">_payload</span><span class="p">.</span><span class="n">EventData</span><span class="p">().</span><span class="n">AsSpan</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="n">Iterations</span> <span class="p">=</span> <span class="n">BitConverter</span><span class="p">.</span><span class="n">ToInt32</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">Slice</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="m">4</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">        <span class="n">Count</span> <span class="p">=</span> <span class="n">BitConverter</span><span class="p">.</span><span class="n">ToInt32</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">Slice</span><span class="p">(</span><span class="m">4</span><span class="p">,</span> <span class="m">4</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">        <span class="n">AllocatedTypes</span> <span class="p">=</span> <span class="n">Encoding</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">data</span><span class="p">.</span><span class="n">Slice</span><span class="p">(</span><span class="n">offsetBeforeString</span><span class="p">,</span> <span class="n">_payload</span><span class="p">.</span><span class="n">EventDataLength</span> <span class="p">-</span> <span class="n">offsetBeforeString</span> <span class="p">-</span> <span class="n">EndOfStringCharLength</span><span class="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>By keeping track of this data, it is possible to show each iteration results:</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-plaintext" data-lang="plaintext"><span class="line"><span class="cl">&gt; starts 100 iterations allocating 1000000 instances
</span></span><span class="line"><span class="cl">0|
</span></span><span class="line"><span class="cl">Tag  SCount  TCount          SSize          TSize   UnitSize     UpscaledSize  UpscaledCount  Name
</span></span><span class="line"><span class="cl">--------------------------------------------------------------------------------------------------
</span></span><span class="line"><span class="cl"> ST     247     384           5928           9216         24         24702711        1029279  Object24
</span></span><span class="line"><span class="cl"> ST     322     106          10304           3392         32         32205122        1006410  Object32
</span></span><span class="line"><span class="cl"> ST     435     509          20880          24432         48         43510266         906463  Object48
</span></span><span class="line"><span class="cl"> ST     587     776          37568          49664         64         58718825         917481  Object64
</span></span><span class="line"><span class="cl"> ST     747     481          53784          34632         72         74726662        1037870  Object72
</span></span><span class="line"><span class="cl"> ST     958     916          91968          87936         96         95845392         998389  Object96
</span></span></code></pre></td></tr></table>
</div>
</div><p>that integrates <strong>AllocationTick</strong> numbers too.</p>
<p>At the end of the run, error distribution per type is also computed over the iterations:</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-plaintext" data-lang="plaintext"><span class="line"><span class="cl">Object72
</span></span><span class="line"><span class="cl">-------------------------
</span></span><span class="line"><span class="cl">   1  -10.5 %
</span></span><span class="line"><span class="cl">   2   -8.9 %
</span></span><span class="line"><span class="cl">   3   -8.4 %
</span></span><span class="line"><span class="cl">   4   -7.8 %
</span></span><span class="line"><span class="cl">   5   -6.4 %
</span></span><span class="line"><span class="cl">        ...
</span></span><span class="line"><span class="cl">  49    0.2 %
</span></span><span class="line"><span class="cl">  50    0.2 %
</span></span><span class="line"><span class="cl">  51    0.2 %
</span></span><span class="line"><span class="cl">        ...
</span></span><span class="line"><span class="cl">  96    6.8 %
</span></span><span class="line"><span class="cl">  97    6.8 %
</span></span><span class="line"><span class="cl">  98    7.7 %
</span></span><span class="line"><span class="cl">  99    8.6 %
</span></span><span class="line"><span class="cl"> 100   10.0 %
</span></span></code></pre></td></tr></table>
</div>
</div><p>You could use the same mechanisms (a custom <strong>EventSource</strong> to emit additional information and an <strong>EventPipe</strong> listener to aggregate the data) for your own usage. This is a different way to use <strong>EventSource</strong> rather than emitting events for monitoring like <a href="https://learn.microsoft.com/en-us/dotnet/core/diagnostics/well-known-event-providers#framework-libraries">what is done by the BCL</a>.</p>
<h2 id="testing-the-standalone-gc">Testing the standalone GC</h2>
<p>In addition to the usual .NET GC, it is needed to validate that the changes are also working for the <em>standalone GC</em>. <a href="https://github.com/dotnet/runtime/blob/main/docs/design/features/standalone-gc-loading.md#identifying-candidate-shared-libraries">Long story short</a>, it is possible to replace the existing .NET garbage collector by your own implementation. For the test, I needed to check that the standalone GC clrgcexp.dll generated by the .NET compilation generates the expected <strong>AllocationSampled</strong> events when the corresponding keyword with informational verbosity is enabled.</p>
<h2 id="debugging-nativeaot-scenarios">Debugging NativeAOT scenarios</h2>
<p>The final step was to implement the feature for the NativeAOT scenario. When you build your C# application for NativeAOT, a lot happens behind the scenes, based on compilers known by Visual Studio corresponding to the official released version of the .NET runtime. In my case, I needed to use the brand-new code of my local branch and debug some simple C# applications. The steps to reach that goal are not that simple.</p>
<p>First, you follow the steps <a href="https://github.com/dotnet/runtime/blob/main/docs/workflow/building/coreclr/nativeaot.md#convenience-visual-studio-repro-project">given by the documentation</a> to build AOT CLR in debug and libs in release:</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-sql" data-lang="sql"><span class="line"><span class="cl"><span class="n">build</span><span class="w"> </span><span class="n">clr</span><span class="p">.</span><span class="n">aot</span><span class="o">+</span><span class="n">lib</span><span class="w"> </span><span class="o">-</span><span class="n">rc</span><span class="w"> </span><span class="n">debug</span><span class="w"> </span><span class="o">-</span><span class="n">lc</span><span class="w"> </span><span class="n">release</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">build</span><span class="w"> </span><span class="o">-</span><span class="k">c</span><span class="w"> </span><span class="n">release</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Then, open src\coreclr\tools\aot\ilc.sln</p>
<p>the repro project contains a program.cs file where you write the C# code you want to test and debug.</p>
<p>When you build a NativeAOT application, you need to select if you want the runtime that emits events or not. This is done by setting the <strong>EventSourceSupport</strong> to true in the .csproj:</p>
<p>Add the following in the .csproj to get 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></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;EventSourceSupport&gt;</span>true<span class="nt">&lt;/EventSourceSupport&gt;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>However, with the repro project, you need to change <strong>ILCompiler.csproj</strong> in a different 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></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;ReproResponseLines</span> <span class="na">Include=</span><span class="s">&#34;--feature:System.Diagnostics.Tracing.EventSource.IsSupported=true&#34;</span> <span class="nt">/&gt;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Also, change the reproNative.vcxproj file to bind to <strong>eventpipe-enabled.lib</strong> instead of <strong>eventpipe-disabled.lib</strong> for the platform/configuration you want to debug:</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-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;ItemDefinitionGroup</span> <span class="na">Condition=</span><span class="s">&#34;&#39;$(Configuration)|$(Platform)&#39;==&#39;Debug|x64&#39;&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">    ...
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;Link&gt;</span>
</span></span><span class="line"><span class="cl">      ...
</span></span><span class="line"><span class="cl">      <span class="nt">&lt;AdditionalDependencies&gt;</span>...$(ArtifactsRoot)bin\coreclr\windows.x64.Debug\aotsdk\eventpipe-enabled.lib;...<span class="nt">&lt;/AdditionalDependencies&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/Link&gt;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Then, build <strong>repro</strong> in Debug x64</p>
<p>Next, change the target for the <strong>ILCompiler</strong> project:</p>
<p><img loading="lazy" src="/posts/2024-09-13_unexpected-usage-of-eventsourc/1_GD25I4_RbCY1-6vEV7zetw.png"></p>
<p>Build and run it to generate the .obj file corresponding to the <strong>repro</strong> project</p>
<p>Finally, open src\coreclr\tools\aot\ILCompiler\reproNative\reproNative.vcxproj that will allow you to debug the program.cs you’ve just built!</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>View your GCs statistics live with dotnet-gcstats!</title><link>https://chrisnas.github.io/posts/2024-03-01_view-your-gcs-statistics/</link><pubDate>Fri, 01 Mar 2024 18:30:13 +0000</pubDate><guid>https://chrisnas.github.io/posts/2024-03-01_view-your-gcs-statistics/</guid><description>Discover how to look at the .NET GC statistics to better understand your garbage collections</description><content:encoded><![CDATA[<hr>
<h2 id="introduction">Introduction</h2>
<p>While working on the second edition of <a href="https://www.amazon.com/Pro-NET-Memory-Management-Performance/dp/148424026X">Pro .NET Memory Management</a>, it was needed to get statistics about each garbage collection to explain the condemned generation and other decisions taken by the GC. This post explains the different internal data structures used by the GC and how to get their value for each collection. Some require debugging the CLR and others are emitted via events. For the latter, I will show how I wrote the new <strong>dotnet-gcstats</strong> CLI tool to collect them and a personal Perfview GCStats displaying live data, garbage collection after garbage collection.</p>
<h2 id="high-level-view-of-gc-internals">High level view of GC Internals</h2>
<p>With regions, the GC keeps track of managed memory allocated by your application in instances of the <strong>gc_heap</strong> class. In Workstation mode, only 1 instance exists and in Server mode, by default, 1 instance is created per core. Each <strong>gc_heap</strong> keeps track of its 5 generations (gen0, gen1, gen2, Large Object Heap and Pinned Object Heap) in an array of 5 <strong>generation</strong> instances. Each generation references its dedicated regions wrapped by instances of <strong>heap_segment</strong>. These regions are reserved from a giant part of the process address space and committed as needed.</p>
<p><img loading="lazy" src="/posts/2024-03-01_view-your-gcs-statistics/1_RVd5JiSJBJzqCqqYjUJBog.png"></p>
<p>During a garbage collection, the GC code relies on global fields per <strong>gc_heap</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-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="k">static</span> <span class="n">gc_mechanisms</span> <span class="n">settings</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">gc_history_global</span> <span class="n">gc_data_global</span><span class="p">;</span>  <span class="c1">// for non background GC including foreground GC during a background
</span></span></span><span class="line"><span class="cl"><span class="n">gc_history_global</span> <span class="n">bgc_data_global</span><span class="p">;</span> <span class="c1">// for background GC only
</span></span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="n">dynamic_data</span> <span class="n">dynamic_data_table</span><span class="p">[</span><span class="n">total_generation_count</span> <span class="o">=</span> <span class="mi">5</span><span class="p">];</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <strong>settings</strong> field contains a few interesting fields:</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-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">gc_mechanisms</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="n">gc_index</span><span class="p">;</span> <span class="c1">// starts from 1 for the first GC
</span></span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">condemned_generation</span><span class="p">;</span>  <span class="c1">// generation to collect
</span></span></span><span class="line"><span class="cl">    <span class="n">BOOL</span> <span class="n">compaction</span><span class="p">;</span>  <span class="c1">// true when compaction instead of sweep
</span></span></span><span class="line"><span class="cl">    <span class="n">BOOL</span> <span class="n">loh_compaction</span><span class="p">;</span>  <span class="c1">// true when LOH needs compaction
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">concurrent</span><span class="p">;</span>  <span class="c1">// 1 = concurrent/background GC 
</span></span></span><span class="line"><span class="cl">    <span class="n">gc_reason</span> <span class="n">reason</span><span class="p">;</span>  <span class="c1">// trigger reason
</span></span></span><span class="line"><span class="cl">    <span class="n">gc_pause_mode</span> <span class="n">pause_mode</span><span class="p">;</span>  <span class="c1">// see GCSettings.LatencyMode
</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>including the trigger reason that will tell if your code called <strong>GC.Collect</strong> (i.e. induced), or if it was due to a LOH or SOH allocation for example. If <strong>compaction</strong> is true, a compacting GC will happen (instead of a sweeping one).</p>
<p>The <strong>gc/bgc_data_global</strong> contains almost the same information:</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-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="k">struct</span> <span class="nc">gc_history_global</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">num_heaps</span><span class="p">;</span>  <span class="c1">// number of gc_heap instances
</span></span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">condemned_generation</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">gc_reason</span> <span class="n">reason</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">pause_mode</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">mem_pressure</span><span class="p">;</span> 
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">global_mechanisms_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>Most of the fields are available from different events:</p>
<ul>
<li><strong>GCStart</strong>: <strong>gc_index</strong> in <strong>Count</strong>, <strong>condemned_generation</strong> in <strong>Depth</strong>, <strong>reason</strong> in <strong>Reason</strong></li>
<li><strong>GCGlobalHeapHistory</strong>: <strong>pause_mode</strong> in <strong>PauseMode</strong> and some others in <strong>GlobalMechanisms</strong></li>
</ul>
<h2 id="which-generation-to-collect--condemned-generation">Which generation to collect = condemned generation</h2>
<p>The computation of the <strong>condemned_generation</strong> is complicated and relies on many factors including metrics stored for each “generation” (gen0, gen1, gen2, LOH and POH) in an array of <a href="https://github.com/dotnet/runtime/blob/main/src/coreclr/gc/gcpriv.h#L1058"><strong>dynamic_data</strong></a> called <a href="https://github.com/dotnet/runtime/blob/main/src/coreclr/gc/gcpriv.h#L3627"><strong>dynamic_data_table</strong></a>. The <strong>dynamic_data</strong> class contains a few fields used by the GC to take decisions such as when a collection should be triggered and which generation to condemn:</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-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">dynamic_data</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="n">ptrdiff_t</span> <span class="n">new_allocation</span><span class="p">;</span>     <span class="c1">// remaining budget = budget - allocated
</span></span></span><span class="line"><span class="cl">    <span class="n">size_t</span>    <span class="n">desired_allocation</span><span class="p">;</span> <span class="c1">// budget to trigger a GC
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// # of bytes taken by survived objects after mark.
</span></span></span><span class="line"><span class="cl">    <span class="n">size_t</span>    <span class="n">survived_size</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// # of bytes taken by survived pinned plugs after mark.
</span></span></span><span class="line"><span class="cl">    <span class="n">size_t</span>    <span class="n">pinned_survived_size</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// total object size after a GC, ie, doesn&#39;t include fragmentation
</span></span></span><span class="line"><span class="cl">    <span class="n">size_t</span>    <span class="n">current_size</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">size_t</span>    <span class="n">promoted_size</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">size_t</span>    <span class="n">fragmentation</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 these fields are found in the payload of <a href="https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/ClrEtwAll.man#L1296">GCPerHeapHistory</a> or <a href="https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/ClrEtwAll.man#L962">GCHeapStat</a> events. However, the most interesting one, <strong>new_allocation</strong> is not available. Why is it interesting? Because it would give you which generation had its budget exceeded. It is initialized with the generation budget at the end of a GC and then, each time an allocation context gets created, its size is deducted from it. When it reaches 0, it means that the budget is exceeded, and a collection should happen.</p>
<p>Since I needed to debug the CLR to better understand all these algorithms, I added a breakpoint at the beginning of <strong>gc_heap::garbage_collect</strong> with the following action:</p>
<pre tabindex="0"><code>#{settings.gc_index}[{gc_trigger_reason}]{&#34;\n&#34;,s8b} new_allocation(0) = {dynamic_data_table[0].new_allocation}{&#34;\n&#34;,s8b} desired_allocation(0) = {dynamic_data_table[0].desired_allocation}{&#34;\n&#34;,s8b} begin_data_size(0) = {dynamic_data_table[0].begin_data_size}{&#34;\n&#34;,s8b} promoted_size(0) = {dynamic_data_table[0].promoted_size}{&#34;\n&#34;,s8b}-{&#34;\n&#34;,s8b} new_allocation(1) = 
...
{dynamic_data_table[4].new_allocation}{&#34;\n&#34;,s8b} desired_allocation(4) = {dynamic_data_table[4].desired_allocation}{&#34;\n&#34;,s8b} begin_data_size(4) = {dynamic_data_table[4].begin_data_size}{&#34;\n&#34;,s8b} promoted_size(4) = {dynamic_data_table[4].promoted_size}{&#34;\n&#34;,s8b}__________{&#34;\n&#34;,s8b}
</code></pre><p>And now, each time a GC happens, I get the corresponding log in my Output pane in Visual Studio:</p>
<pre tabindex="0"><code>#2[reason_alloc_soh (0)]
 new_allocation(0) = -22728
 desired_allocation(0) = 134217728
 begin_data_size(0) = 8391376
 promoted_size(0) = 8383432
-
 new_allocation(1) = -5910416
 desired_allocation(1) = 2473016
 begin_data_size(1) = 375528
 promoted_size(1) = 353288
-
 new_allocation(2) = -91144
 desired_allocation(2) = 262144
 begin_data_size(2) = 0
 promoted_size(2) = 0
-
 new_allocation(3) = 28000088
 desired_allocation(3) = 28000088
 begin_data_size(3) = 8000024
 promoted_size(3) = 8000024
-
 new_allocation(4) = 3145728
 desired_allocation(4) = 3145728
 begin_data_size(4) = 32712
 promoted_size(4) = 32712
</code></pre><p>As you can see, gen0, gen1 and gen2 have all their budget exceeded (i.e. their <strong>new_allocation</strong> is negative) and it explains why a simple gen0 collection (from allocation in SOH = gen0) becomes a gen2 collection. If you wonder how gen1 and gen2 budgets are exceeded as your application is only allocating in gen0, you need to understand that when a GC copy surviving objects from one younger generation to the older, they are counted as allocations in the older and subtracted from its <strong>new_allocation</strong> metric.</p>
<p>The GC is encoding the different steps leading to the final condemned generation in a 32 bit value stored in a <strong>gen_to_condemn_tuning</strong> field that allows you to get:</p>
<ul>
<li>initial condemned generation,</li>
<li>final generation to condemn,</li>
<li>which generation’s budget is exceeded.</li>
</ul>
<p>The value of the last one corresponds to the highest generation for which its <strong>new_allocation</strong> was negative.</p>
<p>This information is available in the <strong>CondemnReasons0</strong> field of the <strong>GCPerHeapHistory</strong> event, and you need some arithmetic to get the generation you want:</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">private</span> <span class="kd">const</span> <span class="kt">int</span> <span class="n">gen_initial</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>          <span class="c1">// indicates the initial gen to condemn.</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">gen_final_per_heap</span> <span class="p">=</span> <span class="m">1</span><span class="p">;</span>   <span class="c1">// indicates the final gen to condemn per heap.</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">gen_alloc_budget</span> <span class="p">=</span> <span class="m">2</span><span class="p">;</span>     <span class="c1">// indicates which gen&#39;s budget is exceeded.</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">const</span> <span class="kt">int</span> <span class="n">InitialGenMask</span> <span class="p">=</span> <span class="m">0x0</span> <span class="p">+</span> <span class="m">0x1</span> <span class="p">+</span> <span class="m">0x2</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="kt">int</span> <span class="n">GetGen</span><span class="p">(</span><span class="kt">int</span> <span class="n">val</span><span class="p">,</span> <span class="kt">int</span> <span class="n">reason</span><span class="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">gen</span> <span class="p">=</span> <span class="p">(</span><span class="n">val</span> <span class="p">&gt;&gt;</span> <span class="m">2</span> <span class="p">*</span> <span class="n">reason</span><span class="p">)</span> <span class="p">&amp;</span> <span class="n">InitialGenMask</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">gen</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="building-your-owntool">Building your own tool</h2>
<p>Even though I could dig into the different matrices available in the Perfview’s GCStats view or its export to Excel, I decided to write dotnet-gcstats. This CLI tool listens to the CLR events emitted by a .NET application thanks to Microsoft.Diagnostics.NETCore.Client (connect to the application EventPipe) and TraceEvent (receive and analyze the CLR events).</p>
<p>The code is amazingly 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><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="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 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 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">GC</span><span class="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">processId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</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><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></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">Task</span> <span class="n">streamTask</span> <span class="p">=</span> <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="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></span><span class="line"><span class="cl">        <span class="n">ClrTraceEventParser</span> <span class="n">clrParser</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ClrTraceEventParser</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">clrParser</span><span class="p">.</span><span class="n">GCPerHeapHistory</span> <span class="p">+=</span> <span class="n">OnGCPerHeapHistory</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">clrParser</span><span class="p">.</span><span class="n">GCStart</span> <span class="p">+=</span> <span class="n">OnGCStart</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">clrParser</span><span class="p">.</span><span class="n">GCGlobalHeapHistory</span> <span class="p">+=</span> <span class="n">OnGCGlobalHeapHistory</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">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">catch</span> <span class="p">(</span><span class="n">Exception</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">ShowError</span><span class="p">(</span><span class="s">$&#34;Error encountered while processing events: {e.Message}&#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>Each event handler is responsible for extracting and translating the interesting fields of its event payload with a few color enhancements:</p>
<ul>
<li><strong>GCStart</strong>: collection count and reason (highlight induced collections).</li>
<li><strong>GCGlobalHeapHistory</strong>: condemned generation, pause mode and memory pressure.</li>
<li><strong>GCPerHeapHistory</strong>: starting -&gt; final condemned generation and for each heap, budget, begin size, begin obj size, final size, promoted size and fragmentation.</li>
</ul>
<p>The final step was to transform a simple console application into a .NET CLI tool that everyone will be able to install with <strong>dotnet tool install -g dotnet-gcstats</strong> and use with <strong>dotnet gcstats <pid></strong>. I followed <a href="https://learn.microsoft.com/en-us/dotnet/core/tools/global-tools-how-to-create?WT.mc_id=DT-MVP-5003325">the documentation</a> by adding the following to the project file:</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-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;PropertyGroup&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;PackAsTool&gt;</span>true<span class="nt">&lt;/PackAsTool&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;ToolCommandName&gt;</span>dotnet-gcstats<span class="nt">&lt;/ToolCommandName&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;PackageOutputPath&gt;</span>./nupkg<span class="nt">&lt;/PackageOutputPath&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;GeneratePackageOnBuild&gt;</span>true<span class="nt">&lt;/GeneratePackageOnBuild&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;/PropertyGroup&gt;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>In addition, I provided a few additional details:</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-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;PropertyGroup&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;PackageId&gt;</span>dotnet-gcstats<span class="nt">&lt;/PackageId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;PackageVersion&gt;</span>1.0.0<span class="nt">&lt;/PackageVersion&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;Title&gt;</span>dotnet-gcstats<span class="nt">&lt;/Title&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;Authors&gt;</span>christophe Nasarre<span class="nt">&lt;/Authors&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;Owners&gt;</span>chrisnas<span class="nt">&lt;/Owners&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;RepositoryUrl&gt;</span>https://github.com/chrisnas<span class="nt">&lt;/RepositoryUrl&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;RepositoryType&gt;</span>git<span class="nt">&lt;/RepositoryType&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;PackageProjectUrl&gt;</span>https://github.com/chrisnas/GCStats<span class="nt">&lt;/PackageProjectUrl&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;PackageLicenseFile&gt;</span>LICENSE<span class="nt">&lt;/PackageLicenseFile&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;Description&gt;</span>Global CLI tool to display live statistics during .NET garbage collections<span class="nt">&lt;/Description&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;PackageReleaseNotes&gt;</span>Initial release<span class="nt">&lt;/PackageReleaseNotes&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;Copyright&gt;</span>Copyright Christophe Nasarre 2024-$([System.DateTime]::UtcNow.ToString(yyyy))<span class="nt">&lt;/Copyright&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;PackageTags&gt;</span>.NET TraceEvent CLR GC<span class="nt">&lt;/PackageTags&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;/PropertyGroup&gt;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Once built, I simply uploaded the generated package to nuget.org et voila!</p>
<p>Now, you should be able to better understand why some collections are triggered:</p>
<p><img loading="lazy" src="/posts/2024-03-01_view-your-gcs-statistics/1_3oHvH2Vxb3PgW46khVMSMQ.png"></p>
<p>And if it is not enough, wait for reading the second edition of Pro .NET Memory Management ;^)</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>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–1)</title><link>https://chrisnas.github.io/posts/2020-05-18_build-your-own-net/</link><pubDate>Mon, 18 May 2020 12:07:01 +0000</pubDate><guid>https://chrisnas.github.io/posts/2020-05-18_build-your-own-net/</guid><description>This post explains how to get the call stack corresponding to the allocations with CLR events.</description><content:encoded><![CDATA[<hr>
<p>In the <a href="/posts/2020-04-18_build-your-own-net/">previous episode</a> of this series, you have seen how to get a sampling of .NET application allocations thanks to the <strong>AllocationTick</strong> and <strong>GCSampleObjectAllocation</strong>(<strong>High</strong>/<strong>Low</strong>) CLR events. However, this is often not enough to investigate unexpected memory consumption: you would need to know which part of the code is triggering the allocations. This post explains how to get the call stack corresponding to the allocations, again with CLR events.</p>
<p><img loading="lazy" src="/posts/2020-05-18_build-your-own-net/1_lYXf1qgB1ctzgi5_RKSDEw.jpeg"></p>
<h2 id="introduction">Introduction</h2>
<p>If you look carefully at the payload of the <code>TraceEvent</code> object mapped by Microsoft <strong>TraceEvent</strong> library (not my fault if they have the same name) for each CLR event, you won’t see anything related to a call stack. However, in the <strong>TraceEvent</strong> <a href="https://github.com/microsoft/perfview/blob/master/src/TraceEvent/Samples/41_TraceLogMonitor.cs#L204">sample 41</a>, the following line looks promising:</p>
<blockquote>
<p>var callStack = data.CallStack();</p>
</blockquote>
<p>with data being a <code>TraceEvent</code> object received for each CLR event!</p>
<p>This <code>CallStack</code> method is <a href="https://github.com/microsoft/perfview/blob/master/src/TraceEvent/TraceLog.cs#L10539">an extension method</a> provided by the <code>TraceLog</code> special kind of event source. You might not have noticed but I have used it in the <strong>AllocationTick</strong> code sample from the <a href="/posts/2020-04-18_build-your-own-net/">previous post</a>. This class (and many more helper classes) is doing a lot of work to :</p>
<ul>
<li>“attach” a call stack to each CLR event; i.e. a list of addresses of assembly code</li>
<li>to translate addresses into string symbols (method names or full signatures), listen to a bunch of JIT related events for managed methods (more on this later), using COM-based <a href="https://docs.microsoft.com/en-us/visualstudio/debugger/debug-interface-access/debug-interface-access-sdk?WT.mc_id=DT-MVP-5003325?view=vs-2019">Debug Interface Access</a> (a.k.a. DIA) and <a href="https://www.nuget.org/packages/System.Reflection.Metadata"><strong>MetadataReaderProvider</strong></a>** **for native functions</li>
</ul>
<p>Notice that since events from all managed processes on the machine are handled by <code>TraceLog</code>, the internal cache for JITted methods description could consume a lot of memory. During my tests with two Visual Studio running, my test profiler consumed more than 500 MB before even handling call stacks. If you are in such an environment with multiple .NET processes, I will show how to “manually” get the same stacks (+ symbols in the next episode) with CLR events and a few methods from dbghelp.dll in a cheaper way.</p>
<p><img loading="lazy" src="/posts/2020-05-18_build-your-own-net/1_jD1PSQqxKAbjsIOdHVSS_Q.png"></p>
<p>The new provider (more on <strong>ClrRundown</strong> later), keywords and events need to be received to make all this work:</p>
<p><img loading="lazy" src="/posts/2020-05-18_build-your-own-net/1_tWU46jlpltvIqt0ieA_YCA.png"></p>
<h2 id="tracelog-the-easyway">TraceLog: the easy way</h2>
<p>As you have seen in the previous posts, the <code>TraceEventSession</code> class exposes a <code>Source</code> property of <code>ETWTraceEventSource</code> type. This source has event parsers properties from which you register handler methods that will be called when CLR events are received. Instead of directly using this source, you should wrap it with a <code>TraceLogEventSource</code> object that provides the same event parsers.</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="k">await</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">_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">SetupProviders</span><span class="p">(</span><span class="n">_session</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</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="n">SetupListeners</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="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><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="whats-new-with-providers">What’s new with providers?</h2>
<p>The code for my<code>SetupProviders</code> method is a little bit different from the previous post even though no new event listeners are needed:</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></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">SetupProviders</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="c1">// Note: the kernel provider MUST be the first provider to be enabled</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// If the kernel provider is not enabled, the callstacks for CLR events are still received </span>
</span></span><span class="line"><span class="cl">    <span class="c1">// but the symbols are not found (except for the application itself)</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// TraceEvent implementation details triggered when a module (image) is loaded</span>
</span></span><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><span class="line"><span class="cl">
</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 class="c1">// this is needed in order to receive AllocationTick_V2 event</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">// required to receive AllocationTick 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">GC</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">// 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="p">|</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="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">Stack</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 class="n">ClrRundownTraceEventParser</span><span class="p">.</span><span class="n">ProviderGuid</span><span class="p">,</span> <span class="n">TraceEventLevel</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">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="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>The kernel provider needs to be enabled with the <strong>ImageLoad</strong> and <strong>Process</strong> keywords in order to let TraceEvent detect when a process loads “images” (i.e. dlls) and at which address (needed to convert Relative Virtual Addresses (RVA) to addresses in the address space). Note that this provider must be enabled before any other provider or your code will trigger an exception.</li>
<li>The CLR provider needs to be enabled with <strong>Jit</strong>, <strong>JittedMethodILToNativeMap</strong>, and <strong>Loader</strong> (in addition to the usual <strong>GC</strong> one).</li>
<li>The <strong>Stack</strong> keyword has to be set on the same CLR provider to receive call stacks events for “normal” CLR event (more on this later)</li>
<li>The CLR Rundown provider is enabled with the same <strong>Jit</strong>, <strong>JittedMethodILToNativeMap</strong>, and <strong>Loader</strong> keywords. That way, JIT events corresponding to <em>already</em> JITted methods will be received (not only the new ones). This is important because otherwise, you won’t be able to map these methods with the address in memory of their JITted native code in the case of processes that have been started before the profiler. This is the case for my AllocationTickProfiler sample.</li>
</ul>
<h2 id="callstacks-andsymbols">Callstacks and symbols</h2>
<p>Now, when an <strong>AllocationTick</strong> event is received, calling the <code>CallStack</code> extension method on the <code>GCAllocationTickTraceData</code> argument returns a <code>TraceCallStack</code> object. <a href="https://github.com/microsoft/perfview/blob/master/src/TraceEvent/TraceLog.cs#L7501">This class</a> is a linked list of <code>TraceCodeAddress</code> representing each stack frame (i.e. address in assembly code). These classes are at the heart of TraceEvent and Perfview callstack management. The method names and signatures are retrieved behind the scene thanks to JIT events and the <code>SymbolReader</code><a href="https://github.com/microsoft/perfview/blob/01b14294ca97b8f3bb2534624fb9cf2405881193/src/TraceEvent/Symbols/SymbolReader.cs#L21"> class</a> that digs into .pdb files.</p>
<p>You first need to initialize a <code>SymbolReader</code> instance:</p>
<ul>
<li>Set the path to find the .pdb; including the Microsoft HTTP endpoint for public .NET versions symbols,</li>
<li>Allow pdb next to the executable to be loaded.</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></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">// By default a symbol Reader uses whatever is in the _NT_SYMBOL_PATH variable.  However you can override</span>
</span></span><span class="line"><span class="cl"><span class="c1">// if you wish by passing it to the SymbolReader constructor.  Since we want this to work even if you </span>
</span></span><span class="line"><span class="cl"><span class="c1">// have not set an _NT_SYMBOL_PATH, so we add the Microsoft default symbol server path to be sure/</span>
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">symbolPath</span> <span class="p">=</span> <span class="k">new</span> <span class="n">SymbolPath</span><span class="p">(</span><span class="n">SymbolPath</span><span class="p">.</span><span class="n">SymbolPathFromEnvironment</span><span class="p">).</span><span class="n">Add</span><span class="p">(</span><span class="n">SymbolPath</span><span class="p">.</span><span class="n">MicrosoftSymbolServerPath</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">_symbolReader</span> <span class="p">=</span> <span class="k">new</span> <span class="n">SymbolReader</span><span class="p">(</span><span class="n">_symbolLookupMessages</span><span class="p">,</span> <span class="n">symbolPath</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">// By default the symbol reader will NOT read PDBs from &#39;unsafe&#39; locations (like next to the EXE)  </span>
</span></span><span class="line"><span class="cl"><span class="c1">// because hackers might make malicious PDBs. If you wish ignore this threat, you can override this</span>
</span></span><span class="line"><span class="cl"><span class="c1">// check to always return &#39;true&#39; for checking that a PDB is &#39;safe&#39;.  </span>
</span></span><span class="line"><span class="cl"><span class="n">_symbolReader</span><span class="p">.</span><span class="n">SecurityCheck</span> <span class="p">=</span> <span class="p">(</span><span class="n">path</span> <span class="p">=&gt;</span> <span class="kc">true</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Then, displaying a <code>TraceCallStack</code> from a received CLR event in a human-readable format is simple:</p>
<ul>
<li>Get one frame after the other from the linked list,</li>
<li>If the <code>CodeAddress</code> field is not cached yet, load the symbols for its module,</li>
<li>Display the <code>FullMethodName</code> field of the frame (or the address if not found).</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></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">DumpStack</span><span class="p">(</span><span class="n">TraceCallStack</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 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="n">Debug</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;Could not find module for Address 0x{codeAddress.Address:x}&#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">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">_symbolReader</span><span class="p">,</span> <span class="n">moduleFile</span><span class="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">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">codeAddress</span><span class="p">.</span><span class="n">FullMethodName</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;     {codeAddress.FullMethodName}&#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">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;     0x{codeAddress.Address:x}&#34;</span><span class="p">);</span>
</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 class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Note that the first frame in the linked list is the last on the stack (i.e. last executed method).</p>
<p>As I mentioned at the beginning of the post, I have been facing OutOfMemory errors due to the TraceEvent symbols management large memory usage when a few other .NET applications were running. Let’s see how to get the call stacks in a less memory consuming way.</p>
<h2 id="manually-rebuilding-the-allocations-callstack">Manually rebuilding the allocations call stack</h2>
<p>Instead of using the call stack and symbol management provided by <code>TraceLog</code> in TraceEvent, I would prefer to manually get them. If you remember the <a href="/posts/2020-04-18_build-your-own-net/">last post</a>, thanks to <strong>GCSampledObjectAllocation</strong> CLR events, it is possible to have a sampling of the allocation size and count per process and per type. What I would like to add to the type picture is the list of call stacks leading to these allocations.</p>
<h2 id="how-to-manually-get-clr-events-callstack">How to manually get CLR events call stack</h2>
<p>The first step is to understand how to get the CLR events call stacks. If you use the <code>TraceLog</code>-based code just presented, you should see the following kind of call stack:</p>
<p><img loading="lazy" src="/posts/2020-05-18_build-your-own-net/1_oZ7Y3712aL4ELCpUixnXrw.png"></p>
<p>The <code>ETWCallout</code> <a href="https://github.com/dotnet/runtime/blob/5178041776634bfbc4f868425710e60d95f7066f/src/coreclr/src/vm/eventtrace.cpp#L4423">CLR helper function</a> is in charge of sending a special event containing the call stack of other normal events from the four supported CLR providers. If you set the <strong>Stack</strong> keyword to the CLR provider, each time an event is sent by a thread, a <strong>ClrStackWalk</strong> event will be sent just after. It means after each <strong>SampleObjectAllocation</strong> event, a <strong>ClrStackWalk</strong> event containing the call stack will be immediately received. In fact, since an application will probably be using more than one thread, it is required to do the mapping between the two events on a per-thread basis.</p>
<p>Each allocation event received by the <code>OnSampleObjectAllocation</code> handler contains the <code>ThreadID</code> property so it is easy to keep track of the last received allocation event per thread. In my case, the <code>ProcessAllocations</code> class stores this information in its <code>_perThreadLastAllocation</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></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">ProcessAllocations</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">string</span><span class="p">,</span> <span class="n">AllocationInfo</span><span class="p">&gt;</span> <span class="n">_allocations</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">Dictionary</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">,</span> <span class="n">AllocationInfo</span><span class="p">&gt;</span> <span class="n">_perThreadLastAllocation</span><span class="p">;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Now, each time a <strong>SampleObjectAllocation</strong> event is received, the id of the sending thread is passed to the updated<code>ProcessAllocations.AddAllocation()</code> method:</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="n">AllocationInfo</span> <span class="n">AddAllocation</span><span class="p">(</span><span class="kt">int</span> <span class="n">pid</span><span class="p">,</span> <span class="kt">ulong</span> <span class="n">size</span><span class="p">,</span> <span class="kt">ulong</span> <span class="n">count</span><span class="p">,</span> <span class="kt">string</span> <span class="n">typeName</span><span class="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">_allocations</span><span class="p">.</span><span class="n">TryGetValue</span><span class="p">(</span><span class="n">typeName</span><span class="p">,</span> <span class="k">out</span> <span class="kt">var</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="k">new</span> <span class="n">AllocationInfo</span><span class="p">(</span><span class="n">typeName</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">_allocations</span><span class="p">[</span><span class="n">typeName</span><span class="p">]</span> <span class="p">=</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></span><span class="line"><span class="cl">    <span class="n">info</span><span class="p">.</span><span class="n">AddAllocation</span><span class="p">(</span><span class="n">size</span><span class="p">,</span> <span class="n">count</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 last allocation is still here without the corresponding stack</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">_perThreadLastAllocation</span><span class="p">.</span><span class="n">TryGetValue</span><span class="p">(</span><span class="n">pid</span><span class="p">,</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">lastAlloc</span><span class="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;no stack for the last allocation&#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">// keep track of the allocation for the given thread</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// --&gt; will be used when the corresponding call stack event will be received</span>
</span></span><span class="line"><span class="cl">    <span class="n">_perThreadLastAllocation</span><span class="p">[</span><span class="n">pid</span><span class="p">]</span> <span class="p">=</span> <span class="n">info</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">info</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>_perThreadLastAllocation</code> dictionary stores the <code>AllocationInfo</code> per thread. If an allocation happens, it is added into the dictionary. When a <strong>ClrStackWalk</strong> event is received for a given thread, the stack will be associated with the last <code>AllocationInfo</code> and removed from the dictionary. If some events are missed (it never happens during my tests but who knows), error message could be logged.</p>
<p>The <code>ClrStackWalkTraceData</code> argument received by the <strong>ClrStackWalk</strong> listener has a <code>FrameCount</code> property that returns the number of frames in the call stack. In addition, its <code>InstructionPointer()</code> method takes a frame position in the stack (starting at 0) and returns the address (in assembly code) at this position on the call 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></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">OnClrStackWalk</span><span class="p">(</span><span class="n">ClrStackWalkTraceData</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="kt">var</span> <span class="n">callstack</span> <span class="p">=</span> <span class="n">BuildCallStack</span><span class="p">(</span><span class="n">data</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">GetProcessAllocations</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">AddStack</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">ThreadID</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 class="kd">private</span> <span class="n">AddressStack</span> <span class="n">BuildCallStack</span><span class="p">(</span><span class="n">ClrStackWalkTraceData</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="kt">var</span> <span class="n">length</span> <span class="p">=</span> <span class="n">data</span><span class="p">.</span><span class="n">FrameCount</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">AddressStack</span> <span class="n">stack</span> <span class="p">=</span> <span class="k">new</span> <span class="n">AddressStack</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">// frame 0 is the last frame of the stack (i.e. last called method)</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">length</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">stack</span><span class="p">.</span><span class="n">AddFrame</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">InstructionPointer</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">stack</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>AddressStack</code> class returned by <code>BuildCallStack</code> stores the frames as a list of addresses so it can be stored in <code>AllocationInfo</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><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">public</span> <span class="k">class</span> <span class="nc">AddressStack</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 first frame is the address of the last called method </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="kt">ulong</span><span class="p">&gt;</span> <span class="n">_stack</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">AddressStack</span><span class="p">(</span><span class="kt">int</span> <span class="n">capacity</span><span class="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">_stack</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="kt">ulong</span><span class="p">&gt;(</span><span class="n">capacity</span><span 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 override GetHashCode because we don&#39;t want to use it as a key in a dictionary</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">override</span> <span class="kt">bool</span> <span class="n">Equals</span><span class="p">(</span><span class="kt">object</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">if</span> <span class="p">(</span><span class="n">obj</span> <span class="p">==</span> <span class="kc">null</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="kt">var</span> <span class="n">stack</span> <span class="p">=</span> <span class="n">obj</span> <span class="k">as</span> <span class="n">AddressStack</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">stack</span> <span class="p">==</span> <span class="kc">null</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="kt">var</span> <span class="n">frameCount</span> <span class="p">=</span> <span class="n">_stack</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">frameCount</span> <span class="p">!=</span> <span class="n">stack</span><span class="p">.</span><span class="n">_stack</span><span class="p">.</span><span class="n">Count</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="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">frameCount</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="k">if</span> <span class="p">(</span><span class="n">_stack</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="p">!=</span> <span class="n">stack</span><span class="p">.</span><span class="n">_stack</span><span class="p">[</span><span class="n">i</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 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">public</span> <span class="n">IReadOnlyList</span><span class="p">&lt;</span><span class="kt">ulong</span><span class="p">&gt;</span> <span class="n">Stack</span> <span class="p">=&gt;</span> <span class="n">_stack</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">AddFrame</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="n">_stack</span><span class="p">.</span><span class="n">Add</span><span class="p">(</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="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>This class overrides the <code>Equals</code> method for a single reason: I want to be able to detect when the “same” stack (i.e. with the exact same frame addresses) is received for a given type allocation. That way, I just need to keep a counter for each different <code>AddressStack</code> and not all call stacks in <code>AllocationInfo</code>. Remember that <code>AllocationInfo</code> is used to keep track of allocations per 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></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">AllocationInfo</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">_typeName</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">_size</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">_count</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="n">StackInfo</span><span class="p">&gt;</span> <span class="n">_stacks</span><span class="p">;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <code>StackInfo</code> class contains an <code>AddressStack</code> and how many times it led to this type of allocation.</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="k">class</span> <span class="nc">StackInfo</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">AddressStack</span> <span class="n">_stack</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">Count</span><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="n">StackInfo</span><span class="p">(</span><span class="n">AddressStack</span> <span class="n">stack</span><span class="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="m">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">_stack</span> <span class="p">=</span> <span class="n">stack</span><span 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">AddressStack</span> <span class="n">Stack</span> <span class="p">=&gt;</span> <span class="n">_stack</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, when a stack event is received, <code>AddStack</code> is called on the last <code>AllocationInfo</code> for the same 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></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="kt">int</span> <span class="n">tid</span><span class="p">,</span> <span class="n">AddressStack</span> <span class="n">stack</span><span class="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">_perThreadLastAllocation</span><span class="p">.</span><span class="n">TryGetValue</span><span class="p">(</span><span class="n">tid</span><span class="p">,</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">lastAlloc</span><span class="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">lastAlloc</span><span class="p">.</span><span class="n">AddStack</span><span class="p">(</span><span class="n">stack</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">_perThreadLastAllocation</span><span class="p">.</span><span class="n">Remove</span><span class="p">(</span><span class="n">tid</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="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The job of <code>AllocationInfo.AddStack()</code> the method is to check if a previous allocation was made with the same call stack (hence the <code>Equals</code> override). If this is the case, just increment the corresponding <code>StackInfo</code> count. Otherwise, create a new <code>StackInfo</code> for this call stack with a count set to 1.</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">AddStack</span><span class="p">(</span><span class="n">AddressStack</span> <span class="n">stack</span><span class="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">info</span> <span class="p">=</span> <span class="n">GetInfo</span><span class="p">(</span><span class="n">stack</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="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">info</span> <span class="p">=</span> <span class="k">new</span> <span class="n">StackInfo</span><span class="p">(</span><span class="n">stack</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">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></span><span class="line"><span class="cl">    <span class="n">info</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></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="n">StackInfo</span> <span class="n">GetInfo</span><span class="p">(</span><span class="n">AddressStack</span> <span class="n">stack</span><span class="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="n">_stacks</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">info</span> <span class="p">=</span> <span class="n">_stacks</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">if</span> <span class="p">(</span><span class="n">stack</span><span class="p">.</span><span class="n">Equals</span><span class="p">(</span><span class="n">info</span><span class="p">.</span><span class="n">Stack</span><span class="p">))</span> <span class="k">return</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></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>Knowing the address in code of each frame for all events call stack is nice but it would be much more useful to translate them into method names… You have to deal with two different cases: managed and native methods. I will cover these topics in the next episode.</p>
<h2 id="resources">Resources</h2>
<ul>
<li>Source code available <a href="https://github.com/chrisnas/ClrEvents">on Github</a>.</li>
<li>TraceEvent <a href="https://github.com/microsoft/perfview/blob/master/src/TraceEvent/Samples/41_TraceLogMonitor.cs#L204">sample 41</a> source code.</li>
</ul>
<hr>
<p>Missed the first part of this story? Check this out:</p>
<p><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>
<p><strong>Interested in joining our journey? Check this out:</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# — Allocations (1/2)</title><link>https://chrisnas.github.io/posts/2020-04-18_build-your-own-net/</link><pubDate>Sat, 18 Apr 2020 09:48:53 +0000</pubDate><guid>https://chrisnas.github.io/posts/2020-04-18_build-your-own-net/</guid><description>This post explains how to collect allocation details by writing your own memory profiler in C#.</description><content:encoded><![CDATA[<hr>
<p>In a <a href="/posts/2019-05-28_spying-on-net-garbage/">previous post</a>, I explained how to get statistics about the .NET Garbage Collector such as suspension time or generation sizes. But what if you would need more details about your application allocations such as how many times instances of a given type were allocated and for what cumulated size or even the allocation rate? This post explains how to get access to such information by writing your own memory profiler. The next one will show how to collect each sampled allocation stack trace.</p>
<h2 id="introduction">Introduction</h2>
<p>I have already used commercial tools to get detailed information about allocated type instances in an application; Visual Studio Profiler, dotTrace, ANTS memory profiler, or Perfview to name a few. With these tools in mind, I started to look at the .NET profiler API documentation and it reminded me the first time I read about the .NET profiler API. It was in December 2001 in <a href="https://docs.microsoft.com/en-us/archive/msdn-magazine/2001/december/under-the-hood-the-net-profiling-api-and-the-dnprofiler-tool?WT.mc_id=DT-MVP-5003325">Matt Pietrek’s MSDN Magazine article</a> (I still have the paper version). When your application is starting, based on an environment variable, the .NET Framework (and now .NET Core) runtime is loading a profiler COM object that implements a specific <a href="https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/icorprofilercallback9-interface?WT.mc_id=DT-MVP-5003325"><strong>ICorProfilerCallback</strong></a> interface (today, runtimes are supporting the 9th version <strong>ICorProfilerCallback9</strong> interface). The methods of this interface will be called by the runtime at specific moments during the application lifetime. For example, the <a href="https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/icorprofilercallback-objectallocated-method?WT.mc_id=DT-MVP-5003325"><strong>ObjectAllocated</strong></a> method is called each time an instance of a class is allocated: perfect for the job but it requires going back to COM and writing native code. Don’t be scared: I won’t go that way :^)</p>
<p><em>However, if you would like to get more details about writing your own .NET profiler in C or C++, I would recommend looking at the Microsoft ClrProfiler <em><a href="https://github.com/microsoftarchive/clrprofiler/tree/master/CLRProfiler"><em>initial .NET Framework implementation</em></a></em> and also Pavel Yosifovich DotNext session about <em><a href="https://www.youtube.com/watch?v=TqS4OEWn6hQ"><em>Writing a .NET Core cross platform profiler in an hour</em></a></em> with the corresponding (more recent and cross platform) <em><a href="https://github.com/zodiacon/DotNextMoscow2019"><em>source code</em></a></em>.</em></p>
<p>Instead, several events that are emitted by the CLR are providing interesting details:</p>
<p><img loading="lazy" src="/posts/2020-04-18_build-your-own-net/1_8RzRelU9Rgux0TJRdFhzzw.png"></p>
<p><img loading="lazy" src="/posts/2020-04-18_build-your-own-net/1_-DSm_89dq8yj8jI1aZ3ZYg.png"></p>
<p>The <strong>GCSampledObjectAllocation</strong> events payload provides a type ID instead of a plain text type name. In order to retrieve the type name given its ID, we need to listen to <strong>TypeBulkType</strong> event that contains the mapping as I described in <a href="/posts/2018-09-28_monitor-finalizers-contention-threads/">my post about finalizers</a>. This is why the last two <strong>GCHeapAndTypeNames</strong> and <strong>Type</strong> keywords are needed.</p>
<p>Remember that if both <strong>GCSampledObjectAllocationLow</strong> and <strong>GCSampledObjectAllocationHigh</strong> keywords are set, an event will be received for EACH allocation. This could be a performance issue both for the monitored application and the profiler. I would recommend starting with either low or high (more on this later).</p>
<p>Last but not least, enabling at least one of these keywords is also <a href="https://github.com/dotnet/runtime/blob/fcd862e06413a000f9cafa9d2f359226c60b9b42/src/coreclr/src/vm/jitinterfacegen.cpp#L69">switching the CLR to use “slower” allocators</a>. This is why you should check that it does not impact your application performance. These slower allocators are also used when your <strong>ICorProfilerCallback.Initialize</strong> method calls <strong>SetEventMask</strong> with <strong>COR_PRF_ENABLE_OBJECT_ALLOCATED</strong> flag to receive allocation notifications.</p>
<p>When you use <a href="https://github.com/Microsoft/perfview/releases">Perfview</a> for memory investigation, you are relying on these events without knowing it. In the Collect/Run dialog, three checkboxes are defining how to get the memory profiling details:</p>
<p><img loading="lazy" src="/posts/2020-04-18_build-your-own-net/1_8kuywni3W8PVleqg5NwWPw.png"></p>
<ul>
<li><em>.NET Alloc</em>: use a custom native C++ <strong>ICorProfilerCallback</strong> implementation (noticeable impact on the profiled application performance).</li>
<li><em>.NET SampAlloc</em>: use the same custom native profiler but with sampled events.</li>
<li><em>ETW .NET Alloc</em>: use <strong>GCSampledObjectAllocationHigh</strong> events</li>
</ul>
<p>In all cases, the profiled application needs to be started after the collection begins.</p>
<h2 id="how-to-listen-to-allocation-events">How to listen to allocation events</h2>
<p>As I have already explained in previous posts, the Microsoft <a href="https://www.nuget.org/packages/Microsoft.Diagnostics.Tracing.TraceEvent/"><strong>TraceEvent</strong> nuget</a> helps you listening to CLR events. First, you create a <strong>TraceEventSession</strong> and setup the providers you want to receive events from:</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="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 class="c1">// this is needed in order to receive AllocationTick_V2 event</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></span><span class="line"><span class="cl">    <span class="c1">// required to receive AllocationTick 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">GC</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 CLR source code indicates that the provider must be set before the monitored application starts</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">GCSampledObjectAllocationLow</span> <span class="p">|</span> 
</span></span><span class="line"><span class="cl">    <span class="c1">//ClrTraceEventParser.Keywords.GCSampledObjectAllocationHigh | </span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// required to receive the BulkType events that allows </span>
</span></span><span class="line"><span class="cl">    <span class="c1">// mapping between the type ID received in the allocation 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">GCHeapAndTypeNames</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">Type</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>Second, you set up the handlers for the events 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><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">SetupListeners</span><span class="p">(</span><span class="n">TraceLogEventSource</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="n">source</span><span class="p">.</span><span class="n">Clr</span><span class="p">.</span><span class="n">GCAllocationTick</span> <span class="p">+=</span> <span class="n">OnAllocationTick</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</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">GCSampledObjectAllocation</span> <span class="p">+=</span> <span class="n">OnSampleObjectAllocation</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// required to receive the mapping between type ID (received in GCSampledObjectAllocation)</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// and their name (received in TypeBulkType)</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">TypeBulkType</span> <span class="p">+=</span> <span class="n">OnTypeBulkType</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 lastly, the processing of received events is done in a dedicated thread until the session is disposed of:</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="k">await</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">_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">SetupProviders</span><span class="p">(</span><span class="n">_session</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">SetupListeners</span><span class="p">(</span><span class="n">_session</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="n">_session</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="p">});</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Now let’s see the difference between the two sets of events.</p>
<h2 id="the-allocationtick-way">The AllocationTick way</h2>
<p>My first idea was to use the <strong>AllocationTick</strong> event because it seemed easy: one sampled event with a size, a type name, and LOH/ephemeral kind. However, how this sampling works makes it impossible to get an exact per type allocated size. Let’s have a look at this list of events received from a WPF test application:</p>
<pre tabindex="0"><code>Small | 105444 : FreezableContextPair[]
Small | 111908 : FreezableContextPair[]
Small | 106720 : System.String
Small | 102488 : System.String
Small | 107028 : System.TimeSpan[]
Small | 106100 : System.String
</code></pre><p>All allocations were s<strong>mall</strong> (i.e. not in the LOH: &lt; 85.000 bytes) and the second column gives the cumulated size of all allocations to reach the 100 KB threshold but not for this particular type! There is no easy way to make a valid guess of the specific last allocation size for which we get the type name.</p>
<p>For example, the first array of <strong>FreezableContextPair</strong> triggered the event for a cumulated size of 105.444 bytes. But how big was this array? We don’t know: could have been 100.000 because only 5444 bytes were allocated before or only 10444 bytes because 95.000 were allocated before. It would have been so useful that the size of the last allocated object would be passed in the event payload…</p>
<p>It is a little bit different (but not that better) for objects allocated in LOH because they have to be at least 85.000 bytes long. For example, allocate 4-byte arrays, each one 85.000 bytes long and let’s see the corresponding events:</p>
<pre tabindex="0"><code>Large | 170064 : System.Byte[]
Large | 170064 : System.Byte[]
</code></pre><p>Two <strong>AllocationTick</strong> events are received with 170064 as cumulated size. Still hard to figure out what was the size of the last allocated array: the only thing we know is that it was larger (or equal) to 85.000 bytes because it was allocated in LOH.</p>
<p>For larger objects, it might seem a little bit more accurate. Let’s allocate 2 byte arrays, each one 110.000 bytes long:</p>
<pre tabindex="0"><code>Large | 195064 : System.Byte[]
Large | 110032 : System.Byte[]
</code></pre><p>There are ~85.000 bytes difference between the two events even though the same 110.000 bytes were allocated. You could remove 85.000 bytes from the value and have an approximation of the LOH allocated object: the larger the allocation the less the error. But still: could be 85.000 size error…</p>
<p>So we won’t be able to rely on the size provided by the <strong>AllocationTick</strong> event; only the type name. In addition, you get a view of objects allocated in LOH. Maybe the other events will provide better results.</p>
<h2 id="the-gcsampledobjectallocation-way">The GCSampledObjectAllocation way</h2>
<p>When an object is allocated by the GC allocator, a <strong>GCSampledObjectAllocation</strong> event is emitted under certain conditions:</p>
<ul>
<li>Both <strong>GCSampledObjectAllocationLow</strong> and <strong>GCSampledObjectAllocationHigh</strong> keywords are set on the CLR provider,</li>
<li>The object size is larger than 10.000 bytes,</li>
<li>At least 1000 instances of the type have been allocated,</li>
<li>Just before the application exits, current statistics for all types <a href="https://github.com/dotnet/runtime/blob/61ec7c7bdacb70ffd51dece09e30179f86156a0d/src/coreclr/src/vm/eventtrace.cpp#L3668">are flushed</a>,</li>
<li>A <a href="https://github.com/dotnet/runtime/blob/61ec7c7bdacb70ffd51dece09e30179f86156a0d/src/coreclr/src/vm/eventtrace.cpp#L3067">complicated piece of code</a> decides based on time since the last event and the type allocation rate.</li>
</ul>
<p>Picking one or the other keyword <a href="https://github.com/dotnet/runtime/blob/61ec7c7bdacb70ffd51dece09e30179f86156a0d/src/coreclr/src/vm/eventtrace.cpp#L2902">changes the maximum number of milliseconds between two events</a> for a given type:</p>
<ul>
<li>High (10 ms) : 100 events / second</li>
<li>Low (200 ms) : 5 events / second</li>
</ul>
<p>You should use low or high depending on the monitored application memory allocation workload to avoid impacting too much the profiler (and even the monitored application performance)</p>
<p>The interesting feature of these events is that, for a given type, the payload contains both the number of allocated instances since the last event and the cumulated size of these instances. Let’s take the same allocation of 4 arrays of byte, each 85000 long:</p>
<pre tabindex="0"><code>226 | 103616 : System.Byte[]
  1 |  85012 : System.Byte[]
  1 |  85012 : System.Byte[]
  1 |  85012 : System.Byte[]
</code></pre><p>This time, we get the exact count in the first column (<strong>ObjectCountForTypeSample</strong>) and the exact cumulated size in the second column (<strong>TotalSizeForTypeSample</strong>). If the count is 1, we have the exact size of that allocation and if it is bigger than 85000 bytes, we know it has been allocated in the LOH. Same accuracy for the 2-byte array of 110.000 elements:</p>
<pre tabindex="0"><code>198 | 123552 : System.Byte[]
  1 | 110012 : System.Byte[]
</code></pre><p>Sounds good. However, you have to remember that profiled applications need to be started after the session was created: it means that you can’t write a tool that will listen to a specific process ID like with <strong>AllocationTick</strong>. Three dictionaries are used by <strong>PerProcessProfilingState</strong> to keep track of per type allocations, type ID mappings, and process names:</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></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="kt">string</span><span class="p">&gt;</span> <span class="n">_processNames</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="kt">string</span><span class="p">&gt;();</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">ProcessTypeMapping</span><span class="p">&gt;</span> <span class="n">_perProcessTypes</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">ProcessTypeMapping</span><span class="p">&gt;();</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">ProcessAllocationInfo</span><span class="p">&gt;</span> <span class="n">_perProcessAllocations</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">ProcessAllocationInfo</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="n">Dictionary</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">,</span> <span class="kt">string</span><span class="p">&gt;</span> <span class="n">Names</span> <span class="p">=&gt;</span> <span class="n">_processNames</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">,</span> <span class="n">ProcessTypeMapping</span><span class="p">&gt;</span> <span class="n">Types</span> <span class="p">=&gt;</span> <span class="n">_perProcessTypes</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">,</span> <span class="n">ProcessAllocationInfo</span><span class="p">&gt;</span> <span class="n">Allocations</span> <span class="p">=&gt;</span> <span class="n">_perProcessAllocations</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>SampledObjectAllocationMemoryProfiler</strong> class uses it for the events processing:</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">SampledObjectAllocationMemoryProfiler</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">TraceEventSession</span> <span class="n">_session</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">PerProcessProfilingState</span> <span class="n">_processes</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1">// because we are not interested in self monitoring</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">_currentPid</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="kt">int</span> <span class="n">_started</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">SampledObjectAllocationMemoryProfiler</span><span class="p">(</span><span class="n">TraceEventSession</span> <span class="n">session</span><span class="p">,</span> <span class="n">PerProcessProfilingState</span> <span class="n">processes</span><span class="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">session</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">_processes</span> <span class="p">=</span> <span class="n">processes</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">_currentPid</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="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The constructor of the profiler keeps track of its own process ID in <strong>_currentPid</strong> to skip its own events.</p>
<h2 id="gathering-typemapping">Gathering type mapping</h2>
<p>The processing of <strong>TypeBulkType</strong> events is quite straightforward: store the type ID/name association into a per-process dictionary:</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">private</span> <span class="k">void</span> <span class="n">OnTypeBulkType</span><span class="p">(</span><span class="n">GCBulkTypeTraceData</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">ProcessTypeMapping</span> <span class="n">mapping</span> <span class="p">=</span> <span class="n">GetProcessTypesMapping</span><span class="p">(</span><span class="n">data</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">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">currentType</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">currentType</span> <span class="p">&lt;</span> <span class="n">data</span><span class="p">.</span><span class="n">Count</span><span class="p">;</span> <span class="n">currentType</span><span class="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">GCBulkTypeValues</span> <span class="k">value</span> <span class="p">=</span> <span class="n">data</span><span class="p">.</span><span class="n">Values</span><span class="p">(</span><span class="n">currentType</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">mapping</span><span class="p">[</span><span class="k">value</span><span class="p">.</span><span class="n">TypeID</span><span class="p">]</span> <span class="p">=</span> <span class="k">value</span><span class="p">.</span><span class="n">TypeName</span><span class="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="n">ProcessTypeMapping</span> <span class="n">GetProcessTypesMapping</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">ProcessTypeMapping</span> <span class="n">mapping</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">_processes</span><span class="p">.</span><span class="n">Types</span><span class="p">.</span><span class="n">TryGetValue</span><span class="p">(</span><span class="n">pid</span><span class="p">,</span> <span class="k">out</span> <span class="n">mapping</span><span class="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">AssociateProcess</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="n">mapping</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ProcessTypeMapping</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">_processes</span><span class="p">.</span><span class="n">Types</span><span class="p">[</span><span class="n">pid</span><span class="p">]</span> <span class="p">=</span> <span class="n">mapping</span><span class="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">mapping</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>Remember that I choose to skip events from the current process detected by <strong>FilterOutEvent()</strong>.</p>
<h2 id="how-to-get-processnames">How to get process names</h2>
<p>Even though each event contains the ID of the emitting process, it would be better to display its name instead. You could use <strong>Process.GetProcessById(pid).ProcessName</strong> when analyzing the details but the process might be long gone at that time.</p>
<p>Another solution would be to enable the Kernel ETW provider and listen to the <strong>ProcessStart</strong> event. The <strong>ImageFileName</strong> field of the payload contains the process filename with the extension. However, it is obviously not working on Linux.</p>
<p>The easiest solution is to use <strong>GetProcessById</strong> but just when you receive the first type mapping for a given process. This is the role of the <strong>AssociateProcess</strong> method called in <strong>GetProcessTypesMapping</strong> shown previously:</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">AssociateProcess</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">_processes</span><span class="p">.</span><span class="n">Names</span><span class="p">[</span><span class="n">pid</span><span class="p">]</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 class="n">ProcessName</span><span class="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="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}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// we might not have access to the process</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>It is now time to process allocation events.</p>
<h2 id="collecting-allocation-details">Collecting allocation details</h2>
<p>The <strong>GCSampledObjectAllocationTraceData</strong> payload contains the size and count of instances since the last event. We just need to store them for the corresponding 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></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">OnSampleObjectAllocation</span><span class="p">(</span><span class="n">GCSampledObjectAllocationTraceData</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">GetProcessAllocations</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">ProcessID</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="n">AddAllocation</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 class="n">data</span><span class="p">.</span><span class="n">TotalSizeForTypeSample</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 class="n">data</span><span class="p">.</span><span class="n">ObjectCountForTypeSample</span><span class="p">,</span> 
</span></span><span class="line"><span class="cl">            <span class="n">GetProcessTypeName</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">TypeID</span><span class="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="kd">private</span> <span class="kt">string</span> <span class="n">GetProcessTypeName</span><span class="p">(</span><span class="kt">int</span> <span class="n">pid</span><span class="p">,</span> <span class="kt">ulong</span> <span class="n">typeID</span><span class="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">_processes</span><span class="p">.</span><span class="n">Types</span><span class="p">.</span><span class="n">TryGetValue</span><span class="p">(</span><span class="n">pid</span><span class="p">,</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">mapping</span><span class="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">typeID</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="kt">var</span> <span class="n">name</span> <span class="p">=</span> <span class="n">mapping</span><span class="p">[</span><span class="n">typeID</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kt">string</span><span class="p">.</span><span class="n">IsNullOrEmpty</span><span class="p">(</span><span class="n">name</span><span class="p">)</span> <span class="p">?</span> <span class="n">typeID</span><span class="p">.</span><span class="n">ToString</span><span class="p">()</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></code></pre></td></tr></table>
</div>
</div><p>The <strong>AddAllocation()</strong> helper method is simply accumulating these numbers for a given type in the <strong>ProcessAllocationInfo</strong> associated to the related process.</p>
<h2 id="displaying-theresults">Displaying the results</h2>
<p>When the profiling session ends, it is easy to show the allocated count and size per type:</p>
<p><img loading="lazy" src="/posts/2020-04-18_build-your-own-net/1_1zQgewOknfy2R_SHJfGkKQ.png"></p>
<p>The code is using a Linq syntax to get top allocations sorted either by count or by 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></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">ShowResults</span><span class="p">(</span><span class="kt">string</span> <span class="n">name</span><span class="p">,</span> <span class="n">ProcessAllocationInfo</span> <span class="n">allocations</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">sortBySize</span><span class="p">,</span> <span class="kt">int</span> <span class="n">topTypesLimit</span><span class="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;Memory allocations for {name}&#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></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">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&#34;    Count        Size   Type&#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">IEnumerable</span><span class="p">&lt;</span><span class="n">AllocationInfo</span><span class="p">&gt;</span> <span class="n">types</span> <span class="p">=</span> <span class="p">(</span><span class="n">sortBySize</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">?</span> <span class="n">allocations</span><span class="p">.</span><span class="n">GetAllocations</span><span class="p">().</span><span class="n">OrderByDescending</span><span class="p">(</span><span class="n">a</span> <span class="p">=&gt;</span> <span class="n">a</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 class="n">allocations</span><span class="p">.</span><span class="n">GetAllocations</span><span class="p">().</span><span class="n">OrderByDescending</span><span class="p">(</span><span class="n">a</span> <span class="p">=&gt;</span> <span class="n">a</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="k">if</span> <span class="p">(</span><span class="n">topTypesLimit</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">types</span> <span class="p">=</span> <span class="n">types</span><span class="p">.</span><span class="n">Take</span><span class="p">(</span><span class="n">topTypesLimit</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</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">allocation</span> <span class="k">in</span> <span class="n">types</span><span class="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;{allocation.Count,9} {allocation.Size,11}   {allocation.TypeName}&#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>Another usage could be a long-running monitoring system that shows the allocation rate: a nice complement to the other GC metrics. However, compared to the other profilers, one important feature is missing: if an unexpected number of instances are created, how to know which part of the code is responsible for the spike?</p>
<p>The next post will explain how to enhance such a sampled memory profiler with call stacks per sampled allocation.</p>
<hr>
<h2 id="resources">Resources</h2>
<ul>
<li>Source code available <a href="https://github.com/chrisnas/ClrEvents">on Github</a>.</li>
<li><a href="/posts/2018-12-15_spying-on-net-garbage/">Spying on .NET Garbage Collector with TraceEvent</a></li>
<li>Pavel Yosifovich — <a href="https://www.youtube.com/watch?v=TqS4OEWn6hQ">Writing a .NET Core cross-platform profiler in an hour</a></li>
<li><a href="https://github.com/microsoftarchive/clrprofiler/tree/master/CLRProfiler">Original Microsoft ClrProfiler source code and documentation</a></li>
</ul>
<hr>
<p><strong>Like what you read? Don’t forget to check out part 2 on this topic:</strong></p>
<p><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>Interested in joining our journey? Check this out:</strong></p>
<p><a href="https://careers.criteo.com/working-in-R&amp;D"><strong>Product, Research &amp; Development | Criteo Careers</strong>
*Product, Research &amp; Development at Criteo. At Criteo, come and meet our teams and join our R &amp; D and also enjoy…*careers.criteo.com</a><a href="https://careers.criteo.com/working-in-R&amp;D"></a></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>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>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></channel></rss>