Add RundownEnabled field to EventPipe IPC protocol (#375)
authorSung Yoon Whang <suwhang@microsoft.com>
Tue, 16 Jul 2019 00:14:06 +0000 (17:14 -0700)
committerGitHub <noreply@github.com>
Tue, 16 Jul 2019 00:14:06 +0000 (17:14 -0700)
* Add requestRundown field to EventPipe IPC protocol

* doc change

* removing accidental .nettrace commit

* Add a new command with an option to specify rundown

* doc changes

* Make the tool compatible with previous versions of tools

* comment typo

* dont rename the existing struct to keep backward compat

* Make dotnet-counters backward compatible

* parse HR code runtime returns and throw/catch appropriate exception

* remove debug print

documentation/design-docs/ipc-protocol.md
src/Microsoft.Diagnostics.Tools.RuntimeClient/DiagnosticsIpc/IpcCommands.cs
src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/EventPipeClient.cs
src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/SessionConfiguration.cs
src/Tools/dotnet-counters/CounterMonitor.cs

index 9d627fd54ad444a78e8ea6700074c77b56d42b05..6d122f1648f840665f98f809e4ca465b104a77c3 100644 (file)
@@ -345,8 +345,9 @@ enum class ServerCommandId : uint8_t
 enum class EventPipeCommandId : uint8_t
 {
     // reserved = 0x00,
-    StopTracing    = 0x01, // stop a given session
-    CollectTracing = 0x02, // create/start a given session
+    StopTracing     = 0x01, // stop a given session
+    CollectTracing  = 0x02, // create/start a given session
+    CollectTracing2 = 0x03, // create/start a given session with/without rundown
 }
 ```
 See: [EventPipe Commands](#EventPipe-Commands)
@@ -381,8 +382,9 @@ For example, the Command to start a stream session with EventPipe would be `0x02
 enum class EventPipeCommandId : uint8_t
 {
     // reserved = 0x00,
-    StopTracing    = 0x01, // stop a given session
-    CollectTracing = 0x02, // create/start a given session
+    StopTracing     = 0x01, // stop a given session
+    CollectTracing  = 0x02, // create/start a given session
+    CollectTracing2 = 0x03, // create/start a given session with/without rundown
 }
 ```
 EventPipe Payloads are encoded with the following rules:
@@ -459,6 +461,66 @@ Payload
 ```
 Followed by an Optional Continuation of a `nettrace` format stream of events.
 
+### `CollectTracing2`
+
+Command Code: `0x0203`
+
+The `CollectTracing2` Command is an extension of the `CollectTracing` command - its behavior is the same as `CollectTracing` command, except that it has another field that lets you specify whether rundown events should be fired by the runtime.
+
+#### Inputs:
+
+Header: `{ Magic; Size; 0x0203; 0x0000 }`
+
+* `uint circularBufferMB`: The size of the circular buffer used for buffering event data while streaming
+* `uint format`: 0 for the legacy NetPerf format and 1 for the NetTrace format
+* `bool requestRundown`: Indicates whether rundown should be fired by the runtime.
+* `array<provider_config> providers`: The providers to turn on for the streaming session
+
+A `provider_config` is composed of the following data:
+* `ulong keywords`: The keywords to turn on with this providers
+* `uint logLevel`: The level of information to turn on
+* `string provider_name`: The name of the provider
+* `string filter_data` (optional): Filter information
+
+> see ETW documentation for a more detailed explanation of Keywords, Filters, and Log Level.
+> 
+#### Returns (as an IPC Message Payload):
+
+Header: `{ Magic; 28; 0xFF00; 0x0000; }`
+
+`CollectTracing2` returns:
+* `ulong sessionId`: the ID for the stream session starting on the current connection
+
+##### Details:
+
+Input:
+```
+Payload
+{
+    uint circularBufferMB,
+    uint format,
+    bool requestRundown,
+    array<provider_config> providers
+}
+
+provider_config 
+{
+    ulong keywords,
+    uint logLevel,
+    string provider_name,
+    string filter_data (optional)
+}
+```
+
+Returns:
+```c
+Payload
+{
+    ulong sessionId
+}
+```
+Followed by an Optional Continuation of a `nettrace` format stream of events.
+
 ### `StopTracing` 
 
 Command Code: `0x0201`
index eea08e478406906dfc6bb9bbc05ac2ee071e3fae..8be8f0a3baf24aa8f60a1e92b4cbe77bff72ed11 100644 (file)
@@ -25,8 +25,9 @@ namespace Microsoft.Diagnostics.Tools.RuntimeClient.DiagnosticsIpc
 
     public enum EventPipeCommandId : byte
     {
-        StopTracing    = 0x01,
-        CollectTracing = 0x02,
+        StopTracing     = 0x01,
+        CollectTracing  = 0x02,
+        CollectTracing2 = 0x03,
     }
 
     public enum DumpCommandId : byte
index be13fcae5799c0eba872132f3ad0b951cd6bbb58..523a5cf5953d49a0e39f80b2fdab8dbb693509b5 100644 (file)
@@ -17,6 +17,33 @@ using System.Text.RegularExpressions;
 
 namespace Microsoft.Diagnostics.Tools.RuntimeClient
 {
+    public enum EventPipeErrorCode : uint
+    {
+        BAD_ENCODING    = 0x80131384,
+        UNKNOWN_COMMAND = 0x80131385,
+        UNKNOWN_MAGIC   = 0x80131386,
+        UNKNOWN_ERROR   = 0x80131387
+    }
+
+    public class EventPipeBadEncodingException : Exception
+    {
+        public EventPipeBadEncodingException(string msg) : base(msg) {}
+    }
+    public class EventPipeUnknownCommandException : Exception
+    {
+        public EventPipeUnknownCommandException(string msg) : base(msg) {}
+    }
+
+    public class EventPipeUnknownMagicException : Exception
+    {
+        public EventPipeUnknownMagicException(string msg) : base(msg) {}
+    }
+
+    public class EventPipeUnknownErrorException : Exception
+    {
+        public EventPipeUnknownErrorException(string msg) : base(msg) {}
+    }
+
     public static class EventPipeClient
     {
         private static string DiagnosticsPortPattern { get; } = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? @"^dotnet-diagnostic-(\d+)$" : @"^dotnet-diagnostic-(\d+)-(\d+)-socket$";
@@ -67,6 +94,29 @@ namespace Microsoft.Diagnostics.Tools.RuntimeClient
             return stream;
         }
 
+        public static Stream CollectTracing2(int processId, SessionConfigurationV2 configuration, out ulong sessionId)
+        {
+            sessionId = 0;
+            var message = new IpcMessage(DiagnosticsServerCommandSet.EventPipe, (byte)EventPipeCommandId.CollectTracing2, configuration.Serialize());
+            var stream = IpcClient.SendMessage(processId, message, out var response);
+
+            switch ((DiagnosticsServerCommandId)response.Header.CommandId)
+            {
+                case DiagnosticsServerCommandId.OK:
+                    sessionId = BitConverter.ToUInt64(response.Payload);
+                    break;
+                case DiagnosticsServerCommandId.Error:
+                    // bad...
+                    uint hr = BitConverter.ToUInt32(response.Payload);
+                    Exception ex = ConvertHRToException(hr, $"Session start FAILED 0x{hr:X8}");
+                    throw ex;
+                default:
+                    break;
+            }
+
+            return stream;
+        }
+
         /// <summary>
         /// Turn off EventPipe logging session for the specified process Id.
         /// </summary>
@@ -92,5 +142,25 @@ namespace Microsoft.Diagnostics.Tools.RuntimeClient
                     return 0;
             }
         }
