Skip to content

Commit

Permalink
Add --clrevents flag to dotnet-trace (#738)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
sywhang authored Jan 10, 2020
1 parent 96f21e4 commit b52e0f2
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 4 deletions.
36 changes: 36 additions & 0 deletions documentation/dotnet-trace-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
45 changes: 41 additions & 4 deletions src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -32,8 +32,11 @@ internal static class CollectCommandHandler
/// <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
{
Expand All @@ -56,7 +59,7 @@ private static async Task<int> Collect(CancellationToken ct, IConsole console, i
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";
Expand All @@ -83,6 +86,22 @@ private static async Task<int> Collect(CancellationToken ct, IConsole console, i
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)
{
Expand Down Expand Up @@ -284,7 +303,9 @@ public static Command CollectCommand() =>
ProvidersOption(),
ProfileOption(),
CommonOptions.FormatOption(),
DurationOption()
DurationOption(),
CLREventsOption(),
CLREventLevelOption()
};

private static uint DefaultCircularBufferSizeInMB => 256;
Expand Down Expand Up @@ -331,5 +352,21 @@ private static Option DurationOption() =>
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: "")
};
}
}
65 changes: 65 additions & 0 deletions src/Tools/dotnet-trace/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -22,6 +56,37 @@ 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)
Expand Down
74 changes: 74 additions & 0 deletions src/tests/dotnet-trace/CLRProviderParsing.cs
Original file line number Diff line number Diff line change
@@ -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));
}
}
}

0 comments on commit b52e0f2

Please sign in to comment.