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)
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:
```
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`
public enum EventPipeCommandId : byte
{
- StopTracing = 0x01,
- CollectTracing = 0x02,
+ StopTracing = 0x01,
+ CollectTracing = 0x02,
+ CollectTracing2 = 0x03,
}
public enum DumpCommandId : byte
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$";
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>
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);
+ }
+ }
}
}
NetTrace
}
- public struct SessionConfiguration
+ public class SessionConfiguration
{
public SessionConfiguration(uint circularBufferSizeMB, EventPipeSerializationFormat format, IReadOnlyCollection<Provider> providers)
{
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())
{
writer.Write(CircularBufferSizeInMB);
writer.Write((uint)Format);
+ writer.Write(RequestRundown);
writer.Write(Providers.Count());
foreach (var provider in Providers)
{
try
{
- EventPipeClient.StopTracing(_processId, _sessionId);
+ EventPipeClient.StopTracing(_processId, _sessionId);
}
catch (Exception) {} // Swallow all exceptions for now.
}
}
+ // 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) {
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)
{