diff --git a/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs b/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs index 5bf2e5ed03..3d74a760f4 100644 --- a/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs +++ b/src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs @@ -17,7 +17,6 @@ using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Loggers; using BenchmarkDotNet.Mathematics; -using BenchmarkDotNet.Portability; using BenchmarkDotNet.Reports; using BenchmarkDotNet.Toolchains; using BenchmarkDotNet.Toolchains.Parameters; @@ -168,15 +167,13 @@ private static Summary Run(BenchmarkRunInfo benchmarkRunInfo, var cultureInfo = config.CultureInfo ?? DefaultCultureInfo.Instance; var reports = new List(); string title = GetTitle(new[] { benchmarkRunInfo }); - var consoleTitle = RuntimeInformation.IsWindows() ? Console.Title : string.Empty; + using var consoleTitler = new ConsoleTitler($"{benchmarksToRunCount}/{totalBenchmarkCount} Remaining"); logger.WriteLineInfo($"// Found {benchmarks.Length} benchmarks:"); foreach (var benchmark in benchmarks) logger.WriteLineInfo($"// {benchmark.DisplayInfo}"); logger.WriteLine(); - UpdateTitle(totalBenchmarkCount, benchmarksToRunCount); - using (var powerManagementApplier = new PowerManagementApplier(logger)) { bool stop = false; @@ -241,15 +238,10 @@ private static Summary Run(BenchmarkRunInfo benchmarkRunInfo, benchmarksToRunCount -= stop ? benchmarks.Length - i : 1; - LogProgress(logger, in runsChronometer, totalBenchmarkCount, benchmarksToRunCount, taskbarProgress); + LogProgress(logger, in runsChronometer, totalBenchmarkCount, benchmarksToRunCount, consoleTitler, taskbarProgress); } } - if (RuntimeInformation.IsWindows()) - { - Console.Title = consoleTitle; - } - var runEnd = runsChronometer.GetElapsed(); return new Summary(title, @@ -652,15 +644,7 @@ private static void Cleanup(HashSet artifactsToCleanup) } } - private static void UpdateTitle(int totalBenchmarkCount, int benchmarksToRunCount) - { - if (!Console.IsOutputRedirected && (RuntimeInformation.IsWindows() || RuntimeInformation.IsLinux() || RuntimeInformation.IsMacOS())) - { - Console.Title = $"{benchmarksToRunCount}/{totalBenchmarkCount} Remaining"; - } - } - - private static void LogProgress(ILogger logger, in StartedClock runsChronometer, int totalBenchmarkCount, int benchmarksToRunCount, TaskbarProgress taskbarProgress) + private static void LogProgress(ILogger logger, in StartedClock runsChronometer, int totalBenchmarkCount, int benchmarksToRunCount, ConsoleTitler consoleTitler, TaskbarProgress taskbarProgress) { int executedBenchmarkCount = totalBenchmarkCount - benchmarksToRunCount; TimeSpan fromNow = GetEstimatedFinishTime(runsChronometer, benchmarksToRunCount, executedBenchmarkCount); @@ -669,10 +653,8 @@ private static void LogProgress(ILogger logger, in StartedClock runsChronometer, $" Estimated finish {estimatedEnd:yyyy-MM-dd H:mm} ({(int)fromNow.TotalHours}h {fromNow.Minutes}m from now) **"; logger.WriteLineHeader(message); - if (!Console.IsOutputRedirected && (RuntimeInformation.IsWindows() || RuntimeInformation.IsLinux() || RuntimeInformation.IsMacOS())) - { - Console.Title = $"{benchmarksToRunCount}/{totalBenchmarkCount} Remaining - {(int)fromNow.TotalHours}h {fromNow.Minutes}m to finish"; - } + consoleTitler.UpdateTitle ($"{benchmarksToRunCount}/{totalBenchmarkCount} Remaining - {(int)fromNow.TotalHours}h {fromNow.Minutes}m to finish"); + taskbarProgress.SetProgress((float) executedBenchmarkCount / totalBenchmarkCount); } @@ -728,4 +710,4 @@ private static int GetIdToResume(string rootArtifactsFolderPath, string currentL return -1; } } -} \ No newline at end of file +} diff --git a/src/BenchmarkDotNet/Running/ConsoleTitler.cs b/src/BenchmarkDotNet/Running/ConsoleTitler.cs new file mode 100644 index 0000000000..7983776e3c --- /dev/null +++ b/src/BenchmarkDotNet/Running/ConsoleTitler.cs @@ -0,0 +1,80 @@ +using System; +using System.IO; +using BenchmarkDotNet.Portability; + +namespace BenchmarkDotNet.Running +{ + /// + /// Updates Console.Title, subject to platform capabilities and Console availability. + /// Restores the original (or fallback) title upon disposal. + /// + internal class ConsoleTitler : IDisposable + { + /// + /// Whether this instance has any effect. This will be false if the platform doesn't support Console retitling, + /// or if Console output is redirected. + /// + public bool IsEnabled { get; private set; } + + private string oldConsoleTitle; + + public ConsoleTitler(string initialTitle) + { + // Return without enabling if Console output is redirected. + if (Console.IsOutputRedirected) + { + return; + } + + try + { + oldConsoleTitle = PlatformSupportsTitleRead() ? Console.Title : ""; + } + catch (IOException) + { + // We're unable to read Console.Title on a platform that supports it. This can happen when no console + // window is available due to the application being Windows Forms, WPF, Windows Service or a daemon. + oldConsoleTitle = ""; + } + + try + { + // Enable ConsoleTitler if and only if we can successfully set the Console.Title property. + Console.Title = initialTitle; + IsEnabled = true; + } + catch (IOException) + { + } + catch (PlatformNotSupportedException) + { + // As of .NET 7, platforms other than Windows, Linux and MacOS do not support Console retitling. + } + } + +#if NET6_0_OR_GREATER + [System.Runtime.Versioning.SupportedOSPlatformGuard("windows")] +#endif + private static bool PlatformSupportsTitleRead() => RuntimeInformation.IsWindows(); + + /// + /// Updates Console.Title if enabled. + /// + public void UpdateTitle(string title) + { + if (IsEnabled) + { + Console.Title = title; + } + } + + public void Dispose() + { + if (IsEnabled) + { + Console.Title = oldConsoleTitle; + IsEnabled = false; + } + } + } +}