From 61e9665ba7ae5d07b9d4f36fa5d98e36582159bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Nag=C3=B3rski?= Date: Thu, 25 Oct 2018 11:51:58 +0200 Subject: [PATCH 1/3] Improve Disassembly exporters and add PrettyGithubMarkdownDiff exporter --- .../Properties/launchSettings.json | 8 + .../Diagnosers/DisassemblyDiagnoser.cs | 3 +- ...tyGithubMarkdownDiffDisassemblyExporter.cs | 137 ++++++++++++++++++ ...PrettyGithubMarkdownDisassemblyExporter.cs | 76 ++-------- ...tyGithubMarkdownDisassemblyExporterBase.cs | 61 ++++++++ .../PrettyHtmlDisassemblyExporter.cs | 66 ++++----- .../Exporters/RawDisassemblyExporter.cs | 51 +++---- 7 files changed, 278 insertions(+), 124 deletions(-) create mode 100644 samples/BenchmarkDotNet.Samples/Properties/launchSettings.json create mode 100644 src/BenchmarkDotNet/Exporters/PrettyGithubMarkdownDiffDisassemblyExporter.cs create mode 100644 src/BenchmarkDotNet/Exporters/PrettyGithubMarkdownDisassemblyExporterBase.cs diff --git a/samples/BenchmarkDotNet.Samples/Properties/launchSettings.json b/samples/BenchmarkDotNet.Samples/Properties/launchSettings.json new file mode 100644 index 0000000000..1ae562a0e2 --- /dev/null +++ b/samples/BenchmarkDotNet.Samples/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "BenchmarkDotNet.Samples": { + "commandName": "Project", + "commandLineArgs": "--filter *IntroDisassemblyRyuJit*" + } + } +} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Diagnosers/DisassemblyDiagnoser.cs b/src/BenchmarkDotNet/Diagnosers/DisassemblyDiagnoser.cs index c0279e0f23..2ab7766117 100644 --- a/src/BenchmarkDotNet/Diagnosers/DisassemblyDiagnoser.cs +++ b/src/BenchmarkDotNet/Diagnosers/DisassemblyDiagnoser.cs @@ -37,7 +37,8 @@ private DisassemblyDiagnoser(WindowsDisassembler windowsDisassembler, MonoDisass new CombinedDisassemblyExporter(results), new RawDisassemblyExporter(results), new PrettyHtmlDisassemblyExporter(results), - new PrettyGithubMarkdownDisassemblyExporter(results) + new PrettyGithubMarkdownDisassemblyExporter(results), + new PrettyGithubMarkdownDiffDisassemblyExporter(results) }; } diff --git a/src/BenchmarkDotNet/Exporters/PrettyGithubMarkdownDiffDisassemblyExporter.cs b/src/BenchmarkDotNet/Exporters/PrettyGithubMarkdownDiffDisassemblyExporter.cs new file mode 100644 index 0000000000..846539ea97 --- /dev/null +++ b/src/BenchmarkDotNet/Exporters/PrettyGithubMarkdownDiffDisassemblyExporter.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Loggers; +using BenchmarkDotNet.Reports; +using BenchmarkDotNet.Running; +using StreamWriter = BenchmarkDotNet.Portability.StreamWriter; + +namespace BenchmarkDotNet.Exporters +{ + public class PrettyGithubMarkdownDiffDisassemblyExporter : PrettyGithubMarkdownDisassemblyExporterBase + { + private readonly IReadOnlyDictionary results; + + private static bool canRead; + + protected override string FileExtension => "md"; + protected override string FileCaption => "asm.pretty.diff"; + + public PrettyGithubMarkdownDiffDisassemblyExporter(IReadOnlyDictionary results) + { + this.results = results; + } + + public override void ExportToLog(Summary summary, ILogger logger) + { + var benchmarksCases = summary.BenchmarksCases + .Where(results.ContainsKey).ToList(); + + logger.WriteLine($"## {summary.Title}"); + + for (int i = 0; i < benchmarksCases.Count; i++) + { + var firstBenchmarkCase = benchmarksCases[i]; + for (int j = i + 1; j < benchmarksCases.Count; j++) + { + var secondBenchmarkCase = benchmarksCases[j]; + + var firstFileName = Export(summary, results[firstBenchmarkCase]); + var secondFileName = Export(summary, results[secondBenchmarkCase]); + try + { + var builder = new StringBuilder(); + + RunGitDiff(firstFileName, secondFileName, builder); + + if (firstBenchmarkCase.Descriptor.WorkloadMethod == secondBenchmarkCase.Descriptor.WorkloadMethod + ) // diff between the same method for different JITs + { + logger.WriteLine($"**Diff for {firstBenchmarkCase.Descriptor.WorkloadMethod.Name} method between:**"); + logger.WriteLine($"{GetImportantInfo(summary[firstBenchmarkCase])}"); + logger.WriteLine($"{GetImportantInfo(summary[secondBenchmarkCase])}"); + } + else // different methods, same JIT + { + logger.WriteLine( + $"**Diff between {firstBenchmarkCase.Descriptor.WorkloadMethod.Name} and {secondBenchmarkCase.Descriptor.WorkloadMethod.Name}**"); + logger.WriteLine($"on {GetImportantInfo(summary[firstBenchmarkCase])}."); + } + + logger.WriteLine("```diff"); + logger.WriteLine(builder.ToString()); + logger.WriteLine("```"); + } + finally + { + File.Delete(firstFileName); + File.Delete(secondFileName); + } + } + } + } + + private static string Export(Summary summary, DisassemblyResult disassemblyResult) + { + string filePath = $"{Path.Combine(summary.ResultsDirectoryPath, Guid.NewGuid().ToString())}-diff.temp"; + + if (File.Exists(filePath)) + File.Delete(filePath); + + using (var stream = StreamWriter.FromPath(filePath)) + { + Export(new StreamLogger(stream), disassemblyResult, quotingCode: false); + } + + return filePath; + } + + private static string GetImportantInfo(BenchmarkReport benchmarkReport) => benchmarkReport.GetRuntimeInfo(); + + private static void RunGitDiff(string firstFile, string secondFile, StringBuilder result) + { + var startInfo = new ProcessStartInfo + { + FileName = "git", + Arguments = $"diff --no-index --no-color --text {firstFile} {secondFile}", + UseShellExecute = false, + RedirectStandardOutput = true, + }; + canRead = false; + + try + { + using (var testProcess = Process.Start(startInfo)) + { + testProcess.OutputDataReceived += (s, e) => ProcessOutput(e.Data, result); + testProcess.BeginOutputReadLine(); + + testProcess.WaitForExit(); + } + } + catch (Exception ex) + { + result.AppendLine("An exception occurred during run Git. Please check if you have Git installed on your system and Git is added to PATH."); + result.AppendLine($"Exception: {ex.Message}"); + } + } + + private static void ProcessOutput(string line, StringBuilder result) + { + if (!string.IsNullOrEmpty(line) && line.Contains("@@")) + { + canRead = true; + return; + } + + if (canRead) + { + result.AppendLine(line); + } + } + } +} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Exporters/PrettyGithubMarkdownDisassemblyExporter.cs b/src/BenchmarkDotNet/Exporters/PrettyGithubMarkdownDisassemblyExporter.cs index 81042f5b8c..ebc2d59e7f 100644 --- a/src/BenchmarkDotNet/Exporters/PrettyGithubMarkdownDisassemblyExporter.cs +++ b/src/BenchmarkDotNet/Exporters/PrettyGithubMarkdownDisassemblyExporter.cs @@ -1,84 +1,36 @@ using System.Collections.Generic; -using System.IO; using System.Linq; using BenchmarkDotNet.Diagnosers; using BenchmarkDotNet.Loggers; using BenchmarkDotNet.Reports; using BenchmarkDotNet.Running; -using StreamWriter = BenchmarkDotNet.Portability.StreamWriter; namespace BenchmarkDotNet.Exporters { - public class PrettyGithubMarkdownDisassemblyExporter : IExporter + public class PrettyGithubMarkdownDisassemblyExporter : PrettyGithubMarkdownDisassemblyExporterBase { private readonly IReadOnlyDictionary results; - public PrettyGithubMarkdownDisassemblyExporter(IReadOnlyDictionary results) => this.results = results; - - public string Name => nameof(PrettyGithubMarkdownDisassemblyExporter); - - public void ExportToLog(Summary summary, ILogger logger) { } - - public IEnumerable ExportToFiles(Summary summary, ILogger consoleLogger) - => summary.BenchmarksCases - .Where(results.ContainsKey) - .Select(benchmark => Export(summary, benchmark)); - - private string Export(Summary summary, BenchmarkCase benchmarkCase) + public PrettyGithubMarkdownDisassemblyExporter(IReadOnlyDictionary results) { - string filePath = $"{Path.Combine(summary.ResultsDirectoryPath, benchmarkCase.FolderInfo)}-asm.pretty.md"; - if (File.Exists(filePath)) - File.Delete(filePath); - - using (var stream = StreamWriter.FromPath(filePath)) - { - Export(new StreamLogger(stream), results[benchmarkCase], benchmarkCase); - } - - return filePath; + this.results = results; } - private static void Export(ILogger logger, DisassemblyResult disassemblyResult, BenchmarkCase benchmarkCase) - { - int methodIndex = 0; - foreach (var method in disassemblyResult.Methods.Where(method => string.IsNullOrEmpty(method.Problem))) - { - logger.WriteLine("```assembly"); - logger.WriteLine($"; {method.Name}"); - - var pretty = DisassemblyPrettifier.Prettify(method, $"M{methodIndex++:00}"); - - uint totalSizeInBytes = 0; - foreach (var element in pretty) - { - if (element is DisassemblyPrettifier.Label label) - { - logger.WriteLine($"{label.TextRepresentation}:"); - - continue; - } - if (element.Source is Asm asm) - { - totalSizeInBytes += asm.SizeInBytes; - } - - logger.WriteLine($" {element.TextRepresentation}"); - } + protected override string FileExtension => "md"; + protected override string FileCaption => "asm.pretty"; - logger.WriteLine($"; Total bytes of code {totalSizeInBytes}"); - logger.WriteLine("```"); - } + public override void ExportToLog(Summary summary, ILogger logger) + { + var benchmarksCases = summary.BenchmarksCases + .Where(results.ContainsKey); - foreach (var withProblems in disassemblyResult.Methods - .Where(method => !string.IsNullOrEmpty(method.Problem)) - .GroupBy(method => method.Problem)) + foreach (var benchmarkCase in benchmarksCases) { - logger.WriteLine($"**{withProblems.Key}**"); - foreach (var withProblem in withProblems) - { - logger.WriteLine(withProblem.Name); - } + logger.WriteLine($"## {GetImportantInfo(summary[benchmarkCase])}"); + Export(logger, results[benchmarkCase]); } } + + private static string GetImportantInfo(BenchmarkReport benchmarkReport) => benchmarkReport.GetRuntimeInfo(); } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Exporters/PrettyGithubMarkdownDisassemblyExporterBase.cs b/src/BenchmarkDotNet/Exporters/PrettyGithubMarkdownDisassemblyExporterBase.cs new file mode 100644 index 0000000000..106b25b82c --- /dev/null +++ b/src/BenchmarkDotNet/Exporters/PrettyGithubMarkdownDisassemblyExporterBase.cs @@ -0,0 +1,61 @@ +using System.Linq; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Loggers; + +namespace BenchmarkDotNet.Exporters +{ + public abstract class PrettyGithubMarkdownDisassemblyExporterBase : ExporterBase + { + protected static void Export(ILogger logger, DisassemblyResult disassemblyResult, bool quotingCode = true) + { + int methodIndex = 0; + foreach (var method in disassemblyResult.Methods.Where(method => string.IsNullOrEmpty(method.Problem))) + { + if (quotingCode) + { + logger.WriteLine("```assembly"); + } + + logger.WriteLine($"; {method.Name}"); + + var pretty = DisassemblyPrettifier.Prettify(method, $"M{methodIndex++:00}"); + + uint totalSizeInBytes = 0; + foreach (var element in pretty) + { + if (element is DisassemblyPrettifier.Label label) + { + logger.WriteLine($"{label.TextRepresentation}:"); + + continue; + } + if (element.Source is Asm asm) + { + totalSizeInBytes += asm.SizeInBytes; + } + + logger.WriteLine($" {element.TextRepresentation}"); + } + + logger.WriteLine($"; Total bytes of code {totalSizeInBytes}"); + if (quotingCode) + { + logger.WriteLine("```"); + } + } + + foreach (var withProblems in disassemblyResult.Methods + .Where(method => !string.IsNullOrEmpty(method.Problem)) + .GroupBy(method => method.Problem)) + { + logger.WriteLine($"**{withProblems.Key}**"); + foreach (var withProblem in withProblems) + { + logger.WriteLine(withProblem.Name); + } + } + + logger.WriteLine(); + } + } +} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Exporters/PrettyHtmlDisassemblyExporter.cs b/src/BenchmarkDotNet/Exporters/PrettyHtmlDisassemblyExporter.cs index 37402723c2..784ab59026 100644 --- a/src/BenchmarkDotNet/Exporters/PrettyHtmlDisassemblyExporter.cs +++ b/src/BenchmarkDotNet/Exporters/PrettyHtmlDisassemblyExporter.cs @@ -1,64 +1,63 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using BenchmarkDotNet.Diagnosers; using BenchmarkDotNet.Helpers; using BenchmarkDotNet.Loggers; using BenchmarkDotNet.Reports; using BenchmarkDotNet.Running; -using StreamWriter = BenchmarkDotNet.Portability.StreamWriter; namespace BenchmarkDotNet.Exporters { - public class PrettyHtmlDisassemblyExporter : IExporter + public class PrettyHtmlDisassemblyExporter : ExporterBase { private static readonly Lazy HighlightingLabelsScript = new Lazy(() => ResourceHelper.LoadTemplate("highlightingLabelsScript.js")); - + private readonly IReadOnlyDictionary results; - public PrettyHtmlDisassemblyExporter(IReadOnlyDictionary results) => this.results = results; - - public string Name => nameof(PrettyHtmlDisassemblyExporter); - - public void ExportToLog(Summary summary, ILogger logger) { } + private static int referenceIndex; - public IEnumerable ExportToFiles(Summary summary, ILogger consoleLogger) - => summary.BenchmarksCases - .Where(results.ContainsKey) - .Select(benchmark => Export(summary, benchmark)); - - private string Export(Summary summary, BenchmarkCase benchmarkCase) + public PrettyHtmlDisassemblyExporter(IReadOnlyDictionary results) { - string filePath = $"{Path.Combine(summary.ResultsDirectoryPath, benchmarkCase.FolderInfo)}-asm.pretty.html"; - if (File.Exists(filePath)) - File.Delete(filePath); - - using (var stream = StreamWriter.FromPath(filePath)) - { - Export(new StreamLogger(stream), results[benchmarkCase], benchmarkCase); - } - - return filePath; + this.results = results; } - private static void Export(ILogger logger, DisassemblyResult disassemblyResult, BenchmarkCase benchmarkCase) + protected override string FileExtension => "html"; + protected override string FileCaption => "asm.pretty"; + + public override void ExportToLog(Summary summary, ILogger logger) { + var benchmarksCases = summary.BenchmarksCases + .Where(results.ContainsKey); + logger.WriteLine(""); - logger.WriteLine($"Pretty Output of DisassemblyDiagnoser for {benchmarkCase.DisplayInfo}"); + logger.WriteLine($"Pretty Output of DisassemblyDiagnoser for {summary.Title}"); logger.WriteLine(InstructionPointerExporter.CssStyle); logger.WriteLine(@" "); +"); logger.WriteLine(""); logger.WriteLine($""); - logger.WriteLine(""); - int methodIndex = 0, referenceIndex = 0; + foreach (var benchmarkCase in benchmarksCases) + { + Export(logger, summary, results[benchmarkCase], benchmarkCase); + } + + logger.WriteLine(""); + } + + private static void Export(ILogger logger, Summary summary, DisassemblyResult disassemblyResult, BenchmarkCase benchmarkCase) + { + logger.WriteLine($"

{GetImportantInfo(summary[benchmarkCase])}

"); + logger.WriteLine("
"); + + int methodIndex = 0; foreach (var method in disassemblyResult.Methods.Where(method => string.IsNullOrEmpty(method.Problem))) { + referenceIndex++; logger.WriteLine($""); var pretty = DisassemblyPrettifier.Prettify(method, $"M{methodIndex++:00}"); @@ -71,7 +70,7 @@ private static void Export(ILogger logger, DisassemblyResult disassemblyResult, even = !even; logger.WriteLine($""); - logger.WriteLine($""); + logger.WriteLine($""); logger.WriteLine(""); continue; @@ -83,7 +82,7 @@ private static void Export(ILogger logger, DisassemblyResult disassemblyResult, string tooltip = element.Source is Asm asm ? $"title=\"{asm.TextRepresentation}\"" : null; if (element is DisassemblyPrettifier.Reference reference) - logger.WriteLine($""); + logger.WriteLine($""); else logger.WriteLine($""); @@ -105,7 +104,8 @@ private static void Export(ILogger logger, DisassemblyResult disassemblyResult, logger.WriteLine(""); } - logger.WriteLine("
{method.Name}
{label.TextRepresentation}
{label.TextRepresentation}
 
{reference.TextRepresentation}
{reference.TextRepresentation}
{element.TextRepresentation}
"); + logger.WriteLine(""); } + private static string GetImportantInfo(BenchmarkReport benchmarkReport) => benchmarkReport.GetRuntimeInfo(); } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Exporters/RawDisassemblyExporter.cs b/src/BenchmarkDotNet/Exporters/RawDisassemblyExporter.cs index c1e036690a..9d8dff8149 100644 --- a/src/BenchmarkDotNet/Exporters/RawDisassemblyExporter.cs +++ b/src/BenchmarkDotNet/Exporters/RawDisassemblyExporter.cs @@ -1,51 +1,47 @@ using System.Collections.Generic; -using System.IO; using System.Linq; using System.Text; using BenchmarkDotNet.Diagnosers; using BenchmarkDotNet.Loggers; using BenchmarkDotNet.Reports; using BenchmarkDotNet.Running; -using StreamWriter = BenchmarkDotNet.Portability.StreamWriter; namespace BenchmarkDotNet.Exporters { - public class RawDisassemblyExporter : IExporter + public class RawDisassemblyExporter : ExporterBase { private readonly IReadOnlyDictionary results; - public RawDisassemblyExporter(IReadOnlyDictionary results) => this.results = results; - - public string Name => nameof(RawDisassemblyExporter); - - public void ExportToLog(Summary summary, ILogger logger) { } + public RawDisassemblyExporter(IReadOnlyDictionary results) + { + this.results = results; + } - public IEnumerable ExportToFiles(Summary summary, ILogger consoleLogger) - => summary.BenchmarksCases - .Where(results.ContainsKey) - .Select(benchmark => Export(summary, benchmark)); + protected override string FileExtension => "html"; + protected override string FileCaption => "asm.raw"; - private string Export(Summary summary, BenchmarkCase benchmarkCase) + public override void ExportToLog(Summary summary, ILogger logger) { - string filePath = $"{Path.Combine(summary.ResultsDirectoryPath, benchmarkCase.FolderInfo)}-asm.raw.html"; - if (File.Exists(filePath)) - File.Delete(filePath); + logger.WriteLine(""); + logger.WriteLine($"Output of DisassemblyDiagnoser for {summary.Title}"); + logger.WriteLine(InstructionPointerExporter.CssStyle); + logger.WriteLine(""); + logger.WriteLine(""); + + var benchmarksCases = summary.BenchmarksCases + .Where(results.ContainsKey); - using (var stream = StreamWriter.FromPath(filePath)) + foreach (var benchmarkCase in benchmarksCases) { - Export(new StreamLogger(stream), results[benchmarkCase], benchmarkCase); + Export(logger, summary, results[benchmarkCase], benchmarkCase); } - return filePath; + logger.WriteLine(""); } - private static void Export(ILogger logger, DisassemblyResult disassemblyResult, BenchmarkCase benchmarkCase) + private static void Export(ILogger logger, Summary summary, DisassemblyResult disassemblyResult, BenchmarkCase benchmarkCase) { - logger.WriteLine(""); - logger.WriteLine($"Output of DisassemblyDiagnoser for {benchmarkCase.DisplayInfo}"); - logger.WriteLine(InstructionPointerExporter.CssStyle); - logger.WriteLine(""); - logger.WriteLine(""); + logger.WriteLine($"

{GetImportantInfo(summary[benchmarkCase])}

"); logger.WriteLine(""); logger.WriteLine(""); @@ -104,11 +100,9 @@ private static void Export(ILogger logger, DisassemblyResult disassemblyResult, logger.WriteLine(""); } - logger.WriteLine("
"); + logger.WriteLine(""); } - - private static string GetShortName(string fullMethodSignature) { int bracketIndex = fullMethodSignature.IndexOf('('); @@ -141,5 +135,6 @@ internal static string FormatMethodAddress(ulong nativeCode) return buffer.ToString(); } + private static string GetImportantInfo(BenchmarkReport benchmarkReport) => benchmarkReport.GetRuntimeInfo(); } } \ No newline at end of file From 2f993682fb54880a41c57d10b12b810d260bfcc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Nag=C3=B3rski?= Date: Thu, 25 Oct 2018 11:55:27 +0200 Subject: [PATCH 2/3] Fix format and remove unused file --- .../Properties/launchSettings.json | 8 -------- .../PrettyGithubMarkdownDiffDisassemblyExporter.cs | 3 +-- 2 files changed, 1 insertion(+), 10 deletions(-) delete mode 100644 samples/BenchmarkDotNet.Samples/Properties/launchSettings.json diff --git a/samples/BenchmarkDotNet.Samples/Properties/launchSettings.json b/samples/BenchmarkDotNet.Samples/Properties/launchSettings.json deleted file mode 100644 index 1ae562a0e2..0000000000 --- a/samples/BenchmarkDotNet.Samples/Properties/launchSettings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "profiles": { - "BenchmarkDotNet.Samples": { - "commandName": "Project", - "commandLineArgs": "--filter *IntroDisassemblyRyuJit*" - } - } -} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Exporters/PrettyGithubMarkdownDiffDisassemblyExporter.cs b/src/BenchmarkDotNet/Exporters/PrettyGithubMarkdownDiffDisassemblyExporter.cs index 846539ea97..4345cd02a3 100644 --- a/src/BenchmarkDotNet/Exporters/PrettyGithubMarkdownDiffDisassemblyExporter.cs +++ b/src/BenchmarkDotNet/Exporters/PrettyGithubMarkdownDiffDisassemblyExporter.cs @@ -48,8 +48,7 @@ public override void ExportToLog(Summary summary, ILogger logger) RunGitDiff(firstFileName, secondFileName, builder); - if (firstBenchmarkCase.Descriptor.WorkloadMethod == secondBenchmarkCase.Descriptor.WorkloadMethod - ) // diff between the same method for different JITs + if (firstBenchmarkCase.Descriptor.WorkloadMethod == secondBenchmarkCase.Descriptor.WorkloadMethod) // diff between the same method for different JITs { logger.WriteLine($"**Diff for {firstBenchmarkCase.Descriptor.WorkloadMethod.Name} method between:**"); logger.WriteLine($"{GetImportantInfo(summary[firstBenchmarkCase])}"); From 8ead6e503348bd1a26ea523e4f08570c325fb8a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Nag=C3=B3rski?= Date: Tue, 30 Oct 2018 10:37:52 +0100 Subject: [PATCH 3/3] Fix review --- ...tyGithubMarkdownDiffDisassemblyExporter.cs | 124 ++++++++---------- ...PrettyGithubMarkdownDisassemblyExporter.cs | 61 ++++++++- ...tyGithubMarkdownDisassemblyExporterBase.cs | 61 --------- .../PrettyHtmlDisassemblyExporter.cs | 21 +-- .../Exporters/RawDisassemblyExporter.cs | 13 +- 5 files changed, 116 insertions(+), 164 deletions(-) delete mode 100644 src/BenchmarkDotNet/Exporters/PrettyGithubMarkdownDisassemblyExporterBase.cs diff --git a/src/BenchmarkDotNet/Exporters/PrettyGithubMarkdownDiffDisassemblyExporter.cs b/src/BenchmarkDotNet/Exporters/PrettyGithubMarkdownDiffDisassemblyExporter.cs index 4345cd02a3..c16a83b66c 100644 --- a/src/BenchmarkDotNet/Exporters/PrettyGithubMarkdownDiffDisassemblyExporter.cs +++ b/src/BenchmarkDotNet/Exporters/PrettyGithubMarkdownDiffDisassemblyExporter.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Helpers; using BenchmarkDotNet.Loggers; using BenchmarkDotNet.Reports; using BenchmarkDotNet.Running; @@ -12,69 +12,62 @@ namespace BenchmarkDotNet.Exporters { - public class PrettyGithubMarkdownDiffDisassemblyExporter : PrettyGithubMarkdownDisassemblyExporterBase + public class PrettyGithubMarkdownDiffDisassemblyExporter : ExporterBase { private readonly IReadOnlyDictionary results; - private static bool canRead; - protected override string FileExtension => "md"; protected override string FileCaption => "asm.pretty.diff"; - public PrettyGithubMarkdownDiffDisassemblyExporter(IReadOnlyDictionary results) - { - this.results = results; - } + public PrettyGithubMarkdownDiffDisassemblyExporter(IReadOnlyDictionary results) => this.results = results; public override void ExportToLog(Summary summary, ILogger logger) { - var benchmarksCases = summary.BenchmarksCases - .Where(results.ContainsKey).ToList(); + var benchmarksCases = summary.BenchmarksCases.Where(results.ContainsKey).ToArray(); logger.WriteLine($"## {summary.Title}"); - - for (int i = 0; i < benchmarksCases.Count; i++) + for (int i = 0; i < benchmarksCases.Length; i++) { var firstBenchmarkCase = benchmarksCases[i]; - for (int j = i + 1; j < benchmarksCases.Count; j++) + for (int j = i + 1; j < benchmarksCases.Length; j++) { var secondBenchmarkCase = benchmarksCases[j]; - var firstFileName = Export(summary, results[firstBenchmarkCase]); - var secondFileName = Export(summary, results[secondBenchmarkCase]); - try - { - var builder = new StringBuilder(); - - RunGitDiff(firstFileName, secondFileName, builder); - - if (firstBenchmarkCase.Descriptor.WorkloadMethod == secondBenchmarkCase.Descriptor.WorkloadMethod) // diff between the same method for different JITs - { - logger.WriteLine($"**Diff for {firstBenchmarkCase.Descriptor.WorkloadMethod.Name} method between:**"); - logger.WriteLine($"{GetImportantInfo(summary[firstBenchmarkCase])}"); - logger.WriteLine($"{GetImportantInfo(summary[secondBenchmarkCase])}"); - } - else // different methods, same JIT - { - logger.WriteLine( - $"**Diff between {firstBenchmarkCase.Descriptor.WorkloadMethod.Name} and {secondBenchmarkCase.Descriptor.WorkloadMethod.Name}**"); - logger.WriteLine($"on {GetImportantInfo(summary[firstBenchmarkCase])}."); - } - - logger.WriteLine("```diff"); - logger.WriteLine(builder.ToString()); - logger.WriteLine("```"); - } - finally - { - File.Delete(firstFileName); - File.Delete(secondFileName); - } + ExportDiff(summary, logger, firstBenchmarkCase, secondBenchmarkCase); + } + } + } + + private void ExportDiff(Summary summary, ILogger logger, BenchmarkCase firstBenchmarkCase, BenchmarkCase secondBenchmarkCase) + { + // We want to get diff for the same method and different JITs + if (firstBenchmarkCase.Descriptor.WorkloadMethod == secondBenchmarkCase.Descriptor.WorkloadMethod) + { + var firstFileName = SaveDisassemblyResult(summary, results[firstBenchmarkCase]); + var secondFileName = SaveDisassemblyResult(summary, results[secondBenchmarkCase]); + try + { + var builder = new StringBuilder(); + + RunGitDiff(firstFileName, secondFileName, builder); + + logger.WriteLine($"**Diff for {firstBenchmarkCase.Descriptor.WorkloadMethod.Name} method between:**"); + logger.WriteLine($"{GetImportantInfo(summary[firstBenchmarkCase])}"); + logger.WriteLine($"{GetImportantInfo(summary[secondBenchmarkCase])}"); + + logger.WriteLine("```diff"); + logger.WriteLine(builder.ToString()); + logger.WriteLine("```"); + } + finally + { + File.Delete(firstFileName); + File.Delete(secondFileName); } } } - private static string Export(Summary summary, DisassemblyResult disassemblyResult) + private static string SaveDisassemblyResult(Summary summary, DisassemblyResult disassemblyResult) { string filePath = $"{Path.Combine(summary.ResultsDirectoryPath, Guid.NewGuid().ToString())}-diff.temp"; @@ -83,7 +76,7 @@ private static string Export(Summary summary, DisassemblyResult disassemblyResul using (var stream = StreamWriter.FromPath(filePath)) { - Export(new StreamLogger(stream), disassemblyResult, quotingCode: false); + PrettyGithubMarkdownDisassemblyExporter.Export(new StreamLogger(stream), disassemblyResult, quotingCode: false); } return filePath; @@ -93,23 +86,24 @@ private static string Export(Summary summary, DisassemblyResult disassemblyResul private static void RunGitDiff(string firstFile, string secondFile, StringBuilder result) { - var startInfo = new ProcessStartInfo - { - FileName = "git", - Arguments = $"diff --no-index --no-color --text {firstFile} {secondFile}", - UseShellExecute = false, - RedirectStandardOutput = true, - }; - canRead = false; - try { - using (var testProcess = Process.Start(startInfo)) + var output = ProcessHelper.RunAndReadOutputLineByLine("git", $"diff --no-index --no-color --text {firstFile} {secondFile}"); + + bool canRead = false; + + foreach (string line in output) { - testProcess.OutputDataReceived += (s, e) => ProcessOutput(e.Data, result); - testProcess.BeginOutputReadLine(); + if (!string.IsNullOrEmpty(line) && line.Contains("@@")) + { + canRead = true; + continue; + } - testProcess.WaitForExit(); + if (canRead) + { + result.AppendLine(line); + } } } catch (Exception ex) @@ -118,19 +112,5 @@ private static void RunGitDiff(string firstFile, string secondFile, StringBuilde result.AppendLine($"Exception: {ex.Message}"); } } - - private static void ProcessOutput(string line, StringBuilder result) - { - if (!string.IsNullOrEmpty(line) && line.Contains("@@")) - { - canRead = true; - return; - } - - if (canRead) - { - result.AppendLine(line); - } - } } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Exporters/PrettyGithubMarkdownDisassemblyExporter.cs b/src/BenchmarkDotNet/Exporters/PrettyGithubMarkdownDisassemblyExporter.cs index ebc2d59e7f..a3b7314849 100644 --- a/src/BenchmarkDotNet/Exporters/PrettyGithubMarkdownDisassemblyExporter.cs +++ b/src/BenchmarkDotNet/Exporters/PrettyGithubMarkdownDisassemblyExporter.cs @@ -7,7 +7,7 @@ namespace BenchmarkDotNet.Exporters { - public class PrettyGithubMarkdownDisassemblyExporter : PrettyGithubMarkdownDisassemblyExporterBase + public class PrettyGithubMarkdownDisassemblyExporter : ExporterBase { private readonly IReadOnlyDictionary results; @@ -21,16 +21,63 @@ public PrettyGithubMarkdownDisassemblyExporter(IReadOnlyDictionary benchmarkReport.GetRuntimeInfo(); + internal static void Export(ILogger logger, DisassemblyResult disassemblyResult, bool quotingCode = true) + { + int methodIndex = 0; + foreach (var method in disassemblyResult.Methods.Where(method => string.IsNullOrEmpty(method.Problem))) + { + if (quotingCode) + { + logger.WriteLine("```assembly"); + } + + logger.WriteLine($"; {method.Name}"); + + var pretty = DisassemblyPrettifier.Prettify(method, $"M{methodIndex++:00}"); + + uint totalSizeInBytes = 0; + foreach (var element in pretty) + { + if (element is DisassemblyPrettifier.Label label) + { + logger.WriteLine($"{label.TextRepresentation}:"); + + continue; + } + if (element.Source is Asm asm) + { + totalSizeInBytes += asm.SizeInBytes; + } + + logger.WriteLine($" {element.TextRepresentation}"); + } + + logger.WriteLine($"; Total bytes of code {totalSizeInBytes}"); + if (quotingCode) + { + logger.WriteLine("```"); + } + } + + foreach (var withProblems in disassemblyResult.Methods + .Where(method => !string.IsNullOrEmpty(method.Problem)) + .GroupBy(method => method.Problem)) + { + logger.WriteLine($"**{withProblems.Key}**"); + foreach (var withProblem in withProblems) + { + logger.WriteLine(withProblem.Name); + } + } + + logger.WriteLine(); + } } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Exporters/PrettyGithubMarkdownDisassemblyExporterBase.cs b/src/BenchmarkDotNet/Exporters/PrettyGithubMarkdownDisassemblyExporterBase.cs deleted file mode 100644 index 106b25b82c..0000000000 --- a/src/BenchmarkDotNet/Exporters/PrettyGithubMarkdownDisassemblyExporterBase.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Linq; -using BenchmarkDotNet.Diagnosers; -using BenchmarkDotNet.Loggers; - -namespace BenchmarkDotNet.Exporters -{ - public abstract class PrettyGithubMarkdownDisassemblyExporterBase : ExporterBase - { - protected static void Export(ILogger logger, DisassemblyResult disassemblyResult, bool quotingCode = true) - { - int methodIndex = 0; - foreach (var method in disassemblyResult.Methods.Where(method => string.IsNullOrEmpty(method.Problem))) - { - if (quotingCode) - { - logger.WriteLine("```assembly"); - } - - logger.WriteLine($"; {method.Name}"); - - var pretty = DisassemblyPrettifier.Prettify(method, $"M{methodIndex++:00}"); - - uint totalSizeInBytes = 0; - foreach (var element in pretty) - { - if (element is DisassemblyPrettifier.Label label) - { - logger.WriteLine($"{label.TextRepresentation}:"); - - continue; - } - if (element.Source is Asm asm) - { - totalSizeInBytes += asm.SizeInBytes; - } - - logger.WriteLine($" {element.TextRepresentation}"); - } - - logger.WriteLine($"; Total bytes of code {totalSizeInBytes}"); - if (quotingCode) - { - logger.WriteLine("```"); - } - } - - foreach (var withProblems in disassemblyResult.Methods - .Where(method => !string.IsNullOrEmpty(method.Problem)) - .GroupBy(method => method.Problem)) - { - logger.WriteLine($"**{withProblems.Key}**"); - foreach (var withProblem in withProblems) - { - logger.WriteLine(withProblem.Name); - } - } - - logger.WriteLine(); - } - } -} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Exporters/PrettyHtmlDisassemblyExporter.cs b/src/BenchmarkDotNet/Exporters/PrettyHtmlDisassemblyExporter.cs index 784ab59026..8c473d2d7b 100644 --- a/src/BenchmarkDotNet/Exporters/PrettyHtmlDisassemblyExporter.cs +++ b/src/BenchmarkDotNet/Exporters/PrettyHtmlDisassemblyExporter.cs @@ -15,21 +15,13 @@ public class PrettyHtmlDisassemblyExporter : ExporterBase private readonly IReadOnlyDictionary results; - private static int referenceIndex; - - public PrettyHtmlDisassemblyExporter(IReadOnlyDictionary results) - { - this.results = results; - } + public PrettyHtmlDisassemblyExporter(IReadOnlyDictionary results) => this.results = results; protected override string FileExtension => "html"; protected override string FileCaption => "asm.pretty"; public override void ExportToLog(Summary summary, ILogger logger) { - var benchmarksCases = summary.BenchmarksCases - .Where(results.ContainsKey); - logger.WriteLine(""); logger.WriteLine($"Pretty Output of DisassemblyDiagnoser for {summary.Title}"); logger.WriteLine(InstructionPointerExporter.CssStyle); @@ -41,17 +33,19 @@ public override void ExportToLog(Summary summary, ILogger logger) logger.WriteLine(""); logger.WriteLine($""); - foreach (var benchmarkCase in benchmarksCases) + int referenceIndex = 0; + + foreach (var benchmarkCase in summary.BenchmarksCases.Where(results.ContainsKey)) { - Export(logger, summary, results[benchmarkCase], benchmarkCase); + Export(logger, summary, results[benchmarkCase], benchmarkCase, ref referenceIndex); } logger.WriteLine(""); } - private static void Export(ILogger logger, Summary summary, DisassemblyResult disassemblyResult, BenchmarkCase benchmarkCase) + private static void Export(ILogger logger, Summary summary, DisassemblyResult disassemblyResult, BenchmarkCase benchmarkCase, ref int referenceIndex) { - logger.WriteLine($"

{GetImportantInfo(summary[benchmarkCase])}

"); + logger.WriteLine($"

{summary[benchmarkCase].GetRuntimeInfo()}

"); logger.WriteLine(""); int methodIndex = 0; @@ -106,6 +100,5 @@ private static void Export(ILogger logger, Summary summary, DisassemblyResult di logger.WriteLine("
"); } - private static string GetImportantInfo(BenchmarkReport benchmarkReport) => benchmarkReport.GetRuntimeInfo(); } } \ No newline at end of file diff --git a/src/BenchmarkDotNet/Exporters/RawDisassemblyExporter.cs b/src/BenchmarkDotNet/Exporters/RawDisassemblyExporter.cs index 9d8dff8149..dcba3c236b 100644 --- a/src/BenchmarkDotNet/Exporters/RawDisassemblyExporter.cs +++ b/src/BenchmarkDotNet/Exporters/RawDisassemblyExporter.cs @@ -12,10 +12,7 @@ public class RawDisassemblyExporter : ExporterBase { private readonly IReadOnlyDictionary results; - public RawDisassemblyExporter(IReadOnlyDictionary results) - { - this.results = results; - } + public RawDisassemblyExporter(IReadOnlyDictionary results) => this.results = results; protected override string FileExtension => "html"; protected override string FileCaption => "asm.raw"; @@ -28,10 +25,7 @@ public override void ExportToLog(Summary summary, ILogger logger) logger.WriteLine(""); logger.WriteLine(""); - var benchmarksCases = summary.BenchmarksCases - .Where(results.ContainsKey); - - foreach (var benchmarkCase in benchmarksCases) + foreach (var benchmarkCase in summary.BenchmarksCases.Where(results.ContainsKey)) { Export(logger, summary, results[benchmarkCase], benchmarkCase); } @@ -41,7 +35,7 @@ public override void ExportToLog(Summary summary, ILogger logger) private static void Export(ILogger logger, Summary summary, DisassemblyResult disassemblyResult, BenchmarkCase benchmarkCase) { - logger.WriteLine($"

{GetImportantInfo(summary[benchmarkCase])}

"); + logger.WriteLine($"

{summary[benchmarkCase].GetRuntimeInfo()}

"); logger.WriteLine(""); logger.WriteLine(""); @@ -135,6 +129,5 @@ internal static string FormatMethodAddress(ulong nativeCode) return buffer.ToString(); } - private static string GetImportantInfo(BenchmarkReport benchmarkReport) => benchmarkReport.GetRuntimeInfo(); } } \ No newline at end of file