diff --git a/.github/workflows/cmake-internal-debug.yml b/.github/workflows/cmake-internal-debug.yml new file mode 100644 index 00000000..f236adee --- /dev/null +++ b/.github/workflows/cmake-internal-debug.yml @@ -0,0 +1,41 @@ +name: CMake + +on: + workflow_dispatch: + push: + branches: [ main, dev ] + pull_request: + branches: [ main, dev ] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.issue.number || github.run_id }} + cancel-in-progress: true + +jobs: + build: + runs-on: self-hosted + + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - name: "Configure CMake" + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: CC=clang-18 CXX=clang++-18 cmake -GNinja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=Debug -DICHOR_USE_SANITIZERS=OFF -DICHOR_USE_BOOST_BEAST=ON -DICHOR_USE_SDEVENT=ON -DICHOR_USE_MOLD=ON -DICHOR_ENABLE_INTERNAL_DEBUGGING=ON -DICHOR_ENABLE_INTERNAL_COROUTINE_DEBUGGING=ON -DICHOR_ENABLE_INTERNAL_IO_DEBUGGING=ON -DICHOR_ENABLE_INTERNAL_STL_DEBUGGING=ON -DICHOR_USE_LIBCPP=OFF + + - name: Build + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build --config Debug + + - name: Test + working-directory: ${{github.workspace}}/build + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --output-on-failure -C ${{env.BUILD_TYPE}} + + - name: Examples + working-directory: ${{github.workspace}}/bin + run: ../bin/ichor_etcd_example && ../bin/ichor_http_example && ../bin/ichor_multithreaded_example && ../bin/ichor_optional_dependency_example && ../bin/ichor_serializer_example && ../bin/ichor_tcp_example && ../bin/ichor_timer_example && ../bin/ichor_factory_example && ../bin/ichor_websocket_example && ../bin/ichor_websocket_example -t4 && ../bin/ichor_yielding_timer_example && ../bin/ichor_event_statistics_example && ../bin/ichor_introspection_example && ../bin/ichor_coroutine_benchmark && ../bin/ichor_event_benchmark && ../bin/ichor_serializer_benchmark && ../bin/ichor_start_benchmark && ../bin/ichor_start_stop_benchmark && ../bin/ichor_utils_benchmark -r + diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 2689ab9d..48097c9b 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -19,7 +19,6 @@ jobs: matrix: compiler: [/opt/gcc-11.3/bin/g++, clang++] spdlog: [ON, OFF] - absl: [ON, OFF] opts: [Debug, Release] sanitizer: [asan] #tsan not yet supported due to missing sanitizer implementation in GCC 10, 11. As well as false positives. See: # - https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=80b4ce1a5190ebe764b1009afae57dcef45f92c2 @@ -36,13 +35,13 @@ jobs: if: ${{matrix.sanitizer == 'asan'}} # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: CXX=${{matrix.compiler}} cmake -GNinja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.opts}} -DICHOR_USE_SPDLOG=${{matrix.spdlog}} -DICHOR_USE_ABSEIL=${{matrix.absl}} -DICHOR_USE_BOOST_BEAST=ON -DICHOR_USE_SDEVENT=ON -DICHOR_USE_MOLD=OFF + run: CXX=${{matrix.compiler}} cmake -GNinja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.opts}} -DICHOR_USE_SPDLOG=${{matrix.spdlog}} -DICHOR_USE_BOOST_BEAST=ON -DICHOR_USE_SDEVENT=ON -DICHOR_USE_MOLD=OFF -DICHOR_USE_LIBCPP=OFF - name: "Configure CMake (spdlog:${{matrix.spdlog}}, tsan)" if: ${{matrix.sanitizer == 'tsan'}} # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type - run: CXX=${{matrix.compiler}} cmake -GNinja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.opts}} -DICHOR_USE_SPDLOG=${{matrix.spdlog}} -DICHOR_USE_ABSEIL=${{matrix.absl}} -DICHOR_USE_BOOST_BEAST=ON -DICHOR_USE_SDEVENT=ON -DICHOR_USE_SANITIZERS=OFF -DICHOR_USE_THREAD_SANITIZER=ON -DICHOR_USE_MOLD=OFF + run: CXX=${{matrix.compiler}} cmake -GNinja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{matrix.opts}} -DICHOR_USE_SPDLOG=${{matrix.spdlog}} -DICHOR_USE_BOOST_BEAST=ON -DICHOR_USE_SDEVENT=ON -DICHOR_USE_SANITIZERS=OFF -DICHOR_USE_THREAD_SANITIZER=ON -DICHOR_USE_MOLD=OFF -DICHOR_USE_LIBCPP=OFF - name: Build # Build your program with the given configuration @@ -56,5 +55,5 @@ jobs: - name: Examples working-directory: ${{github.workspace}}/bin - run: ../bin/ichor_etcd_example && ../bin/ichor_http_example && ../bin/ichor_multithreaded_example && ../bin/ichor_optional_dependency_example && ../bin/ichor_serializer_example && ../bin/ichor_tcp_example && ../bin/ichor_timer_example && ../bin/ichor_tracker_example && ../bin/ichor_websocket_example && ../bin/ichor_websocket_example -t4 && ../bin/ichor_yielding_timer_example && ../bin/ichor_event_statistics_example + run: ../bin/ichor_etcd_example && ../bin/ichor_http_example && ../bin/ichor_multithreaded_example && ../bin/ichor_optional_dependency_example && ../bin/ichor_serializer_example && ../bin/ichor_tcp_example && ../bin/ichor_timer_example && ../bin/ichor_factory_example && ../bin/ichor_websocket_example && ../bin/ichor_websocket_example -t4 && ../bin/ichor_yielding_timer_example && ../bin/ichor_event_statistics_example && ../bin/ichor_introspection_example diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 52003f3c..49c89702 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -13,7 +13,7 @@ concurrency: jobs: build: - runs-on: self-hosted + runs-on: coverage steps: - uses: actions/checkout@v3 @@ -24,7 +24,7 @@ jobs: run: echo "/opt/gcc-11.3/bin" >> $GITHUB_PATH - name: "Configure CMake" - run: cmake -GNinja -B ${{github.workspace}}/build-coverage -DCMAKE_BUILD_TYPE=Debug -DICHOR_USE_BOOST_BEAST=ON -DICHOR_BUILD_COVERAGE=ON -DICHOR_USE_HIREDIS=ON -DICHOR_USE_SANITIZERS=OFF -DICHOR_BUILD_BENCHMARKS=OFF + run: cmake -GNinja -B ${{github.workspace}}/build-coverage -DCMAKE_BUILD_TYPE=Debug -DICHOR_USE_BOOST_BEAST=ON -DICHOR_BUILD_COVERAGE=ON -DICHOR_USE_HIREDIS=ON -DICHOR_USE_SANITIZERS=OFF -DICHOR_USE_LIBCPP=OFF -DICHOR_USE_MOLD=OFF -DICHOR_ENABLE_INTERNAL_DEBUGGING=ON - name: Build run: cmake --build ${{github.workspace}}/build-coverage --config Debug @@ -35,7 +35,7 @@ jobs: - name: Examples working-directory: ${{github.workspace}}/bin - run: ../bin/ichor_etcd_example && ../bin/ichor_http_example && ../bin/ichor_multithreaded_example && ../bin/ichor_optional_dependency_example && ../bin/ichor_serializer_example && ../bin/ichor_tcp_example && ../bin/ichor_timer_example && ../bin/ichor_tracker_example && ../bin/ichor_websocket_example && ../bin/ichor_yielding_timer_example && ../bin/ichor_event_statistics_example + run: ../bin/ichor_etcd_example && ../bin/ichor_http_example && ../bin/ichor_multithreaded_example && ../bin/ichor_optional_dependency_example && ../bin/ichor_serializer_example && ../bin/ichor_tcp_example && ../bin/ichor_timer_example && ../bin/ichor_factory_example && ../bin/ichor_websocket_example && ../bin/ichor_yielding_timer_example && ../bin/ichor_event_statistics_example && ../bin/ichor_introspection_example && ../bin/ichor_coroutine_benchmark && ../bin/ichor_event_benchmark && ../bin/ichor_serializer_benchmark && ../bin/ichor_start_benchmark && ../bin/ichor_start_stop_benchmark && ../bin/ichor_utils_benchmark -r - name: coverage run: lcov --directory . --capture --output-file coverage.info && lcov --remove coverage.info '/usr/*' '/opt/*' '${{github.workspace}}/external/*' --output-file coverage.info && lcov --list coverage.info && codecov -f coverage.info diff --git a/.gitmodules b/.gitmodules index 261c3586..8c3ba5aa 100644 --- a/.gitmodules +++ b/.gitmodules @@ -12,7 +12,7 @@ branch = 7.1.2 [submodule "external/mimalloc"] path = external/mimalloc - url = https://github.com/microsoft/mimalloc.git + url = https://github.com/volt-software/mimalloc-static.git [submodule "external/glaze"] path = external/glaze url = https://github.com/stephenberry/glaze.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 93c792cf..47e46a85 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,10 @@ -cmake_minimum_required(VERSION 3.12) #might need 3.13 due to https://gitlab.kitware.com/cmake/cmake/-/issues/14444 +cmake_minimum_required(VERSION 3.22) +set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) cmake_policy(SET CMP0048 NEW) -project(ichor VERSION 0.1.0 DESCRIPTION "C++20 dependency injection framework" HOMEPAGE_URL https://github.com/volt-software/Ichor LANGUAGES CXX) +cmake_policy(SET CMP0077 NEW) +cmake_policy(SET CMP0110 NEW) +cmake_policy(SET CMP0127 NEW) +project(ichor VERSION 0.4.0 DESCRIPTION "C++20 dependency injection framework" HOMEPAGE_URL https://github.com/volt-software/Ichor LANGUAGES CXX) if(NOT WIN32) # we set this flag manually later on to enable the experimental C++20 stuff set(CMAKE_CXX_STANDARD 20) @@ -8,20 +12,14 @@ endif() set(CMAKE CXX STANDARD REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_VERBOSE_MAKEFILE ON) -if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.19.0") - cmake_policy(SET CMP0110 NEW) -endif() -set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) -cmake_policy(SET CMP0077 NEW) include(CMakeDependentOption) include(CMakePackageConfigHelpers) - if(NOT CMAKE_BUILD_TYPE) SET(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel." FORCE) -endif(NOT CMAKE_BUILD_TYPE) +endif() get_directory_property(hasParent PARENT_DIRECTORY) if(NOT hasParent) @@ -41,31 +39,35 @@ endif() set(ICHOR_TOP_DIR "${CMAKE_CURRENT_SOURCE_DIR}") set(ICHOR_EXTERNAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/") +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set(ICHOR_BUILDING_DEBUG ON) +else() + set(ICHOR_BUILDING_DEBUG ON) +endif() + option(ICHOR_BUILD_EXAMPLES "Build examples" ON) option(ICHOR_BUILD_BENCHMARKS "Build benchmarks" ON) option(ICHOR_BUILD_TESTING "Build tests" ON) option(ICHOR_ENABLE_INTERNAL_DEBUGGING "Add verbose logging of Ichor internals" OFF) option(ICHOR_ENABLE_INTERNAL_COROUTINE_DEBUGGING "Add verbose logging of Ichor coroutine internals" OFF) option(ICHOR_ENABLE_INTERNAL_IO_DEBUGGING "Add verbose logging of Ichor I/O internals" OFF) +option(ICHOR_ENABLE_INTERNAL_STL_DEBUGGING "Add verbose logging of Ichor STL" OFF) option(ICHOR_BUILD_COVERAGE "Build ichor with coverage" OFF) option(ICHOR_USE_SPDLOG "Use spdlog as framework logging implementation" OFF) option(ICHOR_USE_BOOST_BEAST "Add boost asio and boost BEAST as dependencies" OFF) -option(ICHOR_USE_SANITIZERS "Enable sanitizers, catching potential errors but slowing down compilation and execution speed" ON) +cmake_dependent_option(ICHOR_USE_SANITIZERS "Enable sanitizers, catching potential errors but slowing down compilation and execution speed" $ICHOR_BUILDING_DEBUG "NOT ICHOR_MUSL" OFF) cmake_dependent_option(ICHOR_USE_THREAD_SANITIZER "Enable thread sanitizer, catching potential threading errors but slowing down compilation and execution speed. Cannot be combined with ICHOR_USE_SANITIZERS" OFF "NOT WIN32" OFF) option(ICHOR_USE_UGLY_HACK_EXCEPTION_CATCHING "Enable an ugly hack on gcc to enable debugging the point where exceptions are thrown. Useful for debugging boost asio/beast backtraces." OFF) option(ICHOR_REMOVE_SOURCE_NAMES "Remove compiling source file names and line numbers when logging." OFF) cmake_dependent_option(ICHOR_USE_MOLD "Use mold when linking, recommended to use with gcc 12+ or clang" OFF "NOT WIN32" OFF) cmake_dependent_option(ICHOR_USE_SDEVENT "Add sd-event based queue/integration" OFF "NOT WIN32" OFF) -option(ICHOR_USE_ABSEIL "Use abseil provided classes where applicable" OFF) option(ICHOR_DISABLE_RTTI "Disable RTTI. Reduces memory usage, disables dynamic_cast<>()" ON) option(ICHOR_USE_HARDENING "Uses compiler-specific flags which add stack protection and similar features, as well as adding safety checks in Ichor itself." ON) -cmake_dependent_option(ICHOR_USE_MIMALLOC "Use mimalloc for significant performance improvements" ON "NOT ICHOR_USE_SANITIZERS AND NOT ICHOR_MUSL" OFF) -cmake_dependent_option(ICHOR_USE_SYSTEM_MIMALLOC "Use system or vendored mimalloc" OFF "NOT ICHOR_USE_SANITIZERS AND NOT ICHOR_MUSL" OFF) -if(CMAKE_BUILD_TYPE STREQUAL "Debug") - option(ICHOR_USE_BACKWARD "Use backward-cpp to print stacktraces on crashes or when the user wants to. Useful for debugging." ON) -else() - option(ICHOR_USE_BACKWARD "Use backward-cpp to print stacktraces on crashes or when the user wants to. Useful for debugging." OFF) -endif() +cmake_dependent_option(ICHOR_USE_LIBCPP "Uses clang's libc++ stdlib if compiling with clang" ON "CMAKE_CXX_COMPILER_ID STREQUAL \"Clang\"" OFF) +cmake_dependent_option(ICHOR_USE_MIMALLOC "Use mimalloc for significant performance improvements" ON "(CMAKE_CXX_COMPILER_ID STREQUAL \"Clang\" AND ICHOR_USE_SANITIZERS) OR NOT ICHOR_USE_SANITIZERS" OFF) +cmake_dependent_option(ICHOR_USE_SYSTEM_MIMALLOC "Use system or vendored mimalloc" OFF "(CMAKE_CXX_COMPILER_ID STREQUAL \"Clang\" AND ICHOR_USE_SANITIZERS) OR NOT ICHOR_USE_SANITIZERS" OFF) + +option(ICHOR_USE_BACKWARD "Use backward-cpp to print stacktraces on crashes or when the user wants to. Useful for debugging." $ICHOR_BUILDING_DEBUG) option(ICHOR_USE_HIREDIS "Add hiredis dependency" OFF) option(ICHOR_MUSL "Use when building for musl instead of glibc" OFF) option(ICHOR_AARCH64 "Use when building for aarch64. Turns off some x86-specific compiler flags." OFF) @@ -79,7 +81,7 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") endif() set(ICHOR_ARCH_OPTIMIZATION OFF CACHE STRING "Tell compiler to optimize for target") -set_property(CACHE ICHOR_ARCH_OPTIMIZATION PROPERTY STRINGS OFF NATIVE X86_64 X86_64_SSE4 X86_64_AVX2 X86_64_AVX512 MODERN_ARM_GENERIC) +set_property(CACHE ICHOR_ARCH_OPTIMIZATION PROPERTY STRINGS OFF NATIVE X86_64 X86_64_SSE4 X86_64_AVX2 X86_64_AVX512 MODERN_ARM_GENERIC RASPBERRY_PI_ONE RASPBERRY_PI_TWO RASPBERRY_PI_THREE RASPBERRY_PI_FOUR RASPBERRY_PI_FIVE) if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND ICHOR_RUN_CLANG_TIDY) set(CMAKE_CXX_CLANG_TIDY "clang-tidy;-checks=*,-llvmlibc-*,-readability-function-cognitive-complexity,-altera-*,-modernize-use-trailing-return-type,-concurrency-mt-unsafe,-fuchsia-default-arguments-calls,-android-*,-readability-identifier-length,-clang-analyzer-optin.cplusplus.UninitializedObject") @@ -89,15 +91,17 @@ set(FMT_SOURCES ${ICHOR_EXTERNAL_DIR}/fmt/src/format.cc ${ICHOR_EXTERNAL_DIR}/fm file(GLOB_RECURSE ICHOR_FRAMEWORK_SOURCES ${ICHOR_TOP_DIR}/src/ichor/*.cpp) file(GLOB_RECURSE ICHOR_OPTIONAL_ETCD_SOURCES ${ICHOR_TOP_DIR}/src/services/etcd/*.cpp) file(GLOB_RECURSE ICHOR_LOGGING_SOURCES ${ICHOR_TOP_DIR}/src/services/logging/*.cpp) -file(GLOB_RECURSE ICHOR_NETWORK_SOURCES ${ICHOR_TOP_DIR}/src/services/network/*.cpp) +file(GLOB_RECURSE ICHOR_TCP_SOURCES ${ICHOR_TOP_DIR}/src/services/network/tcp/*.cpp) +file(GLOB_RECURSE ICHOR_BOOST_BEAST_SOURCES ${ICHOR_TOP_DIR}/src/services/network/boost/*.cpp) file(GLOB_RECURSE ICHOR_METRICS_SOURCES ${ICHOR_TOP_DIR}/src/services/metrics/*.cpp) file(GLOB_RECURSE ICHOR_TIMER_SOURCES ${ICHOR_TOP_DIR}/src/services/timer/*.cpp) file(GLOB_RECURSE ICHOR_OPTIONAL_HIREDIS_SOURCES ${ICHOR_TOP_DIR}/src/services/redis/*.cpp) file(GLOB_RECURSE ICHOR_IO_SOURCES ${ICHOR_TOP_DIR}/src/services/io/*.cpp) +file(GLOB_RECURSE ICHOR_BASE64_SOURCES ${ICHOR_TOP_DIR}/src/base64/*.cpp) file(GLOB SPDLOG_SOURCES ${ICHOR_EXTERNAL_DIR}/spdlog/src/*.cpp) -add_library(ichor ${FMT_SOURCES} ${ICHOR_FRAMEWORK_SOURCES} ${ICHOR_LOGGING_SOURCES} ${ICHOR_NETWORK_SOURCES} ${ICHOR_METRICS_SOURCES} ${ICHOR_TIMER_SOURCES} ${ICHOR_IO_SOURCES}) +add_library(ichor ${FMT_SOURCES} ${ICHOR_FRAMEWORK_SOURCES} ${ICHOR_LOGGING_SOURCES} ${ICHOR_TCP_SOURCES} ${ICHOR_METRICS_SOURCES} ${ICHOR_TIMER_SOURCES} ${ICHOR_IO_SOURCES} ${ICHOR_BASE64_SOURCES}) if(ICHOR_ENABLE_INTERNAL_DEBUGGING) target_compile_definitions(ichor PUBLIC ICHOR_ENABLE_INTERNAL_DEBUGGING) @@ -108,6 +112,9 @@ endif() if(ICHOR_ENABLE_INTERNAL_IO_DEBUGGING) target_compile_definitions(ichor PUBLIC ICHOR_ENABLE_INTERNAL_IO_DEBUGGING) endif() +if(ICHOR_ENABLE_INTERNAL_STL_DEBUGGING) + target_compile_definitions(ichor PUBLIC ICHOR_ENABLE_INTERNAL_STL_DEBUGGING) +endif() if(ICHOR_USE_SPDLOG) target_compile_definitions(ichor PUBLIC SPDLOG_COMPILED_LIB SPDLOG_NO_EXCEPTIONS SPDLOG_FMT_EXTERNAL SPDLOG_DISABLE_DEFAULT_LOGGER SPDLOG_NO_ATOMIC_LEVELS ICHOR_USE_SPDLOG SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_TRACE) @@ -126,19 +133,31 @@ if(ICHOR_USE_BOOST_BEAST) endif() if(ICHOR_MUSL) target_compile_definitions(ichor PUBLIC ICHOR_MUSL) - target_link_options(ichor PUBLIC -static-libgcc -static-libstdc++) + target_compile_options(ichor PUBLIC -static) + target_link_options(ichor PUBLIC -static-libgcc -static-libstdc++ -static) endif() if(ICHOR_AARCH64) target_compile_definitions(ichor PUBLIC ICHOR_AARCH64) endif() +if(ICHOR_BUILDING_DEBUG) + target_compile_definitions(ichor PUBLIC ICHOR_BUILDING_DEBUG) +endif() if(NOT WIN32) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wshadow -Wnon-virtual-dtor -Wno-unused-variable -Wno-long-long -Wno-unused-parameter -Wnull-dereference -pedantic -Wformat -Wformat-security -Wcast-align -Woverloaded-virtual ") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread -ftemplate-backtrace-limit=0 ") - if(ICHOR_USE_HARDENING AND (CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES RelWithDebInfo)) - # Enable runtime iterator debug checks (like checking two different iterators from different containers) + target_compile_options(ichor PUBLIC -Wall -Wextra -Wshadow -Wno-non-virtual-dtor -Wno-unused-variable -Wno-long-long -Wno-unused-parameter -Wnull-dereference -pedantic -Wformat -Wformat-security -Wcast-align -Woverloaded-virtual -Wdelete-non-virtual-dtor) + target_compile_options(ichor PUBLIC -pthread -ftemplate-backtrace-limit=0) + + # TODO Would love to refactor this to a function, but functions don't seem to be able to reach variables not yet defined when the function is defined. + if(ICHOR_USE_HARDENING AND CMAKE_BUILD_TYPE MATCHES Debug) + # target_compile_definitions annoyingly adds "-D" in front of "-U" and target_compile_options goes after target_compile_definitions. Argh. + target_compile_options(ichor PUBLIC -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3) + + # Enable runtime iterator debug checks (like checking two different iterators from different container target_compile_definitions(ichor PUBLIC _GLIBCXX_ASSERTIONS _GLIBCXX_DEBUG _GLIBCXX_DEBUG_PEDANTIC _ITERATOR_DEBUG_LEVEL=2 _LIBCPP_DEBUG=1 _LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG) elseif(ICHOR_USE_HARDENING) + # target_compile_definitions annoyingly adds "-D" in front of "-U" and target_compile_options goes after target_compile_definitions. Argh. + target_compile_options(ichor PUBLIC -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3) + target_compile_definitions(ichor PUBLIC _GLIBCXX_ASSERTIONS _LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST) endif() target_compile_definitions(ichor PUBLIC _FILE_OFFSET_BITS=64 _TIME_BITS=64) @@ -156,14 +175,22 @@ else() target_compile_definitions(ichor PUBLIC NOMINMAX WIN32_LEAN_AND_MEAN ) endif() +if(ICHOR_USE_LIBCPP) + target_compile_options(ichor PUBLIC -stdlib=libc++) + target_compile_definitions(ichor PUBLIC ICHOR_USE_LIBCPP) + target_link_options(ichor PUBLIC -stdlib=libc++) +endif() + # gcc added support for mold in version 12, before that, the flag is ignored. # Note that with older versions of mold and early versions of gcc 12, issues with the tests have been observed. Please upgrade to the latest if you notice any issues. -if(ICHOR_USE_MOLD) +if(ICHOR_USE_MOLD AND NOT WIN32) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=mold ") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fuse-ld=mold ") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -fuse-ld=mold ") endif() if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wduplicated-cond -Wduplicated-branches -Wlogical-op ") + target_compile_options(ichor PUBLIC -Wduplicated-cond -Wduplicated-branches -Wlogical-op) endif() if(WIN32) @@ -187,6 +214,36 @@ else() target_compile_options(ichor PUBLIC -march=x86-64-v4) elseif(ICHOR_ARCH_OPTIMIZATION STREQUAL "MODERN_ARM_GENERIC") target_compile_options(ichor PUBLIC -march=armv8-a) + elseif(ICHOR_ARCH_OPTIMIZATION STREQUAL "RASPBERRY_PI_ONE") + if(ICHOR_AARCH64) + target_compile_options(ichor PUBLIC -march=armv6zk -mcpu=arm1176jzf-s) + else() + target_compile_options(ichor PUBLIC -march=armv6zk -mcpu=arm1176jzf-s -mfpu=vfp) + endif() + elseif(ICHOR_ARCH_OPTIMIZATION STREQUAL "RASPBERRY_PI_TWO") + if(ICHOR_AARCH64) + target_compile_options(ichor PUBLIC -march=armv7-a -mcpu=cortex-a7) + else() + target_compile_options(ichor PUBLIC -march=armv7-a -mcpu=cortex-a7 -mfpu=neon-vfpv4) + endif() + elseif(ICHOR_ARCH_OPTIMIZATION STREQUAL "RASPBERRY_PI_THREE") + if(ICHOR_AARCH64) + target_compile_options(ichor PUBLIC -march=armv8-a+crc -mcpu=cortex-a53) + else() + target_compile_options(ichor PUBLIC -march=armv8-a+crc -mcpu=cortex-a53 -mfpu=crypto-neon-fp-armv8) + endif() + elseif(ICHOR_ARCH_OPTIMIZATION STREQUAL "RASPBERRY_PI_FOUR") + if(ICHOR_AARCH64) + target_compile_options(ichor PUBLIC -march=armv8-a+crc -mcpu=cortex-a72) + else() + target_compile_options(ichor PUBLIC -march=armv8-a+crc -mcpu=cortex-a72 -mfpu=crypto-neon-fp-armv8) + endif() + elseif(ICHOR_ARCH_OPTIMIZATION STREQUAL "RASPBERRY_PI_FIVE") + if(ICHOR_AARCH64) + target_compile_options(ichor PUBLIC -march=armv8-a+crc+crypto -mcpu=cortex-a76) + else() + target_compile_options(ichor PUBLIC -march=armv8-a+crc+crypto -mcpu=cortex-a76 -mfpu=crypto-neon-fp-armv8) + endif() endif() endif() @@ -236,7 +293,8 @@ endif() if(ICHOR_USE_THREAD_SANITIZER) target_compile_options(ichor PUBLIC -fsanitize=thread -fno-omit-frame-pointer) - target_link_options(ichor PUBLIC -fsanitize=thread -static-libtsan) + target_link_options(ichor PUBLIC -fsanitize=thread) + target_compile_definitions(ichor PUBLIC __SANITIZE_THREAD__) # clang on OSX doesn't accept -no-pie for some reason if(NOT APPLE) @@ -245,17 +303,21 @@ if(ICHOR_USE_THREAD_SANITIZER) if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") target_compile_options(ichor PUBLIC -no-pie) + target_link_options(ichor PUBLIC -static-libtsan) + else() + target_link_options(ichor PUBLIC -static-libsan) endif() if(ICHOR_USE_BOOST_BEAST) - target_compile_definitions(ichor PUBLIC BOOST_USE_UCONTEXT) + target_compile_definitions(ichor PUBLIC BOOST_USE_TSAN BOOST_USE_UCONTEXT) message(WARNING "Sanitizers seem to have issues with stack switches. Need to compile boost library specifically to deal with this, and then still thread sanitizer seems to have issues. Please see https://www.boost.org/doc/libs/master/libs/context/doc/html/context/stack/sanitizers.html and https://github.com/boostorg/beast/issues/2615") endif() endif() -if(NOT DEFINED ICHOR_USE_SANITIZERS AND NOT DEFINED ICHOR_USE_THREAD_SANITIZER) +if(NOT WIN32 AND NOT ICHOR_USE_SANITIZERS AND NOT ICHOR_USE_THREAD_SANITIZER) # see https://github.com/google/sanitizers/issues/856 - target_compile_options(ichor PUBLIC -fPIE) + target_compile_options(ichor PUBLIC -fpie) + target_link_options(ichor PUBLIC -pie) endif() if(WIN32 AND ICHOR_USE_HARDENING) @@ -273,12 +335,18 @@ elseif(ICHOR_USE_HARDENING) target_compile_options(ichor PUBLIC -mbranch-protection=standard) endif() - # stack clash protection not available on OSX - if(NOT APPLE) + # stack clash protection not available on OSX nor on AARCH64 + if(NOT APPLE AND NOT ICHOR_AARCH64) target_compile_options(ichor PUBLIC -fstack-clash-protection) endif() target_compile_definitions(ichor PUBLIC ICHOR_USE_HARDENING) + + if(NOT APPLE) + target_link_options(ichor PUBLIC -Wl,-z,nodlopen -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now) + else() + target_link_options(ichor PUBLIC -Wl,-bind_at_load) + endif() endif() if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") @@ -308,7 +376,8 @@ endif() # Gcc 12.1 and 12.2 have bugs that prevent compilation with Werror at -O2 and higher: # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=107138 # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105329 -if((NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_VERSION VERSION_LESS "12") AND NOT WIN32) +# 12.3 has a bug where [[maybe_unused]] in glaze is somehow ignored. +if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT WIN32) target_compile_options(ichor PRIVATE "-Werror") #prevent externally compiled sources to error out on warnings endif() @@ -329,8 +398,18 @@ if(ICHOR_USE_MIMALLOC) set(MI_USE_CXX ON) option(MI_BUILD_TESTS "Build test executables" OFF) + if(ICHOR_USE_HARDENING OR ICHOR_ENABLE_INTERNAL_DEBUGGING) + set(MI_SECURE ON) + endif() + if(ICHOR_ENABLE_INTERNAL_DEBUGGING) + set(MI_DEBUG_FULL ON) + endif() + if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND ICHOR_USE_SANITIZERS AND NOT WIN32) + set(MI_DEBUG_UBSAN ON) + set(MI_TRACK_ASAN ON) + endif() + add_subdirectory(external/mimalloc EXCLUDE_FROM_ALL) - target_compile_definitions(mimalloc-static PRIVATE MI_USE_ENVIRON=0) target_link_libraries(ichor PUBLIC mimalloc-static) endfunction() @@ -339,15 +418,12 @@ if(ICHOR_USE_MIMALLOC) endif() endif() -if(ICHOR_USE_ABSEIL) - find_package(absl REQUIRED) - target_link_libraries(ichor PUBLIC absl::flat_hash_map absl::flat_hash_set absl::btree absl::hash) - target_compile_definitions(ichor PUBLIC ICHOR_USE_ABSEIL) -endif() - if(ICHOR_USE_SPDLOG) target_sources(ichor PRIVATE ${SPDLOG_SOURCES}) endif() +if(ICHOR_USE_BOOST_BEAST) + target_sources(ichor PRIVATE ${ICHOR_BOOST_BEAST_SOURCES}) +endif() if(ICHOR_USE_SDEVENT) find_package(PkgConfig REQUIRED) @@ -396,14 +472,14 @@ if(NOT WIN32 AND NOT APPLE) target_link_libraries(ichor PUBLIC -ldl -lrt) endif() -target_include_directories(ichor PUBLIC - $ - $) - target_include_directories(ichor PUBLIC $ $) +target_include_directories(ichor PUBLIC + $ + $) + if(ICHOR_USE_SPDLOG) target_include_directories(ichor PUBLIC $ @@ -471,13 +547,31 @@ install(DIRECTORY "${CMAKE_SOURCE_DIR}/include/sole" # source directory DESTINATION "include/ichor/external" # target directory FILES_MATCHING # install only matched files PATTERN "*.h" # select header files - ) +) + +install(DIRECTORY "${CMAKE_SOURCE_DIR}/include/base64" # source directory + DESTINATION "include/ichor/external" # target directory + FILES_MATCHING # install only matched files + PATTERN "*.h" # select header files +) + +install(DIRECTORY "${CMAKE_SOURCE_DIR}/include/ankerl" # source directory + DESTINATION "include/ichor/external" # target directory + FILES_MATCHING # install only matched files + PATTERN "*.h" # select header files +) + +install(DIRECTORY "${CMAKE_SOURCE_DIR}/include/ctre" # source directory + DESTINATION "include/ichor/external" # target directory + FILES_MATCHING # install only matched files + PATTERN "*.h" # select header files +) install(DIRECTORY "${CMAKE_SOURCE_DIR}/include/tl" # source directory DESTINATION "include/ichor/external" # target directory FILES_MATCHING # install only matched files PATTERN "*.h" # select header files - ) +) install(DIRECTORY "${CMAKE_SOURCE_DIR}/include/backward" # source directory DESTINATION "include/ichor/external" # target directory @@ -537,6 +631,7 @@ if(ICHOR_BUILD_TESTING) add_subdirectory(${ICHOR_EXTERNAL_DIR}/Catch2) #enable string_view support in catch2 target_compile_features(Catch2 PRIVATE cxx_std_20) + target_compile_features(Catch2WithMain PRIVATE cxx_std_20) list(APPEND CMAKE_MODULE_PATH "${ICHOR_EXTERNAL_DIR}/Catch2/extras") enable_testing() @@ -545,7 +640,42 @@ if(ICHOR_BUILD_TESTING) if(ICHOR_USE_SANITIZERS) if(WIN32) target_compile_options(Catch2 PUBLIC /fsanitize=address /Zi) + target_compile_options(Catch2WithMain PUBLIC /fsanitize=address /Zi) endif() endif() + if(ICHOR_MUSL) + target_compile_options(Catch2 PUBLIC -static) + target_compile_options(Catch2WithMain PUBLIC -static) + target_link_options(Catch2 PUBLIC -static-libgcc -static-libstdc++ -static) + target_link_options(Catch2WithMain PUBLIC -static-libgcc -static-libstdc++ -static) + endif() + + if(NOT WIN32 AND NOT ICHOR_USE_SANITIZERS AND NOT ICHOR_USE_THREAD_SANITIZER) + target_compile_options(Catch2 PUBLIC -fpie) + target_compile_options(Catch2WithMain PUBLIC -fpie) + target_link_options(Catch2 PUBLIC -pie) + target_link_options(Catch2WithMain PUBLIC -pie) + endif() + + # TODO Would love to refactor this to a function, but functions don't seem to be able to reach variables not yet defined when the function is defined. + if(NOT WIN32 AND ICHOR_USE_HARDENING AND CMAKE_BUILD_TYPE MATCHES Debug) + # target_compile_definitions annoyingly adds "-D" in front of "-U" and target_compile_options goes after target_compile_definitions. Argh. + target_compile_options(Catch2 PRIVATE -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3) + target_compile_options(Catch2WithMain PRIVATE -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3) + # Enable runtime iterator debug checks (like checking two different iterators from different container + target_compile_definitions(Catch2 PRIVATE _GLIBCXX_ASSERTIONS _GLIBCXX_DEBUG _GLIBCXX_DEBUG_PEDANTIC _ITERATOR_DEBUG_LEVEL=2 _LIBCPP_DEBUG=1 _LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG) + target_compile_definitions(Catch2WithMain PRIVATE _GLIBCXX_ASSERTIONS _GLIBCXX_DEBUG _GLIBCXX_DEBUG_PEDANTIC _ITERATOR_DEBUG_LEVEL=2 _LIBCPP_DEBUG=1 _LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG) + elseif(NOT WIN32 AND ICHOR_USE_HARDENING) + # target_compile_definitions annoyingly adds "-D" in front of "-U" and target_compile_options goes after target_compile_definitions. Argh. + target_compile_options(Catch2 PRIVATE -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3) + target_compile_options(Catch2WithMain PRIVATE -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3) + target_compile_definitions(Catch2 PRIVATE _GLIBCXX_ASSERTIONS _LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST) + target_compile_definitions(Catch2WithMain PRIVATE _GLIBCXX_ASSERTIONS _LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST) + endif() + + if(ICHOR_USE_LIBCPP) + target_compile_options(Catch2 PUBLIC -stdlib=libc++) + target_link_options(Catch2 PUBLIC -stdlib=libc++) + endif() endif() diff --git a/Dockerfile b/Dockerfile index 806d8141..a69685ab 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM ubuntu:jammy RUN apt update -RUN apt install -y g++-12 gcc-12 build-essential cmake libssl-dev pkg-config git wget ninja-build nano libzip-dev +RUN apt install -y g++-12 gcc-12 build-essential cmake pkg-config git wget ninja-build nano libzip-dev RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 60 RUN update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 60 @@ -10,17 +10,29 @@ RUN update-alternatives --install /usr/bin/cpp cpp /usr/bin/cpp-12 60 # Run all downloads first to be able to use Docker's layers as cache and prevent excessive redownloads WORKDIR /opt -RUN wget https://boostorg.jfrog.io/artifactory/main/release/1.83.0/source/boost_1_83_0.tar.bz2 RUN wget https://github.com/redis/hiredis/archive/refs/tags/v1.2.0.tar.gz +RUN wget https://boostorg.jfrog.io/artifactory/main/release/1.83.0/source/boost_1_83_0.tar.bz2 +RUN wget https://www.openssl.org/source/openssl-3.0.11.tar.gz + +ENV CFLAGS="-O2 -fstack-protector-strong -fcf-protection -fstack-clash-protection -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3" +ENV CXXFLAGS="-O2 -std=c++20 -fstack-protector-strong -fcf-protection -fstack-clash-protection -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST" +ENV LDFLAGS="-Wl,-z,nodlopen -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now" +RUN tar xf openssl-3.0.11.tar.gz +WORKDIR /opt/openssl-3.0.11 +RUN ./Configure --prefix=/usr --openssldir=/etc/ssl --libdir=lib no-shared +RUN make -j +RUN make -j install + +WORKDIR /opt #Build a new enough boost, apt only contains 1.74 which is too old. RUN tar xf boost_1_83_0.tar.bz2 WORKDIR /opt/boost_1_83_0 RUN ./bootstrap.sh --prefix=/usr -RUN ./b2 variant=release link=static threading=multi -RUN ./b2 variant=release link=static threading=multi install +RUN ./b2 variant=release link=static threading=multi cxxflags="-O2 -std=c++20 -fstack-protector-strong -fcf-protection -fstack-clash-protection -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST" linkflags="-Wl,-z,nodlopen -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now" +RUN ./b2 variant=release link=static threading=multi cxxflags="-O2 -std=c++20 -fstack-protector-strong -fcf-protection -fstack-clash-protection -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST" linkflags="-Wl,-z,nodlopen -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now" install WORKDIR /opt @@ -29,7 +41,7 @@ RUN tar xf v1.2.0.tar.gz RUN mkdir /opt/hiredis-1.2.0/build WORKDIR /opt/hiredis-1.2.0/build -RUN cmake -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DDISABLE_TESTS=1 -DCMAKE_INSTALL_PREFIX=/usr -DENABLE_SSL=1 .. +RUN cmake -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DDISABLE_TESTS=1 -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_SHARED_LIBS=OFF -DENABLE_SSL=1 .. RUN ninja && ninja install RUN mkdir -p /opt/ichor/build @@ -38,4 +50,4 @@ WORKDIR /opt/ichor/build ENTRYPOINT ["/bin/bash", "-c"] -CMD ["cd /opt/ichor/build && cmake -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DICHOR_USE_SANITIZERS=0 -DICHOR_USE_HIREDIS=1 -DICHOR_USE_BOOST_BEAST=1 -DICHOR_USE_SPDLOG=1 /opt/ichor/src && ninja"] +CMD ["unset CFLAGS CXXFLAGS LDFLAGS && cd /opt/ichor/build && cmake -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DICHOR_USE_SANITIZERS=0 -DICHOR_USE_HIREDIS=1 -DICHOR_USE_BOOST_BEAST=1 -DICHOR_USE_SPDLOG=1 -DICHOR_SKIP_EXTERNAL_TESTS=1 /opt/ichor/src && ninja && ninja test"] diff --git a/Dockerfile-asan b/Dockerfile-asan index 649c1829..52fbdec0 100644 --- a/Dockerfile-asan +++ b/Dockerfile-asan @@ -1,7 +1,7 @@ FROM ubuntu:jammy RUN apt update -RUN apt install -y g++-12 gcc-12 build-essential cmake libssl-dev pkg-config git wget ninja-build nano libzip-dev +RUN apt install -y g++-12 gcc-12 build-essential cmake pkg-config git wget ninja-build nano libzip-dev RUN update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 60 RUN update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 60 @@ -10,20 +10,29 @@ RUN update-alternatives --install /usr/bin/cpp cpp /usr/bin/cpp-12 60 # Run all downloads first to be able to use Docker's layers as cache and prevent excessive redownloads WORKDIR /opt -RUN wget https://boostorg.jfrog.io/artifactory/main/release/1.83.0/source/boost_1_83_0.tar.bz2 RUN wget https://github.com/redis/hiredis/archive/refs/tags/v1.2.0.tar.gz +RUN wget https://boostorg.jfrog.io/artifactory/main/release/1.83.0/source/boost_1_83_0.tar.bz2 +RUN wget https://www.openssl.org/source/openssl-3.0.11.tar.gz ENV CFLAGS="-Og -fsanitize=address,undefined" ENV CXXFLAGS="-Og -fsanitize=address,undefined" +ENV LDFLAGS="-fsanitize=address,undefined -static-libasan -static-libgcc -static-libstdc++" + +RUN tar xf openssl-3.0.11.tar.gz +WORKDIR /opt/openssl-3.0.11 +RUN ./Configure --prefix=/usr --openssldir=/etc/ssl --libdir=lib no-shared +RUN make -j +RUN make -j install +WORKDIR /opt #Build boost with support for asan RUN tar xf boost_1_83_0.tar.bz2 WORKDIR /opt/boost_1_83_0 RUN ./bootstrap.sh --prefix=/usr -RUN ./b2 cxxflags="-fsanitize=address,undefined -Og -std=c++17 -DBOOST_USE_ASAN -DBOOST_USE_UCONTEXT" linkflags="-lasan -lubsan" variant=debug link=static threading=multi context-impl=ucontext -RUN ./b2 cxxflags="-fsanitize=address,undefined -Og -std=c++17 -DBOOST_USE_ASAN -DBOOST_USE_UCONTEXT" linkflags="-lasan -lubsan" variant=debug link=static threading=multi context-impl=ucontext install +RUN ./b2 cxxflags="-fsanitize=address,undefined -Og -std=c++20 -DBOOST_USE_ASAN -DBOOST_USE_UCONTEXT" linkflags="-static-libasan -static-libgcc -static-libstdc++ -lubsan" variant=debug link=static threading=multi context-impl=ucontext +RUN ./b2 cxxflags="-fsanitize=address,undefined -Og -std=c++20 -DBOOST_USE_ASAN -DBOOST_USE_UCONTEXT" linkflags="-static-libasan -static-libgcc -static-libstdc++ -lubsan" variant=debug link=static threading=multi context-impl=ucontext install WORKDIR /opt @@ -32,16 +41,13 @@ RUN tar xf v1.2.0.tar.gz RUN mkdir /opt/hiredis-1.2.0/build WORKDIR /opt/hiredis-1.2.0/build -RUN cmake -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DDISABLE_TESTS=1 -DCMAKE_INSTALL_PREFIX=/usr -DENABLE_SSL=1 .. +RUN cmake -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DDISABLE_TESTS=1 -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_SHARED_LIBS=OFF -DENABLE_SSL=1 .. RUN ninja && ninja install RUN mkdir -p /opt/ichor/build WORKDIR /opt/ichor/build -RUN unset CFLAGS -RUN unset CXXFLAGS - ENTRYPOINT ["/bin/bash", "-c"] -CMD ["cd /opt/ichor/build && cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DICHOR_USE_SANITIZERS=1 -DICHOR_USE_HIREDIS=1 -DICHOR_USE_BOOST_BEAST=1 -DICHOR_USE_SPDLOG=1 /opt/ichor/src && ninja"] +CMD ["unset CFLAGS CXXFLAGS && cd /opt/ichor/build && cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DICHOR_USE_SANITIZERS=1 -DICHOR_USE_HIREDIS=1 -DICHOR_USE_BOOST_BEAST=1 -DICHOR_USE_SPDLOG=1 -DICHOR_SKIP_EXTERNAL_TESTS=1 /opt/ichor/src && ninja && ninja test"] diff --git a/Dockerfile-asan-clang b/Dockerfile-asan-clang new file mode 100644 index 00000000..829a9284 --- /dev/null +++ b/Dockerfile-asan-clang @@ -0,0 +1,55 @@ +FROM ubuntu:jammy + +RUN apt update && apt-get install -y wget lsb-release software-properties-common gnupg +RUN wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && ./llvm.sh 18 all +RUN apt install -y cmake pkg-config git ninja-build nano libzip-dev clang-18 + +# Run all downloads first to be able to use Docker's layers as cache and prevent excessive redownloads + +WORKDIR /opt +RUN wget https://github.com/redis/hiredis/archive/refs/tags/v1.2.0.tar.gz +RUN wget https://boostorg.jfrog.io/artifactory/main/release/1.83.0/source/boost_1_83_0.tar.bz2 +RUN wget https://www.openssl.org/source/openssl-3.0.11.tar.gz + +ENV CC="clang" +ENV CXX="clang++" +ENV CFLAGS="-Og -fsanitize=address,undefined -stdlib=libc++" +ENV CXXFLAGS="-Og -fsanitize=address,undefined -stdlib=libc++" +ENV LDFLAGS="-stdlib=libc++ -static-libstdc++ -l:libc++abi.a -static-libsan -static-libgcc" +ENV PATH="${PATH}:/usr/lib/llvm-18/bin" +# false positive in QueueTests, see https://github.com/llvm/llvm-project/issues/59432 +ENV ASAN_OPTIONS="alloc_dealloc_mismatch=0" + +RUN tar xf openssl-3.0.11.tar.gz +WORKDIR /opt/openssl-3.0.11 +RUN ./Configure --prefix=/usr --openssldir=/etc/ssl --libdir=lib no-shared +RUN make -j +RUN make -j install + +WORKDIR /opt +#Build boost with support for asan +RUN tar xf boost_1_83_0.tar.bz2 + +WORKDIR /opt/boost_1_83_0 + +RUN ./bootstrap.sh --prefix=/usr --with-toolset=clang +RUN ./b2 cxxflags="-fsanitize=address,undefined -Og -std=c++17 -stdlib=libc++ -DBOOST_USE_ASAN -DBOOST_USE_UCONTEXT" linkflags="-stdlib=libc++ -static-libstdc++ -l:libc++abi.a -static-libsan -lubsan" variant=debug link=static threading=multi context-impl=ucontext +RUN ./b2 cxxflags="-fsanitize=address,undefined -Og -std=c++17 -stdlib=libc++ -DBOOST_USE_ASAN -DBOOST_USE_UCONTEXT" linkflags="-stdlib=libc++ -static-libstdc++ -l:libc++abi.a -static-libsan -lubsan" variant=debug link=static threading=multi context-impl=ucontext install + +WORKDIR /opt + +#Build latest hiredis containing sdevent support, not available yet in apt +RUN tar xf v1.2.0.tar.gz +RUN mkdir /opt/hiredis-1.2.0/build + +WORKDIR /opt/hiredis-1.2.0/build +RUN cmake -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DDISABLE_TESTS=1 -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_SHARED_LIBS=OFF -DENABLE_SSL=1 .. +RUN ninja && ninja install + +RUN mkdir -p /opt/ichor/build + +WORKDIR /opt/ichor/build + +ENTRYPOINT ["/bin/bash", "-c"] + +CMD ["unset CFLAGS CXXFLAGS && cd /opt/ichor/build && cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DICHOR_USE_SANITIZERS=1 -DICHOR_USE_HIREDIS=1 -DICHOR_USE_BOOST_BEAST=1 -DICHOR_USE_SPDLOG=1 -DICHOR_SKIP_EXTERNAL_TESTS=1 /opt/ichor/src && ninja && ninja test"] diff --git a/Dockerfile-clang b/Dockerfile-clang new file mode 100644 index 00000000..4d7cb732 --- /dev/null +++ b/Dockerfile-clang @@ -0,0 +1,47 @@ +FROM ubuntu:jammy + +RUN apt update && apt-get install -y wget lsb-release software-properties-common gnupg +RUN wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && ./llvm.sh 18 all +RUN apt install -y cmake libssl-dev pkg-config git ninja-build nano libzip-dev clang-18 + +# Run all downloads first to be able to use Docker's layers as cache and prevent excessive redownloads + +WORKDIR /opt +RUN wget https://boostorg.jfrog.io/artifactory/main/release/1.83.0/source/boost_1_83_0.tar.bz2 +RUN wget https://github.com/redis/hiredis/archive/refs/tags/v1.2.0.tar.gz + +ENV CC="clang" +ENV CXX="clang++" +ENV PATH="${PATH}:/usr/lib/llvm-18/bin" +# false positive in QueueTests, see https://github.com/llvm/llvm-project/issues/59432 +ENV ASAN_OPTIONS="alloc_dealloc_mismatch=0" + +ENV CFLAGS="-O2 -fstack-protector-strong -fcf-protection -fstack-clash-protection -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3" +ENV CXXFLAGS="-O2 -std=c++20 -fstack-protector-strong -fcf-protection -fstack-clash-protection -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST" +ENV LDFLAGS="-static-libgcc -static-libstdc++ -Wl,-z,nodlopen -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now" +#Build a new enough boost, apt only contains 1.74 which is too old. +RUN tar xf boost_1_83_0.tar.bz2 + +WORKDIR /opt/boost_1_83_0 + +RUN ./bootstrap.sh --prefix=/usr +RUN ./b2 variant=release link=static threading=multi cxxflags="-O2 -std=c++20 -fstack-protector-strong -fcf-protection -fstack-clash-protection -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST" linkflags="-Wl,-z,nodlopen -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now" +RUN ./b2 variant=release link=static threading=multi cxxflags="-O2 -std=c++20 -fstack-protector-strong -fcf-protection -fstack-clash-protection -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST" linkflags="-Wl,-z,nodlopen -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now" install + +WORKDIR /opt + +#Build latest hiredis containing sdevent support, not available yet in apt +RUN tar xf v1.2.0.tar.gz +RUN mkdir /opt/hiredis-1.2.0/build + +WORKDIR /opt/hiredis-1.2.0/build +RUN cmake -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DDISABLE_TESTS=1 -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_SHARED_LIBS=OFF -DENABLE_SSL=1 .. +RUN ninja && ninja install + +RUN mkdir -p /opt/ichor/build + +WORKDIR /opt/ichor/build + +ENTRYPOINT ["/bin/bash", "-c"] + +CMD ["unset CFLAGS CXXFLAGS LDFLAGS && cd /opt/ichor/build && cmake -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DICHOR_USE_SANITIZERS=0 -DICHOR_USE_HIREDIS=1 -DICHOR_USE_BOOST_BEAST=1 -DICHOR_USE_SPDLOG=1 -DICHOR_SKIP_EXTERNAL_TESTS=1 /opt/ichor/src && ninja && ninja test"] diff --git a/Dockerfile-musl b/Dockerfile-musl index 1d2b55d9..015fea40 100644 --- a/Dockerfile-musl +++ b/Dockerfile-musl @@ -1,4 +1,4 @@ -FROM alpine:3.17 +FROM alpine:3.19 RUN apk update RUN apk add gcc g++ build-base cmake git wget make nano sed linux-headers perl @@ -10,7 +10,9 @@ RUN wget https://boostorg.jfrog.io/artifactory/main/release/1.81.0/source/boost_ RUN wget https://github.com/redis/hiredis/archive/refs/tags/v1.2.0.tar.gz RUN wget https://www.openssl.org/source/openssl-3.0.11.tar.gz -ENV LDFLAGS="-static-libgcc -static-libstdc++ -static" +ENV CFLAGS="-O2 -static -fstack-protector-strong -fcf-protection -fstack-clash-protection -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3" +ENV CXXFLAGS="-O2 -std=c++20 -static -fstack-protector-strong -fcf-protection -fstack-clash-protection -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST" +ENV LDFLAGS="-static-libgcc -static-libstdc++ -static -Wl,-z,nodlopen -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now" #Build openssl statically, alpine (and probably most distros) only provide shared libraries. Might be a security thing? RUN tar xf openssl-3.0.11.tar.gz WORKDIR /opt/openssl-3.0.11 @@ -26,8 +28,8 @@ RUN tar xf boost_1_81_0.tar.bz2 WORKDIR /opt/boost_1_81_0 RUN ./bootstrap.sh --prefix=/usr -RUN ./b2 variant=release link=static threading=multi cxxflags="-O2 -std=c++17 -static -fstack-protector-strong -fcf-protection -fstack-clash-protection" linkflags="-static-libgcc -static-libstdc++ -static" -RUN ./b2 variant=release link=static threading=multi cxxflags="-O2 -std=c++17 -static -fstack-protector-strong -fcf-protection -fstack-clash-protection" linkflags="-static-libgcc -static-libstdc++ -static" install +RUN ./b2 variant=release link=static threading=multi cxxflags="-O2 -std=c++20 -static -fstack-protector-strong -fcf-protection -fstack-clash-protection -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST" linkflags="-static-libgcc -static-libstdc++ -static -Wl,-z,nodlopen -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now" +RUN ./b2 variant=release link=static threading=multi cxxflags="-O2 -std=c++20 -static -fstack-protector-strong -fcf-protection -fstack-clash-protection -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST" linkflags="-static-libgcc -static-libstdc++ -static -Wl,-z,nodlopen -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now" install WORKDIR /opt @@ -44,4 +46,4 @@ WORKDIR /opt/ichor/build ENTRYPOINT ["/bin/sh", "-c"] -CMD ["cd /opt/ichor/build && cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DICHOR_USE_SANITIZERS=0 -DICHOR_USE_HIREDIS=1 -DICHOR_USE_BOOST_BEAST=1 -DICHOR_USE_SPDLOG=1 -DICHOR_MUSL=1 /opt/ichor/src && make -j$(nproc)"] +CMD ["unset CFLAGS CXXFLAGS LDFLAGS && cd /opt/ichor/build && cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DICHOR_USE_SANITIZERS=0 -DICHOR_USE_HIREDIS=1 -DICHOR_USE_BOOST_BEAST=1 -DICHOR_USE_SPDLOG=1 -DICHOR_MUSL=1 -DICHOR_SKIP_EXTERNAL_TESTS=1 /opt/ichor/src && make -j$(nproc) && make test"] diff --git a/Dockerfile-musl-aarch64 b/Dockerfile-musl-aarch64 index e90c2600..cf8808fa 100644 --- a/Dockerfile-musl-aarch64 +++ b/Dockerfile-musl-aarch64 @@ -1,4 +1,4 @@ -FROM arm64v8/alpine:3.17 +FROM arm64v8/alpine:3.19 RUN apk update RUN apk add gcc g++ build-base cmake git wget make nano sed linux-headers perl @@ -10,7 +10,9 @@ RUN wget https://boostorg.jfrog.io/artifactory/main/release/1.81.0/source/boost_ RUN wget https://github.com/redis/hiredis/archive/refs/tags/v1.2.0.tar.gz RUN wget https://www.openssl.org/source/openssl-3.0.11.tar.gz -ENV LDFLAGS="-static-libgcc -static-libstdc++ -static" +ENV CFLAGS="-O2 -fstack-protector-strong -mbranch-protection=standard -fstack-clash-protection -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3" +ENV CXXFLAGS="-O2 -std=c++20 -fstack-protector-strong -mbranch-protection=standard -fstack-clash-protection -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST" +ENV LDFLAGS="-static-libgcc -static-libstdc++ -static -Wl,-z,nodlopen -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now" #Build openssl statically, alpine (and probably most distros) only provide shared libraries. Might be a security thing? RUN tar xf openssl-3.0.11.tar.gz WORKDIR /opt/openssl-3.0.11 @@ -26,8 +28,8 @@ RUN tar xf boost_1_81_0.tar.bz2 WORKDIR /opt/boost_1_81_0 RUN ./bootstrap.sh --prefix=/usr -RUN ./b2 variant=release link=static threading=multi cxxflags="-O2 -std=c++17 -static -fstack-protector-strong -fstack-clash-protection" linkflags="-static-libgcc -static-libstdc++ -static" -RUN ./b2 variant=release link=static threading=multi cxxflags="-O2 -std=c++17 -static -fstack-protector-strong -fstack-clash-protection" linkflags="-static-libgcc -static-libstdc++ -static" install +RUN ./b2 variant=release link=static threading=multi cxxflags="-O2 -std=c++20 -static -fstack-protector-strong -mbranch-protection=standard -fstack-clash-protection -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST" linkflags="-static-libgcc -static-libstdc++ -static -Wl,-z,nodlopen -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now" +RUN ./b2 variant=release link=static threading=multi cxxflags="-O2 -std=c++20 -static -fstack-protector-strong -mbranch-protection=standard -fstack-clash-protection -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 -D_GLIBCXX_ASSERTIONS -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST" linkflags="-static-libgcc -static-libstdc++ -static -Wl,-z,nodlopen -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now" install WORKDIR /opt @@ -44,4 +46,4 @@ WORKDIR /opt/ichor/build ENTRYPOINT ["/bin/sh", "-c"] -CMD ["cd /opt/ichor/build && cmake -DCMAKE_BUILD_TYPE=Debug -DICHOR_USE_SANITIZERS=0 -DICHOR_USE_HIREDIS=1 -DICHOR_USE_BOOST_BEAST=1 -DICHOR_USE_SPDLOG=1 -DICHOR_MUSL=1 -DICHOR_AARCH64=1 /opt/ichor/src && make -j$(nproc)"] +CMD ["unset CFLAGS CXXFLAGS LDFLAGS && cd /opt/ichor/build && cmake -DCMAKE_BUILD_TYPE=Debug -DICHOR_USE_HIREDIS=1 -DICHOR_USE_BOOST_BEAST=1 -DICHOR_USE_SPDLOG=1 -DICHOR_MUSL=1 -DICHOR_AARCH64=1 -DICHOR_ARCH_OPTIMIZATION=MODERN_ARM_GENERIC -DICHOR_SKIP_EXTERNAL_TESTS=1 /opt/ichor/src && make -j$(nproc) && make test"] diff --git a/Dockerfile-musl-aarch64-bench b/Dockerfile-musl-aarch64-bench new file mode 100644 index 00000000..010913b5 --- /dev/null +++ b/Dockerfile-musl-aarch64-bench @@ -0,0 +1,11 @@ +FROM arm64v8/alpine:3.19 + +RUN apk update +RUN apk add gcc g++ build-base cmake git wget make nano sed linux-headers perl +RUN mkdir -p /opt/ichor/build + +WORKDIR /opt/ichor/build + +ENTRYPOINT ["/bin/sh", "-c"] + +CMD ["cd /opt/ichor/build && cmake -DCMAKE_BUILD_TYPE=Release -DICHOR_BUILD_EXAMPLES=OFF -DICHOR_BUILD_TESTING=OFF -DICHOR_MUSL=1 -DICHOR_AARCH64=1 -DICHOR_ARCH_OPTIMIZATION=RASPBERRY_PI_FOUR /opt/ichor/src && make -j$(nproc)"] diff --git a/Dockerfile-tsan b/Dockerfile-tsan index c6b2930c..e1be78bd 100644 --- a/Dockerfile-tsan +++ b/Dockerfile-tsan @@ -60,8 +60,8 @@ EOF RUN patch ./boost/asio/detail/std_fenced_block.hpp < patch RUN ./bootstrap.sh --prefix=/usr -RUN ./b2 cxxflags="-fsanitize=thread -Og -std=c++17 -DBOOST_USE_TSAN" linkflags="-static-libtsan -static-libgcc -static-libstdc++" variant=debug link=static threading=multi -RUN ./b2 cxxflags="-fsanitize=thread -Og -std=c++17 -DBOOST_USE_TSAN" linkflags="-static-libtsan -static-libgcc -static-libstdc++" variant=debug link=static threading=multi install +RUN ./b2 cxxflags="-fsanitize=thread -Og -std=c++20 -DBOOST_USE_TSAN -DBOOST_USE_UCONTEXT" linkflags="-static-libtsan -static-libgcc -static-libstdc++" variant=debug link=static threading=multi context-impl=ucontext +RUN ./b2 cxxflags="-fsanitize=thread -Og -std=c++20 -DBOOST_USE_TSAN -DBOOST_USE_UCONTEXT" linkflags="-static-libtsan -static-libgcc -static-libstdc++" variant=debug link=static threading=multi context-impl=ucontext install WORKDIR /opt @@ -70,16 +70,13 @@ RUN tar xf v1.2.0.tar.gz RUN mkdir /opt/hiredis-1.2.0/build WORKDIR /opt/hiredis-1.2.0/build -RUN cmake -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DDISABLE_TESTS=1 -DCMAKE_INSTALL_PREFIX=/usr -DENABLE_SSL=1 .. +RUN cmake -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DDISABLE_TESTS=1 -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_SHARED_LIBS=OFF -DENABLE_SSL=1 .. RUN ninja && ninja install RUN mkdir -p /opt/ichor/build WORKDIR /opt/ichor/build -RUN unset CFLAGS -RUN unset CXXFLAGS - ENTRYPOINT ["/bin/bash", "-c"] -CMD ["cd /opt/ichor/build && cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DICHOR_USE_SANITIZERS=0 -DICHOR_USE_THREAD_SANITIZER=1 -DICHOR_USE_HIREDIS=1 -DICHOR_USE_BOOST_BEAST=1 -DICHOR_USE_SPDLOG=1 -DICHOR_USE_BACKWARD=0 /opt/ichor/src && ninja"] +CMD ["unset CFLAGS CXXFLAGS && cd /opt/ichor/build && cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DICHOR_USE_SANITIZERS=0 -DICHOR_USE_MIMALLOC=0 -DICHOR_USE_THREAD_SANITIZER=1 -DICHOR_USE_HIREDIS=1 -DICHOR_USE_BOOST_BEAST=1 -DICHOR_USE_SPDLOG=1 -DICHOR_USE_BACKWARD=0 -DICHOR_SKIP_EXTERNAL_TESTS=1 /opt/ichor/src && ninja && ninja test"] diff --git a/benchmark.sh b/benchmark.sh index d67f1008..66232941 100755 --- a/benchmark.sh +++ b/benchmark.sh @@ -44,45 +44,42 @@ run_benchmarks () serializer="$1"/ichor_serializer_benchmark start="$1"/ichor_start_benchmark start_stop="$1"/ichor_start_stop_benchmark + utils="$1"/ichor_utils_benchmark eval $coroutine || exit 1 eval $event || exit 1 eval $serializer || exit 1 - eval $start || exit 1 + eval $start -a || exit 1 + eval $start -c || exit 1 eval $start_stop || exit 1 + eval $utils -r || exit 1 } if [ $REBUILD -eq 1 ]; then - rm -rf ../std_std ../std_mimalloc ../std_no_hardening ../absl_std ../absl_mimalloc - mkdir -p ../std_std ../std_mimalloc ../std_no_hardening ../absl_std ../absl_mimalloc + rm -rf ../std_std ../std_mimalloc ../std_no_hardening ../clang_libcpp_mimalloc + mkdir -p ../std_std ../std_mimalloc ../std_no_hardening ../clang_libcpp_mimalloc rm -rf ./* ../bin/* - cmake -GNinja -DICHOR_USE_ABSEIL=OFF -DICHOR_USE_MIMALLOC=OFF -DICHOR_ARCH_OPTIMIZATION=X86_64_AVX2 -DCMAKE_BUILD_TYPE=Release -DICHOR_USE_SANITIZERS=OFF -DICHOR_USE_MOLD=ON -DICHOR_BUILD_EXAMPLES=OFF -DICHOR_BUILD_TESTING=OFF -DICHOR_USE_BOOST_BEAST=ON .. || exit 1 + cmake -GNinja -DICHOR_USE_MIMALLOC=OFF -DICHOR_ARCH_OPTIMIZATION=X86_64_AVX2 -DCMAKE_BUILD_TYPE=Release -DICHOR_USE_SANITIZERS=OFF -DICHOR_USE_MOLD=ON -DICHOR_BUILD_EXAMPLES=OFF -DICHOR_BUILD_TESTING=OFF -DICHOR_USE_BOOST_BEAST=ON .. || exit 1 ninja || exit 1 cp ../bin/*benchmark ../std_std || exit 1 rm -rf ./* ../bin/* - cmake -GNinja -DICHOR_USE_ABSEIL=OFF -DICHOR_USE_MIMALLOC=ON -DICHOR_ARCH_OPTIMIZATION=X86_64_AVX2 -DCMAKE_BUILD_TYPE=Release -DICHOR_USE_SANITIZERS=OFF -DICHOR_USE_MOLD=ON -DICHOR_BUILD_EXAMPLES=OFF -DICHOR_BUILD_TESTING=OFF -DICHOR_USE_BOOST_BEAST=ON .. || exit 1 + cmake -GNinja -DICHOR_USE_MIMALLOC=ON -DICHOR_ARCH_OPTIMIZATION=X86_64_AVX2 -DCMAKE_BUILD_TYPE=Release -DICHOR_USE_SANITIZERS=OFF -DICHOR_USE_MOLD=ON -DICHOR_BUILD_EXAMPLES=OFF -DICHOR_BUILD_TESTING=OFF -DICHOR_USE_BOOST_BEAST=ON .. || exit 1 ninja || exit 1 cp ../bin/*benchmark ../std_mimalloc || exit 1 rm -rf ./* ../bin/* - cmake -GNinja -DICHOR_USE_ABSEIL=OFF -DICHOR_USE_MIMALLOC=ON -DICHOR_USE_HARDENING=OFF -DICHOR_ARCH_OPTIMIZATION=X86_64_AVX2 -DCMAKE_BUILD_TYPE=Release -DICHOR_USE_SANITIZERS=OFF -DICHOR_USE_MOLD=ON -DICHOR_BUILD_EXAMPLES=OFF -DICHOR_BUILD_TESTING=OFF -DICHOR_USE_BOOST_BEAST=ON .. || exit 1 + CC=clang CXX=clang++ cmake -GNinja -DICHOR_USE_MIMALLOC=ON -DICHOR_ARCH_OPTIMIZATION=X86_64_AVX2 -DCMAKE_BUILD_TYPE=Release -DICHOR_USE_SANITIZERS=OFF -DICHOR_USE_MOLD=ON -DICHOR_BUILD_EXAMPLES=OFF -DICHOR_BUILD_TESTING=OFF -DICHOR_USE_BOOST_BEAST=ON .. || exit 1 ninja || exit 1 - cp ../bin/*benchmark ../std_no_hardening || exit 1 + cp ../bin/*benchmark ../clang_libcpp_mimalloc || exit 1 rm -rf ./* ../bin/* - cmake -GNinja -DICHOR_USE_ABSEIL=ON -DICHOR_USE_MIMALLOC=OFF -DICHOR_ARCH_OPTIMIZATION=X86_64_AVX2 -DCMAKE_BUILD_TYPE=Release -DICHOR_USE_SANITIZERS=OFF -DICHOR_USE_MOLD=ON -DICHOR_BUILD_EXAMPLES=OFF -DICHOR_BUILD_TESTING=OFF -DICHOR_USE_BOOST_BEAST=ON .. || exit 1 + cmake -GNinja -DICHOR_USE_MIMALLOC=ON -DICHOR_USE_HARDENING=OFF -DICHOR_ARCH_OPTIMIZATION=X86_64_AVX2 -DCMAKE_BUILD_TYPE=Release -DICHOR_USE_SANITIZERS=OFF -DICHOR_USE_MOLD=ON -DICHOR_BUILD_EXAMPLES=OFF -DICHOR_BUILD_TESTING=OFF -DICHOR_USE_BOOST_BEAST=ON .. || exit 1 ninja || exit 1 - cp ../bin/*benchmark ../absl_std || exit 1 - - rm -rf ./* ../bin/* - cmake -GNinja -DICHOR_USE_ABSEIL=ON -DICHOR_USE_MIMALLOC=ON -DICHOR_ARCH_OPTIMIZATION=X86_64_AVX2 -DCMAKE_BUILD_TYPE=Release -DICHOR_USE_SANITIZERS=OFF -DICHOR_USE_MOLD=ON -DICHOR_BUILD_EXAMPLES=OFF -DICHOR_BUILD_TESTING=OFF -DICHOR_USE_BOOST_BEAST=ON .. || exit 1 - ninja || exit 1 - cp ../bin/*benchmark ../absl_mimalloc || exit 1 + cp ../bin/*benchmark ../std_no_hardening || exit 1 fi run_benchmarks ../std_std run_benchmarks ../std_mimalloc run_benchmarks ../std_no_hardening -run_benchmarks ../absl_std -run_benchmarks ../absl_mimalloc +run_benchmarks ../clang_libcpp_mimalloc diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index f53f65c3..36a36408 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -1,8 +1,26 @@ file(GLOB_RECURSE PROJECT_BENCHMARK_SOURCES ${ICHOR_TOP_DIR}/benchmarks/*.cpp) +find_package(PkgConfig) +if(PkgConfig_FOUND AND NOT ICHOR_USE_LIBCPP) # Most platforms have compiled RE2 with libstdc++ and mixing that with libc++ fails + pkg_check_modules(RE2 re2) + if(RE2_FOUND) + set(USE_RE2 ON) + else() + set(USE_RE2 OFF) + endif() +else() + set(USE_RE2 OFF) +endif() + foreach(filename ${PROJECT_BENCHMARK_SOURCES}) get_filename_component(benchname ${filename} NAME_WE) add_executable(${benchname} ${filename}) target_link_libraries(${benchname} ${CMAKE_THREAD_LIBS_INIT}) target_link_libraries(${benchname} ichor) + + if(USE_RE2) + target_include_directories(${benchname} PUBLIC ${RE2_INCLUDE_DIRS}) + target_link_libraries(${benchname} ${RE2_LIBRARIES}) + target_compile_definitions(${benchname} PUBLIC ICHOR_USE_RE2) + endif() endforeach() diff --git a/benchmarks/README.md b/benchmarks/README.md index 20e6bc22..88a93e6e 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -1,82 +1,104 @@ # Preliminary benchmark results These benchmarks are mainly used to identify bottlenecks, not to showcase the performance of the framework. Proper throughput and latency benchmarks are TBD. -Setup: AMD 7950X, 6000MHz@CL38 RAM, ubuntu 22.04, gcc 11.3 +Setup: AMD 7950X, 6000MHz@CL38 RAM, ubuntu 22.04, gcc 13.1.1 & clang 18. Listed numbers are for the multi-threaded runs where contention is likely. -| Compile
Options | coroutines | events | start | start & stop | -|-------------------------------------------|:--------------------:|------------------------:|------------------------:|---------------------:| -| std containers
std alloc | 976,216 µs
6MB | 1,942,373 µs
5765MB | 20,156,161 µs
572MB | 2,563,558 µs
6MB | -| std containers
mimalloc | 894,269 µs
7MB | 833,478 µs
4334MB | 13,795,744 µs
479MB | 1,774,788 µs
7MB | -| std containers
mimalloc, no hardening | 868,806 µs
7MB | 802,718 µs
4360MB | 13,731,578 µs
532MB | 1,863,568 µs
7MB | -| absl containers
std alloc | 1,425,984 µs
8MB | 1,404,642 µs
3726MB | 11,868,876 µs
466MB | 2,596,978 µs
8MB | -| absl containers
mimalloc | 940,348 µs
8MB | 875,581 µs
3255MB | 7,598,533 µs
509MB | 2,279,161 µs
8MB | +| Compile Options | coroutines | events | start | start & stop | +|------------------------|:-------------------:|------------------------:|------------------------:|----------------------:| +| std alloc | 882,091 µs
7MB | 1,354,665 µs
2487MB | 20,758,015 µs
309MB | 1,958,287 µs
7MB | +| mimalloc | 865,627 µs
10MB | 1,144,712 µs
2430MB | 20,789,338 µs
307MB | 1,940,218 µs
12MB | +| mimalloc, no hardening | 724,565 µs
7MB | 1,021,035 µs
1793MB | 20,688,429 µs
291MB | 1,630,415 µs
7MB | +| libcpp mimalloc | 924,276 µs
8MB | 989,943 µs
2429MB | 1,235,298 µs
320MB | 2,223,747 µs
10MB | -The most interesting observation here is that disabling hardening does not bring any performance gains larger than run-to-run variance. +Raspberry Pi Model 4B, Debian 12 Bookworm, gcc 12.2. +Listed numbers are for the multi-threaded runs where contention is likely. Note that the benchmarks use 8 threads and the Pi Model 4B only has 4 cores. -Detailed data: +| Compile Options | coroutines | events | start | start & stop | +|-----------------|:---------------------:|-------------------------:|-------------------------:|----------------------:| +| musl mimalloc | 13,033,707 µs
5MB | 16,088,087 µs
2230MB | 173,023,823 µs
308MB | 37,307,513 µs
7MB | +| glibc mimalloc | 9,905,923 µs
6MB | 13,427,431 µs
1737MB | 172,781,402 µs
290MB | 28,564,714 µs
6MB | + +It used to be that disabling hardening would not bring any performance gains larger than run-to-run variance, but since enabling mimalloc's secure mode, that seems to have disappeared. Moreover, memory usage is now similar to std alloc. + +Detailed data 7950X: ```text -../std_std/ichor_coroutine_benchmark single threaded ran for 526,229 µs with 5,767,168 peak memory usage 9,501,566 coroutines/s -../std_std/ichor_coroutine_benchmark multi threaded ran for 976,216 µs with 6,029,312 peak memory usage 40,974,538 coroutines/s -../std_std/ichor_event_benchmark single threaded ran for 1,133,371 µs with 646,184,960 peak memory usage 4,411,618 events/s -../std_std/ichor_event_benchmark multi threaded ran for 1,942,373 µs with 5,764,808,704 peak memory usage 20,593,366 events/s -../std_std/ichor_serializer_benchmark single threaded glaze ran for 201,452 µs with 6,029,312 peak memory usage 769 MB/s -../std_std/ichor_serializer_benchmark multi threaded glaze ran for 240,475 µs with 6,291,456 peak memory usage 5,156 MB/s -../std_std/ichor_start_benchmark single threaded advanced injection ran for 5,116,410 µs with 47,321,088 peak memory usage -../std_std/ichor_start_benchmark multi threaded advanced injection ran for 12,272,126 µs with 357,158,912 peak memory usage -../std_std/ichor_start_benchmark single threaded constructor injection ran for 7,323,145 µs with 357,158,912 peak memory usage -../std_std/ichor_start_benchmark multi threaded constructor injection ran for 20,156,161 µs with 572,489,728 peak memory usage -../std_std/ichor_start_stop_benchmark single threaded ran for 1,424,174 µs with 5,767,168 peak memory usage 702,161 start & stop /s -../std_std/ichor_start_stop_benchmark multi threaded ran for 2,563,558 µs with 6,029,312 peak memory usage 3,120,662 start & stop /s -../std_mimalloc/ichor_coroutine_benchmark single threaded ran for 410,605 µs with 6,815,744 peak memory usage 12,177,153 coroutines/s -../std_mimalloc/ichor_coroutine_benchmark multi threaded ran for 894,269 µs with 6,815,744 peak memory usage 44,729,270 coroutines/s -../std_mimalloc/ichor_event_benchmark single threaded ran for 629,212 µs with 568,590,336 peak memory usage 7,946,447 events/s -../std_mimalloc/ichor_event_benchmark multi threaded ran for 833,478 µs with 4,334,026,752 peak memory usage 47,991,668 events/s -../std_mimalloc/ichor_serializer_benchmark single threaded glaze ran for 180,443 µs with 6,815,744 peak memory usage 858 MB/s -../std_mimalloc/ichor_serializer_benchmark multi threaded glaze ran for 212,152 µs with 6,815,744 peak memory usage 5,844 MB/s -../std_mimalloc/ichor_start_benchmark single threaded advanced injection ran for 3,578,056 µs with 46,907,392 peak memory usage -../std_mimalloc/ichor_start_benchmark multi threaded advanced injection ran for 8,041,827 µs with 367,214,592 peak memory usage -../std_mimalloc/ichor_start_benchmark single threaded constructor injection ran for 5,215,533 µs with 367,214,592 peak memory usage -../std_mimalloc/ichor_start_benchmark multi threaded constructor injection ran for 13,795,744 µs with 478,502,912 peak memory usage -../std_mimalloc/ichor_start_stop_benchmark single threaded ran for 1,107,564 µs with 6,815,744 peak memory usage 902,882 start & stop /s -../std_mimalloc/ichor_start_stop_benchmark multi threaded ran for 1,774,788 µs with 6,815,744 peak memory usage 4,507,580 start & stop /s -../std_no_hardening/ichor_coroutine_benchmark single threaded ran for 388,710 µs with 6,815,744 peak memory usage 12,863,059 coroutines/s -../std_no_hardening/ichor_coroutine_benchmark multi threaded ran for 868,806 µs with 6,815,744 peak memory usage 46,040,197 coroutines/s -../std_no_hardening/ichor_event_benchmark single threaded ran for 610,706 µs with 568,328,192 peak memory usage 8,187,245 events/s -../std_no_hardening/ichor_event_benchmark multi threaded ran for 802,718 µs with 4,360,241,152 peak memory usage 49,830,700 events/s -../std_no_hardening/ichor_serializer_benchmark single threaded glaze ran for 158,830 µs with 6,553,600 peak memory usage 975 MB/s -../std_no_hardening/ichor_serializer_benchmark multi threaded glaze ran for 178,391 µs with 6,815,744 peak memory usage 6,951 MB/s -../std_no_hardening/ichor_start_benchmark single threaded advanced injection ran for 3,850,709 µs with 45,064,192 peak memory usage -../std_no_hardening/ichor_start_benchmark multi threaded advanced injection ran for 8,232,102 µs with 346,742,784 peak memory usage -../std_no_hardening/ichor_start_benchmark single threaded constructor injection ran for 5,432,727 µs with 346,742,784 peak memory usage -../std_no_hardening/ichor_start_benchmark multi threaded constructor injection ran for 13,731,578 µs with 531,845,120 peak memory usage -../std_no_hardening/ichor_start_stop_benchmark single threaded ran for 1,042,270 µs with 6,553,600 peak memory usage 959,444 start & stop /s -../std_no_hardening/ichor_start_stop_benchmark multi threaded ran for 1,863,568 µs with 6,815,744 peak memory usage 4,292,840 start & stop /s -../absl_std/ichor_coroutine_benchmark single threaded ran for 568,563 µs with 7,077,888 peak memory usage 8,794,100 coroutines/s -../absl_std/ichor_coroutine_benchmark multi threaded ran for 1,425,984 µs with 7,602,176 peak memory usage 28,050,805 coroutines/s -../absl_std/ichor_event_benchmark single threaded ran for 584,022 µs with 420,478,976 peak memory usage 8,561,321 events/s -../absl_std/ichor_event_benchmark multi threaded ran for 1,404,642 µs with 3,726,376,960 peak memory usage 28,477,006 events/s -../absl_std/ichor_serializer_benchmark single threaded glaze ran for 206,556 µs with 7,340,032 peak memory usage 750 MB/s -../absl_std/ichor_serializer_benchmark multi threaded glaze ran for 213,755 µs with 7,864,320 peak memory usage 5,801 MB/s -../absl_std/ichor_start_benchmark single threaded advanced injection ran for 5,491,052 µs with 40,632,320 peak memory usage -../absl_std/ichor_start_benchmark multi threaded advanced injection ran for 7,553,260 µs with 307,494,912 peak memory usage -../absl_std/ichor_start_benchmark single threaded constructor injection ran for 7,460,216 µs with 307,494,912 peak memory usage -../absl_std/ichor_start_benchmark multi threaded constructor injection ran for 11,868,876 µs with 466,481,152 peak memory usage -../absl_std/ichor_start_stop_benchmark single threaded ran for 1,442,448 µs with 7,340,032 peak memory usage 693,265 start & stop /s -../absl_std/ichor_start_stop_benchmark multi threaded ran for 2,596,978 µs with 7,602,176 peak memory usage 3,080,503 start & stop /s -../absl_mimalloc/ichor_coroutine_benchmark single threaded ran for 434,036 µs with 7,864,320 peak memory usage 11,519,781 coroutines/s -../absl_mimalloc/ichor_coroutine_benchmark multi threaded ran for 940,348 µs with 7,864,320 peak memory usage 42,537,443 coroutines/s -../absl_mimalloc/ichor_event_benchmark single threaded ran for 509,016 µs with 417,333,248 peak memory usage 9,822,873 events/s -../absl_mimalloc/ichor_event_benchmark multi threaded ran for 875,581 µs with 3,255,042,048 peak memory usage 45,683,951 events/s -../absl_mimalloc/ichor_serializer_benchmark single threaded glaze ran for 186,982 µs with 7,864,320 peak memory usage 828 MB/s -../absl_mimalloc/ichor_serializer_benchmark multi threaded glaze ran for 212,994 µs with 7,864,320 peak memory usage 5,821 MB/s -../absl_mimalloc/ichor_start_benchmark single threaded advanced injection ran for 4,480,755 µs with 42,156,032 peak memory usage -../absl_mimalloc/ichor_start_benchmark multi threaded advanced injection ran for 5,288,896 µs with 314,568,704 peak memory usage -../absl_mimalloc/ichor_start_benchmark single threaded constructor injection ran for 6,119,402 µs with 314,568,704 peak memory usage -../absl_mimalloc/ichor_start_benchmark multi threaded constructor injection ran for 7,598,533 µs with 508,850,176 peak memory usage -../absl_mimalloc/ichor_start_stop_benchmark single threaded ran for 1,178,222 µs with 7,864,320 peak memory usage 848,736 start & stop /s -../absl_mimalloc/ichor_start_stop_benchmark multi threaded ran for 2,279,161 µs with 7,864,320 peak memory usage 3,510,063 start & stop /s +../std_std/ichor_coroutine_benchmark single threaded ran for 444,424 µs with 6,553,600 peak memory usage 11,250,517 coroutines/s +../std_std/ichor_coroutine_benchmark multi threaded ran for 882,091 µs with 6,815,744 peak memory usage 45,346,795 coroutines/s +../std_std/ichor_event_benchmark single threaded ran for 769,611 µs with 286,752,768 peak memory usage 6,496,788 events/s +../std_std/ichor_event_benchmark multi threaded ran for 1,354,665 µs with 2,486,378,496 peak memory usage 29,527,595 events/s +../std_std/ichor_serializer_benchmark single threaded glaze ran for 234,270 µs with 6,553,600 peak memory usage 661 MB/s +../std_std/ichor_serializer_benchmark multi threaded glaze ran for 263,062 µs with 7,077,888 peak memory usage 4,713 MB/s +../std_std/ichor_start_benchmark single threaded advanced injection ran for 3,588,267 µs with 35,254,272 peak memory usage +../std_std/ichor_start_benchmark multi threaded advanced injection ran for 20,777,407 µs with 266,203,136 peak memory usage +../std_std/ichor_start_benchmark single threaded constructor injection ran for 3,505,431 µs with 40,390,656 peak memory usage +../std_std/ichor_start_benchmark multi threaded constructor injection ran for 20,758,015 µs with 309,190,656 peak memory usage +../std_std/ichor_start_stop_benchmark single threaded ran for 1,228,669 µs with 6,553,600 peak memory usage 813,888 start & stop /s +../std_std/ichor_start_stop_benchmark multi threaded ran for 1,958,287 µs with 6,815,744 peak memory usage 4,085,203 start & stop /s +../std_mimalloc/ichor_coroutine_benchmark single threaded ran for 468,289 µs with 7,602,176 peak memory usage 10,677,167 coroutines/s +../std_mimalloc/ichor_coroutine_benchmark multi threaded ran for 865,627 µs with 9,961,472 peak memory usage 46,209,279 coroutines/s +../std_mimalloc/ichor_event_benchmark single threaded ran for 777,572 µs with 313,565,184 peak memory usage 6,430,272 events/s +../std_mimalloc/ichor_event_benchmark multi threaded ran for 1,144,712 µs with 2,430,386,176 peak memory usage 34,943,287 events/s +../std_mimalloc/ichor_serializer_benchmark single threaded glaze ran for 259,178 µs with 7,602,176 peak memory usage 598 MB/s +../std_mimalloc/ichor_serializer_benchmark multi threaded glaze ran for 281,390 µs with 7,602,176 peak memory usage 4,406 MB/s +../std_mimalloc/ichor_start_benchmark single threaded advanced injection ran for 3,561,806 µs with 36,704,256 peak memory usage +../std_mimalloc/ichor_start_benchmark multi threaded advanced injection ran for 20,806,461 µs with 266,969,088 peak memory usage +../std_mimalloc/ichor_start_benchmark single threaded constructor injection ran for 3,462,641 µs with 41,975,808 peak memory usage +../std_mimalloc/ichor_start_benchmark multi threaded constructor injection ran for 20,789,338 µs with 306,909,184 peak memory usage +../std_mimalloc/ichor_start_stop_benchmark single threaded ran for 1,318,876 µs with 7,864,320 peak memory usage 758,221 start & stop /s +../std_mimalloc/ichor_start_stop_benchmark multi threaded ran for 1,940,218 µs with 12,320,768 peak memory usage 4,123,248 start & stop /s +../std_no_hardening/ichor_coroutine_benchmark single threaded ran for 316,692 µs with 7,077,888 peak memory usage 15,788,210 coroutines/s +../std_no_hardening/ichor_coroutine_benchmark multi threaded ran for 724,565 µs with 7,340,032 peak memory usage 55,205,537 coroutines/s +../std_no_hardening/ichor_event_benchmark single threaded ran for 588,660 µs with 241,172,480 peak memory usage 8,493,867 events/s +../std_no_hardening/ichor_event_benchmark multi threaded ran for 1,021,035 µs with 1,792,909,312 peak memory usage 39,175,934 events/s +../std_no_hardening/ichor_serializer_benchmark single threaded glaze ran for 171,266 µs with 7,077,888 peak memory usage 905 MB/s +../std_no_hardening/ichor_serializer_benchmark multi threaded glaze ran for 183,803 µs with 7,077,888 peak memory usage 6,746 MB/s +../std_no_hardening/ichor_start_benchmark single threaded advanced injection ran for 3,500,988 µs with 33,542,144 peak memory usage +../std_no_hardening/ichor_start_benchmark multi threaded advanced injection ran for 20,629,217 µs with 245,940,224 peak memory usage +../std_no_hardening/ichor_start_benchmark single threaded constructor injection ran for 3,451,504 µs with 38,785,024 peak memory usage +../std_no_hardening/ichor_start_benchmark multi threaded constructor injection ran for 20,688,429 µs with 291,004,416 peak memory usage +../std_no_hardening/ichor_start_stop_benchmark single threaded ran for 923,973 µs with 7,077,888 peak memory usage 1,082,282 start & stop /s +../std_no_hardening/ichor_start_stop_benchmark multi threaded ran for 1,630,415 µs with 7,340,032 peak memory usage 4,906,726 start & stop /s +../clang_libcpp_mimalloc/ichor_coroutine_benchmark single threaded ran for 511,009 µs with 6,291,456 peak memory usage 9,784,563 coroutines/s +../clang_libcpp_mimalloc/ichor_coroutine_benchmark multi threaded ran for 924,276 µs with 8,388,608 peak memory usage 43,277,116 coroutines/s +../clang_libcpp_mimalloc/ichor_event_benchmark single threaded ran for 608,019 µs with 286,826,496 peak memory usage 8,223,427 events/s +../clang_libcpp_mimalloc/ichor_event_benchmark multi threaded ran for 989,943 µs with 2,429,202,432 peak memory usage 40,406,366 events/s +../clang_libcpp_mimalloc/ichor_serializer_benchmark single threaded glaze ran for 293,163 µs with 6,029,312 peak memory usage 528 MB/s +../clang_libcpp_mimalloc/ichor_serializer_benchmark multi threaded glaze ran for 307,825 µs with 6,291,456 peak memory usage 4,028 MB/s +../clang_libcpp_mimalloc/ichor_start_benchmark single threaded advanced injection ran for 1,091,436 µs with 34,041,856 peak memory usage +../clang_libcpp_mimalloc/ichor_start_benchmark multi threaded advanced injection ran for 1,217,065 µs with 257,040,384 peak memory usage +../clang_libcpp_mimalloc/ichor_start_benchmark single threaded constructor injection ran for 1,033,726 µs with 41,644,032 peak memory usage +../clang_libcpp_mimalloc/ichor_start_benchmark multi threaded constructor injection ran for 1,235,298 µs with 319,815,680 peak memory usage +../clang_libcpp_mimalloc/ichor_start_stop_benchmark single threaded ran for 1,232,934 µs with 6,291,456 peak memory usage 811,073 start & stop /s +../clang_libcpp_mimalloc/ichor_start_stop_benchmark multi threaded ran for 2,223,747 µs with 10,485,760 peak memory usage 3,597,531 start & stop /s +``` +Detailed data Raspberry Pi 4B w/ mimalloc: +```text +/home/oipo/musl-bin/ichor_coroutine_benchmark single threaded ran for 6174549 µs with 2379776 peak memory usage 809775 coroutines/s +/home/oipo/musl-bin/ichor_coroutine_benchmark multi threaded ran for 13033707 µs with 4517888 peak memory usage 3068965 coroutines/s +/home/oipo/musl-bin/ichor_event_benchmark single threaded ran for 6975412 µs with 282796032 peak memory usage 716803 events/s +/home/oipo/musl-bin/ichor_event_benchmark multi threaded ran for 16088087 µs with 2230222848 peak memory usage 2486311 events/s +/home/oipo/musl-bin/ichor_serializer_benchmark single threaded glaze ran for 2262301 µs with 2379776 peak memory usage 68 MB/s +/home/oipo/musl-bin/ichor_serializer_benchmark multi threaded glaze ran for 4411332 µs with 3526656 peak memory usage 281 MB/s +/home/oipo/musl-bin/ichor_start_benchmark single threaded advanced injection ran for 29877611 µs with 31186944 peak memory usage +/home/oipo/musl-bin/ichor_start_benchmark multi threaded advanced injection ran for 203874601 µs with 261873664 peak memory usage +/home/oipo/musl-bin/ichor_start_benchmark single threaded constructor injection ran for 27568047 µs with 36519936 peak memory usage +/home/oipo/musl-bin/ichor_start_benchmark multi threaded constructor injection ran for 173023823 µs with 308469760 peak memory usage +/home/oipo/musl-bin/ichor_start_stop_benchmark single threaded ran for 16361657 µs with 2379776 peak memory usage 61118 start & stop /s +/home/oipo/musl-bin/ichor_start_stop_benchmark multi threaded ran for 37307513 µs with 7036928 peak memory usage 214434 start & stop /s +/home/oipo/glibc-bin/ichor_coroutine_benchmark single threaded ran for 4,725,725 µs with 2,527,232 peak memory usage 1,058,038 coroutines/s +/home/oipo/glibc-bin/ichor_coroutine_benchmark multi threaded ran for 9,905,923 µs with 5,570,560 peak memory usage 4,037,988 coroutines/s +/home/oipo/glibc-bin/ichor_event_benchmark single threaded ran for 6,399,888 µs with 212,250,624 peak memory usage 781,263 events/s +/home/oipo/glibc-bin/ichor_event_benchmark multi threaded ran for 13,427,431 µs with 1,737,461,760 peak memory usage 2,978,976 events/s +/home/oipo/glibc-bin/ichor_serializer_benchmark single threaded glaze ran for 1,396,208 µs with 2,527,232 peak memory usage 111 MB/s +/home/oipo/glibc-bin/ichor_serializer_benchmark multi threaded glaze ran for 2,679,808 µs with 5,783,552 peak memory usage 462 MB/s +/home/oipo/glibc-bin/ichor_start_benchmark single threaded advanced injection ran for 29,562,803 µs with 30,588,928 peak memory usage +/home/oipo/glibc-bin/ichor_start_benchmark multi threaded advanced injection ran for 196,521,142 µs with 243,556,352 peak memory usage +/home/oipo/glibc-bin/ichor_start_benchmark single threaded constructor injection ran for 25,292,936 µs with 35,725,312 peak memory usage +/home/oipo/glibc-bin/ichor_start_benchmark multi threaded constructor injection ran for 172,781,402 µs with 290,439,168 peak memory usage +/home/oipo/glibc-bin/ichor_start_stop_benchmark single threaded ran for 13,593,462 µs with 2,527,232 peak memory usage 73,564 start & stop /s +/home/oipo/glibc-bin/ichor_start_stop_benchmark multi threaded ran for 28,564,714 µs with 5,705,728 peak memory usage 280,065 start & stop /s ``` Realtime example on a vanilla linux: @@ -100,7 +122,7 @@ Some HTTP benchmarks with different frameworks ### Ichor + Boost.BEAST ```bash -$ mkdir build && cd build && cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DICHOR_ARCH_OPTIMIZATION=X86_64_AVX2 -DICHOR_USE_ABSEIL=ON -DICHOR_USE_BOOST_BEAST=ON -DICHOR_USE_SANITIZERS=OFF .. && ninja +$ mkdir build && cd build && cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DICHOR_ARCH_OPTIMIZATION=X86_64_AVX2 -DICHOR_USE_BOOST_BEAST=ON -DICHOR_USE_SANITIZERS=OFF .. && ninja $ ulimit -n 65535 && ../bin/ichor_pong_example -s & $ wrk -c2 -t1 -d60s --latency -s ../benchmarks/wrk.lua http://localhost:8001/ping Running 1m test @ http://localhost:8001/ping diff --git a/benchmarks/coroutine_benchmark/TestService.h b/benchmarks/coroutine_benchmark/TestService.h index 4ffd222d..7b934d50 100644 --- a/benchmarks/coroutine_benchmark/TestService.h +++ b/benchmarks/coroutine_benchmark/TestService.h @@ -7,7 +7,9 @@ #include #include -#if defined(__SANITIZE_ADDRESS__) +#if defined(ICHOR_ENABLE_INTERNAL_DEBUGGING) || (defined(ICHOR_BUILDING_DEBUG) && (defined(__SANITIZE_ADDRESS__) || defined(__SANITIZE_THREAD__))) +constexpr uint32_t EVENT_COUNT = 100; +#elif defined(__SANITIZE_ADDRESS__) || defined(__SANITIZE_THREAD__) constexpr uint32_t EVENT_COUNT = 500'000; #else constexpr uint32_t EVENT_COUNT = 5'000'000; @@ -17,18 +19,25 @@ using namespace Ichor; struct UselessEvent final : public Event { explicit UselessEvent(uint64_t _id, uint64_t _originatingService, uint64_t _priority) noexcept : - Event(TYPE, NAME, _id, _originatingService, _priority) {} + Event(_id, _originatingService, _priority) {} ~UselessEvent() final = default; - static constexpr uint64_t TYPE = typeNameHash(); + [[nodiscard]] std::string_view get_name() const noexcept final { + return NAME; + } + [[nodiscard]] NameHashType get_type() const noexcept final { + return TYPE; + } + + static constexpr NameHashType TYPE = typeNameHash(); static constexpr std::string_view NAME = typeName(); }; class TestService final : public AdvancedService { public: TestService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::REQUIRED); } ~TestService() final = default; diff --git a/benchmarks/coroutine_benchmark/ichor_coroutine_benchmark.cpp b/benchmarks/coroutine_benchmark/ichor_coroutine_benchmark.cpp index 44251ac1..a4515df7 100644 --- a/benchmarks/coroutine_benchmark/ichor_coroutine_benchmark.cpp +++ b/benchmarks/coroutine_benchmark/ichor_coroutine_benchmark.cpp @@ -1,5 +1,5 @@ #include "TestService.h" -#include +#include #include #include #include @@ -8,6 +8,7 @@ #include #include "../../examples/common/lyra.hpp" + int main(int argc, char *argv[]) { std::locale::global(std::locale("en_US.UTF-8")); @@ -30,7 +31,7 @@ int main(int argc, char *argv[]) { { auto start = std::chrono::steady_clock::now(); - auto queue = std::make_unique(); + auto queue = std::make_unique(); auto &dm = queue->createManager(); dm.createServiceManager, ILoggerFactory>(); dm.createServiceManager(Properties{{"LogLevel", Ichor::make_any(LogLevel::LOG_WARN)}}); @@ -43,8 +44,8 @@ int main(int argc, char *argv[]) { if(!singleOnly) { auto start = std::chrono::steady_clock::now(); std::array threads{}; - std::array queues{}; - for (uint_fast32_t i = 0, j = 0; i < 8; i++, j += 2) { + std::array queues{}; + for (uint_fast32_t i = 0, j = 0; i < queues.size(); i++, j += 2) { threads[i] = std::thread([&queues, i] { auto &dm = queues[i].createManager(); dm.createServiceManager, ILoggerFactory>(); @@ -52,7 +53,7 @@ int main(int argc, char *argv[]) { queues[i].start(CaptureSigInt); }); } - for (uint_fast32_t i = 0; i < 8; i++) { + for (uint_fast32_t i = 0; i < queues.size(); i++) { threads[i].join(); } auto end = std::chrono::steady_clock::now(); diff --git a/benchmarks/event_benchmark/TestService.h b/benchmarks/event_benchmark/TestService.h index b124317f..55e78d5d 100644 --- a/benchmarks/event_benchmark/TestService.h +++ b/benchmarks/event_benchmark/TestService.h @@ -5,7 +5,9 @@ #include #include -#if defined(__SANITIZE_ADDRESS__) +#if defined(ICHOR_ENABLE_INTERNAL_DEBUGGING) || (defined(ICHOR_BUILDING_DEBUG) && (defined(__SANITIZE_ADDRESS__) || defined(__SANITIZE_THREAD__))) +constexpr uint32_t EVENT_COUNT = 100; +#elif defined(__SANITIZE_ADDRESS__) || defined(__SANITIZE_THREAD__) constexpr uint32_t EVENT_COUNT = 500'000; #else constexpr uint32_t EVENT_COUNT = 5'000'000; @@ -15,18 +17,25 @@ using namespace Ichor; struct UselessEvent final : public Event { explicit UselessEvent(uint64_t _id, uint64_t _originatingService, uint64_t _priority) noexcept : - Event(TYPE, NAME, _id, _originatingService, _priority) {} + Event(_id, _originatingService, _priority) {} ~UselessEvent() final = default; - static constexpr uint64_t TYPE = typeNameHash(); + [[nodiscard]] std::string_view get_name() const noexcept final { + return NAME; + } + [[nodiscard]] NameHashType get_type() const noexcept final { + return TYPE; + } + + static constexpr NameHashType TYPE = typeNameHash(); static constexpr std::string_view NAME = typeName(); }; class TestService final : public AdvancedService { public: TestService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::REQUIRED); } ~TestService() final = default; diff --git a/benchmarks/event_benchmark/ichor_event_benchmark.cpp b/benchmarks/event_benchmark/ichor_event_benchmark.cpp index d50a9e64..1a4290d8 100644 --- a/benchmarks/event_benchmark/ichor_event_benchmark.cpp +++ b/benchmarks/event_benchmark/ichor_event_benchmark.cpp @@ -1,5 +1,5 @@ #include "TestService.h" -#include +#include #include #include #include @@ -30,7 +30,7 @@ int main(int argc, char *argv[]) { { auto start = std::chrono::steady_clock::now(); - auto queue = std::make_unique(); + auto queue = std::make_unique(); auto &dm = queue->createManager(); dm.createServiceManager, ILoggerFactory>(); dm.createServiceManager(Properties{{"LogLevel", Ichor::make_any(LogLevel::LOG_WARN)}}); @@ -43,7 +43,7 @@ int main(int argc, char *argv[]) { if(!singleOnly) { auto start = std::chrono::steady_clock::now(); std::array threads{}; - std::array queues{}; + std::array queues{}; for (uint_fast32_t i = 0, j = 0; i < 8; i++, j += 2) { threads[i] = std::thread([&queues, i] { auto &dm = queues[i].createManager(); diff --git a/benchmarks/serializer_benchmark/TestService.h b/benchmarks/serializer_benchmark/TestService.h index c7a53647..6dac384b 100644 --- a/benchmarks/serializer_benchmark/TestService.h +++ b/benchmarks/serializer_benchmark/TestService.h @@ -8,7 +8,9 @@ #include #include "../../examples/common/TestMsg.h" -#if defined(__SANITIZE_ADDRESS__) +#if defined(ICHOR_ENABLE_INTERNAL_DEBUGGING) || (defined(ICHOR_BUILDING_DEBUG) && (defined(__SANITIZE_ADDRESS__) || defined(__SANITIZE_THREAD__))) +constexpr uint32_t SERDE_COUNT = 1'000; +#elif defined(__SANITIZE_ADDRESS__) || defined(__SANITIZE_THREAD__) constexpr uint32_t SERDE_COUNT = 100'000; #else constexpr uint32_t SERDE_COUNT = 5'000'000; @@ -20,8 +22,8 @@ extern uint64_t sizeof_test; class TestService final : public AdvancedService { public: TestService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); - reg.registerDependency>(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency>(this, DependencyFlags::REQUIRED); } ~TestService() final = default; diff --git a/benchmarks/serializer_benchmark/ichor_serializer_benchmark.cpp b/benchmarks/serializer_benchmark/ichor_serializer_benchmark.cpp index 8219df0c..c9541c4e 100644 --- a/benchmarks/serializer_benchmark/ichor_serializer_benchmark.cpp +++ b/benchmarks/serializer_benchmark/ichor_serializer_benchmark.cpp @@ -1,5 +1,5 @@ #include "TestService.h" -#include +#include #include #include #include @@ -35,7 +35,7 @@ int main(int argc, char *argv[]) { { auto start = std::chrono::steady_clock::now(); - auto queue = std::make_unique(); + auto queue = std::make_unique(); auto &dm = queue->createManager(); dm.createServiceManager, ILoggerFactory>(); dm.createServiceManager>(); @@ -49,7 +49,7 @@ int main(int argc, char *argv[]) { if(!singleOnly) { auto start = std::chrono::steady_clock::now(); std::array threads{}; - std::array queues{}; + std::array queues{}; for (uint_fast32_t i = 0, j = 0; i < threadCount; i++, j += 2) { threads[i] = std::thread([&queues, i] { auto &dm = queues[i].createManager(); diff --git a/benchmarks/start_benchmark/TestService.h b/benchmarks/start_benchmark/TestService.h index a067065c..5d25d425 100644 --- a/benchmarks/start_benchmark/TestService.h +++ b/benchmarks/start_benchmark/TestService.h @@ -4,8 +4,9 @@ #include #include #include - -#if defined(__SANITIZE_ADDRESS__) +#if defined(ICHOR_ENABLE_INTERNAL_DEBUGGING) || (defined(ICHOR_BUILDING_DEBUG) && (defined(__SANITIZE_ADDRESS__) || defined(__SANITIZE_THREAD__))) +constexpr uint32_t SERVICES_COUNT = 100; +#elif defined(__SANITIZE_ADDRESS__) || defined(__SANITIZE_THREAD__) constexpr uint32_t SERVICES_COUNT = 1'000; #else constexpr uint32_t SERVICES_COUNT = 10'000; @@ -16,7 +17,7 @@ using namespace Ichor; class TestService final : public AdvancedService { public: TestService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); } ~TestService() final = default; diff --git a/benchmarks/start_benchmark/ichor_start_benchmark.cpp b/benchmarks/start_benchmark/ichor_start_benchmark.cpp index 82d9fa5e..cb8cdd2b 100644 --- a/benchmarks/start_benchmark/ichor_start_benchmark.cpp +++ b/benchmarks/start_benchmark/ichor_start_benchmark.cpp @@ -1,6 +1,6 @@ #include "TestService.h" #include "ConstructorInjectionTestService.h" -#include +#include #include #include #include @@ -14,9 +14,13 @@ int main(int argc, char *argv[]) { bool showHelp{}; bool singleOnly{}; + bool advancedOnly{}; + bool constructerOnly{}; auto cli = lyra::help(showHelp) - | lyra::opt(singleOnly)["-s"]["--single"]("Single core only"); + | lyra::opt(singleOnly)["-s"]["--single"]("Single core only") + | lyra::opt(advancedOnly)["-a"]["--advanced"]("Advanced Service only") + | lyra::opt(constructerOnly)["-c"]["--constructor"]("Constructor Injection only"); auto result = cli.parse( { argc, argv } ); if (!result) { @@ -29,76 +33,86 @@ int main(int argc, char *argv[]) { return 0; } - { - auto start = std::chrono::steady_clock::now(); - auto queue = std::make_unique(); - auto &dm = queue->createManager(); - dm.createServiceManager, ILoggerFactory>(); - for (uint64_t i = 0; i < SERVICES_COUNT; i++) { - dm.createServiceManager(Properties{{"Iteration", Ichor::make_any(i)}, - {"LogLevel", Ichor::make_any(LogLevel::LOG_WARN)}}); - } - queue->start(CaptureSigInt); - auto end = std::chrono::steady_clock::now(); - std::cout << fmt::format("{} single threaded advanced injection ran for {:L} µs with {:L} peak memory usage\n", argv[0], std::chrono::duration_cast(end - start).count(), getPeakRSS()); + if(!advancedOnly && !constructerOnly) { + advancedOnly = true; + constructerOnly = true; } - if(!singleOnly) { - auto start = std::chrono::steady_clock::now(); - std::array threads{}; - std::array queues{}; - for (uint_fast32_t i = 0, j = 0; i < 8; i++, j += 2) { - threads[i] = std::thread([&queues, i] { - auto &dm = queues[i].createManager(); - dm.createServiceManager, ILoggerFactory>(); - for (uint64_t z = 0; z < SERVICES_COUNT; z++) { - dm.createServiceManager(Properties{{"Iteration", Ichor::make_any(z)}, {"LogLevel", Ichor::make_any(LogLevel::LOG_WARN)}}); - } - queues[i].start(CaptureSigInt); - }); - } - for (uint_fast32_t i = 0; i < 8; i++) { - threads[i].join(); + + if(advancedOnly) { + { + auto start = std::chrono::steady_clock::now(); + auto queue = std::make_unique(); + auto &dm = queue->createManager(); + dm.createServiceManager, ILoggerFactory>(); + for (uint64_t i = 0; i < SERVICES_COUNT; i++) { + dm.createServiceManager(Properties{{"Iteration", Ichor::make_any(i)}, + {"LogLevel", Ichor::make_any(LogLevel::LOG_WARN)}}); + } + queue->start(CaptureSigInt); + auto end = std::chrono::steady_clock::now(); + std::cout << fmt::format("{} single threaded advanced injection ran for {:L} µs with {:L} peak memory usage\n", argv[0], std::chrono::duration_cast(end - start).count(), getPeakRSS()); } - auto end = std::chrono::steady_clock::now(); - std::cout << fmt::format("{} multi threaded advanced injection ran for {:L} µs with {:L} peak memory usage\n", - argv[0], std::chrono::duration_cast(end - start).count(), getPeakRSS()); - } - { - auto start = std::chrono::steady_clock::now(); - auto queue = std::make_unique(); - auto &dm = queue->createManager(); - dm.createServiceManager, ILoggerFactory>(); - for (uint64_t i = 0; i < SERVICES_COUNT; i++) { - dm.createServiceManager(Properties{{"Iteration", Ichor::make_any(i)}, - {"LogLevel", Ichor::make_any(LogLevel::LOG_WARN)}}); + if(!singleOnly) { + auto start = std::chrono::steady_clock::now(); + std::array threads{}; + std::array queues{}; + for (uint_fast32_t i = 0, j = 0; i < 8; i++, j += 2) { + threads[i] = std::thread([&queues, i] { + auto &dm = queues[i].createManager(); + dm.createServiceManager, ILoggerFactory>(); + for (uint64_t z = 0; z < SERVICES_COUNT; z++) { + dm.createServiceManager(Properties{{"Iteration", Ichor::make_any(z)}, {"LogLevel", Ichor::make_any(LogLevel::LOG_WARN)}}); + } + queues[i].start(CaptureSigInt); + }); + } + for (uint_fast32_t i = 0; i < 8; i++) { + threads[i].join(); + } + auto end = std::chrono::steady_clock::now(); + std::cout << fmt::format("{} multi threaded advanced injection ran for {:L} µs with {:L} peak memory usage\n", + argv[0], std::chrono::duration_cast(end - start).count(), getPeakRSS()); } - queue->start(CaptureSigInt); - auto end = std::chrono::steady_clock::now(); - std::cout << fmt::format("{} single threaded constructor injection ran for {:L} µs with {:L} peak memory usage\n", argv[0], std::chrono::duration_cast(end - start).count(), getPeakRSS()); } - if(!singleOnly) { - auto start = std::chrono::steady_clock::now(); - std::array threads{}; - std::array queues{}; - for (uint_fast32_t i = 0, j = 0; i < 8; i++, j += 2) { - threads[i] = std::thread([&queues, i] { - auto &dm = queues[i].createManager(); - dm.createServiceManager, ILoggerFactory>(); - for (uint64_t z = 0; z < SERVICES_COUNT; z++) { - dm.createServiceManager(Properties{{"Iteration", Ichor::make_any(z)}, {"LogLevel", Ichor::make_any(LogLevel::LOG_WARN)}}); - } - queues[i].start(CaptureSigInt); - }); + if(constructerOnly) { + { + auto start = std::chrono::steady_clock::now(); + auto queue = std::make_unique(); + auto &dm = queue->createManager(); + dm.createServiceManager, ILoggerFactory>(); + for (uint64_t i = 0; i < SERVICES_COUNT; i++) { + dm.createServiceManager(Properties{{"Iteration", Ichor::make_any(i)}, + {"LogLevel", Ichor::make_any(LogLevel::LOG_WARN)}}); + } + queue->start(CaptureSigInt); + auto end = std::chrono::steady_clock::now(); + std::cout << fmt::format("{} single threaded constructor injection ran for {:L} µs with {:L} peak memory usage\n", argv[0], std::chrono::duration_cast(end - start).count(), getPeakRSS()); } - for (uint_fast32_t i = 0; i < 8; i++) { - threads[i].join(); + + if(!singleOnly) { + auto start = std::chrono::steady_clock::now(); + std::array threads{}; + std::array queues{}; + for (uint_fast32_t i = 0, j = 0; i < 8; i++, j += 2) { + threads[i] = std::thread([&queues, i] { + auto &dm = queues[i].createManager(); + dm.createServiceManager, ILoggerFactory>(); + for (uint64_t z = 0; z < SERVICES_COUNT; z++) { + dm.createServiceManager(Properties{{"Iteration", Ichor::make_any(z)}, {"LogLevel", Ichor::make_any(LogLevel::LOG_WARN)}}); + } + queues[i].start(CaptureSigInt); + }); + } + for (uint_fast32_t i = 0; i < 8; i++) { + threads[i].join(); + } + auto end = std::chrono::steady_clock::now(); + std::cout << fmt::format("{} multi threaded constructor injection ran for {:L} µs with {:L} peak memory usage\n", + argv[0], std::chrono::duration_cast(end - start).count(), getPeakRSS()); } - auto end = std::chrono::steady_clock::now(); - std::cout << fmt::format("{} multi threaded constructor injection ran for {:L} µs with {:L} peak memory usage\n", - argv[0], std::chrono::duration_cast(end - start).count(), getPeakRSS()); } return 0; diff --git a/benchmarks/start_stop_benchmark/StartStopService.h b/benchmarks/start_stop_benchmark/StartStopService.h index e92ddfad..97376eb3 100644 --- a/benchmarks/start_stop_benchmark/StartStopService.h +++ b/benchmarks/start_stop_benchmark/StartStopService.h @@ -6,7 +6,9 @@ #include #include -#if defined(__SANITIZE_ADDRESS__) +#if defined(ICHOR_ENABLE_INTERNAL_DEBUGGING) || (defined(ICHOR_BUILDING_DEBUG) && (defined(__SANITIZE_ADDRESS__) || defined(__SANITIZE_THREAD__))) +constexpr uint32_t START_STOP_COUNT = 100; +#elif defined(__SANITIZE_ADDRESS__) || defined(__SANITIZE_THREAD__) constexpr uint32_t START_STOP_COUNT = 100'000; #else constexpr uint32_t START_STOP_COUNT = 1'000'000; @@ -17,9 +19,9 @@ using namespace Ichor; class StartStopService final : public AdvancedService { public: StartStopService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); - reg.registerDependency(this, true); - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::REQUIRED); } ~StartStopService() final = default; diff --git a/benchmarks/start_stop_benchmark/TestService.h b/benchmarks/start_stop_benchmark/TestService.h index d248e1db..ce93a564 100644 --- a/benchmarks/start_stop_benchmark/TestService.h +++ b/benchmarks/start_stop_benchmark/TestService.h @@ -13,7 +13,7 @@ struct ITestService { class TestService final : public ITestService, public AdvancedService { public: TestService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); } ~TestService() final = default; diff --git a/benchmarks/start_stop_benchmark/ichor_start_stop_benchmark.cpp b/benchmarks/start_stop_benchmark/ichor_start_stop_benchmark.cpp index 210d3d5d..25f764e0 100644 --- a/benchmarks/start_stop_benchmark/ichor_start_stop_benchmark.cpp +++ b/benchmarks/start_stop_benchmark/ichor_start_stop_benchmark.cpp @@ -1,6 +1,6 @@ #include "TestService.h" #include "StartStopService.h" -#include +#include #include #include #include @@ -31,7 +31,7 @@ int main(int argc, char *argv[]) { { auto start = std::chrono::steady_clock::now(); - auto queue = std::make_unique(); + auto queue = std::make_unique(); auto &dm = queue->createManager(); dm.createServiceManager, ILoggerFactory>(); dm.createServiceManager(Properties{{"LogLevel", Ichor::make_any(LogLevel::LOG_INFO)}}); @@ -45,7 +45,7 @@ int main(int argc, char *argv[]) { if(!singleOnly) { auto start = std::chrono::steady_clock::now(); std::array threads{}; - std::array queues{}; + std::array queues{}; for (uint_fast32_t i = 0, j = 0; i < 8; i++, j += 2) { threads[i] = std::thread([&queues, i] { auto &dm = queues[i].createManager(); diff --git a/benchmarks/utils_benchmark/ichor_utils_benchmark.cpp b/benchmarks/utils_benchmark/ichor_utils_benchmark.cpp index b3a64b13..5eb5933e 100644 --- a/benchmarks/utils_benchmark/ichor_utils_benchmark.cpp +++ b/benchmarks/utils_benchmark/ichor_utils_benchmark.cpp @@ -1,31 +1,222 @@ #include #include #include +#include #include +#include +#include "../../examples/common/lyra.hpp" + + +#ifdef ICHOR_USE_RE2 +#include +#endif + +template +struct StringLiteral { + constexpr StringLiteral(const char (&str)[N]) { + std::copy_n(str, N, value); + } + + char value[N]; +}; + +template +struct StdRegexRouteMatch final : public Ichor::RouteMatcher { + ~StdRegexRouteMatch() noexcept final = default; + + bool matches(std::string_view route) noexcept final { + std::match_results matches; + auto result = std::regex_match(route.cbegin(), route.cend(), matches, _r); + + if(!result) { + return false; + } + + _params.reserve(matches.size() - 1); + + for(decltype(matches.size()) i = 1; i < matches.size(); i++) { + _params.emplace_back(matches[i].str()); + } + + return true; + } + + std::vector route_params() noexcept final { + return std::move(_params); + } + +private: + std::vector _params{}; + std::regex _r{REGEX.value, flags}; +}; + +#ifdef ICHOR_USE_RE2 +template +struct Re2RegexRouteMatch final : public Ichor::RouteMatcher { + Re2RegexRouteMatch() : _r(REGEX.value, RE2::CannedOptions::Latin1) { + if(!_r.ok()) { + fmt::print("Couldn't compile RE2 {}\n", REGEX.value); + std::terminate(); + } + } + ~Re2RegexRouteMatch() noexcept final = default; + + bool matches(std::string_view route) noexcept final { + std::size_t args_count = static_cast(_r.NumberOfCapturingGroups()); + _params.resize(static_cast(args_count)); + std::vector args{args_count}; + std::vector arg_ptrs{args_count}; + for (std::size_t i = 0; i < args_count; ++i) { + args[i] = &_params[i]; + arg_ptrs[i] = &args[i]; + } + + return RE2::FullMatchN(route, _r, arg_ptrs.data(), static_cast(args_count)); + } + + std::vector route_params() noexcept final { + return std::move(_params); + } + +private: + std::vector _params{}; + RE2 _r; +}; +#endif #define FMT_INLINE_BUFFER_SIZE 1024 -const uint64_t ITERATION_COUNT = 1'000'000; -int main(int argc, char *argv[]) { - std::locale::global(std::locale("en_US.UTF-8")); - int i = 1'234'567; - double d = 1.234567; - std::string s{"some arbitrary string"}; +#if defined(ICHOR_ENABLE_INTERNAL_DEBUGGING) || (defined(ICHOR_BUILDING_DEBUG) && (defined(__SANITIZE_ADDRESS__) || defined(__SANITIZE_THREAD__))) +const uint64_t ITERATION_COUNT = 100; +#elif defined(__SANITIZE_ADDRESS__) || defined(ICHOR_ENABLE_INTERNAL_DEBUGGING) +const uint64_t ITERATION_COUNT = 5'000; +#else +const uint64_t ITERATION_COUNT = 500'000; +#endif + +template +void run_regex_bench(char *argv) { + Ichor::RegexRouteMatch ctreMatcher{}; + StdRegexRouteMatch stdMatcher{}; +#ifdef ICHOR_USE_RE2 + Re2RegexRouteMatch re2Matcher{}; +#endif + + if(!ctreMatcher.matches("/some/http/10/11/12/test?one=two&three=four")) { + fmt::print("ctre matcher error\n"); + std::terminate(); + } + auto route_params_size = ctreMatcher.route_params().size(); + if(route_params_size != EXPECTED_MATCHES) { + fmt::print("ctre matcher size error expected {} got {}\n", EXPECTED_MATCHES, route_params_size); + std::terminate(); + } + if(!stdMatcher.matches("/some/http/10/11/12/test?one=two&three=four")) { + fmt::print("std matcher error\n"); + std::terminate(); + } + route_params_size = stdMatcher.route_params().size(); + if(route_params_size != EXPECTED_MATCHES) { + fmt::print("std matcher size error expected {} got {}\n", EXPECTED_MATCHES, route_params_size); + std::terminate(); + } +#ifdef ICHOR_USE_RE2 + if(!re2Matcher.matches("/some/http/10/11/12/test?one=two&three=four")) { + fmt::print("re matcher error\n"); + std::terminate(); + } + route_params_size = re2Matcher.route_params().size(); + if(route_params_size != EXPECTED_MATCHES) { + fmt::print("re matcher size error expected {} got {}\n", EXPECTED_MATCHES, route_params_size); + std::terminate(); + } +#endif + + auto startCtre = std::chrono::steady_clock::now(); + for(uint64_t j = 0; j < ITERATION_COUNT; j++) { + static_cast(ctreMatcher.matches("/some/http/10/11/12/test?one=two&three=four")); + static_cast(ctreMatcher.route_params()); + } + auto endCtr = std::chrono::steady_clock::now(); - auto startFmt = std::chrono::steady_clock::now(); + auto startStd = std::chrono::steady_clock::now(); for(uint64_t j = 0; j < ITERATION_COUNT; j++) { - fmt::print("This is a test! {} {} {}", i, d, s); + stdMatcher.matches("/some/http/10/11/12/test?one=two&three=four"); + stdMatcher.route_params(); } - auto endFmt = std::chrono::steady_clock::now(); - auto startCout = std::chrono::steady_clock::now(); + auto endStd = std::chrono::steady_clock::now(); + +#ifdef ICHOR_USE_RE2 + auto startRe2 = std::chrono::steady_clock::now(); for(uint64_t j = 0; j < ITERATION_COUNT; j++) { - fmt::basic_memory_buffer buf{}; - fmt::format_to(std::back_inserter(buf), "This is a test! {} {} {}", i, d, s); - std::cout.write(buf.data(), static_cast(buf.size())); - } - auto endCout = std::chrono::steady_clock::now(); - fmt::print("\n\n{} fmt::print ran for {:L} µs with {:L} peak memory usage {:L} prints/s\n", argv[0], std::chrono::duration_cast(endFmt - startFmt).count(), getPeakRSS(), - std::floor(1'000'000. / static_cast(std::chrono::duration_cast(endFmt - startFmt).count()) * ITERATION_COUNT)); - fmt::print("{} std::cout with fmt::format_to ran for {:L} µs with {:L} peak memory usage {:L} prints/s\n", argv[0], std::chrono::duration_cast(endCout - startCout).count(), getPeakRSS(), - std::floor(1'000'000. / static_cast(std::chrono::duration_cast(endCout - startCout).count()) * ITERATION_COUNT)); + re2Matcher.matches("/some/http/10/11/12/test?one=two&three=four"); + re2Matcher.route_params(); + } + auto endRe2 = std::chrono::steady_clock::now(); +#endif + + fmt::print("{} ctreMatcher {} ran for {:L} µs with {:L} peak memory usage {:L} matches/s\n", argv, REGEX.value, std::chrono::duration_cast(endCtr - startCtre).count(), getPeakRSS(), + std::floor(1'000'000. / static_cast(std::chrono::duration_cast(endCtr - startCtre).count()) * ITERATION_COUNT)); + fmt::print("{} stdMatcher {} ran for {:L} µs with {:L} peak memory usage {:L} matches/s\n", argv, REGEX.value, std::chrono::duration_cast(endStd - startStd).count(), getPeakRSS(), + std::floor(1'000'000. / static_cast(std::chrono::duration_cast(endStd - startStd).count()) * ITERATION_COUNT)); +#ifdef ICHOR_USE_RE2 + fmt::print("{} re2Matcher {} ran for {:L} µs with {:L} peak memory usage {:L} matches/s\n", argv, REGEX.value, std::chrono::duration_cast(endRe2 - startRe2).count(), getPeakRSS(), + std::floor(1'000'000. / static_cast(std::chrono::duration_cast(endRe2 - startRe2).count()) * ITERATION_COUNT)); +#endif +} + +int main(int argc, char *argv[]) { + std::locale::global(std::locale("en_US.UTF-8")); + + bool onlyPrint{}; + bool onlyRegex{}; + bool showHelp{}; + + auto cli = lyra::help(showHelp) + | lyra::opt(onlyPrint)["-p"]["--print"]("Only run print benchmark") + | lyra::opt(onlyRegex)["-r"]["--regex"]("Only run regex benchmark"); + + auto result = cli.parse( { argc, argv } ); + if (!result) { + fmt::print("Error in command line: {}\n", result.message()); + return 1; + } + + if (showHelp) { + std::cout << cli << "\n"; + return 0; + } + + if(onlyPrint || (!onlyPrint && !onlyRegex)) { + int i = 1'234'567; + double d = 1.234567; + std::string s{"some arbitrary string"}; + + auto startFmt = std::chrono::steady_clock::now(); + for(uint64_t j = 0; j < ITERATION_COUNT; j++) { + fmt::print("This is a test! {} {} {}", i, d, s); + } + auto endFmt = std::chrono::steady_clock::now(); + auto startCout = std::chrono::steady_clock::now(); + for(uint64_t j = 0; j < ITERATION_COUNT; j++) { + fmt::basic_memory_buffer buf{}; + fmt::format_to(std::back_inserter(buf), "This is a test! {} {} {}", i, d, s); + std::cout.write(buf.data(), static_cast(buf.size())); + } + auto endCout = std::chrono::steady_clock::now(); + fmt::print("\n\n{} fmt::print ran for {:L} µs with {:L} peak memory usage {:L} prints/s\n", argv[0], std::chrono::duration_cast(endFmt - startFmt).count(), getPeakRSS(), + std::floor(1'000'000. / static_cast(std::chrono::duration_cast(endFmt - startFmt).count()) * ITERATION_COUNT)); + fmt::print("{} std::cout with fmt::format_to ran for {:L} µs with {:L} peak memory usage {:L} prints/s\n", argv[0], std::chrono::duration_cast(endCout - startCout).count(), getPeakRSS(), + std::floor(1'000'000. / static_cast(std::chrono::duration_cast(endCout - startCout).count()) * ITERATION_COUNT)); + } + + + if(onlyRegex || (!onlyPrint && !onlyRegex)) { + run_regex_bench(argv[0]); + run_regex_bench(argv[0]); + run_regex_bench(argv[0]); + run_regex_bench(argv[0]); + run_regex_bench(argv[0]); + } + } diff --git a/build.sh b/build.sh index e987ea4a..41086923 100755 --- a/build.sh +++ b/build.sh @@ -57,7 +57,8 @@ run_examples () ../bin/ichor_serializer_example || exit 1 ../bin/ichor_tcp_example || exit 1 ../bin/ichor_timer_example || exit 1 - ../bin/ichor_tracker_example || exit 1 + ../bin/ichor_factory_example || exit 1 + ../bin/ichor_introspection_example || exit 1 ../bin/ichor_websocket_example || exit 1 ../bin/ichor_websocket_example -t4 || exit 1 ../bin/ichor_yielding_timer_example || exit 1 @@ -70,63 +71,106 @@ run_benchmarks () ../bin/ichor_serializer_benchmark || exit 1 ../bin/ichor_start_benchmark || exit 1 ../bin/ichor_start_stop_benchmark || exit 1 + ../bin/ichor_utils_benchmark -r || exit 1 } if [[ $DOCKER -eq 1 ]]; then rm -rf ./* ../bin/* docker build -f ../Dockerfile -t ichor . || exit 1 - docker run -v $(pwd)/../:/opt/ichor/src -it ichor || exit 1 + docker run -v $(pwd)/../:/opt/ichor/src -v $(pwd)/../build:/opt/ichor/build --rm -it ichor || exit 1 run_examples + docker run -v $(pwd)/../:/opt/ichor/src -v $(pwd)/../build:/opt/ichor/build --rm -it ichor "rm -rf /opt/ichor/src/bin/* /opt/ichor/src/build/*" || exit 1 rm -rf ./* ../bin/* docker build -f ../Dockerfile-musl -t ichor-musl . || exit 1 - docker run -v $(pwd)/../:/opt/ichor/src -it ichor-musl || exit 1 + docker run -v $(pwd)/../:/opt/ichor/src -v $(pwd)/../build:/opt/ichor/build --rm -it ichor-musl || exit 1 run_examples + docker run -v $(pwd)/../:/opt/ichor/src -v $(pwd)/../build:/opt/ichor/build --rm -it ichor-musl "rm -rf /opt/ichor/src/bin/* /opt/ichor/src/build/*" || exit 1 rm -rf ./* ../bin/* docker build -f ../Dockerfile-asan -t ichor-asan . || exit 1 - docker run -v $(pwd)/../:/opt/ichor/src -it ichor-asan || exit 1 + docker run -v $(pwd)/../:/opt/ichor/src -v $(pwd)/../build:/opt/ichor/build --rm -it ichor-asan || exit 1 run_examples + run_benchmarks + docker run -v $(pwd)/../:/opt/ichor/src -v $(pwd)/../build:/opt/ichor/build --rm -it ichor-asan "rm -rf /opt/ichor/src/bin/* /opt/ichor/src/build/*" || exit 1 + + rm -rf ./* ../bin/* + docker build -f ../Dockerfile-asan-clang -t ichor-asan-clang . || exit 1 + docker run -v $(pwd)/../:/opt/ichor/src -v $(pwd)/../build:/opt/ichor/build --rm -it ichor-asan-clang || exit 1 + run_examples + run_benchmarks + docker run -v $(pwd)/../:/opt/ichor/src -v $(pwd)/../build:/opt/ichor/build --rm -it ichor-asan-clang "rm -rf /opt/ichor/src/bin/* /opt/ichor/src/build/*" || exit 1 - # tsan is purposefully not run automatically, because it usually contains false positives. + rm -rf ./* ../bin/* + docker build -f ../Dockerfile-tsan -t ichor-tsan . || exit 1 + docker run -v $(pwd)/../:/opt/ichor/src -v $(pwd)/../build:/opt/ichor/build --rm -it ichor-tsan || exit 1 + run_examples + run_benchmarks + docker run -v $(pwd)/../:/opt/ichor/src -v $(pwd)/../build:/opt/ichor/build --rm -it ichor-tsan "rm -rf /opt/ichor/src/bin/* /opt/ichor/src/build/*" || exit 1 rm -rf ./* ../bin/* docker run --rm --privileged multiarch/qemu-user-static --reset -p yes || exit 1 docker build -f ../Dockerfile-musl-aarch64 -t ichor-musl-aarch64 . || exit 1 - docker run -v $(pwd)/../:/opt/ichor/src -it ichor-musl-aarch64 || exit 1 + docker run -v $(pwd)/../:/opt/ichor/src -v $(pwd)/../build:/opt/ichor/build --rm -it ichor-musl-aarch64 || exit 1 + rm -f ../bin/run_aarch64_examples_and_tests.sh cat >> ../bin/run_aarch64_examples_and_tests.sh << EOF #!/bin/sh FILES=/opt/ichor/src/bin/* for f in \$FILES; do - if [[ "\$f" != *"Redis"* ]] && [[ "\$f" != *"benchmark"* ]] && [[ "\$f" != *"minimal"* ]] && [[ "\$f" != *"ping"* ]] && [[ "\$f" != *"etcd"* ]] && [[ "\$f" != *"Etcd"* ]] && [[ "\$f" != *"pong"* ]] && [[ "\$f" != *"Started"* ]] && [[ "\$f" != *".sh" ]] && [[ -x "\$f" ]] && [[ ! -d "\$f" ]] ; then - echo "Running \$f" + if [[ "\$f" != *"Tests" ]] && [[ "\$f" != *"benchmark" ]] && [[ "\$f" != *"minimal"* ]] && [[ "\$f" != *"tcp"* ]] && [[ "\$f" != *"ping"* ]] && [[ "\$f" != *"etcd"* ]] && [[ "\$f" != *"pong"* ]] && [[ "\$f" != *".sh" ]] && [[ -x "\$f" ]] && [[ ! -d "\$f" ]] ; then + echo "Running \${f}" \$f || exit 1 fi done EOF chmod +x ../bin/run_aarch64_examples_and_tests.sh - docker run -v $(pwd)/../:/opt/ichor/src --privileged -it ichor-musl-aarch64 "sh -c 'ulimit -r unlimited && /opt/ichor/src/bin/run_aarch64_examples_and_tests.sh'" || exit 1 - docker run -v $(pwd)/../:/opt/ichor/src --privileged -it ichor-musl-aarch64 "rm -rf /opt/ichor/src/bin/* /opt/ichor/src/build/*" || exit 1 + docker run -v $(pwd)/../:/opt/ichor/src -v $(pwd)/../build:/opt/ichor/build --rm --privileged -it ichor-musl-aarch64 "sh -c 'ulimit -r unlimited && /opt/ichor/src/bin/run_aarch64_examples_and_tests.sh'" || exit 1 + docker run -v $(pwd)/../:/opt/ichor/src -v $(pwd)/../build:/opt/ichor/build --rm --privileged -it ichor-musl-aarch64 "rm -rf /opt/ichor/src/bin/* /opt/ichor/src/build/*" || exit 1 + + rm -rf ./* ../bin/* + docker run --rm --privileged multiarch/qemu-user-static --reset -p yes || exit 1 + docker build -f ../Dockerfile-musl-aarch64-bench -t ichor-musl-aarch64-bench . || exit 1 + docker run -v $(pwd)/../:/opt/ichor/src -v $(pwd)/../build:/opt/ichor/build --rm -it ichor-musl-aarch64-bench || exit 1 + mkdir -p ../arm_bench + mv -f ../bin/* ../arm_bench + rm -f ../arm_bench/*.a + docker run -v $(pwd)/../:/opt/ichor/src -v $(pwd)/../build:/opt/ichor/build --rm --privileged -it ichor-musl-aarch64 "rm -rf /opt/ichor/src/bin/* /opt/ichor/src/build/*" || exit 1 fi +# Quick libcpp compile check +rm -rf ./* ../bin/* +CC=clang-18 CXX=clang++-18 cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DICHOR_USE_SANITIZERS=OFF -DICHOR_ENABLE_INTERNAL_DEBUGGING=OFF -DICHOR_USE_MOLD=ON -DICHOR_USE_BOOST_BEAST=ON -DICHOR_USE_SPDLOG=ON -DICHOR_USE_HIREDIS=ON .. || exit 1 +ninja || exit 1 +ninja test || exit 1 +run_examples + for i in ${!ccompilers[@]}; do rm -rf ./* ../bin/* - CC=${ccompilers[i]} CXX=${cppcompilers[i]} cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DICHOR_USE_SANITIZERS=ON -DICHOR_USE_ABSEIL=ON -DICHOR_ENABLE_INTERNAL_DEBUGGING=OFF -DICHOR_USE_MOLD=ON -DICHOR_USE_BOOST_BEAST=ON -DICHOR_USE_HIREDIS=ON .. || exit 1 + CC=${ccompilers[i]} CXX=${cppcompilers[i]} cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DICHOR_USE_SANITIZERS=ON -DICHOR_ENABLE_INTERNAL_DEBUGGING=ON -DICHOR_USE_MOLD=ON -DICHOR_USE_BOOST_BEAST=ON -DICHOR_USE_LIBCPP=OFF -DICHOR_USE_HIREDIS=ON .. || exit 1 ninja || exit 1 ninja test || exit 1 run_examples + run_benchmarks rm -rf ./* ../bin/* - CC=${ccompilers[i]} CXX=${cppcompilers[i]} cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DICHOR_USE_SANITIZERS=ON -DICHOR_USE_ABSEIL=OFF -DICHOR_ENABLE_INTERNAL_DEBUGGING=ON -DICHOR_USE_MOLD=ON -DICHOR_USE_BOOST_BEAST=ON -DICHOR_USE_SPDLOG=ON -DICHOR_USE_HIREDIS=ON .. || exit 1 + CC=${ccompilers[i]} CXX=${cppcompilers[i]} cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DICHOR_USE_SANITIZERS=ON -DICHOR_ENABLE_INTERNAL_DEBUGGING=ON -DICHOR_USE_MOLD=ON -DICHOR_USE_BOOST_BEAST=ON -DICHOR_USE_LIBCPP=OFF -DICHOR_USE_SPDLOG=ON -DICHOR_USE_HIREDIS=ON .. || exit 1 ninja || exit 1 ninja test || exit 1 run_examples + run_benchmarks rm -rf ./* ../bin/* - CC=${ccompilers[i]} CXX=${cppcompilers[i]} cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DICHOR_USE_SANITIZERS=OFF -DICHOR_USE_ABSEIL=ON -DICHOR_ENABLE_INTERNAL_DEBUGGING=OFF -DICHOR_USE_MOLD=ON -DICHOR_USE_BOOST_BEAST=ON -DICHOR_USE_HIREDIS=ON .. || exit 1 + CC=${ccompilers[i]} CXX=${cppcompilers[i]} cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DICHOR_USE_SANITIZERS=OFF -DICHOR_ENABLE_INTERNAL_DEBUGGING=ON -DICHOR_USE_MOLD=ON -DICHOR_USE_BOOST_BEAST=ON -DICHOR_USE_LIBCPP=OFF -DICHOR_USE_HIREDIS=ON .. || exit 1 ninja || exit 1 ninja test || exit 1 run_examples run_benchmarks done + +rm -rf ./* ../bin/* +CC=clang-18 CXX=clang++-18 cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DICHOR_USE_SANITIZERS=OFF -DICHOR_USE_MOLD=ON -DICHOR_USE_BOOST_BEAST=ON -DICHOR_USE_HIREDIS=ON .. || exit 1 +ninja || exit 1 +ninja test || exit 1 +run_examples +run_benchmarks diff --git a/cloc.sh b/cloc.sh index 322877ef..6645350d 100755 --- a/cloc.sh +++ b/cloc.sh @@ -1,4 +1,4 @@ #!/bin/bash -cloc src/ include/ test/ examples/ benchmarks/ --exclude-dir=etcd,etcd_example,tl,sole,backward,gsm_enc --exclude-content=LYRA_LYRA_HPP --by-file +cloc src/ include/ test/ examples/ benchmarks/ --exclude-dir=tl,sole,backward,gsm_enc,base64,ctre,ankerl --exclude-content=LYRA_LYRA_HPP --by-file echo "" -cloc src/ include/ test/ examples/ benchmarks/ --exclude-dir=etcd,etcd_example,tl,sole,backward,gsm_enc --exclude-content=LYRA_LYRA_HPP +cloc src/ include/ test/ examples/ benchmarks/ --exclude-dir=tl,sole,backward,gsm_enc,base64,ctre,ankerl --exclude-content=LYRA_LYRA_HPP diff --git a/docs/01-GettingStarted.md b/docs/01-GettingStarted.md index 92e0a3a0..00c2e304 100644 --- a/docs/01-GettingStarted.md +++ b/docs/01-GettingStarted.md @@ -55,15 +55,6 @@ Ubuntu 22.04: sudo apt install libboost1.74-all-dev libssl-dev ``` -If using abseil: -Ubuntu 20.04: -Not available on apt. Please compile manually. - -Ubuntu 22.04: -``` -sudo apt install libabsl-dev -``` - #### Windows Install MSVC 17.4 or newer. Open Ichor in MSVC and configure CMake according to your wishes. Build and install and you should find an `out` directory in Ichor's top level directory. @@ -131,10 +122,10 @@ Starting a dependency manager is quite easy. Instantiate it, tell it which servi ```c++ // main.cpp #include -#include +#include int main() { - auto queue = std::make_unique(); // use a priority queue based on Multimap + auto queue = std::make_unique(); // use a priority queue based on std::priority_queue auto &dm = queue->createManager(); // create the dependency manager dm.start(CaptureSigInt); @@ -179,12 +170,12 @@ struct MyService final { // Don't need an interface here, nothing has a dependen ```c++ // main.cpp #include -#include +#include #include "SomeDependency.h" #include "MyService.h" int main() { - auto queue = std::make_unique(); + auto queue = std::make_unique(); auto &dm = queue->createManager(); dm.createServiceManager(); // register SomeDependency as providing an ISomeDependency dm.createServiceManager(); // register MyService (requested dependencies get registered automatically) @@ -255,11 +246,11 @@ struct EventManipulationService final { // Don't need an interface here, nothing ```c++ // main.cpp #include -#include +#include #include "EventManipulationService.h" int main() { - auto queue = std::make_unique(); + auto queue = std::make_unique(); auto &dm = queue->createManager(); dm.createServiceManager(); dm.start(CaptureSigInt); @@ -293,14 +284,14 @@ struct MyTimerService final { ```c++ // main.cpp #include -#include +#include #include // Add this #include "MyService.h" #include "MyDependencyService.h" #include "MyTimerService.h" // Add this int main() { - auto queue = std::make_unique(); + auto queue = std::make_unique(); auto &dm = queue->createManager(); dm.createServiceManager(); dm.createServiceManager(); @@ -384,13 +375,13 @@ struct MyCoroutineTimerService final { ```c++ // main.cpp #include -#include +#include #include #include "AwaitService.h" #include "MyCoroutineTimerService.h" int main() { - auto queue = std::make_unique(); + auto queue = std::make_unique(); auto &dm = queue->createManager(); dm.createServiceManager(); dm.createServiceManager(); @@ -462,7 +453,7 @@ struct LoggerFactory final { Properties props{}; // Filter is a special property in Ichor, if this is detected, it gets checked before asking another service if they're interested in it. // In this case, we apply a filter specifically so that the requesting service id is the only one that will match. - props.template emplace<>("Filter", Ichor::make_any(Filter{ServiceIdFilterEntry{evt.originatingService}})); + props.template emplace<>("Filter", Ichor::make_any(ServiceIdFilterEntry{evt.originatingService})); auto *newLogger = _dm->createServiceManager(std::move(props)); _loggers.emplace(evt.originatingService, newLogger); } @@ -489,7 +480,7 @@ struct SomeServiceUsingLogger final { }; int main() { - auto queue = std::make_unique(); + auto queue = std::make_unique(); auto &dm = queue->createManager(); dm.createServiceManager(); // LoggerFactory will end up resolving the ILogger request from SomeServiceUsingLogger dm.createServiceManager(); @@ -532,11 +523,18 @@ Your events can then be inserted, intercepted or handled as you would e.g. a `Qu ```c++ struct MyEvent final : public Event { - MyEvent(uint64_t _id, uint64_t _originatingService, uint64_t _priority, uint64_t _someData) noexcept : Event(TYPE, NAME, _id, _originatingService, _priority), someData(_someData) {} + MyEvent(uint64_t _id, uint64_t _originatingService, uint64_t _priority, uint64_t _someData) noexcept : Event(_id, _originatingService, _priority), someData(_someData) {} ~MyEvent() final = default; + [[nodiscard]] std::string_view get_name() const noexcept final { + return NAME; + } + [[nodiscard]] NameHashType get_type() const noexcept final { + return TYPE; + } + uint64_t someData; - static constexpr uint64_t TYPE = typeNameHash(); + static constexpr NameHashType TYPE = typeNameHash(); static constexpr std::string_view NAME = typeName(); }; @@ -582,14 +580,14 @@ Starting up two manager is easy, but allowing services to communicate to service ```c++ // main.cpp #include -#include +#include #include int main() { Ichor::CommunicationChannel channel{}; - auto queueOne = std::make_unique(); + auto queueOne = std::make_unique(); auto &dmOne = queue->createManager(); // ID = 0 - auto queueTwo = std::make_unique(); + auto queueTwo = std::make_unique(); auto &dmTwo = queue->createManager(); // ID = 1 // Register the manager to the channel diff --git a/docs/02-DependencyInjection.md b/docs/02-DependencyInjection.md index f0633ca5..8998bc24 100644 --- a/docs/02-DependencyInjection.md +++ b/docs/02-DependencyInjection.md @@ -39,7 +39,7 @@ class BasicService final { public: BasicService(IHttpHostService *hostService) { _routeRegistration = hostService->addRoute(HttpMethod::get, "/basic", [this, serializer](HttpRequest &req) -> AsyncGenerator { - co_return HttpResponse{false, HttpStatus::ok, "application/text, "This is my basic webpage", {}}; + co_return HttpResponse{HttpStatus::ok, "application/text, "This is my basic webpage", {}}; }); } @@ -51,7 +51,7 @@ private: Of course, Ichor needs to know about this type and this is done by registering it during initialization: ```c++ -auto queue = std::make_unique(spinlock); +auto queue = std::make_unique(spinlock); auto &dm = queue->createManager(); dm.createServiceManager(Properties{{"Address", Ichor::make_any("127.0.0.1")}, {"Port", Ichor::make_any(static_cast(80))}}); dm.createServiceManager(); @@ -71,8 +71,8 @@ class MyService final : public AdvancedService { public: // Creating instances of a service includes properties, and these need to be stored in the parent class. Be careful to move them each time and don't use the props variable but instead call getProperties(), if you need them. MyService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); // Request ILogger as a required dependency (the start function will only be called if all required dependencies have at least 1 instance available) - reg.registerDependency(this, false); // Request IOptionalService as an optional dependency. This does not influence the start and stop functions. + reg.registerDependency(this, DependencyFlags::REQUIRED); // Request ILogger as a required dependency (the start function will only be called if all required dependencies have at least 1 instance available) + reg.registerDependency(this, DependencyFlags::NONE); // Request IOptionalService as an optional dependency. This does not influence the start and stop functions. } ~MyService() final = default; @@ -109,10 +109,36 @@ private: }; ``` +## Multiple Requests Of Same Type + +There are two ways to request dependencies of the same type. Note that this requires using the AdvancedService method of requestion dependencies. + +The easiest is to specify the `ALLOW_MULTIPLE` flag for the dependency: + +```c++ +MyService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { + reg.registerDependency(this, DependencyFlags::ALLOW_MULTIPLE); // optional dependency + reg.registerDependency(this, DependencyFlags::REQUIRED | DependencyFlags::ALLOW_MULTIPLE); // required dependency +} +``` + +This can also be combined with the `REQUIRED` flag, although as soon as one service is injected of the request type, it is considered satisfied and may start the service. Similarly, the service is only stopped once all services of the requested type are removed. + +The second way is to duplicate the request for the number of times you want: + +```c++ +MyService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { + // MyService now needs exactly 3 services before it starts. + reg.registerDependency(this, DependencyFlags::REQUIRE); + reg.registerDependency(this, DependencyFlags::REQUIRE); + reg.registerDependency(this, DependencyFlags::REQUIRE); +} +``` + ## Extra Resources [How to Use C++ Dependency Injection to Write Maintainable Software - Francesco Zoffoli CppCon 2022](https://www.youtube.com/watch?v=l6Y9PqyK1Mc) [Cody Morterud's blog](https://www.codymorterud.com/design/2018/09/07/dependency-injection-cpp.html) -[C++Now 2019: Kris Jusiak “Dependency Injection - a 25-dollar term for a 5-cent concept”](https://www.youtube.com/watch?v=yVogS4NbL6U) \ No newline at end of file +[C++Now 2019: Kris Jusiak “Dependency Injection - a 25-dollar term for a 5-cent concept”](https://www.youtube.com/watch?v=yVogS4NbL6U) diff --git a/docs/03-CMakeOptions.md b/docs/03-CMakeOptions.md index fa6371d2..369fe0b3 100644 --- a/docs/03-CMakeOptions.md +++ b/docs/03-CMakeOptions.md @@ -65,10 +65,6 @@ Usage with gcc 12+ is technically possible, but it might throw off [Catch2 unit Enables the use of the [sdevent event queue](../include/ichor/event_queues/SdeventQueue.h). Requires having sdevent headers and libraries installed on your system to compile. -## ICHOR_USE_ABSEIL (optional dependency) - -Enables the use of the abseil containers in Ichor. Requires having abseil headers and libraries installed on your system to compile. - ## ICHOR_USE_HIREDIS (optional dependency) Enables the use of the Hiredis library and exposes the Redis service. Requires having Hiredis libraries and headers installed on your system to compile. @@ -97,6 +93,10 @@ This option results in statically linking libgcc and libstdc++ as well as turnin Control flow protection is not available on Aarch64, enable this to allow (cross-)compiling for that architecture. +## ICHOR_USE_LIBCPP + +Tells ichor to add the `-stdlib=libc++` compiler and linker flags. Requires compiling with clang, as gcc and msvc do not support libc++. + ## ICHOR_ARCH_OPTIMIZATION Compile Ichor with architecture specific optimizations. Allowed settings: `NATIVE`, `X86_64`, `X86_64_SSE4`, `X86_64_AVX2`, `X86_64_AVX512`, `MODERN_ARM_GENERIC`. diff --git a/docs/04-WhyUseIchor.md b/docs/04-WhyUseIchor.md index f9a28345..f7790671 100644 --- a/docs/04-WhyUseIchor.md +++ b/docs/04-WhyUseIchor.md @@ -1,6 +1,6 @@ -# ASIO supports io_uring, can do all of that, and more. Any reason i'd want to use your lib instead? +# ASIO supports io_uring, can do all of that, and more. Any reason I'd want to use your lib instead? -Ichor is a combination of things: Event Loops, Dependency Injection, as well as pre-made implementations for HTTP(s) servers/clients, websocket servers/clients, redis client and more. +Ichor is a combination of things: Event Loops, Dependency Injection, as well as pre-made implementations for HTTP(s) servers/clients, websocket servers/clients, redis client, etcd client and more. Ichor is intended to be integrated on top of an existing event loop (e.g. [systemd](https://github.com/volt-software/Ichor/blob/main/src/ichor/event_queues/SdeventQueue.cpp) ). But if you have none, Ichor provides a simple implementation. Integrating Ichor into an existing boost.ASIO loop is possible, but not implemented today. @@ -10,14 +10,14 @@ Ichor forces one thread per 'executor', which reduces, but doesn't completely el Here's a short hand comparison between boost.ASIO and Ichor when it comes to event loops: -||Ichor|Boost.ASIO| -|:-|:-|:-| -|Integrate multiple event loops|✅|❌| -|Async File IO|Kinda, missing directory operations|Kinda, only on Windows (with I/O completion ports) and Linux (if using io_uring) and missing functionality such as deleting files and directory operations| -|io_uring|❌|✅ since Boost 1.78| -|Thread Pool|❌|✅| -|Threading|One thread per executor|one or more threads per executor| -|Cognitive Load Thread Safety|Low|Medium| +| |Ichor|Boost.ASIO| +|:-------------------------------|:-|:-| +| Integrate multiple event loops |✅|❌| +| Async File IO |Kinda, missing directory operations|Kinda, only on Windows (with I/O completion ports) and Linux (if using io_uring) and missing functionality such as deleting files and directory operations| +| io_uring |❌|✅ since Boost 1.78| +| Thread Pool |❌|✅| +| Threading |One thread per executor|one or more threads per executor| +| Cognitive Load Thread Safety |Low|Medium| # But Boost.DI also provides DI. Why would I use your lib? @@ -27,16 +27,16 @@ The best example here would be creating a new logger for each instance that is r Here's a short hand comparison between Ichor and various DI libraries: -||Ichor|Hypodermic|Boost.DI|Google.Fruit|CppMicroservices| -|:-|:-|:-|:-|:-|:-| -|Runtime/compile-time|Runtime|Runtime|Compile time (with some runtime)|Compile time (with some runtime)|Runtime| -|Constructor Injection|✅|✅|✅|✅|❌| -|Per-instance resolving|✅|❌|❌|❌|✅| -|Factories|✅|❌?|Partial|Partial|✅| -|Coroutines|✅|❌|❌|❌|❌| -|Optional dependencies|✅|❌|❌|❌|✅?| -|Per-instance lifetime|✅|❌|❌|❌|✅| -|Minimum c++|20|11|14|11|17| +| |Ichor|Hypodermic|Boost.DI|Google.Fruit|CppMicroservices| +|:-----------------------|:-|:-|:-|:-|:-| +| Runtime/compile-time |Runtime|Runtime|Compile time (with some runtime)|Compile time (with some runtime)|Runtime| +| Constructor Injection |✅|✅|✅|✅|❌| +| Per-instance resolving |✅|❌|❌|❌|✅| +| Factories |✅|❌?|Partial|Partial|✅| +| Coroutines |✅|❌|❌|❌|❌| +| Optional dependencies |✅|❌|❌|❌|✅?| +| Per-instance lifetime |✅|❌|❌|❌|✅| +| Minimum c++ |20|11|14|11|17| Question marks denote me being unsure @@ -44,15 +44,15 @@ Question marks denote me being unsure If you need all the bells and whistles that Boost.BEAST provides, then by all means, use that. If you need performance, the techempowered benchmarks will probably tell you to use Drogon or something similar. Ichor has a gap here. -||Ichor|Boost.BEAST| -|:-|:-|:-| -|Basic HTTP|✅|✅| -|Basic HTTPS|✅|✅| -|Basic Websocket|✅|✅| -|Basic ssl Websocket|❌|✅| -|Thread Pool|❌|✅| -|Threading|One thread per executor|one or more threads per executor| -|Cognitive Load Thread Safety|Low|Medium| +| |Ichor|Boost.BEAST| +|:-----------------------------|:-|:-| +| Basic HTTP |✅|✅| +| Basic HTTPS |✅|✅| +| Basic Websocket |✅|✅| +| Basic ssl Websocket |❌|✅| +| Thread Pool |❌|✅| +| Threading |One thread per executor|one or more threads per executor| +| Cognitive Load Thread Safety |Low|Medium| # Stop avoiding me and please answer the question, why would I use your lib? diff --git a/docs/06-HttpsConnections.md b/docs/06-HttpsConnections.md index 6055debd..07f3585f 100644 --- a/docs/06-HttpsConnections.md +++ b/docs/06-HttpsConnections.md @@ -66,7 +66,7 @@ pFbool/8ZDecmB4ZSa03aw== Now we need to get these into the code. We can do this by reading a file using standard C++, but for convenience, this doc will just copy and paste them into code: ```c++ -auto queue = std::make_unique(); +auto queue = std::make_unique(); auto &dm = queue->createManager(); std::string key = "-----BEGIN PRIVATE KEY-----\n" @@ -137,7 +137,7 @@ dm.createServiceManager(Properties{{"Address" Connecting to a Https webserver is somewhat similar. Using the same certificate from above, we need to tell the client to trust it: ```c++ -auto queue = std::make_unique(); +auto queue = std::make_unique(); auto &dm = queue->createManager(); std::string cert = "-----BEGIN CERTIFICATE-----\n" diff --git a/docs/08-AsyncIO.md b/docs/08-AsyncIO.md index 4e5647f4..8c8b4381 100644 --- a/docs/08-AsyncIO.md +++ b/docs/08-AsyncIO.md @@ -15,7 +15,7 @@ The abstraction Ichor provides allows you to queue up file I/O on another thread Here's an example showcasing reading and writing a file: ```c++ -auto queue = std::make_unique(); +auto queue = std::make_unique(); auto &dm = queue->createManager(); uint64_t ioSvcId{}; @@ -71,4 +71,4 @@ t.join(); ## Possible different implementations -Ichor currently provides only one type of implementation due to time constraints: one I/O thread shared over all Ichor threads. Obviously, this won't fit all use cases. Creating a new implementation is possible that, for example, uses multiple I/O threads and schedules requests round robin. As long as the implementation adheres to the `IAsyncFileIO` interface, it is possible to swap. \ No newline at end of file +Ichor currently provides only one type of implementation due to time constraints: one I/O thread shared over all Ichor threads. Obviously, this won't fit all use cases. Creating a new implementation is possible that, for example, uses multiple I/O threads and schedules requests round robin. As long as the implementation adheres to the `IAsyncFileIO` interface, it is possible to swap. diff --git a/docs/09-HttpServer.md b/docs/09-HttpServer.md new file mode 100644 index 00000000..a15f1c74 --- /dev/null +++ b/docs/09-HttpServer.md @@ -0,0 +1,93 @@ +# Http Server + +Ichor provides an Expressjs-inspired abstraction for setting up an Http(s) server. + +## Creating Host + +The Boost HttpHostService implementation requires a logger, the AsioContextService and the HttpHostService itself to be set up: + +```c++ +#include +#include +#include + + +int main(int argc, char *argv[]) { + auto queue = std::make_unique(); + auto &dm = queue->createManager(); + dm.createServiceManager, ILoggerFactory>(); + dm.createServiceManager(Properties{{"Threads", Ichor::make_any(2)}}); + dm.createServiceManager(Properties{{"Address", Ichor::make_any("localhost")}, {"Port", Ichor::make_any(static_cast(8001))}}); + queue->start(CaptureSigInt); + return 0; +} +``` + +## Adding routes + +```c++ +#include + +class UsingHttpService final { + UsingHttpService(IHttpHostService *host) { + _routeRegistrations.emplace_back(host->addRoute(HttpMethod::post, "/test", [this](HttpRequest &req) -> AsyncGenerator { + co_return HttpResponse{HttpStatus::ok, "application/text", "This is my basic webpage", {}}; + })); + } + std::vector> _routeRegistrations{}; +}; +``` + +## Using regex in routes + +To add routes that capture parts of the URL, Ichor provides a regex route matcher that supports capture groups: + +```c++ +#include + +class UsingHttpService final { + UsingHttpService(IHttpHostService *host) { + _routeRegistrations.emplace_back(svc.addRoute(HttpMethod::get, std::make_unique>(), [this](HttpRequest &req) -> AsyncGenerator { + std::string user_id = req.regex_params[0]; + if(req.regex_params.size() > 1) { + std::string query_params = req.regex_params[1]; // e.g. param1=one¶m2=two, parsing string is left to the user for now, though Ichor does provide a string_view split function in stl/StringUtils.h + } + co_return HttpResponse{HttpStatus::ok, "text/plain", {}, {}}; + })); + } + std::vector> _routeRegistrations{}; +}; +``` + +The `regex_params` is a vector with the capture groups counted from left to right. + +## Custom route matcher + +If you want to add custom logic on matching routes, because maybe you want to use a different regex library than CTRE, implement the interface to RouteMatcher: + +```c++ +struct CustomRouteMatcher final : public RouteMatcher { + ~CustomRouteMatcher() noexcept final = default; + + [[nodiscard]] bool matches(std::string_view route) noexcept final { + if(route == "/test") { + return true; + } + + return false; + } + + // these get moved into HttpRequest's regex_params + [[nodiscard]] std::vector route_params() noexcept final { + return {}; + } +}; +``` + +And use that with the route registration: + +```c++ +_routeRegistrations.emplace_back(svc.addRoute(HttpMethod::get, std::make_unique(), [this](HttpRequest &req) -> AsyncGenerator { + co_return HttpResponse{HttpStatus::ok, "text/plain", {}, {}}; +})); +``` diff --git a/docs/Outdated-Fundamentals.md b/docs/Outdated-Fundamentals.md index e066632f..3eb57818 100644 --- a/docs/Outdated-Fundamentals.md +++ b/docs/Outdated-Fundamentals.md @@ -25,8 +25,8 @@ struct ISomeDependency{}; struct ISomeOptionalDependency{}; struct DependencyService final : public Service { DependencyService(DependencyRegister ®, Properties props, DependencyManager *) : Service(std::move(props), mng) { - reg.registerDependency(this, true /* required dependency */); - reg.registerDependency(this, false /* optional dependency */); + reg.registerDependency(this, DependencyFlags::REQUIRED /* required dependency */); + reg.registerDependency(this, DependencyFlags::NONE /* optional dependency */); } ~DependencyService() final = default; diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 37c9bce9..9fbced71 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -8,10 +8,15 @@ add_executable(ichor_minimal_example ${EXAMPLE_SOURCES}) target_link_libraries(ichor_minimal_example ${CMAKE_THREAD_LIBS_INIT}) target_link_libraries(ichor_minimal_example ichor) -file(GLOB_RECURSE EXAMPLE_SOURCES ${ICHOR_TOP_DIR}/examples/tracker_example/*.cpp) -add_executable(ichor_tracker_example ${EXAMPLE_SOURCES}) -target_link_libraries(ichor_tracker_example ${CMAKE_THREAD_LIBS_INIT}) -target_link_libraries(ichor_tracker_example ichor) +file(GLOB_RECURSE EXAMPLE_SOURCES ${ICHOR_TOP_DIR}/examples/introspection_example/*.cpp) +add_executable(ichor_introspection_example ${EXAMPLE_SOURCES}) +target_link_libraries(ichor_introspection_example ${CMAKE_THREAD_LIBS_INIT}) +target_link_libraries(ichor_introspection_example ichor) + +file(GLOB_RECURSE EXAMPLE_SOURCES ${ICHOR_TOP_DIR}/examples/factory_example/*.cpp) +add_executable(ichor_factory_example ${EXAMPLE_SOURCES}) +target_link_libraries(ichor_factory_example ${CMAKE_THREAD_LIBS_INIT}) +target_link_libraries(ichor_factory_example ichor) if(NOT WIN32 AND NOT APPLE) file(GLOB_RECURSE EXAMPLE_SOURCES ${ICHOR_TOP_DIR}/examples/tcp_example/*.cpp) diff --git a/examples/common/DebugService.h b/examples/common/DebugService.h index 9640f836..fd71ae1c 100644 --- a/examples/common/DebugService.h +++ b/examples/common/DebugService.h @@ -11,15 +11,15 @@ using namespace Ichor; class DebugService final : public AdvancedService { public: DebugService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true, Properties{{"LogLevel", Ichor::make_any(LogLevel::LOG_INFO)}}); - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED, Properties{{"LogLevel", Ichor::make_any(LogLevel::LOG_INFO)}}); + reg.registerDependency(this, DependencyFlags::REQUIRED); } ~DebugService() final = default; private: Task> start() final { auto &_timer = _timerFactory->createTimer(); _timer.setCallback([this]() { - auto svcs = dm.getServiceInfo(); + auto svcs = dm.getAllServices(); for(auto &[id, svc] : svcs) { ICHOR_LOG_INFO(_logger, "Svc {}:{} {}", svc->getServiceId(), svc->getServiceName(), svc->getServiceState()); } diff --git a/examples/common/TestMsgGlazeSerializer.h b/examples/common/TestMsgGlazeSerializer.h index 6ed0e74c..373a61db 100644 --- a/examples/common/TestMsgGlazeSerializer.h +++ b/examples/common/TestMsgGlazeSerializer.h @@ -2,18 +2,7 @@ #include #include "TestMsg.h" - -// Glaze uses different conventions than Ichor, ignore them to prevent being spammed by warnings -#if defined( __GNUC__ ) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wsign-conversion" -# pragma GCC diagnostic ignored "-Wshadow" -# pragma GCC diagnostic ignored "-Wconversion" -#endif -#include -#if defined( __GNUC__ ) -# pragma GCC diagnostic pop -#endif +#include using namespace Ichor; @@ -35,14 +24,14 @@ class TestMsgGlazeSerializer final : public ISerializer { return buf; } - std::optional deserialize(std::vector &&stream) final { + tl::optional deserialize(std::vector &&stream) final { TestMsg msg; auto err = glz::read_json(msg, stream); if(err) { fmt::print("Glaze error {} at {}\n", (int)err.ec, err.location); fmt::print("json {}\n", (char*)stream.data()); - return std::nullopt; + return tl::nullopt; } return msg; diff --git a/examples/common/lyra.hpp b/examples/common/lyra.hpp index e805b7c0..fc19559a 100644 --- a/examples/common/lyra.hpp +++ b/examples/common/lyra.hpp @@ -247,7 +247,7 @@ struct is_specialization_of, Primary> : std::true_type # ifdef __has_include # if __has_include() && __cplusplus >= 201703L # include -# define LYRA_CONFIG_OPTIONAL_TYPE std::optional +# define LYRA_CONFIG_OPTIONAL_TYPE tl::optional # endif # endif #endif diff --git a/examples/etcd_example/UsingEtcdService.h b/examples/etcd_example/UsingEtcdService.h index a6d8966f..8f1dafc7 100644 --- a/examples/etcd_example/UsingEtcdService.h +++ b/examples/etcd_example/UsingEtcdService.h @@ -12,8 +12,10 @@ class UsingEtcdV2Service final { public: UsingEtcdV2Service(IService *self, IEventQueue *queue, ILogger *logger, IEtcd *EtcdV2Service) { ICHOR_LOG_INFO(logger, "UsingEtcdV2Service started"); - queue->pushEvent(0, [logger, EtcdV2Service, queue, self]() -> AsyncGenerator { - auto ret = co_await EtcdV2Service->put("test", "2"); + + // standard put/get + queue->pushEvent(0, [logger, EtcdV2Service]() -> AsyncGenerator { + auto ret = co_await EtcdV2Service->put("test", "2", 10u); // put with a TTL of 10 seconds if(ret) { ICHOR_LOG_INFO(logger, "Successfully put key/value into etcd"); auto storedVal = co_await EtcdV2Service->get("test"); @@ -26,9 +28,48 @@ class UsingEtcdV2Service final { ICHOR_LOG_ERROR(logger, "Error putting key/value into etcd: {}", ret.error()); } + co_return {}; + }); + + // wait for update and set + queue->pushEvent(0, [logger, EtcdV2Service, queue, self]() -> AsyncGenerator { + auto ret = co_await EtcdV2Service->put("watch", "3", 10u); // set value + if(!ret) { + std::terminate(); + } + + // update the "test" key in 250 ms + auto start = std::chrono::steady_clock::now(); + queue->pushEvent(0, [EtcdV2Service, start]() -> AsyncGenerator { + auto now = std::chrono::steady_clock::now(); + while(now - start < std::chrono::milliseconds(250)) { + co_yield IchorBehaviour::DONE; + now = std::chrono::steady_clock::now(); + } + + auto ret2 = co_await EtcdV2Service->put("watch", "4", 10u); // update value, this should trigger the watch + if(!ret2) { + std::terminate(); + } + + co_return {}; + }); + + // wait for a reply, this blocks this async event (hence why we need to update the key in another async event). + // Note that this does not block the thread, just this event. + auto getReply = co_await EtcdV2Service->get("watch", false, false, true, {}); + if(!getReply) { + throw std::runtime_error(""); + } + + if(getReply.value().node->value != "4") { + std::terminate(); + } + ICHOR_LOG_ERROR(logger, "Successfully got a value when watching 'watch'", ret.error()); + queue->pushEvent(self->getServiceId()); - co_return IchorBehaviour::DONE; + co_return {}; }); } }; diff --git a/examples/etcd_example/main.cpp b/examples/etcd_example/main.cpp index 69e130c9..56778a37 100644 --- a/examples/etcd_example/main.cpp +++ b/examples/etcd_example/main.cpp @@ -1,10 +1,10 @@ #include "UsingEtcdService.h" -#include +#include #include #include #include -#include -#include +#include +#include #include // Some compile time logic to instantiate a regular cout logger or to use the spdlog logger, if Ichor has been compiled with it. @@ -25,7 +25,7 @@ int main(int argc, char *argv[]) { std::locale::global(std::locale("en_US.UTF-8")); auto start = std::chrono::steady_clock::now(); - auto queue = std::make_unique(); + auto queue = std::make_unique(); auto &dm = queue->createManager(); #ifdef ICHOR_USE_SPDLOG dm.createServiceManager(); diff --git a/examples/event_statistics_example/README.md b/examples/event_statistics_example/README.md index 7413510b..7d83470a 100644 --- a/examples/event_statistics_example/README.md +++ b/examples/event_statistics_example/README.md @@ -28,7 +28,7 @@ public: void postInterceptEvent(Event const &evt, bool processed) { auto now = std::chrono::steady_clock::now(); auto processingTime = now - _startProcessingTimestamp; - fmt::print("Processing of event type {} took {} ns", evt.name, std::chrono::duration_cast(processingTime).count()); + fmt::print("Processing of event type {} took {} ns", evt.get_name(), std::chrono::duration_cast(processingTime).count()); } private: diff --git a/examples/event_statistics_example/main.cpp b/examples/event_statistics_example/main.cpp index 2ca7d79b..c7a6cb34 100644 --- a/examples/event_statistics_example/main.cpp +++ b/examples/event_statistics_example/main.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -23,15 +23,18 @@ int main(int argc, char *argv[]) { std::locale::global(std::locale("en_US.UTF-8")); auto start = std::chrono::steady_clock::now(); - auto queue = std::make_unique(); + auto queue = std::make_unique(); auto &dm = queue->createManager(); + + uint64_t priorityToEnsureStartingFirst = 51; + #ifdef ICHOR_USE_SPDLOG - dm.createServiceManager(); + dm.createServiceManager(Properties{}, priorityToEnsureStartingFirst); #endif - dm.createServiceManager, ILoggerFactory>(Properties{{"DefaultLogLevel", Ichor::make_any(LogLevel::LOG_INFO)}}); - dm.createServiceManager(Properties{{"ShowStatisticsOnStop", make_any(true)}}); + dm.createServiceManager, ILoggerFactory>(Properties{{"DefaultLogLevel", Ichor::make_any(LogLevel::LOG_INFO)}}, priorityToEnsureStartingFirst); + dm.createServiceManager(Properties{{"ShowStatisticsOnStop", make_any(true)}}, priorityToEnsureStartingFirst); dm.createServiceManager(); - dm.createServiceManager(); + dm.createServiceManager(Properties{}, priorityToEnsureStartingFirst); queue->start(CaptureSigInt); auto end = std::chrono::steady_clock::now(); fmt::print("{} ran for {:L} µs\n", argv[0], std::chrono::duration_cast(end-start).count()); diff --git a/examples/tracker_example/TrackerService.h b/examples/factory_example/FactoryService.h similarity index 93% rename from examples/tracker_example/TrackerService.h rename to examples/factory_example/FactoryService.h index 28d7e0c2..41624c7d 100644 --- a/examples/tracker_example/TrackerService.h +++ b/examples/factory_example/FactoryService.h @@ -23,14 +23,14 @@ class ScopeFilterEntry final { std::string scope; }; -class TrackerService final { +class FactoryService final { public: - TrackerService(DependencyManager *dm, IService *self, ILogger *logger) : _self(self), _logger(logger) { - ICHOR_LOG_INFO(_logger, "TrackerService started"); + FactoryService(DependencyManager *dm, IService *self, ILogger *logger) : _self(self), _logger(logger) { + ICHOR_LOG_INFO(_logger, "FactoryService started"); _trackerRegistration = dm->registerDependencyTracker(this, self); } - ~TrackerService() { - ICHOR_LOG_INFO(_logger, "TrackerService stopped"); + ~FactoryService() { + ICHOR_LOG_INFO(_logger, "FactoryService stopped"); } private: @@ -60,7 +60,7 @@ class TrackerService final { auto newProps = *evt.properties.value(); // `Filter` is a magic keyword that Ichor uses to determine if this service is global or if Ichor should use its filtering logic. // In this case, we tell Ichor to only insert this service if the requesting service has a matching scope - newProps.emplace("Filter", Ichor::make_any(Filter{ScopeFilterEntry{scope}})); + newProps.emplace("Filter", Ichor::make_any(ScopeFilterEntry{scope})); _scopedRuntimeServices.emplace(scope, GetThreadLocalManager().createServiceManager(std::move(newProps))); } diff --git a/examples/tracker_example/README.md b/examples/factory_example/README.md similarity index 85% rename from examples/tracker_example/README.md rename to examples/factory_example/README.md index c122525d..13820b87 100644 --- a/examples/tracker_example/README.md +++ b/examples/factory_example/README.md @@ -1,4 +1,4 @@ -# Tracker Example +# Factory Example This example shows how to 'track' dependency requests. Specifically, this is most commonly used to create factories. @@ -9,4 +9,4 @@ When a service is created normally, that dependency is considered a globally ava This demonstrates the following concepts: * Fulfilling a dependency per-request, instead of globally -* How to use the advanced `filter` feature to create a per-request dependency +* How to use the `filter` feature to create a per-request dependency diff --git a/examples/tracker_example/RuntimeCreatedService.h b/examples/factory_example/RuntimeCreatedService.h similarity index 100% rename from examples/tracker_example/RuntimeCreatedService.h rename to examples/factory_example/RuntimeCreatedService.h diff --git a/examples/tracker_example/TestService.h b/examples/factory_example/TestService.h similarity index 90% rename from examples/tracker_example/TestService.h rename to examples/factory_example/TestService.h index ffa2bf74..59b7a171 100644 --- a/examples/tracker_example/TestService.h +++ b/examples/factory_example/TestService.h @@ -11,8 +11,8 @@ using namespace Ichor; class TestService final : public AdvancedService { public: TestService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); - reg.registerDependency(this, true, getProperties()); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::REQUIRED, getProperties()); } ~TestService() final = default; diff --git a/examples/tracker_example/main.cpp b/examples/factory_example/main.cpp similarity index 88% rename from examples/tracker_example/main.cpp rename to examples/factory_example/main.cpp index 91ebfe45..675fd1cc 100644 --- a/examples/tracker_example/main.cpp +++ b/examples/factory_example/main.cpp @@ -1,7 +1,7 @@ #include "TestService.h" -#include "TrackerService.h" +#include "FactoryService.h" #include "RuntimeCreatedService.h" -#include +#include #include // Some compile time logic to instantiate a regular cout logger or to use the spdlog logger, if Ichor has been compiled with it. @@ -22,7 +22,7 @@ int main(int argc, char *argv[]) { std::locale::global(std::locale("en_US.UTF-8")); auto start = std::chrono::steady_clock::now(); - auto queue = std::make_unique(); + auto queue = std::make_unique(); auto &dm = queue->createManager(); #ifdef ICHOR_USE_SPDLOG dm.createServiceManager(); @@ -30,7 +30,7 @@ int main(int argc, char *argv[]) { dm.createServiceManager, ILoggerFactory>(Properties{{"DefaultLogLevel", Ichor::make_any(LogLevel::LOG_INFO)}}); dm.createServiceManager(Properties{{"scope", Ichor::make_any("one"s)}}); dm.createServiceManager(Properties{{"scope", Ichor::make_any("two"s)}}); - dm.createServiceManager(); + dm.createServiceManager(); queue->start(CaptureSigInt); auto end = std::chrono::steady_clock::now(); fmt::print("{} ran for {:L} µs\n", argv[0], std::chrono::duration_cast(end-start).count()); diff --git a/examples/http_example/README.md b/examples/http_example/README.md index 4522bf79..0777081e 100644 --- a/examples/http_example/README.md +++ b/examples/http_example/README.md @@ -6,7 +6,7 @@ This demonstrates the following concepts: * Defining and using serializers * How to set up an HTTP server * How to set up an HTTP client using the `ClientFactory` -* Using the advanced parameter `Spinlock` in the `MultimapQueue`. +* Using the advanced parameter `Spinlock` in the `PriorityQueue`. ## Command Line Arguments diff --git a/examples/http_example/UsingHttpService.h b/examples/http_example/UsingHttpService.h index 4cf5aec9..c7ab526a 100644 --- a/examples/http_example/UsingHttpService.h +++ b/examples/http_example/UsingHttpService.h @@ -17,10 +17,10 @@ using namespace Ichor; class UsingHttpService final : public AdvancedService { public: UsingHttpService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); - reg.registerDependency>(this, true); - reg.registerDependency(this, true, getProperties()); // using getProperties here prevents us refactoring this class to constructor injection - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency>(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::REQUIRED, getProperties()); // using getProperties here prevents us refactoring this class to constructor injection + reg.registerDependency(this, DependencyFlags::REQUIRED); } ~UsingHttpService() final = default; @@ -31,7 +31,9 @@ class UsingHttpService final : public AdvancedService { GetThreadLocalEventQueue().pushEvent(getServiceId(), [this]() -> AsyncGenerator { auto toSendMsg = _serializer->serialize(TestMsg{11, "hello"}); - co_await sendTestRequest(std::move(toSendMsg)).begin(); + co_await sendTestRequest(std::move(toSendMsg)); + co_await sendRegexRequest(); + GetThreadLocalEventQueue().pushEvent(getServiceId()); co_return {}; }); @@ -39,7 +41,7 @@ class UsingHttpService final : public AdvancedService { } Task stop() final { - _routeRegistration.reset(); + _routeRegistrations.clear(); ICHOR_LOG_INFO(_logger, "UsingHttpService stopped"); co_return; } @@ -73,23 +75,30 @@ class UsingHttpService final : public AdvancedService { void addDependencyInstance(IHttpHostService &svc, IService&) { ICHOR_LOG_INFO(_logger, "Inserted IHttpHostService"); - _routeRegistration = svc.addRoute(HttpMethod::post, "/test", [this](HttpRequest &req) -> AsyncGenerator { + _routeRegistrations.emplace_back(svc.addRoute(HttpMethod::post, "/test", [this](HttpRequest &req) -> AsyncGenerator { auto msg = _serializer->deserialize(std::move(req.body)); ICHOR_LOG_WARN(_logger, "received request on route {} {} with testmsg {} - {}", (int)req.method, req.route, msg->id, msg->val); - co_return HttpResponse{false, HttpStatus::ok, "application/json", _serializer->serialize(TestMsg{11, "hello"}), {}}; - }); + co_return HttpResponse{HttpStatus::ok, "application/json", _serializer->serialize(TestMsg{11, "hello"}), {}}; + })); + _routeRegistrations.emplace_back(svc.addRoute(HttpMethod::get, std::make_unique>(), [this](HttpRequest &req) -> AsyncGenerator { + ICHOR_LOG_WARN(_logger, "received request on route {} {} with params:", (int)req.method, req.route); + for(auto const ¶m : req.regex_params) { + ICHOR_LOG_WARN(_logger, "{}", param); + } + co_return HttpResponse{HttpStatus::ok, "text/plain", {}, {}}; + })); } void removeDependencyInstance(IHttpHostService&, IService&) { ICHOR_LOG_INFO(_logger, "Removed IHttpHostService"); - _routeRegistration.reset(); + _routeRegistrations.clear(); } friend DependencyRegister; - AsyncGenerator sendTestRequest(std::vector &&toSendMsg) { + Task sendTestRequest(std::vector &&toSendMsg) { ICHOR_LOG_INFO(_logger, "sendTestRequest"); - std::vector headers{HttpHeader{"Content-Type", "application/json"}}; + unordered_map headers{{"Content-Type", "application/json"}}; auto response = co_await _connectionService->sendAsync(HttpMethod::post, "/test", std::move(headers), std::move(toSendMsg)); if(_serializer == nullptr) { @@ -103,7 +112,14 @@ class UsingHttpService final : public AdvancedService { } else { ICHOR_LOG_ERROR(_logger, "Received status {}", (int)response.status); } - GetThreadLocalEventQueue().pushEvent(getServiceId()); + + co_return; + } + + Task sendRegexRequest() { + ICHOR_LOG_INFO(_logger, "sendRegexRequest"); + auto response = co_await _connectionService->sendAsync(HttpMethod::get, "/regex_test/one?param=123", {}, {}); + ICHOR_LOG_ERROR(_logger, "Received status {}", (int)response.status); co_return; } @@ -111,5 +127,5 @@ class UsingHttpService final : public AdvancedService { ILogger *_logger{}; ISerializer *_serializer{}; IHttpConnectionService *_connectionService{}; - std::unique_ptr _routeRegistration{}; + std::vector> _routeRegistrations{}; }; diff --git a/examples/http_example/main.cpp b/examples/http_example/main.cpp index 279bdd35..23fca8f1 100644 --- a/examples/http_example/main.cpp +++ b/examples/http_example/main.cpp @@ -1,11 +1,11 @@ #include "UsingHttpService.h" #include "../common/TestMsgGlazeSerializer.h" #include "../common/lyra.hpp" -#include +#include #include #include -#include -#include +#include +#include #include #include @@ -68,7 +68,7 @@ int main(int argc, char *argv[]) { } auto start = std::chrono::steady_clock::now(); - auto queue = std::make_unique(spinlock); + auto queue = std::make_unique(spinlock); auto &dm = queue->createManager(); #ifdef ICHOR_USE_SPDLOG dm.createServiceManager(); diff --git a/examples/http_ping_pong/PingMsgJsonSerializer.h b/examples/http_ping_pong/PingMsgJsonSerializer.h index 887af194..cad2431b 100644 --- a/examples/http_ping_pong/PingMsgJsonSerializer.h +++ b/examples/http_ping_pong/PingMsgJsonSerializer.h @@ -2,18 +2,7 @@ #include #include "PingMsg.h" - -// Glaze uses different conventions than Ichor, ignore them to prevent being spammed by warnings -#if defined( __GNUC__ ) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wsign-conversion" -# pragma GCC diagnostic ignored "-Wshadow" -# pragma GCC diagnostic ignored "-Wconversion" -#endif -#include -#if defined( __GNUC__ ) -# pragma GCC diagnostic pop -#endif +#include using namespace Ichor; @@ -33,14 +22,14 @@ class PingMsgJsonSerializer final : public ISerializer { buf.push_back('\0'); return buf; } - std::optional deserialize(std::vector &&stream) final { + tl::optional deserialize(std::vector &&stream) final { PingMsg msg; auto err = glz::read_json(msg, stream); if(err) { fmt::print("Glaze error {} at {}\n", (int)err.ec, err.location); fmt::print("json {}\n", (char*)stream.data()); - return std::nullopt; + return tl::nullopt; } return msg; diff --git a/examples/http_ping_pong/PingService.h b/examples/http_ping_pong/PingService.h index a8d065a0..bb4c3755 100644 --- a/examples/http_ping_pong/PingService.h +++ b/examples/http_ping_pong/PingService.h @@ -18,10 +18,10 @@ using namespace Ichor; class PingService final : public AdvancedService { public: PingService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true, Properties{{"LogLevel", Ichor::make_any(LogLevel::LOG_INFO)}}); // using customer properties here prevents us refactoring this class to constructor injection - reg.registerDependency>(this, true); - reg.registerDependency(this, true, getProperties()); // using getProperties here prevents us refactoring this class to constructor injection - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED, Properties{{"LogLevel", Ichor::make_any(LogLevel::LOG_INFO)}}); // using customer properties here prevents us refactoring this class to constructor injection + reg.registerDependency>(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::REQUIRED, getProperties()); // using getProperties here prevents us refactoring this class to constructor injection + reg.registerDependency(this, DependencyFlags::REQUIRED); } ~PingService() final = default; @@ -102,13 +102,13 @@ class PingService final : public AdvancedService { friend DependencyRegister; - Task> sendTestRequest(std::vector &&toSendMsg) { - std::vector headers{HttpHeader{"Content-Type", "application/json"}}; + Task> sendTestRequest(std::vector &&toSendMsg) { + unordered_map headers{{"Content-Type", "application/json"}}; auto response = co_await _connectionService->sendAsync(HttpMethod::post, "/ping", std::move(headers), std::move(toSendMsg)); if(_serializer == nullptr) { // we're stopping, gotta bail. - co_return std::optional{}; + co_return tl::optional{}; } if(response.status == HttpStatus::ok) { @@ -116,7 +116,7 @@ class PingService final : public AdvancedService { co_return msg; } else { ICHOR_LOG_ERROR(_logger, "Received status {}", (int)response.status); - co_return std::optional{}; + co_return tl::optional{}; } } diff --git a/examples/http_ping_pong/PongService.h b/examples/http_ping_pong/PongService.h index 7f834750..60f03023 100644 --- a/examples/http_ping_pong/PongService.h +++ b/examples/http_ping_pong/PongService.h @@ -17,7 +17,7 @@ class PongService final { ICHOR_LOG_INFO(_logger, "received request from {} with body {} ", req.address, std::string_view{reinterpret_cast(req.body.data()), req.body.size()}); auto msg = serializer->deserialize(std::move(req.body)); ICHOR_LOG_INFO(_logger, "received request from {} on route {} {} with PingMsg {}", req.address, (int) req.method, req.route, msg->sequence); - co_return HttpResponse{false, HttpStatus::ok, "application/json", serializer->serialize(PingMsg{msg->sequence}), {}}; + co_return HttpResponse{HttpStatus::ok, "application/json", serializer->serialize(PingMsg{msg->sequence}), {}}; }); } ~PongService() { diff --git a/examples/http_ping_pong/ping.cpp b/examples/http_ping_pong/ping.cpp index 81b9e605..9f7cdcac 100644 --- a/examples/http_ping_pong/ping.cpp +++ b/examples/http_ping_pong/ping.cpp @@ -1,10 +1,10 @@ #include "PingService.h" #include "PingMsgJsonSerializer.h" #include "../common/lyra.hpp" -#include +#include #include -#include -#include +#include +#include #include #include #include @@ -65,7 +65,7 @@ int main(int argc, char *argv[]) { } auto start = std::chrono::steady_clock::now(); - auto queue = std::make_unique(); + auto queue = std::make_unique(); auto &dm = queue->createManager(); #ifdef ICHOR_USE_SPDLOG diff --git a/examples/http_ping_pong/pong.cpp b/examples/http_ping_pong/pong.cpp index 8841c25e..dbda1d6d 100644 --- a/examples/http_ping_pong/pong.cpp +++ b/examples/http_ping_pong/pong.cpp @@ -1,10 +1,10 @@ #include "PongService.h" #include "PingMsgJsonSerializer.h" #include "../common/lyra.hpp" -#include +#include #include -#include -#include +#include +#include #include #include @@ -67,7 +67,7 @@ int main(int argc, char *argv[]) { } auto start = std::chrono::steady_clock::now(); - auto queue = std::make_unique(spinlock); + auto queue = std::make_unique(spinlock); auto &dm = queue->createManager(); #ifdef ICHOR_USE_SPDLOG diff --git a/examples/introspection_example/IntrospectionService.h b/examples/introspection_example/IntrospectionService.h new file mode 100644 index 00000000..a3248bf3 --- /dev/null +++ b/examples/introspection_example/IntrospectionService.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include +#include +#include + +using namespace Ichor; + +class IntrospectionService final { +public: + IntrospectionService(IService *self, IEventQueue *queue, DependencyManager *dm, ILogger *logger, ITimerFactory *) : _logger(logger) { + ICHOR_LOG_INFO(_logger, "IntrospectionService started"); + + auto svcs = dm->getAllServices(); + + for(auto &[svcId, svc] : svcs) { + ICHOR_LOG_INFO(_logger, "Service {}:{}, guid {} priority {} state {}", svc->getServiceId(), svc->getServiceName(), svc->getServiceGid(), svc->getServicePriority(), svc->getServiceState()); + + ICHOR_LOG_INFO(_logger, "\tInterfaces:"); + for(auto &iface : dm->getProvidedInterfacesForService(svc->getServiceId())) { + ICHOR_LOG_INFO(_logger, "\t\tInterface {} hash {}", iface.interfaceName, iface.interfaceNameHash); + } + + ICHOR_LOG_INFO(_logger, "\tProperties:"); + for(auto &[key, val] : svc->getProperties()) { + ICHOR_LOG_INFO(_logger, "\t\tProperty {} value {} size {} type {}", key, val, val.get_size(), val.type_name()); + } + + auto deps = dm->getDependencyRequestsForService(svc->getServiceId()); + ICHOR_LOG_INFO(_logger, "\tDependencies:"); + for(auto &dep : deps) { + ICHOR_LOG_INFO(_logger, "\t\tDependency {} flags {} satisfied {}", dep.interfaceName, dep.flags, dep.satisfied); + } + + auto dependants = dm->getDependentsForService(svc->getServiceId()); + ICHOR_LOG_INFO(_logger, "\tUsed by:"); + for(auto &dep : dependants) { + ICHOR_LOG_INFO(_logger, "\t\tDependant {}:{}", dep->getServiceId(), dep->getServiceName()); + } + + auto trackers = dm->getTrackersForService(svc->getServiceId()); + ICHOR_LOG_INFO(_logger, "\tTrackers:"); + for(auto &tracker : trackers) { + ICHOR_LOG_INFO(_logger, "\t\tTracker for interface {} hash {}", tracker.interfaceName, tracker.interfaceNameHash); + } + + ICHOR_LOG_INFO(_logger, ""); + + } + + // TODO set logger of other service + + queue->pushEvent(self->getServiceId()); + } + ~IntrospectionService() { + ICHOR_LOG_INFO(_logger, "IntrospectionService stopped"); + } + +private: + ILogger *_logger; +}; diff --git a/examples/introspection_example/README.md b/examples/introspection_example/README.md new file mode 100644 index 00000000..1c77b029 --- /dev/null +++ b/examples/introspection_example/README.md @@ -0,0 +1,81 @@ +# Introspection Example + +This example showcases Ichor's capability introspect services and properties, without knowing their type directly. Each service contains metadata about its Id/Guid, whether it is started, what its properties are and so on. + +Introspecting allows for monitoring and controlling individual services. In this example, all services are iterated and their metadata printed to stdout. It also shows how to change logger settings that belong to a specific service. + +Example output: + +```text +Service 1:Ichor::IEventQueue, guid 93319e7b-319e-45d0-b601-755ce135720b priority 1000 state INSTALLED + Interfaces: + Interface Ichor::IEventQueue hash 2671418589834563989 + Properties: + Dependencies: + Used by: + Dependant 5:IntrospectionService + Dependant 7:Ichor::TimerFactory + Trackers: + +Service 2:Ichor::DependencyManager, guid 13b34101-b4f9-4987-a58c-cce9e401b4ae priority 1000 state INSTALLED + Interfaces: + Interface Ichor::DependencyManager hash 6048060061124314661 + Properties: + Dependencies: + Used by: + Dependant 5:IntrospectionService + Trackers: + +Service 3:Ichor::LoggerFactory, guid ad5bd7a1-f94d-41eb-bd9e-756ae81a978b priority 1000 state ACTIVE + Interfaces: + Interface Ichor::ILoggerFactory hash 4064173856622543226 + Properties: + Property DefaultLogLevel value LOG_INFO size 8 type Ichor::LogLevel + Dependencies: + Dependency Ichor::IFrameworkLogger flags NONE satisfied 0 + Used by: + Trackers: + Tracker for interface Ichor::ILogger hash 13978885023924282811 + +Service 4:Ichor::TimerFactoryFactory, guid 01198fe5-2751-4d56-82a4-d2e25a45e3e1 priority 1000 state INJECTING + Interfaces: + Properties: + Dependencies: + Used by: + Trackers: + Tracker for interface Ichor::ITimerFactory hash 8128936943696194044 + +Service 5:IntrospectionService, guid ad91d65c-975d-4429-8137-719b0c8791ae priority 1000 state STARTING + Interfaces: + Properties: + Dependencies: + Dependency Ichor::ITimerFactory flags REQUIRED satisfied 1 + Dependency Ichor::ILogger flags REQUIRED satisfied 1 + Dependency Ichor::DependencyManager flags REQUIRED satisfied 1 + Dependency Ichor::IEventQueue flags REQUIRED satisfied 1 + Dependency Ichor::IService flags REQUIRED satisfied 1 + Used by: + Trackers: + +Service 6:Ichor::CoutLogger, guid 71bcde8e-b686-4b86-84f9-d3c3765278d3 priority 100 state ACTIVE + Interfaces: + Interface Ichor::ILogger hash 13978885023924282811 + Properties: + Property Filter value size 8 type Ichor::Filter + Property LogLevel value LOG_INFO size 8 type Ichor::LogLevel + Dependencies: + Used by: + Dependant 5:IntrospectionService + Trackers: + +Service 7:Ichor::TimerFactory, guid 5beeaf6a-12a3-49bb-a440-a432e7b0c01f priority 100 state ACTIVE + Interfaces: + Interface Ichor::ITimerFactory hash 8128936943696194044 + Properties: + Property requestingSvcId value 5 size 8 type unsigned long + Dependencies: + Dependency Ichor::IEventQueue flags REQUIRED satisfied 1 + Used by: + Dependant 5:IntrospectionService + Trackers: +``` diff --git a/examples/introspection_example/main.cpp b/examples/introspection_example/main.cpp new file mode 100644 index 00000000..474086cd --- /dev/null +++ b/examples/introspection_example/main.cpp @@ -0,0 +1,38 @@ +#include +#include +#include +#include "IntrospectionService.h" + +// Some compile time logic to instantiate a regular cout logger or to use the spdlog logger, if Ichor has been compiled with it. +#ifdef ICHOR_USE_SPDLOG +#include +#define LOGGER_TYPE SpdlogLogger +#else +#include +#define LOGGER_TYPE CoutLogger +#endif + +#include +#include + +using namespace Ichor; +using namespace std::string_literals; + +int main(int argc, char *argv[]) { + std::locale::global(std::locale("en_US.UTF-8")); + + auto start = std::chrono::steady_clock::now(); + auto queue = std::make_unique(); + auto &dm = queue->createManager(); +#ifdef ICHOR_USE_SPDLOG + dm.createServiceManager(); +#endif + dm.createServiceManager, ILoggerFactory>(Properties{{"DefaultLogLevel", Ichor::make_any(LogLevel::LOG_INFO)}}); + dm.createServiceManager(); + dm.createServiceManager(); + queue->start(CaptureSigInt); + auto end = std::chrono::steady_clock::now(); + fmt::print("{} ran for {:L} µs\n", argv[0], std::chrono::duration_cast(end-start).count()); + + return 0; +} diff --git a/examples/minimal_example/main.cpp b/examples/minimal_example/main.cpp index c33cd34f..5e12db4f 100644 --- a/examples/minimal_example/main.cpp +++ b/examples/minimal_example/main.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -38,7 +38,7 @@ class SigIntService final { int main(int argc, char *argv[]) { std::locale::global(std::locale("en_US.UTF-8")); // some loggers require having a locale - auto queue = std::make_unique(); + auto queue = std::make_unique(); auto &dm = queue->createManager(); dm.createServiceManager(); diff --git a/examples/multithreaded_example/CustomEvent.h b/examples/multithreaded_example/CustomEvent.h index f4a46350..d683e1bc 100644 --- a/examples/multithreaded_example/CustomEvent.h +++ b/examples/multithreaded_example/CustomEvent.h @@ -5,10 +5,17 @@ namespace Ichor { struct CustomEvent final : public Event { explicit CustomEvent(uint64_t _id, uint64_t _originatingService, uint64_t _priority) noexcept : - Event(TYPE, NAME, _id, _originatingService, _priority) {} + Event(_id, _originatingService, _priority) {} ~CustomEvent() final = default; - static constexpr uint64_t TYPE = typeNameHash(); + [[nodiscard]] std::string_view get_name() const noexcept final { + return NAME; + } + [[nodiscard]] NameHashType get_type() const noexcept final { + return TYPE; + } + + static constexpr NameHashType TYPE = typeNameHash(); static constexpr std::string_view NAME = typeName(); }; -} \ No newline at end of file +} diff --git a/examples/multithreaded_example/main.cpp b/examples/multithreaded_example/main.cpp index 2a7cca67..431f4edb 100644 --- a/examples/multithreaded_example/main.cpp +++ b/examples/multithreaded_example/main.cpp @@ -1,6 +1,6 @@ #include "OneService.h" #include "OtherService.h" -#include +#include #include // Some compile time logic to instantiate a regular cout logger or to use the spdlog logger, if Ichor has been compiled with it. @@ -23,9 +23,9 @@ int main(int argc, char *argv[]) { auto start = std::chrono::steady_clock::now(); CommunicationChannel channel{}; - auto queueOne = std::make_unique(); + auto queueOne = std::make_unique(); auto &dmOne = queueOne->createManager(); - auto queueTwo = std::make_unique(); + auto queueTwo = std::make_unique(); auto &dmTwo = queueTwo->createManager(); channel.addManager(&dmOne); channel.addManager(&dmTwo); diff --git a/examples/optional_dependency_example/OptionalService.h b/examples/optional_dependency_example/OptionalService.h index 667c9453..7bb1d913 100644 --- a/examples/optional_dependency_example/OptionalService.h +++ b/examples/optional_dependency_example/OptionalService.h @@ -13,7 +13,7 @@ struct IOptionalService { class OptionalService final : public IOptionalService, public AdvancedService { public: OptionalService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); } ~OptionalService() final = default; diff --git a/examples/optional_dependency_example/TestService.h b/examples/optional_dependency_example/TestService.h index 4205beef..0b3840ef 100644 --- a/examples/optional_dependency_example/TestService.h +++ b/examples/optional_dependency_example/TestService.h @@ -10,14 +10,14 @@ using namespace Ichor; class TestService final : public AdvancedService { public: TestService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); - reg.registerDependency(this, false); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::ALLOW_MULTIPLE); } ~TestService() final = default; private: Task> start() final { - ICHOR_LOG_INFO(_logger, "TestService started with dependency"); + ICHOR_LOG_INFO(_logger, "TestService {} started with dependency", getServiceId()); _started = true; if(_injectionCount == 2) { GetThreadLocalEventQueue().pushEvent(getServiceId()); diff --git a/examples/optional_dependency_example/main.cpp b/examples/optional_dependency_example/main.cpp index 97eb1510..730cc154 100644 --- a/examples/optional_dependency_example/main.cpp +++ b/examples/optional_dependency_example/main.cpp @@ -1,6 +1,6 @@ #include "TestService.h" #include "OptionalService.h" -#include +#include #include // Some compile time logic to instantiate a regular cout logger or to use the spdlog logger, if Ichor has been compiled with it. @@ -21,7 +21,7 @@ int main(int argc, char *argv[]) { std::locale::global(std::locale("en_US.UTF-8")); auto start = std::chrono::steady_clock::now(); - auto queue = std::make_unique(); + auto queue = std::make_unique(); auto &dm = queue->createManager(); #ifdef ICHOR_USE_SPDLOG dm.createServiceManager(); diff --git a/examples/realtime_example/GlobalRealtimeSettings.cpp b/examples/realtime_example/GlobalRealtimeSettings.cpp index 8db6a2c2..a8e701df 100644 --- a/examples/realtime_example/GlobalRealtimeSettings.cpp +++ b/examples/realtime_example/GlobalRealtimeSettings.cpp @@ -73,6 +73,7 @@ GlobalRealtimeSettings::GlobalRealtimeSettings() { } } } else { + reenable_smt = false; fmt::print("SMT missing\n"); } @@ -80,6 +81,7 @@ GlobalRealtimeSettings::GlobalRealtimeSettings() { if(setpriority(PRIO_PROCESS, 0, -20) != 0) { fmt::print("setpriority failed\n"); } + fmt::print("finished settings\n"); #endif } diff --git a/examples/realtime_example/OptionalService.h b/examples/realtime_example/OptionalService.h index 667c9453..7bb1d913 100644 --- a/examples/realtime_example/OptionalService.h +++ b/examples/realtime_example/OptionalService.h @@ -13,7 +13,7 @@ struct IOptionalService { class OptionalService final : public IOptionalService, public AdvancedService { public: OptionalService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); + reg.registerDependency(this, DependencyFlags::REQUIRED); } ~OptionalService() final = default; diff --git a/examples/realtime_example/TestService.h b/examples/realtime_example/TestService.h index 870e7dc9..77b24870 100644 --- a/examples/realtime_example/TestService.h +++ b/examples/realtime_example/TestService.h @@ -18,24 +18,31 @@ using namespace Ichor; #endif struct ExecuteTaskEvent final : public Event { - ExecuteTaskEvent(uint64_t _id, uint64_t _originatingService, uint64_t _priority) noexcept : Event(TYPE, NAME, _id, _originatingService, _priority) {} + ExecuteTaskEvent(uint64_t _id, uint64_t _originatingService, uint64_t _priority) noexcept : Event(_id, _originatingService, _priority) {} ~ExecuteTaskEvent() final = default; - static constexpr uint64_t TYPE = typeNameHash(); + [[nodiscard]] std::string_view get_name() const noexcept final { + return NAME; + } + [[nodiscard]] NameHashType get_type() const noexcept final { + return TYPE; + } + + static constexpr NameHashType TYPE = typeNameHash(); static constexpr std::string_view NAME = typeName(); }; class TestService final : public AdvancedService { public: TestService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); - reg.registerDependency(this, false); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::ALLOW_MULTIPLE); } ~TestService() final = default; private: Task> start() final { - ICHOR_LOG_INFO(_logger, "TestService started with dependency"); + ICHOR_LOG_INFO(_logger, "TestService started with dependency {}", _injectionCount); _started = true; _eventHandlerRegistration = GetThreadLocalManager().registerEventHandler(this, this); if(_injectionCount == 2) { @@ -61,7 +68,7 @@ class TestService final : public AdvancedService { } void addDependencyInstance(IOptionalService&, IService &isvc) { - ICHOR_LOG_INFO(_logger, "Inserted IOptionalService svcid {}", isvc.getServiceId()); + ICHOR_LOG_INFO(_logger, "Inserted IOptionalService svcid {} {}", isvc.getServiceId(), _injectionCount); _injectionCount++; if(_started && _injectionCount == 2) { diff --git a/examples/realtime_example/main.cpp b/examples/realtime_example/main.cpp index 6db67c72..29f98f3c 100644 --- a/examples/realtime_example/main.cpp +++ b/examples/realtime_example/main.cpp @@ -1,6 +1,6 @@ #include "TestService.h" #include "OptionalService.h" -#include +#include #include #include "GlobalRealtimeSettings.h" @@ -34,7 +34,7 @@ void* run_example(void*) { #endif { - auto queue = std::make_unique(); + auto queue = std::make_unique(); auto &dm = queue->createManager(); dm.createServiceManager, ILoggerFactory>(Properties{{"DefaultLogLevel", Ichor::make_any(LogLevel::LOG_INFO)}}); dm.createServiceManager(); diff --git a/examples/serializer_example/README.md b/examples/serializer_example/README.md index 772397f1..62e2287a 100644 --- a/examples/serializer_example/README.md +++ b/examples/serializer_example/README.md @@ -17,7 +17,7 @@ public: return std::vector{}; } - std::optional deserialize(std::vector &&stream) final { + tl::optional deserialize(std::vector &&stream) final { // deserialize and return a MyMsg with the right id return MyMsg{}; } diff --git a/examples/serializer_example/main.cpp b/examples/serializer_example/main.cpp index 0a2cb698..f20300c2 100644 --- a/examples/serializer_example/main.cpp +++ b/examples/serializer_example/main.cpp @@ -1,6 +1,6 @@ #include "TestService.h" #include "../common/TestMsgGlazeSerializer.h" -#include +#include #include #include @@ -20,7 +20,7 @@ int main(int argc, char *argv[]) { std::locale::global(std::locale("en_US.UTF-8")); auto start = std::chrono::steady_clock::now(); - auto queue = std::make_unique(); + auto queue = std::make_unique(); auto &dm = queue->createManager(); #ifdef ICHOR_USE_SPDLOG dm.createServiceManager(); diff --git a/examples/tcp_example/UsingTcpService.h b/examples/tcp_example/UsingTcpService.h index e267d30d..5cf05f18 100644 --- a/examples/tcp_example/UsingTcpService.h +++ b/examples/tcp_example/UsingTcpService.h @@ -15,9 +15,9 @@ using namespace Ichor; class UsingTcpService final : public AdvancedService { public: UsingTcpService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); - reg.registerDependency>(this, true); - reg.registerDependency(this, true, getProperties()); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency>(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::REQUIRED, getProperties()); } ~UsingTcpService() final = default; @@ -26,7 +26,10 @@ class UsingTcpService final : public AdvancedService { ICHOR_LOG_INFO(_logger, "UsingTcpService started"); _dataEventRegistration = GetThreadLocalManager().registerEventHandler(this, this); _failureEventRegistration = GetThreadLocalManager().registerEventHandler(this, this); - _connectionService->sendAsync(_serializer->serialize(TestMsg{11, "hello"})); + auto ret = _connectionService->sendAsync(_serializer->serialize(TestMsg{11, "hello"})); + if(!ret) { + ICHOR_LOG_ERROR(_logger, "start() send error: {}", (int)ret.error()); + } co_return {}; } @@ -74,7 +77,10 @@ class UsingTcpService final : public AdvancedService { AsyncGenerator handleEvent(FailedSendMessageEvent const &evt) { ICHOR_LOG_INFO(_logger, "Failed to send message id {}, retrying", evt.msgId); - _connectionService->sendAsync(std::move(evt.data)); + auto ret = _connectionService->sendAsync(std::move(evt.data)); + if(!ret) { + ICHOR_LOG_ERROR(_logger, "handleEvent() send error: {}", (int)ret.error()); + } co_return {}; } diff --git a/examples/tcp_example/main.cpp b/examples/tcp_example/main.cpp index c5eaa54c..27ee7ff1 100644 --- a/examples/tcp_example/main.cpp +++ b/examples/tcp_example/main.cpp @@ -1,6 +1,6 @@ #include "UsingTcpService.h" #include "../common/TestMsgGlazeSerializer.h" -#include +#include #include #include #include @@ -25,16 +25,19 @@ int main(int argc, char *argv[]) { std::locale::global(std::locale("en_US.UTF-8")); auto start = std::chrono::steady_clock::now(); - auto queue = std::make_unique(); + auto queue = std::make_unique(); auto &dm = queue->createManager(); + + uint64_t priorityToEnsureHostStartingFirst = 51; + #ifdef ICHOR_USE_SPDLOG - dm.createServiceManager(); + dm.createServiceManager(Properties{}, priorityToEnsureHostStartingFirst); #endif - dm.createServiceManager, ILoggerFactory>(Properties{{"DefaultLogLevel", Ichor::make_any(LogLevel::LOG_INFO)}}); + dm.createServiceManager, ILoggerFactory>(Properties{{"DefaultLogLevel", Ichor::make_any(LogLevel::LOG_INFO)}}, priorityToEnsureHostStartingFirst); dm.createServiceManager>(); - dm.createServiceManager(Properties{{"Address", Ichor::make_any("127.0.0.1"s)}, {"Port", Ichor::make_any(static_cast(8001))}}); + dm.createServiceManager(Properties{{"Address", Ichor::make_any("127.0.0.1"s)}, {"Port", Ichor::make_any(static_cast(8001))}}, priorityToEnsureHostStartingFirst); dm.createServiceManager, IClientFactory>(); - dm.createServiceManager(); + dm.createServiceManager(Properties{}, priorityToEnsureHostStartingFirst); dm.createServiceManager(Properties{{"Address", Ichor::make_any("127.0.0.1"s)}, {"Port", Ichor::make_any(static_cast(8001))}}); queue->start(CaptureSigInt); auto end = std::chrono::steady_clock::now(); diff --git a/examples/timer_example/main.cpp b/examples/timer_example/main.cpp index 18d11841..bba97fbf 100644 --- a/examples/timer_example/main.cpp +++ b/examples/timer_example/main.cpp @@ -1,5 +1,5 @@ #include "UsingTimerService.h" -#include +#include #include #include @@ -21,7 +21,7 @@ int main(int argc, char *argv[]) { std::locale::global(std::locale("en_US.UTF-8")); auto start = std::chrono::steady_clock::now(); - auto queue = std::make_unique(); + auto queue = std::make_unique(); auto &dm = queue->createManager(); #ifdef ICHOR_USE_SPDLOG dm.createServiceManager(); diff --git a/examples/websocket_example/README.md b/examples/websocket_example/README.md index 678cf89d..e52402b2 100644 --- a/examples/websocket_example/README.md +++ b/examples/websocket_example/README.md @@ -6,7 +6,7 @@ This demonstrates the following concepts: * Defining and using serializers * How to set up a websocket server * How to set up a websocket client using the `ClientFactory` -* Using the advanced parameter `Spinlock` in the `MultimapQueue`. +* Using the advanced parameter `Spinlock` in the `PriorityQueue`. ## Command Line Arguments diff --git a/examples/websocket_example/UsingWsService.h b/examples/websocket_example/UsingWsService.h index 4500b988..178f9239 100644 --- a/examples/websocket_example/UsingWsService.h +++ b/examples/websocket_example/UsingWsService.h @@ -15,9 +15,9 @@ using namespace Ichor; class UsingWsService final : public AdvancedService { public: UsingWsService(DependencyRegister ®, Properties props) : AdvancedService(std::move(props)) { - reg.registerDependency(this, true); - reg.registerDependency>(this, true); - reg.registerDependency(this, true, getProperties()); + reg.registerDependency(this, DependencyFlags::REQUIRED); + reg.registerDependency>(this, DependencyFlags::REQUIRED); + reg.registerDependency(this, DependencyFlags::REQUIRED, getProperties()); } ~UsingWsService() final = default; @@ -26,7 +26,11 @@ class UsingWsService final : public AdvancedService { ICHOR_LOG_INFO(_logger, "UsingWsService started"); _dataEventRegistration = GetThreadLocalManager().registerEventHandler(this, this); _failureEventRegistration = GetThreadLocalManager().registerEventHandler(this, this); - _connectionService->sendAsync(_serializer->serialize(TestMsg{11, "hello"})); + auto ret = _connectionService->sendAsync(_serializer->serialize(TestMsg{11, "hello"})); + if(!ret) { + ICHOR_LOG_ERROR(_logger, "start() send error: {}", (int)ret.error()); + } + co_return {}; } @@ -80,7 +84,10 @@ class UsingWsService final : public AdvancedService { AsyncGenerator handleEvent(FailedSendMessageEvent const &evt) { ICHOR_LOG_INFO(_logger, "Failed to send message id {}, retrying", evt.msgId); - _connectionService->sendAsync(std::move(evt.data)); + auto ret = _connectionService->sendAsync(std::move(evt.data)); + if(!ret) { + ICHOR_LOG_ERROR(_logger, "handleEvent() send error: {}", (int)ret.error()); + } co_return {}; } diff --git a/examples/websocket_example/main.cpp b/examples/websocket_example/main.cpp index 5e922e38..ee959bd0 100644 --- a/examples/websocket_example/main.cpp +++ b/examples/websocket_example/main.cpp @@ -2,10 +2,10 @@ #include "../common/TestMsgGlazeSerializer.h" #include "../common/lyra.hpp" #include -#include +#include #include -#include -#include +#include +#include #include #include @@ -67,7 +67,7 @@ int main(int argc, char *argv[]) { } auto start = std::chrono::steady_clock::now(); - auto queue = std::make_unique(spinlock); + auto queue = std::make_unique(spinlock); auto &dm = queue->createManager(); #ifdef ICHOR_USE_SPDLOG dm.createServiceManager(); diff --git a/examples/yielding_timer_example/main.cpp b/examples/yielding_timer_example/main.cpp index 297281da..7d737d06 100644 --- a/examples/yielding_timer_example/main.cpp +++ b/examples/yielding_timer_example/main.cpp @@ -1,5 +1,5 @@ #include "UsingTimerService.h" -#include +#include #include #include @@ -21,7 +21,7 @@ int main(int argc, char *argv[]) { std::locale::global(std::locale("en_US.UTF-8")); auto start = std::chrono::steady_clock::now(); - auto queue = std::make_unique(); + auto queue = std::make_unique(); auto &dm = queue->createManager(); #ifdef ICHOR_USE_SPDLOG dm.createServiceManager(); diff --git a/external/Catch2 b/external/Catch2 index 5df88da1..05e10dfc 160000 --- a/external/Catch2 +++ b/external/Catch2 @@ -1 +1 @@ -Subproject commit 5df88da16e276f853cc0c45f4b570419be77dd43 +Subproject commit 05e10dfccc28c7f973727c54f850237d07d5e10f diff --git a/external/fmt b/external/fmt index a3370119..e69e5f97 160000 --- a/external/fmt +++ b/external/fmt @@ -1 +1 @@ -Subproject commit a33701196adfad74917046096bf5a2aa0ab0bb50 +Subproject commit e69e5f977d458f2650bb346dadf2ad30c5320281 diff --git a/external/glaze b/external/glaze index efed8237..031db00e 160000 --- a/external/glaze +++ b/external/glaze @@ -1 +1 @@ -Subproject commit efed823718cf388a148a197599b7ba97893b348b +Subproject commit 031db00e852fb36e6a8f3125276860b890395fe1 diff --git a/external/mimalloc b/external/mimalloc index f2712f4a..23e89dac 160000 --- a/external/mimalloc +++ b/external/mimalloc @@ -1 +1 @@ -Subproject commit f2712f4a8f038a7fb4df2790f4c3b7e3ed9e219b +Subproject commit 23e89dacb4634b095c5a7c23158e7a5e3de38d90 diff --git a/external/spdlog b/external/spdlog index 76fb40d9..7c02e204 160000 --- a/external/spdlog +++ b/external/spdlog @@ -1 +1 @@ -Subproject commit 76fb40d95455f249bd70824ecfcae7a8f0930fa3 +Subproject commit 7c02e204c92545f869e2f04edaab1f19fe8b19fd diff --git a/include/ankerl/unordered_dense.h b/include/ankerl/unordered_dense.h new file mode 100644 index 00000000..8f17f657 --- /dev/null +++ b/include/ankerl/unordered_dense.h @@ -0,0 +1,2032 @@ +///////////////////////// ankerl::unordered_dense::{map, set} ///////////////////////// + +// A fast & densely stored hashmap and hashset based on robin-hood backward shift deletion. +// Version 4.4.0 +// https://github.com/martinus/unordered_dense +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2022-2023 Martin Leitner-Ankerl +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef ANKERL_UNORDERED_DENSE_H +#define ANKERL_UNORDERED_DENSE_H + +// see https://semver.org/spec/v2.0.0.html +#define ANKERL_UNORDERED_DENSE_VERSION_MAJOR 4 // NOLINT(cppcoreguidelines-macro-usage) incompatible API changes +#define ANKERL_UNORDERED_DENSE_VERSION_MINOR 4 // NOLINT(cppcoreguidelines-macro-usage) backwards compatible functionality +#define ANKERL_UNORDERED_DENSE_VERSION_PATCH 0 // NOLINT(cppcoreguidelines-macro-usage) backwards compatible bug fixes + +// API versioning with inline namespace, see https://www.foonathan.net/2018/11/inline-namespaces/ + +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define ANKERL_UNORDERED_DENSE_VERSION_CONCAT1(major, minor, patch) v##major##_##minor##_##patch +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define ANKERL_UNORDERED_DENSE_VERSION_CONCAT(major, minor, patch) ANKERL_UNORDERED_DENSE_VERSION_CONCAT1(major, minor, patch) +#define ANKERL_UNORDERED_DENSE_NAMESPACE \ + ANKERL_UNORDERED_DENSE_VERSION_CONCAT( \ + ANKERL_UNORDERED_DENSE_VERSION_MAJOR, ANKERL_UNORDERED_DENSE_VERSION_MINOR, ANKERL_UNORDERED_DENSE_VERSION_PATCH) + +#if defined(_MSVC_LANG) +# define ANKERL_UNORDERED_DENSE_CPP_VERSION _MSVC_LANG +#else +# define ANKERL_UNORDERED_DENSE_CPP_VERSION __cplusplus +#endif + +#if defined(__GNUC__) +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +# define ANKERL_UNORDERED_DENSE_PACK(decl) decl __attribute__((__packed__)) +#elif defined(_MSC_VER) +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +# define ANKERL_UNORDERED_DENSE_PACK(decl) __pragma(pack(push, 1)) decl __pragma(pack(pop)) +#endif + +// exceptions +#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND) +# define ANKERL_UNORDERED_DENSE_HAS_EXCEPTIONS() 1 // NOLINT(cppcoreguidelines-macro-usage) +#else +# define ANKERL_UNORDERED_DENSE_HAS_EXCEPTIONS() 0 // NOLINT(cppcoreguidelines-macro-usage) +#endif +#ifdef _MSC_VER +# define ANKERL_UNORDERED_DENSE_NOINLINE __declspec(noinline) +#else +# define ANKERL_UNORDERED_DENSE_NOINLINE __attribute__((noinline)) +#endif + +// defined in unordered_dense.cpp +#if !defined(ANKERL_UNORDERED_DENSE_EXPORT) +# define ANKERL_UNORDERED_DENSE_EXPORT +#endif + +#if ANKERL_UNORDERED_DENSE_CPP_VERSION < 201703L +# error ankerl::unordered_dense requires C++17 or higher +#else +# include // for array +# include // for uint64_t, uint32_t, uint8_t, UINT64_C +# include // for size_t, memcpy, memset +# include // for equal_to, hash +# include // for initializer_list +# include // for pair, distance +# include // for numeric_limits +# include // for allocator, allocator_traits, shared_ptr +# include // for optional +# include // for out_of_range +# include // for basic_string +# include // for basic_string_view, hash +# include // for forward_as_tuple +# include // for enable_if_t, declval, conditional_t, ena... +# include // for forward, exchange, pair, as_const, piece... +# include // for vector +# if ANKERL_UNORDERED_DENSE_HAS_EXCEPTIONS() == 0 +# include // for abort +# endif + +# if defined(__has_include) +# if __has_include() +# define ANKERL_UNORDERED_DENSE_PMR std::pmr // NOLINT(cppcoreguidelines-macro-usage) +# include // for polymorphic_allocator +# elif __has_include() +# define ANKERL_UNORDERED_DENSE_PMR std::experimental::pmr // NOLINT(cppcoreguidelines-macro-usage) +# include // for polymorphic_allocator +# endif +# endif + +# if defined(_MSC_VER) && defined(_M_X64) +# include +# pragma intrinsic(_umul128) +# endif + +# if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__) +# define ANKERL_UNORDERED_DENSE_LIKELY(x) __builtin_expect(x, 1) // NOLINT(cppcoreguidelines-macro-usage) +# define ANKERL_UNORDERED_DENSE_UNLIKELY(x) __builtin_expect(x, 0) // NOLINT(cppcoreguidelines-macro-usage) +# else +# define ANKERL_UNORDERED_DENSE_LIKELY(x) (x) // NOLINT(cppcoreguidelines-macro-usage) +# define ANKERL_UNORDERED_DENSE_UNLIKELY(x) (x) // NOLINT(cppcoreguidelines-macro-usage) +# endif + +namespace ankerl::unordered_dense { + inline namespace ANKERL_UNORDERED_DENSE_NAMESPACE { + + namespace detail { + +# if ANKERL_UNORDERED_DENSE_HAS_EXCEPTIONS() + +// make sure this is not inlined as it is slow and dramatically enlarges code, thus making other +// inlinings more difficult. Throws are also generally the slow path. + [[noreturn]] inline ANKERL_UNORDERED_DENSE_NOINLINE void on_error_key_not_found() { + throw std::out_of_range("ankerl::unordered_dense::map::at(): key not found"); + } + [[noreturn]] inline ANKERL_UNORDERED_DENSE_NOINLINE void on_error_bucket_overflow() { + throw std::overflow_error("ankerl::unordered_dense: reached max bucket size, cannot increase size"); + } + [[noreturn]] inline ANKERL_UNORDERED_DENSE_NOINLINE void on_error_too_many_elements() { + throw std::out_of_range("ankerl::unordered_dense::map::replace(): too many elements"); + } + +# else + + [[noreturn]] inline void on_error_key_not_found() { + abort(); +} +[[noreturn]] inline void on_error_bucket_overflow() { + abort(); +} +[[noreturn]] inline void on_error_too_many_elements() { + abort(); +} + +# endif + + } // namespace detail + +// hash /////////////////////////////////////////////////////////////////////// + +// This is a stripped-down implementation of wyhash: https://github.com/wangyi-fudan/wyhash +// No big-endian support (because different values on different machines don't matter), +// hardcodes seed and the secret, reformats the code, and clang-tidy fixes. + namespace detail::wyhash { + + inline void mum(uint64_t* a, uint64_t* b) { +# if defined(__SIZEOF_INT128__) + __uint128_t r = *a; + r *= *b; + *a = static_cast(r); + *b = static_cast(r >> 64U); +# elif defined(_MSC_VER) && defined(_M_X64) + *a = _umul128(*a, *b, b); +# else + uint64_t ha = *a >> 32U; + uint64_t hb = *b >> 32U; + uint64_t la = static_cast(*a); + uint64_t lb = static_cast(*b); + uint64_t hi{}; + uint64_t lo{}; + uint64_t rh = ha * hb; + uint64_t rm0 = ha * lb; + uint64_t rm1 = hb * la; + uint64_t rl = la * lb; + uint64_t t = rl + (rm0 << 32U); + auto c = static_cast(t < rl); + lo = t + (rm1 << 32U); + c += static_cast(lo < t); + hi = rh + (rm0 >> 32U) + (rm1 >> 32U) + c; + *a = lo; + *b = hi; +# endif + } + +// multiply and xor mix function, aka MUM + [[nodiscard]] inline auto mix(uint64_t a, uint64_t b) -> uint64_t { + mum(&a, &b); + return a ^ b; + } + +// read functions. WARNING: we don't care about endianness, so results are different on big endian! + [[nodiscard]] inline auto r8(const uint8_t* p) -> uint64_t { + uint64_t v{}; + std::memcpy(&v, p, 8U); + return v; + } + + [[nodiscard]] inline auto r4(const uint8_t* p) -> uint64_t { + uint32_t v{}; + std::memcpy(&v, p, 4); + return v; + } + +// reads 1, 2, or 3 bytes + [[nodiscard]] inline auto r3(const uint8_t* p, size_t k) -> uint64_t { + return (static_cast(p[0]) << 16U) | (static_cast(p[k >> 1U]) << 8U) | p[k - 1]; + } + + [[maybe_unused]] [[nodiscard]] inline auto hash(void const* key, size_t len) -> uint64_t { + static constexpr auto secret = std::array{UINT64_C(0xa0761d6478bd642f), + UINT64_C(0xe7037ed1a0b428db), + UINT64_C(0x8ebc6af09c88c6e3), + UINT64_C(0x589965cc75374cc3)}; + + auto const* p = static_cast(key); + uint64_t seed = secret[0]; + uint64_t a{}; + uint64_t b{}; + if (ANKERL_UNORDERED_DENSE_LIKELY(len <= 16)) { + if (ANKERL_UNORDERED_DENSE_LIKELY(len >= 4)) { + a = (r4(p) << 32U) | r4(p + ((len >> 3U) << 2U)); + b = (r4(p + len - 4) << 32U) | r4(p + len - 4 - ((len >> 3U) << 2U)); + } else if (ANKERL_UNORDERED_DENSE_LIKELY(len > 0)) { + a = r3(p, len); + b = 0; + } else { + a = 0; + b = 0; + } + } else { + size_t i = len; + if (ANKERL_UNORDERED_DENSE_UNLIKELY(i > 48)) { + uint64_t see1 = seed; + uint64_t see2 = seed; + do { + seed = mix(r8(p) ^ secret[1], r8(p + 8) ^ seed); + see1 = mix(r8(p + 16) ^ secret[2], r8(p + 24) ^ see1); + see2 = mix(r8(p + 32) ^ secret[3], r8(p + 40) ^ see2); + p += 48; + i -= 48; + } while (ANKERL_UNORDERED_DENSE_LIKELY(i > 48)); + seed ^= see1 ^ see2; + } + while (ANKERL_UNORDERED_DENSE_UNLIKELY(i > 16)) { + seed = mix(r8(p) ^ secret[1], r8(p + 8) ^ seed); + i -= 16; + p += 16; + } + a = r8(p + i - 16); + b = r8(p + i - 8); + } + + return mix(secret[1] ^ len, mix(a ^ secret[1], b ^ seed)); + } + + [[nodiscard]] inline auto hash(uint64_t x) -> uint64_t { + return detail::wyhash::mix(x, UINT64_C(0x9E3779B97F4A7C15)); + } + + } // namespace detail::wyhash + + ANKERL_UNORDERED_DENSE_EXPORT template + struct hash { + auto operator()(T const& obj) const noexcept(noexcept(std::declval>().operator()(std::declval()))) + -> uint64_t { + return std::hash{}(obj); + } + }; + + template + struct hash> { + using is_avalanching = void; + auto operator()(std::basic_string const& str) const noexcept -> uint64_t { + return detail::wyhash::hash(str.data(), sizeof(CharT) * str.size()); + } + }; + + template + struct hash> { + using is_avalanching = void; + auto operator()(std::basic_string_view const& sv) const noexcept -> uint64_t { + return detail::wyhash::hash(sv.data(), sizeof(CharT) * sv.size()); + } + }; + + template + struct hash { + using is_avalanching = void; + auto operator()(T* ptr) const noexcept -> uint64_t { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + return detail::wyhash::hash(reinterpret_cast(ptr)); + } + }; + + template + struct hash> { + using is_avalanching = void; + auto operator()(std::unique_ptr const& ptr) const noexcept -> uint64_t { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + return detail::wyhash::hash(reinterpret_cast(ptr.get())); + } + }; + + template + struct hash> { + using is_avalanching = void; + auto operator()(std::shared_ptr const& ptr) const noexcept -> uint64_t { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + return detail::wyhash::hash(reinterpret_cast(ptr.get())); + } + }; + + template + struct hash::value>::type> { + using is_avalanching = void; + auto operator()(Enum e) const noexcept -> uint64_t { + using underlying = typename std::underlying_type_t; + return detail::wyhash::hash(static_cast(e)); + } + }; + + template + struct tuple_hash_helper { + // Converts the value into 64bit. If it is an integral type, just cast it. Mixing is doing the rest. + // If it isn't an integral we need to hash it. + template + [[nodiscard]] constexpr static auto to64(Arg const& arg) -> uint64_t { + if constexpr (std::is_integral_v || std::is_enum_v) { + return static_cast(arg); + } else { + return hash{}(arg); + } + } + + [[nodiscard]] static auto mix64(uint64_t state, uint64_t v) -> uint64_t { + return detail::wyhash::mix(state + v, uint64_t{0x9ddfea08eb382d69}); + } + + // Creates a buffer that holds all the data from each element of the tuple. If possible we memcpy the data directly. If + // not, we hash the object and use this for the array. Size of the array is known at compile time, and memcpy is optimized + // away, so filling the buffer is highly efficient. Finally, call wyhash with this buffer. + template + [[nodiscard]] static auto calc_hash(T const& t, std::index_sequence) noexcept -> uint64_t { + auto h = uint64_t{}; + ((h = mix64(h, to64(std::get(t)))), ...); + return h; + } + }; + + template + struct hash> : tuple_hash_helper { + using is_avalanching = void; + auto operator()(std::tuple const& t) const noexcept -> uint64_t { + return tuple_hash_helper::calc_hash(t, std::index_sequence_for{}); + } + }; + + template + struct hash> : tuple_hash_helper { + using is_avalanching = void; + auto operator()(std::pair const& t) const noexcept -> uint64_t { + return tuple_hash_helper::calc_hash(t, std::index_sequence_for{}); + } + }; + +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +# define ANKERL_UNORDERED_DENSE_HASH_STATICCAST(T) \ + template <> \ + struct hash { \ + using is_avalanching = void; \ + auto operator()(T const& obj) const noexcept -> uint64_t { \ + return detail::wyhash::hash(static_cast(obj)); \ + } \ + } + +# if defined(__GNUC__) && !defined(__clang__) + # pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wuseless-cast" +# endif +// see https://en.cppreference.com/w/cpp/utility/hash + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(bool); + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char); + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(signed char); + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned char); +# if ANKERL_UNORDERED_DENSE_CPP_VERSION >= 202002L && defined(__cpp_char8_t) + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char8_t); +# endif + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char16_t); + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(char32_t); + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(wchar_t); + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(short); + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned short); + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(int); + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned int); + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(long); + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(long long); + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned long); + ANKERL_UNORDERED_DENSE_HASH_STATICCAST(unsigned long long); + +# if defined(__GNUC__) && !defined(__clang__) +# pragma GCC diagnostic pop +# endif + +// bucket_type ////////////////////////////////////////////////////////// + + namespace bucket_type { + + struct standard { + static constexpr uint32_t dist_inc = 1U << 8U; // skip 1 byte fingerprint + static constexpr uint32_t fingerprint_mask = dist_inc - 1; // mask for 1 byte of fingerprint + + uint32_t m_dist_and_fingerprint; // upper 3 byte: distance to original bucket. lower byte: fingerprint from hash + uint32_t m_value_idx; // index into the m_values vector. + }; + + ANKERL_UNORDERED_DENSE_PACK(struct big { + static constexpr uint32_t dist_inc = 1U << 8U; // skip 1 byte fingerprint + static constexpr uint32_t fingerprint_mask = dist_inc - 1; // mask for 1 byte of fingerprint + + uint32_t m_dist_and_fingerprint; // upper 3 byte: distance to original bucket. lower byte: fingerprint from hash + size_t m_value_idx; // index into the m_values vector. + }); + + } // namespace bucket_type + + namespace detail { + + struct nonesuch {}; + + template class Op, class... Args> + struct detector { + using value_t = std::false_type; + using type = Default; + }; + + template class Op, class... Args> + struct detector>, Op, Args...> { + using value_t = std::true_type; + using type = Op; + }; + + template