Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 69 additions & 6 deletions documentation/design-docs/ipc-protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ enum class ProfilerCommandId : uint8_t
AttachProfiler = 0x01,
// future
}
```
```
See: [Profiler Commands](#Profiler-Commands)

```c++
Expand Down Expand Up @@ -460,7 +460,7 @@ Payload
array<provider_config> providers
}

provider_config
provider_config
{
ulong keywords,
uint logLevel,
Expand All @@ -482,7 +482,7 @@ Followed by an Optional Continuation of a `nettrace` format stream of events.

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.
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:

Expand All @@ -500,7 +500,7 @@ A `provider_config` is composed of the following data:
* `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; }`
Expand All @@ -520,7 +520,7 @@ Payload
array<provider_config> providers
}

provider_config
provider_config
{
ulong keywords,
uint logLevel,
Expand All @@ -538,7 +538,70 @@ Payload
```
Followed by an Optional Continuation of a `nettrace` format stream of events.

### `StopTracing`
### `CollectTracing3`

Command Code: `0x0204`

The `CollectTracing3` command is an extension of the `CollectTracing2` command - its behavior is the same as `CollectTracing2` command, except that it has another field that lets you specify whether the stackwalk should be made for each event.

#### 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.
* `bool requestStackwalk`: Indicates whether stacktrace information should be recorded.
* `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,
bool requestStackwalk,
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`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ internal Task WaitForConnectionAsync(CancellationToken token)
/// </returns>
public EventPipeSession StartEventPipeSession(IEnumerable<EventPipeProvider> providers, bool requestRundown = true, int circularBufferMB = 256)
{
return EventPipeSession.Start(_endpoint, providers, requestRundown, circularBufferMB);
EventPipeSessionConfiguration config = new(providers, circularBufferMB, requestRundown: requestRundown, requestStackwalk: true);
return EventPipeSession.Start(_endpoint, config);
}

