mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-19 18:11:39 -05:00
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:
1
.github/actions/spelling/excludes.txt
vendored
1
.github/actions/spelling/excludes.txt
vendored
@@ -106,6 +106,7 @@
|
|||||||
^src/terminal/parser/ft_fuzzwrapper/run\.bat$
|
^src/terminal/parser/ft_fuzzwrapper/run\.bat$
|
||||||
^src/terminal/parser/ut_parser/Base64Test.cpp$
|
^src/terminal/parser/ut_parser/Base64Test.cpp$
|
||||||
^src/terminal/parser/ut_parser/run\.bat$
|
^src/terminal/parser/ut_parser/run\.bat$
|
||||||
|
^src/tools/benchcat
|
||||||
^src/tools/integrity/packageuwp/ConsoleUWP\.appxSources$
|
^src/tools/integrity/packageuwp/ConsoleUWP\.appxSources$
|
||||||
^src/tools/lnkd/lnkd\.bat$
|
^src/tools/lnkd/lnkd\.bat$
|
||||||
^src/tools/pixels/pixels\.bat$
|
^src/tools/pixels/pixels\.bat$
|
||||||
|
|||||||
2
.github/actions/spelling/expect/expect.txt
vendored
2
.github/actions/spelling/expect/expect.txt
vendored
@@ -105,6 +105,7 @@ bcx
|
|||||||
bcz
|
bcz
|
||||||
BEFOREPARENT
|
BEFOREPARENT
|
||||||
beginthread
|
beginthread
|
||||||
|
benchcat
|
||||||
bgfx
|
bgfx
|
||||||
bgidx
|
bgidx
|
||||||
Bgk
|
Bgk
|
||||||
@@ -987,7 +988,6 @@ langid
|
|||||||
LANGUAGELIST
|
LANGUAGELIST
|
||||||
lasterror
|
lasterror
|
||||||
LASTEXITCODE
|
LASTEXITCODE
|
||||||
lastexitcode
|
|
||||||
LAYOUTRTL
|
LAYOUTRTL
|
||||||
lbl
|
lbl
|
||||||
LBN
|
LBN
|
||||||
|
|||||||
@@ -420,6 +420,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TerminalStress", "src\tools
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RenderingTests", "src\tools\RenderingTests\RenderingTests.vcxproj", "{37C995E0-2349-4154-8E77-4A52C0C7F46D}"
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RenderingTests", "src\tools\RenderingTests\RenderingTests.vcxproj", "{37C995E0-2349-4154-8E77-4A52C0C7F46D}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "benchcat", "src\tools\benchcat\benchcat.vcxproj", "{2C836962-9543-4CE5-B834-D28E1F124B66}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
AuditMode|Any CPU = AuditMode|Any CPU
|
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|Any CPU.ActiveCfg = Debug|Win32
|
||||||
{37C995E0-2349-4154-8E77-4A52C0C7F46D}.Debug|ARM.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.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.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.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|Any CPU.ActiveCfg = Fuzzing|Win32
|
||||||
{37C995E0-2349-4154-8E77-4A52C0C7F46D}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32
|
{37C995E0-2349-4154-8E77-4A52C0C7F46D}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32
|
||||||
{37C995E0-2349-4154-8E77-4A52C0C7F46D}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64
|
{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|Any CPU.ActiveCfg = Release|Win32
|
||||||
{37C995E0-2349-4154-8E77-4A52C0C7F46D}.Release|ARM.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.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.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.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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -2904,6 +2920,7 @@ Global
|
|||||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD} = {40BD8415-DD93-4200-8D82-498DDDC08CC8}
|
{3C67784E-1453-49C2-9660-483E2CC7F7AD} = {40BD8415-DD93-4200-8D82-498DDDC08CC8}
|
||||||
{613CCB57-5FA9-48EF-80D0-6B1E319E20C4} = {A10C4720-DCA4-4640-9749-67F4314F527C}
|
{613CCB57-5FA9-48EF-80D0-6B1E319E20C4} = {A10C4720-DCA4-4640-9749-67F4314F527C}
|
||||||
{37C995E0-2349-4154-8E77-4A52C0C7F46D} = {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
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {3140B1B7-C8EE-43D1-A772-D82A7061A271}
|
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
|
# returns a non-zero exit code if there are any diffs in the tracked files in the repo
|
||||||
git diff-index --quiet HEAD --
|
git diff-index --quiet HEAD --
|
||||||
if ($lastExitCode -eq 1) {
|
if ($LASTEXITCODE -eq 1) {
|
||||||
|
|
||||||
# Write the list of files that need updating to the log
|
# Write the list of files that need updating to the log
|
||||||
git diff-index --name-only HEAD
|
git diff-index --name-only HEAD
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<VCProjectVersion>16.0</VCProjectVersion>
|
<VCProjectVersion>16.0</VCProjectVersion>
|
||||||
<Keyword>Win32Proj</Keyword>
|
<Keyword>Win32Proj</Keyword>
|
||||||
<ProjectGuid>{37c995e0-2349-4154-8e77-4a52c0c7f46d}</ProjectGuid>
|
<ProjectGuid>{37c995e0-2349-4154-8e77-4a52c0c7f46d}</ProjectGuid>
|
||||||
|
<ProjectName>RenderingTests</ProjectName>
|
||||||
<RootNamespace>RenderingTests</RootNamespace>
|
<RootNamespace>RenderingTests</RootNamespace>
|
||||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
45
src/tools/benchcat/benchcat.vcxproj
Normal file
45
src/tools/benchcat/benchcat.vcxproj
Normal file
@@ -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>
|
||||||
22
src/tools/benchcat/crt.cpp
Normal file
22
src/tools/benchcat/crt.cpp
Normal file
@@ -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
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 ","
|
$xamlsForStyler = (git ls-files "$root/**/*.xaml") -join ","
|
||||||
dotnet tool run xstyler -- -c "$root\XamlStyler.json" -f "$xamlsForStyler" --passive
|
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"
|
throw "Xaml formatting bad, run Invoke-XamlFormat on branch"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user