diff --git a/samples/BenchmarkDotNet.Samples/IntroDotMemoryDiagnoser.cs b/samples/BenchmarkDotNet.Samples/IntroDotMemoryDiagnoser.cs index 846e2178f4..894bfc6c34 100644 --- a/samples/BenchmarkDotNet.Samples/IntroDotMemoryDiagnoser.cs +++ b/samples/BenchmarkDotNet.Samples/IntroDotMemoryDiagnoser.cs @@ -4,16 +4,10 @@ namespace BenchmarkDotNet.Samples { - // Enables dotMemory profiling for all jobs + // Profile benchmarks via dotMemory SelfApi profiling for all jobs [DotMemoryDiagnoser] - // Adds the default "external-process" job - // Profiling is performed using dotMemory Command-Line Profiler - // See: https://www.jetbrains.com/help/dotmemory/Working_with_dotMemory_Command-Line_Profiler.html - [SimpleJob] - // Adds an "in-process" job - // Profiling is performed using dotMemory SelfApi - // NuGet reference: https://www.nuget.org/packages/JetBrains.Profiler.SelfApi - [InProcess] + [SimpleJob] // external-process execution + [InProcess] // in-process execution public class IntroDotMemoryDiagnoser { [Params(1024)] diff --git a/samples/BenchmarkDotNet.Samples/IntroDotTraceDiagnoser.cs b/samples/BenchmarkDotNet.Samples/IntroDotTraceDiagnoser.cs index 351207c78b..047e6ee059 100644 --- a/samples/BenchmarkDotNet.Samples/IntroDotTraceDiagnoser.cs +++ b/samples/BenchmarkDotNet.Samples/IntroDotTraceDiagnoser.cs @@ -3,16 +3,11 @@ namespace BenchmarkDotNet.Samples { - // Enables dotTrace profiling for all jobs + // Profile benchmarks via dotTrace SelfApi profiling for all jobs + // See: https://www.nuget.org/packages/JetBrains.Profiler.SelfApi [DotTraceDiagnoser] - // Adds the default "external-process" job - // Profiling is performed using dotTrace command-line Tools - // See: https://www.jetbrains.com/help/profiler/Performance_Profiling__Profiling_Using_the_Command_Line.html - [SimpleJob] - // Adds an "in-process" job - // Profiling is performed using dotTrace SelfApi - // NuGet reference: https://www.nuget.org/packages/JetBrains.Profiler.SelfApi - [InProcess] + [SimpleJob] // external-process execution + [InProcess] // in-process execution public class IntroDotTraceDiagnoser { [Benchmark] diff --git a/src/BenchmarkDotNet.Diagnostics.dotMemory/DotMemoryDiagnoser.cs b/src/BenchmarkDotNet.Diagnostics.dotMemory/DotMemoryDiagnoser.cs index 5639aa0580..c97f066fa4 100644 --- a/src/BenchmarkDotNet.Diagnostics.dotMemory/DotMemoryDiagnoser.cs +++ b/src/BenchmarkDotNet.Diagnostics.dotMemory/DotMemoryDiagnoser.cs @@ -1,148 +1,121 @@ using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using BenchmarkDotNet.Analysers; +using System.Reflection; using BenchmarkDotNet.Detectors; using BenchmarkDotNet.Diagnosers; -using BenchmarkDotNet.Engines; -using BenchmarkDotNet.Exporters; +using BenchmarkDotNet.Helpers; using BenchmarkDotNet.Jobs; -using BenchmarkDotNet.Loggers; -using BenchmarkDotNet.Reports; -using BenchmarkDotNet.Running; -using BenchmarkDotNet.Validators; -using RunMode = BenchmarkDotNet.Diagnosers.RunMode; +using JetBrains.Profiler.SelfApi; -namespace BenchmarkDotNet.Diagnostics.dotMemory +namespace BenchmarkDotNet.Diagnostics.dotMemory; + +public class DotMemoryDiagnoser(Uri? nugetUrl = null, string? downloadTo = null) : SnapshotProfilerBase { - public class DotMemoryDiagnoser(Uri? nugetUrl = null, string? toolsDownloadFolder = null) : IProfiler + public override string ShortName => "dotMemory"; + + protected override void InitTool(Progress progress) { - private DotMemoryTool? tool; + DotMemory.InitAsync(progress, nugetUrl, NuGetApi.V3, downloadTo).Wait(); + } - public IEnumerable Ids => new[] { "DotMemory" }; - public string ShortName => "dotMemory"; + protected override void AttachToCurrentProcess(string snapshotFile) + { + DotMemory.Attach(new DotMemory.Config().SaveToFile(snapshotFile)); + } - public RunMode GetRunMode(BenchmarkCase benchmarkCase) - { - return IsSupported(benchmarkCase.Job.Environment.GetRuntime().RuntimeMoniker) ? RunMode.ExtraRun : RunMode.None; - } + protected override void AttachToProcessByPid(int pid, string snapshotFile) + { + DotMemory.Attach(new DotMemory.Config().ProfileExternalProcess(pid).SaveToFile(snapshotFile)); + } - private readonly List snapshotFilePaths = new (); + protected override void TakeSnapshot() + { + DotMemory.GetSnapshot(); + } - public void Handle(HostSignal signal, DiagnoserActionParameters parameters) - { - var logger = parameters.Config.GetCompositeLogger(); - var job = parameters.BenchmarkCase.Job; + protected override void Detach() + { + DotMemory.Detach(); + } - var runtimeMoniker = job.Environment.GetRuntime().RuntimeMoniker; - if (!IsSupported(runtimeMoniker)) - { - logger.WriteLineError($"Runtime '{runtimeMoniker}' is not supported by dotMemory"); - return; - } + protected override string CreateSnapshotFilePath(DiagnoserActionParameters parameters) + { + return ArtifactFileNameHelper.GetFilePath(parameters, "snapshots", DateTime.Now, "dmw", ".0000".Length); + } - switch (signal) - { - case HostSignal.BeforeAnythingElse: - if (tool is null) - { - tool = new DotMemoryTool(logger, nugetUrl, downloadTo: toolsDownloadFolder); - tool.Init(); - } - break; - case HostSignal.BeforeActualRun: - if (tool is null) - throw new InvalidOperationException("DotMemory tool is not initialized"); - snapshotFilePaths.Add(tool.Start(parameters)); - break; - case HostSignal.AfterActualRun: - if (tool is null) - throw new InvalidOperationException("DotMemory tool is not initialized"); - tool.Stop(); - tool = null; - break; - } - } + protected override string GetRunnerPath() + { + var consoleRunnerPackageField = typeof(DotMemory).GetField("ConsoleRunnerPackage", BindingFlags.NonPublic | BindingFlags.Static); + if (consoleRunnerPackageField == null) + throw new InvalidOperationException("Field 'ConsoleRunnerPackage' not found."); - public IEnumerable Exporters => Enumerable.Empty(); - public IEnumerable Analysers => Enumerable.Empty(); + object? consoleRunnerPackage = consoleRunnerPackageField.GetValue(null); + if (consoleRunnerPackage == null) + throw new InvalidOperationException("Unable to get value of 'ConsoleRunnerPackage'."); - public IEnumerable Validate(ValidationParameters validationParameters) - { - var runtimeMonikers = validationParameters.Benchmarks.Select(b => b.Job.Environment.GetRuntime().RuntimeMoniker).Distinct(); - foreach (var runtimeMoniker in runtimeMonikers) - { - if (!IsSupported(runtimeMoniker)) - yield return new ValidationError(true, $"Runtime '{runtimeMoniker}' is not supported by dotMemory"); - } - } + var consoleRunnerPackageType = consoleRunnerPackage.GetType(); + var getRunnerPathMethod = consoleRunnerPackageType.GetMethod("GetRunnerPath"); + if (getRunnerPathMethod == null) + throw new InvalidOperationException("Method 'GetRunnerPath' not found."); - internal static bool IsSupported(RuntimeMoniker runtimeMoniker) - { - switch (runtimeMoniker) - { - case RuntimeMoniker.HostProcess: - case RuntimeMoniker.Net461: - case RuntimeMoniker.Net462: - case RuntimeMoniker.Net47: - case RuntimeMoniker.Net471: - case RuntimeMoniker.Net472: - case RuntimeMoniker.Net48: - case RuntimeMoniker.Net481: - case RuntimeMoniker.Net50: - case RuntimeMoniker.Net60: - case RuntimeMoniker.Net70: - case RuntimeMoniker.Net80: - case RuntimeMoniker.Net90: - return true; - case RuntimeMoniker.NotRecognized: - case RuntimeMoniker.Mono: - case RuntimeMoniker.NativeAot60: - case RuntimeMoniker.NativeAot70: - case RuntimeMoniker.NativeAot80: - case RuntimeMoniker.NativeAot90: - case RuntimeMoniker.Wasm: - case RuntimeMoniker.WasmNet50: - case RuntimeMoniker.WasmNet60: - case RuntimeMoniker.WasmNet70: - case RuntimeMoniker.WasmNet80: - case RuntimeMoniker.WasmNet90: - case RuntimeMoniker.MonoAOTLLVM: - case RuntimeMoniker.MonoAOTLLVMNet60: - case RuntimeMoniker.MonoAOTLLVMNet70: - case RuntimeMoniker.MonoAOTLLVMNet80: - case RuntimeMoniker.MonoAOTLLVMNet90: - case RuntimeMoniker.Mono60: - case RuntimeMoniker.Mono70: - case RuntimeMoniker.Mono80: - case RuntimeMoniker.Mono90: -#pragma warning disable CS0618 // Type or member is obsolete - case RuntimeMoniker.NetCoreApp50: -#pragma warning restore CS0618 // Type or member is obsolete - return false; - case RuntimeMoniker.NetCoreApp20: - case RuntimeMoniker.NetCoreApp21: - case RuntimeMoniker.NetCoreApp22: - return OsDetector.IsWindows(); - case RuntimeMoniker.NetCoreApp30: - case RuntimeMoniker.NetCoreApp31: - return OsDetector.IsWindows() || OsDetector.IsLinux(); - default: - throw new ArgumentOutOfRangeException(nameof(runtimeMoniker), runtimeMoniker, $"Runtime moniker {runtimeMoniker} is not supported"); - } - } + string? runnerPath = getRunnerPathMethod.Invoke(consoleRunnerPackage, null) as string; + if (runnerPath == null) + throw new InvalidOperationException("Unable to invoke 'GetRunnerPath'."); - public IEnumerable ProcessResults(DiagnoserResults results) => ImmutableArray.Empty; + return runnerPath; + } - public void DisplayResults(ILogger logger) + internal override bool IsSupported(RuntimeMoniker runtimeMoniker) + { + switch (runtimeMoniker) { - if (snapshotFilePaths.Any()) - { - logger.WriteLineInfo("The following dotMemory snapshots were generated:"); - foreach (string snapshotFilePath in snapshotFilePaths) - logger.WriteLineInfo($"* {snapshotFilePath}"); - } + case RuntimeMoniker.HostProcess: + case RuntimeMoniker.Net461: + case RuntimeMoniker.Net462: + case RuntimeMoniker.Net47: + case RuntimeMoniker.Net471: + case RuntimeMoniker.Net472: + case RuntimeMoniker.Net48: + case RuntimeMoniker.Net481: + case RuntimeMoniker.Net50: + case RuntimeMoniker.Net60: + case RuntimeMoniker.Net70: + case RuntimeMoniker.Net80: + case RuntimeMoniker.Net90: + return true; + case RuntimeMoniker.NotRecognized: + case RuntimeMoniker.Mono: + case RuntimeMoniker.NativeAot60: + case RuntimeMoniker.NativeAot70: + case RuntimeMoniker.NativeAot80: + case RuntimeMoniker.NativeAot90: + case RuntimeMoniker.Wasm: + case RuntimeMoniker.WasmNet50: + case RuntimeMoniker.WasmNet60: + case RuntimeMoniker.WasmNet70: + case RuntimeMoniker.WasmNet80: + case RuntimeMoniker.WasmNet90: + case RuntimeMoniker.MonoAOTLLVM: + case RuntimeMoniker.MonoAOTLLVMNet60: + case RuntimeMoniker.MonoAOTLLVMNet70: + case RuntimeMoniker.MonoAOTLLVMNet80: + case RuntimeMoniker.MonoAOTLLVMNet90: + case RuntimeMoniker.Mono60: + case RuntimeMoniker.Mono70: + case RuntimeMoniker.Mono80: + case RuntimeMoniker.Mono90: +#pragma warning disable CS0618 // Type or member is obsolete + case RuntimeMoniker.NetCoreApp50: +#pragma warning restore CS0618 // Type or member is obsolete + return false; + case RuntimeMoniker.NetCoreApp20: + case RuntimeMoniker.NetCoreApp21: + case RuntimeMoniker.NetCoreApp22: + return OsDetector.IsWindows(); + case RuntimeMoniker.NetCoreApp30: + case RuntimeMoniker.NetCoreApp31: + return OsDetector.IsWindows() || OsDetector.IsLinux(); + default: + throw new ArgumentOutOfRangeException(nameof(runtimeMoniker), runtimeMoniker, $"Runtime moniker {runtimeMoniker} is not supported"); } } } \ No newline at end of file diff --git a/src/BenchmarkDotNet.Diagnostics.dotMemory/DotMemoryDiagnoserAttribute.cs b/src/BenchmarkDotNet.Diagnostics.dotMemory/DotMemoryDiagnoserAttribute.cs index f9a3612471..c0fb55d4f1 100644 --- a/src/BenchmarkDotNet.Diagnostics.dotMemory/DotMemoryDiagnoserAttribute.cs +++ b/src/BenchmarkDotNet.Diagnostics.dotMemory/DotMemoryDiagnoserAttribute.cs @@ -1,22 +1,22 @@ using System; using BenchmarkDotNet.Configs; -namespace BenchmarkDotNet.Diagnostics.dotMemory +namespace BenchmarkDotNet.Diagnostics.dotMemory; + +[AttributeUsage(AttributeTargets.Class)] +public class DotMemoryDiagnoserAttribute : Attribute, IConfigSource { - [AttributeUsage(AttributeTargets.Class)] - public class DotMemoryDiagnoserAttribute : Attribute, IConfigSource - { - public IConfig Config { get; } + public IConfig Config { get; } - public DotMemoryDiagnoserAttribute() - { - Config = ManualConfig.CreateEmpty().AddDiagnoser(new DotMemoryDiagnoser()); - } + public DotMemoryDiagnoserAttribute() + { + var diagnoser = new DotMemoryDiagnoser(); + Config = ManualConfig.CreateEmpty().AddDiagnoser(diagnoser); + } - public DotMemoryDiagnoserAttribute(string? nugetUrl = null, string? toolsDownloadFolder = null) - { - var nugetUri = nugetUrl == null ? null : new Uri(nugetUrl); - Config = ManualConfig.CreateEmpty().AddDiagnoser(new DotMemoryDiagnoser(nugetUri, toolsDownloadFolder)); - } + public DotMemoryDiagnoserAttribute(Uri? nugetUrl, string? downloadTo = null) + { + var diagnoser = new DotMemoryDiagnoser(nugetUrl, downloadTo); + Config = ManualConfig.CreateEmpty().AddDiagnoser(diagnoser); } } \ No newline at end of file diff --git a/src/BenchmarkDotNet.Diagnostics.dotMemory/DotMemoryTool.cs b/src/BenchmarkDotNet.Diagnostics.dotMemory/DotMemoryTool.cs deleted file mode 100644 index c28e08fdb2..0000000000 --- a/src/BenchmarkDotNet.Diagnostics.dotMemory/DotMemoryTool.cs +++ /dev/null @@ -1,140 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Reflection; -using BenchmarkDotNet.Diagnosers; -using BenchmarkDotNet.Helpers; -using BenchmarkDotNet.Loggers; -using JetBrains.Profiler.SelfApi; - -namespace BenchmarkDotNet.Diagnostics.dotMemory -{ - internal sealed class DotMemoryTool - { - private readonly ILogger logger; - private readonly Uri? nugetUrl; - private readonly NuGetApi nugetApi; - private readonly string? downloadTo; - - public DotMemoryTool(ILogger logger, Uri? nugetUrl = null, NuGetApi nugetApi = NuGetApi.V3, string? downloadTo = null) - { - this.logger = logger; - this.nugetUrl = nugetUrl; - this.nugetApi = nugetApi; - this.downloadTo = downloadTo; - } - - public void Init() - { - try - { - logger.WriteLineInfo("Ensuring that dotMemory prerequisite is installed..."); - var progress = new Progress(logger, "Installing DotMemory"); - DotMemory.InitAsync(progress, nugetUrl, nugetApi, downloadTo).Wait(); - logger.WriteLineInfo("dotMemory prerequisite is installed"); - logger.WriteLineInfo($"dotMemory runner path: {GetRunnerPath()}"); - } - catch (Exception e) - { - logger.WriteLineError(e.ToString()); - } - } - - public string Start(DiagnoserActionParameters parameters) - { - string snapshotFile = ArtifactFileNameHelper.GetFilePath(parameters, "snapshots", DateTime.Now, "dmw", ".0000".Length); - string? snapshotDirectory = Path.GetDirectoryName(snapshotFile); - logger.WriteLineInfo($"Target snapshot file: {snapshotFile}"); - if (!Directory.Exists(snapshotDirectory) && snapshotDirectory != null) - { - try - { - Directory.CreateDirectory(snapshotDirectory); - } - catch (Exception e) - { - logger.WriteLineError($"Failed to create directory: {snapshotDirectory}"); - logger.WriteLineError(e.ToString()); - } - } - - try - { - logger.WriteLineInfo("Attaching dotMemory to the process..."); - Attach(parameters, snapshotFile); - logger.WriteLineInfo("dotMemory is successfully attached"); - } - catch (Exception e) - { - logger.WriteLineError(e.ToString()); - return snapshotFile; - } - - return snapshotFile; - } - - public void Stop() - { - try - { - logger.WriteLineInfo("Taking dotMemory snapshot..."); - Snapshot(); - logger.WriteLineInfo("dotMemory snapshot is successfully taken"); - } - catch (Exception e) - { - logger.WriteLineError(e.ToString()); - } - - try - { - logger.WriteLineInfo("Detaching dotMemory from the process..."); - Detach(); - logger.WriteLineInfo("dotMemory is successfully detached"); - } - catch (Exception e) - { - logger.WriteLineError(e.ToString()); - } - } - - private void Attach(DiagnoserActionParameters parameters, string snapshotFile) - { - var config = new DotMemory.Config(); - - var pid = parameters.Process.Id; - var currentPid = Process.GetCurrentProcess().Id; - if (pid != currentPid) - config = config.ProfileExternalProcess(pid); - - config = config.SaveToFile(snapshotFile); - DotMemory.Attach(config); - } - - private void Snapshot() => DotMemory.GetSnapshot(); - - private void Detach() => DotMemory.Detach(); - - private string GetRunnerPath() - { - var consoleRunnerPackageField = typeof(DotMemory).GetField("ConsoleRunnerPackage", BindingFlags.NonPublic | BindingFlags.Static); - if (consoleRunnerPackageField == null) - throw new InvalidOperationException("Field 'ConsoleRunnerPackage' not found."); - - object? consoleRunnerPackage = consoleRunnerPackageField.GetValue(null); - if (consoleRunnerPackage == null) - throw new InvalidOperationException("Unable to get value of 'ConsoleRunnerPackage'."); - - var consoleRunnerPackageType = consoleRunnerPackage.GetType(); - var getRunnerPathMethod = consoleRunnerPackageType.GetMethod("GetRunnerPath"); - if (getRunnerPathMethod == null) - throw new InvalidOperationException("Method 'GetRunnerPath' not found."); - - string? runnerPath = getRunnerPathMethod.Invoke(consoleRunnerPackage, null) as string; - if (runnerPath == null) - throw new InvalidOperationException("Unable to invoke 'GetRunnerPath'."); - - return runnerPath; - } - } -} \ No newline at end of file diff --git a/src/BenchmarkDotNet.Diagnostics.dotMemory/Progress.cs b/src/BenchmarkDotNet.Diagnostics.dotMemory/Progress.cs deleted file mode 100644 index 738997bb6d..0000000000 --- a/src/BenchmarkDotNet.Diagnostics.dotMemory/Progress.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Diagnostics; -using BenchmarkDotNet.Loggers; - -namespace BenchmarkDotNet.Diagnostics.dotMemory -{ - public class Progress : IProgress - { - private static readonly TimeSpan ReportInterval = TimeSpan.FromSeconds(0.1); - - private readonly ILogger logger; - private readonly string title; - - public Progress(ILogger logger, string title) - { - this.logger = logger; - this.title = title; - } - - private int lastProgress; - private Stopwatch? stopwatch; - - public void Report(double value) - { - int progress = (int)Math.Floor(value); - bool needToReport = stopwatch == null || - (stopwatch != null && stopwatch?.Elapsed > ReportInterval) || - progress == 100; - - if (lastProgress != progress && needToReport) - { - logger.WriteLineInfo($"{title}: {progress}%"); - lastProgress = progress; - stopwatch = Stopwatch.StartNew(); - } - } - } -} \ No newline at end of file diff --git a/src/BenchmarkDotNet.Diagnostics.dotTrace/DotTraceDiagnoser.cs b/src/BenchmarkDotNet.Diagnostics.dotTrace/DotTraceDiagnoser.cs index da06c83472..be02cc30c8 100644 --- a/src/BenchmarkDotNet.Diagnostics.dotTrace/DotTraceDiagnoser.cs +++ b/src/BenchmarkDotNet.Diagnostics.dotTrace/DotTraceDiagnoser.cs @@ -1,142 +1,124 @@ using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using BenchmarkDotNet.Analysers; +using System.Reflection; using BenchmarkDotNet.Detectors; using BenchmarkDotNet.Diagnosers; -using BenchmarkDotNet.Engines; -using BenchmarkDotNet.Exporters; +using BenchmarkDotNet.Helpers; using BenchmarkDotNet.Jobs; -using BenchmarkDotNet.Loggers; -using BenchmarkDotNet.Reports; -using BenchmarkDotNet.Running; -using BenchmarkDotNet.Toolchains; -using BenchmarkDotNet.Validators; -using RunMode = BenchmarkDotNet.Diagnosers.RunMode; +using JetBrains.Profiler.SelfApi; -namespace BenchmarkDotNet.Diagnostics.dotTrace +namespace BenchmarkDotNet.Diagnostics.dotTrace; + +public class DotTraceDiagnoser(Uri? nugetUrl = null, string? downloadTo = null) : SnapshotProfilerBase { - public class DotTraceDiagnoser(Uri? nugetUrl = null, string? toolsDownloadFolder = null) : IProfiler + public override string ShortName => "dotTrace"; + + protected override void InitTool(Progress progress) { - public IEnumerable Ids => new[] { "DotTrace" }; - public string ShortName => "dotTrace"; + DotTrace.InitAsync(progress, nugetUrl, NuGetApi.V3, downloadTo).Wait(); + } - public RunMode GetRunMode(BenchmarkCase benchmarkCase) - { - return IsSupported(benchmarkCase.Job.Environment.GetRuntime().RuntimeMoniker) ? RunMode.ExtraRun : RunMode.None; - } + protected override void AttachToCurrentProcess(string snapshotFile) + { + DotTrace.Attach(new DotTrace.Config().SaveToFile(snapshotFile)); + DotTrace.StartCollectingData(); + } - private readonly List snapshotFilePaths = new (); + protected override void AttachToProcessByPid(int pid, string snapshotFile) + { + DotTrace.Attach(new DotTrace.Config().ProfileExternalProcess(pid).SaveToFile(snapshotFile)); + DotTrace.StartCollectingData(); + } - public void Handle(HostSignal signal, DiagnoserActionParameters parameters) - { - var job = parameters.BenchmarkCase.Job; - bool isInProcess = job.GetToolchain().IsInProcess; - var logger = parameters.Config.GetCompositeLogger(); - DotTraceToolBase tool = isInProcess - ? new InProcessDotTraceTool(logger, nugetUrl, downloadTo: toolsDownloadFolder) - : new ExternalDotTraceTool(logger, nugetUrl, downloadTo: toolsDownloadFolder); + protected override void TakeSnapshot() + { + DotTrace.StopCollectingData(); + DotTrace.SaveData(); + } - var runtimeMoniker = job.Environment.GetRuntime().RuntimeMoniker; - if (!IsSupported(runtimeMoniker)) - { - logger.WriteLineError($"Runtime '{runtimeMoniker}' is not supported by dotTrace"); - return; - } + protected override void Detach() + { + DotTrace.Detach(); + } - switch (signal) - { - case HostSignal.BeforeAnythingElse: - tool.Init(parameters); - break; - case HostSignal.BeforeActualRun: - snapshotFilePaths.Add(tool.Start(parameters)); - break; - case HostSignal.AfterActualRun: - tool.Stop(parameters); - break; - } - } + protected override string CreateSnapshotFilePath(DiagnoserActionParameters parameters) + { + return ArtifactFileNameHelper.GetFilePath(parameters, "snapshots", DateTime.Now, "dtp", ".0000".Length); + } - public IEnumerable Exporters => Enumerable.Empty(); - public IEnumerable Analysers => Enumerable.Empty(); + protected override string GetRunnerPath() + { + var consoleRunnerPackageField = typeof(DotTrace).GetField("ConsoleRunnerPackage", BindingFlags.NonPublic | BindingFlags.Static); + if (consoleRunnerPackageField == null) + throw new InvalidOperationException("Field 'ConsoleRunnerPackage' not found."); - public IEnumerable Validate(ValidationParameters validationParameters) - { - var runtimeMonikers = validationParameters.Benchmarks.Select(b => b.Job.Environment.GetRuntime().RuntimeMoniker).Distinct(); - foreach (var runtimeMoniker in runtimeMonikers) - { - if (!IsSupported(runtimeMoniker)) - yield return new ValidationError(true, $"Runtime '{runtimeMoniker}' is not supported by dotTrace"); - } - } + object? consoleRunnerPackage = consoleRunnerPackageField.GetValue(null); + if (consoleRunnerPackage == null) + throw new InvalidOperationException("Unable to get value of 'ConsoleRunnerPackage'."); - internal static bool IsSupported(RuntimeMoniker runtimeMoniker) - { - switch (runtimeMoniker) - { - case RuntimeMoniker.HostProcess: - case RuntimeMoniker.Net461: - case RuntimeMoniker.Net462: - case RuntimeMoniker.Net47: - case RuntimeMoniker.Net471: - case RuntimeMoniker.Net472: - case RuntimeMoniker.Net48: - case RuntimeMoniker.Net481: - case RuntimeMoniker.Net50: - case RuntimeMoniker.Net60: - case RuntimeMoniker.Net70: - case RuntimeMoniker.Net80: - case RuntimeMoniker.Net90: - return true; - case RuntimeMoniker.NotRecognized: - case RuntimeMoniker.Mono: - case RuntimeMoniker.NativeAot60: - case RuntimeMoniker.NativeAot70: - case RuntimeMoniker.NativeAot80: - case RuntimeMoniker.NativeAot90: - case RuntimeMoniker.Wasm: - case RuntimeMoniker.WasmNet50: - case RuntimeMoniker.WasmNet60: - case RuntimeMoniker.WasmNet70: - case RuntimeMoniker.WasmNet80: - case RuntimeMoniker.WasmNet90: - case RuntimeMoniker.MonoAOTLLVM: - case RuntimeMoniker.MonoAOTLLVMNet60: - case RuntimeMoniker.MonoAOTLLVMNet70: - case RuntimeMoniker.MonoAOTLLVMNet80: - case RuntimeMoniker.MonoAOTLLVMNet90: - case RuntimeMoniker.Mono60: - case RuntimeMoniker.Mono70: - case RuntimeMoniker.Mono80: - case RuntimeMoniker.Mono90: -#pragma warning disable CS0618 // Type or member is obsolete - case RuntimeMoniker.NetCoreApp50: -#pragma warning restore CS0618 // Type or member is obsolete - return false; - case RuntimeMoniker.NetCoreApp20: - case RuntimeMoniker.NetCoreApp21: - case RuntimeMoniker.NetCoreApp22: - return OsDetector.IsWindows(); - case RuntimeMoniker.NetCoreApp30: - case RuntimeMoniker.NetCoreApp31: - return OsDetector.IsWindows() || OsDetector.IsLinux(); - default: - throw new ArgumentOutOfRangeException(nameof(runtimeMoniker), runtimeMoniker, $"Runtime moniker {runtimeMoniker} is not supported"); - } - } + var consoleRunnerPackageType = consoleRunnerPackage.GetType(); + var getRunnerPathMethod = consoleRunnerPackageType.GetMethod("GetRunnerPath"); + if (getRunnerPathMethod == null) + throw new InvalidOperationException("Method 'GetRunnerPath' not found."); - public IEnumerable ProcessResults(DiagnoserResults results) => ImmutableArray.Empty; + string? runnerPath = getRunnerPathMethod.Invoke(consoleRunnerPackage, null) as string; + if (runnerPath == null) + throw new InvalidOperationException("Unable to invoke 'GetRunnerPath'."); + + return runnerPath; + } - public void DisplayResults(ILogger logger) + internal override bool IsSupported(RuntimeMoniker runtimeMoniker) + { + switch (runtimeMoniker) { - if (snapshotFilePaths.Any()) - { - logger.WriteLineInfo("The following dotTrace snapshots were generated:"); - foreach (string snapshotFilePath in snapshotFilePaths) - logger.WriteLineInfo($"* {snapshotFilePath}"); - } + case RuntimeMoniker.HostProcess: + case RuntimeMoniker.Net461: + case RuntimeMoniker.Net462: + case RuntimeMoniker.Net47: + case RuntimeMoniker.Net471: + case RuntimeMoniker.Net472: + case RuntimeMoniker.Net48: + case RuntimeMoniker.Net481: + case RuntimeMoniker.Net50: + case RuntimeMoniker.Net60: + case RuntimeMoniker.Net70: + case RuntimeMoniker.Net80: + case RuntimeMoniker.Net90: + return true; + case RuntimeMoniker.NotRecognized: + case RuntimeMoniker.Mono: + case RuntimeMoniker.NativeAot60: + case RuntimeMoniker.NativeAot70: + case RuntimeMoniker.NativeAot80: + case RuntimeMoniker.NativeAot90: + case RuntimeMoniker.Wasm: + case RuntimeMoniker.WasmNet50: + case RuntimeMoniker.WasmNet60: + case RuntimeMoniker.WasmNet70: + case RuntimeMoniker.WasmNet80: + case RuntimeMoniker.WasmNet90: + case RuntimeMoniker.MonoAOTLLVM: + case RuntimeMoniker.MonoAOTLLVMNet60: + case RuntimeMoniker.MonoAOTLLVMNet70: + case RuntimeMoniker.MonoAOTLLVMNet80: + case RuntimeMoniker.MonoAOTLLVMNet90: + case RuntimeMoniker.Mono60: + case RuntimeMoniker.Mono70: + case RuntimeMoniker.Mono80: + case RuntimeMoniker.Mono90: +#pragma warning disable CS0618 // Type or member is obsolete + case RuntimeMoniker.NetCoreApp50: +#pragma warning restore CS0618 // Type or member is obsolete + return false; + case RuntimeMoniker.NetCoreApp20: + case RuntimeMoniker.NetCoreApp21: + case RuntimeMoniker.NetCoreApp22: + return OsDetector.IsWindows(); + case RuntimeMoniker.NetCoreApp30: + case RuntimeMoniker.NetCoreApp31: + return OsDetector.IsWindows() || OsDetector.IsLinux(); + default: + throw new ArgumentOutOfRangeException(nameof(runtimeMoniker), runtimeMoniker, $"Runtime moniker {runtimeMoniker} is not supported"); } } } \ No newline at end of file diff --git a/src/BenchmarkDotNet.Diagnostics.dotTrace/DotTraceDiagnoserAttribute.cs b/src/BenchmarkDotNet.Diagnostics.dotTrace/DotTraceDiagnoserAttribute.cs index 90d4173016..f056a98cbd 100644 --- a/src/BenchmarkDotNet.Diagnostics.dotTrace/DotTraceDiagnoserAttribute.cs +++ b/src/BenchmarkDotNet.Diagnostics.dotTrace/DotTraceDiagnoserAttribute.cs @@ -1,22 +1,22 @@ using System; using BenchmarkDotNet.Configs; -namespace BenchmarkDotNet.Diagnostics.dotTrace +namespace BenchmarkDotNet.Diagnostics.dotTrace; + +[AttributeUsage(AttributeTargets.Class)] +public class DotTraceDiagnoserAttribute : Attribute, IConfigSource { - [AttributeUsage(AttributeTargets.Class)] - public class DotTraceDiagnoserAttribute : Attribute, IConfigSource - { - public IConfig Config { get; } + public IConfig Config { get; } - public DotTraceDiagnoserAttribute() - { - Config = ManualConfig.CreateEmpty().AddDiagnoser(new DotTraceDiagnoser()); - } + public DotTraceDiagnoserAttribute() + { + var diagnoser = new DotTraceDiagnoser(); + Config = ManualConfig.CreateEmpty().AddDiagnoser(diagnoser); + } - public DotTraceDiagnoserAttribute(string? nugetUrl = null, string? toolsDownloadFolder = null) - { - var nugetUri = nugetUrl == null ? null : new Uri(nugetUrl); - Config = ManualConfig.CreateEmpty().AddDiagnoser(new DotTraceDiagnoser(nugetUri, toolsDownloadFolder)); - } + public DotTraceDiagnoserAttribute(Uri? nugetUrl, string? downloadTo = null) + { + var diagnoser = new DotTraceDiagnoser(nugetUrl, downloadTo); + Config = ManualConfig.CreateEmpty().AddDiagnoser(diagnoser); } } \ No newline at end of file diff --git a/src/BenchmarkDotNet.Diagnostics.dotTrace/DotTraceToolBase.cs b/src/BenchmarkDotNet.Diagnostics.dotTrace/DotTraceToolBase.cs deleted file mode 100644 index 2fb36e9c66..0000000000 --- a/src/BenchmarkDotNet.Diagnostics.dotTrace/DotTraceToolBase.cs +++ /dev/null @@ -1,145 +0,0 @@ -using System; -using System.IO; -using System.Reflection; -using BenchmarkDotNet.Diagnosers; -using BenchmarkDotNet.Helpers; -using BenchmarkDotNet.Loggers; -using JetBrains.Profiler.SelfApi; - -namespace BenchmarkDotNet.Diagnostics.dotTrace -{ - internal abstract class DotTraceToolBase - { - private readonly ILogger logger; - private readonly Uri? nugetUrl; - private readonly NuGetApi nugetApi; - private readonly string? downloadTo; - - protected DotTraceToolBase(ILogger logger, Uri? nugetUrl = null, NuGetApi nugetApi = NuGetApi.V3, string? downloadTo = null) - { - this.logger = logger; - this.nugetUrl = nugetUrl; - this.nugetApi = nugetApi; - this.downloadTo = downloadTo; - } - - public void Init(DiagnoserActionParameters parameters) - { - try - { - logger.WriteLineInfo("Ensuring that dotTrace prerequisite is installed..."); - var progress = new Progress(logger, "Installing DotTrace"); - DotTrace.InitAsync(progress, nugetUrl, nugetApi, downloadTo).Wait(); - logger.WriteLineInfo("dotTrace prerequisite is installed"); - logger.WriteLineInfo($"dotTrace runner path: {GetRunnerPath()}"); - } - catch (Exception e) - { - logger.WriteLineError(e.ToString()); - } - } - - protected abstract bool AttachOnly { get; } - protected abstract void Attach(DiagnoserActionParameters parameters, string snapshotFile); - protected abstract void StartCollectingData(); - protected abstract void SaveData(); - protected abstract void Detach(); - - public string Start(DiagnoserActionParameters parameters) - { - string snapshotFile = ArtifactFileNameHelper.GetFilePath(parameters, "snapshots", DateTime.Now, "dtp", ".0000".Length); - string? snapshotDirectory = Path.GetDirectoryName(snapshotFile); - logger.WriteLineInfo($"Target snapshot file: {snapshotFile}"); - if (!Directory.Exists(snapshotDirectory) && snapshotDirectory != null) - { - try - { - Directory.CreateDirectory(snapshotDirectory); - } - catch (Exception e) - { - logger.WriteLineError($"Failed to create directory: {snapshotDirectory}"); - logger.WriteLineError(e.ToString()); - } - } - - try - { - logger.WriteLineInfo("Attaching dotTrace to the process..."); - Attach(parameters, snapshotFile); - logger.WriteLineInfo("dotTrace is successfully attached"); - } - catch (Exception e) - { - logger.WriteLineError(e.ToString()); - return snapshotFile; - } - - if (!AttachOnly) - { - try - { - logger.WriteLineInfo("Start collecting data using dataTrace..."); - StartCollectingData(); - logger.WriteLineInfo("Data collecting is successfully started"); - } - catch (Exception e) - { - logger.WriteLineError(e.ToString()); - } - } - - return snapshotFile; - } - - public void Stop(DiagnoserActionParameters parameters) - { - if (!AttachOnly) - { - try - { - logger.WriteLineInfo("Saving dotTrace snapshot..."); - SaveData(); - logger.WriteLineInfo("dotTrace snapshot is successfully saved to the artifact folder"); - } - catch (Exception e) - { - logger.WriteLineError(e.ToString()); - } - - try - { - logger.WriteLineInfo("Detaching dotTrace from the process..."); - Detach(); - logger.WriteLineInfo("dotTrace is successfully detached"); - } - catch (Exception e) - { - logger.WriteLineError(e.ToString()); - } - } - } - - protected string GetRunnerPath() - { - var consoleRunnerPackageField = typeof(DotTrace).GetField("ConsoleRunnerPackage", BindingFlags.NonPublic | BindingFlags.Static); - if (consoleRunnerPackageField == null) - throw new InvalidOperationException("Field 'ConsoleRunnerPackage' not found."); - - object? consoleRunnerPackage = consoleRunnerPackageField.GetValue(null); - if (consoleRunnerPackage == null) - throw new InvalidOperationException("Unable to get value of 'ConsoleRunnerPackage'."); - - var consoleRunnerPackageType = consoleRunnerPackage.GetType(); - var getRunnerPathMethod = consoleRunnerPackageType.GetMethod("GetRunnerPath"); - if (getRunnerPathMethod == null) - throw new InvalidOperationException("Method 'GetRunnerPath' not found."); - - string? runnerPath = getRunnerPathMethod.Invoke(consoleRunnerPackage, null) as string; - if (runnerPath == null) - throw new InvalidOperationException("Unable to invoke 'GetRunnerPath'."); - - return runnerPath; - } - } -} \ No newline at end of file diff --git a/src/BenchmarkDotNet.Diagnostics.dotTrace/ExternalDotTraceTool.cs b/src/BenchmarkDotNet.Diagnostics.dotTrace/ExternalDotTraceTool.cs deleted file mode 100644 index c7f1cf18c8..0000000000 --- a/src/BenchmarkDotNet.Diagnostics.dotTrace/ExternalDotTraceTool.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using System.Diagnostics; -using System.Threading.Tasks; -using BenchmarkDotNet.Diagnosers; -using BenchmarkDotNet.Loggers; -using JetBrains.Profiler.SelfApi; -using ILogger = BenchmarkDotNet.Loggers.ILogger; - -namespace BenchmarkDotNet.Diagnostics.dotTrace -{ - internal class ExternalDotTraceTool : DotTraceToolBase - { - private static readonly TimeSpan AttachTimeout = TimeSpan.FromMinutes(5); - - public ExternalDotTraceTool(ILogger logger, Uri? nugetUrl = null, NuGetApi nugetApi = NuGetApi.V3, string? downloadTo = null) : - base(logger, nugetUrl, nugetApi, downloadTo) { } - - protected override bool AttachOnly => true; - - protected override void Attach(DiagnoserActionParameters parameters, string snapshotFile) - { - var logger = parameters.Config.GetCompositeLogger(); - - string runnerPath = GetRunnerPath(); - int pid = parameters.Process.Id; - string arguments = $"attach {pid} --save-to=\"{snapshotFile}\" --service-output=on"; - - logger.WriteLineInfo($"Starting process: '{runnerPath} {arguments}'"); - - var processStartInfo = new ProcessStartInfo - { - FileName = runnerPath, - WorkingDirectory = "", - Arguments = arguments, - UseShellExecute = false, - CreateNoWindow = true, - RedirectStandardOutput = true, - RedirectStandardError = true - }; - - var attachWaitingTask = new TaskCompletionSource(); - var process = new Process { StartInfo = processStartInfo }; - try - { - process.OutputDataReceived += (_, args) => - { - string? content = args.Data; - if (content != null) - { - logger.WriteLineInfo("[dotTrace] " + content); - if (content.Contains("##dotTrace[\"started\"")) - attachWaitingTask.TrySetResult(true); - } - }; - process.ErrorDataReceived += (_, args) => - { - string? content = args.Data; - if (content != null) - logger.WriteLineError("[dotTrace] " + args.Data); - }; - process.Exited += (_, _) => { attachWaitingTask.TrySetResult(false); }; - process.Start(); - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - } - catch (Exception e) - { - attachWaitingTask.TrySetResult(false); - logger.WriteLineError(e.ToString()); - } - - if (!attachWaitingTask.Task.Wait(AttachTimeout)) - throw new Exception($"Failed to attach dotTrace to the target process (timeout: {AttachTimeout.TotalSeconds} sec)"); - if (!attachWaitingTask.Task.Result) - throw new Exception($"Failed to attach dotTrace to the target process (ExitCode={process.ExitCode})"); - } - - protected override void StartCollectingData() { } - - protected override void SaveData() { } - - protected override void Detach() { } - } -} \ No newline at end of file diff --git a/src/BenchmarkDotNet.Diagnostics.dotTrace/InProcessDotTraceTool.cs b/src/BenchmarkDotNet.Diagnostics.dotTrace/InProcessDotTraceTool.cs deleted file mode 100644 index a02c9c1995..0000000000 --- a/src/BenchmarkDotNet.Diagnostics.dotTrace/InProcessDotTraceTool.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using BenchmarkDotNet.Diagnosers; -using BenchmarkDotNet.Loggers; -using JetBrains.Profiler.SelfApi; - -namespace BenchmarkDotNet.Diagnostics.dotTrace -{ - internal class InProcessDotTraceTool : DotTraceToolBase - { - public InProcessDotTraceTool(ILogger logger, Uri? nugetUrl = null, NuGetApi nugetApi = NuGetApi.V3, string? downloadTo = null) : - base(logger, nugetUrl, nugetApi, downloadTo) { } - - protected override bool AttachOnly => false; - - protected override void Attach(DiagnoserActionParameters parameters, string snapshotFile) - { - var config = new DotTrace.Config(); - config.SaveToFile(snapshotFile); - DotTrace.Attach(config); - } - - protected override void StartCollectingData() => DotTrace.StartCollectingData(); - - protected override void SaveData() => DotTrace.SaveData(); - - protected override void Detach() => DotTrace.Detach(); - } -} \ No newline at end of file diff --git a/src/BenchmarkDotNet.Diagnostics.dotTrace/Progress.cs b/src/BenchmarkDotNet.Diagnostics.dotTrace/Progress.cs deleted file mode 100644 index c353939f1f..0000000000 --- a/src/BenchmarkDotNet.Diagnostics.dotTrace/Progress.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Diagnostics; -using BenchmarkDotNet.Loggers; - -namespace BenchmarkDotNet.Diagnostics.dotTrace -{ - public class Progress : IProgress - { - private static readonly TimeSpan ReportInterval = TimeSpan.FromSeconds(0.1); - - private readonly ILogger logger; - private readonly string title; - - public Progress(ILogger logger, string title) - { - this.logger = logger; - this.title = title; - } - - private int lastProgress; - private Stopwatch? stopwatch; - - public void Report(double value) - { - int progress = (int)Math.Floor(value); - bool needToReport = stopwatch == null || - (stopwatch != null && stopwatch?.Elapsed > ReportInterval) || - progress == 100; - - if (lastProgress != progress && needToReport) - { - logger.WriteLineInfo($"{title}: {progress}%"); - lastProgress = progress; - stopwatch = Stopwatch.StartNew(); - } - } - } -} \ No newline at end of file diff --git a/src/BenchmarkDotNet/Diagnosers/SnapshotProfilerBase.cs b/src/BenchmarkDotNet/Diagnosers/SnapshotProfilerBase.cs new file mode 100644 index 0000000000..3105ceeeb9 --- /dev/null +++ b/src/BenchmarkDotNet/Diagnosers/SnapshotProfilerBase.cs @@ -0,0 +1,195 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.IO; +using System.Linq; +using BenchmarkDotNet.Analysers; +using BenchmarkDotNet.Engines; +using BenchmarkDotNet.Exporters; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Loggers; +using BenchmarkDotNet.Reports; +using BenchmarkDotNet.Running; +using BenchmarkDotNet.Validators; + +namespace BenchmarkDotNet.Diagnosers; + +public abstract class SnapshotProfilerBase : IProfiler +{ + public abstract string ShortName { get; } + + protected abstract void InitTool(Progress progress); + protected abstract void AttachToCurrentProcess(string snapshotFile); + protected abstract void AttachToProcessByPid(int pid, string snapshotFile); + protected abstract void TakeSnapshot(); + protected abstract void Detach(); + + protected abstract string CreateSnapshotFilePath(DiagnoserActionParameters parameters); + protected abstract string GetRunnerPath(); + internal abstract bool IsSupported(RuntimeMoniker runtimeMoniker); + + private readonly List snapshotFilePaths = []; + + public IEnumerable Ids => [ShortName]; + public IEnumerable Exporters => []; + public IEnumerable Analysers => []; + + public RunMode GetRunMode(BenchmarkCase benchmarkCase) => + IsSupported(benchmarkCase.Job.Environment.GetRuntime().RuntimeMoniker) ? RunMode.ExtraRun : RunMode.None; + + public void Handle(HostSignal signal, DiagnoserActionParameters parameters) + { + var logger = parameters.Config.GetCompositeLogger(); + var job = parameters.BenchmarkCase.Job; + + var runtimeMoniker = job.Environment.GetRuntime().RuntimeMoniker; + if (!IsSupported(runtimeMoniker)) + { + logger.WriteLineError($"Runtime '{runtimeMoniker}' is not supported by dotMemory"); + return; + } + + switch (signal) + { + case HostSignal.BeforeAnythingElse: + Init(logger); + break; + case HostSignal.BeforeActualRun: + string snapshotFilePath = Start(logger, parameters); + snapshotFilePaths.Add(snapshotFilePath); + break; + case HostSignal.AfterActualRun: + Stop(logger); + break; + } + } + + public IEnumerable Validate(ValidationParameters validationParameters) + { + var runtimeMonikers = validationParameters.Benchmarks.Select(b => b.Job.Environment.GetRuntime().RuntimeMoniker).Distinct(); + foreach (var runtimeMoniker in runtimeMonikers) + if (!IsSupported(runtimeMoniker)) + yield return new ValidationError(true, $"Runtime '{runtimeMoniker}' is not supported by dotMemory"); + } + + public IEnumerable ProcessResults(DiagnoserResults results) => ImmutableArray.Empty; + + public void DisplayResults(ILogger logger) + { + if (snapshotFilePaths.Count != 0) + { + logger.WriteLineInfo($"The following {ShortName} snapshots were generated:"); + foreach (string snapshotFilePath in snapshotFilePaths) + logger.WriteLineInfo($"* {snapshotFilePath}"); + } + } + + private void Init(ILogger logger) + { + try + { + logger.WriteLineInfo($"Ensuring that {ShortName} prerequisite is installed..."); + var progress = new Progress(logger, $"Installing {ShortName}"); + InitTool(progress); + logger.WriteLineInfo($"{ShortName} prerequisite is installed"); + logger.WriteLineInfo($"{ShortName} runner path: {GetRunnerPath()}"); + } + catch (Exception e) + { + logger.WriteLineError(e.ToString()); + } + } + + private string Start(ILogger logger, DiagnoserActionParameters parameters) + { + string snapshotFilePath = CreateSnapshotFilePath(parameters); + string? snapshotDirectory = Path.GetDirectoryName(snapshotFilePath); + logger.WriteLineInfo($"Target snapshot file: {snapshotFilePath}"); + if (!Directory.Exists(snapshotDirectory) && snapshotDirectory != null) + { + try + { + Directory.CreateDirectory(snapshotDirectory); + } + catch (Exception e) + { + logger.WriteLineError($"Failed to create directory: {snapshotDirectory}"); + logger.WriteLineError(e.ToString()); + } + } + + try + { + logger.WriteLineInfo($"Attaching {ShortName} to the process..."); + Attach(parameters, snapshotFilePath); + logger.WriteLineInfo($"{ShortName} is successfully attached"); + } + catch (Exception e) + { + logger.WriteLineError(e.ToString()); + return snapshotFilePath; + } + + return snapshotFilePath; + } + + private void Stop(ILogger logger) + { + try + { + logger.WriteLineInfo($"Taking {ShortName} snapshot..."); + TakeSnapshot(); + logger.WriteLineInfo($"{ShortName} snapshot is successfully taken"); + } + catch (Exception e) + { + logger.WriteLineError(e.ToString()); + } + + try + { + logger.WriteLineInfo($"Detaching {ShortName} from the process..."); + Detach(); + logger.WriteLineInfo($"{ShortName} is successfully detached"); + } + catch (Exception e) + { + logger.WriteLineError(e.ToString()); + } + } + + + private void Attach(DiagnoserActionParameters parameters, string snapshotFile) + { + int pid = parameters.Process.Id; + int currentPid = Process.GetCurrentProcess().Id; + if (pid != currentPid) + AttachToProcessByPid(pid, snapshotFile); + else + AttachToCurrentProcess(snapshotFile); + } + + protected class Progress(ILogger logger, string title) : IProgress + { + private static readonly TimeSpan ReportInterval = TimeSpan.FromSeconds(0.1); + + private int lastProgress; + private Stopwatch? stopwatch; + + public void Report(double value) + { + int progress = (int)Math.Floor(value); + bool needToReport = stopwatch == null || + (stopwatch != null && stopwatch?.Elapsed > ReportInterval) || + progress == 100; + + if (lastProgress != progress && needToReport) + { + logger.WriteLineInfo($"{title}: {progress}%"); + lastProgress = progress; + stopwatch = Stopwatch.StartNew(); + } + } + } +} \ No newline at end of file diff --git a/tests/BenchmarkDotNet.Tests/dotMemory/DotMemoryTests.cs b/tests/BenchmarkDotNet.Tests/dotMemory/DotMemoryTests.cs index 7142486280..d3aec8dd96 100644 --- a/tests/BenchmarkDotNet.Tests/dotMemory/DotMemoryTests.cs +++ b/tests/BenchmarkDotNet.Tests/dotMemory/DotMemoryTests.cs @@ -3,15 +3,15 @@ using BenchmarkDotNet.Jobs; using Xunit; -namespace BenchmarkDotNet.Tests.dotMemory +namespace BenchmarkDotNet.Tests.dotMemory; + +public class DotMemoryTests { - public class DotMemoryTests + [Fact] + public void AllRuntimeMonikerAreKnown() { - [Fact] - public void AllRuntimeMonikerAreKnown() - { - foreach (RuntimeMoniker moniker in Enum.GetValues(typeof(RuntimeMoniker))) - DotMemoryDiagnoser.IsSupported(moniker); // Just check that it doesn't throw exceptions - } + var diagnoser = new DotMemoryDiagnoser(); + foreach (RuntimeMoniker moniker in Enum.GetValues(typeof(RuntimeMoniker))) + diagnoser.IsSupported(moniker); // Just check that it doesn't throw exceptions } } \ No newline at end of file diff --git a/tests/BenchmarkDotNet.Tests/dotTrace/DotTraceTests.cs b/tests/BenchmarkDotNet.Tests/dotTrace/DotTraceTests.cs index a71cae2aef..751412fd55 100644 --- a/tests/BenchmarkDotNet.Tests/dotTrace/DotTraceTests.cs +++ b/tests/BenchmarkDotNet.Tests/dotTrace/DotTraceTests.cs @@ -3,15 +3,15 @@ using BenchmarkDotNet.Jobs; using Xunit; -namespace BenchmarkDotNet.Tests.dotTrace +namespace BenchmarkDotNet.Tests.dotTrace; + +public class DotTraceTests { - public class DotTraceTests + [Fact] + public void AllRuntimeMonikerAreKnown() { - [Fact] - public void AllRuntimeMonikerAreKnown() - { - foreach (RuntimeMoniker moniker in Enum.GetValues(typeof(RuntimeMoniker))) - DotTraceDiagnoser.IsSupported(moniker); // Just check that it doesn't throw exceptions - } + var diagnoser = new DotTraceDiagnoser(); + foreach (RuntimeMoniker moniker in Enum.GetValues(typeof(RuntimeMoniker))) + diagnoser.IsSupported(moniker); // Just check that it doesn't throw exceptions } } \ No newline at end of file