{"id":5698,"date":"2020-01-06T15:00:15","date_gmt":"2020-01-06T15:00:15","guid":{"rendered":"http:\/\/writeasync.net\/?p=5698"},"modified":"2020-01-04T18:10:30","modified_gmt":"2020-01-04T18:10:30","slug":"eventsource-and-span-take-1","status":"publish","type":"post","link":"https:\/\/writeasync.net\/?p=5698","title":{"rendered":"EventSource and Span: take 1"},"content":{"rendered":"<p>Let&#8217;s say you&#8217;re living the zero-allocation, <a href=\"https:\/\/blogs.msdn.microsoft.com\/mazhou\/2018\/03\/25\/c-7-series-part-10-spant-and-universal-memory-management\/\">universal memory<\/a> .NET life, and you need to trace some <code>char<\/code> data. The only problem is that you have a <a href=\"https:\/\/adamsitnik.com\/Span\/\">Span&lt;char&gt;<\/a> and EventSource only supports <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.string\">string<\/a>. What to do?<\/p>\n<p>We&#8217;ll start with a typical EventSource implementation using <code>string<\/code>:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n    &#x5B;EventSource(Guid = &quot;c451aca3-50ae-4955-9723-38b9ae7d98c0&quot;)]\r\n    public sealed class MyEventSource : EventSource\r\n    {\r\n        public static readonly MyEventSource Instance = new MyEventSource();\r\n\r\n        private MyEventSource()\r\n        {\r\n        }\r\n\r\n        &#x5B;Event(1, Message = &quot;Hello {0} {1} {2}&quot;)]\r\n        public void Hello(int n1, int n2, string s1)\r\n        {\r\n            this.WriteEvent(1, n1, n2, s1);\r\n        }\r\n    }\r\n<\/pre>\n<p>This generates a manifest like the following:<\/p>\n<pre class=\"brush: xml; title: ; notranslate\" title=\"\">\r\n&lt;instrumentationManifest xmlns=&quot;http:\/\/schemas.microsoft.com\/win\/2004\/08\/events&quot;&gt;\r\n &lt;instrumentation xmlns:xs=&quot;http:\/\/www.w3.org\/2001\/XMLSchema&quot; xmlns:xsi=&quot;http:\/\/www.w3.org\/2001\/XMLSchema-instance&quot; xmlns:win=&quot;http:\/\/manifests.microsoft.com\/win\/2004\/08\/windows\/events&quot;&gt;\r\n  &lt;events xmlns=&quot;http:\/\/schemas.microsoft.com\/win\/2004\/08\/events&quot;&gt;\r\n&lt;provider name=&quot;MyEventSource&quot; guid=&quot;{c451aca3-50ae-4955-9723-38b9ae7d98c0}&quot; resourceFileName=&quot;x.dll&quot; messageFileName=&quot;x.dll&quot; symbol=&quot;MyEventSource&quot;&gt;\r\n &lt;tasks&gt;\r\n  &lt;task name=&quot;Hello&quot; message=&quot;$(string.task_Hello)&quot; value=&quot;65533&quot;\/&gt;\r\n  &lt;task name=&quot;EventSourceMessage&quot; message=&quot;$(string.task_EventSourceMessage)&quot; value=&quot;65534&quot;\/&gt;\r\n &lt;\/tasks&gt;\r\n &lt;opcodes&gt;\r\n &lt;\/opcodes&gt;\r\n &lt;keywords&gt;\r\n  &lt;keyword name=&quot;Session3&quot; message=&quot;$(string.keyword_Session3)&quot; mask=&quot;0x100000000000&quot;\/&gt;\r\n  &lt;keyword name=&quot;Session2&quot; message=&quot;$(string.keyword_Session2)&quot; mask=&quot;0x200000000000&quot;\/&gt;\r\n  &lt;keyword name=&quot;Session1&quot; message=&quot;$(string.keyword_Session1)&quot; mask=&quot;0x400000000000&quot;\/&gt;\r\n  &lt;keyword name=&quot;Session0&quot; message=&quot;$(string.keyword_Session0)&quot; mask=&quot;0x800000000000&quot;\/&gt;\r\n &lt;\/keywords&gt;\r\n &lt;events&gt;\r\n  &lt;event value=&quot;0&quot; version=&quot;0&quot; level=&quot;win:LogAlways&quot; symbol=&quot;EventSourceMessage&quot; task=&quot;EventSourceMessage&quot; template=&quot;EventSourceMessageArgs&quot;\/&gt;\r\n  &lt;event value=&quot;1&quot; version=&quot;0&quot; level=&quot;win:Informational&quot; symbol=&quot;Hello&quot; message=&quot;$(string.event_Hello)&quot; task=&quot;Hello&quot; template=&quot;HelloArgs&quot;\/&gt;\r\n &lt;\/events&gt;\r\n &lt;templates&gt;\r\n  &lt;template tid=&quot;EventSourceMessageArgs&quot;&gt;\r\n   &lt;data name=&quot;message&quot; inType=&quot;win:UnicodeString&quot;\/&gt;\r\n  &lt;\/template&gt;\r\n  &lt;template tid=&quot;HelloArgs&quot;&gt;\r\n   &lt;data name=&quot;n1&quot; inType=&quot;win:Int32&quot;\/&gt;\r\n   &lt;data name=&quot;n2&quot; inType=&quot;win:Int32&quot;\/&gt;\r\n   &lt;data name=&quot;s1&quot; inType=&quot;win:UnicodeString&quot;\/&gt;\r\n  &lt;\/template&gt;\r\n &lt;\/templates&gt;\r\n&lt;\/provider&gt;\r\n&lt;\/events&gt;\r\n&lt;\/instrumentation&gt;\r\n&lt;localization&gt;\r\n &lt;resources culture=&quot;en-US&quot;&gt;\r\n  &lt;stringTable&gt;\r\n   &lt;string id=&quot;event_Hello&quot; value=&quot;Hello %1 %2 %3&quot;\/&gt;\r\n   &lt;string id=&quot;keyword_Session0&quot; value=&quot;Session0&quot;\/&gt;\r\n   &lt;string id=&quot;keyword_Session1&quot; value=&quot;Session1&quot;\/&gt;\r\n   &lt;string id=&quot;keyword_Session2&quot; value=&quot;Session2&quot;\/&gt;\r\n   &lt;string id=&quot;keyword_Session3&quot; value=&quot;Session3&quot;\/&gt;\r\n   &lt;string id=&quot;task_EventSourceMessage&quot; value=&quot;EventSourceMessage&quot;\/&gt;\r\n   &lt;string id=&quot;task_Hello&quot; value=&quot;Hello&quot;\/&gt;\r\n  &lt;\/stringTable&gt;\r\n &lt;\/resources&gt;\r\n&lt;\/localization&gt;\r\n&lt;\/instrumentationManifest&gt;\r\n<\/pre>\n<p>Finally, a complete self-contained sample to trace and collect the EventSource data:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n    internal sealed class Program\r\n    {\r\n        private static void Main()\r\n        {\r\n            const string TraceName = &quot;SampleTrace&quot;;\r\n            const string TracePath = &quot;SampleTrace.etl&quot;;\r\n            const string ManifestPath = &quot;SampleTrace.man&quot;;\r\n            string providerId = EventSource.GetGuid(typeof(MyEventSource)).ToString(&quot;b&quot;);\r\n            File.WriteAllText(ManifestPath, EventSource.GenerateManifest(typeof(MyEventSource), &quot;x.dll&quot;));\r\n            Logman(&quot;create trace&quot;, TraceName, &quot;-ow -o&quot;, TracePath, &quot;-p&quot;, providerId, &quot;0xFFFFFFFFFFFFFFFF 5&quot;);\r\n            Logman(&quot;start&quot;, TraceName);\r\n            \r\n            MyEventSource.Instance.Hello(1, 2, &quot;three&quot;);\r\n            \r\n            Logman(&quot;stop&quot;, TraceName);\r\n            Logman(&quot;delete&quot;, TraceName);\r\n            FileInfo traceFile = new DirectoryInfo(&quot;.&quot;).EnumerateFiles(&quot;SampleTrace*.etl&quot;).Last();\r\n            Exec(&quot;tracerpt.exe&quot;, &quot;-y -l&quot;, traceFile.FullName, &quot;-import&quot;, ManifestPath);\r\n        }\r\n\r\n        private static void Logman(params string&#x5B;] args) =&gt; Exec(&quot;logman.exe&quot;, args);\r\n\r\n        private static void Exec(string fileName, params string&#x5B;] args)\r\n        {\r\n            using Process process = Process.Start(fileName, string.Join(' ', args));\r\n            process.WaitForExit();\r\n            if (process.ExitCode != 0)\r\n            {\r\n                throw new InvalidOperationException($&quot;{fileName} failed with code {process.ExitCode}.&quot;);\r\n            }\r\n        }\r\n    }\r\n<\/pre>\n<p>We&#8217;re using logman.exe to create the trace collector and <a href=\"http:\/\/writeasync.net\/?p=1371\">tracerpt.exe to decode the ETL data<\/a>. The resulting dumpfile.xml shows the expected event data:<\/p>\n<pre class=\"brush: xml; title: ; notranslate\" title=\"\">\r\n\t&lt;EventData&gt;\r\n\t\t&lt;Data Name=&quot;n1&quot;&gt;1&lt;\/Data&gt;\r\n\t\t&lt;Data Name=&quot;n2&quot;&gt;2&lt;\/Data&gt;\r\n\t\t&lt;Data Name=&quot;s1&quot;&gt;three&lt;\/Data&gt;\r\n\t&lt;\/EventData&gt;\r\n<\/pre>\n<p>Obviously, this EventSource does not meet our requirements. It uses a string and also the <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.diagnostics.tracing.eventsource.writeevent?view=netframework-4.8#System_Diagnostics_Tracing_EventSource_WriteEvent_System_Int32_System_Object___\">slow WriteEvent overload<\/a>. Let&#8217;s fix the latter problem:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n    &#x5B;EventSource(Guid = &quot;c451aca3-50ae-4955-9723-38b9ae7d98c0&quot;)]\r\n    public sealed class MyEventSource : EventSource\r\n    {\r\n        public static readonly MyEventSource Instance = new MyEventSource();\r\n\r\n        private MyEventSource()\r\n        {\r\n        }\r\n\r\n        &#x5B;Event(1, Message = &quot;Hello {0} {1} {2}&quot;)]\r\n        public unsafe void Hello(int n1, int n2, string s1)\r\n        {\r\n            if (this.IsEnabled())\r\n            {\r\n                s1 ??= string.Empty;\r\n                fixed (char* _s1 = s1)\r\n                {\r\n                    EventData* descrs = stackalloc EventData&#x5B;3];\r\n                    descrs&#x5B;0].DataPointer = (IntPtr)(&amp;n1);\r\n                    descrs&#x5B;0].Size = sizeof(int);\r\n                    descrs&#x5B;1].DataPointer = (IntPtr)(&amp;n2);\r\n                    descrs&#x5B;1].Size = sizeof(int);\r\n                    descrs&#x5B;2].DataPointer = (IntPtr)_s1;\r\n                    descrs&#x5B;2].Size = (s1.Length + 1) * 2;\r\n                    this.WriteEventWithRelatedActivityIdCore(1, null, 3, descrs);\r\n                }\r\n            }\r\n        }\r\n    }\r\n<\/pre>\n<p>It&#8217;s not pretty to look at but it has the same outcome as the simple version without the overhead. Still, we haven&#8217;t solved the Span problem. How can we get EventSource to accept a nonstandard event signature while still generating a proper manifest?<\/p>\n<p>It turns out that event methods do not have to be public, so we can write the undesirable string version as a private method. Then we can use <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.diagnostics.tracing.noneventattribute\">[NonEvent]<\/a> methods to implement the Span logic that we need:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n    &#x5B;EventSource(Guid = &quot;c451aca3-50ae-4955-9723-38b9ae7d98c0&quot;)]\r\n    public sealed class MyEventSource3 : EventSource\r\n    {\r\n        public static readonly MyEventSource3 Instance = new MyEventSource3();\r\n\r\n        private MyEventSource3()\r\n        {\r\n        }\r\n\r\n        &#x5B;NonEvent]\r\n        public void Hello(int n1, int n2, ReadOnlySpan&lt;char&gt; s1)\r\n        {\r\n            this.HelloCore(n1, n2, s1);\r\n        }\r\n\r\n        &#x5B;Event(1, Message = &quot;Hello {0} {1} {2}&quot;)]\r\n        private void Hello(int n1, int n2, string s1)\r\n        {\r\n            this.HelloCore(n1, n2, s1);\r\n        }\r\n\r\n        &#x5B;NonEvent]\r\n        private unsafe void HelloCore(int n1, int n2, ReadOnlySpan&lt;char&gt; s1)\r\n        {\r\n            if (this.IsEnabled())\r\n            {\r\n                fixed (char* _s1 = s1)\r\n                {\r\n                    EventData* descrs = stackalloc EventData&#x5B;3];\r\n                    descrs&#x5B;0].DataPointer = (IntPtr)(&amp;n1);\r\n                    descrs&#x5B;0].Size = sizeof(int);\r\n                    descrs&#x5B;1].DataPointer = (IntPtr)(&amp;n2);\r\n                    descrs&#x5B;1].Size = sizeof(int);\r\n                    descrs&#x5B;2].DataPointer = (IntPtr)_s1;\r\n                    descrs&#x5B;2].Size = (s1.Length + 1) * 2;\r\n                    this.WriteEventWithRelatedActivityIdCore(1, null, 3, descrs);\r\n                }\r\n            }\r\n        }\r\n    }\r\n<\/pre>\n<p>(Note that since string is implicitly convertible to Span&lt;char&gt;, we can call the &#8220;core&#8221; method in both situations.)<\/p>\n<p>We need one modification of the main program to demonstrate the Span case:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n            const string Str = &quot;threeeeeeeeeeee&quot;;\r\n            ReadOnlySpan&lt;char&gt; s = Str.AsSpan(0, 5);\r\n            MyEventSource.Instance.Hello(1, 2, s);\r\n<\/pre>\n<p>The generated manifest is identical, so that&#8217;s a win. Unfortunately, <strong>the data is not what we expected<\/strong>:<\/p>\n<pre class=\"brush: xml; title: ; notranslate\" title=\"\">\r\n\t&lt;EventData&gt;\r\n\t\t&lt;Data Name=&quot;n1&quot;&gt;1&lt;\/Data&gt;\r\n\t\t&lt;Data Name=&quot;n2&quot;&gt;2&lt;\/Data&gt;\r\n\t\t&lt;Data Name=&quot;s1&quot;&gt;threee&lt;\/Data&gt;\r\n\t&lt;\/EventData&gt;\r\n<\/pre>\n<p>We expected &#8220;three&#8221; but got &#8220;threee&#8221;. The problem is here:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n                    descrs&#x5B;2].Size = (s1.Length + 1) * 2;\r\n<\/pre>\n<p>ETW expects all strings to be null-terminated, but we are using a span which is a five character prefix from a larger string, no more and no less. What we have here is an <a href=\"https:\/\/stackoverflow.com\/questions\/15646973\/how-dangerous-is-it-to-access-an-array-out-of-bounds\">out of bounds error<\/a> that is not reported by the runtime. When we said <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/csharp\/language-reference\/keywords\/unsafe\"><code>unsafe<\/code><\/a> we meant it!<\/p>\n<p>All we wanted was allocation-free string data tracing, but it would seem that we&#8217;re out of luck here with Span. Is there any way around this limitation? We&#8217;ll find out, when we pick up this investigation later&#8230;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Let&#8217;s say you&#8217;re living the zero-allocation, universal memory .NET life, and you need to trace some char data. The only problem is that you have a Span&lt;char&gt; and EventSource only supports string. What to do? We&#8217;ll start with a typical&hellip; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[71,104],"tags":[],"class_list":["post-5698","post","type-post","status-publish","format-standard","hentry","category-diagnostics","category-performance"],"_links":{"self":[{"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5698","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=5698"}],"version-history":[{"count":1,"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5698\/revisions"}],"predecessor-version":[{"id":5699,"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5698\/revisions\/5699"}],"wp:attachment":[{"href":"https:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=5698"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=5698"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=5698"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}