+
+        private static Exception ConvertHRToException(uint hr, string msg)
+        {
+            if (hr == (uint)EventPipeErrorCode.BAD_ENCODING)
+            {
+                return new EventPipeBadEncodingException(msg);
+            }
+            else if (hr == (uint)EventPipeErrorCode.UNKNOWN_COMMAND)
+            {
+                return new EventPipeUnknownCommandException(msg);
+            }
+            else if (hr == (uint)EventPipeErrorCode.UNKNOWN_MAGIC)
+            {
+                return new EventPipeUnknownMagicException(msg);
+            }
+            else
+            {
+                return new EventPipeUnknownErrorException(msg);
+            }
+        }
     }
 }
index da1ccd0bb4ccb2f5a5ab1e4e9aeb03aa8a3887cf..1fcf12a9d865be805a90e4af86274f13996b3890 100644 (file)
@@ -15,7 +15,7 @@ namespace Microsoft.Diagnostics.Tools.RuntimeClient
         NetTrace
     }
 
-    public struct SessionConfiguration
+    public class SessionConfiguration
     {
         public SessionConfiguration(uint circularBufferSizeMB, EventPipeSerializationFormat format, IReadOnlyCollection<Provider> providers)
         {
@@ -37,12 +37,47 @@ namespace Microsoft.Diagnostics.Tools.RuntimeClient
         public uint CircularBufferSizeInMB { get; }
         public EventPipeSerializationFormat Format { get; }
 
-
         public IReadOnlyCollection<Provider> Providers => _providers.AsReadOnly();
 
         private readonly List<Provider> _providers;
 
-        public byte[] Serialize()
+        public virtual byte[] Serialize()
+        {
+            byte[] serializedData = null;
+            using (var stream = new MemoryStream())
+            using (var writer = new BinaryWriter(stream))
+            {
+                writer.Write(CircularBufferSizeInMB);
+                writer.Write((uint)Format);
+
+                writer.Write(Providers.Count());
+                foreach (var provider in Providers)
+                {
+                    writer.Write(provider.Keywords);
+                    writer.Write((uint)provider.EventLevel);
+
+                    writer.WriteString(provider.Name);
+                    writer.WriteString(provider.FilterData);
+                }
+
+                writer.Flush();
+                serializedData = stream.ToArray();
+            }
+
+            return serializedData;
+        }
+    }
+
+    public class SessionConfigurationV2 : SessionConfiguration
+    {
+        public SessionConfigurationV2(uint circularBufferSizeMB, EventPipeSerializationFormat format, bool requestRundown, IReadOnlyCollection<Provider> providers) : base(circularBufferSizeMB, format, providers)
+        {
+            RequestRundown = requestRundown;
+        }
+
+        public bool RequestRundown { get; }
+
+        public override byte[] Serialize()
         {
             byte[] serializedData = null;
             using (var stream = new MemoryStream())
@@ -50,6 +85,7 @@ namespace Microsoft.Diagnostics.Tools.RuntimeClient
             {
                 writer.Write(CircularBufferSizeInMB);
                 writer.Write((uint)Format);
+                writer.Write(RequestRundown);
 
                 writer.Write(Providers.Count());
                 foreach (var provider in Providers)
index 07003ef85345385c4af5d15c97a86be9a38deea2..bce93d7de271cba007ab820c509a26cec99fd03a 100644 (file)
@@ -95,7 +95,7 @@ namespace Microsoft.Diagnostics.Tools.Counters
             {
                 try
                 {
-                    EventPipeClient.StopTracing(_processId, _sessionId);    
+                    EventPipeClient.StopTracing(_processId, _sessionId);
                 }
                 catch (Exception) {} // Swallow all exceptions for now.
                 
@@ -104,6 +104,32 @@ namespace Microsoft.Diagnostics.Tools.Counters
             }
         }
 
+        // Use EventPipe CollectTracing2 command to start monitoring. This may throw.
+        private void RequestTracingV2(string providerString)
+        {
+            var configuration = new SessionConfigurationV2(
+                                        circularBufferSizeMB: 1000,
+                                        format: EventPipeSerializationFormat.NetTrace,
+                                        requestRundown: false,
+                                        providers: Trace.Extensions.ToProviders(providerString));
+            var binaryReader = EventPipeClient.CollectTracing2(_processId, configuration, out _sessionId);
+            EventPipeEventSource source = new EventPipeEventSource(binaryReader);
+            source.Dynamic.All += Dynamic_All;
+            source.Process();
+        }
+        // Use EventPipe CollectTracing command to start monitoring. This may throw.
+        private void RequestTracingV1(string providerString)
+        {
+            var configuration = new SessionConfiguration(
+                                        circularBufferSizeMB: 1000,
+                                        format: EventPipeSerializationFormat.NetTrace,
+                                        providers: Trace.Extensions.ToProviders(providerString));
+            var binaryReader = EventPipeClient.CollectTracing(_processId, configuration, out _sessionId);
+            EventPipeEventSource source = new EventPipeEventSource(binaryReader);
+            source.Dynamic.All += Dynamic_All;
+            source.Process();
+        }
+
         private async Task<int> StartMonitor()
         {
             if (_processId == 0) {
@@ -174,15 +200,12 @@ namespace Microsoft.Diagnostics.Tools.Counters
             Task monitorTask = new Task(() => {
                 try
                 {
-                    var configuration = new SessionConfiguration(
-                        circularBufferSizeMB: 1000,
-                        format: EventPipeSerializationFormat.NetTrace,
-                        providers: Trace.Extensions.ToProviders(providerString));
-
-                    var binaryReader = EventPipeClient.CollectTracing(_processId, configuration, out _sessionId);
-                    EventPipeEventSource source = new EventPipeEventSource(binaryReader);
-                    source.Dynamic.All += Dynamic_All;
-                    source.Process();
+                    RequestTracingV2(providerString);
+                }
+                catch (EventPipeUnknownCommandException)
+                {
+                    // If unknown command exception is thrown, it's likely the app being monitored is running an older version of runtime that doesn't support CollectTracingV2. Try again with V1.
+                    RequestTracingV1(providerString);
                 }
                 catch (Exception ex)
                 {