This change supports collecting GCSettingsEvent for gc-collect profile.
The change will work for both dotnet-trace and dotnet-monitor.
Co-authored-by: Noah Falk <noahfalk@users.noreply.github.com>
StopTracing = 0x01, // stop a given session
CollectTracing = 0x02, // create/start a given session
CollectTracing2 = 0x03, // create/start a given session with/without rundown
+ CollectTracing3 = 0x04, // create/start a given session with/without collecting stacks
+ CollectTracing4 = 0x05, // create/start a given session with specific rundown keyword
}
```
See: [EventPipe Commands](#EventPipe-Commands)
StopTracing = 0x01, // stop a given session
CollectTracing = 0x02, // create/start a given session
CollectTracing2 = 0x03, // create/start a given session with/without rundown
+ CollectTracing3 = 0x04, // create/start a given session with/without collecting stacks
+ CollectTracing4 = 0x05, // create/start a given session with specific rundown keyword
}
```
EventPipe Payloads are encoded with the following rules:
Header: `{ Magic; 28; 0xFF00; 0x0000; }`
-`CollectTracing2` returns:
+`CollectTracing3` returns:
* `ulong sessionId`: the ID for the stream session starting on the current connection
##### Details:
```
Followed by an Optional Continuation of a `nettrace` format stream of events.
+### `CollectTracing4`
+
+Command Code: `0x0205`
+
+The `CollectTracing4` command is an extension of the `CollectTracing3` command - its behavior is the same as `CollectTracing3` command, except the requestRundown field is replaced by the rundownKeyword field to allow customizing the set of rundown events to be fired.
+
+> Note available for .NET 9.0 and later.
+
+#### Inputs:
+
+Header: `{ Magic; Size; 0x0205; 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
+* `ulong rundownKeyword`: Indicates the keyword for the rundown provider
+* `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; }`
+
+`CollectTracing4` returns:
+* `ulong sessionId`: the ID for the stream session starting on the current connection
+
+##### Details:
+
+Input:
+```
+Payload
+{
+ uint circularBufferMB,
+ uint format,
+ ulong rundownKeyword
+ 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`
return _configurations.SelectMany(c => c.GetProviders()).ToList();
}
- public override bool RequestRundown
+ public override long RundownKeyword
{
- get => _configurations.Any(c => c.RequestRundown);
+ get => _configurations.Select(c => c.RundownKeyword).Aggregate((x, y) => x | y);
+ set => throw new NotSupportedException();
+ }
+
+ public override RetryStrategy RetryStrategy
+ {
+ get
+ {
+ RetryStrategy result = RetryStrategy.NothingToRetry;
+ foreach (MonitoringSourceConfiguration configuration in _configurations)
+ {
+ if (configuration.RetryStrategy == RetryStrategy.ForbiddenToRetry)
+ {
+ // Nothing overrides ForbiddenToRetry
+ return RetryStrategy.ForbiddenToRetry;
+ }
+ else if (result == RetryStrategy.NothingToRetry)
+ {
+ // Anything override NothingToRetry
+ result = configuration.RetryStrategy;
+ }
+ else if (result == RetryStrategy.DropKeywordDropRundown)
+ {
+ if (configuration.RetryStrategy == RetryStrategy.DropKeywordKeepRundown)
+ {
+ // DropKeywordKeepRundown overrides DropKeywordDropRundown
+ result = RetryStrategy.DropKeywordKeepRundown;
+ }
+ }
+ }
+ return result;
+ }
set => throw new NotSupportedException();
}
}
public AspNetTriggerSourceConfiguration(float? heartbeatIntervalSeconds = null)
{
- RequestRundown = false;
+ RundownKeyword = 0;
_heartbeatIntervalSeconds = heartbeatIntervalSeconds;
}
private readonly IEnumerable<EventPipeProvider> _providers;
private readonly int _bufferSizeInMB;
- public EventPipeProviderSourceConfiguration(bool requestRundown = true, int bufferSizeInMB = 256, params EventPipeProvider[] providers)
+ public EventPipeProviderSourceConfiguration(long rundownKeyword = EventPipeSession.DefaultRundownKeyword, int bufferSizeInMB = 256, params EventPipeProvider[] providers)
{
_providers = providers;
- RequestRundown = requestRundown;
+ RundownKeyword = rundownKeyword;
_bufferSizeInMB = bufferSizeInMB;
}
{
public GCDumpSourceConfiguration()
{
- RequestRundown = false;
+ RundownKeyword = 0;
}
public override IList<EventPipeProvider> GetProviders()
{
public GcCollectConfiguration()
{
- RequestRundown = false;
+ RundownKeyword = (long)Tracing.Parsers.ClrTraceEventParser.Keywords.GC;
+ RetryStrategy = RetryStrategy.DropKeywordDropRundown;
}
public override IList<EventPipeProvider> GetProviders() =>
public HttpRequestSourceConfiguration()
{
//CONSIDER removing rundown for this scenario.
- RequestRundown = true;
+ RundownKeyword = EventPipeSession.DefaultRundownKeyword;
}
// This string is shared between HttpRequestSourceConfiguration and AspNetTriggerSourceConfiguration
public LoggingSourceConfiguration(LogLevel level, LogMessageType messageType, IDictionary<string, LogLevel?> filterSpecs, bool useAppFilters,
bool collectScopes)
{
- RequestRundown = false;
+ RundownKeyword = 0;
_filterSpecs = ToFilterSpecsString(filterSpecs, useAppFilters);
_keywords = (long)ToKeywords(messageType);
_level = ToEventLevel(level);
throw new ArgumentNullException(nameof(providers));
}
- RequestRundown = false;
+ RundownKeyword = 0;
_eventPipeProviders = providers.Where(provider => provider.Type.HasFlag(MetricType.EventCounter))
.Select((MetricEventPipeProvider provider) => new EventPipeProvider(provider.Provider,
public abstract IList<EventPipeProvider> GetProviders();
- public virtual bool RequestRundown { get; set; } = true;
+ public virtual long RundownKeyword { get; set; } = EventPipeSession.DefaultRundownKeyword;
public virtual int BufferSizeInMB => 256;
+
+ public virtual RetryStrategy RetryStrategy { get; set; } = RetryStrategy.NothingToRetry;
}
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+//
+// This class describes the various strategies for retrying a command.
+// The rough idea is that these numbers form a state machine.
+// Any time a command execution fails, a retry will be attempted by matching the
+// condition of the config as well as this strategy number to generate a
+// modified config as well as a modified strategy.
+//
+// This is designed with forward compatibility in mind. We might have newer
+// capabilities that only exists in newer runtimes, but we will never know exactly
+// how we should retry. So this give us a way to encode the retry strategy in the
+// profiles without having to introducing new concepts.
+//
+namespace Microsoft.Diagnostics.Monitoring.EventPipe
+{
+ public enum RetryStrategy
+ {
+ NothingToRetry = 0,
+ DropKeywordKeepRundown = 1,
+ DropKeywordDropRundown = 2,
+ ForbiddenToRetry = 3
+ }
+}
{
public sealed class SampleProfilerConfiguration : MonitoringSourceConfiguration
{
+ public SampleProfilerConfiguration()
+ {
+ RundownKeyword = 0;
+ }
+
public override IList<EventPipeProvider> GetProviders() =>
new EventPipeProvider[]
{
};
public override int BufferSizeInMB => 1;
-
- public override bool RequestRundown
- {
- get => false;
- set => throw new NotSupportedException();
- }
}
}
// The .NET Foundation licenses this file to you under the MIT license.
using System;
+using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
EventPipeSession session = null;
try
{
- session = await client.StartEventPipeSessionAsync(_sourceConfig.GetProviders(), _sourceConfig.RequestRundown, _sourceConfig.BufferSizeInMB, cancellationToken).ConfigureAwait(false);
+ IEnumerable<EventPipeProvider> providers = _sourceConfig.GetProviders();
+ int bufferSizeInMB = _sourceConfig.BufferSizeInMB;
+ long rundownKeyword = _sourceConfig.RundownKeyword;
+ RetryStrategy retryStrategy = _sourceConfig.RetryStrategy;
+ try
+ {
+ EventPipeSessionConfiguration config = new(providers, bufferSizeInMB, rundownKeyword, true);
+ session = await client.StartEventPipeSessionAsync(config, cancellationToken).ConfigureAwait(false);
+ }
+ catch (UnsupportedCommandException) when (retryStrategy == RetryStrategy.DropKeywordKeepRundown)
+ {
+ //
+ // If you are building new profiles or options, you can test with these asserts to make sure you are writing
+ // the retry strategies correctly.
+ //
+ // If these assert ever fires, it means something is wrong with the option generation logic leading to unnecessary retries.
+ // unnecessary retries is not fatal.
+ //
+ // Debug.Assert(rundownKeyword != 0);
+ // Debug.Assert(rundownKeyword != EventPipeSession.DefaultRundownKeyword);
+ //
+ EventPipeSessionConfiguration config = new(providers, bufferSizeInMB, EventPipeSession.DefaultRundownKeyword, true);
+ session = await client.StartEventPipeSessionAsync(config, cancellationToken).ConfigureAwait(false);
+ }
+ catch (UnsupportedCommandException) when (retryStrategy == RetryStrategy.DropKeywordDropRundown)
+ {
+ //
+ // If you are building new profiles or options, you can test with these asserts to make sure you are writing
+ // the retry strategies correctly.
+ //
+ // If these assert ever fires, it means something is wrong with the option generation logic leading to unnecessary retries.
+ // unnecessary retries is not fatal.
+ //
+ // Debug.Assert(rundownKeyword != 0);
+ // Debug.Assert(rundownKeyword != EventPipeSession.DefaultRundownKeyword);
+ //
+ EventPipeSessionConfiguration config = new(providers, bufferSizeInMB, 0, true);
+ session = await client.StartEventPipeSessionAsync(config, cancellationToken).ConfigureAwait(false);
+ }
if (resumeRuntime)
{
try
return EventPipeSession.Start(_endpoint, config);
}
+ /// <summary>
+ /// Start tracing the application and return an EventPipeSession object
+ /// </summary>
+ /// <param name="config">The configuration for start tracing.</param>
+ /// <returns>
+ /// An EventPipeSession object representing the EventPipe session that just started.
+ /// </returns>
+ public EventPipeSession StartEventPipeSession(EventPipeSessionConfiguration config)
+ {
+ return EventPipeSession.Start(_endpoint, config);
+ }
+
/// <summary>
/// Start tracing the application and return an EventPipeSession object
/// </summary>
{
public class EventPipeSession : IDisposable
{
+ //! This is CoreCLR specific keywords for native ETW events (ending up in event pipe).
+ //! The keywords below seems to correspond to:
+ //! GCKeyword (0x00000001)
+ //! LoaderKeyword (0x00000008)
+ //! JitKeyword (0x00000010)
+ //! NgenKeyword (0x00000020)
+ //! unused_keyword (0x00000100)
+ //! JittedMethodILToNativeMapKeyword (0x00020000)
+ //! ThreadTransferKeyword (0x80000000)
+ internal const long DefaultRundownKeyword = 0x80020139;
+
private ulong _sessionId;
private IpcEndpoint _endpoint;
private bool _disposedValue; // To detect redundant calls
private static IpcMessage CreateStartMessage(EventPipeSessionConfiguration config)
{
// 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();
+ EventPipeCommandId command;
+ byte[] payload;
+ if (config.RundownKeyword != DefaultRundownKeyword && config.RundownKeyword != 0)
+ {
+ // V4 has added support to specify rundown keyword
+ command = EventPipeCommandId.CollectTracing4;
+ payload = config.SerializeV4();
+ }
+ else if (!config.RequestStackwalk)
+ {
+ // V3 has added support to disable the stacktraces
+ command = EventPipeCommandId.CollectTracing3;
+ payload = config.SerializeV3();
+ }
+ else
+ {
+ command = EventPipeCommandId.CollectTracing2;
+ payload = config.SerializeV2();
+ }
+
return new IpcMessage(DiagnosticsServerCommandSet.EventPipe, (byte)command, payload);
}
IEnumerable<EventPipeProvider> providers,
int circularBufferSizeMB = 256,
bool requestRundown = true,
- bool requestStackwalk = true) : this(circularBufferSizeMB, EventPipeSerializationFormat.NetTrace, providers, requestRundown, requestStackwalk)
+ bool requestStackwalk = true) : this(circularBufferSizeMB, EventPipeSerializationFormat.NetTrace, providers, requestStackwalk, (requestRundown ? EventPipeSession.DefaultRundownKeyword : 0))
+ {}
+
+ /// <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="rundownKeyword">The keyword for rundown events.</param>
+ /// <param name="requestStackwalk">If true, record a stacktrace for every emitted event.</param>
+ public EventPipeSessionConfiguration(
+ IEnumerable<EventPipeProvider> providers,
+ int circularBufferSizeMB,
+ long rundownKeyword,
+ bool requestStackwalk = true) : this(circularBufferSizeMB, EventPipeSerializationFormat.NetTrace, providers, requestStackwalk, rundownKeyword)
{}
private EventPipeSessionConfiguration(
int circularBufferSizeMB,
EventPipeSerializationFormat format,
IEnumerable<EventPipeProvider> providers,
- bool requestRundown,
- bool requestStackwalk)
+ bool requestStackwalk,
+ long rundownKeyword)
{
if (circularBufferSizeMB == 0)
{
CircularBufferSizeInMB = circularBufferSizeMB;
Format = format;
- RequestRundown = requestRundown;
RequestStackwalk = requestStackwalk;
+ RundownKeyword = rundownKeyword;
}
/// <summary>
/// <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; }
+ public bool RequestRundown => this.RundownKeyword != 0;
/// <summary>
/// The size of the runtime's buffer for collecting events in MB.
/// </summary>
public bool RequestStackwalk { get; }
+ /// <summary>
+ /// The keywords enabled for the rundown provider.
+ /// </summary>
+ public long RundownKeyword { get; internal set; }
+
/// <summary>
/// Providers to enable for this session.
/// </summary>
return serializedData;
}
+ public static byte[] SerializeV4(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.RundownKeyword);
+ 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);
CollectTracing = 0x02,
CollectTracing2 = 0x03,
CollectTracing3 = 0x04,
+ CollectTracing4 = 0x05,
}
internal enum DumpCommandId : byte
enabledBy[providerCollectionProvider.Name] = "--providers ";
}
- bool collectRundownEvents = true;
+ long rundownKeyword = EventPipeSession.DefaultRundownKeyword;
+ RetryStrategy retryStrategy = RetryStrategy.NothingToRetry;
if (profile.Length != 0)
{
return (int)ReturnCode.ArgumentError;
}
- collectRundownEvents = selectedProfile.Rundown;
+ rundownKeyword = selectedProfile.RundownKeyword;
+ retryStrategy = selectedProfile.RetryStrategy;
Profile.MergeProfileAndProviders(selectedProfile, providerCollection, enabledBy);
}
if (rundown.HasValue)
{
- collectRundownEvents = rundown.Value;
+ if (rundown.Value)
+ {
+ rundownKeyword |= EventPipeSession.DefaultRundownKeyword;
+ retryStrategy = (rundownKeyword == EventPipeSession.DefaultRundownKeyword) ? RetryStrategy.NothingToRetry : RetryStrategy.DropKeywordKeepRundown;
+ }
+ else
+ {
+ rundownKeyword = 0;
+ retryStrategy = RetryStrategy.NothingToRetry;
+ }
}
// Parse --clrevents parameter
EventPipeSession session = null;
try
{
- session = diagnosticsClient.StartEventPipeSession(providerCollection, collectRundownEvents, (int)buffersize);
- if (resumeRuntime)
- {
- try
- {
- diagnosticsClient.ResumeRuntime();
- }
- catch (UnsupportedCommandException)
- {
- // Noop if command is unsupported, since the target is most likely a 3.1 app.
- }
- }
+ EventPipeSessionConfiguration config = new(providerCollection, (int)buffersize, rundownKeyword: rundownKeyword, requestStackwalk: true);
+ session = diagnosticsClient.StartEventPipeSession(config);
}
- catch (DiagnosticsClientException e)
+ catch (UnsupportedCommandException e)
{
- Console.Error.WriteLine($"Unable to start a tracing session: {e}");
- return (int)ReturnCode.SessionCreationError;
+ if (retryStrategy == RetryStrategy.DropKeywordKeepRundown)
+ {
+ Console.Error.WriteLine("The runtime version being traced doesn't support the custom rundown feature used by this tracing configuration, retrying with the standard rundown keyword");
+ //
+ // If you are building new profiles or options, you can test with these asserts to make sure you are writing
+ // the retry strategies correctly.
+ //
+ // If these assert ever fires, it means something is wrong with the option generation logic leading to unnecessary retries.
+ // unnecessary retries is not fatal.
+ //
+ // Debug.Assert(rundownKeyword != 0);
+ // Debug.Assert(rundownKeyword != EventPipeSession.DefaultRundownKeyword);
+ //
+ EventPipeSessionConfiguration config = new(providerCollection, (int)buffersize, rundownKeyword: EventPipeSession.DefaultRundownKeyword, requestStackwalk: true);
+ session = diagnosticsClient.StartEventPipeSession(config);
+ }
+ else if (retryStrategy == RetryStrategy.DropKeywordDropRundown)
+ {
+ Console.Error.WriteLine("The runtime version being traced doesn't support the custom rundown feature used by this tracing configuration, retrying with the rundown omitted");
+ //
+ // If you are building new profiles or options, you can test with these asserts to make sure you are writing
+ // the retry strategies correctly.
+ //
+ // If these assert ever fires, it means something is wrong with the option generation logic leading to unnecessary retries.
+ // unnecessary retries is not fatal.
+ //
+ // Debug.Assert(rundownKeyword != 0);
+ // Debug.Assert(rundownKeyword != EventPipeSession.DefaultRundownKeyword);
+ //
+ EventPipeSessionConfiguration config = new(providerCollection, (int)buffersize, rundownKeyword: 0, requestStackwalk: true);
+ session = diagnosticsClient.StartEventPipeSession(config);
+ }
+ else
+ {
+ Console.Error.WriteLine($"Unable to start a tracing session: {e}");
+ return (int)ReturnCode.SessionCreationError;
+ }
}
catch (UnauthorizedAccessException e)
{
Console.Error.WriteLine($"dotnet-trace does not have permission to access the specified app: {e.GetType()}");
return (int)ReturnCode.SessionCreationError;
}
-
+ if (resumeRuntime)
+ {
+ try
+ {
+ diagnosticsClient.ResumeRuntime();
+ }
+ catch (UnsupportedCommandException)
+ {
+ // Noop if command is unsupported, since the target is most likely a 3.1 app.
+ }
+ }
if (session == null)
{
Console.Error.WriteLine("Unable to create session.");
keywords: (long)ClrTraceEventParser.Keywords.GC |
(long)ClrTraceEventParser.Keywords.GCHandle |
(long)ClrTraceEventParser.Keywords.Exception
- ),
+ )
},
"Tracks GC collections and samples object allocations."),
new Profile(
keywords: (long)ClrTraceEventParser.Keywords.GC
)
},
- "Tracks GC collections only at very low overhead.") { Rundown = false },
+ "Tracks GC collections only at very low overhead.") { RundownKeyword = (long)ClrTraceEventParser.Keywords.GC, RetryStrategy = RetryStrategy.DropKeywordDropRundown },
new Profile(
"database",
new EventPipeProvider[] {
public string Description { get; }
- public bool Rundown { get; set; } = true;
+ public long RundownKeyword { get; set; }
+
+ public RetryStrategy RetryStrategy { get; set; } = RetryStrategy.NothingToRetry;
public static void MergeProfileAndProviders(Profile selectedProfile, List<EventPipeProvider> providerCollection, Dictionary<string, string> enabledBy)
{
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+//
+// This class describes the various strategies for retrying a command.
+// The rough idea is that these numbers form a state machine.
+// Any time a command execution fails, a retry will be attempted by matching the
+// condition of the config as well as this strategy number to generate a
+// modified config as well as a modified strategy.
+//
+// This is designed with forward compatibility in mind. We might have newer
+// capabilities that only exists in newer runtimes, but we will never know exactly
+// how we should retry. So this give us a way to encode the retry strategy in the
+// profiles without having to introducing new concepts.
+//
+namespace Microsoft.Diagnostics.Tools.Trace
+{
+ internal enum RetryStrategy
+ {
+ NothingToRetry = 0,
+ DropKeywordKeepRundown = 1,
+ DropKeywordDropRundown = 2,
+ ForbiddenToRetry = 3
+ }
+}