--- /dev/null
+using Microsoft.Tools.Common;
+using System;
+using System.Collections.Generic;
+using System.CommandLine;
+using System.CommandLine.Binding;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Diagnostics.Tracing.Etlx;
+using Microsoft.Diagnostics.Symbols;
+using Microsoft.Diagnostics.Tracing.Stacks;
+using Microsoft.Diagnostics.Tracing;
+using Diagnostics.Tracing.StackSources;
+using Microsoft.Diagnostics.Tools.Trace.CommandLine;
+
+namespace Microsoft.Diagnostics.Tools.Trace
+{
+ internal static class ReportCommandHandler
+ {
+ static List<string> unwantedMethodNames = new List<string>() { "ROOT", "Process"};
+
+ //Create an extension function to help
+ public static List<CallTreeNodeBase> ByIDSortedInclusiveMetric(this CallTree callTree)
+ {
+ var ret = new List<CallTreeNodeBase>(callTree.ByID);
+ ret.Sort((x, y) => Math.Abs(y.InclusiveMetric).CompareTo(Math.Abs(x.InclusiveMetric)));
+ return ret;
+ }
+ delegate Task<int> ReportDelegate(CancellationToken ct, IConsole console, string traceFile);
+ private static Task<int> Report(CancellationToken ct, IConsole console, string traceFile)
+ {
+ Console.Error.WriteLine("Error: subcommand was not provided. Available subcommands:");
+ Console.Error.WriteLine(" topN: Finds the top N methods on the callstack the longest.");
+ return Task.FromResult(-1);
+ }
+
+ delegate Task<int> TopNReportDelegate(CancellationToken ct, IConsole console, string traceFile, int n, bool inclusive, bool verbose);
+ private static async Task<int> TopNReport(CancellationToken ct, IConsole console, string traceFile, int number, bool inclusive, bool verbose)
+ {
+ try
+ {
+ string tempEtlxFilename = TraceLog.CreateFromEventPipeDataFile(traceFile);
+ int count = 0;
+ int index = 0;
+ List<CallTreeNodeBase> nodesToReport = new List<CallTreeNodeBase>();
+ using (var symbolReader = new SymbolReader(System.IO.TextWriter.Null) { SymbolPath = SymbolPath.MicrosoftSymbolServerPath })
+ using (var eventLog = new TraceLog(tempEtlxFilename))
+ {
+ var stackSource = new MutableTraceEventStackSource(eventLog)
+ {
+ OnlyManagedCodeStacks = true
+ };
+
+ var computer = new SampleProfilerThreadTimeComputer(eventLog,symbolReader);
+
+ computer.GenerateThreadTimeStacks(stackSource);
+
+ FilterParams filterParams = new FilterParams()
+ {
+ FoldRegExs = "CPU_TIME;UNMANAGED_CODE_TIME;{Thread (}",
+ };
+ FilterStackSource filterStack = new FilterStackSource(filterParams, stackSource, ScalingPolicyKind.ScaleToData);
+ CallTree callTree = new(ScalingPolicyKind.ScaleToData);
+ callTree.StackSource = filterStack;
+
+ List<CallTreeNodeBase> callTreeNodes = null;
+
+ if(!inclusive)
+ {
+ callTreeNodes = callTree.ByIDSortedExclusiveMetric();
+ }
+ else
+ {
+ callTreeNodes = callTree.ByIDSortedInclusiveMetric();
+ }
+
+ int totalElements = callTreeNodes.Count;
+ while(count < number && index < totalElements)
+ {
+ CallTreeNodeBase node = callTreeNodes[index];
+ index++;
+ if(!unwantedMethodNames.Any(node.Name.Contains))
+ {
+ nodesToReport.Add(node);
+ count++;
+ }
+ }
+
+ PrintReportHelper.TopNWriteToStdOut(nodesToReport, inclusive, verbose);
+ }
+ return await Task.FromResult(0);
+ }
+ catch(Exception ex)
+ {
+ Console.Error.WriteLine($"[ERROR] {ex.ToString()}");
+ }
+
+ return await Task.FromResult(0);
+ }
+
+ public static Command ReportCommand() =>
+ new Command(
+ name: "report",
+ description: "Generates a report into stdout from a previously generated trace.")
+ {
+ //Handler
+ HandlerDescriptor.FromDelegate((ReportDelegate)Report).GetCommandHandler(),
+ //Options
+ FileNameArgument(),
+ new Command(
+ name: "topN",
+ description: "Finds the top N methods that have been on the callstack the longest.")
+ {
+ //Handler
+ HandlerDescriptor.FromDelegate((TopNReportDelegate)TopNReport).GetCommandHandler(),
+ TopNOption(),
+ InclusiveOption(),
+ VerboseOption(),
+ }
+ };
+
+ private static Argument<string> FileNameArgument() =>
+ new Argument<string>("trace_filename")
+ {
+ Name = "tracefile",
+ Description = "The file path for the trace being analyzed.",
+ Arity = new ArgumentArity(1, 1)
+ };
+
+ private static Option TopNOption()
+ {
+ return new Option(
+ aliases: new[] {"-n", "--number" },
+ description: $"Gives the top N methods on the callstack.")
+ {
+ Argument = new Argument<int>(name: "n", getDefaultValue: () => 5)
+ };
+ }
+
+ private static Option InclusiveOption() =>
+ new Option(
+ aliases: new[] { "--inclusive" },
+ description: $"Output the top N methods based on inclusive time. If not specified, exclusive time is used by default.")
+ {
+ Argument = new Argument<bool>(name: "inclusive", getDefaultValue: () => false)
+ };
+
+ private static Option VerboseOption() =>
+ new Option(
+ aliases: new[] {"-v", "--verbose"},
+ description: $"Output the parameters of each method in full. If not specified, parameters will be truncated.")
+ {
+ Argument = new Argument<bool>(name: "verbose", getDefaultValue: () => false)
+ };
+ }
+}
\ No newline at end of file
--- /dev/null
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Microsoft.Diagnostics.Tracing.Stacks;
+using System.Text.RegularExpressions;
+
+namespace Microsoft.Diagnostics.Tools.Trace.CommandLine
+{
+ internal static class PrintReportHelper
+ {
+ private static string MakeFixedWidth(string text, int width)
+ {
+ if(text.Length == width)
+ {
+ return text;
+ }
+ else if(text.Length > width)
+ {
+ return text.Substring(0, width);
+ }
+ else
+ {
+ return text += new string(' ', width - text.Length);
+ }
+ }
+
+ private static string FormatFunction(string name)
+ {
+ string classMethod;
+ Regex nameRx = new Regex(@"(.*\..*)(\(.*\))");
+ Match match = nameRx.Match(name);
+ string functionList = match.Groups[1].Value;
+ string arguments = match.Groups[2].Value;
+ string[] usingStatement = functionList.Split(".");
+ int length = usingStatement.Length;
+
+ if (length < 2)
+ {
+ if (length == 1)
+ {
+ classMethod = usingStatement[length - 1];
+ }
+ else
+ {
+ classMethod = usingStatement[length];
+ }
+ }
+ else
+ {
+ classMethod = usingStatement[length - 2] + "." + usingStatement[length - 1];
+ }
+ return classMethod + arguments;
+ }
+
+ public static List<string> SplitInto(string str, int n)
+ {
+ int length = str.Length;
+ if(length < n)
+ {
+ string shortName = MakeFixedWidth(str, n);
+
+ return new List<string> {shortName};
+ }
+
+ if (String.IsNullOrEmpty(str) || n < 1)
+ {
+ throw new ArgumentException();
+ }
+ IEnumerable<string> uniformName = Enumerable.Range(0, length / n).Select(i => str.Substring(i * n, n));
+ List<string> strList = uniformName.ToList<string>();
+ int remainder = (length / n)*n;
+ strList.Add(str.Substring(remainder, length - remainder));
+ return strList;
+ }
+
+
+ internal static void TopNWriteToStdOut(List<CallTreeNodeBase> nodesToReport, bool isInclusive, bool isVerbose)
+ {
+ const int functionColumnWidth = 70;
+ const int measureColumnWidth = 20;
+ string measureType = null;
+ if (isInclusive)
+ {
+ measureType = "Inclusive";
+ }
+ else
+ {
+ measureType = "Exclusive";
+ }
+
+ int n = nodesToReport.Count;
+ int maxDigit = n.ToString().Count();
+ string extra = new string(' ', maxDigit - 1);
+
+ string header = "Top " + n.ToString() + " Functions (" + measureType + ")";
+ string uniformHeader = MakeFixedWidth(header, functionColumnWidth+7);
+
+ string inclusive = "Inclusive";
+ string uniformInclusive = MakeFixedWidth(inclusive, measureColumnWidth);
+
+ string exclusive = "Exclusive";
+ string uniformExclusive = MakeFixedWidth(exclusive, measureColumnWidth);
+ Console.WriteLine(uniformHeader + extra + uniformInclusive + uniformExclusive);
+
+ int numLines;
+ for(int i = 0; i < n; i++)
+ {
+
+ int iLength = (i+1).ToString().Count();
+ int numSpace = maxDigit - iLength + 1;
+
+ CallTreeNodeBase node = nodesToReport[i];
+ string name = node.Name;
+ string formatName = FormatFunction(name);
+ List<string> nameList = SplitInto(formatName, functionColumnWidth);
+
+ if(isVerbose)
+ {
+ numLines = nameList.Count;
+ }
+ else
+ {
+ numLines = 1;
+ }
+
+ for(int j = 0; j < numLines; j++)
+ {
+ string inclusiveMeasure = "";
+ string exclusiveMeasure = "";
+ string number = new string(' ', maxDigit + 2); //+2 lines 130 and 137 account for '. '
+
+ if(j == 0)
+ {
+ inclusiveMeasure = Math.Round(node.InclusiveMetricPercent, 2).ToString() + "%";
+ exclusiveMeasure = Math.Round(node.ExclusiveMetricPercent, 2).ToString() + "%";
+ number = (i + 1).ToString() + "." + number.Substring(maxDigit - numSpace + 2);
+ }
+
+ string uniformIMeasure = MakeFixedWidth(inclusiveMeasure, measureColumnWidth).PadLeft(measureColumnWidth+4);
+ string uniformEMeasure = MakeFixedWidth(exclusiveMeasure, measureColumnWidth);
+ Console.WriteLine(number + nameList[j] + uniformIMeasure + uniformEMeasure);
+ }
+
+ }
+ }
+ }
+}
\ No newline at end of file