Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --clrevents flag to dotnet-trace #738

Merged
Merged
Show file tree
Hide file tree
Changes from 4 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
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
44 changes: 39 additions & 5 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);

/// <summary>
/// Collects a diagnostic trace from a currently running process.
Expand All @@ -32,8 +32,10 @@ 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>
/// <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)
{
try
{
Expand All @@ -56,7 +58,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 +85,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);
providerCollection.Add(clrProvider);
enabledBy[Extensions.CLREventProviderName] = "--clrevents";
}
}


if (providerCollection.Count <= 0)
{
Expand Down Expand Up @@ -216,7 +234,6 @@ private static void PrintProviders(IReadOnlyList<EventPipeProvider> providers, D
{
Console.Out.WriteLine(String.Format("{0, -80}", $"{GetProviderDisplayString(provider)}") + $"{enabledBy[provider.Name]}");
}
Console.Out.WriteLine();
}
private static string GetProviderDisplayString(EventPipeProvider provider) =>
String.Format("{0, -40}", provider.Name) + String.Format("0x{0, -18}", $"{provider.Keywords:X16}") + String.Format("{0, -8}", provider.EventLevel.ToString() + $"({(int)provider.EventLevel})");
Expand Down Expand Up @@ -273,7 +290,8 @@ public static Command CollectCommand() =>
ProvidersOption(),
ProfileOption(),
CommonOptions.FormatOption(),
DurationOption()
DurationOption(),
CLREventsOption()
};

private static uint DefaultCircularBufferSizeInMB => 256;
Expand Down Expand Up @@ -320,5 +338,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() =>
sywhang marked this conversation as resolved.
Show resolved Hide resolved
new Option(
alias: "--clreventlevel",
description: @"Verbosity of CLR events to be emitted.")
{
Argument = new Argument<string>(name: "clreventlevel", defaultValue: "")
};
}
}
57 changes: 57 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This thought isn't in the scope of this PR, but you and I have made comments like this pretty routinely in our code. I think we may want to discuss a way to automate the generation of these types of constructs. The .man file is very highly structured so I imagine we could script around it easily.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah... It's also in PerfView. Maybe we can do something like the dependency update bot does for arcade? The main challenge I see is that there's bits of logic involved in every repo. The .man file is structured, but the .cs files that consume these from PerfView or diagnostics repo may not be. I'm always up for discussion on how to keep these consistent though!

private static Dictionary<string, long> CLREventKeywords = new Dictionary<string, long>()
{
{ "gc", 0x1 },
sywhang marked this conversation as resolved.
Show resolved Hide resolved
sywhang marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Could have PascalCase the keywords here :(

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i thought about it but since these keys won't be displayed on the console.... meh :D

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have expected them to show up on the --help instead of expecting users to go to the documentation in the repo.

{ "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,29 @@ public static List<EventPipeProvider> ToProviders(string providers)
new List<EventPipeProvider>() : providers.Split(',').Select(ToProvider).ToList();
}

public static EventPipeProvider ToCLREventPipeProvider(string clreventslist)
{
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");
}
}
return new EventPipeProvider(CLREventProviderName, (EventLevel)4, clrEventsKeywordsMask, null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the EventLevel be configurable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, the "dead code" above is supposed to handle this - will update this. Thanks for catching that!

}

private static EventLevel GetEventLevel(string token)
{
if (Int32.TryParse(token, out int level) && level >= 0)
Expand Down