Add Convert command (#196)
authorJohn Salem <josalem@microsoft.com>
Fri, 26 Apr 2019 23:34:01 +0000 (16:34 -0700)
committerGitHub <noreply@github.com>
Fri, 26 Apr 2019 23:34:01 +0000 (16:34 -0700)
* 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
src/Tools/dotnet-trace/CommandLine/Commands/CollectCommand.cs
src/Tools/dotnet-trace/CommandLine/Commands/ConvertCommand.cs [new file with mode: 0644]
src/Tools/dotnet-trace/CommandLine/Options/CommonOptions.cs
src/Tools/dotnet-trace/Program.cs
src/Tools/dotnet-trace/TraceFileFormatConverter.cs

index 39a5e80ee28c82d072afaeb7ab14dd5ec7a9355c..d65101f5d822dae3711d33acd59d33a39e50ead3 100644 (file)
@@ -255,7 +255,7 @@ COLLECT
                          [-o|--output <trace-file-path>]
                          [--profile <profile_name>]
                          [--providers <list-of-comma-separated-providers>]
-                         [-f|--format <trace-file-format>]
+                         [--format <trace-file-format>]
 
     Collects a diagnostic trace from a currently running process
 
@@ -293,8 +293,8 @@ COLLECT
     --buffersize <Size>
         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 <output_file_path>]
-                         [--to-speedscope]
+                         --format <format>
                          <trace_file_path>
 
     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 -speedscope
       Writing:       ./trace.speedscope.json
       Conversion complete
 
index 9343723a740e1a721c7df17fdc625f3f65c9831f..48316f947d32d95f653704d172432672a277564c 100644 (file)
@@ -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<uint>(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 (file)
index 0000000..f2a8034
--- /dev/null
@@ -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<FileInfo>(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<IConsole, FileInfo, TraceFileFormat, FileInfo>(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<FileInfo>() { Name = "output-filename" },
+                isHidden: false
+            );
+    }
+}
index c3fde6988a65849905466ad149151db09b09cdfe..e6095231b14e17f1a22c37bb06d9de67b153d99d 100644 (file)
@@ -17,12 +17,11 @@ namespace Microsoft.Diagnostics.Tools.Trace
                 argument: new Argument<int> { 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<TraceFileFormat>(defaultValue: DefaultTraceFileFormat) { Name = "trace-file-format" },
                 isHidden: false);
index c0099a2c094cb34e3d3802980272673a390f8c09..48d00fb3276b3de5dfd8007edd649e4ebdb5c27f 100644 (file)
@@ -19,6 +19,7 @@ namespace Microsoft.Diagnostics.Tools.Trace
                 .AddCommand(CollectCommandHandler.CollectCommand())
                 .AddCommand(ListProcessesCommandHandler.ListProcessesCommand())
                 .AddCommand(ListProfilesCommandHandler.ListProfilesCommand())
+                .AddCommand(ConvertCommandHandler.ConvertCommand())
                 .UseDefaults()
                 .Build();
 
index 66502c21c1ac11393ae5cd58f8b0d5df87d439d5..2aa791b13f076652fb164a1ec833f4e5b9d8e954 100644 (file)
@@ -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<TraceFileFormat, string> TraceFileFormatExtensions = new Dictionary<TraceFileFormat, string>() {
+            { 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);
             }
+            
         }
     }
 }