KeyValueArgs format: '[key1=value1][;key2=value2]'
note: values that contain ';' or '=' characters should be surrounded by double quotes ("), e.g., 'key="value;with=symbols";key2=value2'
+ --clrevents <clrevents>
+ List of CLR events to collect.
+
+ The string should be in the format '[Keyword1]+[Keyword2]+...+[KeywordN]'. For example: --clrevents GC+GCHandle
+
+ List of CLR event keywords:
+ * GC
+ * GCHandle
+ * Fusion
+ * Loader
+ * JIT
+ * NGEN
+ * StartEnumeration
+ * EndEnumeration
+ * Security
+ * AppDomainResourceManagement
+ * JITTracing
+ * Interop
+ * Contention
+ * Exception
+ * Threading
+ * JittedMethodILToNativeMap
+ * OverrideAndSuppressNGENEvents
+ * Type
+ * GCHeapDump
+ * GCSampledObjectAllocationHigh
+ * GCHeapSurvivalAndMovement
+ * GCHeapCollect
+ * GCHeapAndTypeNames
+ * GCSampledObjectAllocationLow
+ * PerfTrack
+ * Stack
+ * ThreadTransfer
+ * Debugger
+
+
--buffersize <Size>
Sets the size of the in-memory circular buffer in megabytes. Default 256 MB.
{
internal static class CollectCommandHandler
{
- delegate Task<int> CollectDelegate(CancellationToken ct, IConsole console, int processId, FileInfo output, uint buffersize, string providers, string profile, TraceFileFormat format, TimeSpan duration);
+ delegate Task<int> CollectDelegate(CancellationToken ct, IConsole console, int processId, FileInfo output, uint buffersize, string providers, string profile, TraceFileFormat format, TimeSpan duration, string clrevents, string clreventlevel);
/// <summary>
/// Collects a diagnostic trace from a currently running process.
/// <param name="providers">A list of EventPipe providers to be enabled. This is in the form 'Provider[,Provider]', where Provider is in the form: 'KnownProviderName[:Flags[:Level][:KeyValueArgs]]', and KeyValueArgs is in the form: '[key1=value1][;key2=value2]'</param>
/// <param name="profile">A named pre-defined set of provider configurations that allows common tracing scenarios to be specified succinctly.</param>
/// <param name="format">The desired format of the created trace file.</param>
+ /// <param name="duration">The duration of trace to be taken. </param>
+ /// <param name="clrevents">A list of CLR events to be emitted.</param>
+ /// <param name="clreventlevel">The verbosity level of CLR events</param>
/// <returns></returns>
- private static async Task<int> Collect(CancellationToken ct, IConsole console, int processId, FileInfo output, uint buffersize, string providers, string profile, TraceFileFormat format, TimeSpan duration)
+ private static async Task<int> Collect(CancellationToken ct, IConsole console, int processId, FileInfo output, uint buffersize, string providers, string profile, TraceFileFormat format, TimeSpan duration, string clrevents, string clreventlevel)
{
try
{
return ErrorCodes.ArgumentError;
}
- if (profile.Length == 0 && providers.Length == 0)
+ if (profile.Length == 0 && providers.Length == 0 && clrevents.Length == 0)
{
Console.Out.WriteLine("No profile or providers specified, defaulting to trace profile 'cpu-sampling'");
profile = "cpu-sampling";
Profile.MergeProfileAndProviders(selectedProfile, providerCollection, enabledBy);
}
+ // Parse --clrevents parameter
+ if (clrevents.Length != 0)
+ {
+ // Ignore --clrevents if CLR event provider was already specified via --profile or --providers command.
+ if (enabledBy.ContainsKey(Extensions.CLREventProviderName))
+ {
+ Console.WriteLine($"The argument --clrevents {clrevents} will be ignored because the CLR provider was configured via either --profile or --providers command.");
+ }
+ else
+ {
+ var clrProvider = Extensions.ToCLREventPipeProvider(clrevents, clreventlevel);
+ providerCollection.Add(clrProvider);
+ enabledBy[Extensions.CLREventProviderName] = "--clrevents";
+ }
+ }
+
if (providerCollection.Count <= 0)
{
ProvidersOption(),
ProfileOption(),
CommonOptions.FormatOption(),
- DurationOption()
+ DurationOption(),
+ CLREventsOption(),
+ CLREventLevelOption()
};
private static uint DefaultCircularBufferSizeInMB => 256;
Argument = new Argument<TimeSpan>(name: "duration-timespan", defaultValue: default),
IsHidden = true
};
+
+ private static Option CLREventsOption() =>
+ new Option(
+ alias: "--clrevents",
+ description: @"List of CLR runtime events to emit.")
+ {
+ Argument = new Argument<string>(name: "clrevents", defaultValue: "")
+ };
+
+ private static Option CLREventLevelOption() =>
+ new Option(
+ alias: "--clreventlevel",
+ description: @"Verbosity of CLR events to be emitted.")
+ {
+ Argument = new Argument<string>(name: "clreventlevel", defaultValue: "")
+ };
}
}
{
internal static class Extensions
{
+ public static string CLREventProviderName = "Microsoft-Windows-DotNETRuntime";
+
private static EventLevel defaultEventLevel = EventLevel.Verbose;
+ // Keep this in sync with runtime repo's clretwall.man
+ private static Dictionary<string, long> CLREventKeywords = new Dictionary<string, long>(StringComparer.InvariantCultureIgnoreCase)
+ {
+ { "gc", 0x1 },
+ { "gchandle", 0x2 },
+ { "fusion", 0x4 },
+ { "loader", 0x8 },
+ { "jit", 0x10 },
+ { "ngen", 0x20 },
+ { "startenumeration", 0x40 },
+ { "endenumeration", 0x80 },
+ { "security", 0x400 },
+ { "appdomainresourcemanagement", 0x800 },
+ { "jittracing", 0x1000 },
+ { "interop", 0x2000 },
+ { "contention", 0x4000 },
+ { "exception", 0x8000 },
+ { "threading", 0x10000 },
+ { "jittedmethodiltonativemap", 0x20000 },
+ { "overrideandsuppressngenevents", 0x40000 },
+ { "type", 0x80000 },
+ { "gcheapdump", 0x100000 },
+ { "gcsampledobjectallcationhigh", 0x200000 },
+ { "gcheapsurvivalandmovement", 0x400000 },
+ { "gcheapcollect", 0x800000 },
+ { "gcheapandtypenames", 0x1000000 },
+ { "gcsampledobjectallcationlow", 0x2000000 },
+ { "perftrack", 0x20000000 },
+ { "stack", 0x40000000 },
+ { "threadtransfer", 0x80000000 },
+ { "debugger", 0x100000000 }
+ };
public static List<EventPipeProvider> ToProviders(string providers)
{
new List<EventPipeProvider>() : providers.Split(',').Select(ToProvider).ToList();
}
+ public static EventPipeProvider ToCLREventPipeProvider(string clreventslist, string clreventlevel)
+ {
+ if (clreventslist == null || clreventslist.Length == 0)
+ {
+ return null;
+ }
+
+ var clrevents = clreventslist.Split("+");
+ long clrEventsKeywordsMask = 0;
+ for (var i = 0; i < clrevents.Length; i++)
+ {
+ if (CLREventKeywords.TryGetValue(clrevents[i], out var keyword))
+ {
+ clrEventsKeywordsMask |= keyword;
+ }
+ else
+ {
+ throw new ArgumentException($"{clrevents[i]} is not a valid CLR event keyword");
+ }
+ }
+
+ EventLevel level = (EventLevel)4; // Default event level
+
+ if (clreventlevel.Length != 0)
+ {
+ level = GetEventLevel(clreventlevel);
+ }
+
+ return new EventPipeProvider(CLREventProviderName, level, clrEventsKeywordsMask, null);
+ }
+
private static EventLevel GetEventLevel(string token)
{
if (Int32.TryParse(token, out int level) && level >= 0)
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Diagnostics.NETCore.Client;
+using System;
+using Xunit;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Microsoft.Diagnostics.Tools.Trace
+{
+ public class CLRProviderParsingTests
+ {
+ private static string CLRProviderName = "Microsoft-Windows-DotNETRuntime";
+
+ [Theory]
+ [InlineData("gc")]
+ [InlineData("Gc")]
+ [InlineData("GC")]
+ public void ValidSingleCLREvent(string providerToParse)
+ {
+ var provider = Extensions.ToCLREventPipeProvider(providerToParse, "4");
+ Assert.True(provider.Name == CLRProviderName);
+ Assert.True(provider.Keywords == 1);
+ Assert.True(provider.EventLevel == System.Diagnostics.Tracing.EventLevel.Informational);
+ Assert.True(provider.Arguments == null);
+ }
+
+ [Theory]
+ [InlineData("nosuchevent")]
+ [InlineData("something")]
+ [InlineData("haha")]
+ public void InValidSingleCLREvent(string providerToParse)
+ {
+ Assert.Throws<ArgumentException>(() => Extensions.ToCLREventPipeProvider(providerToParse, "4"));
+ }
+
+ [Theory]
+ [InlineData("gc+gchandle")]
+ [InlineData("gc+GCHandle")]
+ [InlineData("GC+GCHandle")]
+ public void ValidManyCLREvents(string providerToParse)
+ {
+ var provider = Extensions.ToCLREventPipeProvider(providerToParse, "5");
+ Assert.True(provider.Name == CLRProviderName);
+ Assert.True(provider.Keywords == 3);
+ Assert.True(provider.EventLevel == System.Diagnostics.Tracing.EventLevel.Verbose);
+ Assert.True(provider.Arguments == null);
+ }
+
+ [Theory]
+ [InlineData("informational")]
+ [InlineData("4")]
+ [InlineData("Informational")]
+ [InlineData("InFORMationAL")]
+ public void ValidCLREventLevel(string clreventlevel)
+ {
+ var provider = Extensions.ToCLREventPipeProvider("gc", clreventlevel);
+ Assert.True(provider.Name == CLRProviderName);
+ Assert.True(provider.Keywords == 1);
+ Assert.True(provider.EventLevel == System.Diagnostics.Tracing.EventLevel.Informational);
+ Assert.True(provider.Arguments == null);
+ }
+
+ [Theory]
+ [InlineData("something")]
+ [InlineData("hello")]
+ public void InvalidCLREventLevel(string clreventlevel)
+ {
+ Assert.Throws<ArgumentException>(() => Extensions.ToCLREventPipeProvider("gc", clreventlevel));
+ }
+ }
+}
\ No newline at end of file