/// <summary>
Expand All @@ -82,7 +83,8 @@ public EventPipeSession StartEventPipeSession(IEnumerable<EventPipeProvider> pro
/// </returns>
public EventPipeSession StartEventPipeSession(EventPipeProvider provider, bool requestRundown = true, int circularBufferMB = 256)
{
return EventPipeSession.Start(_endpoint, new[] { provider }, requestRundown, circularBufferMB);
EventPipeSessionConfiguration config = new(new[] {provider}, circularBufferMB, requestRundown: requestRundown, requestStackwalk: true);
return EventPipeSession.Start(_endpoint, config);
}

/// <summary>
Expand All @@ -97,7 +99,8 @@ public EventPipeSession StartEventPipeSession(EventPipeProvider provider, bool r
/// </returns>
internal Task<EventPipeSession> StartEventPipeSessionAsync(IEnumerable<EventPipeProvider> providers, bool requestRundown, int circularBufferMB, CancellationToken token)
{
return EventPipeSession.StartAsync(_endpoint, providers, requestRundown, circularBufferMB, token);
EventPipeSessionConfiguration config = new(providers, circularBufferMB, requestRundown: requestRundown, requestStackwalk: true);
return EventPipeSession.StartAsync(_endpoint, config, token);
}

/// <summary>
Expand All @@ -112,7 +115,21 @@ internal Task<EventPipeSession> StartEventPipeSessionAsync(IEnumerable<EventPipe
/// </returns>
internal Task<EventPipeSession> StartEventPipeSessionAsync(EventPipeProvider provider, bool requestRundown, int circularBufferMB, CancellationToken token)
{
return EventPipeSession.StartAsync(_endpoint, new[] { provider }, requestRundown, circularBufferMB, token);
EventPipeSessionConfiguration config = new(new[] {provider}, circularBufferMB, requestRundown: requestRundown, requestStackwalk: true);
return EventPipeSession.StartAsync(_endpoint, config, token);
}

/// <summary>
/// Start tracing the application and return an EventPipeSession object
/// </summary>
/// <param name="configuration">Configuration of this EventPipeSession</param>
/// <param name="token">The token to monitor for cancellation requests.</param>
/// <returns>
/// An EventPipeSession object representing the EventPipe session that just started.
/// </returns>
public Task<EventPipeSession> StartEventPipeSessionAsync(EventPipeSessionConfiguration configuration, CancellationToken token)
{
return EventPipeSession.StartAsync(_endpoint, configuration, token);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,16 @@ private EventPipeSession(IpcEndpoint endpoint, IpcResponse response, ulong sessi

public Stream EventStream => _response.Continuation;

internal static EventPipeSession Start(IpcEndpoint endpoint, IEnumerable<EventPipeProvider> providers, bool requestRundown, int circularBufferMB)
internal static EventPipeSession Start(IpcEndpoint endpoint, EventPipeSessionConfiguration config)
{
IpcMessage requestMessage = CreateStartMessage(providers, requestRundown, circularBufferMB);
IpcMessage requestMessage = CreateStartMessage(config);
IpcResponse? response = IpcClient.SendMessageGetContinuation(endpoint, requestMessage);
return CreateSessionFromResponse(endpoint, ref response, nameof(Start));
}

internal static async Task<EventPipeSession> StartAsync(IpcEndpoint endpoint, IEnumerable<EventPipeProvider> providers, bool requestRundown, int circularBufferMB, CancellationToken cancellationToken)
internal static async Task<EventPipeSession> StartAsync(IpcEndpoint endpoint, EventPipeSessionConfiguration config, CancellationToken cancellationToken)
{
IpcMessage requestMessage = CreateStartMessage(providers, requestRundown, circularBufferMB);
IpcMessage requestMessage = CreateStartMessage(config);
IpcResponse? response = await IpcClient.SendMessageGetContinuationAsync(endpoint, requestMessage, cancellationToken).ConfigureAwait(false);
return CreateSessionFromResponse(endpoint, ref response, nameof(StartAsync));
}
Expand Down Expand Up @@ -81,10 +81,14 @@ public async Task StopAsync(CancellationToken cancellationToken)
}
}

private static IpcMessage CreateStartMessage(IEnumerable<EventPipeProvider> providers, bool requestRundown, int circularBufferMB)
private static IpcMessage CreateStartMessage(EventPipeSessionConfiguration config)
{
EventPipeSessionConfiguration config = new(circularBufferMB, EventPipeSerializationFormat.NetTrace, providers, requestRundown);
return new IpcMessage(DiagnosticsServerCommandSet.EventPipe, (byte)EventPipeCommandId.CollectTracing2, config.SerializeV2());
// To keep backward compatibility with older runtimes we only use newer serialization format when needed
// V3 has added support to disable the stacktraces
bool shouldUseV3 = !config.RequestStackwalk;
EventPipeCommandId command = shouldUseV3 ? EventPipeCommandId.CollectTracing3 : EventPipeCommandId.CollectTracing2;
byte[] payload = shouldUseV3 ? config.SerializeV3() : config.SerializeV2();
return new IpcMessage(DiagnosticsServerCommandSet.EventPipe, (byte)command, payload);
}

private static EventPipeSession CreateSessionFromResponse(IpcEndpoint endpoint, ref IpcResponse? response, string operationName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace Microsoft.Diagnostics.NETCore.Client
{
Expand All @@ -13,9 +14,29 @@ internal enum EventPipeSerializationFormat
NetTrace
}

internal class EventPipeSessionConfiguration
public sealed class EventPipeSessionConfiguration
{
public EventPipeSessionConfiguration(int circularBufferSizeMB, EventPipeSerializationFormat format, IEnumerable<EventPipeProvider> providers, bool requestRundown = true)
/// <summary>
/// Creates a new configuration object for the EventPipeSession.
/// For details, see the documentation of each property of this object.
/// </summary>
/// <param name="providers">An IEnumerable containing the list of Providers to turn on.</param>
/// <param name="circularBufferSizeMB">The size of the runtime's buffer for collecting events in MB</param>
/// <param name="requestRundown">If true, request rundown events from the runtime.</param>
/// <param name="requestStackwalk">If true, record a stacktrace for every emitted event.</param>
public EventPipeSessionConfiguration(
IEnumerable<EventPipeProvider> providers,
int circularBufferSizeMB = 256,
bool requestRundown = true,
bool requestStackwalk = true) : this(circularBufferSizeMB, EventPipeSerializationFormat.NetTrace, providers, requestRundown, requestStackwalk)
{}

private EventPipeSessionConfiguration(
int circularBufferSizeMB,
EventPipeSerializationFormat format,
IEnumerable<EventPipeProvider> providers,
bool requestRundown,
bool requestStackwalk)
{
if (circularBufferSizeMB == 0)
{
Expand All @@ -35,36 +56,60 @@ public EventPipeSessionConfiguration(int circularBufferSizeMB, EventPipeSerializ
CircularBufferSizeInMB = circularBufferSizeMB;
Format = format;
RequestRundown = requestRundown;
RequestStackwalk = requestStackwalk;
_providers = new List<EventPipeProvider>(providers);
}

/// <summary>
/// If true, request rundown events from the runtime.
/// <list type="bullet">
/// <item>Rundown events are needed to correctly decode the stacktrace information for dynamically generated methods.</item>
/// <item>Rundown happens at the end of the session. It increases the time needed to finish the session and, for large applications, may have important impact on the final trace file size.</item>
/// <item>Consider to set this parameter to false if you don't need stacktrace information or if you're analyzing events on the fly.</item>
/// </list>
/// </summary>
public bool RequestRundown { get; }

/// <summary>
/// The size of the runtime's buffer for collecting events in MB.
/// If the buffer size is too small to accommodate all in-flight events some events may be lost.
/// </summary>
public int CircularBufferSizeInMB { get; }
public EventPipeSerializationFormat Format { get; }

/// <summary>
/// If true, record a stacktrace for every emitted event.
/// <list type="bullet">
/// <item>The support of this parameter only comes with NET 9. Before, the stackwalk is always enabled and if this property is set to false the connection attempt will fail.</item>
/// <item>Disabling the stackwalk makes event collection overhead considerably less</item>
/// <item>Note that some events may choose to omit the stacktrace regardless of this parameter, specifically the events emitted from the native runtime code.</item>
/// <item>If the stacktrace collection is disabled application-wide (using the env variable <c>DOTNET_EventPipeEnableStackwalk</c>) this parameter is ignored.</item>
/// </list>
/// </summary>
public bool RequestStackwalk { get; }

/// <summary>
/// Providers to enable for this session.
/// </summary>
public IReadOnlyCollection<EventPipeProvider> Providers => _providers.AsReadOnly();

private readonly List<EventPipeProvider> _providers;

public byte[] SerializeV2()
internal EventPipeSerializationFormat Format { get; }
}

internal static class EventPipeSessionConfigurationExtensions
{
public static byte[] SerializeV2(this EventPipeSessionConfiguration config)
{
byte[] serializedData = null;
using (MemoryStream stream = new())
using (BinaryWriter writer = new(stream))
{
writer.Write(CircularBufferSizeInMB);
writer.Write((uint)Format);
writer.Write(RequestRundown);

writer.Write(Providers.Count);
foreach (EventPipeProvider provider in Providers)
{
writer.Write(unchecked((ulong)provider.Keywords));
writer.Write((uint)provider.EventLevel);
writer.Write(config.CircularBufferSizeInMB);
writer.Write((uint)config.Format);
writer.Write(config.RequestRundown);

writer.WriteString(provider.Name);
writer.WriteString(provider.GetArgumentString());
}
SerializeProviders(config, writer);

writer.Flush();
serializedData = stream.ToArray();
Expand All @@ -73,6 +118,36 @@ public byte[] SerializeV2()
return serializedData;
}

public static byte[] SerializeV3(this EventPipeSessionConfiguration config)
{
byte[] serializedData = null;
using (MemoryStream stream = new())
using (BinaryWriter writer = new(stream))
{
writer.Write(config.CircularBufferSizeInMB);
writer.Write((uint)config.Format);
writer.Write(config.RequestRundown);
writer.Write(config.RequestStackwalk);

SerializeProviders(config, writer);

writer.Flush();
serializedData = stream.ToArray();
}

return serializedData;
}

private static void SerializeProviders(EventPipeSessionConfiguration config, BinaryWriter writer)
{
writer.Write(config.Providers.Count);
foreach (EventPipeProvider provider in config.Providers)
{
writer.Write(unchecked((ulong)provider.Keywords));
writer.Write((uint)provider.EventLevel);
writer.WriteString(provider.Name);
writer.WriteString(provider.GetArgumentString());
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ internal enum EventPipeCommandId : byte
StopTracing = 0x01,
CollectTracing = 0x02,
CollectTracing2 = 0x03,
CollectTracing3 = 0x04,
}

internal enum DumpCommandId : byte
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,17 @@ public async Task ResumeRuntime(TimeSpan timeout)
}
}

public async Task<EventPipeSession> StartEventPipeSession(EventPipeSessionConfiguration config, TimeSpan timeout)
{
if (_useAsync)
{
CancellationTokenSource cancellation = new(timeout);
return await _client.StartEventPipeSessionAsync(config, cancellation.Token).ConfigureAwait(false);
}

throw new NotSupportedException($"{nameof(StartEventPipeSession)} with config parameter is only supported on async path");
}

public async Task<EventPipeSession> StartEventPipeSession(IEnumerable<EventPipeProvider> providers, TimeSpan timeout)
{
if (_useAsync)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ public static Task<EventPipeSession> StartEventPipeSession(this DiagnosticsClien
return shim.StartEventPipeSession(provider, DefaultPositiveVerificationTimeout);
}

public static Task<EventPipeSession> StartEventPipeSession(this DiagnosticsClientApiShim shim, EventPipeSessionConfiguration config)
{
return shim.StartEventPipeSession(config, DefaultPositiveVerificationTimeout);
}

public static Task EnablePerfMap(this DiagnosticsClientApiShim shim, PerfMapType type)
{
return shim.EnablePerfMap(type, DefaultPositiveVerificationTimeout);
Expand Down
Loading