From: Sung Yoon Whang Date: Tue, 16 Jul 2019 00:14:06 +0000 (-0700) Subject: Add RundownEnabled field to EventPipe IPC protocol (#375) X-Git-Tag: submit/tizen/20190813.035844~4^2^2~1 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=aa953d1632b2797bdb580016ac618bfdd8a3439e;p=platform%2Fcore%2Fdotnet%2Fdiagnostics.git Add RundownEnabled field to EventPipe IPC protocol (#375) * 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 --- diff --git a/documentation/design-docs/ipc-protocol.md b/documentation/design-docs/ipc-protocol.md index 9d627fd54..6d122f164 100644 --- a/documentation/design-docs/ipc-protocol.md +++ b/documentation/design-docs/ipc-protocol.md @@ -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 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 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` diff --git a/src/Microsoft.Diagnostics.Tools.RuntimeClient/DiagnosticsIpc/IpcCommands.cs b/src/Microsoft.Diagnostics.Tools.RuntimeClient/DiagnosticsIpc/IpcCommands.cs index eea08e478..8be8f0a3b 100644 --- a/src/Microsoft.Diagnostics.Tools.RuntimeClient/DiagnosticsIpc/IpcCommands.cs +++ b/src/Microsoft.Diagnostics.Tools.RuntimeClient/DiagnosticsIpc/IpcCommands.cs @@ -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 diff --git a/src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/EventPipeClient.cs b/src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/EventPipeClient.cs index be13fcae5..523a5cf59 100644 --- a/src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/EventPipeClient.cs +++ b/src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/EventPipeClient.cs @@ -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; + } + /// /// Turn off EventPipe logging session for the specified process Id. /// @@ -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); + } + } } } diff --git a/src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/SessionConfiguration.cs b/src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/SessionConfiguration.cs index da1ccd0bb..1fcf12a9d 100644 --- a/src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/SessionConfiguration.cs +++ b/src/Microsoft.Diagnostics.Tools.RuntimeClient/Eventing/SessionConfiguration.cs @@ -15,7 +15,7 @@ namespace Microsoft.Diagnostics.Tools.RuntimeClient NetTrace } - public struct SessionConfiguration + public class SessionConfiguration { public SessionConfiguration(uint circularBufferSizeMB, EventPipeSerializationFormat format, IReadOnlyCollection providers) { @@ -37,12 +37,47 @@ namespace Microsoft.Diagnostics.Tools.RuntimeClient public uint CircularBufferSizeInMB { get; } public EventPipeSerializationFormat Format { get; } - public IReadOnlyCollection Providers => _providers.AsReadOnly(); private readonly List _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 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) diff --git a/src/Tools/dotnet-counters/CounterMonitor.cs b/src/Tools/dotnet-counters/CounterMonitor.cs index 07003ef85..bce93d7de 100644 --- a/src/Tools/dotnet-counters/CounterMonitor.cs +++ b/src/Tools/dotnet-counters/CounterMonitor.cs @@ -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 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) {