From: mikelle-rogers <45022607+mikelle-rogers@users.noreply.github.com> Date: Wed, 1 Dec 2021 08:14:17 +0000 (-0700) Subject: initial pull request for dotnet-trace report (topN feature) (#2705) X-Git-Tag: accepted/tizen/unified/20221103.165810~28^2^2~151 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=3f3621829b5ed4dc2e87966b5f0cc61b1adf0632;p=platform%2Fcore%2Fdotnet%2Fdiagnostics.git initial pull request for dotnet-trace report (topN feature) (#2705) --- diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/ReportCommand.cs b/src/Tools/dotnet-trace/CommandLine/Commands/ReportCommand.cs new file mode 100644 index 000000000..c64540e79 --- /dev/null +++ b/src/Tools/dotnet-trace/CommandLine/Commands/ReportCommand.cs @@ -0,0 +1,156 @@ +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 unwantedMethodNames = new List() { "ROOT", "Process"}; + + //Create an extension function to help + public static List ByIDSortedInclusiveMetric(this CallTree callTree) + { + var ret = new List(callTree.ByID); + ret.Sort((x, y) => Math.Abs(y.InclusiveMetric).CompareTo(Math.Abs(x.InclusiveMetric))); + return ret; + } + delegate Task ReportDelegate(CancellationToken ct, IConsole console, string traceFile); + private static Task 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 TopNReportDelegate(CancellationToken ct, IConsole console, string traceFile, int n, bool inclusive, bool verbose); + private static async Task 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 nodesToReport = new List(); + 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 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 FileNameArgument() => + new Argument("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(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(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(name: "verbose", getDefaultValue: () => false) + }; + } +} \ No newline at end of file diff --git a/src/Tools/dotnet-trace/CommandLine/PrintReportHelper.cs b/src/Tools/dotnet-trace/CommandLine/PrintReportHelper.cs new file mode 100644 index 000000000..caa3d5d6d --- /dev/null +++ b/src/Tools/dotnet-trace/CommandLine/PrintReportHelper.cs @@ -0,0 +1,148 @@ +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 SplitInto(string str, int n) + { + int length = str.Length; + if(length < n) + { + string shortName = MakeFixedWidth(str, n); + + return new List {shortName}; + } + + if (String.IsNullOrEmpty(str) || n < 1) + { + throw new ArgumentException(); + } + IEnumerable uniformName = Enumerable.Range(0, length / n).Select(i => str.Substring(i * n, n)); + List strList = uniformName.ToList(); + int remainder = (length / n)*n; + strList.Add(str.Substring(remainder, length - remainder)); + return strList; + } + + + internal static void TopNWriteToStdOut(List 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 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 diff --git a/src/Tools/dotnet-trace/Program.cs b/src/Tools/dotnet-trace/Program.cs index 2b7f53815..d99260580 100644 --- a/src/Tools/dotnet-trace/Program.cs +++ b/src/Tools/dotnet-trace/Program.cs @@ -20,6 +20,7 @@ namespace Microsoft.Diagnostics.Tools.Trace .AddCommand(ProcessStatusCommandHandler.ProcessStatusCommand("Lists the dotnet processes that traces can be collected")) .AddCommand(ListProfilesCommandHandler.ListProfilesCommand()) .AddCommand(ConvertCommandHandler.ConvertCommand()) + .AddCommand(ReportCommandHandler.ReportCommand()) .UseDefaults() .Build(); ParseResult parseResult = parser.Parse(args);