From 55dee32c79cfef5376b149283dcfad88df936a5c Mon Sep 17 00:00:00 2001 From: John Salem Date: Fri, 26 Apr 2019 16:34:01 -0700 Subject: [PATCH] Add Convert command (#196) * Add Convert command: * make default format for all platforms 'netperf' * update spec to refelct impl * Simplify try/catch to using statements in converter code * Account for scenario when someone doesn't specify an output filename and the input trace file doesn't have the default name * Grammar nit * Update enum value to use Pascal case * remove -f shorthand for format option * * remove try/catch * use ExistingOnly argument extension * remove unnecessary Arity qualifications --- documentation/design-docs/dotnet-tools.md | 17 +++--- .../CommandLine/Commands/CollectCommand.cs | 5 +- .../CommandLine/Commands/ConvertCommand.cs | 58 +++++++++++++++++++ .../CommandLine/Options/CommonOptions.cs | 5 +- src/Tools/dotnet-trace/Program.cs | 1 + .../dotnet-trace/TraceFileFormatConverter.cs | 52 +++++++++-------- 6 files changed, 100 insertions(+), 38 deletions(-) create mode 100644 src/Tools/dotnet-trace/CommandLine/Commands/ConvertCommand.cs diff --git a/documentation/design-docs/dotnet-tools.md b/documentation/design-docs/dotnet-tools.md index 39a5e80ee..d65101f5d 100644 --- a/documentation/design-docs/dotnet-tools.md +++ b/documentation/design-docs/dotnet-tools.md @@ -255,7 +255,7 @@ COLLECT [-o|--output ] [--profile ] [--providers ] - [-f|--format ] + [--format ] Collects a diagnostic trace from a currently running process @@ -293,8 +293,8 @@ COLLECT --buffersize Sets the size of the in-memory circular buffer in megabytes. Default 256 MB. - -f, --format - The format of the output trace file. This defaults to "netperf" on Windows and "speedscope" on other OSes. + --format + The format of the output trace file. The default value is netperf. Examples: @@ -307,7 +307,7 @@ CONVERT dotnet-trace convert [-h|--help] [-o|--output ] - [--to-speedscope] + --format Converts traces to alternate formats for use with alternate trace analysis tools @@ -319,15 +319,14 @@ CONVERT The path where the converted file is written. If unspecified the file is written in the current directory using the same base filename as the input file and the extension appropriate for the new format. - --to-speedscope - Indicates that the output file format should be speedscope JSON format used by https://www.speedscope.app/ - Currently this is the only supported format, but other options may be available in the future. + --format + Specifies the format to convert the netperf file to. Currently, the only valid input is 'speedscope'. trace_file_path - The path to the trace file that should be converted. The trace file can be in either a netperf or netperf.zip file. + The path to the trace file that should be converted. The trace file can be a netperf file. Defaults to 'trace.netperf'. Examples: - > dotnet-trace convert trace.netperf --to-speedscope + > dotnet-trace convert trace.netperf -f speedscope Writing: ./trace.speedscope.json Conversion complete diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs index 9343723a7..48316f947 100644 --- a/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs +++ b/src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs @@ -112,7 +112,8 @@ namespace Microsoft.Diagnostics.Tools.Trace Console.Out.WriteLine(); Console.Out.WriteLine("Trace completed."); - TraceFileFormatConverter.ConvertToFormat(format, output.FullName); + if (format != TraceFileFormat.Netperf) + TraceFileFormatConverter.ConvertToFormat(format, output.FullName); await Task.FromResult(0); return sessionId != 0 ? 0 : 1; @@ -193,7 +194,7 @@ namespace Microsoft.Diagnostics.Tools.Trace argument: new Argument(defaultValue: DefaultCircularBufferSizeInMB) { Name = "size" }, isHidden: false); - private static string DefaultTraceName => "trace.netperf"; + public static string DefaultTraceName => "trace.netperf"; private static Option OutputPathOption() => new Option( diff --git a/src/Tools/dotnet-trace/CommandLine/Commands/ConvertCommand.cs b/src/Tools/dotnet-trace/CommandLine/Commands/ConvertCommand.cs new file mode 100644 index 000000000..f2a803471 --- /dev/null +++ b/src/Tools/dotnet-trace/CommandLine/Commands/ConvertCommand.cs @@ -0,0 +1,58 @@ +// 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.Tools.RuntimeClient; +using System; +using System.IO; +using System.CommandLine; +using System.CommandLine.Builder; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; + +namespace Microsoft.Diagnostics.Tools.Trace +{ + internal static class ConvertCommandHandler + { + public static int ConvertFile(IConsole console, FileInfo inputFilename, TraceFileFormat format, FileInfo output) + { + if (format == TraceFileFormat.Netperf) + throw new ArgumentException("Cannot convert to netperf format."); + + if (!inputFilename.Exists) + throw new FileNotFoundException($"File '{inputFilename}' does not exist."); + + if (output == null) + output = inputFilename; + + TraceFileFormatConverter.ConvertToFormat(format, inputFilename.FullName, output.FullName); + + return 0; + } + + public static Command ConvertCommand() => + new Command( + name: "convert", + description: "Converts traces to alternate formats for use with alternate trace analysis tools. Can only convert from the netperf format.", + argument: (new Argument(defaultValue: new FileInfo(CollectCommandHandler.DefaultTraceName)) { + Name = "input-filename", + Description = $"Input trace file to be converted. Defaults to '{CollectCommandHandler.DefaultTraceName}'." + }).ExistingOnly(), + symbols: new Option[] { + CommonOptions.FormatOption(), + OutputOption() + }, + handler: System.CommandLine.Invocation.CommandHandler.Create(ConvertFile), + isHidden: false + ); + + public static Option OutputOption() => + new Option( + aliases: new [] { "-o", "--output" }, + description: "Output filename. Extension of target format will be added.", + argument: new Argument() { Name = "output-filename" }, + isHidden: false + ); + } +} diff --git a/src/Tools/dotnet-trace/CommandLine/Options/CommonOptions.cs b/src/Tools/dotnet-trace/CommandLine/Options/CommonOptions.cs index c3fde6988..e6095231b 100644 --- a/src/Tools/dotnet-trace/CommandLine/Options/CommonOptions.cs +++ b/src/Tools/dotnet-trace/CommandLine/Options/CommonOptions.cs @@ -17,12 +17,11 @@ namespace Microsoft.Diagnostics.Tools.Trace argument: new Argument { Name = "pid" }, isHidden: false); - public static TraceFileFormat DefaultTraceFileFormat => - RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? TraceFileFormat.netperf : TraceFileFormat.speedscope; + public static TraceFileFormat DefaultTraceFileFormat => TraceFileFormat.Netperf; public static Option FormatOption() => new Option( - aliases: new[] { "-f", "--format" }, + aliases: new[] { "--format" }, description: $"Sets the output format for the trace file. Default is {DefaultTraceFileFormat}", argument: new Argument(defaultValue: DefaultTraceFileFormat) { Name = "trace-file-format" }, isHidden: false); diff --git a/src/Tools/dotnet-trace/Program.cs b/src/Tools/dotnet-trace/Program.cs index c0099a2c0..48d00fb32 100644 --- a/src/Tools/dotnet-trace/Program.cs +++ b/src/Tools/dotnet-trace/Program.cs @@ -19,6 +19,7 @@ namespace Microsoft.Diagnostics.Tools.Trace .AddCommand(CollectCommandHandler.CollectCommand()) .AddCommand(ListProcessesCommandHandler.ListProcessesCommand()) .AddCommand(ListProfilesCommandHandler.ListProfilesCommand()) + .AddCommand(ConvertCommandHandler.ConvertCommand()) .UseDefaults() .Build(); diff --git a/src/Tools/dotnet-trace/TraceFileFormatConverter.cs b/src/Tools/dotnet-trace/TraceFileFormatConverter.cs index 66502c21c..2aa791b13 100644 --- a/src/Tools/dotnet-trace/TraceFileFormatConverter.cs +++ b/src/Tools/dotnet-trace/TraceFileFormatConverter.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.IO; using Microsoft.Diagnostics.Symbols; using Microsoft.Diagnostics.Tracing; @@ -12,34 +13,42 @@ using Microsoft.Diagnostics.Tracing.Stacks.Formats; namespace Microsoft.Diagnostics.Tools.Trace { - internal enum TraceFileFormat { netperf, speedscope }; + internal enum TraceFileFormat { Netperf, Speedscope }; internal static class TraceFileFormatConverter { - public static void ConvertToFormat(TraceFileFormat format, string fileToConvert) + private static Dictionary TraceFileFormatExtensions = new Dictionary() { + { TraceFileFormat.Netperf, "netperf" }, + { TraceFileFormat.Speedscope, "speedscope.json" } + }; + + public static void ConvertToFormat(TraceFileFormat format, string fileToConvert, string outputFilename = "") { + if (string.IsNullOrWhiteSpace(outputFilename)) + outputFilename = fileToConvert; + + outputFilename = Path.ChangeExtension(outputFilename, TraceFileFormatExtensions[format]); + Console.Out.WriteLine($"Writing:\t{outputFilename}"); + switch (format) { - case TraceFileFormat.netperf: + case TraceFileFormat.Netperf: break; - case TraceFileFormat.speedscope: - Console.Out.WriteLine($"Converting to {format}..."); - ConvertToSpeedscope(fileToConvert); + case TraceFileFormat.Speedscope: + ConvertToSpeedscope(fileToConvert, outputFilename); break; default: // Validation happened way before this, so we shoud never reach this... - throw new Exception($"Invalid TraceFileFormat \"{format}\""); + throw new ArgumentException($"Invalid TraceFileFormat \"{format}\""); } + Console.Out.WriteLine("Conversion complete"); } - private static void ConvertToSpeedscope(string fileToConvert) + private static void ConvertToSpeedscope(string fileToConvert, string outputFilename) { - var symbolReader = new SymbolReader(System.IO.TextWriter.Null) { SymbolPath = SymbolPath.MicrosoftSymbolServerPath }; var etlxFilePath = TraceLog.CreateFromEventPipeDataFile(fileToConvert); - - var eventLog = new TraceLog(etlxFilePath); - - try + using (var symbolReader = new SymbolReader(System.IO.TextWriter.Null) { SymbolPath = SymbolPath.MicrosoftSymbolServerPath }) + using (var eventLog = new TraceLog(etlxFilePath)) { var stackSource = new MutableTraceEventStackSource(eventLog) { @@ -47,21 +56,16 @@ namespace Microsoft.Diagnostics.Tools.Trace }; var computer = new SampleProfilerThreadTimeComputer(eventLog, symbolReader); - computer.GenerateThreadTimeStacks(stackSource); + computer.GenerateThreadTimeStacks(stackSource); - var speedScopeFilePath = Path.ChangeExtension(fileToConvert, "speedscope.json"); - - SpeedScopeStackSourceWriter.WriteStackViewAsJson(stackSource, speedScopeFilePath); + SpeedScopeStackSourceWriter.WriteStackViewAsJson(stackSource, outputFilename); } - finally - { - eventLog.Dispose(); - if (File.Exists(etlxFilePath)) - { - File.Delete(etlxFilePath); - } + if (File.Exists(etlxFilePath)) + { + File.Delete(etlxFilePath); } + } } } -- 2.34.1