Add --clrevents flag to dotnet-trace (#738)
authorSung Yoon Whang <suwhang@microsoft.com>
Fri, 10 Jan 2020 01:23:47 +0000 (17:23 -0800)
committerGitHub <noreply@github.com>
Fri, 10 Jan 2020 01:23:47 +0000 (17:23 -0800)
* Add clrevents flag

* Ignore clrevents if providers is already specified

* Docs change

* Change the error message a little bit

* case insensitive comparison for keywords, add clreventlevel option

* Threw back in a line that was deleted accidentally

* Add test for CLR provider parsing

* use stringcomparer instead of tolower

documentation/dotnet-trace-instructions.md
src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs
src/Tools/dotnet-trace/Extensions.cs
src/tests/dotnet-trace/CLRProviderParsing.cs [new file with mode: 0644]

index 62ba14e752f6b0a9a9465cb572271a1de3ac5a6b..db623c59624e38b6a08a1b0bf75442324dfa3678 100644 (file)
@@ -175,6 +175,42 @@ Options:
         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.
 
index b28eab6e2897a3906281f070a19b768e2d3440a2..fc7f6f50b7ce0877609f553a22f2f3c406ff5fd4 100644 (file)
@@ -19,7 +19,7 @@ namespace Microsoft.Diagnostics.Tools.Trace
 {
     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.
@@ -32,8 +32,11 @@ namespace Microsoft.Diagnostics.Tools.Trace
         /// <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
             {
@@ -56,7 +59,7 @@ namespace Microsoft.Diagnostics.Tools.Trace
                     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";
@@ -83,6 +86,22 @@ namespace Microsoft.Diagnostics.Tools.Trace
                     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)
                 {
@@ -284,7 +303,9 @@ namespace Microsoft.Diagnostics.Tools.Trace
                 ProvidersOption(),
                 ProfileOption(),
                 CommonOptions.FormatOption(),
-                DurationOption()
+                DurationOption(),
+                CLREventsOption(),
+                CLREventLevelOption()
             };
 
         private static uint DefaultCircularBufferSizeInMB => 256;
@@ -331,5 +352,21 @@ namespace Microsoft.Diagnostics.Tools.Trace
                 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: "")
+            };
     }
 }
index 59b54664ec68b0243acadce7c35e6e9618d0503b..aa0c8bff1e968bf232cb97d899a11a245852d7a6 100644 (file)
@@ -12,7 +12,41 @@ namespace Microsoft.Diagnostics.Tools.Trace
 {
     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)
         {
@@ -22,6 +56,37 @@ namespace Microsoft.Diagnostics.Tools.Trace
                 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)
diff --git a/src/tests/dotnet-trace/CLRProviderParsing.cs b/src/tests/dotnet-trace/CLRProviderParsing.cs
new file mode 100644 (file)
index 0000000..b391f9f
--- /dev/null
@@ -0,0 +1,74 @@
+// 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