diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..5edcb6b --- /dev/null +++ b/.clang-format @@ -0,0 +1,81 @@ +--- +BasedOnStyle: Microsoft +AccessModifierOffset: '-2' +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: 'true' +AlignConsecutiveDeclarations: 'false' +AlignEscapedNewlines: Left +AlignOperands: 'true' +AlignTrailingComments: 'true' +AllowAllArgumentsOnNextLine: 'true' +AllowAllConstructorInitializersOnNextLine: 'true' +AllowAllParametersOfDeclarationOnNextLine: 'true' +AllowShortBlocksOnASingleLine: 'false' +AllowShortCaseLabelsOnASingleLine: 'true' +AllowShortFunctionsOnASingleLine: InlineOnly +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: Inline +AllowShortLoopsOnASingleLine: 'false' +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: 'false' +AlwaysBreakTemplateDeclarations: 'Yes' +BinPackArguments: 'false' +BinPackParameters: 'false' +BreakBeforeBinaryOperators: NonAssignment +BreakBeforeBraces: Custom +BreakBeforeTernaryOperators: 'true' +BreakConstructorInitializers: AfterColon +BreakInheritanceList: AfterColon +BreakStringLiterals: 'true' +ColumnLimit: '110' +CompactNamespaces: 'false' +ConstructorInitializerAllOnOneLineOrOnePerLine: 'true' +ConstructorInitializerIndentWidth: '4' +ContinuationIndentWidth: '4' +Cpp11BracedListStyle: 'false' +DerivePointerAlignment: 'false' +FixNamespaceComments: 'true' +IncludeBlocks: Regroup +IndentCaseLabels: true +IndentPPDirectives: BeforeHash +IndentWidth: '4' +IndentWrappedFunctionNames: 'false' +Language: Cpp +MaxEmptyLinesToKeep: '1' +NamespaceIndentation: Inner +PenaltyBreakAssignment: '0' +PointerAlignment: Left +ReflowComments: 'true' +SortIncludes: 'true' +SortUsingDeclarations: 'true' +SpaceAfterCStyleCast: 'true' +SpaceAfterLogicalNot: 'false' +SpaceAfterTemplateKeyword: 'true' +SpaceBeforeAssignmentOperators: 'true' +SpaceBeforeCpp11BracedList: 'true' +SpaceBeforeCtorInitializerColon: 'false' +SpaceBeforeInheritanceColon: 'false' +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: 'false' +SpaceInEmptyParentheses: 'false' +SpacesInAngles: 'false' +SpacesInCStyleCastParentheses: 'false' +SpacesInContainerLiterals: 'false' +SpacesInParentheses: 'false' +SpacesInSquareBrackets: 'false' +Standard: Cpp11 +TabWidth: '4' +UseTab: Never +IncludeCategories: + - Regex: '^<(tb)/' + Priority: 0 + - Regex: '^<(libtermbench)/' + Priority: 1 + - Regex: '^' + Priority: 81 + - Regex: '<[[:alnum:]_]+\.h>' + Priority: 82 + - Regex: '.*' + Priority: 99 diff --git a/.github/mock-font-locator.yml b/.github/mock-font-locator.yml new file mode 100644 index 0000000..6c18003 --- /dev/null +++ b/.github/mock-font-locator.yml @@ -0,0 +1,10 @@ + +mock_font_locator: + # Ubuntu 20.04 + - { family: "monospace", slant: normal, weight: normal, path: "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf" } + - { family: "emoji", slant: normal, weight: normal, path: "/usr/share/fonts/truetype/noto/NotoColorEmoji.ttf" } + + # That would be it for macOS + # - { family: "monospace", slant: normal, weight: normal, path: "/Users/trapni/Library/Fonts/JetBrains Mono Regular Nerd Font Complete Mono.ttf" } + # - { family: "emoji", slant: normal, weight: normal, path: "/System/Library/Fonts/Apple Color Emoji.ttc" } + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 428b1b7..26f3ba0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,6 +23,26 @@ env: CTEST_OUTPUT_ON_FAILURE: 1 jobs: + + + check_clang_format: + name: "Check C++ style" + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + - name: Install clang + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 17 + sudo apt-get install clang-format-17 + - name: "Clang-format libtermbench" + run: find ./libtermbench -name "*.cpp" -o -name "*.h" | xargs clang-format-17 --Werror --dry-run + - name: "Clang-format tb1" + run: find ./tb -name "*.cpp" -o -name "*.h" | xargs clang-format-17 --Werror --dry-run + - name: "Check includes" + run: ./scripts/check-includes.sh + ubuntu_linux: name: "Ubuntu Linux 22.04" runs-on: ubuntu-22.04 @@ -43,62 +63,56 @@ jobs: set -ex sudo apt -q update sudo ./scripts/install-deps.sh + - name: "Install GCC 13" + run: sudo apt install g++-13 - name: "cmake" - run: cmake -S . -B build -DCMAKE_BUILD_TYPE="RelWithDebInfo" + run: cmake -S . -B build -DCMAKE_BUILD_TYPE="RelWithDebInfo" -D CMAKE_CXX_COMPILER="g++-13" - name: "build" run: cmake --build build/ -- -j3 - - osx: - name: "OS/X" - runs-on: macos-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/cache@v2 - with: - path: "**/cpm_modules" - key: ${{github.workflow}}-cpm-modules-${{ hashFiles('**/CMakeLists.txt', '**/*.cmake') }} - - name: set variables - id: set_vars - run: ./scripts/ci-set-vars.sh - env: - REPOSITORY: ${{ github.event.repository.name }} - - name: "Install dependencies" + - name: "install dependencies" + run: ./scripts/xvfb-deps.sh + - name: "Install contour" + run: | + wget https://github.com/contour-terminal/contour/releases/download/v0.4.3.6442/contour-0.4.3.6442-ubuntu22.04-amd64.deb + sudo dpkg -i contour-0.4.3.6442-ubuntu22.04-amd64.deb + - name: "create and patch contour.yml config file" run: | set -ex - #brew update - ./scripts/install-deps.sh - - name: "Create build directory" - run: mkdir build - - name: "Generate build files" - run: cmake -S . -B build -DCMAKE_BUILD_TYPE="RelWithDebInfo" - - name: "Build" - run: cmake --build build/ - - windows: - name: "Windows" - runs-on: windows-latest - steps: - - uses: actions/checkout@v2 - - name: setup environment - shell: powershell - id: set_vars - run: .\scripts\ci-set-vars.ps1 - env: - REPOSITORY: ${{ github.event.repository.name }} - - name: "vcpkg: Install dependencies" - uses: lukka/run-vcpkg@v11.1 - id: runvcpkg + mkdir -p ~/.config/contour/ + contour generate config to ~/.config/contour/contour.yml + sed -i -e 's/locator: native/locator: mock/' ~/.config/contour/contour.yml + sed -i -e 's/strict_spacing: true/strict_spacing: false/' ~/.config/contour/contour.yml + sed -i -e 's/columns: 80/columns: 100/' ~/.config/contour/contour.yml + cat .github/mock-font-locator.yml >> ~/.config/contour/contour.yml + cat ~/.config/contour/contour.yml + - name: "Install alacritty" + run: cargo install alacritty + - name: "Install kitty and xterm" + run: sudo apt install -y kitty xterm xvfb + - name: "run benchmarks" + run: ./scripts/Xvfb-bench-run.sh + - name: "ls" + run: ls -la + - name: "cat contour_results" + run: cat contour_results + - name: "cat kitty_results" + run: cat kitty_results + - name: "cat xterm_results" + run: cat xterm_results + - name: "cat alacritty_results" + run: cat alacritty_results + - name: "Set up Julia" + uses: julia-actions/setup-julia@v1 + - name: "Create Plots" + run: julia ./scripts/plot_results.jl + - name: "Upload results and plot" + uses: actions/upload-artifact@v3 with: - vcpkgDirectory: ${{ runner.workspace }}/vcpkg/ - vcpkgGitCommitId: 3e93bb69a1cadeb36fe9eca3b6f3912d84f618d5 - - name: "create build directory" - shell: powershell - run: | - If (!(Test-Path build)) - { - New-Item -ItemType Directory -Force -Path build - } - - name: "Generate build files" - run: cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE="${{ runner.workspace }}/vcpkg/scripts/buildsystems/vcpkg.cmake" -DVCPKG_TARGET_TRIPLET=x64-windows -B build . - - name: "Build" - run: cmake --build build/ --config Release + name: benchmark_results + path: | + results_full.png + results_ascii.png + contour_results + alacritty_results + xterm_results + kitty_results diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b40f21..4bf145c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,9 +28,6 @@ if(DEFINED MSVC) add_definitions(-D_USE_MATH_DEFINES) endif() -if(NOT TARGET fmt) - find_package(fmt REQUIRED) -endif() add_subdirectory(libtermbench) add_subdirectory(tb) diff --git a/libtermbench/CMakeLists.txt b/libtermbench/CMakeLists.txt index 64caf4b..f34c81e 100644 --- a/libtermbench/CMakeLists.txt +++ b/libtermbench/CMakeLists.txt @@ -22,7 +22,6 @@ set_target_properties(termbench PROPERTIES VERSION "${PROJECT_VERSION}" SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}" ) -target_link_libraries(termbench PUBLIC fmt::fmt-header-only) target_include_directories(termbench PUBLIC $ $) diff --git a/libtermbench/termbench.cpp b/libtermbench/termbench.cpp index 5455f45..16b5f67 100644 --- a/libtermbench/termbench.cpp +++ b/libtermbench/termbench.cpp @@ -14,14 +14,12 @@ #include "termbench.h" #include +#include +#include #include #include #include -#include - -#include - using std::function; using std::make_unique; using std::min; @@ -38,15 +36,15 @@ namespace { std::string sizeStr(double _value) { - if ((long double)(_value) >= (1024ull * 1024ull * 1024ull)) // GB - return fmt::format("{:7.3f} GB", _value / 1024.0 / 1024.0 / 1024.0); + if ((long double) (_value) >= (1024ull * 1024ull * 1024ull)) // GB + return std::format("{:7.3f} GB", _value / 1024.0 / 1024.0 / 1024.0); if (_value >= (1024 * 1024)) // MB - return fmt::format("{:7.3f} MB", _value / 1024.0 / 1024.0); + return std::format("{:7.3f} MB", _value / 1024.0 / 1024.0); if (_value >= 1024) // KB - return fmt::format("{:7.3f} KB", _value / 1024.0); - return fmt::format("{:7.3f} bytes", _value); + return std::format("{:7.3f} KB", _value / 1024.0); + return std::format("{:7.3f} bytes", _value); } -} +} // namespace using u16 = unsigned short; @@ -55,11 +53,11 @@ Benchmark::Benchmark(function _writer, unsigned short _width, unsigned short _height, function _beforeTest): - writer_{std::move(_writer)}, - beforeTest_{std::move(_beforeTest)}, - testSizeMB_{_testSizeMB}, - width_{_width}, - height_{_height} + writer_ { std::move(_writer) }, + beforeTest_ { std::move(_beforeTest) }, + testSizeMB_ { _testSizeMB }, + width_ { _width }, + height_ { _height } { } @@ -84,15 +82,18 @@ void Benchmark::runAll() auto const totalSizeBytes = testSizeMB_ * 1024 * 1024; auto const beginTime = steady_clock::now(); auto remainingBytes = totalSizeBytes; - while (remainingBytes > 0) + auto const diff = duration_cast(steady_clock::now() - beginTime); + while (diff < 1s) { - auto const n = std::min(output.size(), remainingBytes); - writer_(output.data(), n); - remainingBytes -= n; + while (remainingBytes > 0) + { + auto const n = std::min(output.size(), remainingBytes); + writer_(output.data(), n); + remainingBytes -= n; + } } - auto const diff = duration_cast(steady_clock::now() - beginTime); - results_.emplace_back(Result{*test, diff, totalSizeBytes}); + results_.emplace_back(Result { *test, diff, totalSizeBytes }); buffer->clear(); test->teardown(*buffer); @@ -103,14 +104,14 @@ void Benchmark::runAll() buffer->clear(); } } - +// void Benchmark::summarize(std::ostream& os) { - os << fmt::format("All {} tests finished.\n", results_.size()); - os << fmt::format("---------------------\n\n"); + os << std::format("All {} tests finished.\n", results_.size()); + os << std::format("---------------------\n\n"); auto const gridCellCount = width_ * height_; - std::chrono::milliseconds totalTime{}; + std::chrono::milliseconds totalTime {}; size_t totalBytes = 0; for (auto const& result: results_) { @@ -118,31 +119,27 @@ void Benchmark::summarize(std::ostream& os) auto const bps = double(result.bytesWritten) / (double(result.time.count()) / 1000.0); totalBytes += result.bytesWritten; totalTime += result.time; - os << fmt::format( - "{:>20}: {:>3}.{:04} seconds, {}/s (normalized: {}/s)\n", - test.name, - result.time.count() / 1000, - result.time.count() % 1000, - sizeStr(bps), - sizeStr(bps / static_cast(gridCellCount)) - ); + os << std::format("{:>40}: {:>3}.{:04} seconds, {}/s (normalized: {}/s)\n", + test.name, + result.time.count() / 1000, + result.time.count() % 1000, + sizeStr(bps), + sizeStr(bps / static_cast(gridCellCount))); } auto const bps = double(totalBytes) / (double(totalTime.count()) / 1000.0); - os << fmt::format( - "{:>20}: {:>3}.{:04} seconds, {}/s (normalized: {}/s)\n", - "all tests", - totalTime.count() / 1000, - totalTime.count() % 1000, - sizeStr(bps), - sizeStr(bps / static_cast(gridCellCount)) - ); + os << std::format("{:>40}: {:>3}.{:04} seconds, {}/s (normalized: {}/s)\n", + "all tests", + totalTime.count() / 1000, + totalTime.count() % 1000, + sizeStr(bps), + sizeStr(bps / static_cast(gridCellCount))); os << "\n"; - os << fmt::format(" screen size: {}x{}\n", width_, height_); - os << fmt::format(" data size: {}\n", sizeStr(static_cast(testSizeMB_ * 1024 * 1024))); + os << std::format(" screen size: {}x{}\n", width_, height_); + os << std::format(" data size: {}\n", sizeStr(static_cast(testSizeMB_ * 1024 * 1024))); } -} +} // namespace contour::termbench namespace contour::termbench::tests { @@ -150,216 +147,231 @@ namespace contour::termbench::tests namespace { -static char randomAsciiChar() -{ - auto constexpr Min = 'a'; // 0x20; - auto constexpr Max = 'z'; // 0x7E; - return static_cast(Min + rand() % (Max - Min + 1)); -} - -void writeChar(Buffer& _sink, char ch) -{ - _sink.write(std::string_view{&ch, 1}); -} - -void writeNumber(Buffer& _sink, unsigned _value) -{ - unsigned remains = _value; - for (unsigned divisor = 1000000000; divisor != 0; divisor /= 10) + static char randomAsciiChar() { - auto const digit = remains / divisor; - remains -= digit * divisor; - - if (digit || (_value != remains) || (divisor == 1)) - writeChar(_sink, static_cast('0' + digit)); + auto constexpr Min = 'a'; // 0x20; + auto constexpr Max = 'z'; // 0x7E; + return static_cast(Min + rand() % (Max - Min + 1)); } -} - -void moveCursor(Buffer& _sink, unsigned x, unsigned y) -{ - writeChar(_sink, '\033'); - writeChar(_sink, '['); - writeNumber(_sink, y); - writeChar(_sink, ';'); - writeNumber(_sink, x); - writeChar(_sink, 'H'); -} -void setTextColor(Buffer& _sink, uint8_t r, uint8_t g, uint8_t b) -{ - _sink.write("\033[38;2;"); - writeNumber(_sink, r & 0xFF); - writeChar(_sink, ';'); - writeNumber(_sink, g & 0xFF); - writeChar(_sink, ';'); - writeNumber(_sink, b & 0xFF); - writeChar(_sink, 'm'); -} - -void setBackgroundColor(Buffer& _sink, uint8_t r, uint8_t g, uint8_t b) -{ - _sink.write("\033[48;2;"); - writeNumber(_sink, r & 0xFF); - writeChar(_sink, ';'); - writeNumber(_sink, g & 0xFF); - writeChar(_sink, ';'); - writeNumber(_sink, b & 0xFF); - writeChar(_sink, 'm'); -} - -class ManyLines: public Test -{ -public: - ManyLines() noexcept: Test("many_lines", "") {} + void writeChar(Buffer& _sink, char ch) + { + _sink.write(std::string_view { &ch, 1 }); + } - void setup(unsigned short, unsigned short) override + void writeNumber(Buffer& _sink, unsigned _value) { - text.resize(4 * 1024 * 1024); - for (auto i = text.data(), e = i + text.size(); i != e; ++i) + unsigned remains = _value; + for (unsigned divisor = 1000000000; divisor != 0; divisor /= 10) { - char const value = randomAsciiChar(); - if (value % 26 != 0) - *i = value; - else - *i = '\n'; + auto const digit = remains / divisor; + remains -= digit * divisor; + + if (digit || (_value != remains) || (divisor == 1)) + writeChar(_sink, static_cast('0' + digit)); } } - void run(Buffer& _sink) noexcept override + void moveCursor(Buffer& _sink, unsigned x, unsigned y) { - while (_sink.good()) - _sink.write(text); + writeChar(_sink, '\033'); + writeChar(_sink, '['); + writeNumber(_sink, y); + writeChar(_sink, ';'); + writeNumber(_sink, x); + writeChar(_sink, 'H'); } -private: - std::string text; -}; + void setTextColor(Buffer& _sink, uint8_t r, uint8_t g, uint8_t b) + { + _sink.write("\033[38;2;"); + writeNumber(_sink, r & 0xFF); + writeChar(_sink, ';'); + writeNumber(_sink, g & 0xFF); + writeChar(_sink, ';'); + writeNumber(_sink, b & 0xFF); + writeChar(_sink, 'm'); + } -class LongLines: public Test -{ -public: - LongLines() noexcept: Test("long_lines", "") {} + void setBackgroundColor(Buffer& _sink, uint8_t r, uint8_t g, uint8_t b) + { + _sink.write("\033[48;2;"); + writeNumber(_sink, r & 0xFF); + writeChar(_sink, ';'); + writeNumber(_sink, g & 0xFF); + writeChar(_sink, ';'); + writeNumber(_sink, b & 0xFF); + writeChar(_sink, 'm'); + } - void run(Buffer& _sink) noexcept override + class ManyLines: public Test { - while (_sink.good()) + public: + ManyLines() noexcept: Test("many_lines", "") {} + + void setup(unsigned short, unsigned short) override { - writeChar(_sink, randomAsciiChar()); + text.resize(4 * 1024 * 1024); + for (auto i = text.data(), e = i + text.size(); i != e; ++i) + { + char const value = randomAsciiChar(); + if (value % 26 != 0) + *i = value; + else + *i = '\n'; + } } - } -}; -class SgrFgColoredText: public Test -{ -public: - SgrFgColoredText() noexcept: Test("sgr_fg_lines", "") {} + void run(Buffer& _sink) noexcept override + { + while (_sink.good()) + _sink.write(text); + } - unsigned short width = 80; - unsigned short height = 24; + private: + std::string text; + }; - void setup(unsigned short _width, unsigned short _height) noexcept override + class LongLines: public Test { - width = _width; - height = _height; - } + public: + LongLines() noexcept: Test("long_lines", "") {} + + void run(Buffer& _sink) noexcept override + { + while (_sink.good()) + { + writeChar(_sink, randomAsciiChar()); + } + } + }; - void run(Buffer& _sink) noexcept override + class SgrFgColoredText: public Test { - for (unsigned frameID = 0; _sink.good(); ++frameID) + public: + SgrFgColoredText() noexcept: Test("sgr_fg_lines", "") {} + + unsigned short width = 80; + unsigned short height = 24; + + void setup(unsigned short _width, unsigned short _height) noexcept override { - for (u16 y = 0; y < height; ++y) + width = _width; + height = _height; + } + + void run(Buffer& _sink) noexcept override + { + for (unsigned frameID = 0; _sink.good(); ++frameID) { - moveCursor(_sink, 1, y + 1u); - for (u16 x = 0; x < width; ++x) + for (u16 y = 0; y < height; ++y) { - auto const r = frameID; - auto const g = frameID + y; - auto const b = frameID + y + x; - - setTextColor(_sink, r & 0xff, g & 0xff, b & 0xff); - writeChar(_sink, static_cast('a' + (frameID + x + y) % ('z' - 'a'))); + moveCursor(_sink, 1, y + 1u); + for (u16 x = 0; x < width; ++x) + { + auto const r = frameID; + auto const g = frameID + y; + auto const b = frameID + y + x; + + setTextColor(_sink, r & 0xff, g & 0xff, b & 0xff); + writeChar(_sink, static_cast('a' + (frameID + x + y) % ('z' - 'a'))); + } } } } - } -}; + }; -class SgrFgBgColoredText: public Test -{ -public: - SgrFgBgColoredText() noexcept: Test("sgr_fg_bg_lines", "") {} + class SgrFgBgColoredText: public Test + { + public: + SgrFgBgColoredText() noexcept: Test("sgr_fg_bg_lines", "") {} - unsigned short width = 80; - unsigned short height = 24; + unsigned short width = 80; + unsigned short height = 24; - void setup(unsigned short _width, unsigned short _height) noexcept override - { - width = _width; - height = _height; - } + void setup(unsigned short _width, unsigned short _height) noexcept override + { + width = _width; + height = _height; + } - void run(Buffer& _sink) noexcept override - { - for (unsigned frameID = 0; _sink.good(); ++frameID) + void run(Buffer& _sink) noexcept override { - for (u16 y = 0; y < height; ++y) + for (unsigned frameID = 0; _sink.good(); ++frameID) { - moveCursor(_sink, 1, y + 1u); - for (u16 x = 0; x < width; ++x) + for (u16 y = 0; y < height; ++y) { - auto r = static_cast(frameID); - auto g = static_cast(frameID + y); - auto b = static_cast(frameID + y + x); - setTextColor(_sink, r, g, b); - - r = static_cast(frameID + y + x); - g = static_cast(frameID + y); - b = static_cast(frameID); - setBackgroundColor(_sink, r, g, b); - - writeChar(_sink, static_cast('a' + (frameID + x + y) % ('z' - 'a'))); + moveCursor(_sink, 1, y + 1u); + for (u16 x = 0; x < width; ++x) + { + auto r = static_cast(frameID); + auto g = static_cast(frameID + y); + auto b = static_cast(frameID + y + x); + setTextColor(_sink, r, g, b); + + r = static_cast(frameID + y + x); + g = static_cast(frameID + y); + b = static_cast(frameID); + setBackgroundColor(_sink, r, g, b); + + writeChar(_sink, static_cast('a' + (frameID + x + y) % ('z' - 'a'))); + } } } } - } -}; + }; -class Binary: public Test -{ -public: - Binary() noexcept: Test("binary", "") {} - - void setup(unsigned short, unsigned short) override + class Binary: public Test { - text.resize(4 * 1024 * 1024); - for (auto i = text.data(), e = i + text.size(); i != e; ++i) + public: + Binary() noexcept: Test("binary", "") {} + + void setup(unsigned short, unsigned short) override { - char const value = randomAsciiChar(); - if (value % 26 != 0) - *i = value; - else - *i = '\n'; + text.resize(4 * 1024 * 1024); + for (auto i = text.data(), e = i + text.size(); i != e; ++i) + { + char const value = randomAsciiChar(); + if (value % 26 != 0) + *i = value; + else + *i = '\n'; + } } - } - void run(Buffer& _sink) noexcept override - { - while (_sink.good()) + void run(Buffer& _sink) noexcept override { - _sink.write(text); + while (_sink.good()) + { + _sink.write(text); + } } - } - void teardown(Buffer& _sink) noexcept override + void teardown(Buffer& _sink) noexcept override { _sink.write("\033c"); } + + private: + std::string text; + }; + + class Line: public Test { - _sink.write("\033c"); - } + public: + Line(std::string name, std::string text): Test(name, ""), text { text } {} + void setup(unsigned short, unsigned short) override {} -private: - std::string text; -}; + void run(Buffer& _sink) noexcept override + { + for (size_t i = 0; i < 1000; ++i) + { + while (_sink.good()) + _sink.write(text); + } + } -} + private: + std::string text; + }; +} // namespace unique_ptr many_lines() { @@ -386,4 +398,33 @@ unique_ptr binary() return make_unique(); } +unique_ptr ascii_line(size_t N) +{ + auto name = std::to_string(N) + " chars per line"; + auto text = std::string(N, 'a') + std::string { "\n" }; + return make_unique(name, text); } + +unique_ptr sgr_line(size_t N) +{ + auto name = std::to_string(N) + " chars with sgr per line"; + std::string text {}; + text += std::string { "\033[38;2;20;200;200m" }; + text += std::string(N, 'a'); + text += std::string { "\n" }; + text += std::string { "\033[38;2;255;255;255m" }; + return make_unique(name, text); +} + +unique_ptr sgrbg_line(size_t N) +{ + auto name = std::to_string(N) + " chars with sgr and bg per line"; + std::string text {}; + text += std::string { "\033[38;2;20;200;200m\033[48;2;100;100;100m" }; + text += std::string(N, 'a'); + text += std::string { "\033[38;2;255;255;255m\033[48;2;0;0;0m" }; + text += std::string { "\n" }; + return make_unique(name, text); +} + +} // namespace contour::termbench::tests diff --git a/libtermbench/termbench.h b/libtermbench/termbench.h index ec0f64c..b613480 100644 --- a/libtermbench/termbench.h +++ b/libtermbench/termbench.h @@ -27,21 +27,14 @@ namespace contour::termbench struct Buffer { -public: - explicit Buffer(size_t _maxWriteSizeMB) noexcept: - maxWriteSize{_maxWriteSizeMB * 1024 * 1024} - {} + public: + explicit Buffer(size_t _maxWriteSizeMB) noexcept: maxWriteSize { _maxWriteSizeMB * 1024 * 1024 } {} - bool good() const noexcept - { - return nwritten < maxWriteSize; - } + bool good() const noexcept { return nwritten < maxWriteSize; } void write(std::string_view _data) noexcept { - auto const n = nwritten + _data.size() < maxWriteSize - ? _data.size() - : maxWriteSize - nwritten; + auto const n = nwritten + _data.size() < maxWriteSize ? _data.size() : maxWriteSize - nwritten; auto i = _data.data(); auto p = data.data() + nwritten; auto e = data.data() + nwritten + n; @@ -51,30 +44,29 @@ struct Buffer nwritten += n; } - std::string_view output() const noexcept { return std::string_view{data.data(), nwritten}; } + std::string_view output() const noexcept { return std::string_view { data.data(), nwritten }; } void clear() noexcept { nwritten = 0; } bool empty() const noexcept { return nwritten == 0; } -private: + private: size_t maxWriteSize = 4 * 1024 * 1024; - std::array data{}; + std::array data {}; size_t nwritten = 0; }; /// Describes a single test. struct Test { - std::string_view name; - std::string_view description; + std::string name; + std::string description; virtual ~Test() = default; - Test(std::string_view _name, std::string_view _description) noexcept: - name{_name}, - description{_description} - {} + Test(std::string _name, std::string _description) noexcept: name { _name }, description { _description } + { + } virtual void setup(unsigned short, unsigned short) {} virtual void run(Buffer&) noexcept = 0; @@ -90,7 +82,7 @@ struct Result class Benchmark { -public: + public: Benchmark(std::function _writer, size_t _testSizeMB, unsigned short _width, @@ -105,7 +97,7 @@ class Benchmark std::vector const& results() const noexcept { return results_; } -private: + private: std::function writer_; std::function beforeTest_; size_t testSizeMB_; @@ -116,14 +108,17 @@ class Benchmark std::vector results_; }; -} +} // namespace contour::termbench // Holds a set of pre-defined terminal benchmark tests. namespace contour::termbench::tests { - std::unique_ptr many_lines(); - std::unique_ptr long_lines(); - std::unique_ptr sgr_fg_lines(); - std::unique_ptr sgr_fgbg_lines(); - std::unique_ptr binary(); -} +std::unique_ptr many_lines(); +std::unique_ptr long_lines(); +std::unique_ptr sgr_fg_lines(); +std::unique_ptr sgr_fgbg_lines(); +std::unique_ptr binary(); +std::unique_ptr ascii_line(size_t); +std::unique_ptr sgr_line(size_t); +std::unique_ptr sgrbg_line(size_t); +} // namespace contour::termbench::tests diff --git a/scripts/Xvfb-bench-run.sh b/scripts/Xvfb-bench-run.sh new file mode 100755 index 0000000..a90d637 --- /dev/null +++ b/scripts/Xvfb-bench-run.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +# +# Usage: Xvfb-contour-run.sh + +set -x + + +LIBGL_ALWAYS_SOFTWARE="${LIBGL_ALWAYS_SOFTWARE:-true}" +DISPLAY=:99 +export LIBGL_ALWAYS_SOFTWARE +export DISPLAY + +Xvfb $DISPLAY -screen 0 1280x1024x24 & +XVFB_PID=$! +trap "kill $XVFB_PID" EXIT + +sleep 3 + +TB_BIN=$PWD/build/tb/tb + +contour display ${DISPLAY} $TB_BIN --output contour_results +mv $HOME/contour_results . +kitty -e $TB_BIN --output kitty_results +xterm -display ${DISPLAY} -e $TB_BIN --output xterm_results +alacritty -e $TB_BIN --output alacritty_results + +if [[ "$GITHUB_OUTPUT" != "" ]]; then + echo "exitCode=$?" >> "$GITHUB_OUTPUT" +fi diff --git a/scripts/check-includes.sh b/scripts/check-includes.sh new file mode 100755 index 0000000..97d5ab1 --- /dev/null +++ b/scripts/check-includes.sh @@ -0,0 +1,14 @@ +#! /bin/bash + +grep -R --include '*.cpp' -E '^#.*include ".*"$' src/ +rv1=$? + +grep -R --include '*.h' -E '^#.*include ".*"$' src/ +rv2=$? + +if [[ $rv1 -eq 0 || $rv2 -eq 0 ]]; then + echo 1>&2 "Error: found #include \"...\" in C++ files." + exit 1 +else + echo "All good. ;-)" +fi diff --git a/scripts/install-deps.sh b/scripts/install-deps.sh index 3fcb4c6..16dc616 100755 --- a/scripts/install-deps.sh +++ b/scripts/install-deps.sh @@ -11,7 +11,6 @@ install_deps_ubuntu() cmake g++ make - libfmt-dev ) if [[ "${RELEASE}" < "19.04" ]]; then @@ -34,13 +33,6 @@ main_linux() ;; esac } - -main_darwin() -{ - # brew install cmake clang? - brew install fmt -} - main() { case "$OSTYPE" in @@ -48,7 +40,6 @@ main() main_linux ;; darwin*) - main_darwin ;; *) echo "OS not supported." diff --git a/scripts/plot_results.jl b/scripts/plot_results.jl new file mode 100644 index 0000000..bb89dba --- /dev/null +++ b/scripts/plot_results.jl @@ -0,0 +1,85 @@ +using Pkg +Pkg.add("CairoMakie") +using CairoMakie + +function parse_line(line) + if count("MB",line) > 0 + return parse(Float64,strip(split(split(line,',')[2],"MB/s")[1])) + else # KB + return parse(Float64,split(split(split(line,",")[2], "(")[1]," ")[2]) / 1024 + end +end + +function get_data(data, data_type) + if data_type == :ascii + name = "chars per line" + elseif data_type == :sgr + name = "chars with sgr per line" + elseif data_type == :sgr_bg + name = "chars with sgr and bg per line" + end + + lines = [] + for el in data + if occursin(name, el) + push!(lines, el) + end + end + return [ parse_line(val) for val in lines] +end + +function insert_from_data(ax, file_name, marker_style, type) + data = split(open(io->read(io, String), file_name), '\n') + terminal_name = split(file_name,"_")[1] + + data_speed = get_data(data,type) + scatter!(ax, data_speed, label= terminal_name * "_ascii", marker = marker_style, markersize = 8) +end + + + +function generate_for_terminal(file_name) + terminal_name = split(file_name,"_")[1] + + fig = Figure() + ax = Axis(fig[1,1], title = "Results for "*terminal_name ,xlabel = "Length of line", ylabel = "throughput, MB/s") + + data = split(open(io->read(io, String), file_name), '\n') + terminal_name = split(file_name,"_")[1] + + get_data_l = (type) -> get_data(data,type) + ascii_speed = get_data_l(:ascii) + sgr_speed = get_data_l(:sgr) + sgr_bg_speed = get_data_l(:sgr_bg) + + marker_size = 8 + scatter!(ax,ascii_speed, label= terminal_name * "_ascii", marker = :circle, markersize = marker_size) + scatter!(ax,sgr_speed, label=terminal_name*"_sgr", marker = :rect, markersize = marker_size) + scatter!(ax,sgr_bg_speed, label=terminal_name*"_sgr_and_bg", marker = :cross, markersize = marker_size) + axislegend(position = :rt) + + save("results_"*terminal_name*".png", fig) +end + +function generate_comparison(type) + fig = Figure() + ax = Axis(fig[1,1], title = "Comparison for "*string(type), xlabel = "Length of line", ylabel = "throughput, MB/s") + + insert_from_data(ax,"contour_results",:circle, type) + insert_from_data(ax,"alacritty_results",:rect, type) + insert_from_data(ax,"xterm_results",:cross, type) + insert_from_data(ax,"kitty_results",:utriangle, type) + axislegend(position = :lt) + return fig +end + + +generate_for_terminal("contour_results") +generate_for_terminal("alacritty_results") +generate_for_terminal("xterm_results") +generate_for_terminal("kitty_results") + + +save("comparison_ascii.png", generate_comparison(:ascii)) +save("comparison_sgr.png", generate_comparison(:sgr)) +save("comparison_sgr_bg.png", generate_comparison(:sgr_bg)) diff --git a/scripts/xvfb-deps.sh b/scripts/xvfb-deps.sh new file mode 100755 index 0000000..45ca102 --- /dev/null +++ b/scripts/xvfb-deps.sh @@ -0,0 +1,66 @@ +#! /bin/bash +set -ex + +packages=" + libqt6core5compat6 \ + libqt6gui6 \ + libqt6multimedia6 \ + libqt6multimediaquick6 \ + libqt6multimediawidgets6 \ + libqt6opengl6 \ + libqt6openglwidgets6 \ + libqt6qml6 \ + libqt6quick6 \ + \ + qml6-module-qt-labs-platform \ + qml6-module-qt5compat-graphicaleffects \ + qml6-module-qtmultimedia \ + qml6-module-qtqml-workerscript \ + qml6-module-qtquick-controls \ + qml6-module-qtquick-layouts \ + qml6-module-qtquick-templates \ + qml6-module-qtquick-window \ + qt6-qpa-plugins \ + \ + xvfb \ + \ + ffmpeg \ + libavcodec58 \ + libavdevice58 \ + libavformat58 \ + libavutil56 \ + libdeflate0 \ + libncurses6 \ + libqrcodegen1 \ + libswscale5 \ + libunistring2 \ + \ + libfontconfig1 \ + libfreetype6 \ + libharfbuzz0b \ + \ + libqt5core5a \ + libqt5gui5 \ + libqt5gui5 \ + libqt5multimedia5 \ + libqt5multimedia5-plugins \ + libqt5network5 \ + libqt5opengl5-dev \ + libqt5x11extras5 \ + libqt5x11extras5-dev \ + qml-module-qt-labs-platform \ + qml-module-qtmultimedia \ + qml-module-qtquick-controls \ + qml-module-qtquick-controls2 \ + qtbase5-dev \ + qtdeclarative5-dev \ + qtmultimedia5-dev \ + qtquickcontrols2-5-dev \ + \ + libutempter0 \ + libyaml-cpp0.7\ + \ + cargo +" + +sudo apt install -y $packages diff --git a/tb/CMakeLists.txt b/tb/CMakeLists.txt index 08f91d4..62a5ffa 100644 --- a/tb/CMakeLists.txt +++ b/tb/CMakeLists.txt @@ -1,7 +1,7 @@ include(GNUInstallDirs) add_executable(tb main.cpp) -target_link_libraries(tb PRIVATE termbench fmt::fmt-header-only) +target_link_libraries(tb PRIVATE termbench) # Set the RPATH so that the executable can find the shared libraries # when installed in a non-standard location diff --git a/tb/main.cpp b/tb/main.cpp index d576993..b65e6c5 100644 --- a/tb/main.cpp +++ b/tb/main.cpp @@ -13,11 +13,13 @@ */ #include -#include + #include #include +#include +#include +#include #include -#include using std::cerr; using std::cout; @@ -28,85 +30,87 @@ using namespace std::string_view_literals; using namespace std::placeholders; #if !defined(_WIN32) -#include -#include + #include + + #include #else -#include + #include #endif namespace { - std::pair getTerminalSize() noexcept - { - auto const DefaultSize = std::pair{80, 24}; +std::pair getTerminalSize() noexcept +{ + auto const DefaultSize = std::pair { 80, 24 }; #if !defined(_WIN32) - winsize ws; - if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) < 0) - return DefaultSize; - return {ws.ws_col, ws.ws_row}; -#else - // TODO: Windows + winsize ws; + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) < 0) return DefaultSize; + return { ws.ws_col, ws.ws_row }; +#else + // TODO: Windows + return DefaultSize; #endif - } - - void nullWrite(char const*, size_t) - { - } +} - void chunkedWriteToStdout(char const* _data, size_t _size) - { - auto constexpr PageSize = 4096; // 8192; +void nullWrite(char const*, size_t) +{ +} - #if defined(_WIN32) - HANDLE stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); - DWORD nwritten{}; - #endif +void chunkedWriteToStdout(char const* _data, size_t _size) +{ + auto constexpr PageSize = 4096; // 8192; - while (_size >= PageSize) - { - #if !defined(_WIN32) - auto const n = write(STDOUT_FILENO, _data, PageSize); - if (n < 0) - perror("write"); - _data += n; - _size -= static_cast(n); - #else - WriteConsoleA(stdoutHandle, _data, static_cast(_size), &nwritten, nullptr); - #endif - } +#if defined(_WIN32) + HANDLE stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); + DWORD nwritten {}; +#endif - #if !defined(_WIN32) - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wunused-result" - write(STDOUT_FILENO, _data, _size); - #pragma GCC diagnostic pop - #else + while (_size >= PageSize) + { +#if !defined(_WIN32) + auto const n = write(STDOUT_FILENO, _data, PageSize); + if (n < 0) + perror("write"); + _data += n; + _size -= static_cast(n); +#else WriteConsoleA(stdoutHandle, _data, static_cast(_size), &nwritten, nullptr); - #endif +#endif } + +#if !defined(_WIN32) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wunused-result" + write(STDOUT_FILENO, _data, _size); + #pragma GCC diagnostic pop +#else + WriteConsoleA(stdoutHandle, _data, static_cast(_size), &nwritten, nullptr); +#endif } +} // namespace int main(int argc, char const* argv[]) { - #if defined(_WIN32) +#if defined(_WIN32) { HANDLE stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleMode(stdoutHandle, ENABLE_VIRTUAL_TERMINAL_PROCESSING); } - #endif +#endif // TODO: Also run against NullSink to get a base value. auto [width, height] = getTerminalSize(); size_t testSizeMB = 32; bool nullSink = false; + std::string fileout {}; for (int i = 1; i < argc; ++i) { if (argv[i] == "--null-sink"sv) { - cout << fmt::format("Using null-sink.\n"); + cout << std::format("Using null-sink.\n"); nullSink = true; } else if (argv[i] == "--size"sv && i + 1 < argc) @@ -116,35 +120,57 @@ int main(int argc, char const* argv[]) } else if (argv[i] == "--help"sv || argv[i] == "-h"sv) { - cout << fmt::format("{} [--null-sink] [--size MB]\n", argv[0]); + cout << std::format("{} [--null-sink] [--size MB]\n", argv[0]); return EXIT_SUCCESS; } + else if (argv[i] == "--output"sv && i + 1 < argc) + { + ++i; + fileout = argv[i]; + } else { - cerr << fmt::format("Invalid argument usage.\n"); + cerr << std::format("Invalid argument usage.\n"); return EXIT_FAILURE; } } - contour::termbench::Benchmark tb{ - nullSink ? nullWrite : chunkedWriteToStdout, - testSizeMB, // MB per test - width, - height - }; + contour::termbench::Benchmark tb { nullSink ? nullWrite : chunkedWriteToStdout, + testSizeMB, // MB per test + width, + height }; // mlfgb tb.add(contour::termbench::tests::many_lines()); tb.add(contour::termbench::tests::long_lines()); tb.add(contour::termbench::tests::sgr_fg_lines()); tb.add(contour::termbench::tests::sgr_fgbg_lines()); - //tb.add(contour::termbench::tests::binary()); + tb.add(contour::termbench::tests::binary()); + // tb.add(contour::termbench::tests::binary()); + constexpr size_t Max_lines { 200 }; + for (size_t i = 0; i < Max_lines; ++i) + tb.add(contour::termbench::tests::ascii_line(i)); + for (size_t i = 0; i < Max_lines; ++i) + tb.add(contour::termbench::tests::sgr_line(i)); + for (size_t i = 0; i < Max_lines; ++i) + tb.add(contour::termbench::tests::sgrbg_line(i)); + + cout << "\033[8;30;100t"; + cout.flush(); tb.runAll(); cout << "\033[m\033[H\033[J"; cout.flush(); - tb.summarize(cout); + if (fileout.empty()) + tb.summarize(cout); + else + { + cout << "Writing summary into " << fileout << std::endl; + std::ofstream writerToFile; + writerToFile.open(fileout); + tb.summarize(writerToFile); + } return EXIT_SUCCESS; }