Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add taskbar progress #2158

Merged
merged 8 commits into from
Oct 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 197 additions & 0 deletions src/BenchmarkDotNet/Helpers/Taskbar/TaskbarProgress.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
using System;
using System.Runtime.InteropServices;

namespace BenchmarkDotNet.Helpers
{
internal class TaskbarProgress : IDisposable
{
private static readonly bool OsVersionIsSupported = Portability.RuntimeInformation.IsWindows()
// Must be windows 7 or greater
&& Environment.OSVersion.Version >= new Version(6, 1);

private IntPtr consoleWindowHandle = IntPtr.Zero;
private IntPtr consoleHandle = IntPtr.Zero;

[DllImport("kernel32.dll")]
private static extern IntPtr GetConsoleWindow();
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GetStdHandle(int nStdHandle);

private const int STD_OUTPUT_HANDLE = -11;

internal TaskbarProgress()
{
if (OsVersionIsSupported)
{
consoleWindowHandle = GetConsoleWindow();
consoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
Console.CancelKeyPress += OnConsoleCancelEvent;
}
}

internal void SetState(TaskbarProgressState state)
{
if (OsVersionIsSupported)
{
TaskbarProgressCom.SetState(consoleWindowHandle, consoleHandle, state);
}
}

internal void SetProgress(float progressValue)
{
if (OsVersionIsSupported)
{
TaskbarProgressCom.SetValue(consoleWindowHandle, consoleHandle, progressValue);
}
}

private void OnConsoleCancelEvent(object sender, ConsoleCancelEventArgs e)
{
Dispose();
}

public void Dispose()
{
if (OsVersionIsSupported)
{
TaskbarProgressCom.SetState(consoleWindowHandle, consoleHandle, TaskbarProgressState.NoProgress);
consoleWindowHandle = IntPtr.Zero;
consoleHandle = IntPtr.Zero;
Console.CancelKeyPress -= OnConsoleCancelEvent;
}
}
}

internal enum TaskbarProgressState
{
NoProgress = 0,
Indeterminate = 0x1,
Normal = 0x2,
Error = 0x4,
Paused = 0x8,
Warning = Paused
}

internal static class TaskbarProgressCom
{
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool GetConsoleMode(IntPtr hConsoleHandle, out ConsoleModes lpMode);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetConsoleMode(IntPtr hConsoleHandle, ConsoleModes dwMode);

[Flags]
private enum ConsoleModes : uint
{
ENABLE_PROCESSED_INPUT = 0x0001,
ENABLE_LINE_INPUT = 0x0002,
ENABLE_ECHO_INPUT = 0x0004,
ENABLE_WINDOW_INPUT = 0x0008,
ENABLE_MOUSE_INPUT = 0x0010,
ENABLE_INSERT_MODE = 0x0020,
ENABLE_QUICK_EDIT_MODE = 0x0040,
ENABLE_EXTENDED_FLAGS = 0x0080,
ENABLE_AUTO_POSITION = 0x0100,

ENABLE_PROCESSED_OUTPUT = 0x0001,
ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002,
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004,
DISABLE_NEWLINE_AUTO_RETURN = 0x0008,
ENABLE_LVB_GRID_WORLDWIDE = 0x0010
}

[ComImport]
[Guid("ea1afb91-9e28-4b86-90e9-9e9f8a5eefaf")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface ITaskbarList3
{
// ITaskbarList
[PreserveSig]
void HrInit();
[PreserveSig]
void AddTab(IntPtr hwnd);
[PreserveSig]
void DeleteTab(IntPtr hwnd);
[PreserveSig]
void ActivateTab(IntPtr hwnd);
[PreserveSig]
void SetActiveAlt(IntPtr hwnd);

// ITaskbarList2
[PreserveSig]
void MarkFullscreenWindow(IntPtr hwnd, [MarshalAs(UnmanagedType.Bool)] bool fFullscreen);

// ITaskbarList3
[PreserveSig]
void SetProgressValue(IntPtr hwnd, ulong ullCompleted, ulong ullTotal);
[PreserveSig]
void SetProgressState(IntPtr hwnd, TaskbarProgressState state);
}

[Guid("56FDF344-FD6D-11d0-958A-006097C9A090")]
[ClassInterface(ClassInterfaceType.None)]
[ComImport]
private class TaskbarInstance
{
}

private static readonly ITaskbarList3 s_taskbarInstance = (ITaskbarList3) new TaskbarInstance();

internal static void SetState(IntPtr consoleWindowHandle, IntPtr consoleHandle, TaskbarProgressState taskbarState)
{
if (consoleWindowHandle != IntPtr.Zero)
{
s_taskbarInstance.SetProgressState(consoleWindowHandle, taskbarState);
}

if (consoleHandle != IntPtr.Zero)
{
// Write progress state to console for Windows Terminal (https://github.com/microsoft/terminal/issues/6700).
GetConsoleMode(consoleHandle, out ConsoleModes previousConsoleMode);
SetConsoleMode(consoleHandle, ConsoleModes.ENABLE_VIRTUAL_TERMINAL_PROCESSING | ConsoleModes.ENABLE_PROCESSED_OUTPUT);
switch (taskbarState)
{
case TaskbarProgressState.NoProgress:
Console.Write("\x1b]9;4;0;0\x1b\\");
break;
case TaskbarProgressState.Indeterminate:
Console.Write("\x1b]9;4;3;0\x1b\\");
break;
case TaskbarProgressState.Normal:
// Do nothing, this is set automatically when SetValue is called (and WT has no documented way to set this).
break;
case TaskbarProgressState.Error:
Console.Write("\x1b]9;4;2;0\x1b\\");
break;
case TaskbarProgressState.Warning:
Console.Write("\x1b]9;4;4;0\x1b\\");
break;
};
SetConsoleMode(consoleHandle, previousConsoleMode);
}
}

internal static void SetValue(IntPtr consoleWindowHandle, IntPtr consoleHandle, float progressValue)
{
bool isValidRange = progressValue >= 0 & progressValue <= 1;
if (!isValidRange)
{
throw new ArgumentOutOfRangeException(nameof(progressValue), "progressValue must be between 0 and 1 inclusive.");
}
uint value = (uint) (progressValue * 100);

if (consoleWindowHandle != IntPtr.Zero)
{
s_taskbarInstance.SetProgressValue(consoleWindowHandle, value, 100);
}

if (consoleHandle != IntPtr.Zero)
{
// Write progress sequence to console for Windows Terminal (https://github.com/microsoft/terminal/discussions/14268).
GetConsoleMode(consoleHandle, out ConsoleModes previousConsoleMode);
SetConsoleMode(consoleHandle, ConsoleModes.ENABLE_VIRTUAL_TERMINAL_PROCESSING | ConsoleModes.ENABLE_PROCESSED_OUTPUT);
Console.Write($"\x1b]9;4;1;{value}\x1b\\");
SetConsoleMode(consoleHandle, previousConsoleMode);
}
}
}
}
14 changes: 10 additions & 4 deletions src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ internal static class BenchmarkRunnerClean

internal static Summary[] Run(BenchmarkRunInfo[] benchmarkRunInfos)
{
using var taskbarProgress = new TaskbarProgress();
taskbarProgress.SetState(TaskbarProgressState.Indeterminate);

var resolver = DefaultResolver;
var artifactsToCleanup = new List<string>();

Expand Down Expand Up @@ -85,7 +88,8 @@ internal static Summary[] Run(BenchmarkRunInfo[] benchmarkRunInfos)
foreach (var benchmarkRunInfo in supportedBenchmarks) // we run them in the old order now using the new build artifacts
{
var summary = Run(benchmarkRunInfo, benchmarkToBuildResult, resolver, compositeLogger, artifactsToCleanup,
resultsFolderPath, logFilePath, totalBenchmarkCount, in runsChronometer, ref benchmarksToRunCount);
resultsFolderPath, logFilePath, totalBenchmarkCount, in runsChronometer, ref benchmarksToRunCount,
taskbarProgress);

if (!benchmarkRunInfo.Config.Options.IsSet(ConfigOptions.JoinSummary))
PrintSummary(compositeLogger, benchmarkRunInfo.Config, summary);
Expand Down Expand Up @@ -141,7 +145,8 @@ private static Summary Run(BenchmarkRunInfo benchmarkRunInfo,
string logFilePath,
int totalBenchmarkCount,
in StartedClock runsChronometer,
ref int benchmarksToRunCount)
ref int benchmarksToRunCount,
TaskbarProgress taskbarProgress)
{
var runStart = runsChronometer.GetElapsed();

Expand Down Expand Up @@ -224,7 +229,7 @@ private static Summary Run(BenchmarkRunInfo benchmarkRunInfo,

benchmarksToRunCount -= stop ? benchmarks.Length - i : 1;

LogProgress(logger, in runsChronometer, totalBenchmarkCount, benchmarksToRunCount);
LogProgress(logger, in runsChronometer, totalBenchmarkCount, benchmarksToRunCount, taskbarProgress);
}
}

Expand Down Expand Up @@ -643,7 +648,7 @@ private static void UpdateTitle(int totalBenchmarkCount, int benchmarksToRunCoun
}
}

private static void LogProgress(ILogger logger, in StartedClock runsChronometer, int totalBenchmarkCount, int benchmarksToRunCount)
private static void LogProgress(ILogger logger, in StartedClock runsChronometer, int totalBenchmarkCount, int benchmarksToRunCount, TaskbarProgress taskbarProgress)
{
int executedBenchmarkCount = totalBenchmarkCount - benchmarksToRunCount;
TimeSpan fromNow = GetEstimatedFinishTime(runsChronometer, benchmarksToRunCount, executedBenchmarkCount);
Expand All @@ -656,6 +661,7 @@ private static void LogProgress(ILogger logger, in StartedClock runsChronometer,
{
Console.Title = $"{benchmarksToRunCount}/{totalBenchmarkCount} Remaining - {(int)fromNow.TotalHours}h {fromNow.Minutes}m to finish";
}
taskbarProgress.SetProgress((float) executedBenchmarkCount / totalBenchmarkCount);
}

private static TimeSpan GetEstimatedFinishTime(in StartedClock runsChronometer, int benchmarksToRunCount, int executedBenchmarkCount)
Expand Down