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.
This commit is contained in:
Leonard Hecker 2023-06-30 16:18:35 +02:00 коммит произвёл GitHub
Родитель c22d9b1c77
Коммит d628c46cd6
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
9 изменённых файлов: 787 добавлений и 9 удалений

1
.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$

2
.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

Просмотреть файл

@ -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}

Просмотреть файл

@ -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

Просмотреть файл

@ -4,6 +4,7 @@
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{37c995e0-2349-4154-8e77-4a52c0c7f46d}</ProjectGuid>
<ProjectName>RenderingTests</ProjectName>
<RootNamespace>RenderingTests</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>

Просмотреть файл

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{2C836962-9543-4CE5-B834-D28E1F124B66}</ProjectGuid>
<ProjectName>benchcat</ProjectName>
<RootNamespace>benchcat</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
<TargetName>bc</TargetName>
</PropertyGroup>
<Import Project="$(SolutionDir)\src\common.build.pre.props" />
<ItemDefinitionGroup>
<ClCompile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
</Link>
</ItemDefinitionGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<GenerateManifest>false</GenerateManifest>
<WholeProgramOptimization>false</WholeProgramOptimization>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<BufferSecurityCheck>false</BufferSecurityCheck>
<ControlFlowGuard>false</ControlFlowGuard>
<ExceptionHandling>false</ExceptionHandling>
<PreprocessorDefinitions>NODEFAULTLIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>false</SDLCheck>
</ClCompile>
<Link>
<EntryPointSymbol>main</EntryPointSymbol>
<IgnoreAllDefaultLibraries>true</IgnoreAllDefaultLibraries>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="main.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="crt.cpp" />
</ItemGroup>
<Import Project="$(SolutionDir)\src\common.build.post.props" />
</Project>

Просмотреть файл

@ -0,0 +1,22 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#ifdef NODEFAULTLIB
#include <intrin.h>
#pragma function(memcpy)
void* memcpy(void* dst, const void* src, size_t size)
{
__movsb(static_cast<BYTE*>(dst), static_cast<const BYTE*>(src), size);
return dst;
}
#pragma function(memset)
void* memset(void* dst, int val, size_t size)
{
__stosb(static_cast<BYTE*>(dst), static_cast<BYTE>(val), size);
return dst;
}
#endif

692
src/tools/benchcat/main.cpp Normal file
Просмотреть файл

@ -0,0 +1,692 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <Shlwapi.h>
#include <shellapi.h>
#include <icu.h>
#include <cstdint>
#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 <oneill@pcg-random.org>,
* 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<typename T>
constexpr T min(T a, T b)
{
return a < b ? a : b;
}
template<typename T>
constexpr T max(T a, T b)
{
return a > b ? a : b;
}
template<typename T>
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<char*>(VirtualAlloc(nullptr, large_size, MEM_COMMIT | MEM_RESERVE | MEM_LARGE_PAGES, PAGE_READWRITE)))
{
return address;
}
}
if (const auto address = static_cast<char*>(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<uint32_t>(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] <filename>\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<BOOLEAN(APIENTRY*)(PVOID, ULONG)>(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<size_t>(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<DWORD>(min<size_t>(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<DWORD>(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<decltype(&utext_openUTF8)>(GetProcAddress(icu, "utext_openUTF8"));
const auto p_ubrk_open = reinterpret_cast<decltype(&ubrk_open)>(GetProcAddress(icu, "ubrk_open"));
const auto p_ubrk_setUText = reinterpret_cast<decltype(&ubrk_setUText)>(GetProcAddress(icu, "ubrk_setUText"));
const auto p_ubrk_next = reinterpret_cast<decltype(&ubrk_next)>(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<uint8_t>(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<DWORD>(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<DWORD>(min<size_t>(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<LONGLONG>(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<size_t>(status_length));
buffer_end = buffer_append_string(buffer_end, "\r\n");
WriteFile(g_stderr, &buffer[0], static_cast<DWORD>(buffer_end - &buffer[0]), nullptr, nullptr);
clean_exit(0);
}

Просмотреть файл

@ -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"
}