From d628c46cd67a67628922a47c4c296511e269438a Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 30 Jun 2023 16:18:35 +0200 Subject: [PATCH] Add benchcat: cat + throughput measurements (#15564) benchcat, "bc" for short, is a tool that I've written over the last two years to help me benchmark OpenConsole and Windows Terminal. Initially it only measured the time it took to print a file as fast as possible, but it's grown to support a number of arguments, including chunk (`WriteFile` call) sizes, repeat counts and VT mode with italic and colorized output. In the future I also wish to add a way to generate the output data on the fly via command line arguments. One unusual trait of benchcat is that it is compiled entirely without CRT and vcruntime. I did this so that I could test it on Windows XP. Also, it's kind of funny seeing how it's only about 11kB. This commit also fixes a couple `$LASTEXITCODE` cases, because our spellchecker was bothering me a lot with this PR and so I just fixed it. --- .github/actions/spelling/excludes.txt | 1 + .github/actions/spelling/expect/expect.txt | 2 +- OpenConsole.sln | 29 +- build/scripts/Invoke-FormattingCheck.ps1 | 2 +- .../RenderingTests/RenderingTests.vcxproj | 1 + src/tools/benchcat/benchcat.vcxproj | 45 ++ src/tools/benchcat/crt.cpp | 22 + src/tools/benchcat/main.cpp | 692 ++++++++++++++++++ tools/OpenConsole.psm1 | 2 +- 9 files changed, 787 insertions(+), 9 deletions(-) create mode 100644 src/tools/benchcat/benchcat.vcxproj create mode 100644 src/tools/benchcat/crt.cpp create mode 100644 src/tools/benchcat/main.cpp diff --git a/.github/actions/spelling/excludes.txt b/.github/actions/spelling/excludes.txt index 3f3c4a691d5..441d1f295ca 100644 --- a/.github/actions/spelling/excludes.txt +++ b/.github/actions/spelling/excludes.txt @@ -106,6 +106,7 @@ ^src/terminal/parser/ft_fuzzwrapper/run\.bat$ ^src/terminal/parser/ut_parser/Base64Test.cpp$ ^src/terminal/parser/ut_parser/run\.bat$ +^src/tools/benchcat ^src/tools/integrity/packageuwp/ConsoleUWP\.appxSources$ ^src/tools/lnkd/lnkd\.bat$ ^src/tools/pixels/pixels\.bat$ diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 74dc2109b20..8a9596f02a0 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -105,6 +105,7 @@ bcx bcz BEFOREPARENT beginthread +benchcat bgfx bgidx Bgk @@ -987,7 +988,6 @@ langid LANGUAGELIST lasterror LASTEXITCODE -lastexitcode LAYOUTRTL lbl LBN diff --git a/OpenConsole.sln b/OpenConsole.sln index 571824e7250..c329b756ca4 100644 --- a/OpenConsole.sln +++ b/OpenConsole.sln @@ -420,6 +420,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TerminalStress", "src\tools EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RenderingTests", "src\tools\RenderingTests\RenderingTests.vcxproj", "{37C995E0-2349-4154-8E77-4A52C0C7F46D}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "benchcat", "src\tools\benchcat\benchcat.vcxproj", "{2C836962-9543-4CE5-B834-D28E1F124B66}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution AuditMode|Any CPU = AuditMode|Any CPU @@ -2780,11 +2782,8 @@ Global {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Debug|Any CPU.ActiveCfg = Debug|Win32 {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Debug|ARM.ActiveCfg = Debug|Win32 {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Debug|ARM64.Build.0 = Debug|ARM64 {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Debug|x64.ActiveCfg = Debug|x64 - {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Debug|x64.Build.0 = Debug|x64 {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Debug|x86.ActiveCfg = Debug|Win32 - {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Debug|x86.Build.0 = Debug|Win32 {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 @@ -2793,11 +2792,28 @@ Global {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Release|Any CPU.ActiveCfg = Release|Win32 {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Release|ARM.ActiveCfg = Release|Win32 {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Release|ARM64.ActiveCfg = Release|ARM64 - {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Release|ARM64.Build.0 = Release|ARM64 {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Release|x64.ActiveCfg = Release|x64 - {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Release|x64.Build.0 = Release|x64 {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Release|x86.ActiveCfg = Release|Win32 - {37C995E0-2349-4154-8E77-4A52C0C7F46D}.Release|x86.Build.0 = Release|Win32 + {2C836962-9543-4CE5-B834-D28E1F124B66}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 + {2C836962-9543-4CE5-B834-D28E1F124B66}.AuditMode|ARM.ActiveCfg = AuditMode|Win32 + {2C836962-9543-4CE5-B834-D28E1F124B66}.AuditMode|ARM64.ActiveCfg = Release|ARM64 + {2C836962-9543-4CE5-B834-D28E1F124B66}.AuditMode|x64.ActiveCfg = Release|x64 + {2C836962-9543-4CE5-B834-D28E1F124B66}.AuditMode|x86.ActiveCfg = Release|Win32 + {2C836962-9543-4CE5-B834-D28E1F124B66}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {2C836962-9543-4CE5-B834-D28E1F124B66}.Debug|ARM.ActiveCfg = Debug|Win32 + {2C836962-9543-4CE5-B834-D28E1F124B66}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {2C836962-9543-4CE5-B834-D28E1F124B66}.Debug|x64.ActiveCfg = Debug|x64 + {2C836962-9543-4CE5-B834-D28E1F124B66}.Debug|x86.ActiveCfg = Debug|Win32 + {2C836962-9543-4CE5-B834-D28E1F124B66}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32 + {2C836962-9543-4CE5-B834-D28E1F124B66}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32 + {2C836962-9543-4CE5-B834-D28E1F124B66}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64 + {2C836962-9543-4CE5-B834-D28E1F124B66}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 + {2C836962-9543-4CE5-B834-D28E1F124B66}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 + {2C836962-9543-4CE5-B834-D28E1F124B66}.Release|Any CPU.ActiveCfg = Release|Win32 + {2C836962-9543-4CE5-B834-D28E1F124B66}.Release|ARM.ActiveCfg = Release|Win32 + {2C836962-9543-4CE5-B834-D28E1F124B66}.Release|ARM64.ActiveCfg = Release|ARM64 + {2C836962-9543-4CE5-B834-D28E1F124B66}.Release|x64.ActiveCfg = Release|x64 + {2C836962-9543-4CE5-B834-D28E1F124B66}.Release|x86.ActiveCfg = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2904,6 +2920,7 @@ Global {3C67784E-1453-49C2-9660-483E2CC7F7AD} = {40BD8415-DD93-4200-8D82-498DDDC08CC8} {613CCB57-5FA9-48EF-80D0-6B1E319E20C4} = {A10C4720-DCA4-4640-9749-67F4314F527C} {37C995E0-2349-4154-8E77-4A52C0C7F46D} = {A10C4720-DCA4-4640-9749-67F4314F527C} + {2C836962-9543-4CE5-B834-D28E1F124B66} = {A10C4720-DCA4-4640-9749-67F4314F527C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3140B1B7-C8EE-43D1-A772-D82A7061A271} diff --git a/build/scripts/Invoke-FormattingCheck.ps1 b/build/scripts/Invoke-FormattingCheck.ps1 index ef780b5f480..d1110943d1f 100644 --- a/build/scripts/Invoke-FormattingCheck.ps1 +++ b/build/scripts/Invoke-FormattingCheck.ps1 @@ -10,7 +10,7 @@ function Invoke-CheckBadCodeFormatting() { # returns a non-zero exit code if there are any diffs in the tracked files in the repo git diff-index --quiet HEAD -- - if ($lastExitCode -eq 1) { + if ($LASTEXITCODE -eq 1) { # Write the list of files that need updating to the log git diff-index --name-only HEAD diff --git a/src/tools/RenderingTests/RenderingTests.vcxproj b/src/tools/RenderingTests/RenderingTests.vcxproj index 0824d3dd669..5de14c0f43e 100644 --- a/src/tools/RenderingTests/RenderingTests.vcxproj +++ b/src/tools/RenderingTests/RenderingTests.vcxproj @@ -4,6 +4,7 @@ 16.0 Win32Proj {37c995e0-2349-4154-8e77-4a52c0c7f46d} + RenderingTests RenderingTests 10.0 diff --git a/src/tools/benchcat/benchcat.vcxproj b/src/tools/benchcat/benchcat.vcxproj new file mode 100644 index 00000000000..67a51675a14 --- /dev/null +++ b/src/tools/benchcat/benchcat.vcxproj @@ -0,0 +1,45 @@ + + + + 16.0 + Win32Proj + {2C836962-9543-4CE5-B834-D28E1F124B66} + benchcat + benchcat + 10.0 + bc + + + + + NotUsing + + + Console + + + + false + false + + + + false + false + false + NODEFAULTLIB;%(PreprocessorDefinitions) + false + + + main + true + + + + + + + + + + diff --git a/src/tools/benchcat/crt.cpp b/src/tools/benchcat/crt.cpp new file mode 100644 index 00000000000..f3dc3f6016e --- /dev/null +++ b/src/tools/benchcat/crt.cpp @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#ifdef NODEFAULTLIB + +#include + +#pragma function(memcpy) +void* memcpy(void* dst, const void* src, size_t size) +{ + __movsb(static_cast(dst), static_cast(src), size); + return dst; +} + +#pragma function(memset) +void* memset(void* dst, int val, size_t size) +{ + __stosb(static_cast(dst), static_cast(val), size); + return dst; +} + +#endif diff --git a/src/tools/benchcat/main.cpp b/src/tools/benchcat/main.cpp new file mode 100644 index 00000000000..ff2e63434be --- /dev/null +++ b/src/tools/benchcat/main.cpp @@ -0,0 +1,692 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN + +#include +#include +#include +#include + +#include + +#include "crt.cpp" + +// This warning is broken on if/else chains with init statements. +#pragma warning(disable : 4456) // declaration of '...' hides previous local declaration + +namespace pcg_engines +{ + /* + * PCG Random Number Generation for C++ + * + * Copyright 2014-2017 Melissa O'Neill , + * and the PCG Project contributors. + * + * SPDX-License-Identifier: (Apache-2.0 OR MIT) + * + * Licensed under the Apache License, Version 2.0 (provided in + * LICENSE-APACHE.txt and at http://www.apache.org/licenses/LICENSE-2.0) + * or under the MIT license (provided in LICENSE-MIT.txt and at + * http://opensource.org/licenses/MIT), at your option. This file may not + * be copied, modified, or distributed except according to those terms. + * + * Distributed on an "AS IS" BASIS, WITHOUT WARRANTY OF ANY KIND, either + * express or implied. See your chosen license for details. + * + * For additional information about the PCG random number generation scheme, + * visit http://www.pcg-random.org/. + */ + + class oneseq_dxsm_64_32 + { + using xtype = uint32_t; + using itype = uint64_t; + + itype state_; + + static constexpr uint64_t multiplier() noexcept + { + return 6364136223846793005ULL; + } + + static constexpr uint64_t increment() noexcept + { + return 1442695040888963407ULL; + } + + static constexpr itype bump(itype state) noexcept + { + return state * multiplier() + increment(); + } + + constexpr itype base_generate0() noexcept + { + itype old_state = state_; + state_ = bump(state_); + return old_state; + } + + public: + explicit constexpr oneseq_dxsm_64_32(itype state = 0xcafef00dd15ea5e5ULL) noexcept : + state_(bump(state + increment())) + { + } + + constexpr xtype operator()() noexcept + { + constexpr auto xtypebits = uint8_t(sizeof(xtype) * 8); + constexpr auto itypebits = uint8_t(sizeof(itype) * 8); + static_assert(xtypebits <= itypebits / 2, "Output type must be half the size of the state type."); + + auto internal = base_generate0(); + auto hi = xtype(internal >> (itypebits - xtypebits)); + auto lo = xtype(internal); + + lo |= 1; + hi ^= hi >> (xtypebits / 2); + hi *= xtype(multiplier()); + hi ^= hi >> (3 * (xtypebits / 4)); + hi *= lo; + return hi; + } + + constexpr xtype operator()(xtype upper_bound) noexcept + { + uint32_t threshold = (UINT64_MAX + uint32_t(1) - upper_bound) % upper_bound; + for (;;) + { + auto r = operator()(); + if (r >= threshold) + return r % upper_bound; + } + } + }; +} // namespace pcg_engines + +template +constexpr T min(T a, T b) +{ + return a < b ? a : b; +} + +template +constexpr T max(T a, T b) +{ + return a > b ? a : b; +} + +template +constexpr T clamp(T val, T min, T max) +{ + return val < min ? min : (val > max ? max : val); +} + +static uint32_t parse_number(const wchar_t* str, const wchar_t** end) noexcept +{ + static constexpr uint32_t max = 0x0fffffff; + uint32_t accumulator = 0; + + for (;; ++str) + { + if (*str == '\0' || *str < '0' || *str > '9') + { + break; + } + + accumulator = accumulator * 10 + *str - '0'; + if (accumulator >= max) + { + accumulator = max; + break; + } + } + + if (end) + { + *end = str; + } + + return accumulator; +} + +static uint32_t parse_number_with_suffix(const wchar_t* str) noexcept +{ + const wchar_t* str_end = nullptr; + auto value = parse_number(str, &str_end); + + if (str_end[0]) + { + uint32_t mul = 1000; + + if (str_end[1]) + { + mul = 1024; + + if (str_end[1] != L'i') + { + value = 0; + } + } + + switch (*str_end) + { + case L'g': + case L'G': + value *= mul; + [[fallthrough]]; + case L'm': + case L'M': + value *= mul; + [[fallthrough]]; + case L'k': + case L'K': + value *= mul; + break; + default: + value = 0; + break; + } + } + + return value; +} + +static bool has_suffix(const wchar_t* str, const wchar_t* suffix) +{ + return wcscmp(str, suffix) == 0; +} + +static const wchar_t* split_prefix(const wchar_t* str, const wchar_t* prefix) noexcept +{ + for (; *prefix; ++prefix, ++str) + { + if (*str != *prefix) + { + return nullptr; + } + } + return str; +} + +static char* buffer_append_long(char* dst, const void* src, size_t size) +{ + memcpy(dst, src, size); + return dst + size; +} + +static char* buffer_append(char* dst, const char* src, size_t size) +{ + for (size_t i = 0; i < size; ++i, ++src, ++dst) + { + *dst = *src; + } + return dst; +} + +static char* buffer_append_string(char* dst, const char* src) +{ + return buffer_append(dst, src, strlen(src)); +} + +char* buffer_append_number(char* dst, uint8_t val) +{ + if (val >= 10) + { + if (val >= 100) + { + const uint8_t d = val / 100; + *dst++ = '0' + d; + val -= d * 100; + } + + const uint8_t d = val / 10; + *dst++ = '0' + d; + val -= d * 10; + } + + *dst++ = '0' + val; + return dst; +} + +struct FormatResult +{ + LONGLONG integral; + LONGLONG fractional; + const char* suffix; +}; + +#define FORMAT_RESULT_FMT "%lld.%03lld%s" +#define FORMAT_RESULT_ARGS(r) r.integral, r.fractional, r.suffix + +static FormatResult format_size(LONGLONG value) +{ + FormatResult result; + if (value >= 1'000'000'000) + { + result.integral = value / 1'000'000'000; + result.fractional = ((value + 500'000) / 1'000'000) % 1000; + result.suffix = "G"; + } + else if (value >= 1'000'000) + { + result.integral = value / 1'000'000; + result.fractional = ((value + 500) / 1'000) % 1000; + result.suffix = "M"; + } + else if (value >= 1'000) + { + result.integral = value / 1'000; + result.fractional = value % 1'000; + result.suffix = "k"; + } + else + { + result.integral = value; + result.fractional = 0; + result.suffix = ""; + } + return result; +} + +static FormatResult format_duration(LONGLONG microseconds) +{ + FormatResult result; + if (microseconds >= 1'000'000) + { + result.integral = microseconds / 1'000'000; + result.fractional = ((microseconds + 500) / 1'000) % 1000; + result.suffix = ""; + } + else + { + result.integral = microseconds / 1'000; + result.fractional = microseconds % 1'000; + result.suffix = "m"; + } + return result; +} + +static int format(char* buffer, int size, const char* format, ...) noexcept +{ + va_list vl; + va_start(vl, format); + const auto length = wvnsprintfA(buffer, size, format, vl); + va_end(vl); + return length; +} + +enum class VtMode +{ + Off, + On, + Italic, + Color +}; + +static HANDLE g_stdout; +static HANDLE g_stderr; +static UINT g_console_cp_old; +static DWORD g_console_mode_old; +static size_t g_large_page_minimum; + +[[noreturn]] static void clean_exit(UINT code) +{ + if (g_console_cp_old) + { + SetConsoleCP(g_console_cp_old); + } + if (g_console_mode_old) + { + SetConsoleMode(g_stdout, g_console_mode_old); + } + ExitProcess(code); +} + +[[noreturn]] static void eprintf(const char* format, ...) noexcept +{ + char buffer[1024]; + + va_list vl; + va_start(vl, format); + const auto length = wvnsprintfA(buffer, sizeof(buffer), format, vl); + va_end(vl); + + if (length > 0) + { + WriteFile(g_stderr, buffer, length, nullptr, nullptr); + } + + clean_exit(1); +} + +[[noreturn]] static void print_last_error(const char* what) noexcept +{ + eprintf("\r\nfailed to %s with 0x%08lx\r\n", what, GetLastError()); +} + +static void acquire_lock_memory_privilege() noexcept +{ + HANDLE token; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &token)) + { + return; + } + + TOKEN_PRIVILEGES privileges{}; + privileges.PrivilegeCount = 1; + privileges.Privileges[0].Luid = { 4, 0 }; // SE_LOCK_MEMORY_PRIVILEGE is a well known LUID and always {4, 0} + privileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + // AdjustTokenPrivileges can return true and still set the last error to ERROR_NOT_ALL_ASSIGNED. This API is nuts... + const bool success = AdjustTokenPrivileges(token, FALSE, &privileges, 0, nullptr, nullptr); + if (success && GetLastError() == S_OK) + { + g_large_page_minimum = GetLargePageMinimum(); + } + + CloseHandle(token); +} + +static char* allocate(size_t size) +{ + if (g_large_page_minimum) + { + const auto large_size = (size + g_large_page_minimum - 1) & ~(g_large_page_minimum - 1); + if (const auto address = static_cast(VirtualAlloc(nullptr, large_size, MEM_COMMIT | MEM_RESERVE | MEM_LARGE_PAGES, PAGE_READWRITE))) + { + return address; + } + } + + if (const auto address = static_cast(VirtualAlloc(nullptr, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE))) + { + return address; + } + + print_last_error("allocate memory"); +} + +static BOOL WINAPI consoleCtrlHandler(DWORD) +{ + CancelIoEx(g_stdout, nullptr); + return TRUE; +} + +int __stdcall main() noexcept +{ + g_stdout = GetStdHandle(STD_OUTPUT_HANDLE); + g_stderr = GetStdHandle(STD_OUTPUT_HANDLE); + g_console_cp_old = GetConsoleOutputCP(); + + SetConsoleCtrlHandler(consoleCtrlHandler, TRUE); + SetConsoleOutputCP(CP_UTF8); + + const wchar_t* path = nullptr; + uint32_t chunk_size = 128 * 1024; + uint32_t repeat = 1; + VtMode vt = VtMode::Off; + uint64_t seed = 0; + bool has_seed = false; + + { + int argc; + const auto argv = CommandLineToArgvW(GetCommandLineW(), &argc); + + for (int i = 1; i < argc; ++i) + { + if (const auto suffix = split_prefix(argv[i], L"-c")) + { + // 1GiB is the maximum buffer size WriteFile seems to accept. + chunk_size = min(parse_number_with_suffix(suffix), 1024 * 1024 * 1024); + } + else if (const auto suffix = split_prefix(argv[i], L"-r")) + { + repeat = parse_number_with_suffix(suffix); + } + else if (const auto suffix = split_prefix(argv[i], L"-v")) + { + vt = VtMode::On; + if (has_suffix(suffix, L"i")) + { + vt = VtMode::Italic; + } + else if (has_suffix(suffix, L"c")) + { + vt = VtMode::Color; + } + else if (*suffix) + { + break; + } + } + else if (has_suffix(argv[i], L"-s")) + { + seed = parse_number_with_suffix(suffix); + has_seed = true; + } + else + { + if (argc - i == 1) + { + path = argv[i]; + } + break; + } + } + } + + if (!path || !chunk_size || !repeat) + { + eprintf( + "bc [options] \r\n" + " -v enable VT\r\n" + " -vi print as italic\r\n" + " -vc print colorized\r\n" + " -c{d}{u} chunk size, defaults to 128Ki\r\n" + " -r{d}{u} repeats, defaults to 1\r\n" + " -s{d} RNG seed\r\n" + "{d} are base-10 digits\r\n" + "{u} are suffix units k, Ki, M, Mi, G, Gi\r\n"); + } + + if (!has_seed && vt == VtMode::Color) + { + const auto cryptbase = LoadLibraryExW(L"cryptbase.dll", nullptr, 0); + if (!cryptbase) + { + print_last_error("get handle to cryptbase.dll"); + } + + const auto RtlGenRandom = reinterpret_cast(GetProcAddress(cryptbase, "SystemFunction036")); + if (!RtlGenRandom) + { + print_last_error("get handle to RtlGenRandom"); + } + + RtlGenRandom(&seed, sizeof(seed)); + } + + pcg_engines::oneseq_dxsm_64_32 rng{ seed }; + + const auto stdout = GetStdHandle(STD_OUTPUT_HANDLE); + const auto file = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (file == INVALID_HANDLE_VALUE) + { + print_last_error("open file"); + } + + size_t file_size = 0; + { +#ifdef _WIN64 + LARGE_INTEGER i; + if (!GetFileSizeEx(file, &i)) + { + print_last_error("open file"); + } + + file_size = static_cast(i.QuadPart); + +#else + file_size = GetFileSize(file, nullptr); + if (file_size == INVALID_FILE_SIZE) + { + print_last_error("open file"); + } +#endif + } + + acquire_lock_memory_privilege(); + + const auto file_data = allocate(file_size); + auto stdout_size = file_size; + auto stdout_data = file_data; + + { + auto read_data = file_data; + DWORD read = 0; + + for (auto remaining = file_size; remaining > 0; remaining -= read, read_data += read) + { + read = static_cast(min(0xffffffff, remaining)); + if (!ReadFile(file, read_data, read, &read, nullptr)) + { + print_last_error("read"); + } + } + } + + switch (vt) + { + case VtMode::Italic: + { + stdout_data = allocate(file_size + 16); + auto p = stdout_data; + p = buffer_append_string(p, "\x1b[3m"); + p = buffer_append_long(p, file_data, file_size); + p = buffer_append_string(p, "\x1b[0m"); + stdout_size = static_cast(p - stdout_data); + break; + } + case VtMode::Color: + { + if (const auto icu = LoadLibraryExW(L"icuuc.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32)) + { + stdout_data = allocate(file_size * 20 + 8); + auto p = stdout_data; + + const auto p_utext_openUTF8 = reinterpret_cast(GetProcAddress(icu, "utext_openUTF8")); + const auto p_ubrk_open = reinterpret_cast(GetProcAddress(icu, "ubrk_open")); + const auto p_ubrk_setUText = reinterpret_cast(GetProcAddress(icu, "ubrk_setUText")); + const auto p_ubrk_next = reinterpret_cast(GetProcAddress(icu, "ubrk_next")); + + auto error = U_ZERO_ERROR; + UText text = UTEXT_INITIALIZER; + p_utext_openUTF8(&text, file_data, file_size, &error); + + const auto it = p_ubrk_open(UBRK_CHARACTER, "", nullptr, 0, &error); + p_ubrk_setUText(it, &text, &error); + + for (int32_t ubrk0 = 0, ubrk1; (ubrk1 = p_ubrk_next(it)) != UBRK_DONE; ubrk0 = ubrk1) + { + p = buffer_append_string(p, "\x1b[38;2"); + for (int i = 0; i < 3; i++) + { + *p++ = ';'; + p = buffer_append_number(p, static_cast(rng())); + } + p = buffer_append_string(p, "m"); + p = buffer_append(p, file_data + ubrk0, ubrk1 - ubrk0); + } + + p = buffer_append_string(p, "\x1b[39;49m"); + stdout_size = static_cast(p - stdout_data); + } + break; + } + default: + break; + } + + { + DWORD mode = 0; + if (!GetConsoleMode(g_stdout, &mode)) + { + print_last_error("get console mode"); + } + + g_console_mode_old = mode; + + mode |= ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT; + mode &= ~(ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN); + if (vt != VtMode::Off) + { + mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + } + + if (!SetConsoleMode(g_stdout, mode)) + { + print_last_error("set console mode"); + } + } + + LARGE_INTEGER frequency, beg, end; + QueryPerformanceFrequency(&frequency); + QueryPerformanceCounter(&beg); + + for (size_t iteration = 0; iteration < repeat; ++iteration) + { + auto write_data = stdout_data; + DWORD written = 0; + + for (auto remaining = stdout_size; remaining != 0; remaining -= written, write_data += written) + { + written = static_cast(min(remaining, chunk_size)); + if (!WriteFile(stdout, write_data, written, &written, nullptr)) + { + print_last_error("write"); + } + } + } + + QueryPerformanceCounter(&end); + + const auto elapsed_ticks = end.QuadPart - beg.QuadPart; + const auto elapsed_us = (elapsed_ticks * 1'000'000) / frequency.QuadPart; + LONGLONG total_size = static_cast(stdout_size) * repeat; + const auto bytes_per_second = (total_size * frequency.QuadPart) / elapsed_ticks; + + const auto written = format_size(total_size); + const auto duration = format_duration(elapsed_us); + const auto throughput = format_size(bytes_per_second); + + char status[128]; + const auto status_length = format( + &status[0], + 1024, + FORMAT_RESULT_FMT "B, " FORMAT_RESULT_FMT "s, " FORMAT_RESULT_FMT "B/s", + FORMAT_RESULT_ARGS(written), + FORMAT_RESULT_ARGS(duration), + FORMAT_RESULT_ARGS(throughput)); + + if (status_length <= 0) + { + clean_exit(1); + } + + char buffer[256]; + char* buffer_end = &buffer[0]; + + buffer_end = buffer_append_string(buffer_end, "\r\n"); + for (int i = 0; i < status_length; ++i) + { + *buffer_end++ = '-'; + } + buffer_end = buffer_append_string(buffer_end, "\r\n"); + buffer_end = buffer_append_long(buffer_end, &status[0], static_cast(status_length)); + buffer_end = buffer_append_string(buffer_end, "\r\n"); + + WriteFile(g_stderr, &buffer[0], static_cast(buffer_end - &buffer[0]), nullptr, nullptr); + clean_exit(0); +} diff --git a/tools/OpenConsole.psm1 b/tools/OpenConsole.psm1 index fd0ededc112..3dd17957161 100644 --- a/tools/OpenConsole.psm1 +++ b/tools/OpenConsole.psm1 @@ -367,7 +367,7 @@ function Test-XamlFormat() { $xamlsForStyler = (git ls-files "$root/**/*.xaml") -join "," dotnet tool run xstyler -- -c "$root\XamlStyler.json" -f "$xamlsForStyler" --passive - if ($lastExitCode -eq 1) { + if ($LASTEXITCODE -eq 1) { throw "Xaml formatting bad, run Invoke-XamlFormat on branch" }