├── .clang-format ├── .clangd ├── .devcontainer ├── alpine │ └── devcontainer.json ├── fedora │ └── devcontainer.json ├── post-create.sh ├── ubuntu22 │ └── devcontainer.json └── ubuntu24 │ └── devcontainer.json ├── .dockerignore ├── .github └── workflows │ ├── ci.yml │ ├── docker-release.yml │ └── mac-os.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .pre-commit-hooks.yaml ├── .vscode └── c_cpp_properties.json ├── CMakeLists.txt ├── LICENSE ├── README.md ├── base ├── CMakeLists.txt ├── LICENSE.base ├── ProducerConsumerQueue.h ├── RWSpinLock.h ├── abseil_test.cc ├── aquahash.h ├── bits.h ├── cpu_features.cc ├── cpu_features.h ├── cuckoo_map-internal.h ├── cuckoo_map.cc ├── cuckoo_map.h ├── cuckoo_map_test.cc ├── cxx_test.cc ├── cycle_clock.h ├── endian.h ├── expected.hpp ├── flags.h ├── flit.h ├── flit_test.cc ├── function2.hpp ├── gtest.h ├── gtest_main.cc ├── hash.cc ├── hash.h ├── hash_test.cc ├── histogram.cc ├── histogram.h ├── histogram_test.cc ├── init.cc ├── init.h ├── integral_types.h ├── io_buf.cc ├── io_buf.h ├── iterator.h ├── libdivide.h ├── logging.cc ├── logging.h ├── malloc_test.cc ├── mpmc_bounded_queue.h ├── mpmc_bounded_queue_test.cc ├── mpsc_intrusive_queue.h ├── mpsc_intrusive_queue_test.cc ├── pmr │ ├── CMakeLists.txt │ ├── arena.cc │ ├── arena.h │ ├── arena_test.cc │ ├── memory_resource.h │ ├── pod_array.h │ └── pod_array_test.cc ├── pod_array.h ├── port.h ├── proc_util.cc ├── proc_util.h ├── pthread_utils.cc ├── pthread_utils.h ├── random.h ├── ring_buffer.h ├── ring_buffer_test.cc ├── segment_pool.cc ├── segment_pool.h ├── spinlock.h ├── sse2neon.h ├── sse2rvv.h ├── stl_util.h ├── string_view_sso.h ├── string_view_sso_test.cc ├── type_traits.h ├── varz_node.cc ├── varz_node.h ├── varz_value.h └── zipf_gen.h ├── blaze.sh ├── cmake ├── internal.cmake └── third_party.cmake ├── examples ├── CMakeLists.txt ├── echo_server.cc ├── gcs_demo.cc ├── https_client_cli.cc ├── pingserver │ ├── CMakeLists.txt │ ├── ping_iouring_server.cc │ ├── resp_parser.cc │ └── resp_parser.h ├── proactor_stress.cc ├── raw_echo_server.cc ├── redis_dict │ ├── CMakeLists.txt │ ├── alloc.c │ ├── alloc.h │ ├── dict.c │ ├── dict.h │ ├── fmacros.h │ ├── sds.c │ ├── sds.h │ └── sdsalloc.h └── s3_demo.cc ├── install-dependencies.sh ├── io ├── CMakeLists.txt ├── file.cc ├── file.h ├── file_test.cc ├── file_util.cc ├── file_util.h ├── io.cc ├── io.h ├── io_buf.h ├── io_test.cc ├── line_reader.cc ├── line_reader.h ├── proc_reader.cc ├── proc_reader.h ├── testdata │ └── ids.txt.zst ├── zstd_sinksource.cc └── zstd_sinksource.h ├── patches ├── abseil-20240116.2.patch ├── aws-sdk-cpp-3e51fa016655eeb6b6610bdf8fe7cf33ebbf3e00.patch └── mimalloc-v2.1.6.patch ├── strings ├── CMakeLists.txt ├── escaping.cc ├── escaping.h ├── human_readable.cc ├── human_readable.h └── strings_test.cc ├── tools ├── docker │ ├── Dockerfile.ubuntu-prod │ └── entrypoint.sh └── rpm │ └── build_rpm.sh └── util ├── CMakeLists.txt ├── accept_server.h ├── accept_server_test.cc ├── asio_stream_adapter.h ├── aws ├── CMakeLists.txt ├── aws.cc ├── aws.h ├── credentials_provider_chain.cc ├── credentials_provider_chain.h ├── http_client.cc ├── http_client.h ├── http_client_factory.cc ├── http_client_factory.h ├── logger.cc ├── logger.h ├── s3_endpoint_provider.cc ├── s3_endpoint_provider.h ├── s3_read_file.cc ├── s3_read_file.h ├── s3_write_file.cc └── s3_write_file.h ├── cloud ├── CMakeLists.txt ├── azure │ ├── CMakeLists.txt │ ├── azure.cc │ ├── creds_provider.h │ ├── storage.cc │ └── storage.h ├── gcp │ ├── CMakeLists.txt │ ├── gcp_creds_provider.h │ ├── gcp_utils.cc │ ├── gcp_utils.h │ ├── gcs.cc │ ├── gcs.h │ ├── gcs_file.cc │ └── gcs_file.h ├── utils.cc └── utils.h ├── connection.h ├── fiber_socket_base.h ├── fibers ├── CMakeLists.txt ├── accept_server.cc ├── detail │ ├── fiber_interface.cc │ ├── fiber_interface.h │ ├── fiber_interface_impl.h │ ├── result_mover.h │ ├── scheduler.cc │ ├── scheduler.h │ ├── wait_queue.cc │ └── wait_queue.h ├── dns_resolve.cc ├── dns_resolve.h ├── epoll_proactor.cc ├── epoll_proactor.h ├── epoll_socket.cc ├── epoll_socket.h ├── fiber_file.cc ├── fiber_file.h ├── fiber_socket_base.cc ├── fiber_socket_test.cc ├── fiberqueue_threadpool.cc ├── fiberqueue_threadpool.h ├── fibers.cc ├── fibers.h ├── fibers_test.cc ├── future.h ├── listener_interface.cc ├── pool.cc ├── pool.h ├── prebuilt_asio.cc ├── proactor_base.cc ├── proactor_base.h ├── proactor_pool.cc ├── simple_channel.h ├── sliding_counter.cc ├── stacktrace.cc ├── stacktrace.h ├── submit_entry.h ├── synchronization.cc ├── synchronization.h ├── uring_file.cc ├── uring_file.h ├── uring_file_test.cc ├── uring_proactor.cc ├── uring_proactor.h ├── uring_socket.cc ├── uring_socket.h ├── uring_types.h └── varz.cc ├── html ├── CMakeLists.txt ├── main.js ├── sorted_table.cc ├── sorted_table.h └── style.css ├── http ├── CMakeLists.txt ├── captain.gif ├── encoding.cc ├── encoding.h ├── favicon-32x32.png ├── http_client.cc ├── http_client.h ├── http_common.cc ├── http_common.h ├── http_handler.cc ├── http_handler.h ├── http_main.cc ├── http_server_utils.h ├── http_status_code.cc ├── http_status_code.h ├── https_client_pool.cc ├── https_client_pool.h ├── logo.png ├── prebuilt_beast.cc ├── profilez_handler.cc ├── status_page.cc ├── status_page.css └── status_page.js ├── listener_interface.h ├── metrics ├── CMakeLists.txt ├── family.cc ├── family.h ├── metrics.cc └── metrics.h ├── proactor_pool.h ├── sliding_counter.h ├── tls ├── CMakeLists.txt ├── README.md ├── certificates │ ├── ca-cert.pem │ ├── ca-key.pem │ ├── server-cert.pem │ └── server-key.pem ├── tls_engine.cc ├── tls_engine.h ├── tls_engine_test.cc ├── tls_socket.cc ├── tls_socket.h └── tls_socket_test.cc └── varz.h /.clang-format: -------------------------------------------------------------------------------- 1 | # --- 2 | # We'll use defaults from the Google style, but with 2 columns indentation. 3 | BasedOnStyle: Google 4 | IndentWidth: 2 5 | ColumnLimit: 100 6 | --- 7 | Language: Cpp 8 | AllowShortLoopsOnASingleLine: false 9 | AllowShortFunctionsOnASingleLine: false 10 | AllowShortIfStatementsOnASingleLine: false 11 | AlwaysBreakTemplateDeclarations: false 12 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 13 | DerivePointerAlignment: false 14 | PointerAlignment: Left 15 | BasedOnStyle: Google 16 | ColumnLimit: 100 17 | --- 18 | Language: Proto 19 | BasedOnStyle: Google 20 | -------------------------------------------------------------------------------- /.clangd: -------------------------------------------------------------------------------- 1 | Diagnostics: 2 | UnusedIncludes: None 3 | MissingIncludes: None 4 | Includes: 5 | IgnoreHeader: base/*.h 6 | 7 | CompileFlags: 8 | CompilationDatabase: build-dbg/ # Search for compile_commands.json 9 | -------------------------------------------------------------------------------- /.devcontainer/alpine/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alpine", 3 | "image": "ghcr.io/romange/alpine-dev", 4 | "customizations": { 5 | "vscode": { 6 | "extensions": [ 7 | "ms-vscode.cpptools", 8 | "ms-vscode.cmake-tools", 9 | "ms-vscode.cpptools-themes", 10 | "twxs.cmake" 11 | ], 12 | "settings": { 13 | "cmake.buildDirectory": "/build", 14 | "extensions.ignoreRecommendations": true, 15 | "cmake.configureArgs": [] 16 | } 17 | } 18 | }, 19 | "mounts": [ 20 | "source=alpine-vol,target=/build,type=volume" 21 | ], 22 | "postCreateCommand": ".devcontainer/post-create.sh ${containerWorkspaceFolder}" 23 | } -------------------------------------------------------------------------------- /.devcontainer/fedora/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fedora", 3 | "image": "ghcr.io/romange/fedora:30", 4 | "customizations": { 5 | "vscode": { 6 | "extensions": [ 7 | "ms-vscode.cpptools", 8 | "ms-vscode.cmake-tools", 9 | "ms-vscode.cpptools-themes", 10 | "twxs.cmake" 11 | ], 12 | "settings": { 13 | "cmake.buildDirectory": "/build", 14 | "extensions.ignoreRecommendations": true, 15 | "cmake.configureArgs": [ 16 | ] 17 | } 18 | } 19 | }, 20 | "mounts": [ 21 | "source=fedora-vol,target=/build,type=volume" 22 | ], 23 | "postCreateCommand": ".devcontainer/post-create.sh ${containerWorkspaceFolder}" 24 | } -------------------------------------------------------------------------------- /.devcontainer/post-create.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | containerWorkspaceFolder=$1 4 | git config --global --add safe.directory ${containerWorkspaceFolder} 5 | mkdir -p /root/.local/share/CMakeTools -------------------------------------------------------------------------------- /.devcontainer/ubuntu22/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ubuntu22", 3 | "image": "ghcr.io/romange/ubuntu-dev:22", 4 | "customizations": { 5 | "vscode": { 6 | "extensions": [ 7 | "ms-vscode.cpptools", 8 | "ms-vscode.cmake-tools", 9 | "ms-vscode.cpptools-themes", 10 | "twxs.cmake" 11 | ], 12 | "settings": { 13 | "cmake.buildDirectory": "/build", 14 | "extensions.ignoreRecommendations": true, 15 | "cmake.configureArgs": [ 16 | "-DUSE_MOLD=ON" 17 | ] 18 | } 19 | } 20 | }, 21 | "mounts": [ 22 | "source=ubuntu22-vol,target=/build,type=volume" 23 | ], 24 | "postCreateCommand": ".devcontainer/post-create.sh ${containerWorkspaceFolder}" 25 | } -------------------------------------------------------------------------------- /.devcontainer/ubuntu24/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ubuntu24-ucontext-asan", 3 | "image": "ghcr.io/romange/ubuntu-dev:24", 4 | "customizations": { 5 | "vscode": { 6 | "extensions": [ 7 | "ms-vscode.cpptools", 8 | "ms-vscode.cmake-tools", 9 | "ms-vscode.cpptools-themes", 10 | "twxs.cmake" 11 | ], 12 | "settings": { 13 | "cmake.buildDirectory": "/build", 14 | "cmake.configureArgs": [ 15 | "-DUSE_MOLD=ON", 16 | "-DBOOST_ROOT=/opt/boost", // This is a custom built boost with ucontext support 17 | "-DCMAKE_CXX_FLAGS='-DBOOST_USE_UCONTEXT -DBOOST_USE_ASAN'" 18 | ] 19 | } 20 | } 21 | }, 22 | "mounts": [ 23 | "source=ubuntu24-vol,target=/build,type=volume" 24 | ], 25 | "postCreateCommand": ".devcontainer/post-create.sh ${containerWorkspaceFolder}" 26 | } -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | _deps/* 2 | build-* 3 | .github/ -------------------------------------------------------------------------------- /.github/workflows/docker-release.yml: -------------------------------------------------------------------------------- 1 | name: docker release 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | permissions: 7 | packages: write 8 | 9 | jobs: 10 | # =============================================================== 11 | # Building Dev Images 12 | # =============================================================== 13 | release: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: checkout 17 | uses: actions/checkout@v3 18 | with: 19 | submodules: true 20 | - 21 | name: Set up QEMU 22 | id: qemu 23 | uses: docker/setup-qemu-action@v3 24 | with: 25 | platforms: arm64,amd64 26 | 27 | - name: Set up Docker Buildx 28 | uses: docker/setup-buildx-action@v3 29 | 30 | - name: Login to GitHub Container Registry 31 | uses: docker/login-action@v3 32 | with: 33 | registry: ghcr.io 34 | username: ${{ github.actor }} 35 | password: ${{ secrets.GITHUB_TOKEN }} 36 | - name: Build echo_server ubuntu 37 | uses: docker/build-push-action@v5 38 | with: 39 | context: . 40 | platforms: linux/amd64,linux/arm64 41 | build-args: | 42 | QEMU_CPU=max,pauth-impdef=on 43 | 44 | push: ${{ github.event_name != 'pull_request' }} 45 | tags: | 46 | ghcr.io/${{ github.actor }}/echo_server:latest 47 | file: tools/docker/Dockerfile.ubuntu-prod -------------------------------------------------------------------------------- /.github/workflows/mac-os.yml: -------------------------------------------------------------------------------- 1 | name: mac-os-ci 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | workflow_dispatch: 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 12 | cancel-in-progress: true 13 | 14 | env: 15 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 16 | BUILD_TYPE: Debug 17 | 18 | jobs: 19 | build-macos: 20 | runs-on: macos-13 21 | timeout-minutes: 40 # ✨✨✨✨ 22 | steps: 23 | - uses: actions/checkout@v4 24 | - run: | 25 | brew update && brew install ninja boost automake 26 | - name: Configure CMake 27 | run: | 28 | cmake --version 29 | gcc-12 --version 30 | uname -a 31 | cmake -B ${{github.workspace}}/build \ 32 | -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} \ 33 | -GNinja \ 34 | -DCMAKE_C_COMPILER=gcc-12 \ 35 | -DCMAKE_CXX_COMPILER=g++-12 \ 36 | -DCMAKE_CXX_FLAGS="-Wl,-ld_classic" # due to a linker bug on macos, see https://github.com/Homebrew/homebrew-core/issues/145991 37 | 38 | - name: Build & Test 39 | run: | 40 | cd ${{github.workspace}}/build 41 | cat CMakeCache.txt 42 | 43 | ninja gperf_project || cat third_party/src/gperf_project-stamp/gperf_project-install-*.log 44 | ninja cares_project || cat third_party/src/cares_project-stamp/cares_project-build-*.log 45 | ninja -k 5 base/all io/all strings/all util/all echo_server ping_iouring_server \ 46 | https_client_cli s3_demo 47 | ./fibers_test --logtostderr --gtest_repeat=10 48 | ctest -V -L CI 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | build-* 3 | clang/* 4 | clang-* 5 | .vscode/*.db 6 | .vscode/settings.json 7 | third_party/* 8 | genfiles/* 9 | *.sublime-* 10 | .tags 11 | third_party 12 | *.pyc 13 | _deps 14 | *.orig -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | default_stages: [commit] 2 | repos: 3 | - repo: local 4 | hooks: 5 | - id: conventional-commits 6 | name: Conventional Commit Minder 7 | entry: contrib/scripts/conventional-commits 8 | language: script 9 | stages: [commit-msg] 10 | 11 | - repo: https://github.com/pre-commit/pre-commit-hooks 12 | rev: v4.3.0 13 | hooks: 14 | - id: trailing-whitespace 15 | - id: end-of-file-fixer 16 | 17 | - repo: https://github.com/pre-commit/mirrors-clang-format 18 | rev: v14.0.6 19 | hooks: 20 | - id: clang-format 21 | name: Clang formatting 22 | -------------------------------------------------------------------------------- /.pre-commit-hooks.yaml: -------------------------------------------------------------------------------- 1 | - id: conventional-commits 2 | name: Conventional Commits Minder 3 | entry: contrib/scripts/conventional-commits 4 | language: script 5 | description: Conventional Commits Enforcement at the `git commit` client-side level 6 | always_run: true 7 | stages: [commit-msg] 8 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "cppStandard": "c++17", 6 | "cStandard": "c17", 7 | "intelliSenseMode": "${default}", 8 | "compileCommands": "${workspaceFolder}/build-dbg/compile_commands.json", 9 | "configurationProvider": "ms-vscode.cmake-tools" 10 | } 11 | ], 12 | "version": 4 13 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | set(PACKAGE_NAME "helio") 4 | set(PROJECT_CONTACT romange@gmail.com) 5 | 6 | project(${PACKAGE_NAME} C CXX) 7 | enable_testing() 8 | 9 | get_directory_property(PARENT_PROJ_NAME PARENT_DIRECTORY) 10 | 11 | Message(STATUS "PROJECT_BINARY_DIR ${PROJECT_BINARY_DIR} GENERATOR ${CMAKE_GENERATOR}") 12 | Message(STATUS "PROJECT_SOURCE_DIR ${PROJECT_SOURCE_DIR} CMAKE_SOURCE_DIR ${CMAKE_SOURCE_DIR}") 13 | 14 | if (APPLE) 15 | option (WITH_UNWIND "Enable libunwind support" OFF) 16 | else() 17 | option (WITH_UNWIND "Enable libunwind support" ON) 18 | endif() 19 | 20 | option (HELIO_USE_SANITIZER "Use asan and usan sanitizers" OFF) 21 | option (BUILD_DOCS "Generate documentation " ON) 22 | option (BUILD_SHARED_LIBS "Build shared libraries" OFF) 23 | option (USE_MOLD "whether to use mold linker" OFF) 24 | set (HELIO_MIMALLOC_OPTS "" CACHE STRING "additional mimalloc compile options") 25 | set (HELIO_MIMALLOC_LIBNAME "libmimalloc.a" CACHE STRING "name of mimalloc library") 26 | 27 | 28 | include(CheckCXXCompilerFlag) 29 | 30 | # Can not use CHECK_CXX_COMPILER_FLAG due to linker problems. 31 | set(CMAKE_REQUIRED_FLAGS "-fsanitize=address") 32 | check_cxx_source_compiles("int main() { return 0; }" SUPPORT_ASAN) 33 | 34 | set(CMAKE_REQUIRED_FLAGS "-fsanitize=undefined") 35 | check_cxx_source_compiles("int main() { return 0; }" SUPPORT_USAN) 36 | set(CMAKE_REQUIRED_FLAGS "") 37 | 38 | 39 | if (PARENT_PROJ_NAME) 40 | Message(STATUS "PARENT_PROJ_NAME ${PARENT_PROJ_NAME}") 41 | else() 42 | # Define our own project 43 | set(CMAKE_CXX_STANDARD 17) 44 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) 45 | 46 | if (HELIO_USE_SANITIZER AND SUPPORT_ASAN) 47 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address") 48 | endif() 49 | 50 | if (HELIO_USE_SANITIZER AND SUPPORT_USAN) 51 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=undefined") 52 | endif() 53 | 54 | if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 55 | set(CMAKE_CXX_FLAGS "-Wthread-safety ${CMAKE_CXX_FLAGS}") 56 | endif() 57 | 58 | include(third_party) 59 | include(internal) 60 | endif() 61 | 62 | 63 | add_definitions(-DUSE_FB2) 64 | 65 | add_subdirectory(base) 66 | add_subdirectory(io) 67 | add_subdirectory(examples) 68 | add_subdirectory(strings) 69 | add_subdirectory(util) 70 | -------------------------------------------------------------------------------- /base/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(base cpu_features.cc hash.cc histogram.cc init.cc logging.cc proc_util.cc 2 | pthread_utils.cc varz_node.cc cuckoo_map.cc io_buf.cc segment_pool.cc) 3 | 4 | if (LEGACY_GLOG) 5 | set(LOG_LIBS glog::glog) 6 | else() 7 | 8 | target_compile_definitions(base PUBLIC -DUSE_ABSL_LOG=1) 9 | set(LOG_LIBS absl::check absl::log -Wl,--whole-archive absl::log_flags 10 | absl::log_initialize) 11 | endif() 12 | 13 | cxx_link(base ${LOG_LIBS} absl::flags_parse 14 | absl::strings absl::symbolize absl::time absl::failure_signal_handler TRDP::xxhash) 15 | 16 | # Define default gtest_main for tests. 17 | add_library(gtest_main_ext gtest_main.cc) 18 | target_link_libraries(gtest_main_ext gmock base benchmark TRDP::gperf) 19 | 20 | add_subdirectory(pmr) 21 | 22 | cxx_test(mpmc_bounded_queue_test base LABELS CI) 23 | cxx_test(mpsc_intrusive_queue_test base LABELS CI) 24 | cxx_test(abseil_test base absl::str_format LABELS CI) 25 | cxx_test(hash_test base absl::random_random LABELS CI) 26 | cxx_test(cuckoo_map_test base absl::flat_hash_map LABELS CI) 27 | cxx_test(histogram_test base LABELS CI) 28 | if (NOT ${CMAKE_SYSTEM_NAME} MATCHES "Darwin") 29 | cxx_test(malloc_test base TRDP::mimalloc TRDP::jemalloc io LABELS CI) 30 | endif() 31 | 32 | cxx_test(flit_test base LABELS CI) 33 | cxx_test(cxx_test base absl::flat_hash_map LABELS CI) 34 | cxx_test(string_view_sso_test base LABELS CI) 35 | cxx_test(ring_buffer_test base LABELS CI) 36 | -------------------------------------------------------------------------------- /base/LICENSE.base: -------------------------------------------------------------------------------- 1 | sse2neon.h is from https://github.com/DLTcollab/sse2neon/ and is distributed under MIT license. 2 | libdivide.h is taken from https://github.com/ridiculousfish/libdivide and is distributed under Boost and zlib licenses. 3 | 4 | RWSpinlock.h is taken from https://github.com/facebook/folly and it's distributed under Apache 2.0 license 5 | function2.hpp is taken from https://github.com/Naios/function2 and is distributed under Boost license (BSL-1.0) 6 | 7 | pmr/pod_array.h is based on Clickhouse PodArray : https://github.com/ClickHouse/ClickHouse/ and it's distributed 8 | under Apache 2.0 license. 9 | 10 | mpmc_bounded_queue.h is based on D.Vyukov's implementation http://www.1024cores.net/home/lock-free-algorithms/queues/bounded-mpmc-queue 11 | and is distributed under simplified BSD license. 12 | 13 | histogram.* is taken from https://github.com/google/leveldb and is distributed under BSD 3-clause license. 14 | 15 | expected.hpp is distributed under BSL 1.0 (boost) and is taken from https://github.com/martinmoene/expected-lite -------------------------------------------------------------------------------- /base/bits.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | #pragma once 5 | 6 | #include 7 | 8 | class [[deprecated]] Bits { 9 | public: 10 | static int CountOnes(uint32_t n) { 11 | return __builtin_popcount(n); 12 | } 13 | 14 | static inline constexpr int CountOnes64(uint64_t n) { 15 | return __builtin_popcountll(n); 16 | } 17 | 18 | static uint32_t RoundUp(uint32_t x) { 19 | return absl::bit_ceil(x); 20 | } 21 | 22 | static uint64_t RoundUp64(uint64_t x) { 23 | return absl::bit_ceil(x); 24 | } 25 | 26 | private: 27 | Bits(const Bits&) = delete; 28 | void operator=(const Bits&) = delete; 29 | }; 30 | -------------------------------------------------------------------------------- /base/cpu_features.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #include "base/cpu_features.h" 6 | 7 | #include 8 | 9 | // aarch64 is currently a noop. 10 | #ifdef __x86_64__ 11 | 12 | namespace base { 13 | 14 | namespace { 15 | 16 | // See for constants reference 17 | constexpr unsigned BIT_AVX2 = (1 << 5); 18 | constexpr unsigned BIT_AVX512F = (1 << 16); 19 | 20 | constexpr unsigned BIT_XSAVE = (1 << 26); 21 | constexpr unsigned BIT_OSXSAVE = (1 << 27); 22 | 23 | // A struct to hold the result of a call to cpuid. 24 | typedef struct { 25 | uint32_t eax, ebx, ecx, edx; 26 | } Leaf; 27 | 28 | Leaf GetCpuidLeaf(uint32_t leaf_id) { 29 | Leaf leaf; 30 | __asm__ __volatile__("cpuid" 31 | : "=a"(leaf.eax), "=b"(leaf.ebx), "=c"(leaf.ecx), "=d"(leaf.edx) 32 | : "a"(leaf_id), "c"(0)); 33 | return leaf; 34 | } 35 | 36 | uint32_t GetXCR0() { 37 | uint32_t xcr0; 38 | __asm__("xgetbv" : "=a"(xcr0) : "c"(0) : "%edx"); 39 | return xcr0; 40 | } 41 | 42 | } // namespace 43 | 44 | CpuFeatures GetCpuFeatures() { 45 | CpuFeatures res; 46 | Leaf leaf = GetCpuidLeaf(0); 47 | const uint32_t max_cpuid_leaf = leaf.eax; 48 | 49 | if (max_cpuid_leaf < 7) 50 | return res; 51 | 52 | leaf = GetCpuidLeaf(1); 53 | 54 | bool has_xcr0 = (leaf.ecx & BIT_OSXSAVE) && (leaf.ecx & BIT_XSAVE); 55 | if (!has_xcr0) 56 | return res; 57 | 58 | leaf = GetCpuidLeaf(7); 59 | const uint32_t xcr0 = GetXCR0(); 60 | 61 | if ((xcr0 & 6) != 6) 62 | return res; 63 | 64 | // See https://en.wikichip.org/wiki/x86/avx-512 for explanation about the variants 65 | res.has_avx2 = leaf.ebx & BIT_AVX2; 66 | res.has_avx512f = leaf.ebx & BIT_AVX512F; 67 | 68 | return res; 69 | } 70 | 71 | } // namespace base 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /base/cpu_features.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #pragma once 6 | 7 | // Much slimmer version of https://github.com/google/cpu_features/ 8 | // Assumes only relatively recent cpu families (found in the cloud) 9 | namespace base { 10 | 11 | struct CpuFeatures { 12 | #ifdef __x86_64__ 13 | bool has_avx2 = false; 14 | bool has_avx512f = false; 15 | #endif 16 | 17 | #ifdef __aarch64__ 18 | // TBD 19 | #endif 20 | }; 21 | 22 | #ifdef __x86_64__ 23 | CpuFeatures GetCpuFeatures(); 24 | 25 | #else 26 | 27 | // Stub for now. 28 | inline CpuFeatures GetCpuFeatures() { 29 | return CpuFeatures{}; 30 | 31 | } 32 | 33 | #endif 34 | 35 | } // namespace base -------------------------------------------------------------------------------- /base/cuckoo_map.h: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | #ifndef _CUCKOO_MAP_H 5 | #define _CUCKOO_MAP_H 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "base/bits.h" 12 | #include "base/cuckoo_map-internal.h" 13 | #include "base/integral_types.h" 14 | 15 | /* Cuckoo works very bad with non-prime table sizes. 16 | In particular, for random input we quickly find many numbers pairs that map to the same 17 | bucket pairs, i.e. n1..n8 map to buckets x and y. Once this happens cuckoo table must regrow 18 | no matter how unutilized the table is. However, with table prime sizes, 19 | those collisions disapear. 20 | */ 21 | namespace base { 22 | 23 | uint64 GetPrimeNotLessThan(uint64 value); 24 | /* 25 | Cuckoo Set. 26 | */ 27 | class CuckooSet : public CuckooMapTableWrapperBase { 28 | public: 29 | // Allocates space for the minimal number of values. 30 | explicit CuckooSet(uint32 capacity = 0) : CuckooMapTableWrapperBase(0, capacity) { 31 | } 32 | 33 | // Inserts x into the map. This function invalidates all dense_ids. 34 | std::pair Insert(KeyType v) { 35 | return table_.Insert(v, nullptr); 36 | } 37 | KeyType FromDenseId(DenseId d) const { 38 | return table_.FromDenseId(d).first; 39 | } 40 | }; 41 | 42 | /* 43 | Cuckoo Map. T must be trivially copyable type. 44 | */ 45 | template class CuckooMap : public CuckooMapTableWrapperBase { 46 | public: 47 | explicit CuckooMap(uint32 capacity = 0) : CuckooMapTableWrapperBase(sizeof(T), capacity) { 48 | // TODO(roman): to add is_trivially_copyable restriction once it's supported by gcc. 49 | // static_assert(std::is_trivially_copyable::value, "T should be copied trvially"); 50 | } 51 | 52 | // Inserts x into the map. This function invalidates all dense_ids. 53 | std::pair Insert(KeyType v, const T& t) { 54 | return table_.Insert(v, reinterpret_cast(&t)); 55 | } 56 | 57 | std::pair FromDenseId(DenseId d) { 58 | auto p = table_.FromDenseId(d); 59 | return std::pair(p.first, reinterpret_cast(p.second)); 60 | } 61 | 62 | std::pair FromDenseId(DenseId d) const { 63 | auto p = table_.FromDenseId(d); 64 | return std::pair(p.first, reinterpret_cast(p.second)); 65 | } 66 | }; 67 | 68 | } // namespace base 69 | 70 | #endif // _CUCKOO_MAP_H 71 | -------------------------------------------------------------------------------- /base/cycle_clock.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #if !defined(__x86_64__) && !defined(__aarch64__) 10 | #include 11 | #endif 12 | 13 | namespace base { 14 | 15 | // CycleClock is a class that provides a high-resolution cycle counter based on TSC. 16 | // It is used to measure very short time intervals. 17 | 18 | class CycleClock { 19 | public: 20 | // Called once to initialize the frequency of the cycle counter. 21 | static void InitOnce(); 22 | 23 | // Returns the current value of the cycle counter. 24 | static uint64_t Now() { 25 | #if defined(__x86_64__) 26 | unsigned long low, high; 27 | __asm__ volatile("rdtsc" : "=a"(low), "=d"(high)); 28 | return ((static_cast(high) << 32) + low); 29 | #elif defined(__aarch64__) 30 | int64_t tv; 31 | asm volatile("mrs %0, cntvct_el0" : "=r"(tv)); 32 | return tv; 33 | #else 34 | return absl::base_internal::CycleClock::Now(); 35 | #endif 36 | } 37 | 38 | // Returns the frequency of the cycle counter in Hz. 39 | static uint64_t Frequency() { 40 | return frequency_; 41 | } 42 | 43 | private: 44 | static uint64_t frequency_; 45 | }; 46 | 47 | } // namespace base 48 | -------------------------------------------------------------------------------- /base/endian.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | namespace base { 10 | 11 | namespace LE { 12 | 13 | template static void StoreT(T val, void* dest); 14 | template static T LoadT(const void* dest); 15 | 16 | template <> inline void StoreT(uint16_t val, void* dest) { 17 | absl::little_endian::Store16(dest, val); 18 | } 19 | 20 | template <> inline void StoreT(uint32_t val, void* dest) { 21 | absl::little_endian::Store32(dest, val); 22 | } 23 | 24 | template <> inline void StoreT(uint64_t val, void* dest) { 25 | absl::little_endian::Store64(dest, val); 26 | } 27 | 28 | template <> inline uint8_t LoadT(const void* src) { 29 | return *reinterpret_cast(src); 30 | } 31 | 32 | template <> inline uint16_t LoadT(const void* src) { 33 | return absl::little_endian::Load16(src); 34 | } 35 | 36 | template <> inline uint32_t LoadT(const void* src) { 37 | return absl::little_endian::Load32(src); 38 | } 39 | 40 | template <> inline uint64_t LoadT(const void* src) { 41 | return absl::little_endian::Load64(src); 42 | } 43 | 44 | constexpr bool IsLittleEndian() { 45 | return absl::little_endian::IsLittleEndian(); 46 | } 47 | 48 | }; // namespace LE 49 | 50 | } // namespace base 51 | -------------------------------------------------------------------------------- /base/flags.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | #pragma once 5 | 6 | #include 7 | #include 8 | -------------------------------------------------------------------------------- /base/gtest.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | namespace base { 10 | 11 | // Used to avoid compiler optimizations for these benchmarks. 12 | // Just call it with the return value of the function. 13 | template void sink_result(const T& t0) { 14 | volatile T t = t0; 15 | (void)t; 16 | } 17 | 18 | // Returns unique test dir - the same for the run of the process. 19 | // The directory is cleaned automatically if all the tests finish succesfully. 20 | std::string GetTestTempDir(); 21 | std::string GetTestTempPath(const std::string& base_name); 22 | 23 | std::string RandStr(const unsigned len); 24 | 25 | std::string ProgramRunfile(const std::string& relative_path); // relative to runtime dir. 26 | 27 | } // namespace base 28 | -------------------------------------------------------------------------------- /base/hash.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | // based on MurmurHash code. 5 | // 6 | #include "base/hash.h" 7 | 8 | #include 9 | 10 | #include "absl/base/macros.h" 11 | #include "absl/base/optimization.h" 12 | 13 | #define XXH_INLINE_ALL 14 | #include 15 | 16 | namespace { 17 | 18 | inline uint32_t fmix(uint32_t h) { 19 | h ^= h >> 16; 20 | h *= 0x85ebca6b; 21 | h ^= h >> 13; 22 | h *= 0xc2b2ae35; 23 | h ^= h >> 16; 24 | 25 | return h; 26 | } 27 | 28 | inline uint32_t rotl32(uint32_t x, int8_t r) { 29 | return (x << r) | (x >> (32 - r)); 30 | } 31 | 32 | } // namespace 33 | 34 | namespace base { 35 | 36 | uint32_t MurmurHash3_x86_32(const uint8_t* data, uint32_t len, uint32_t seed) { 37 | const uint32_t nblocks = len / 4; 38 | 39 | uint32_t h1 = seed; 40 | 41 | uint32_t c1 = 0xcc9e2d51; 42 | uint32_t c2 = 0x1b873593; 43 | 44 | //---------- 45 | // body 46 | 47 | const uint32_t* blocks = (const uint32_t*)(data + nblocks * 4); 48 | 49 | int i; 50 | for (i = -nblocks; i; i++) { 51 | uint32_t k1; 52 | memcpy(&k1, blocks + i, sizeof(uint32_t)); 53 | 54 | k1 *= c1; 55 | k1 = rotl32(k1, 15); 56 | k1 *= c2; 57 | 58 | h1 ^= k1; 59 | h1 = rotl32(h1, 13); 60 | h1 = h1 * 5 + 0xe6546b64; 61 | } 62 | 63 | //---------- 64 | // tail 65 | 66 | const uint8_t* tail = data + nblocks * 4; 67 | 68 | uint32_t k1 = 0; 69 | 70 | switch (len & 3) { 71 | case 3: 72 | k1 ^= tail[2] << 16; 73 | ABSL_FALLTHROUGH_INTENDED; 74 | case 2: 75 | k1 ^= tail[1] << 8; 76 | ABSL_FALLTHROUGH_INTENDED; 77 | case 1: 78 | k1 ^= tail[0]; 79 | k1 *= c1; 80 | k1 = rotl32(k1, 15); 81 | k1 *= c2; 82 | h1 ^= k1; 83 | } 84 | 85 | //---------- 86 | // finalization 87 | 88 | h1 ^= len; 89 | 90 | h1 = fmix(h1); 91 | 92 | return h1; 93 | } 94 | 95 | uint64_t Fingerprint(const char* str, uint32_t len) { 96 | uint64_t res = XXH64(str, len, 24061983); 97 | if (ABSL_PREDICT_TRUE(res > 1)) 98 | return res; 99 | return 2; 100 | } 101 | 102 | } // namespace base 103 | -------------------------------------------------------------------------------- /base/hash_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2014, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | #include "base/hash.h" 5 | 6 | #include 7 | 8 | #include "base/gtest.h" 9 | #include "base/logging.h" 10 | #include "base/zipf_gen.h" 11 | 12 | using namespace std; 13 | 14 | namespace base { 15 | 16 | class HashTest : public testing::Test { 17 | protected: 18 | }; 19 | 20 | TEST_F(HashTest, Basic) { 21 | EXPECT_EQ(187264267u, XXHash32(32)); 22 | 23 | #if SKYLAKE_DEFINED 24 | const uint8_t* ptr = reinterpret_cast("foo"); 25 | __m128i res = AquaHash::SmallKeyAlgorithm(ptr, 3); 26 | __int128 val; 27 | _mm_store_si128((__m128i*)&val, res); 28 | EXPECT_NE(0, val); 29 | #endif 30 | } 31 | 32 | TEST_F(HashTest, Zipf) { 33 | ZipfianGenerator zipf(0, 9, 0.99); 34 | absl::BitGen gen; 35 | 36 | for (unsigned i = 0; i < 100; ++i) { 37 | LOG(INFO) << zipf.Next(gen); 38 | } 39 | } 40 | 41 | } // namespace base 42 | -------------------------------------------------------------------------------- /base/histogram.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2011 The LevelDB Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. See the AUTHORS file for names of contributors. 4 | 5 | #ifndef _BASE_HISTOGRAM_H_ 6 | #define _BASE_HISTOGRAM_H_ 7 | 8 | #include 9 | #include 10 | 11 | #include "base/integral_types.h" 12 | 13 | namespace base { 14 | 15 | class Histogram { 16 | public: 17 | Histogram(); 18 | ~Histogram(); 19 | 20 | void Clear(); 21 | void Add(double value) { 22 | Add(value, 1); 23 | } 24 | void Add(double value, uint32 count); 25 | void Merge(const Histogram& other); 26 | 27 | std::string ToString() const; 28 | 29 | unsigned long count() const { 30 | return num_; 31 | } 32 | 33 | double Median() const { 34 | return Percentile(50.0); 35 | } 36 | 37 | // p in [0, 100]. 38 | double Percentile(double p) const; 39 | double Average() const; 40 | double StdDev() const; 41 | double max() const { 42 | return max_; 43 | } 44 | double min() const { 45 | return min_; 46 | } 47 | 48 | // trim_low_percentile, trim_high_percentile in [0, 100]. 49 | // Returns truncated mean according to http://en.wikipedia.org/wiki/Truncated_mean 50 | // Do not use it - it's broken! See the failing test. 51 | // double TruncatedMean(double trim_low_percentile, double trim_high_percentile) const; 52 | 53 | private: 54 | typedef unsigned long ulong; 55 | 56 | // returns the interpolated value based on its position inside the bucket and 57 | // the number of items in the bucket. position should be valid 58 | // (i.e. in range [1, buckets_[bucket]]) 59 | double InterpolateVal(unsigned bucket, ulong position) const; 60 | double SumFirstK(unsigned bucket, ulong position) const; 61 | 62 | static std::pair BucketLimits(unsigned bucket); 63 | static double BucketAverage(unsigned b); 64 | 65 | double min_; 66 | double max_; 67 | double sum_; 68 | double sum_squares_; 69 | uint32 num_; 70 | 71 | enum { kNumBuckets = 154 }; 72 | static const double kBucketLimit[kNumBuckets]; 73 | 74 | std::vector buckets_; 75 | }; 76 | 77 | } // namespace base 78 | 79 | #endif // _BASE_HISTOGRAM_H_ 80 | -------------------------------------------------------------------------------- /base/init.h: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | #pragma once 5 | 6 | #include "base/flags.h" 7 | #include "base/logging.h" 8 | 9 | namespace __internal__ { 10 | 11 | class ModuleInitializer { 12 | public: 13 | typedef void (*VoidFunction)(void); 14 | 15 | ModuleInitializer(VoidFunction ctor, bool is_ctor); 16 | 17 | static void RunFtors(bool is_ctor); 18 | 19 | private: 20 | struct CtorNode { 21 | VoidFunction func; 22 | CtorNode* next; 23 | 24 | bool is_ctor; 25 | } __attribute__((packed)); 26 | 27 | CtorNode node_; 28 | 29 | static CtorNode*& global_list(); 30 | 31 | ModuleInitializer(const ModuleInitializer&) = delete; 32 | void operator=(const ModuleInitializer&) = delete; 33 | }; 34 | 35 | } // namespace __internal__ 36 | 37 | #define REGISTER_MODULE_INITIALIZER(name, body) \ 38 | namespace { \ 39 | static void google_init_module_##name() { \ 40 | body; \ 41 | } \ 42 | __internal__::ModuleInitializer google_initializer_module_##name(google_init_module_##name, \ 43 | true); \ 44 | } 45 | 46 | #define REGISTER_MODULE_DESTRUCTOR(name, body) \ 47 | namespace { \ 48 | static void google_destruct_module_##name() { \ 49 | body; \ 50 | } \ 51 | __internal__::ModuleInitializer google_destructor_module_##name(google_destruct_module_##name, \ 52 | false); \ 53 | } 54 | 55 | class MainInitGuard { 56 | public: 57 | MainInitGuard(int* argc, char*** argv, uint32_t flags = 0); 58 | 59 | ~MainInitGuard(); 60 | }; 61 | 62 | #define MainInitGuard(x, y) static_assert(false, "Forgot variable name") 63 | -------------------------------------------------------------------------------- /base/io_buf.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | #include "base/io_buf.h" 5 | 6 | namespace base { 7 | 8 | IoBuf::~IoBuf() { 9 | operator delete[](buf_, std::align_val_t{alignment_}); 10 | } 11 | 12 | void IoBuf::ConsumeInput(size_t sz) { 13 | if (offs_ + sz >= size_) { 14 | size_ = 0; 15 | offs_ = 0; 16 | } else { 17 | offs_ += sz; 18 | if (2 * offs_ > size_ && size_ - offs_ < 512) { 19 | memcpy(buf_, buf_ + offs_, size_ - offs_); 20 | size_ -= offs_; 21 | offs_ = 0; 22 | } 23 | } 24 | } 25 | 26 | void IoBuf::ReadAndConsume(size_t sz, void* dest) { 27 | ConstBytes b = InputBuffer(); 28 | assert(b.size() >= sz); 29 | memcpy(dest, b.data(), sz); 30 | ConsumeInput(sz); 31 | } 32 | 33 | void IoBuf::WriteAndCommit(const void* source, size_t num_write) { 34 | EnsureCapacity(num_write); 35 | memcpy(AppendBuffer().data(), source, num_write); 36 | CommitWrite(num_write); 37 | } 38 | 39 | void IoBuf::Reserve(size_t sz) { 40 | if (sz < capacity_) 41 | return; 42 | 43 | sz = absl::bit_ceil(sz); 44 | uint8_t* nb = new (std::align_val_t{alignment_}) uint8_t[sz]; 45 | if (buf_) { 46 | if (size_ > offs_) { 47 | memcpy(nb, buf_ + offs_, size_ - offs_); 48 | size_ -= offs_; 49 | offs_ = 0; 50 | } else { 51 | size_ = offs_ = 0; 52 | } 53 | ::operator delete[](buf_, std::align_val_t{alignment_}); 54 | } 55 | 56 | buf_ = nb; 57 | capacity_ = sz; 58 | } 59 | 60 | void IoBuf::Swap(IoBuf& other) { 61 | std::swap(buf_, other.buf_); 62 | std::swap(offs_, other.offs_); 63 | std::swap(size_, other.size_); 64 | std::swap(alignment_, other.alignment_); 65 | std::swap(capacity_, other.capacity_); 66 | } 67 | 68 | }; // namespace base 69 | -------------------------------------------------------------------------------- /base/logging.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2017, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | 5 | #include "base/logging.h" 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #ifdef __APPLE__ 13 | #include 14 | #endif 15 | 16 | namespace base { 17 | 18 | using std::string; 19 | 20 | #ifdef __FreeBSD__ 21 | static constexpr char kProcSelf[] = "/proc/curproc/file"; 22 | #else 23 | static constexpr char kProcSelf[] = "/proc/self/exe"; 24 | #endif 25 | 26 | static constexpr char kDeletedSuffix[] = " (deleted)"; 27 | 28 | constexpr ssize_t kDeletedSuffixLen = sizeof(kDeletedSuffix) - 1; 29 | 30 | string ProgramAbsoluteFileName() { 31 | string res(2048, '\0'); 32 | 33 | #ifdef __APPLE__ 34 | uint32_t sz = res.size(); 35 | 36 | if (_NSGetExecutablePath(&res.front(), &sz) != 0) { 37 | // Buffer size is too small. 38 | return res; 39 | } 40 | 41 | // _NSGetExecutablePath doesn't seem to set sz, so update to exclude zero 42 | // characters from res. 43 | if (sz == res.size()) { 44 | if (auto pos = res.find('\0'); pos != string::npos) { 45 | sz = pos; 46 | } 47 | } 48 | 49 | #else // not __APPLE__ 50 | ssize_t sz = readlink(kProcSelf, &res.front(), res.size()); 51 | CHECK_GT(sz, 0); 52 | if (sz > kDeletedSuffixLen) { 53 | // When binary was deleted, linux link contains kDeletedSuffix at the end. 54 | // Lets strip it. 55 | if (res.compare(sz - kDeletedSuffixLen, kDeletedSuffixLen, kDeletedSuffix) == 0) { 56 | sz -= kDeletedSuffixLen; 57 | res[sz] = '\0'; 58 | } 59 | } 60 | #endif 61 | res.resize(sz); 62 | return res; 63 | } 64 | 65 | string ProgramBaseName() { 66 | string res = ProgramAbsoluteFileName(); 67 | size_t pos = res.rfind("/"); 68 | if (pos == string::npos) 69 | return res; 70 | return res.substr(pos + 1); 71 | } 72 | 73 | string MyUserName() { 74 | const char* str = std::getenv("USER"); 75 | return str ? str : string("unknown-user"); 76 | } 77 | 78 | #ifndef USE_ABSL_LOG 79 | void ConsoleLogSink::send(google::LogSeverity severity, const char* full_filename, 80 | const char* base_filename, int line, const struct ::tm* tm_time, 81 | const char* message, size_t message_len) { 82 | std::cout.write(message, message_len); 83 | std::cout << std::endl; 84 | } 85 | 86 | ConsoleLogSink* ConsoleLogSink::instance() { 87 | static ConsoleLogSink sink; 88 | return &sink; 89 | } 90 | #endif 91 | 92 | const char* kProgramName = ""; 93 | 94 | } // namespace base 95 | -------------------------------------------------------------------------------- /base/logging.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | 5 | #pragma once 6 | 7 | #ifdef USE_ABSL_LOG 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #define CHECK ABSL_CHECK 15 | #define CHECK_GT ABSL_CHECK_GT 16 | #define CHECK_LT ABSL_CHECK_LT 17 | #define CHECK_EQ ABSL_CHECK_EQ 18 | #define CHECK_NE ABSL_CHECK_NE 19 | #define CHECK_GE ABSL_CHECK_GE 20 | #define CHECK_LE ABSL_CHECK_LE 21 | 22 | #define DCHECK ABSL_DCHECK 23 | #define DCHECK_GT ABSL_DCHECK_GT 24 | #define DCHECK_LT ABSL_DCHECK_LT 25 | #define DCHECK_EQ ABSL_DCHECK_EQ 26 | #define DCHECK_NE ABSL_DCHECK_NE 27 | #define DCHECK_GE ABSL_DCHECK_GE 28 | #define DCHECK_LE ABSL_DCHECK_LE 29 | 30 | #define DVLOG ABSL_DVLOG 31 | #define VLOG ABSL_VLOG 32 | #define LOG ABSL_LOG 33 | #define LOG_IF ABSL_LOG_IF 34 | #define LOG_FIRST_N ABSL_LOG_FIRST_N 35 | #define VLOG_IF(verboselevel, condition) \ 36 | ABSL_LOG_IF(INFO, (condition) && ABSL_VLOG_IS_ON(verboselevel)) 37 | 38 | 39 | template T* CHECK_NOTNULL(T* t) { 40 | CHECK(t); 41 | return t; 42 | } 43 | 44 | #else 45 | #include 46 | 47 | #define CONSOLE_INFO LOG_TO_SINK(base::ConsoleLogSink::instance(), INFO) 48 | 49 | #endif 50 | 51 | #include 52 | 53 | namespace base { 54 | std::string ProgramAbsoluteFileName(); 55 | 56 | std::string ProgramBaseName(); 57 | 58 | std::string MyUserName(); 59 | 60 | #ifdef USE_ABSL_LOG 61 | 62 | inline void FlushLogs() { 63 | absl::FlushLogSinks(); 64 | } 65 | 66 | inline int SetVLogLevel(std::string_view module_pattern, int log_level) { 67 | return absl::SetVLogLevel(module_pattern, log_level); 68 | } 69 | 70 | #else 71 | 72 | inline void FlushLogs() { 73 | google::FlushLogFiles(google::INFO); 74 | } 75 | 76 | inline int SetVLogLevel(std::string_view module_pattern, int log_level) { 77 | return google::SetVLOGLevel(module_pattern.data(), log_level); 78 | } 79 | 80 | class ConsoleLogSink : public google::LogSink { 81 | public: 82 | virtual void send(google::LogSeverity severity, const char* full_filename, 83 | const char* base_filename, int line, const struct ::tm* tm_time, 84 | const char* message, size_t message_len) override; 85 | 86 | static ConsoleLogSink* instance(); 87 | }; 88 | #endif 89 | extern const char* kProgramName; 90 | 91 | } // namespace base 92 | -------------------------------------------------------------------------------- /base/mpmc_bounded_queue_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #include "base/mpmc_bounded_queue.h" 6 | 7 | #include 8 | 9 | #include "base/gtest.h" 10 | #include "base/logging.h" 11 | 12 | using namespace std; 13 | 14 | namespace base { 15 | 16 | class MPMCTest : public testing::Test {}; 17 | 18 | struct A { 19 | static int ref; 20 | 21 | A() { 22 | ++ref; 23 | } 24 | A(const A&) { 25 | ++ref; 26 | } 27 | A(A&&) { 28 | ++ref; 29 | } 30 | 31 | ~A() { 32 | --ref; 33 | } 34 | }; 35 | 36 | struct Moveable { 37 | static int ref; 38 | 39 | Moveable() { 40 | ++ref; 41 | } 42 | 43 | ~Moveable() { 44 | --ref; 45 | } 46 | 47 | Moveable(const Moveable&) = delete; 48 | void operator=(const Moveable&) = delete; 49 | 50 | Moveable(Moveable&&) { 51 | ++ref; 52 | } 53 | 54 | Moveable& operator=(Moveable&&) = default; 55 | }; 56 | 57 | int A::ref = 0; 58 | int Moveable::ref = 0; 59 | 60 | TEST_F(MPMCTest, Enqueue) { 61 | mpmc_bounded_queue q(2); 62 | ASSERT_TRUE(q.try_enqueue(5)); 63 | const int val = 6; 64 | ASSERT_FALSE(q.is_full()); 65 | ASSERT_TRUE(q.try_enqueue(val)); 66 | ASSERT_TRUE(q.is_full()); 67 | ASSERT_FALSE(q.try_enqueue(val)); 68 | 69 | int tmp = 0; 70 | ASSERT_TRUE(q.try_dequeue(tmp)); 71 | EXPECT_EQ(5, tmp); 72 | ASSERT_TRUE(q.try_dequeue(tmp)); 73 | EXPECT_EQ(6, tmp); 74 | ASSERT_FALSE(q.try_dequeue(tmp)); 75 | 76 | mpmc_bounded_queue> sh_q(2); 77 | int* const ptr = new int(5); 78 | ASSERT_TRUE(sh_q.try_enqueue(ptr)); 79 | 80 | auto ptr2 = std::make_unique(3); 81 | ASSERT_TRUE(sh_q.try_enqueue(std::move(ptr2))); 82 | ASSERT_FALSE(ptr2); 83 | ptr2 = std::make_unique(3); 84 | 85 | ASSERT_FALSE(sh_q.try_enqueue(std::move(ptr2))); 86 | ASSERT_TRUE(ptr2); 87 | } 88 | 89 | TEST_F(MPMCTest, Dtor) { 90 | for (unsigned i = 1; i <= 8; ++i) { 91 | mpmc_bounded_queue tst(8); 92 | EXPECT_EQ(0, A::ref); 93 | 94 | for (unsigned j = 0; j < i; ++j) { 95 | EXPECT_TRUE(tst.try_enqueue(A{})); 96 | EXPECT_EQ(j + 1, A::ref); 97 | } 98 | } 99 | } 100 | 101 | TEST_F(MPMCTest, Moveable) { 102 | mpmc_bounded_queue queue(16); 103 | 104 | EXPECT_TRUE(queue.try_enqueue(Moveable{})); 105 | EXPECT_EQ(1, Moveable::ref); 106 | { 107 | Moveable dest; 108 | EXPECT_EQ(2, Moveable::ref); 109 | 110 | EXPECT_TRUE(queue.try_dequeue(dest)); 111 | EXPECT_EQ(1, Moveable::ref); 112 | } 113 | EXPECT_EQ(0, Moveable::ref); 114 | } 115 | 116 | } // namespace base 117 | -------------------------------------------------------------------------------- /base/mpsc_intrusive_queue_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | #include "base/mpsc_intrusive_queue.h" 5 | 6 | #include 7 | 8 | #include "base/gtest.h" 9 | #include "base/logging.h" 10 | 11 | using namespace std; 12 | 13 | namespace base { 14 | 15 | struct TestNode { 16 | std::atomic next{nullptr}; 17 | }; 18 | 19 | void MPSC_intrusive_store_next(TestNode* dest, TestNode* next_node) { 20 | dest->next.store(next_node, std::memory_order_release); 21 | } 22 | 23 | TestNode* MPSC_intrusive_load_next(const TestNode& src) { 24 | return src.next.load(std::memory_order_acquire); 25 | } 26 | 27 | class MPSCTest : public testing::Test { 28 | protected: 29 | MPSCIntrusiveQueue q_; 30 | }; 31 | 32 | TEST_F(MPSCTest, Basic) { 33 | EXPECT_TRUE(q_.Pop() == nullptr); 34 | TestNode a, b, c; 35 | q_.Push(&a); 36 | q_.Push(&b); 37 | q_.Push(&c); 38 | 39 | TestNode* res = q_.Pop(); 40 | EXPECT_EQ(&a, res); 41 | 42 | res = q_.Pop(); 43 | EXPECT_EQ(&b, res); 44 | 45 | res = q_.Pop(); 46 | EXPECT_EQ(&c, res); 47 | 48 | EXPECT_TRUE(q_.Pop() == nullptr); 49 | } 50 | 51 | } // namespace base 52 | -------------------------------------------------------------------------------- /base/pmr/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # base does not depend on this lib 2 | add_library(base_pmr arena.cc) 3 | cxx_link(base_pmr absl_base) 4 | 5 | cxx_test(pod_array_test LABELS CI) 6 | cxx_test(arena_test base_pmr LABELS CI) 7 | -------------------------------------------------------------------------------- /base/pmr/arena.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2021, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #include "base/pmr/arena.h" 6 | 7 | #include 8 | 9 | namespace base { 10 | 11 | static const int kBlockSize = 8192; 12 | using namespace std; 13 | 14 | PmrArena::PmrArena(PMR_NS::memory_resource* mr) : mr_(mr), blocks_(mr) { 15 | } 16 | 17 | PmrArena::~PmrArena() { 18 | for (size_t i = 0; i < blocks_.size(); i++) { 19 | mr_->deallocate(blocks_[i].ptr, blocks_[i].sz); 20 | } 21 | } 22 | 23 | char* PmrArena::AllocateFallback(size_t bytes) { 24 | if (bytes > kBlockSize / 4) { 25 | // Object is more than a quarter of our block size. Allocate it separately 26 | // to avoid wasting too much space in leftover bytes. 27 | char* result = AllocateNewBlock(bytes); 28 | return result; 29 | } 30 | 31 | // We waste the remaining space in the current block. 32 | alloc_ptr_ = AllocateNewBlock(kBlockSize); 33 | alloc_bytes_remaining_ = kBlockSize; 34 | 35 | char* result = alloc_ptr_; 36 | alloc_ptr_ += bytes; 37 | alloc_bytes_remaining_ -= bytes; 38 | return result; 39 | } 40 | 41 | char* PmrArena::AllocateAligned(size_t bytes) { 42 | const int align = sizeof(void*); // We'll align to pointer size 43 | assert((align & (align - 1)) == 0); // Pointer size should be a power of 2 44 | size_t current_mod = reinterpret_cast(alloc_ptr_) & (align - 1); 45 | size_t slop = (current_mod == 0 ? 0 : align - current_mod); 46 | size_t needed = bytes + slop; 47 | char* result; 48 | if (needed <= alloc_bytes_remaining_) { 49 | result = alloc_ptr_ + slop; 50 | alloc_ptr_ += needed; 51 | alloc_bytes_remaining_ -= needed; 52 | } else { 53 | // AllocateFallback always returned aligned memory 54 | result = AllocateFallback(bytes); 55 | } 56 | assert((reinterpret_cast(result) & (align - 1)) == 0); 57 | return result; 58 | } 59 | 60 | char* PmrArena::AllocateNewBlock(uint32_t block_bytes) { 61 | char* result = reinterpret_cast(mr_->allocate(block_bytes)); 62 | blocks_memory_ += block_bytes; 63 | blocks_.push_back(Block{result, block_bytes}); 64 | return result; 65 | } 66 | 67 | void PmrArena::Swap(PmrArena& other) { 68 | swap(other.alloc_ptr_, alloc_ptr_); 69 | swap(other.alloc_bytes_remaining_, alloc_bytes_remaining_); 70 | 71 | swap(other.blocks_, blocks_); 72 | swap(other.blocks_memory_, blocks_memory_); 73 | std::swap(other.mr_, mr_); 74 | } 75 | 76 | } // namespace base 77 | -------------------------------------------------------------------------------- /base/pmr/arena.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "memory_resource.h" 13 | 14 | namespace base { 15 | 16 | // Used to allocate small blobs with size upto 4GB. 17 | class PmrArena { 18 | public: 19 | PmrArena(PMR_NS::memory_resource* mr = PMR_NS::get_default_resource()); 20 | ~PmrArena(); 21 | 22 | // Return a pointer to a newly allocated memory block of "bytes" bytes. 23 | char* Allocate(size_t bytes); 24 | 25 | // Allocate memory with the normal alignment guarantees provided by malloc 26 | char* AllocateAligned(size_t bytes); 27 | 28 | // Returns an estimate of the total memory usage of data allocated 29 | // by the arena (including space allocated but not yet used for user 30 | // allocations). 31 | size_t MemoryUsage() const { 32 | return blocks_memory_ + blocks_.capacity() * sizeof(char*); 33 | } 34 | 35 | void Swap(PmrArena& other); 36 | 37 | private: 38 | char* AllocateFallback(size_t bytes); 39 | char* AllocateNewBlock(uint32_t block_bytes); 40 | 41 | PMR_NS::memory_resource* mr_; 42 | 43 | // Allocation state 44 | char* alloc_ptr_ = nullptr; 45 | size_t alloc_bytes_remaining_ = 0; 46 | 47 | // Bytes of memory in blocks allocated so far 48 | size_t blocks_memory_ = 0; 49 | 50 | struct Block { 51 | char* ptr; 52 | uint32_t sz; 53 | } __attribute__((packed)); 54 | 55 | static_assert(sizeof(Block) == 12); 56 | 57 | // Array of the allocated memory blocks 58 | using BlockAllocator = PMR_NS::polymorphic_allocator; 59 | std::vector blocks_; 60 | 61 | // No copying allowed 62 | PmrArena(const PmrArena&) = delete; 63 | void operator=(const PmrArena&) = delete; 64 | }; 65 | 66 | inline char* PmrArena::Allocate(size_t bytes) { 67 | // The semantics of what to return are a bit messy if we allow 68 | // 0-byte allocations, so we disallow them here (we don't need 69 | // them for our internal use). 70 | assert(bytes > 0); 71 | if (bytes <= alloc_bytes_remaining_) { 72 | char* result = alloc_ptr_; 73 | alloc_ptr_ += bytes; 74 | alloc_bytes_remaining_ -= bytes; 75 | return result; 76 | } 77 | return AllocateFallback(bytes); 78 | } 79 | 80 | } // namespace base 81 | -------------------------------------------------------------------------------- /base/pmr/arena_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2021, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #include "base/pmr/arena.h" 6 | 7 | #include 8 | 9 | #include 10 | 11 | #include "base/gtest.h" 12 | #include "base/logging.h" 13 | #include "strings/human_readable.h" 14 | 15 | namespace base { 16 | 17 | class PmrArenaTest {}; 18 | 19 | TEST(PmrArenaTest, Empty) { 20 | PmrArena arena; 21 | } 22 | 23 | TEST(PmrArenaTest, Simple) { 24 | std::default_random_engine rand(301); 25 | std::vector > allocated; 26 | PmrArena arena; 27 | const int N = 100000; 28 | size_t bytes = 0; 29 | for (int i = 0; i < N; i++) { 30 | size_t s; 31 | if (i % (N / 10) == 0) { 32 | s = i; 33 | } else { 34 | s = (rand() % 4000 == 1) ? rand() % 6000 : (rand() % 10 == 1) ? rand() % 100 : rand() % 20; 35 | } 36 | if (s == 0) { 37 | // Our arena disallows size 0 allocations. 38 | s = 1; 39 | } 40 | char* r; 41 | if (rand() % 10 == 0) { 42 | r = arena.AllocateAligned(s); 43 | } else { 44 | r = arena.Allocate(s); 45 | } 46 | 47 | for (size_t b = 0; b < s; b++) { 48 | // Fill the "i"th allocation with a known bit pattern 49 | r[b] = i % 256; 50 | } 51 | bytes += s; 52 | allocated.push_back(std::make_pair(s, r)); 53 | ASSERT_GE(arena.MemoryUsage(), bytes); 54 | if (i > N / 10) { 55 | ASSERT_LE(arena.MemoryUsage(), bytes * 1.10); 56 | } 57 | } 58 | for (size_t i = 0; i < allocated.size(); i++) { 59 | size_t num_bytes = allocated[i].first; 60 | const char* p = allocated[i].second; 61 | 62 | for (size_t b = 0; b < num_bytes; b++) { 63 | // Check the "i"th allocation for the known bit pattern 64 | ASSERT_EQ(int(p[b]) & 0xff, i % 256); 65 | } 66 | } 67 | } 68 | 69 | } // namespace base 70 | -------------------------------------------------------------------------------- /base/pmr/memory_resource.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /// A workaround for libc++ versions with only experimental memory_resource support. 3 | 4 | #include 5 | 6 | #if defined(__clang__) && !defined(__cpp_lib_memory_resource) 7 | #include 8 | namespace PMR_NS = std::experimental::pmr; 9 | #else 10 | #include 11 | namespace PMR_NS = std::pmr; 12 | #endif 13 | -------------------------------------------------------------------------------- /base/pmr/pod_array_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2017, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | 5 | #include "base/pod_array.h" 6 | 7 | #include "base/gtest.h" 8 | 9 | namespace base { 10 | class PodArrayTest {}; 11 | 12 | TEST(BitsTest, Padded) { 13 | PODArray arr; 14 | typedef decltype(arr)::value_type value_t; 15 | arr.push_back(0); 16 | EXPECT_EQ(0, (ptrdiff_t)arr.data() % arr.alignment_v) << arr.data(); 17 | EXPECT_EQ(arr.alignment_v / sizeof(value_t), arr.capacity()); 18 | 19 | for (unsigned i = 1; i < 1024; ++i) 20 | arr.push_back(i); 21 | for (unsigned i = 0; i < 1024; ++i) { 22 | ASSERT_EQ(i, arr[i]); 23 | } 24 | EXPECT_EQ(1024, arr.allocated_size() / sizeof(value_t)); 25 | 26 | arr.emplace_back(0); 27 | 28 | EXPECT_EQ(0, arr.back()); 29 | EXPECT_EQ(1025, arr.size()); 30 | EXPECT_EQ(2048, arr.capacity()); 31 | } 32 | 33 | } // namespace base 34 | -------------------------------------------------------------------------------- /base/pod_array.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #pragma once 6 | 7 | #include "base/pmr/pod_array.h" 8 | -------------------------------------------------------------------------------- /base/proc_util.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | #include "base/proc_util.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "base/integral_types.h" 12 | #include "base/logging.h" 13 | 14 | // for MacOS we declare explicitly. 15 | extern "C" char** environ; 16 | 17 | namespace base { 18 | 19 | // Runs a child process efficiently. 20 | // argv must be null terminated array of arguments where the first item in the array is the base 21 | // name of the executable passed by path. 22 | // Returns 0 if child process run successfully and updates child_status with its return status. 23 | static int spawn(const char* path, char* argv[], int* child_status) { 24 | pid_t pid; 25 | // posix_spawn in this configuration calls vfork without duplicating parents 26 | // virtual memory for the child. That's all we want. 27 | int status = posix_spawn(&pid, path, NULL, NULL, argv, environ); 28 | if (status != 0) 29 | return status; 30 | 31 | if (waitpid(pid, child_status, 0) == -1) 32 | return errno; 33 | return 0; 34 | } 35 | 36 | namespace sys { 37 | 38 | void GetKernelVersion(KernelVersion* version) { 39 | struct utsname buffer; 40 | 41 | CHECK_EQ(0, uname(&buffer)); 42 | 43 | int res = sscanf(buffer.release, "%u.%u.%u-%u", &version->kernel, &version->major, 44 | &version->minor, &version->patch); 45 | CHECK(res == 3 || res == 4) << res; 46 | } 47 | 48 | } // namespace sys 49 | 50 | int sh_exec(const char* cmd) { 51 | char sh_bin[] = "sh"; 52 | char arg1[] = "-c"; 53 | 54 | std::string cmd2(cmd); 55 | 56 | char* argv[] = {sh_bin, arg1, &cmd2.front(), NULL}; 57 | int child_status = 0; 58 | return spawn("/bin/sh", argv, &child_status); 59 | } 60 | 61 | } // namespace base 62 | -------------------------------------------------------------------------------- /base/proc_util.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | 5 | #ifndef PROC_STATUS_H 6 | #define PROC_STATUS_H 7 | 8 | #pragma once 9 | 10 | namespace base { 11 | 12 | namespace sys { 13 | struct KernelVersion { 14 | unsigned kernel; 15 | unsigned major; 16 | unsigned minor; 17 | unsigned patch; 18 | }; 19 | 20 | void GetKernelVersion(KernelVersion* version); 21 | } // namespace sys 22 | 23 | // Runs sh with the command. Returns 0 if succeeded. Child status is ignored. 24 | int sh_exec(const char* cmd); 25 | 26 | } // namespace base 27 | 28 | #endif // PROC_STATUS_H 29 | -------------------------------------------------------------------------------- /base/pthread_utils.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | 5 | #include "base/pthread_utils.h" 6 | 7 | #include "base/logging.h" 8 | 9 | #if defined(__APPLE__) && defined(__MACH__) 10 | #define _MAC_OS_ 1 11 | #endif 12 | 13 | namespace base { 14 | 15 | static void* start_cpp_function(void* arg) { 16 | std::function* fp = (std::function*)arg; 17 | CHECK(*fp); 18 | (*fp)(); 19 | delete fp; 20 | 21 | return nullptr; 22 | } 23 | 24 | void InitCondVarWithClock(clockid_t clock_id, pthread_cond_t* var) { 25 | pthread_condattr_t attr; 26 | PTHREAD_CHECK(condattr_init(&attr)); 27 | #ifndef _MAC_OS_ 28 | PTHREAD_CHECK(condattr_setclock(&attr, clock_id)); 29 | #endif 30 | PTHREAD_CHECK(cond_init(var, &attr)); 31 | PTHREAD_CHECK(condattr_destroy(&attr)); 32 | } 33 | 34 | pthread_t StartThread(const char* name, void* (*start_routine)(void*), void* arg) { 35 | CHECK_LT(strlen(name), 16U); 36 | 37 | pthread_attr_t attrs; 38 | PTHREAD_CHECK(attr_init(&attrs)); 39 | PTHREAD_CHECK(attr_setstacksize(&attrs, kThreadStackSize)); 40 | 41 | pthread_t result; 42 | VLOG(1) << "Starting thread " << name; 43 | 44 | PTHREAD_CHECK(create(&result, &attrs, start_routine, arg)); 45 | #ifndef _MAC_OS_ 46 | int my_err = pthread_setname_np(result, name); 47 | if (my_err != 0) { 48 | LOG(WARNING) << "Could not set name on thread " << result << " : " << strerror(my_err); 49 | } 50 | #endif 51 | PTHREAD_CHECK(attr_destroy(&attrs)); 52 | return result; 53 | } 54 | 55 | pthread_t StartThread(const char* name, std::function f) { 56 | return StartThread(name, start_cpp_function, new std::function(std::move(f))); 57 | } 58 | 59 | } // namespace base 60 | -------------------------------------------------------------------------------- /base/pthread_utils.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | #pragma once 5 | 6 | #include // PTHREAD_STACK_MIN 7 | #include 8 | 9 | #include 10 | 11 | #include "base/logging.h" 12 | 13 | #define PTHREAD_CHECK(x) \ 14 | do { \ 15 | int my_err = pthread_##x; \ 16 | CHECK_EQ(0, my_err) << #x << ", error: " << strerror(my_err); \ 17 | } while (false) 18 | 19 | namespace base { 20 | 21 | constexpr size_t kThreadStackSize = 1 << 18; 22 | 23 | void InitCondVarWithClock(clockid_t clock_id, pthread_cond_t* var); 24 | 25 | pthread_t StartThread(const char* name, void* (*start_routine)(void*), void* arg); 26 | pthread_t StartThread(const char* name, std::function f); 27 | 28 | } // namespace base 29 | -------------------------------------------------------------------------------- /base/ring_buffer.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace base { 12 | 13 | // Simple, single-threaded ring buffer. 14 | // T must be default constructible. 15 | template class RingBuffer { 16 | public: 17 | using value_type = T; 18 | 19 | explicit RingBuffer(unsigned cap) : capacity_(cap) { 20 | buf_.reset(new T[cap]); 21 | mask_ = cap - 1; 22 | // cap must be power of 2. 23 | assert((mask_ & capacity_) == 0); 24 | } 25 | 26 | unsigned size() const { 27 | return tail_ - head_; 28 | } 29 | 30 | [[nodiscard]] bool empty() const { 31 | return tail_ == head_; 32 | } 33 | 34 | [[nodiscard]] bool Full() const { 35 | return tail_ - head_ == capacity_; 36 | } 37 | 38 | // Try to inserts into tail of the buffer. 39 | template bool TryEmplace(U&& u) { 40 | T* slot = GetTail(); 41 | if (slot) { 42 | *slot = std::forward(u); 43 | } 44 | return bool(slot); 45 | } 46 | 47 | // Inserts into tail. If buffer is full overrides the first inserted item at head 48 | // and keeps the ring at maximal capacity. 49 | // Returns true if an item was overriden and false otherwise. 50 | template bool EmplaceOrOverride(U&& u) { 51 | bool is_full = Full(); 52 | *GetTail(/*override=*/true) = std::forward(u); 53 | return is_full; 54 | } 55 | 56 | // Gets a pointer to the next tail entry, if `override` is set, override existing 57 | // entries when needed. 58 | // Advances the tail to the next position. 59 | [[nodiscard]] T* GetTail(bool override = false) { 60 | T* res = nullptr; 61 | if (Full()) { 62 | if (override) 63 | head_++; 64 | else 65 | return res; 66 | } 67 | 68 | res = buf_.get() + (tail_ & mask_); 69 | ++tail_; 70 | return res; 71 | } 72 | 73 | // Tries to pull out of buffer's head. 74 | bool TryDeque(T& t) { 75 | if (size() == 0) { 76 | return false; 77 | } 78 | t = std::forward(buf_[head_ & mask_]); 79 | ++head_; 80 | return true; 81 | } 82 | 83 | unsigned capacity() const { 84 | return capacity_; 85 | } 86 | 87 | T& operator[](unsigned index) { 88 | assert(index < size()); 89 | return buf_[(head_ + index) & mask_]; 90 | } 91 | 92 | const T& operator[](unsigned index) const { 93 | assert(index < size()); 94 | return buf_[(head_ + index) & mask_]; 95 | } 96 | 97 | // Requires: n <= size() 98 | void ConsumeHead(unsigned n) { 99 | assert(n <= size()); 100 | head_ += n; 101 | } 102 | 103 | private: 104 | unsigned capacity_; 105 | unsigned mask_; 106 | unsigned head_ = 0; 107 | unsigned tail_ = 0; 108 | 109 | std::unique_ptr buf_; 110 | }; 111 | 112 | } // namespace base 113 | -------------------------------------------------------------------------------- /base/ring_buffer_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #include "base/ring_buffer.h" 6 | 7 | #include 8 | 9 | #include "base/gtest.h" 10 | 11 | TEST(ring_buffer, simple) { 12 | base::RingBuffer buf(4); 13 | 14 | EXPECT_EQ(buf.capacity(), 4); 15 | 16 | for (size_t i = 0; i < 23; i++) 17 | buf.EmplaceOrOverride(i); 18 | 19 | EXPECT_EQ(buf.size(), 4); 20 | EXPECT_FALSE(buf.empty()); 21 | EXPECT_TRUE(buf.Full()); 22 | 23 | EXPECT_EQ(buf[0], 19); 24 | EXPECT_EQ(buf[1], 20); 25 | EXPECT_EQ(buf[2], 21); 26 | EXPECT_EQ(buf[3], 22); 27 | 28 | buf.ConsumeHead(2); 29 | EXPECT_EQ(buf.size(), 2); 30 | EXPECT_FALSE(buf.empty()); 31 | EXPECT_FALSE(buf.Full()); 32 | EXPECT_EQ(buf[0], 21); 33 | EXPECT_EQ(buf[1], 22); 34 | 35 | int res; 36 | EXPECT_TRUE(buf.TryDeque(res)); 37 | EXPECT_EQ(res, 21); 38 | EXPECT_TRUE(buf.TryDeque(res)); 39 | EXPECT_EQ(res, 22); 40 | EXPECT_FALSE(buf.TryDeque(res)); 41 | 42 | EXPECT_EQ(buf.size(), 0); 43 | EXPECT_TRUE(buf.empty()); 44 | EXPECT_FALSE(buf.Full()); 45 | } 46 | 47 | TEST(ring_buffer, TryEmplace) { 48 | base::RingBuffer buf(4); 49 | 50 | EXPECT_TRUE(buf.TryEmplace(4)); 51 | for (size_t i = 0; i < 4; i++) 52 | buf.EmplaceOrOverride(i); 53 | EXPECT_FALSE(buf.TryEmplace(4)); 54 | } 55 | 56 | TEST(ring_buffer, GetTail) { 57 | base::RingBuffer buf(4); 58 | 59 | EXPECT_TRUE(buf.GetTail()); 60 | EXPECT_EQ(buf.size(), 1); 61 | 62 | for (size_t i = 0; i < 4; i++) 63 | buf.EmplaceOrOverride(i); 64 | 65 | EXPECT_FALSE(buf.GetTail()); 66 | EXPECT_TRUE(buf.GetTail(/*override=*/true)); 67 | } 68 | -------------------------------------------------------------------------------- /base/segment_pool.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #include "base/segment_pool.h" 6 | 7 | #include 8 | 9 | #include "base/logging.h" 10 | 11 | namespace base { 12 | 13 | using namespace std; 14 | 15 | optional SegmentPool::Request(unsigned length) { 16 | // Invariant: elem.first + elem.second <= size_ for any elem in the queue. 17 | if (taken_.empty()) { 18 | if (length > size_) 19 | return nullopt; 20 | return taken_.emplace_back(0, length).first; 21 | } 22 | 23 | // If possible, squeeze segment before first occupied segment. 24 | if (size_t head_mark = taken_.front().first; length <= head_mark) 25 | return taken_.emplace_front(head_mark - length, length).first; 26 | 27 | // Otherwise, try appending after last occupied segment. 28 | size_t tail_mark = taken_.back().first + taken_.back().second; 29 | if (tail_mark + length <= size_) 30 | return taken_.emplace_back(tail_mark, length).first; 31 | 32 | return nullopt; 33 | } 34 | 35 | void SegmentPool::Return(unsigned offset) { 36 | auto it = taken_.end(); 37 | 38 | if (taken_.front().first == offset) // fast path 39 | it = taken_.begin(); 40 | else 41 | it = lower_bound(taken_.begin(), taken_.end(), make_pair(offset, 0u)); 42 | DCHECK(it != taken_.end()); 43 | 44 | it->second = 0; // clear length 45 | 46 | // Deleting from mid-point has linear complexity, so wait till 47 | // head or tail are emptied and we can collapse free segments. 48 | while (!taken_.empty() && taken_.front().second == 0) 49 | taken_.pop_front(); 50 | 51 | while (!taken_.empty() && taken_.back().second == 0) 52 | taken_.pop_back(); 53 | } 54 | 55 | void SegmentPool::Grow(unsigned additional) { 56 | size_ += additional; 57 | } 58 | 59 | } // namespace base 60 | -------------------------------------------------------------------------------- /base/segment_pool.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | namespace base { 11 | 12 | // Simple pool for borrowing subsegments inside a large segment. 13 | // Does not guarantee optimal space usage and instaead prioritizes performance. 14 | struct SegmentPool { 15 | SegmentPool(unsigned size = 0) : size_(size), taken_() { 16 | } 17 | 18 | // Request segment of specified length, returns offset if a free segment found. 19 | // Constant-time, but can fail even if there is theoretically space available 20 | std::optional Request(unsigned length); 21 | 22 | // Return segment by offset returned from Request(). 23 | // Logarithmic by number of borrowed segments on average. 24 | void Return(unsigned offset); 25 | 26 | // Grow internal segment size 27 | void Grow(unsigned additional); 28 | 29 | unsigned Size() const { 30 | return size_; 31 | } 32 | 33 | private: 34 | unsigned size_; 35 | std::deque> taken_; 36 | }; 37 | 38 | } // namespace base 39 | -------------------------------------------------------------------------------- /base/spinlock.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | namespace base { 10 | 11 | class ABSL_LOCKABLE SpinLock { 12 | public: 13 | SpinLock() = default; 14 | SpinLock(const SpinLock&) = delete; 15 | SpinLock& operator=(const SpinLock&) = delete; 16 | 17 | void lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() { 18 | lock_.Lock(); 19 | } 20 | 21 | void unlock() ABSL_UNLOCK_FUNCTION() { 22 | lock_.Unlock(); 23 | } 24 | 25 | bool try_lock() ABSL_EXCLUSIVE_TRYLOCK_FUNCTION(true) { 26 | return lock_.TryLock(); 27 | } 28 | 29 | private: 30 | absl::base_internal::SpinLock lock_; 31 | }; 32 | 33 | } // namespace base 34 | -------------------------------------------------------------------------------- /base/stl_util.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "base/integral_types.h" 10 | 11 | template std::ostream& operator<<(std::ostream& o, const std::vector& vec) { 12 | o << "["; 13 | for (size_t i = 0; i < vec.size(); ++i) { 14 | o << vec[i]; 15 | if (i + 1 < vec.size()) 16 | o << ","; 17 | } 18 | o << "]"; 19 | return o; 20 | } 21 | 22 | template std::ostream& operator<<(std::ostream& o, std::initializer_list vec) { 23 | o << "["; 24 | for (auto it = vec.begin(); it != vec.end(); ++it) { 25 | o << *it; 26 | if (it + 1 != vec.end()) 27 | o << ","; 28 | } 29 | o << "]"; 30 | return o; 31 | } 32 | 33 | namespace base { 34 | 35 | template bool _in(const T& t, std::initializer_list l) { 36 | return std::find(l.begin(), l.end(), t) != l.end(); 37 | } 38 | 39 | } // namespace base 40 | -------------------------------------------------------------------------------- /base/string_view_sso_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | #include "base/string_view_sso.h" 5 | 6 | #include "base/flags.h" 7 | #include "base/gtest.h" 8 | 9 | namespace base { 10 | 11 | class StringViewSSOTest : public testing::Test {}; 12 | 13 | using namespace std; 14 | 15 | TEST_F(StringViewSSOTest, SSO) { 16 | // Test that string_view_sso has a large-enough internal buffer to work with all std::string. 17 | for (int i = 0; i < 100; ++i) { 18 | // Construct a string of size `i` 19 | string s(i, '.'); 20 | string_view_sso sv(s); 21 | string new_s = std::move(s); 22 | s = "bla"; 23 | EXPECT_EQ(new_s, sv); 24 | EXPECT_NE(new_s, s); 25 | } 26 | } 27 | 28 | } // namespace base 29 | -------------------------------------------------------------------------------- /base/varz_node.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2021, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | 5 | #include "base/varz_node.h" 6 | 7 | #include 8 | 9 | namespace base { 10 | 11 | using namespace std; 12 | using absl::StrAppend; 13 | 14 | folly::RWSpinLock VarzListNode::g_varz_lock; 15 | 16 | VarzListNode::VarzListNode(const char* name) : name_(name), prev_(nullptr) { 17 | folly::RWSpinLock::WriteHolder guard(g_varz_lock); 18 | 19 | next_ = global_list(); 20 | if (next_) { 21 | next_->prev_ = this; 22 | } 23 | global_list() = this; 24 | } 25 | 26 | VarzListNode::~VarzListNode() { 27 | folly::RWSpinLock::WriteHolder guard(g_varz_lock); 28 | if (global_list() == this) { 29 | global_list() = next_; 30 | } else { 31 | if (next_) { 32 | next_->prev_ = prev_; 33 | } 34 | if (prev_) { 35 | prev_->next_ = next_; 36 | } 37 | } 38 | } 39 | 40 | string VarzListNode::Format(const AnyValue& av) { 41 | string result; 42 | 43 | switch (av.type) { 44 | case VarzValue::STRING: 45 | StrAppend(&result, "\"", av.str, "\""); 46 | break; 47 | case VarzValue::NUM: 48 | case VarzValue::TIME: 49 | StrAppend(&result, av.num); 50 | break; 51 | case VarzValue::DOUBLE: 52 | StrAppend(&result, av.dbl); 53 | break; 54 | case VarzValue::MAP: 55 | result.append("{ "); 56 | for (const auto& k_v : av.key_value_array) { 57 | StrAppend(&result, "\"", k_v.first, "\": ", Format(k_v.second), ","); 58 | } 59 | result.back() = ' '; 60 | result.append("}"); 61 | break; 62 | } 63 | return result; 64 | } 65 | 66 | VarzListNode*& VarzListNode::global_list() { 67 | static VarzListNode* varz_global_list = nullptr; 68 | return varz_global_list; 69 | } 70 | 71 | void VarzListNode::Iterate(std::function f) { 72 | folly::RWSpinLock::ReadHolder guard(g_varz_lock); 73 | 74 | for (VarzListNode* node = global_list(); node != nullptr; node = node->next_) { 75 | if (node->name_ != nullptr) { 76 | f(node->name_, node->GetData()); 77 | } 78 | } 79 | } 80 | 81 | } // namespace base 82 | -------------------------------------------------------------------------------- /base/varz_node.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | #include "base/RWSpinLock.h" 10 | #include "base/varz_value.h" 11 | 12 | namespace base { 13 | 14 | class VarzListNode { 15 | public: 16 | typedef base::VarzValue AnyValue; 17 | 18 | explicit VarzListNode(const char* name); 19 | virtual ~VarzListNode(); 20 | 21 | // New interface. Func is a function accepting 'const char*' and VarzValue&&. 22 | static void Iterate(std::function f); 23 | 24 | // Old interface. Appends string representations of each active node in the list to res. 25 | // Used for outputting the current state. 26 | static void IterateValues(std::function cb) { 27 | Iterate([&](const char* name, AnyValue&& av) { cb(name, Format(av)); }); 28 | } 29 | 30 | protected: 31 | virtual AnyValue GetData() const = 0; 32 | 33 | const char* name_; 34 | 35 | static std::string Format(const AnyValue& av); 36 | 37 | private: 38 | // Returns the head to varz linked list. Note that the list becomes invalid after at least one 39 | // linked list node was destroyed. 40 | static VarzListNode*& global_list(); 41 | static folly::RWSpinLock g_varz_lock; 42 | 43 | VarzListNode* next_; 44 | VarzListNode* prev_; 45 | }; 46 | 47 | } // namespace base 48 | -------------------------------------------------------------------------------- /base/varz_value.h: -------------------------------------------------------------------------------- 1 | // Copyright 2018, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace base { 12 | 13 | class VarzValue { 14 | public: 15 | typedef std::vector> Map; 16 | 17 | // Should be union but too complicated to code it in c++11. 18 | int64_t num; 19 | double dbl; 20 | Map key_value_array; 21 | std::string str; 22 | 23 | enum Type { NUM, STRING, MAP, DOUBLE, TIME } type; 24 | 25 | VarzValue(std::string s) : str(std::move(s)), type(STRING) { 26 | } 27 | 28 | VarzValue(Map s) : key_value_array(std::move(s)), type(MAP) { 29 | } 30 | 31 | VarzValue(const VarzValue&) = default; 32 | 33 | VarzValue& operator=(const VarzValue&) = default; 34 | 35 | static VarzValue FromTime(time_t t) { 36 | return VarzValue{t, TIME}; 37 | } 38 | 39 | static VarzValue FromDouble(double d) { 40 | return VarzValue{d}; 41 | } 42 | 43 | static VarzValue FromInt(int64_t n) { 44 | return VarzValue{n, NUM}; 45 | } 46 | 47 | private: 48 | VarzValue(int64_t n, Type t) : num(n), type(t) { 49 | } 50 | VarzValue(double d) : dbl(d), type(DOUBLE) { 51 | } 52 | }; 53 | 54 | } // namespace base 55 | -------------------------------------------------------------------------------- /base/zipf_gen.h: -------------------------------------------------------------------------------- 1 | // 2 | // zipfian_generator.h 3 | // YCSB-cpp 4 | // 5 | // Copyright (c) 2020 Youngjae Lee . 6 | // Copyright (c) 2014 Jinglei Ren . 7 | // 8 | // Cosmetic changes by Roman Gershman. 9 | 10 | #ifndef YCSB_C_ZIPFIAN_GENERATOR_H_ 11 | #define YCSB_C_ZIPFIAN_GENERATOR_H_ 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace base { 19 | 20 | class ZipfianGenerator { 21 | public: 22 | static constexpr double kZipfianConst = 0.99; 23 | static constexpr uint64_t kMaxNumItems = (UINT64_MAX >> 24); 24 | 25 | ZipfianGenerator(uint64_t num_items) : ZipfianGenerator(0, num_items - 1) { 26 | } 27 | 28 | ZipfianGenerator(uint64_t min, uint64_t max, double zipfian_const = kZipfianConst) 29 | : ZipfianGenerator(min, max, zipfian_const, Zeta(0, max - min + 1, zipfian_const, 0)) { 30 | } 31 | 32 | ZipfianGenerator(uint64_t min, uint64_t max, double zipfian_const, double zeta_n) 33 | : items_(max - min + 1), base_(min), theta_(zipfian_const) { 34 | assert(items_ >= 2 && items_ < kMaxNumItems); 35 | 36 | zeta_2_ = Zeta(0, 2, theta_, 0); 37 | 38 | alpha_ = 1.0 / (1.0 - theta_); 39 | zeta_n_ = zeta_n; 40 | count_for_zeta_ = items_; 41 | eta_ = Eta(); 42 | } 43 | 44 | template uint64_t Next(URNG& u); 45 | 46 | private: 47 | double Eta() { 48 | return (1 - std::pow(2.0 / items_, 1 - theta_)) / (1 - zeta_2_ / zeta_n_); 49 | } 50 | 51 | /// 52 | /// Calculate the zeta constant needed for a distribution. 53 | /// Do this incrementally from the last_num of items to the cur_num. 54 | /// Use the zipfian constant as theta. Remember the new number of items 55 | /// so that, if it is changed, we can recompute zeta. 56 | /// 57 | static double Zeta(uint64_t last_num, uint64_t cur_num, double theta, double last_zeta) { 58 | double zeta = last_zeta; 59 | for (uint64_t i = last_num + 1; i <= cur_num; ++i) { 60 | zeta += 1 / std::pow(i, theta); 61 | } 62 | return zeta; 63 | } 64 | 65 | uint64_t items_; 66 | uint64_t base_; /// Min number of items to generate 67 | 68 | // Computed parameters for generating the distribution 69 | double theta_, zeta_n_, eta_, alpha_, zeta_2_; 70 | uint64_t count_for_zeta_; /// Number of items used to compute zeta_n 71 | }; 72 | 73 | template uint64_t ZipfianGenerator::Next(URNG& gen) { 74 | std::uniform_real_distribution uniform(0, 1); 75 | double u = uniform(gen); 76 | double uz = u * zeta_n_; 77 | 78 | double res; 79 | if (uz < 1.0) { 80 | res = base_; 81 | } else if (uz < 1.0 + std::pow(0.5, theta_)) { 82 | res = base_ + 1; 83 | } else { 84 | res = base_ + count_for_zeta_ * std::pow(eta_ * u - eta_ + 1, alpha_); 85 | } 86 | return res; 87 | } 88 | 89 | } // namespace base 90 | 91 | #endif // YCSB_C_ZIPFIAN_GENERATOR_H_ 92 | -------------------------------------------------------------------------------- /blaze.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | TARGET_BUILD_TYPE=Debug 4 | BUILD_PREF=build 5 | BUILD_SUF=dbg 6 | COMPILER='g++' 7 | GENERATOR='-GNinja' 8 | LAUNCHER=$(command -v ccache) 9 | if [ -x $LAUNCHER ]; then 10 | echo "Using launcher $LAUNCHER" 11 | LAUNCHER="-DCMAKE_CXX_COMPILER_LAUNCHER=$LAUNCHER" 12 | else 13 | LAUNCHER='' 14 | fi 15 | 16 | for ((i=1;i <= $#;));do 17 | ARG=${!i} 18 | case "$ARG" in 19 | -release) 20 | TARGET_BUILD_TYPE=Release 21 | BUILD_SUF=opt 22 | shift 23 | ;; 24 | -clang) 25 | COMPILER=`which clang++` 26 | BUILD_PREF=clang 27 | shift 28 | ;; 29 | -ninja) 30 | GENERATOR='-GNinja' 31 | shift 32 | ;; 33 | -make) 34 | GENERATOR=-G'Unix Makefiles' 35 | shift 36 | ;; 37 | -D*) # bypass flags 38 | i=$((i + 1)) 39 | ;; 40 | *) 41 | echo bad option "$ARG" 42 | exit 1 43 | ;; 44 | esac 45 | done 46 | 47 | BUILD_DIR=${BUILD_PREF}-${BUILD_SUF} 48 | 49 | set -x 50 | 51 | result="$(cmake --version | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+')" 52 | major="${result%%.*}" 53 | remainder="${result#*.}" 54 | minor="${remainder%.*}" 55 | 56 | if [ "$major" -lt 3 ] || 57 | ( [ "$major" -eq 3 ] && 58 | ( [ "$minor" -lt 4 ] )) ; then 59 | echo "you are using an older version of cmake, need cmake >= 3.4" 60 | exit 1 61 | fi 62 | 63 | cmake -L -B $BUILD_DIR -DCMAKE_BUILD_TYPE=$TARGET_BUILD_TYPE -DCMAKE_CXX_COMPILER=$COMPILER \ 64 | "$GENERATOR" $LAUNCHER "$@" 65 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(pingserver) 2 | add_subdirectory(redis_dict) 3 | 4 | add_executable(raw_echo_server raw_echo_server.cc) 5 | cxx_link(raw_echo_server base fibers2 TRDP::uring TRDP::gperf) 6 | 7 | add_executable(echo_server echo_server.cc) 8 | cxx_link(echo_server base fibers2 http_server_lib TRDP::gperf) 9 | 10 | # add_executable(proactor_stress proactor_stress.cc) 11 | # cxx_link(proactor_stress base uring_fiber_lib http_server_lib) 12 | 13 | add_executable(s3_demo s3_demo.cc) 14 | cxx_link(s3_demo base awsv2_lib) 15 | 16 | add_executable(gcs_demo gcs_demo.cc) 17 | cxx_link(gcs_demo gcp_lib azure_lib) 18 | 19 | add_executable(https_client_cli https_client_cli.cc) 20 | cxx_link(https_client_cli base fibers2 http_client_lib tls_lib) 21 | -------------------------------------------------------------------------------- /examples/pingserver/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(ping_iouring_server ping_iouring_server.cc resp_parser.cc) 2 | cxx_link(ping_iouring_server base fibers2 tls_lib http_server_lib) 3 | -------------------------------------------------------------------------------- /examples/pingserver/resp_parser.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace redis { 12 | 13 | class RespParser { 14 | public: 15 | enum Status { 16 | RESP_OK, 17 | MORE_INPUT, 18 | INVALID_ARRAYLEN, 19 | INVALID_BULKLEN, 20 | INVALID_STRING, 21 | INVALID_INT 22 | }; 23 | using Buffer = absl::Span; 24 | 25 | explicit RespParser() { 26 | } 27 | 28 | // It's a zero-copy parser. A user should not invalidate str if the parser returns COMMAND_READY 29 | // as long as he continues accessing RespExpr. However, if parser returns MORE_INPUT a user may 30 | // invalidate str because parser caches the intermediate state internally according to 'consumed' 31 | // result. A parser does not guarantee to consume the string or part of it if less than a minimal 32 | // threshold was passed and COMMAN_READY is not reached. 33 | Status Parse(Buffer str, uint32_t* consumed, std::vector* res); 34 | 35 | private: 36 | enum ParseResult : uint8_t { 37 | OK, 38 | MORE, 39 | INVALID, 40 | }; 41 | 42 | void InitStart(uint8_t prefix_b, std::vector* res); 43 | void CacheState(std::vector* res); 44 | 45 | // Skips the first character (*). 46 | Status ConsumeArrayLen(Buffer str); 47 | Status ParseArg(Buffer str); 48 | Status ConsumeBulk(Buffer str); 49 | Status ParseInline(Buffer str); 50 | 51 | // Updates last_consumed_ 52 | ParseResult ParseNum(Buffer str, int64_t* res); 53 | void HandleFinishArg(); 54 | void ExtendLastString(Buffer str); 55 | 56 | enum State : uint8_t { 57 | START = 0, 58 | INLINE, 59 | ARRAY_LEN, 60 | PARSE_ARG, // Parse [$:+-]string\r\n 61 | BULK_STR, 62 | FINISH_ARG, 63 | CMD_COMPLETE, 64 | }; 65 | 66 | State state_ = START; 67 | Status last_status_ = RESP_OK; 68 | 69 | uint32_t last_consumed_ = 0; 70 | uint32_t bulk_len_ = 0; 71 | uint32_t last_cached_level_ = 0, last_cached_index_ = 0; 72 | 73 | absl::InlinedVector*>, 4> arr_stack_; 74 | std::vector>> ast_vec_; 75 | 76 | using BlobPtr = std::unique_ptr; 77 | std::vector buf_stash_; 78 | std::vector* top_ = nullptr; 79 | bool is_broken_token_ = false; 80 | }; 81 | 82 | } // namespace redis 83 | -------------------------------------------------------------------------------- /examples/redis_dict/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(redis_dict alloc.c dict.c sds.c) -------------------------------------------------------------------------------- /examples/redis_dict/fmacros.h: -------------------------------------------------------------------------------- 1 | #ifndef __HIREDIS_FMACRO_H 2 | #define __HIREDIS_FMACRO_H 3 | 4 | #define _XOPEN_SOURCE 600 5 | #define _POSIX_C_SOURCE 200112L 6 | 7 | #if defined(__APPLE__) && defined(__MACH__) 8 | /* Enable TCP_KEEPALIVE */ 9 | #define _DARWIN_C_SOURCE 10 | #endif 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /examples/redis_dict/sdsalloc.h: -------------------------------------------------------------------------------- 1 | /* SDSLib 2.0 -- A C dynamic strings library 2 | * 3 | * Copyright (c) 2006-2015, Salvatore Sanfilippo 4 | * Copyright (c) 2015, Oran Agra 5 | * Copyright (c) 2015, Redis Labs, Inc 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions are met: 10 | * 11 | * * Redistributions of source code must retain the above copyright notice, 12 | * this list of conditions and the following disclaimer. 13 | * * Redistributions in binary form must reproduce the above copyright 14 | * notice, this list of conditions and the following disclaimer in the 15 | * documentation and/or other materials provided with the distribution. 16 | * * Neither the name of Redis nor the names of its contributors may be used 17 | * to endorse or promote products derived from this software without 18 | * specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | * POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | /* SDS allocator selection. 34 | * 35 | * This file is used in order to change the SDS allocator at compile time. 36 | * Just define the following defines to what you want to use. Also add 37 | * the include of your alternate allocator if needed (not needed in order 38 | * to use the default libc allocator). */ 39 | 40 | #include "alloc.h" 41 | 42 | #define s_malloc hi_malloc 43 | #define s_realloc hi_realloc 44 | #define s_free hi_free 45 | -------------------------------------------------------------------------------- /install-dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | apt install -y cmake libunwind-dev zip bison ninja-build autoconf-archive libtool 6 | apt install -y curl 7 | g++ --version 8 | 9 | BVER=1.76.0 10 | BOOST=boost_${BVER//./_} # replace all . with _ 11 | 12 | # For sake of boost install we always use g++. 13 | export CXX=g++ 14 | 15 | install_boost() { 16 | mkdir -p /tmp/boost && pushd /tmp/boost 17 | if ! [ -d $BOOST ]; then 18 | url="https://boostorg.jfrog.io/artifactory/main/release/${BVER}/source/$BOOST.tar.bz2" 19 | echo "Downloading from $url" 20 | if ! [ -e $BOOST.tar.bz2 ]; then wget -nv ${url} -O $BOOST.tar.bz2; fi 21 | 22 | tar -xjf $BOOST.tar.bz2 && chown ${SUDO_USER}:${SUDO_USER} -R $BOOST.tar.bz2 $BOOST 23 | fi 24 | 25 | booststap_arg="--prefix=/opt/${BOOST} --without-libraries=graph_parallel,graph,wave,test,mpi,python" 26 | cd $BOOST 27 | boostrap_cmd=`readlink -f bootstrap.sh` 28 | 29 | echo "CXX compiler ${CXX}" 30 | echo "Running ${boostrap_cmd} ${booststap_arg}" 31 | ${boostrap_cmd} ${booststap_arg} || { cat bootstrap.log; return 1; } 32 | b2_args=(define=BOOST_COROUTINES_NO_DEPRECATION_WARNING=1 link=static variant=release debug-symbols=on 33 | threading=multi --without-test --without-math --without-log --without-locale --without-wave 34 | --without-regex --without-python -j4) 35 | 36 | echo "Building targets with ${b2_args[@]}" 37 | ./b2 "${b2_args[@]}" cxxflags='-std=c++14 -Wno-deprecated-declarations' 38 | ./b2 install "${b2_args[@]}" -d0 39 | chown ${SUDO_USER}:${SUDO_USER} -R ./ 40 | popd 41 | } 42 | 43 | if ! [ -d /opt/${BOOST}/lib ]; then 44 | install_boost 45 | else 46 | echo "Skipping installing ${BOOST}" 47 | fi 48 | 49 | if ! [ -d /opt/boost ]; then 50 | ln -sf /opt/${BOOST} /opt/boost 51 | fi 52 | -------------------------------------------------------------------------------- /io/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(io file.cc file_util.cc io.cc line_reader.cc proc_reader.cc zstd_sinksource.cc) 2 | cxx_link(io base TRDP::zstd) 3 | 4 | add_library(file ALIAS io) 5 | 6 | cxx_test(io_test io LABELS CI) 7 | cxx_test(file_test io DATA testdata/ids.txt.zst LABELS CI) 8 | -------------------------------------------------------------------------------- /io/file_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2021, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | 5 | #include "io/file.h" 6 | 7 | #include 8 | #include 9 | 10 | #include "base/gtest.h" 11 | #include "base/logging.h" 12 | #include "io/file_util.h" 13 | #include "io/line_reader.h" 14 | 15 | namespace io { 16 | 17 | using namespace std; 18 | using testing::EndsWith; 19 | using testing::SizeIs; 20 | class FileTest : public ::testing::Test { 21 | protected: 22 | }; 23 | 24 | TEST_F(FileTest, Util) { 25 | string path1 = base::GetTestTempPath("foo1.txt"); 26 | WriteStringToFileOrDie("foo", path1); 27 | string path2 = base::GetTestTempPath("foo2.txt"); 28 | WriteStringToFileOrDie("foo", path2); 29 | 30 | string glob = base::GetTestTempPath("foo?.txt"); 31 | Result res = StatFiles(glob); 32 | ASSERT_TRUE(res); 33 | ASSERT_THAT(res.value(), SizeIs(2)); 34 | EXPECT_THAT(res.value()[0].name, EndsWith("/foo1.txt")); 35 | EXPECT_THAT(res.value()[1].name, EndsWith("/foo2.txt")); 36 | 37 | auto res2 = ReadFileToString(path2); 38 | ASSERT_TRUE(res2); 39 | EXPECT_EQ(res2.value(), "foo"); 40 | } 41 | 42 | TEST_F(FileTest, LineReader) { 43 | string path = base::ProgramRunfile("testdata/ids.txt.zst"); 44 | Result src = OpenUncompressed(path); 45 | ASSERT_TRUE(src); 46 | 47 | LineReader lr(*src, DO_NOT_TAKE_OWNERSHIP); 48 | string_view line; 49 | uint64_t val; 50 | while (lr.Next(&line)) { 51 | ASSERT_TRUE(absl::SimpleHexAtoi(line, &val)) << lr.line_num(); 52 | } 53 | EXPECT_EQ(48, lr.line_num()); 54 | } 55 | 56 | TEST_F(FileTest, Direct) { 57 | string path = base::GetTestTempPath("write.bin"); 58 | WriteFile::Options opts; 59 | opts.direct = true; 60 | auto res = OpenWrite(path, opts); 61 | ASSERT_TRUE(res); 62 | WriteFile* file = *res; 63 | char* src = nullptr; 64 | constexpr unsigned kLen = 4096; 65 | CHECK_EQ(0, posix_memalign((void**)&src, 4096, kLen)); 66 | memset(src, 'a', 4096); 67 | auto ec = file->Write(string_view(src, kLen)); 68 | free(src); 69 | ASSERT_FALSE(ec) << ec; 70 | 71 | ec = file->Close(); 72 | ASSERT_FALSE(ec); 73 | } 74 | 75 | 76 | } // namespace io 77 | -------------------------------------------------------------------------------- /io/file_util.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | #pragma once 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include "io/io.h" 11 | 12 | namespace io { 13 | 14 | struct StatShort { 15 | std::string name; 16 | time_t last_modified; // nanoseconds since Epoch time. 17 | uint64_t size; 18 | mode_t st_mode; 19 | }; 20 | 21 | using StatShortVec = std::vector; 22 | 23 | Result StatFiles(std::string_view path); 24 | 25 | // Create a file and write a std::string to it. 26 | void WriteStringToFileOrDie(std::string_view contents, std::string_view name); 27 | 28 | // Read an entire file to a std::string. Return true if successful, false 29 | // otherwise. 30 | Result ReadFileToString(std::string_view path); 31 | 32 | } // namespace io 33 | -------------------------------------------------------------------------------- /io/io_buf.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #pragma once 6 | 7 | #include "base/io_buf.h" 8 | 9 | namespace io { 10 | using IoBuf = base::IoBuf; 11 | } -------------------------------------------------------------------------------- /io/line_reader.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | #pragma once 5 | 6 | #include 7 | 8 | #include "base/integral_types.h" 9 | #include "io/io.h" 10 | 11 | namespace io { 12 | 13 | // Assumes that source provides stream of text characters. 14 | // Will break the stream into lines ending with EOL (either \r\n\ or \n). 15 | class LineReader { 16 | public: 17 | enum { DEFAULT_BUF_LOG = 17 }; 18 | 19 | class Iterator { 20 | std::string scratch_; 21 | std::string_view result_; 22 | LineReader* master_ = nullptr; 23 | 24 | public: 25 | Iterator(LineReader* lr = nullptr); 26 | 27 | std::string_view operator*() const { 28 | return result_; 29 | } 30 | 31 | Iterator& operator++(); 32 | 33 | bool operator==(const Iterator& o) const { 34 | return o.master_ == master_; 35 | } 36 | 37 | bool operator!=(const Iterator& o) const { 38 | return o.master_ != master_; 39 | } 40 | }; 41 | 42 | LineReader(Source* source, Ownership ownership, uint32_t buf_log = DEFAULT_BUF_LOG) 43 | : source_(source), ownership_(ownership) { 44 | Init(buf_log); 45 | } 46 | 47 | ~LineReader(); 48 | 49 | uint64_t line_num() const { 50 | return line_num_ & (kEofMask - 1); 51 | } 52 | 53 | // Sets the result to point to null-terminated line. 54 | // Empty lines are also returned. 55 | // Returns true if new line was found or false if end of stream was reached. 56 | bool Next(std::string_view* result, std::string* scratch = nullptr); 57 | 58 | ::std::error_code status() const { 59 | return status_; 60 | } 61 | 62 | void set_line_len_limit(uint64_t lim) { 63 | line_len_limit_ = lim; 64 | } 65 | 66 | uint64_t line_len_limit() const { 67 | return line_len_limit_; 68 | } 69 | 70 | Iterator begin() { 71 | return Iterator{this}; 72 | } 73 | 74 | Iterator end() { 75 | return Iterator{}; 76 | } 77 | 78 | private: 79 | void Init(uint32_t buf_log); 80 | 81 | Source* source_; 82 | uint64_t line_num_ = 0; // MSB bit means EOF was reached. 83 | uint64_t line_len_limit_ = -1; 84 | 85 | std::unique_ptr buf_; 86 | char *next_, *end_; 87 | 88 | Ownership ownership_; 89 | uint32_t page_size_; 90 | std::string scratch_; 91 | std::error_code status_; 92 | 93 | static constexpr uint64_t kEofMask = 1ULL << 63; 94 | }; 95 | 96 | namespace ini { 97 | 98 | using Section = std::string; 99 | using Contents = std::unordered_map>; 100 | 101 | io::Result Parse(Source* source, Ownership ownership); 102 | 103 | }; // namespace ini 104 | 105 | } // namespace io 106 | -------------------------------------------------------------------------------- /io/proc_reader.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include "io/io.h" 10 | 11 | namespace io { 12 | 13 | // Sizes in bytes as opposed to status files where sizes are in kb. 14 | struct StatusData { 15 | size_t vm_peak = 0; 16 | size_t vm_rss = 0; 17 | size_t vm_size = 0; 18 | size_t vm_swap = 0; 19 | size_t hugetlb_pages = 0; 20 | }; 21 | 22 | // Sizes in bytes. 23 | struct MemInfoData { 24 | size_t mem_total = 0; 25 | size_t mem_free = 0; 26 | size_t mem_avail = 0; 27 | size_t mem_buffers = 0; 28 | size_t mem_cached = 0; 29 | size_t mem_SReclaimable = 0; 30 | size_t swap_cached = 0; 31 | size_t swap_total = 0; 32 | size_t swap_free = 0; 33 | 34 | // in meminfo.c of free program - mem used 35 | // equals to: total - (free + buffers + cached + SReclaimable). 36 | }; 37 | 38 | struct SelfStat { 39 | uint64_t start_time_sec = 0; 40 | uint64_t maj_flt = 0; 41 | }; 42 | 43 | Result ReadStatusInfo(); 44 | Result ReadMemInfo(); 45 | Result ReadSelfStat(); 46 | 47 | // key,value list from /etc/os-release 48 | using DistributionInfo = std::vector>; 49 | 50 | Result ReadDistributionInfo(); 51 | 52 | struct TcpInfo { 53 | bool is_ipv6 = false; 54 | // TCP state. 55 | // https://en.wikipedia.org/wiki/Transmission_Control_Protocol#Protocol_operation 56 | // https://www.ibm.com/support/pages/network-connection-state-meanings-and-transitions 57 | unsigned state = 0; 58 | unsigned local_port = 0; 59 | unsigned remote_port = 0; 60 | unsigned inode = 0; 61 | uint32_t local_addr = 0; 62 | uint32_t remote_addr = 0; 63 | unsigned char local_addr6[16] = {0}; 64 | unsigned char remote_addr6[16] = {0}; 65 | }; 66 | 67 | std::string TcpStateToString(unsigned state); 68 | 69 | // sock_inode can be fetched by fstat call on socket file descriptor 70 | Result ReadTcpInfo(ino_t sock_inode); 71 | Result ReadTcp6Info(ino_t sock_inode); 72 | 73 | } // namespace io 74 | -------------------------------------------------------------------------------- /io/testdata/ids.txt.zst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romange/helio/af218326b50fb831b05fdabbf6c2087057127729/io/testdata/ids.txt.zst -------------------------------------------------------------------------------- /io/zstd_sinksource.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2017, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | #define ZSTD_STATIC_LINKING_ONLY 5 | 6 | #include "io/zstd_sinksource.h" 7 | 8 | #include 9 | 10 | #include "base/logging.h" 11 | 12 | namespace io { 13 | 14 | using namespace std; 15 | 16 | #define DC_HANDLE reinterpret_cast(zstd_handle_) 17 | 18 | const unsigned kReadSize = 1 << 12; 19 | 20 | ZStdSource::ZStdSource(Source* upstream) : sub_stream_(upstream) { 21 | CHECK(upstream); 22 | zstd_handle_ = ZSTD_createDStream(); 23 | size_t const res = ZSTD_initDStream(DC_HANDLE); 24 | CHECK(!ZSTD_isError(res)) << ZSTD_getErrorName(res); 25 | buf_.reset(new uint8_t[kReadSize]); 26 | } 27 | 28 | ZStdSource::~ZStdSource() { 29 | ZSTD_freeDStream(DC_HANDLE); 30 | } 31 | 32 | Result ZStdSource::ReadSome(const iovec* v, uint32_t len) { 33 | DCHECK_GT(len, 0u); 34 | 35 | // TODO: fill entire iovec. 36 | ZSTD_outBuffer output = {v->iov_base, v->iov_len, 0}; 37 | 38 | do { 39 | if (offs_ >= buf_len_) { 40 | auto res = sub_stream_->Read(MutableBytes(buf_.get(), kReadSize)); 41 | if (!res) 42 | return res; 43 | if (*res == 0) // EOF 44 | break; 45 | offs_ = 0; 46 | buf_len_ = *res; 47 | } 48 | 49 | ZSTD_inBuffer input{buf_.get() + offs_, buf_len_ - offs_, 0}; 50 | 51 | size_t to_read = ZSTD_decompressStream(DC_HANDLE, &output, &input); 52 | if (ZSTD_isError(to_read)) { 53 | return nonstd::make_unexpected(make_error_code(errc::illegal_byte_sequence)); 54 | } 55 | 56 | offs_ += input.pos; 57 | if (input.pos < input.size) { 58 | CHECK_EQ(output.pos, output.size); // Invariant check. 59 | } 60 | } while (output.pos < output.size); 61 | 62 | return output.pos; 63 | } 64 | 65 | } // namespace io 66 | -------------------------------------------------------------------------------- /io/zstd_sinksource.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include "io/io.h" 10 | 11 | namespace io { 12 | 13 | class ZStdSource : public Source { 14 | public: 15 | explicit ZStdSource(Source* upstream); 16 | ~ZStdSource(); 17 | 18 | private: 19 | Result ReadSome(const iovec* v, uint32_t len) override; 20 | 21 | std::unique_ptr sub_stream_; 22 | void* zstd_handle_; 23 | std::unique_ptr buf_; 24 | uint32_t offs_ = 0, buf_len_ = 0; 25 | }; 26 | 27 | } // namespace io 28 | -------------------------------------------------------------------------------- /patches/abseil-20240116.2.patch: -------------------------------------------------------------------------------- 1 | diff --git a/absl/debugging/internal/symbolize.h b/absl/debugging/internal/symbolize.h 2 | index 5593fde6..d3a2ba28 100644 3 | --- a/absl/debugging/internal/symbolize.h 4 | +++ b/absl/debugging/internal/symbolize.h 5 | @@ -28,7 +28,7 @@ 6 | 7 | #ifdef ABSL_INTERNAL_HAVE_ELF_SYMBOLIZE 8 | #error ABSL_INTERNAL_HAVE_ELF_SYMBOLIZE cannot be directly set 9 | -#elif defined(__ELF__) && defined(__GLIBC__) && !defined(__native_client__) \ 10 | +#elif defined(__ELF__) && !defined(__native_client__) \ 11 | && !defined(__asmjs__) && !defined(__wasm__) 12 | #define ABSL_INTERNAL_HAVE_ELF_SYMBOLIZE 1 13 | 14 | -------------------------------------------------------------------------------- /patches/mimalloc-v2.1.6.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/alloc.c b/src/alloc.c 2 | index 86aaae75..876dfd38 100644 3 | --- a/src/alloc.c 4 | +++ b/src/alloc.c 5 | @@ -577,6 +577,24 @@ mi_decl_nodiscard void* mi_new_reallocn(void* p, size_t newcount, size_t size) { 6 | } 7 | } 8 | 9 | +bool mi_heap_page_is_underutilized(mi_heap_t* heap, void* p, float ratio) mi_attr_noexcept { 10 | + mi_page_t* page = _mi_ptr_page(p); // get the page that this belongs to 11 | + 12 | + mi_heap_t* page_heap = (mi_heap_t*)(mi_atomic_load_acquire(&(page)->xheap)); 13 | + 14 | + // the heap id matches and it is not a full page 15 | + if (mi_likely(page_heap == heap && page->flags.x.in_full == 0)) { 16 | + // first in the list, meaning it's the head of page queue, thus being used for malloc 17 | + if (page->prev == NULL) 18 | + return false; 19 | + 20 | + // this page belong to this heap and is not first in the page queue. Lets check its 21 | + // utilization. 22 | + return page->used <= (unsigned)(page->capacity * ratio); 23 | + } 24 | + return false; 25 | +} 26 | + 27 | // ------------------------------------------------------ 28 | // ensure explicit external inline definitions are emitted! 29 | // ------------------------------------------------------ 30 | -------------------------------------------------------------------------------- /strings/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(strings_lib escaping.cc human_readable.cc) 2 | cxx_link(strings_lib base absl::strings absl::str_format) 3 | 4 | cxx_test(strings_test strings_lib LABELS CI) -------------------------------------------------------------------------------- /strings/escaping.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | #include 5 | 6 | namespace strings { 7 | using namespace std; 8 | 9 | namespace { 10 | inline bool IsValidUrlChar(char ch) { 11 | return absl::ascii_isalnum(ch) || ch == '-' || ch == '_' || ch == '_' || ch == '.' || ch == '!' || 12 | ch == '~' || ch == '*' || ch == '(' || ch == ')'; 13 | } 14 | 15 | static size_t InternalUrlEncode(absl::string_view src, char* dest) { 16 | static const char digits[] = "0123456789ABCDEF"; 17 | 18 | char* start = dest; 19 | for (char ch_c : src) { 20 | unsigned char ch = static_cast(ch_c); 21 | if (IsValidUrlChar(ch)) { 22 | *dest++ = ch_c; 23 | } else { 24 | *dest++ = '%'; 25 | *dest++ = digits[(ch >> 4) & 0x0F]; 26 | *dest++ = digits[ch & 0x0F]; 27 | } 28 | } 29 | *dest = 0; 30 | 31 | return static_cast(dest - start); 32 | } 33 | 34 | inline unsigned int hex_digit_to_int(char c) { 35 | static_assert('0' == 0x30 && 'A' == 0x41 && 'a' == 0x61, "Character set must be ASCII."); 36 | assert(absl::ascii_isxdigit(static_cast(c))); 37 | unsigned int x = static_cast(c); 38 | if (x > '9') { 39 | x += 9; 40 | } 41 | return x & 0xf; 42 | } 43 | 44 | static ssize_t InternalUrlDecode(absl::string_view src, char* dest) { 45 | char* start = dest; 46 | for (unsigned i = 0; i < src.size(); ++i) { 47 | char ch_c = src[i]; 48 | if (ch_c != '%') { 49 | *dest++ = ch_c; 50 | continue; 51 | } 52 | 53 | if (i + 3 > src.size()) { 54 | return -1; 55 | } 56 | 57 | if (!absl::ascii_isxdigit(src[i + 1]) || !absl::ascii_isxdigit(src[i + 2])) { 58 | return -1; 59 | } 60 | unsigned ch = (hex_digit_to_int(src[i + 1]) << 4) | hex_digit_to_int(src[i + 2]); 61 | *dest++ = ch; 62 | i += 2; 63 | } 64 | 65 | *dest = 0; 66 | return dest - start; 67 | } 68 | } // namespace 69 | 70 | void AppendUrlEncoded(const std::string_view src, string* dest) { 71 | size_t sz = dest->size(); 72 | dest->resize(dest->size() + src.size() * 3 + 1); 73 | char* next = &dest->front() + sz; 74 | size_t written = InternalUrlEncode(src, next); 75 | dest->resize(sz + written); 76 | } 77 | 78 | bool AppendUrlDecoded(const std::string_view src, std::string* dest) { 79 | size_t sz = dest->size(); 80 | dest->resize(src.size() + sz); 81 | char* next = &dest->front() + sz; 82 | ssize_t written = InternalUrlDecode(src, next); 83 | if (written < 0) { 84 | dest->resize(sz); 85 | return false; 86 | } 87 | 88 | dest->resize(sz + written); 89 | return true; 90 | } 91 | 92 | } // namespace strings -------------------------------------------------------------------------------- /strings/escaping.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | namespace strings { 10 | 11 | void AppendUrlEncoded(const std::string_view src, std::string* dest); 12 | bool AppendUrlDecoded(const std::string_view src, std::string* dest); 13 | 14 | } // namespace strings -------------------------------------------------------------------------------- /strings/human_readable.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2015 The TensorFlow Authors. All Rights Reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | ==============================================================================*/ 15 | 16 | #ifndef HUMAN_READABLE_NUMBERS_H_ 17 | #define HUMAN_READABLE_NUMBERS_H_ 18 | 19 | #include 20 | #include 21 | 22 | namespace strings { 23 | 24 | // Converts from an int64 to a human readable string representing the 25 | // same number, using decimal powers. e.g. 1200000 -> "1.20M". 26 | std::string HumanReadableNum(int64_t value); 27 | 28 | // Converts from an int64 representing a number of bytes to a 29 | // human readable string representing the same number. 30 | // e.g. 12345678 -> "11.77MiB". 31 | std::string HumanReadableNumBytes(int64_t num_bytes); 32 | 33 | // Converts a time interval as double to a human readable 34 | // string. For example: 35 | // 0.001 -> "1 ms" 36 | // 10.0 -> "10 s" 37 | // 933120.0 -> "10.8 days" 38 | // 39420000.0 -> "1.25 years" 39 | // -10 -> "-10 s" 40 | std::string HumanReadableElapsedTime(double seconds); 41 | 42 | } // namespace strings 43 | 44 | #endif // HUMAN_READABLE_NUMBERS_H_ 45 | -------------------------------------------------------------------------------- /strings/strings_test.cc: -------------------------------------------------------------------------------- 1 | 2 | // Copyright 2021, Beeri 15. All rights reserved. 3 | // Author: Roman Gershman (romange@gmail.com) 4 | // 5 | 6 | #include "base/gtest.h" 7 | #include "base/integral_types.h" 8 | #include "strings/human_readable.h" 9 | 10 | namespace strings { 11 | 12 | TEST(HumanReadableNum, Basic) { 13 | EXPECT_EQ(HumanReadableNum(823), "823"); 14 | EXPECT_EQ(HumanReadableNum(1024), "1.02k"); 15 | EXPECT_EQ(HumanReadableNum(4000), "4.00k"); 16 | EXPECT_EQ(HumanReadableNum(999499), "999.50k"); 17 | EXPECT_EQ(HumanReadableNum(1000000), "1.00M"); 18 | EXPECT_EQ(HumanReadableNum(1048575), "1.05M"); 19 | EXPECT_EQ(HumanReadableNum(1048576), "1.05M"); 20 | EXPECT_EQ(HumanReadableNum(23956812342), "23.96B"); 21 | EXPECT_EQ(HumanReadableNum(123456789012345678), "1.23E+17"); 22 | } 23 | 24 | TEST(HumanReadableNumBytes, Bytes) { 25 | EXPECT_EQ("0B", HumanReadableNumBytes(0)); 26 | EXPECT_EQ("4B", HumanReadableNumBytes(4)); 27 | EXPECT_EQ("1023B", HumanReadableNumBytes(1023)); 28 | 29 | EXPECT_EQ("1.0KiB", HumanReadableNumBytes(1024)); 30 | EXPECT_EQ("1.0KiB", HumanReadableNumBytes(1025)); 31 | EXPECT_EQ("1.5KiB", HumanReadableNumBytes(1500)); 32 | EXPECT_EQ("1.9KiB", HumanReadableNumBytes(1927)); 33 | 34 | EXPECT_EQ("2.0KiB", HumanReadableNumBytes(2048)); 35 | EXPECT_EQ("1.00MiB", HumanReadableNumBytes(1 << 20)); 36 | EXPECT_EQ("11.77MiB", HumanReadableNumBytes(12345678)); 37 | EXPECT_EQ("1.00GiB", HumanReadableNumBytes(1 << 30)); 38 | 39 | EXPECT_EQ("1.00TiB", HumanReadableNumBytes(1LL << 40)); 40 | EXPECT_EQ("1.00PiB", HumanReadableNumBytes(1LL << 50)); 41 | EXPECT_EQ("1.00EiB", HumanReadableNumBytes(1LL << 60)); 42 | 43 | // Try a few negative numbers 44 | EXPECT_EQ("-1B", HumanReadableNumBytes(-1)); 45 | EXPECT_EQ("-4B", HumanReadableNumBytes(-4)); 46 | EXPECT_EQ("-1000B", HumanReadableNumBytes(-1000)); 47 | EXPECT_EQ("-11.77MiB", HumanReadableNumBytes(-12345678)); 48 | EXPECT_EQ("-8E", HumanReadableNumBytes(kint64min)); 49 | } 50 | 51 | TEST(HumanReadableElapsedTime, Basic) { 52 | EXPECT_EQ(HumanReadableElapsedTime(-10), "-10 s"); 53 | EXPECT_EQ(HumanReadableElapsedTime(-0.001), "-1 ms"); 54 | EXPECT_EQ(HumanReadableElapsedTime(-60.0), "-1 min"); 55 | EXPECT_EQ(HumanReadableElapsedTime(0.00000001), "0.01 us"); 56 | EXPECT_EQ(HumanReadableElapsedTime(0.0000012), "1.2 us"); 57 | EXPECT_EQ(HumanReadableElapsedTime(0.0012), "1.2 ms"); 58 | EXPECT_EQ(HumanReadableElapsedTime(0.12), "120 ms"); 59 | EXPECT_EQ(HumanReadableElapsedTime(1.12), "1.12 s"); 60 | EXPECT_EQ(HumanReadableElapsedTime(90.0), "1.5 min"); 61 | EXPECT_EQ(HumanReadableElapsedTime(600.0), "10 min"); 62 | EXPECT_EQ(HumanReadableElapsedTime(9000.0), "2.5 h"); 63 | EXPECT_EQ(HumanReadableElapsedTime(87480.0), "1.01 days"); 64 | EXPECT_EQ(HumanReadableElapsedTime(7776000.0), "2.96 months"); 65 | EXPECT_EQ(HumanReadableElapsedTime(78840000.0), "2.5 years"); 66 | EXPECT_EQ(HumanReadableElapsedTime(382386614.40), "12.1 years"); 67 | EXPECT_EQ(HumanReadableElapsedTime(DBL_MAX), "5.7e+300 years"); 68 | } 69 | } // namespace strings 70 | -------------------------------------------------------------------------------- /tools/docker/Dockerfile.ubuntu-prod: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | from ghcr.io/romange/ubuntu-dev:20 as builder 3 | 4 | # Serves as environment variable during the build 5 | # QEMU_CPU tunes how qemu emulator runs. 6 | ARG QEMU_CPU 7 | 8 | WORKDIR /build 9 | COPY ./ ./ 10 | RUN ./blaze.sh -release -DBoost_USE_STATIC_LIBS=ON 11 | 12 | WORKDIR build-opt 13 | RUN ninja echo_server 14 | 15 | FROM ubuntu:20.04 16 | 17 | # Serves as environment variable during the build phase. 18 | ARG QEMU_CPU 19 | 20 | LABEL org.opencontainers.image.title echo_server 21 | LABEL org.opencontainers.image.source https://github.com/romange/helio 22 | ENV DEBIAN_FRONTEND=noninteractive 23 | 24 | RUN apt update && apt install -y libunwind8 libssl1.1 && \ 25 | rm -rf /var/lib/apt/lists/* 26 | 27 | RUN mkdir /data 28 | WORKDIR /data 29 | COPY tools/docker/entrypoint.sh /usr/local/bin/entrypoint.sh 30 | COPY --from=builder /build/build-opt/echo_server /usr/local/bin/ 31 | 32 | ENTRYPOINT ["entrypoint.sh"] 33 | 34 | EXPOSE 8081 35 | CMD ["echo_server", "--logtostderr"] -------------------------------------------------------------------------------- /tools/docker/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | # first arg is `-some-option` 6 | if [ "${1#-}" != "$1" ]; then 7 | set -- echo_server --logtostderr "$@" 8 | fi 9 | 10 | exec "$@" 11 | -------------------------------------------------------------------------------- /tools/rpm/build_rpm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$#" -ne 1 ]; then 4 | echo "Usage: $0 /path/to/binary" 5 | exit 1 6 | fi 7 | 8 | # Get the full path of the binary 9 | BINARY_PATH=$(realpath "$1") 10 | echo "Preparing $BINARY_PATH" 11 | 12 | # Get the basename to use as the package name 13 | PKG_NAME=$(basename "$BINARY_PATH") 14 | 15 | # Setup RPM build environment in a unique subdirectory under /tmp 16 | RPM_ROOT=$(mktemp -d /tmp/rpmbuild_XXXXXX) 17 | echo "Working dir is $RPM_ROOT" 18 | mkdir -p $RPM_ROOT/{BUILD,RPMS,SOURCES,SPECS} 19 | 20 | # Copy the binary to the SOURCES directory 21 | cp "$BINARY_PATH" "$RPM_ROOT/SOURCES/" 22 | 23 | # Generate a simple RPM spec file 24 | cat < "$RPM_ROOT/SPECS/$PKG_NAME.spec" 25 | Name: $PKG_NAME 26 | Version: 1.0 27 | Release: 1%{?dist} 28 | Summary: Auto generated package for $PKG_NAME 29 | 30 | License: APACHE 2.0 31 | Source0: $PKG_NAME 32 | 33 | %description 34 | Auto generated package for $PKG_NAME 35 | 36 | %prep 37 | %build 38 | 39 | %install 40 | mkdir -p %{buildroot}/usr/local/bin 41 | install -m 755 %{SOURCE0} %{buildroot}/usr/local/bin/ 42 | 43 | %files 44 | /usr/local/bin/$PKG_NAME 45 | 46 | %changelog 47 | EOF 48 | 49 | # Build only the binary RPM package 50 | rpmbuild --define "_topdir $RPM_ROOT" -bb "$RPM_ROOT/SPECS/$PKG_NAME.spec" 51 | 52 | echo "RPM package built and located in $RPM_ROOT/RPMS/" 53 | -------------------------------------------------------------------------------- /util/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cxx_test(accept_server_test fibers2 http_beast_prebuilt LABELS CI) 2 | 3 | find_package(OpenSSL) 4 | 5 | add_subdirectory(fibers) 6 | add_subdirectory(html) 7 | add_subdirectory(metrics) 8 | add_subdirectory(tls) 9 | add_subdirectory(http) 10 | add_subdirectory(cloud) 11 | 12 | if (WITH_AWS) 13 | add_subdirectory(aws) 14 | endif() 15 | -------------------------------------------------------------------------------- /util/accept_server.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "base/pmr/memory_resource.h" 12 | #include "util/connection.h" 13 | #include "util/fibers/synchronization.h" 14 | 15 | namespace util { 16 | 17 | class ListenerInterface; 18 | class ProactorPool; 19 | 20 | class AcceptServer { 21 | AcceptServer(const AcceptServer&) = delete; 22 | void operator=(const AcceptServer&) = delete; 23 | 24 | public: 25 | explicit AcceptServer(ProactorPool* pool, bool break_on_int = true) 26 | : AcceptServer(pool, nullptr, break_on_int) { 27 | } 28 | 29 | AcceptServer(ProactorPool* pool, PMR_NS::memory_resource* mr, bool break_on_int); 30 | 31 | ~AcceptServer(); 32 | 33 | void Run(); 34 | 35 | // If wait is false - does not wait for the server to stop. 36 | // Then you need to run Wait() to wait for proper shutdown. 37 | void Stop(bool wait = false); 38 | 39 | void Wait(); 40 | 41 | // Returns the port number to which the listener was bound. 42 | // Check-fails in case of an error. 43 | uint16_t AddListener(uint16_t port, ListenerInterface* listener); 44 | 45 | // Advanced version that allows to specify bind address. 46 | // bind_addr can be null, in that case the behavior is to bind on all interfaces. 47 | // Does not check-fail - it's responsibility of the caller to check the error code. 48 | std::error_code AddListener(const char* bind_addr, uint16_t port, ListenerInterface* listener); 49 | 50 | // Adds a listener on unix domain sockets. 51 | std::error_code AddUDSListener(const char* path, mode_t permissions, ListenerInterface* listener); 52 | 53 | void TriggerOnBreakSignal(std::function f) { 54 | on_break_hook_ = std::move(f); 55 | } 56 | 57 | void set_back_log(uint16_t backlog) { 58 | backlog_ = backlog; 59 | } 60 | 61 | private: 62 | void BreakListeners(); 63 | 64 | ProactorPool* pool_; 65 | PMR_NS::memory_resource* mr_; 66 | 67 | // Called if a termination signal has been caught (SIGTERM/SIGINT). 68 | std::function on_break_hook_; 69 | 70 | std::vector> list_interface_; 71 | fb2::BlockingCounter ref_bc_; // to synchronize listener threads during the shutdown. 72 | 73 | bool was_run_ = false; 74 | bool break_on_int_; 75 | 76 | uint16_t backlog_ = 128; 77 | }; 78 | 79 | } // namespace util 80 | -------------------------------------------------------------------------------- /util/asio_stream_adapter.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | #include "util/fiber_socket_base.h" 11 | 12 | namespace util { 13 | 14 | template class AsioStreamAdapter { 15 | Socket& s_; 16 | 17 | public: 18 | using error_code = ::boost::system::error_code; 19 | 20 | explicit AsioStreamAdapter(Socket& s) : s_(s) { 21 | } 22 | 23 | // Read/Write functions should be called from IoContext thread. 24 | // (fiber) SyncRead interface: 25 | // https://www.boost.org/doc/libs/1_69_0/doc/html/boost_asio/reference/SyncReadStream.html 26 | template size_t read_some(const MBS& bufs, error_code& ec); 27 | 28 | // To calm SyncReadStream compile-checker we provide exception-enabled 29 | // interface without implementing it. 30 | template size_t read_some(const MBS& bufs); 31 | 32 | // SyncWrite interface: 33 | // https://www.boost.org/doc/libs/1_69_0/doc/html/boost_asio/reference/SyncWriteStream.html 34 | template size_t write_some(const BS& bufs, error_code& ec); 35 | 36 | // To calm SyncWriteStream compile-checker we provide exception-enabled 37 | // interface without implementing it. 38 | template size_t write_some(const BS& bufs); 39 | }; 40 | 41 | template 42 | template 43 | size_t AsioStreamAdapter::read_some(const MBS& bufs, error_code& ec) { 44 | using badapter = 45 | ::boost::asio::detail::buffer_sequence_adapter; 46 | badapter bsa(bufs); 47 | 48 | auto res = s_.Recv(bsa.buffers(), bsa.count()); 49 | if (res) 50 | return res.value(); 51 | ec = error_code(std::move(res.error()).value(), boost::system::system_category()); 52 | 53 | return 0; 54 | } 55 | 56 | template 57 | template 58 | size_t AsioStreamAdapter::write_some(const BS& bufs, error_code& ec) { 59 | using badapter = 60 | ::boost::asio::detail::buffer_sequence_adapter; 61 | badapter bsa(bufs); 62 | 63 | std::error_code lec; 64 | auto res = s_.WriteSome(bsa.buffers(), bsa.count()); 65 | if (res) { 66 | return res.value(); 67 | } 68 | ec = error_code(std::move(res.error()).value(), boost::system::system_category()); 69 | 70 | return 0; 71 | } 72 | 73 | } // namespace util 74 | -------------------------------------------------------------------------------- /util/aws/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(OpenSSL) 2 | find_package(ZLIB) 3 | 4 | add_library(awsv2_lib aws.cc credentials_provider_chain.cc logger.cc 5 | http_client.cc http_client_factory.cc s3_endpoint_provider.cc 6 | s3_read_file.cc s3_write_file.cc) 7 | 8 | if (APPLE) 9 | find_library(SECURITY Security) 10 | else() 11 | set(S2N_LIRARY TRDP::s2n) 12 | endif() 13 | 14 | cxx_link(awsv2_lib base http_utils http_client_lib TRDP::aws-cpp-sdk-s3 TRDP::aws-cpp-sdk-core 15 | TRDP::aws-crt-cpp TRDP::aws-c-mqtt TRDP::aws-c-event-stream 16 | TRDP::aws-c-s3 TRDP::aws-c-auth TRDP::aws-c-http TRDP::aws-c-io 17 | ${S2N_LIRARY} TRDP::aws-c-compression TRDP::aws-c-cal TRDP::aws-c-sdkutils 18 | TRDP::aws-checksums TRDP::aws-c-common OpenSSL::Crypto ZLIB::ZLIB ${CMAKE_DL_LIBS} ${SECURITY}) 19 | -------------------------------------------------------------------------------- /util/aws/aws.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | 4 | #include "util/aws/aws.h" 5 | 6 | #include 7 | #include 8 | 9 | #include "util/aws/http_client_factory.h" 10 | #include "util/aws/logger.h" 11 | 12 | namespace util { 13 | namespace aws { 14 | 15 | namespace { 16 | 17 | // Required by both Init and Shutdown so make static. 18 | static Aws::SDKOptions options; 19 | 20 | } // namespace 21 | 22 | void Init() { 23 | // Add a glog based logger. The logger handles discarding logs by level so 24 | // use trace logging to get all logs from AWS then filter in the logger. 25 | options.loggingOptions.logger_create_fn = 26 | []() -> std::shared_ptr { 27 | return std::make_shared(); 28 | }; 29 | options.loggingOptions.logLevel = Aws::Utils::Logging::LogLevel::Trace; 30 | 31 | // Disable starting the background IO event loop thread which we don't need. 32 | options.ioOptions.clientBootstrap_create_fn = 33 | []() -> std::shared_ptr { return nullptr; }; 34 | 35 | // We must use non-blocking network IO so use our own fiber based HTTP client. 36 | options.httpOptions.httpClientFactory_create_fn = 37 | []() -> std::shared_ptr { 38 | return std::make_shared(); 39 | }; 40 | 41 | Aws::InitAPI(options); 42 | } 43 | 44 | void Shutdown() { 45 | Aws::ShutdownAPI(options); 46 | } 47 | 48 | } // namespace aws 49 | } // namespace util 50 | -------------------------------------------------------------------------------- /util/aws/aws.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | 4 | #pragma once 5 | 6 | namespace util { 7 | namespace aws { 8 | 9 | // Initialises the AWS library. This must be called before using any AWS 10 | // services. 11 | void Init(); 12 | 13 | void Shutdown(); 14 | 15 | } // namespace aws 16 | } // namespace util 17 | -------------------------------------------------------------------------------- /util/aws/credentials_provider_chain.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | 4 | #include "util/aws/credentials_provider_chain.h" 5 | 6 | #include 7 | #include 8 | 9 | #include "base/logging.h" 10 | 11 | namespace util { 12 | namespace aws { 13 | 14 | CredentialsProviderChain::CredentialsProviderChain() { 15 | providers_.push_back(std::make_pair( 16 | "environment", std::make_shared())); 17 | providers_.push_back( 18 | std::make_pair("profile config file", 19 | std::make_shared())); 20 | providers_.push_back(std::make_pair( 21 | "web-identity", std::make_shared())); 22 | 23 | const auto ec2_metadata_disabled = Aws::Environment::GetEnv("AWS_EC2_METADATA_DISABLED"); 24 | if (Aws::Utils::StringUtils::ToLower(ec2_metadata_disabled.c_str()) != "true") { 25 | providers_.push_back(std::make_pair( 26 | "ec2 metadata", std::make_shared())); 27 | } else { 28 | LOG(INFO) << "aws: disabled EC2 metadata"; 29 | } 30 | } 31 | 32 | Aws::Auth::AWSCredentials CredentialsProviderChain::GetAWSCredentials() { 33 | for (const auto& provider : providers_) { 34 | Aws::Auth::AWSCredentials credentials = provider.second->GetAWSCredentials(); 35 | if (!credentials.GetAWSAccessKeyId().empty() && !credentials.GetAWSSecretKey().empty()) { 36 | LOG_FIRST_N(INFO, 1) << "aws: loaded credentials; provider=" << provider.first; 37 | return credentials; 38 | } 39 | } 40 | 41 | LOG(ERROR) << "aws: failed to load credentials; no credential provider found"; 42 | return Aws::Auth::AWSCredentials{}; 43 | } 44 | 45 | } // namespace aws 46 | } // namespace util 47 | -------------------------------------------------------------------------------- /util/aws/credentials_provider_chain.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | namespace util { 9 | namespace aws { 10 | 11 | // Loads a chain of providers: 12 | // 1. Environment variables 13 | // 2. Local configuration file 14 | // 3. EC2 metadata (unless the AWS_EC2_METADATA_DISABLED environment variable 15 | // is set to 'true') 16 | // 17 | // Note we avoid using the default credentials chain to avoid blocking the 18 | // thread, such as we don't support the process credential provider. 19 | class CredentialsProviderChain : public Aws::Auth::AWSCredentialsProviderChain { 20 | public: 21 | CredentialsProviderChain(); 22 | 23 | virtual Aws::Auth::AWSCredentials GetAWSCredentials() override; 24 | 25 | private: 26 | std::vector>> providers_; 27 | }; 28 | 29 | } // namespace aws 30 | } // namespace util 31 | -------------------------------------------------------------------------------- /util/aws/http_client.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include "util/fibers/proactor_base.h" 13 | 14 | namespace util { 15 | namespace aws { 16 | 17 | // HTTP client manages connecting to and sending HTTP requests. 18 | // 19 | // This is stateless so can be accessed by multiple threads. 20 | class HttpClient : public Aws::Http::HttpClient { 21 | public: 22 | HttpClient(const Aws::Client::ClientConfiguration& client_conf); 23 | 24 | ~HttpClient(); 25 | 26 | HttpClient(const HttpClient&) = delete; 27 | HttpClient& operator=(const HttpClient&) = delete; 28 | 29 | HttpClient(HttpClient&&) = delete; 30 | HttpClient& operator=(HttpClient&&) = delete; 31 | 32 | // Sends the given HTTP request to the server and returns a response. 33 | // 34 | // Note we don't support readLimiter or writeLimiter. 35 | std::shared_ptr MakeRequest( 36 | const std::shared_ptr& request, 37 | Aws::Utils::RateLimits::RateLimiterInterface* readLimiter = nullptr, 38 | Aws::Utils::RateLimits::RateLimiterInterface* writeLimiter = nullptr) const override; 39 | 40 | void DisableRequestProcessing() override; 41 | 42 | void EnableRequestProcessing() override; 43 | 44 | bool IsRequestProcessingEnabled() const override; 45 | 46 | void RetryRequestSleep(std::chrono::milliseconds sleep_time) override; 47 | 48 | private: 49 | io::Result> Connect(const std::string& host, uint16_t port, 50 | ProactorBase* proactor) const; 51 | 52 | io::Result Resolve(const std::string& host, 53 | ProactorBase* proactor) const; 54 | 55 | std::error_code EnableKeepAlive(int fd) const; 56 | 57 | Aws::Client::ClientConfiguration client_conf_; 58 | 59 | SSL_CTX* ctx_; 60 | }; 61 | 62 | } // namespace aws 63 | } // namespace util 64 | -------------------------------------------------------------------------------- /util/aws/http_client_factory.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | 4 | #include "util/aws/http_client_factory.h" 5 | 6 | #include 7 | 8 | #include "util/aws/http_client.h" 9 | 10 | namespace util { 11 | namespace aws { 12 | 13 | std::shared_ptr HttpClientFactory::CreateHttpClient( 14 | const Aws::Client::ClientConfiguration& client_conf) const { 15 | return std::make_shared(client_conf); 16 | } 17 | 18 | std::shared_ptr HttpClientFactory::CreateHttpRequest( 19 | const Aws::String& uri, Aws::Http::HttpMethod method, 20 | const Aws::IOStreamFactory& stream_factory) const { 21 | return CreateHttpRequest(Aws::Http::URI(uri), method, stream_factory); 22 | } 23 | 24 | std::shared_ptr HttpClientFactory::CreateHttpRequest( 25 | const Aws::Http::URI& uri, Aws::Http::HttpMethod method, 26 | const Aws::IOStreamFactory& stream_factory) const { 27 | auto request = std::make_shared(uri, method); 28 | request->SetResponseStreamFactory(stream_factory); 29 | return request; 30 | } 31 | 32 | } // namespace aws 33 | } // namespace util 34 | -------------------------------------------------------------------------------- /util/aws/http_client_factory.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #include 9 | 10 | namespace util { 11 | namespace aws { 12 | 13 | class HttpClientFactory : public Aws::Http::HttpClientFactory { 14 | public: 15 | std::shared_ptr CreateHttpClient( 16 | const Aws::Client::ClientConfiguration& client_conf) const override; 17 | 18 | std::shared_ptr CreateHttpRequest( 19 | const Aws::String& uri, Aws::Http::HttpMethod method, 20 | const Aws::IOStreamFactory& stream_factory) const override; 21 | 22 | std::shared_ptr CreateHttpRequest( 23 | const Aws::Http::URI& uri, Aws::Http::HttpMethod method, 24 | const Aws::IOStreamFactory& stream_factory) const override; 25 | }; 26 | 27 | } // namespace aws 28 | } // namespace util 29 | -------------------------------------------------------------------------------- /util/aws/logger.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | 4 | #include "util/aws/logger.h" 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include "base/logging.h" 12 | 13 | namespace util { 14 | namespace aws { 15 | 16 | Aws::Utils::Logging::LogLevel Logger::GetLogLevel() const { 17 | return Aws::Utils::Logging::LogLevel::Debug; 18 | } 19 | 20 | void Logger::Log(Aws::Utils::Logging::LogLevel level, const char* tag, const char* format_str, 21 | ...) { 22 | // Note Logger::Log is almost unused by the SDK, it instead uses LogStream. 23 | // Copied formatting vararg from the AWS SDK. 24 | 25 | std::stringstream ss; 26 | 27 | va_list args; 28 | va_start(args, format_str); 29 | 30 | va_list tmp_args; 31 | va_copy(tmp_args, args); 32 | const int len = vsnprintf(nullptr, 0, format_str, tmp_args) + 1; 33 | va_end(tmp_args); 34 | 35 | std::vector buf(len); 36 | vsnprintf(buf.data(), len, format_str, args); 37 | 38 | ss << buf.data(); 39 | 40 | va_end(args); 41 | 42 | switch (level) { 43 | case Aws::Utils::Logging::LogLevel::Trace: 44 | DVLOG(2) << "aws: " << tag << ": " << ss.str(); 45 | break; 46 | case Aws::Utils::Logging::LogLevel::Debug: 47 | VLOG(2) << "aws: " << tag << ": " << ss.str(); 48 | break; 49 | case Aws::Utils::Logging::LogLevel::Info: 50 | VLOG(1) << "aws: " << tag << ": " << ss.str(); 51 | break; 52 | case Aws::Utils::Logging::LogLevel::Warn: 53 | VLOG(1) << "aws: " << tag << ": " << ss.str(); 54 | break; 55 | case Aws::Utils::Logging::LogLevel::Error: 56 | VLOG(1) << "aws: " << tag << ": " << ss.str(); 57 | break; 58 | case Aws::Utils::Logging::LogLevel::Fatal: 59 | LOG(FATAL) << "aws: " << tag << ": " << ss.str(); 60 | break; 61 | default: 62 | break; 63 | } 64 | } 65 | 66 | void Logger::LogStream(Aws::Utils::Logging::LogLevel level, const char* tag, 67 | const Aws::OStringStream& message_stream) { 68 | switch (level) { 69 | case Aws::Utils::Logging::LogLevel::Trace: 70 | DVLOG(2) << "aws: " << tag << ": " << message_stream.str(); 71 | break; 72 | case Aws::Utils::Logging::LogLevel::Debug: 73 | VLOG(1) << "aws: " << tag << ": " << message_stream.str(); 74 | break; 75 | case Aws::Utils::Logging::LogLevel::Info: 76 | VLOG(1) << "aws: " << tag << ": " << message_stream.str(); 77 | break; 78 | case Aws::Utils::Logging::LogLevel::Warn: 79 | VLOG(1) << "aws: " << tag << ": " << message_stream.str(); 80 | break; 81 | case Aws::Utils::Logging::LogLevel::Error: 82 | VLOG(1) << "aws: " << tag << ": " << message_stream.str(); 83 | break; 84 | case Aws::Utils::Logging::LogLevel::Fatal: 85 | LOG(FATAL) << "aws: " << tag << ": " << message_stream.str(); 86 | break; 87 | default: 88 | break; 89 | } 90 | } 91 | 92 | void Logger::Flush() { 93 | } 94 | 95 | } // namespace aws 96 | } // namespace util 97 | -------------------------------------------------------------------------------- /util/aws/logger.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | namespace util { 9 | namespace aws { 10 | 11 | // A glog based logger for AWS. 12 | class Logger : public Aws::Utils::Logging::LogSystemInterface { 13 | public: 14 | Aws::Utils::Logging::LogLevel GetLogLevel() const override; 15 | 16 | void Log(Aws::Utils::Logging::LogLevel level, const char* tag, const char* format_str, 17 | ...) override; 18 | 19 | void LogStream(Aws::Utils::Logging::LogLevel level, const char* tag, 20 | const Aws::OStringStream& message_stream) override; 21 | 22 | void Flush() override; 23 | }; 24 | 25 | } // namespace aws 26 | } // namespace util 27 | -------------------------------------------------------------------------------- /util/aws/s3_endpoint_provider.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | 4 | #include "util/aws/s3_endpoint_provider.h" 5 | 6 | #include "base/logging.h" 7 | 8 | namespace util { 9 | namespace aws { 10 | 11 | S3EndpointProvider::S3EndpointProvider(const std::string& endpoint, bool https) 12 | : endpoint_{endpoint}, https_{https} { 13 | } 14 | 15 | Aws::Endpoint::ResolveEndpointOutcome S3EndpointProvider::ResolveEndpoint( 16 | const Aws::Endpoint::EndpointParameters& endpoint_params) const { 17 | // If no custom endpoint is configured, use the aws default. 18 | if (endpoint_ == "") { 19 | Aws::Endpoint::ResolveEndpointOutcome outcome = 20 | Aws::S3::S3EndpointProvider::ResolveEndpoint(endpoint_params); 21 | if (outcome.IsSuccess()) { 22 | Aws::Http::URI uri = outcome.GetResult().GetURI(); 23 | uri.SetScheme((https_) ? Aws::Http::Scheme::HTTPS : Aws::Http::Scheme::HTTP); 24 | outcome.GetResult().SetURI(uri); 25 | } 26 | return outcome; 27 | } 28 | 29 | // If a custom endpoint is configured, construct the URL. Note this misses 30 | // some functionality of Aws::S3::S3EndpointProvider, though we are 31 | // currently only using the custom endpoint for testing. 32 | 33 | std::string bucket; 34 | for (auto p : endpoint_params) { 35 | if (p.GetName() == "Bucket") { 36 | CHECK(p.GetString(bucket) == Aws::Endpoint::EndpointParameter::GetSetResult::SUCCESS); 37 | } 38 | } 39 | 40 | Aws::Endpoint::AWSEndpoint endpoint; 41 | const std::string scheme = (https_) ? "https://" : "http://"; 42 | if (bucket != "") { 43 | endpoint.SetURL(scheme + endpoint_ + "/" + bucket); 44 | } else { 45 | endpoint.SetURL(scheme + endpoint_); 46 | } 47 | return Aws::Endpoint::ResolveEndpointOutcome(std::move(endpoint)); 48 | } 49 | 50 | } // namespace aws 51 | } // namespace util 52 | -------------------------------------------------------------------------------- /util/aws/s3_endpoint_provider.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | namespace util { 9 | namespace aws { 10 | 11 | // S3 endpoint provider. 12 | // 13 | // We override the default to support configuring custom endpoints, which the 14 | // C++ SDK doesn't yet support. 15 | // 16 | // We also only support HTTP. 17 | class S3EndpointProvider : public Aws::S3::S3EndpointProvider { 18 | public: 19 | // Configure a non-empty endpoint string to configure a custom endpoint. 20 | S3EndpointProvider(const std::string& endpoint = "", bool https = true); 21 | 22 | Aws::Endpoint::ResolveEndpointOutcome ResolveEndpoint( 23 | const Aws::Endpoint::EndpointParameters& endpoint_params) const override; 24 | 25 | private: 26 | std::string endpoint_; 27 | 28 | bool https_; 29 | }; 30 | 31 | } // namespace aws 32 | } // namespace util 33 | -------------------------------------------------------------------------------- /util/aws/s3_read_file.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | #include "base/io_buf.h" 10 | #include "io/file.h" 11 | #include "io/io.h" 12 | 13 | namespace util { 14 | namespace aws { 15 | 16 | // 8MB is the limit for the boost response parser. 17 | constexpr size_t kDefaultChunkSize = 8 * (1 << 20); 18 | 19 | // Reads files from S3. 20 | // 21 | // This downloads chunks of the file with the given chunk size, then Read 22 | // consumes from the buffered chunk. Once a chunk has been read, it downloads 23 | // another. 24 | class S3ReadFile final : public io::ReadonlyFile { 25 | public: 26 | S3ReadFile(const std::string& bucket, const std::string& key, 27 | std::shared_ptr client, size_t chunk_size = kDefaultChunkSize); 28 | 29 | io::Result Read(size_t offset, const iovec* v, uint32_t len) override; 30 | 31 | std::error_code Close() override; 32 | 33 | size_t Size() const override; 34 | 35 | int Handle() const override; 36 | 37 | private: 38 | io::Result Read(iovec v); 39 | 40 | std::error_code DownloadChunk(); 41 | 42 | std::string NextByteRange() const; 43 | 44 | std::error_code ParseFileSize(const Aws::S3::Model::GetObjectResult& result); 45 | 46 | std::string bucket_; 47 | 48 | std::string key_; 49 | 50 | base::IoBuf buf_; 51 | 52 | // Total bytes in the file read. 53 | size_t file_read_ = 0; 54 | 55 | // Size of the target file to read. Set on first download. 56 | size_t file_size_ = 0; 57 | 58 | std::shared_ptr client_; 59 | }; 60 | 61 | } // namespace aws 62 | } // namespace util 63 | -------------------------------------------------------------------------------- /util/aws/s3_write_file.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #include "io/file.h" 9 | #include "io/io.h" 10 | 11 | namespace util { 12 | namespace aws { 13 | 14 | constexpr size_t kDefaultPartSize = 1ULL << 23; // 8MB. 15 | 16 | struct PartMetadata { 17 | std::string etag; 18 | std::string crc32; 19 | }; 20 | 21 | // File handle that writes to S3. 22 | // 23 | // This uses multipart uploads, where it will buffer upto the configured part 24 | // size before uploading. 25 | class S3WriteFile : public io::WriteFile { 26 | public: 27 | // Writes bytes to the S3 object. This will either buffer internally or 28 | // write a part to S3. 29 | io::Result WriteSome(const iovec* v, uint32_t len) override; 30 | 31 | // Closes the object and completes the multipart upload. Therefore the object 32 | // will not be uploaded unless Close is called. 33 | std::error_code Close() override; 34 | 35 | static io::Result Open(const std::string& bucket, const std::string& key, 36 | std::shared_ptr client, 37 | size_t part_size = kDefaultPartSize); 38 | 39 | private: 40 | S3WriteFile(const std::string& bucket, const std::string& key, const std::string& upload_id, 41 | std::shared_ptr client, size_t part_size = kDefaultPartSize); 42 | 43 | // Uploads the data buffered in buf_. Note this must not be called until 44 | // there are at least 5MB bytes in buf_, unless it is the last upload. 45 | std::error_code Flush(); 46 | 47 | std::string bucket_; 48 | 49 | std::string key_; 50 | 51 | std::string upload_id_; 52 | 53 | // Etags of the uploaded parts. 54 | std::vector parts_; 55 | 56 | // A buffer containing the pending bytes waiting to be uploaded. Only offset_ 57 | // bytes have been written. 58 | std::vector buf_; 59 | 60 | // Offset of the consumed bytes in buf_. 61 | size_t offset_ = 0; 62 | 63 | std::shared_ptr client_; 64 | }; 65 | 66 | } // namespace aws 67 | } // namespace util 68 | -------------------------------------------------------------------------------- /util/cloud/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | add_library(cloud_lib utils.cc) 3 | cxx_link(cloud_lib http_client_lib strings_lib) 4 | 5 | add_subdirectory(gcp) 6 | add_subdirectory(azure) -------------------------------------------------------------------------------- /util/cloud/azure/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(azure_lib azure.cc storage.cc) 2 | cxx_link(azure_lib http_client_lib strings_lib TRDP::pugixml) 3 | -------------------------------------------------------------------------------- /util/cloud/azure/creds_provider.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | #include "util/cloud/utils.h" 10 | 11 | namespace util { 12 | namespace cloud::azure { 13 | 14 | class Credentials : public CredentialsProvider { 15 | public: 16 | std::error_code Init(unsigned) final; 17 | 18 | const std::string& account_name() const { 19 | return account_name_; 20 | } 21 | const std::string& account_key() const { 22 | return account_key_; 23 | } 24 | 25 | std::string ServiceEndpoint() const; 26 | 27 | void Sign(detail::HttpRequestBase* req) const final; 28 | std::error_code RefreshToken() final; 29 | 30 | private: 31 | std::string account_name_; 32 | std::string account_key_; 33 | }; 34 | 35 | } // namespace cloud::azure 36 | } // namespace util -------------------------------------------------------------------------------- /util/cloud/azure/storage.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "io/file.h" 11 | #include "util/cloud/utils.h" 12 | 13 | typedef struct ssl_ctx_st SSL_CTX; 14 | 15 | namespace util { 16 | namespace cloud::azure { 17 | 18 | class Credentials; 19 | 20 | class Storage { 21 | public: 22 | explicit Storage(CredentialsProvider* creds) : creds_(creds) { 23 | } 24 | 25 | using ContainerItem = std::string_view; 26 | 27 | using ListItem = StorageListItem; 28 | std::error_code ListContainers(std::function cb); 29 | std::error_code List(std::string_view container, std::string_view prefix, bool recursive, 30 | unsigned max_results, std::function cb); 31 | 32 | private: 33 | CredentialsProvider* creds_; 34 | }; 35 | 36 | struct ReadFileOptions { 37 | CredentialsProvider* creds_provider = nullptr; 38 | SSL_CTX* ssl_cntx; 39 | }; 40 | 41 | using WriteFileOptions = ReadFileOptions; 42 | 43 | io::Result OpenReadFile(const std::string& container, const std::string& key, 44 | const ReadFileOptions& opts); 45 | 46 | io::Result OpenWriteFile(const std::string& container, const std::string& key, 47 | const WriteFileOptions& opts); 48 | 49 | } // namespace cloud::azure 50 | } // namespace util -------------------------------------------------------------------------------- /util/cloud/gcp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(gcp_lib gcs.cc gcs_file.cc gcp_utils.cc) 2 | 3 | cxx_link(gcp_lib cloud_lib strings_lib TRDP::rapidjson) 4 | -------------------------------------------------------------------------------- /util/cloud/gcp/gcp_creds_provider.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | 4 | #pragma once 5 | 6 | #include "base/RWSpinLock.h" 7 | #include "util/cloud/utils.h" 8 | 9 | namespace util { 10 | 11 | namespace fb2 { 12 | class ProactorBase; 13 | } // namespace fb2 14 | 15 | namespace cloud { 16 | 17 | class GCPCredsProvider : public CredentialsProvider { 18 | GCPCredsProvider(const GCPCredsProvider&) = delete; 19 | GCPCredsProvider& operator=(const GCPCredsProvider&) = delete; 20 | 21 | public: 22 | GCPCredsProvider() = default; 23 | 24 | std::error_code Init(unsigned connect_ms) final; 25 | 26 | void Sign(detail::HttpRequestBase* req) const final; 27 | 28 | // Thread-safe method issues refresh of the token. 29 | // Right now will do the refresh unconditonally. 30 | // TODO: to use expire_time_ to skip the refresh if expire time is far away. 31 | std::error_code RefreshToken(); 32 | 33 | std::string ServiceEndpoint() const final; 34 | 35 | const std::string& project_id() const { 36 | return project_id_; 37 | } 38 | 39 | const std::string& client_id() const { 40 | return client_id_; 41 | } 42 | 43 | // Thread-safe method to access the token. 44 | std::string access_token() const { 45 | folly::RWSpinLock::ReadHolder lock(lock_); 46 | return access_token_; 47 | } 48 | 49 | time_t expire_time() const { 50 | return expire_time_.load(std::memory_order_acquire); 51 | } 52 | 53 | private: 54 | bool use_instance_metadata_ = false; 55 | unsigned connect_ms_ = 0; 56 | 57 | std::string account_id_; 58 | std::string project_id_; 59 | 60 | std::string client_id_, client_secret_, refresh_token_; 61 | 62 | mutable folly::RWSpinLock lock_; // protects access_token_ 63 | std::string access_token_; 64 | std::atomic expire_time_ = 0; // seconds since epoch 65 | }; 66 | 67 | } // namespace cloud 68 | } // namespace util -------------------------------------------------------------------------------- /util/cloud/gcp/gcp_utils.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | 4 | #include "util/cloud/gcp/gcp_utils.h" 5 | 6 | #include 7 | 8 | #include "base/logging.h" 9 | #include "util/cloud/gcp/gcp_creds_provider.h" 10 | #include "util/http/http_client.h" 11 | 12 | namespace util::cloud { 13 | using namespace std; 14 | 15 | namespace h2 = boost::beast::http; 16 | 17 | namespace detail { 18 | 19 | 20 | string AuthHeader(string_view access_token) { 21 | return absl::StrCat("Bearer ", access_token); 22 | } 23 | 24 | EmptyRequestImpl CreateGCPEmptyRequest(boost::beast::http::verb req_verb, std::string_view endpoint, 25 | std::string_view url, const std::string_view access_token) { 26 | EmptyRequestImpl res(req_verb, url); 27 | res.SetHeader(h2::field::host, endpoint); 28 | res.SetHeader(h2::field::authorization, AuthHeader(access_token)); 29 | return res; 30 | } 31 | 32 | } // namespace detail 33 | } // namespace util::cloud 34 | -------------------------------------------------------------------------------- /util/cloud/gcp/gcp_utils.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "util/cloud/utils.h" 12 | #include "util/http/https_client_pool.h" 13 | 14 | namespace util::cloud { 15 | 16 | namespace detail { 17 | EmptyRequestImpl CreateGCPEmptyRequest(boost::beast::http::verb req_verb, std::string_view endpoint, 18 | std::string_view url, const std::string_view access_token); 19 | 20 | std::string AuthHeader(std::string_view access_token); 21 | 22 | } // namespace detail 23 | 24 | } // namespace util::cloud -------------------------------------------------------------------------------- /util/cloud/gcp/gcs.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include "util/cloud/gcp/gcp_creds_provider.h" 11 | #include "util/http/http_client.h" 12 | #include "util/http/https_client_pool.h" 13 | 14 | typedef struct ssl_ctx_st SSL_CTX; 15 | 16 | namespace util { 17 | namespace cloud { 18 | 19 | class GCS { 20 | public: 21 | using BucketItem = std::string_view; 22 | using ObjectItem = StorageListItem; 23 | 24 | using ListBucketCb = std::function; 25 | using ListObjectCb = std::function; 26 | 27 | GCS(GCPCredsProvider* creds_provider, SSL_CTX* ssl_cntx, fb2::ProactorBase* pb); 28 | ~GCS(); 29 | 30 | std::error_code ListBuckets(ListBucketCb cb); 31 | std::error_code List(std::string_view bucket, std::string_view prefix, bool recursive, 32 | ListObjectCb cb); 33 | 34 | http::ClientPool* GetConnectionPool() { 35 | return client_pool_.get(); 36 | } 37 | 38 | static std::unique_ptr CreateApiConnectionPool(SSL_CTX* ssl_ctx, 39 | fb2::ProactorBase* pb); 40 | 41 | private: 42 | GCPCredsProvider& creds_provider_; 43 | SSL_CTX* ssl_ctx_; 44 | std::unique_ptr client_pool_; 45 | }; 46 | 47 | } // namespace cloud 48 | } // namespace util -------------------------------------------------------------------------------- /util/cloud/gcp/gcs_file.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | 4 | #pragma once 5 | 6 | #include "io/file.h" 7 | #include "util/cloud/gcp/gcp_creds_provider.h" 8 | #include "util/http/https_client_pool.h" 9 | 10 | namespace util { 11 | 12 | namespace cloud { 13 | 14 | static constexpr size_t kDefaultGCPPartSize = 1ULL << 23; // 8MB. 15 | 16 | struct GcsFileOptions { 17 | GCPCredsProvider* creds_provider = nullptr; 18 | http::ClientPool* pool = nullptr; 19 | bool pool_owned = false; // true if file assumes ownership of the pool. 20 | }; 21 | 22 | struct GcsWriteFileOptions : public GcsFileOptions { 23 | size_t part_size = kDefaultGCPPartSize; 24 | }; 25 | 26 | using GcsReadFileOptions = GcsFileOptions; 27 | 28 | io::Result OpenWriteGcsFile(const std::string& bucket, const std::string& key, 29 | const GcsWriteFileOptions& opts); 30 | 31 | io::Result OpenReadGcsFile(const std::string& bucket, const std::string& key, 32 | const GcsReadFileOptions& opts); 33 | 34 | } // namespace cloud 35 | } // namespace util -------------------------------------------------------------------------------- /util/connection.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | #pragma once 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "util/fiber_socket_base.h" 11 | #include "util/fibers/proactor_base.h" 12 | 13 | namespace util { 14 | 15 | class ListenerInterface; 16 | 17 | /** 18 | * @brief A connection object that represents a client tcp connection, managed by a listener. 19 | * 20 | * We use boost::intrusive_ref_counter to be able to access the object from multiple fibers 21 | * in the same thread. Specifically, we may shutdown a connection during the shutdown of 22 | * ListenerInterface::RunAcceptLoop, 23 | * while the connection is going through a closing sequence itself. 24 | */ 25 | class Connection : public boost::intrusive_ref_counter { 26 | using connection_hook_t = ::boost::intrusive::list_member_hook< 27 | ::boost::intrusive::link_mode<::boost::intrusive::safe_link>>; 28 | 29 | connection_hook_t hook_; 30 | 31 | public: 32 | using member_hook_t = 33 | ::boost::intrusive::member_hook; 34 | 35 | virtual ~Connection() { 36 | } 37 | 38 | void SetSocket(FiberSocketBase* s) { 39 | socket_.reset(s); 40 | } 41 | 42 | FiberSocketBase* socket() { 43 | return socket_.get(); 44 | } 45 | 46 | const FiberSocketBase* socket() const { 47 | return socket_.get(); 48 | } 49 | 50 | FiberSocketBase* ReleaseSocket() { 51 | return socket_.release(); 52 | } 53 | 54 | // Calls shutdown(SHUT_RDWR) on a socket and then 55 | // calls OnShutdown(). 56 | void Shutdown(); 57 | 58 | protected: 59 | // The main loop for a connection. Runs in the same proactor thread as of socket_. 60 | virtual void HandleRequests() = 0; 61 | 62 | virtual void OnShutdown() { 63 | } 64 | 65 | virtual void OnPreMigrateThread() { 66 | } 67 | virtual void OnPostMigrateThread() { 68 | } 69 | 70 | ListenerInterface* listener() const { 71 | return listener_; 72 | } 73 | 74 | std::unique_ptr socket_; 75 | 76 | private: 77 | ListenerInterface* listener_ = nullptr; 78 | friend class ListenerInterface; 79 | }; 80 | 81 | } // namespace util 82 | -------------------------------------------------------------------------------- /util/fibers/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") 2 | set(FB_LINUX_LIBS TRDP::uring rt) # rt is required for timerfd_create 3 | set(FB_LINUX_SRCS uring_proactor.cc uring_socket.cc uring_file.cc) 4 | 5 | cxx_test(uring_file_test fibers2 LABELS CI) 6 | endif() 7 | 8 | add_library(fibers2 fibers.cc proactor_base.cc synchronization.cc 9 | fiber_file.cc epoll_proactor.cc epoll_socket.cc pool.cc 10 | detail/scheduler.cc detail/fiber_interface.cc detail/wait_queue.cc 11 | accept_server.cc 12 | fiber_socket_base.cc listener_interface.cc 13 | prebuilt_asio.cc proactor_pool.cc stacktrace.cc 14 | sliding_counter.cc varz.cc fiberqueue_threadpool.cc dns_resolve.cc 15 | ${FB_LINUX_SRCS}) 16 | 17 | cxx_link(fibers2 base io ${FB_LINUX_LIBS} Boost::context Boost::headers TRDP::cares) 18 | 19 | cxx_test(fibers_test fibers2 LABELS CI) 20 | cxx_test(fiber_socket_test fibers2 LABELS CI) 21 | 22 | -------------------------------------------------------------------------------- /util/fibers/detail/fiber_interface_impl.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | #pragma once 5 | 6 | #include "util/fibers/detail/scheduler.h" 7 | 8 | namespace util { 9 | namespace fb2 { 10 | namespace detail { 11 | 12 | inline bool FiberInterface::WaitUntil(std::chrono::steady_clock::time_point tp) { 13 | return scheduler_->WaitUntil(tp, this); 14 | } 15 | 16 | inline void FiberInterface::Suspend() { 17 | auto res = scheduler_->Preempt(); 18 | assert(!res); 19 | assert(!IsScheduledRemotely()); 20 | } 21 | 22 | inline void FiberInterface::Yield() { 23 | scheduler_->Yield(this); 24 | } 25 | 26 | } // namespace detail 27 | } // namespace fb2 28 | } // namespace util 29 | -------------------------------------------------------------------------------- /util/fibers/detail/result_mover.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | #pragma once 5 | 6 | namespace util { 7 | namespace detail { 8 | 9 | template class ResultMover { 10 | R r_; // todo: to set as optional to support objects without default c'tor. 11 | public: 12 | template void Apply(Func&& f) { 13 | r_ = f(); 14 | } 15 | 16 | // Returning rvalue-reference means returning the same object r_ instead of creating a 17 | // temporary R{r_}. Please note that when we return function-local object, we do not need to 18 | // return rvalue because RVO eliminates redundant object creation. 19 | // But for returning data member r_ it's more efficient. 20 | // "get() &&" means you can call this function only on rvalue ResultMover&& object. 21 | R&& get() && { 22 | return std::forward(r_); 23 | } 24 | }; 25 | 26 | template <> class ResultMover { 27 | public: 28 | template void Apply(Func&& f) { 29 | f(); 30 | } 31 | void get() { 32 | } 33 | }; 34 | 35 | } // namespace detail 36 | 37 | } // namespace util 38 | -------------------------------------------------------------------------------- /util/fibers/detail/wait_queue.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #include "util/fibers/detail/wait_queue.h" 6 | 7 | #include "base/logging.h" 8 | #include "util/fibers/detail/fiber_interface.h" 9 | 10 | namespace util { 11 | namespace fb2 { 12 | namespace detail { 13 | 14 | [[maybe_unused]] constexpr size_t WakeOtherkSizeOfWaitQ = sizeof(WaitQueue); 15 | 16 | void WaitQueue::Link(Waiter* waiter) { 17 | DCHECK(waiter); 18 | wait_list_.push_back(*waiter); 19 | DCHECK(!wait_list_.empty()); 20 | } 21 | 22 | bool WaitQueue::NotifyOne(FiberInterface* active) { 23 | if (wait_list_.empty()) 24 | return false; 25 | 26 | Waiter* waiter = &wait_list_.front(); 27 | DCHECK(waiter) << wait_list_.empty(); 28 | 29 | FiberInterface* cntx = waiter->cntx(); 30 | DCHECK(cntx); 31 | 32 | wait_list_.pop_front(); 33 | NotifyImpl(waiter->cntx(), active); 34 | 35 | return true; 36 | } 37 | 38 | void WaitQueue::NotifyAll(FiberInterface* active) { 39 | while (!wait_list_.empty()) { 40 | Waiter* waiter = &wait_list_.front(); 41 | DCHECK(waiter) << wait_list_.empty(); 42 | 43 | FiberInterface* cntx = waiter->cntx(); 44 | DCHECK(cntx); 45 | 46 | wait_list_.pop_front(); 47 | 48 | DVLOG(2) << "Scheduling " << cntx->name() << " from " << active->name(); 49 | 50 | active->ActivateOther(cntx); 51 | } 52 | } 53 | 54 | void WaitQueue::NotifyImpl(FiberInterface* suspended, FiberInterface* active) { 55 | active->ActivateOther(suspended); 56 | } 57 | 58 | } // namespace detail 59 | } // namespace fb2 60 | } // namespace util 61 | -------------------------------------------------------------------------------- /util/fibers/detail/wait_queue.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | namespace util { 10 | namespace fb2 { 11 | namespace detail { 12 | 13 | class FiberInterface; 14 | 15 | class Waiter { 16 | friend class FiberInterface; 17 | 18 | explicit Waiter(FiberInterface* cntx) : cntx_(cntx) { 19 | } 20 | 21 | public: 22 | // For some boost versions/distributions, the default move c'tor does not work well, 23 | // so we implement it explicitly. 24 | Waiter(Waiter&& o) : cntx_(o.cntx_) { 25 | // it does not work well for slist because its reference is used by slist members 26 | // (probably when caching last). 27 | o.wait_hook.swap_nodes(wait_hook); 28 | o.cntx_ = nullptr; 29 | } 30 | 31 | // safe_link is used in assertions via IsLinked() method. 32 | using ListHookType = boost::intrusive::list_member_hook< 33 | boost::intrusive::link_mode>; 34 | 35 | FiberInterface* cntx() const { 36 | return cntx_; 37 | } 38 | 39 | bool IsLinked() const { 40 | return wait_hook.is_linked(); 41 | } 42 | 43 | ListHookType wait_hook; 44 | 45 | private: 46 | FiberInterface* cntx_; 47 | }; 48 | 49 | // All WaitQueue are not thread safe and must be run under a lock. 50 | 51 | class WaitQueue { 52 | public: 53 | bool empty() const { 54 | return wait_list_.empty(); 55 | } 56 | 57 | void Link(Waiter* waiter); 58 | 59 | void Unlink(Waiter* waiter) { 60 | auto it = WaitList::s_iterator_to(*waiter); 61 | wait_list_.erase(it); 62 | } 63 | 64 | bool NotifyOne(FiberInterface* active); 65 | void NotifyAll(FiberInterface* active); 66 | 67 | private: 68 | using WaitList = boost::intrusive::list< 69 | Waiter, boost::intrusive::member_hook, 70 | boost::intrusive::constant_time_size>; 71 | 72 | void NotifyImpl(FiberInterface* suspended, FiberInterface* active); 73 | 74 | WaitList wait_list_; 75 | }; 76 | 77 | } // namespace detail 78 | } // namespace fb2 79 | } // namespace util 80 | -------------------------------------------------------------------------------- /util/fibers/dns_resolve.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace util { 12 | namespace fb2 { 13 | 14 | class ProactorBase; 15 | // Resolve addresss - either ipv4 or ipv6. dest_ip must be able to hold INET6_ADDRSTRLEN 16 | std::error_code DnsResolve(const std::string& host, uint32_t wait_ms, char dest_ip[], 17 | ProactorBase* proactor); 18 | 19 | void InitDnsResolver(uint32_t timeout_ms); 20 | std::error_code DnsResolve(const std::string& host, char dest_ip[]); 21 | 22 | } // namespace fb2 23 | } // namespace util 24 | -------------------------------------------------------------------------------- /util/fibers/epoll_proactor.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #pragma once 6 | 7 | #include "util/fibers/proactor_base.h" 8 | 9 | namespace util { 10 | namespace fb2 { 11 | 12 | namespace detail { 13 | class Scheduler; 14 | } 15 | 16 | class EpollProactor : public ProactorBase { 17 | public: 18 | EpollProactor(); 19 | ~EpollProactor(); 20 | 21 | // should be called from the thread that owns this EpollProactor before calling Run. 22 | void Init(unsigned pool_index); 23 | 24 | using IoResult = int; 25 | 26 | // event_mask passed from epoll_event.events or from kevent. 27 | // int error is kevent specific. 28 | using CbType = std::function; 29 | 30 | // Returns the handler id for the armed event. 31 | unsigned Arm(int fd, CbType cb, uint32_t event_mask); 32 | void Disarm(int fd, unsigned arm_index); 33 | 34 | int ev_loop_fd() const { 35 | return epoll_fd_; 36 | } 37 | 38 | Kind GetKind() const final { 39 | return EPOLL; 40 | } 41 | 42 | private: 43 | void DispatchCompletions(const void* cevents, unsigned count); 44 | 45 | void MainLoop(detail::Scheduler* sched) final; 46 | LinuxSocketBase* CreateSocket() final; 47 | void SchedulePeriodic(uint32_t id, PeriodicItem* item) final; 48 | void CancelPeriodicInternal(PeriodicItem* item) final; 49 | void WakeRing() final; 50 | void PeriodicCb(PeriodicItem* item); 51 | 52 | void RegrowCentries(); 53 | void ArmWakeupEvent(); 54 | 55 | int epoll_fd_ = -1; 56 | 57 | // friend class EpollFiberAlgo; 58 | struct CompletionEntry { 59 | CbType cb; 60 | 61 | // serves for linked list management when unused. Also can store an additional payload 62 | // field when in flight. 63 | int32_t index = -1; 64 | int32_t unused = -1; 65 | }; 66 | 67 | std::vector centries_; 68 | int32_t next_free_ce_ = -1; 69 | }; 70 | 71 | } // namespace fb2 72 | } // namespace util 73 | -------------------------------------------------------------------------------- /util/fibers/epoll_socket.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | #include "util/fiber_socket_base.h" 10 | #include "util/fibers/epoll_proactor.h" 11 | 12 | namespace util { 13 | namespace fb2 { 14 | 15 | class EpollSocket : public LinuxSocketBase { 16 | public: 17 | template using Result = io::Result; 18 | 19 | EpollSocket(int fd = -1); 20 | 21 | virtual ~EpollSocket(); 22 | 23 | ABSL_MUST_USE_RESULT AcceptResult Accept() final; 24 | 25 | ABSL_MUST_USE_RESULT error_code Connect(const endpoint_type& ep, 26 | std::function on_pre_connect) final; 27 | ABSL_MUST_USE_RESULT error_code Close() final; 28 | 29 | // Really need here expected. 30 | Result WriteSome(const iovec* ptr, uint32_t len) override; 31 | 32 | void AsyncWriteSome(const iovec* v, uint32_t len, io::AsyncProgressCb cb) override; 33 | void AsyncReadSome(const iovec* v, uint32_t len, io::AsyncProgressCb cb) override; 34 | 35 | Result RecvMsg(const msghdr& msg, int flags) override; 36 | Result Recv(const io::MutableBytes& mb, int flags = 0) override; 37 | 38 | error_code Shutdown(int how) override; 39 | 40 | unsigned RecvProvided(unsigned buf_len, ProvidedBuffer* dest) final; 41 | void ReturnProvided(const ProvidedBuffer& pbuf) final; 42 | 43 | void RegisterOnErrorCb(std::function cb) final; 44 | void CancelOnErrorCb() final; 45 | 46 | using FiberSocketBase::IsConnClosed; 47 | 48 | private: 49 | class PendingReq; 50 | 51 | struct AsyncReq { 52 | uint32_t len; 53 | iovec* vec; 54 | io::AsyncProgressCb cb; 55 | 56 | AsyncReq(iovec* v, uint32_t l, io::AsyncProgressCb _cb) : len(l), vec(v), cb(std::move(_cb)) { 57 | } 58 | 59 | // Caller is responsible for *calling* cb. 60 | std::pair> Run(int fd, bool is_send); 61 | }; 62 | 63 | EpollProactor* GetProactor() { 64 | return static_cast(proactor()); 65 | } 66 | void OnSetProactor() final; 67 | void OnResetProactor() final; 68 | 69 | // kevent pass error code together with completion event. 70 | void Wakey(uint32_t event_flags, int error, EpollProactor* cntr); 71 | 72 | void HandleAsyncRequest(error_code ec, bool is_send); 73 | 74 | union { 75 | PendingReq* write_req_; 76 | AsyncReq* async_write_req_; 77 | }; 78 | 79 | union { 80 | PendingReq* read_req_; 81 | AsyncReq* async_read_req_; 82 | }; 83 | 84 | int32_t arm_index_ = -1; 85 | 86 | static constexpr uint32_t kMaxBufSize = 1 << 16; 87 | static constexpr uint32_t kMinBufSize = 1 << 4; 88 | uint32_t bufreq_sz_ = kMinBufSize; 89 | uint8_t async_write_pending_ : 1; 90 | uint8_t async_read_pending_ : 1; 91 | std::function error_cb_; 92 | }; 93 | 94 | constexpr size_t kSizeofEpollSocket = sizeof(EpollSocket); 95 | 96 | } // namespace fb2 97 | } // namespace util 98 | -------------------------------------------------------------------------------- /util/fibers/fiber_file.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #include "io/file.h" 6 | #include "util/fibers/fiberqueue_threadpool.h" 7 | 8 | namespace util { 9 | 10 | namespace fb_namesp = fb2; 11 | 12 | // Fiber-friendly file handler. Returns ReadonlyFile* instance that does not block the current 13 | // thread unlike the regular posix implementation. All the read opearations will run 14 | // in FiberQueueThreadPool. 15 | struct FiberReadOptions : public io::ReadonlyFile::Options { 16 | struct Stats { 17 | size_t cache_bytes = 0; // read because of prefetch logic. 18 | size_t disk_bytes = 0; // read via ThreadPool calls. 19 | size_t read_prefetch_cnt = 0; 20 | size_t preempt_cnt = 0; 21 | }; 22 | 23 | size_t prefetch_size = 1 << 16; // 65K 24 | Stats* stats = nullptr; 25 | }; 26 | 27 | ABSL_MUST_USE_RESULT io::ReadonlyFileOrError OpenFiberReadFile( 28 | std::string_view name, fb_namesp::FiberQueueThreadPool* tp, 29 | const FiberReadOptions& opts = FiberReadOptions{}); 30 | 31 | struct FiberWriteOptions : public io::WriteFile::Options { 32 | bool consistent_thread = true; // whether to send the write request to the thread in the pool. 33 | }; 34 | 35 | ABSL_MUST_USE_RESULT io::Result OpenFiberWriteFile( 36 | std::string_view name, fb_namesp::FiberQueueThreadPool* tp, 37 | const FiberWriteOptions& opts = FiberWriteOptions()); 38 | 39 | } // namespace util 40 | -------------------------------------------------------------------------------- /util/fibers/fiberqueue_threadpool.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | #include "util/fibers/fiberqueue_threadpool.h" 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include "base/pthread_utils.h" 11 | 12 | namespace util { 13 | namespace fb2 { 14 | 15 | using namespace std; 16 | 17 | FiberQueue::FiberQueue(unsigned queue_size) : queue_(queue_size) { 18 | } 19 | 20 | void FiberQueue::Run() { 21 | bool is_closed = false; 22 | CbFunc func; 23 | 24 | auto cb = [&] { 25 | if (queue_.try_dequeue(func)) { 26 | push_ec_.notify(); 27 | return true; 28 | } 29 | 30 | if (is_closed_.load(std::memory_order_acquire)) { 31 | is_closed = true; 32 | return true; 33 | } 34 | return false; 35 | }; 36 | 37 | while (true) { 38 | pull_ec_.await(cb); 39 | 40 | if (is_closed) 41 | break; 42 | try { 43 | func(); 44 | } catch (std::exception& e) { 45 | // std::exception_ptr p = std::current_exception(); 46 | LOG(FATAL) << "Exception " << e.what(); 47 | } 48 | } 49 | } 50 | 51 | void FiberQueue::Shutdown() { 52 | is_closed_.store(true, memory_order_seq_cst); 53 | pull_ec_.notifyAll(); 54 | } 55 | 56 | FiberQueueThreadPool::FiberQueueThreadPool(unsigned num_threads, unsigned queue_size) { 57 | if (num_threads == 0) { 58 | num_threads = std::thread::hardware_concurrency(); 59 | } 60 | worker_size_ = num_threads; 61 | workers_.reset(new Worker[num_threads]); 62 | 63 | for (unsigned i = 0; i < num_threads; ++i) { 64 | string name = absl::StrCat("fq_pool", i); 65 | 66 | auto fn = std::bind(&FiberQueueThreadPool::WorkerFunction, this, i); 67 | workers_[i].q.reset(new FiberQueue(queue_size)); 68 | workers_[i].tid = base::StartThread(name.c_str(), fn); 69 | } 70 | } 71 | 72 | FiberQueueThreadPool::~FiberQueueThreadPool() { 73 | VLOG(1) << "FiberQueueThreadPool::~FiberQueueThreadPool"; 74 | 75 | Shutdown(); 76 | } 77 | 78 | void FiberQueueThreadPool::Shutdown() { 79 | if (!workers_) 80 | return; 81 | 82 | for (size_t i = 0; i < worker_size_; ++i) { 83 | workers_[i].q->Shutdown(); 84 | } 85 | 86 | for (size_t i = 0; i < worker_size_; ++i) { 87 | auto& w = workers_[i]; 88 | pthread_join(w.tid, nullptr); 89 | } 90 | 91 | workers_.reset(); 92 | VLOG(1) << "FiberQueueThreadPool::ShutdownEnd"; 93 | } 94 | 95 | void FiberQueueThreadPool::WorkerFunction(unsigned index) { 96 | /* 97 | sched_param param; 98 | param.sched_priority = 1; 99 | int err = pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m); 100 | if (err) { 101 | LOG(INFO) << "Could not set FIFO priority in fiber-queue-thread"; 102 | }*/ 103 | 104 | workers_[index].q->Run(); 105 | 106 | VLOG(1) << "FiberQueueThreadPool::Exit"; 107 | } 108 | 109 | } // namespace fb2 110 | } // namespace util 111 | -------------------------------------------------------------------------------- /util/fibers/fibers.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #include "util/fibers/fibers.h" 6 | 7 | #include "base/logging.h" 8 | 9 | using namespace std; 10 | 11 | namespace util { 12 | namespace fb2 { 13 | 14 | Fiber::~Fiber() { 15 | CHECK(!impl_) << "Fiber destructor called on a joinable fiber " << impl_->name(); 16 | } 17 | 18 | Fiber& Fiber::operator=(Fiber&& other) noexcept { 19 | CHECK(!IsJoinable()); 20 | 21 | if (this == &other) { 22 | return *this; 23 | } 24 | 25 | impl_.swap(other.impl_); 26 | return *this; 27 | } 28 | 29 | void Fiber::Detach() { 30 | impl_.reset(); 31 | } 32 | 33 | void Fiber::Join() { 34 | CHECK(IsJoinable()); 35 | impl_->Join(); 36 | DVLOG(1) << "Fiber::Joined() " << impl_->name() << " " << impl_->DEBUG_use_count(); 37 | impl_.reset(); 38 | } 39 | 40 | void Fiber::JoinIfNeeded() { 41 | if (IsJoinable()) 42 | Join(); 43 | } 44 | 45 | void SetDefaultStackResource(PMR_NS::memory_resource* mr, size_t default_size) { 46 | CHECK(detail::default_stack_resource == nullptr); 47 | detail::default_stack_resource = mr; 48 | detail::default_stack_size = default_size; 49 | std::atomic_thread_fence(std::memory_order_seq_cst); 50 | } 51 | 52 | void* StdMallocResource::do_allocate(size_t size, size_t align) { 53 | void* res = malloc(size); 54 | if (res == nullptr) { 55 | throw bad_alloc(); 56 | } 57 | return res; 58 | } 59 | 60 | void StdMallocResource::do_deallocate(void* ptr, size_t size, size_t align) { 61 | free(ptr); 62 | } 63 | 64 | StdMallocResource std_malloc_resource; 65 | 66 | } // namespace fb2 67 | } // namespace util 68 | -------------------------------------------------------------------------------- /util/fibers/future.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "util/fibers/synchronization.h" 12 | 13 | namespace util { 14 | namespace fb2 { 15 | 16 | // Simple thread-safe fiber-blocking future for waiting for value. Pass by value. 17 | template struct Future { 18 | Future() : block{std::make_shared()} { 19 | } 20 | 21 | T Get() { 22 | block->waker.await( 23 | [this] { return block->has_value.exchange(false, std::memory_order_relaxed); }); 24 | return std::move(block->value); 25 | } 26 | 27 | void Resolve(T result) { 28 | block->value = std::move(result); 29 | block->has_value.store(true, std::memory_order_relaxed); 30 | block->waker.notify(); 31 | } 32 | 33 | private: 34 | struct Block { 35 | T value{}; // replace with aligned_storage or optional if T is not default-constructible 36 | std::atomic_bool has_value = false; 37 | util::fb2::EventCount waker{}; 38 | }; 39 | std::shared_ptr block; 40 | }; 41 | 42 | } // namespace fb2 43 | } // namespace util 44 | -------------------------------------------------------------------------------- /util/fibers/pool.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #include "util/fibers/pool.h" 6 | 7 | #include "base/logging.h" 8 | #include "util/fibers/epoll_proactor.h" 9 | 10 | #ifdef __linux__ 11 | #include "util/fibers/uring_proactor.h" 12 | #endif 13 | 14 | namespace util { 15 | namespace fb2 { 16 | 17 | Pool* Pool::Epoll(size_t pool_size) { 18 | Pool* res = new Pool(ProactorBase::Kind::EPOLL, pool_size); 19 | return res; 20 | } 21 | 22 | #ifdef __linux__ 23 | Pool* Pool::IOUring(size_t ring_depth, size_t pool_size) { 24 | Pool* res = new Pool(ProactorBase::Kind::IOURING, pool_size); 25 | res->ring_depth_ = ring_depth; 26 | return res; 27 | } 28 | #endif 29 | 30 | ProactorBase* Pool::CreateProactor() { 31 | switch (kind_) { 32 | case ProactorBase::Kind::EPOLL: 33 | return new EpollProactor; 34 | case ProactorBase::Kind::IOURING: 35 | #ifdef __linux__ 36 | return new UringProactor; 37 | #else 38 | LOG(FATAL) << "IOUring is not supported on this platform"; 39 | #endif 40 | } 41 | return nullptr; 42 | } 43 | 44 | void Pool::InitInThread(unsigned pool_index) { 45 | switch (kind_) { 46 | case ProactorBase::Kind::EPOLL: { 47 | EpollProactor* p = static_cast(proactor_[pool_index]); 48 | p->Init(pool_index); 49 | break; 50 | } 51 | 52 | case ProactorBase::Kind::IOURING: { 53 | #ifdef __linux__ 54 | UringProactor* p = static_cast(proactor_[pool_index]); 55 | p->Init(pool_index, ring_depth_); 56 | #else 57 | CHECK(false); 58 | #endif 59 | break; 60 | } 61 | } 62 | } 63 | 64 | } // namespace fb2 65 | 66 | } // namespace util 67 | -------------------------------------------------------------------------------- /util/fibers/pool.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #pragma once 6 | 7 | #include "util/proactor_pool.h" 8 | 9 | namespace util { 10 | namespace fb2 { 11 | 12 | class Pool : public ProactorPool { 13 | public: 14 | static Pool* Epoll(size_t pool_size = 0); 15 | static Pool* IOUring(size_t ring_depth, size_t pool_size = 0); 16 | 17 | private: 18 | Pool(ProactorBase::Kind kind, size_t pool_size) : ProactorPool(pool_size), kind_(kind) { 19 | } 20 | 21 | ProactorBase::Kind kind_; 22 | unsigned ring_depth_ = 0; 23 | 24 | ProactorBase* CreateProactor() final; 25 | void InitInThread(unsigned index) final; 26 | }; 27 | } // namespace fb2 28 | } // namespace util 29 | -------------------------------------------------------------------------------- /util/fibers/prebuilt_asio.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2019, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | 5 | #ifdef BOOST_ASIO_SEPARATE_COMPILATION 6 | #include 7 | #endif 8 | -------------------------------------------------------------------------------- /util/fibers/sliding_counter.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2021, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | #include "util/sliding_counter.h" 5 | 6 | #include "absl/time/clock.h" 7 | #include "base/logging.h" 8 | 9 | using namespace std; 10 | 11 | namespace util { 12 | namespace detail { 13 | 14 | void SlidingCounterBase::InitInternal(ProactorPool* pp) { 15 | CHECK(pp_ == nullptr); 16 | pp_ = CHECK_NOTNULL(pp); 17 | } 18 | 19 | void SlidingCounterBase::CheckInit() const { 20 | CHECK_NOTNULL(pp_); 21 | } 22 | 23 | unsigned SlidingCounterBase::ProactorThreadIndex() const { 24 | int32_t tnum = CHECK_NOTNULL(pp_)->size(); 25 | 26 | int32_t indx = fb2::ProactorBase::me()->GetPoolIndex(); 27 | CHECK_GE(indx, 0) << "Must be called from proactor thread!"; 28 | CHECK_LT(indx, tnum) << "Invalid thread index " << indx; 29 | 30 | return unsigned(indx); 31 | } 32 | 33 | } // namespace detail 34 | } // namespace util 35 | -------------------------------------------------------------------------------- /util/fibers/stacktrace.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #include "util/fibers/stacktrace.h" 6 | 7 | #include "absl/debugging/stacktrace.h" 8 | #include "absl/debugging/symbolize.h" 9 | #include "absl/strings/str_format.h" 10 | 11 | #ifdef NDEBUG 12 | #define SKIP_COUNT_TOP 2 13 | #define SKIP_COUNT_BOTTOM 2 14 | #else 15 | #define SKIP_COUNT_TOP 5 16 | #define SKIP_COUNT_BOTTOM 5 17 | #endif 18 | 19 | std::string util::fb2::GetStacktrace() { 20 | void* addresses[20]; 21 | int size = absl::GetStackTrace(addresses, sizeof(addresses) / sizeof(void*), 22 | /*skip_count=*/SKIP_COUNT_TOP); 23 | 24 | std::string rv; 25 | for (int i = 0; i < size - SKIP_COUNT_BOTTOM; i++) { 26 | char symbol_buf[1024]; 27 | const char* symbol = "(unknown)"; 28 | if (absl::Symbolize(addresses[i], symbol_buf, sizeof(symbol_buf))) { 29 | symbol = symbol_buf; 30 | } 31 | rv += absl::StrFormat("%p %s\n", addresses[i], symbol); 32 | } 33 | 34 | return rv; 35 | } 36 | -------------------------------------------------------------------------------- /util/fibers/stacktrace.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | namespace util::fb2 { 10 | 11 | std::string GetStacktrace(); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /util/fibers/uring_file_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #include "util/fibers/uring_file.h" 6 | 7 | #include 8 | 9 | #include "base/gtest.h" 10 | #include "base/logging.h" 11 | #include "util/fibers/uring_proactor.h" 12 | 13 | using namespace std; 14 | 15 | namespace util { 16 | namespace fb2 { 17 | 18 | class UringFileTest : public testing::Test { 19 | protected: 20 | UringFileTest() { 21 | proactor_.reset(new UringProactor); 22 | } 23 | 24 | void SetUp() final { 25 | proactor_thread_ = thread{[this] { 26 | proactor_->Init(0, 16); 27 | proactor_->Run(); 28 | }}; 29 | } 30 | 31 | void TearDown() final { 32 | proactor_->Stop(); 33 | proactor_thread_.join(); 34 | } 35 | 36 | std::unique_ptr proactor_; 37 | std::thread proactor_thread_; 38 | }; 39 | 40 | TEST_F(UringFileTest, Basic) { 41 | string path = base::GetTestTempPath("1.log"); 42 | proactor_->Await([path] { 43 | auto res = OpenLinux(path, O_RDWR | O_CREAT | O_TRUNC, 0666); 44 | ASSERT_TRUE(res); 45 | LinuxFile* wf = (*res).get(); 46 | 47 | auto ec = wf->Write(io::Buffer("hello"), -1, RWF_APPEND); 48 | ASSERT_FALSE(ec); 49 | 50 | char buf[] = " world"; 51 | Done done; 52 | wf->WriteAsync(io::Buffer(buf), 5, [done](int res) mutable { 53 | done.Notify(); 54 | ASSERT_EQ(res, 6); 55 | }); 56 | done.WaitFor(100ms); 57 | ec = wf->Close(); 58 | EXPECT_FALSE(ec); 59 | }); 60 | 61 | proactor_->Await([&] { 62 | auto res = OpenLinux(path, O_RDWR, 0666); 63 | ASSERT_TRUE(res); 64 | unique_ptr lf = std::move(*res); 65 | char buf[100]; 66 | Done done; 67 | lf->ReadAsync(io::MutableBuffer(buf), 0, [&](int res) { 68 | ASSERT_EQ(res, 11); 69 | EXPECT_EQ("hello world", string(buf, res)); 70 | done.Notify(); 71 | }); 72 | 73 | ASSERT_TRUE(done.WaitFor(10ms)); 74 | error_code ec = lf->Close(); 75 | EXPECT_FALSE(ec); 76 | }); 77 | } 78 | 79 | TEST_F(UringFileTest, WriteAsync) { 80 | string path = base::GetTestTempPath("file.log"); 81 | char src[] = "hello world"; 82 | 83 | proactor_->Await([&] { 84 | auto res = OpenLinux(path, O_RDWR | O_CREAT | O_TRUNC, 0666); 85 | ASSERT_TRUE(res); 86 | unique_ptr lf = std::move(*res); 87 | 88 | BlockingCounter bc{0}; 89 | 90 | auto cb = [bc](int res) mutable { 91 | ASSERT_GT(res, 0); 92 | bc->Dec(); 93 | }; 94 | 95 | for (unsigned i = 0; i < 100; ++i) { 96 | bc->Add(1); 97 | lf->WriteAsync(io::Buffer(src), 5, cb); 98 | } 99 | ASSERT_TRUE(bc->WaitFor(100ms)); 100 | auto ec = lf->Close(); 101 | EXPECT_FALSE(ec); 102 | }); 103 | } 104 | 105 | } // namespace fb2 106 | } // namespace util 107 | -------------------------------------------------------------------------------- /util/fibers/uring_types.h: -------------------------------------------------------------------------------- 1 | // Copyright 2025, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #pragma once 6 | 7 | #include "io/io.h" 8 | 9 | namespace util { 10 | namespace fb2 { 11 | 12 | // A slice that is sub-region of registered buffer (io_uring_register_buffers) 13 | struct RegisteredSlice { 14 | io::MutableBytes bytes; // buf, nbytes 15 | unsigned buf_idx; // registered buffer id 16 | }; 17 | 18 | } // namespace fb2 19 | } // namespace util 20 | -------------------------------------------------------------------------------- /util/fibers/varz.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #include "util/varz.h" 6 | 7 | #include "base/logging.h" 8 | #include "util/fibers/synchronization.h" 9 | 10 | using base::VarzValue; 11 | using namespace std; 12 | 13 | namespace util { 14 | 15 | using fb2::Mutex; 16 | using fb2::ProactorBase; 17 | 18 | VarzValue VarzQps::GetData() const { 19 | uint32_t qps = val_.SumTail() / (Counter::WIN_SIZE - 1); // Average over kWinSize values. 20 | return VarzValue::FromInt(qps); 21 | } 22 | 23 | VarzMapAverage::~VarzMapAverage() { 24 | } 25 | 26 | void VarzMapAverage::Init(ProactorPool* pp) { 27 | CHECK(pp_ == nullptr); 28 | pp_ = CHECK_NOTNULL(pp); 29 | avg_map_.reset(new Map[pp->size()]); 30 | } 31 | 32 | void VarzMapAverage::Shutdown() { 33 | avg_map_.reset(); 34 | pp_ = nullptr; 35 | } 36 | 37 | unsigned VarzMapAverage::ProactorThreadIndex() const { 38 | unsigned tnum = CHECK_NOTNULL(pp_)->size(); 39 | 40 | int32_t indx = ProactorBase::me()->GetPoolIndex(); 41 | CHECK_GE(indx, 0) << "Must be called from proactor thread!"; 42 | CHECK_LT(unsigned(indx), tnum) << "Invalid thread index " << indx; 43 | 44 | return unsigned(indx); 45 | } 46 | 47 | auto VarzMapAverage::FindSlow(string_view key) -> Map::iterator { 48 | auto str = pp_->GetString(key); 49 | auto& map = avg_map_[ProactorBase::me()->GetPoolIndex()]; 50 | auto res = map.emplace(str, SumCnt{}); 51 | 52 | CHECK(res.second); 53 | return res.first; 54 | } 55 | 56 | VarzValue VarzMapAverage::GetData() const { 57 | CHECK(pp_); 58 | 59 | AnyValue::Map result; 60 | Mutex mu; 61 | 62 | auto cb = [&](unsigned index, auto*) { 63 | auto& map = avg_map_[index]; 64 | 65 | for (const auto& k_v : map) { 66 | AnyValue::Map items; 67 | int64 count = k_v.second.second.Sum(); 68 | int64 sum = k_v.second.first.Sum(); 69 | items.emplace_back("count", VarzValue::FromInt(count)); 70 | items.emplace_back("sum", VarzValue::FromInt(sum)); 71 | 72 | if (count) { 73 | double avg = count > 0 ? double(sum) / count : 0; 74 | items.emplace_back("average", VarzValue::FromDouble(avg)); 75 | } 76 | 77 | lock_guard lk(mu); 78 | result.emplace_back(string(k_v.first), std::move(items)); 79 | } 80 | }; 81 | pp_->AwaitFiberOnAll(cb); 82 | 83 | return result; 84 | } 85 | 86 | VarzValue VarzFunction::GetData() const { 87 | AnyValue::Map result = cb_(); 88 | return AnyValue(result); 89 | } 90 | 91 | void VarzCount::Init(ProactorPool* pp) { 92 | CHECK_NOTNULL(pp); 93 | } 94 | 95 | void VarzCount::Shutdown() { 96 | } 97 | 98 | VarzValue VarzCount::GetData() const { 99 | AnyValue res = VarzValue::FromInt(count_.load(memory_order_relaxed)); 100 | return res; 101 | } 102 | 103 | VarzCount::~VarzCount() { 104 | } 105 | 106 | } // namespace util 107 | -------------------------------------------------------------------------------- /util/html/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(html_lib sorted_table.cc) 2 | cxx_link(html_lib absl::strings) 3 | -------------------------------------------------------------------------------- /util/html/main.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | $("table").tablesorter({ 4 | theme : "blue", 5 | 6 | widthFixed: true, 7 | 8 | // widget code contained in the jquery.tablesorter.widgets.js file 9 | // use the zebra stripe widget if you plan on hiding any rows (filter widget) 10 | // the uitheme widget is NOT REQUIRED! 11 | widgets : [ "filter", "columns", "zebra" ], 12 | 13 | widgetOptions : { 14 | // using the default zebra striping class name, so it actually isn't included in the theme variable above 15 | // this is ONLY needed for bootstrap theming if you are using the filter widget, because rows are hidden 16 | zebra : ["even", "odd"], 17 | 18 | // class names added to columns when sorted 19 | columns: [ "primary", "secondary", "tertiary" ], 20 | filter_columnFilters: true, 21 | 22 | // reset filters button 23 | filter_reset : ".reset", 24 | filter_placeholder: { search : 'Search...' }, 25 | } 26 | }) 27 | .tablesorterPager({ 28 | // target the pager markup - see the HTML block below 29 | container: $(".ts-pager"), 30 | 31 | // target the pager page select dropdown - choose a page 32 | cssGoto : ".pagenum", 33 | 34 | // remove rows from the table to speed up the sort of large tables. 35 | // setting this to false, only hides the non-visible rows; needed if you plan to add/remove rows with the pager enabled. 36 | removeRows: false, 37 | 38 | // output string - default is '{page}/{totalPages}'; 39 | // possible variables: {page}, {totalPages}, {filteredPages}, {startRow}, {endRow}, {filteredRows} and {totalRows} 40 | output: '{startRow} - {endRow} / {filteredRows} ({totalRows})', 41 | size: 10, 42 | }); 43 | 44 | }); 45 | -------------------------------------------------------------------------------- /util/html/sorted_table.h: -------------------------------------------------------------------------------- 1 | // Copyright 2018, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | #include 5 | #include 6 | 7 | namespace util { 8 | namespace html { 9 | 10 | class SortedTable { 11 | public: 12 | static std::string HtmlStart(); 13 | 14 | static void StartTable(const std::vector& header, std::string* dest); 15 | 16 | static void Row(const std::vector& row, std::string* dest); 17 | 18 | static void EndTable(std::string* dest); 19 | }; 20 | 21 | /* 22 | std::string SortedTable::Start(const Container& header) { 23 | std::string res( 24 | absl::StrCat(" \n")); 25 | for (auto v : header) { 26 | res.append(absl::StrCat("")); 27 | } 28 | res.append("\n \n"); 29 | return res; 30 | } 31 | 32 | template 33 | std::string SortedTable::Row(std::string_view style, const Container& row) { 34 | std::string res(""); 35 | std::string td_tag; 36 | if (style.empty()) { 37 | td_tag = ""); 43 | } 44 | res.append("\n"); 45 | return res; 46 | } 47 | */ 48 | 49 | } // namespace html 50 | } // namespace util 51 | -------------------------------------------------------------------------------- /util/html/style.css: -------------------------------------------------------------------------------- 1 | /* apply additional customization here */ 2 | 3 | .form-inline { 4 | display: flex; 5 | margin: 10px 0; 6 | } -------------------------------------------------------------------------------- /util/http/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(http_beast_prebuilt prebuilt_beast.cc) 2 | cxx_link(http_beast_prebuilt Boost::system) 3 | 4 | add_library(http_utils encoding.cc http_common.cc) 5 | cxx_link(http_utils base http_beast_prebuilt) 6 | 7 | add_library(http_server_lib status_page.cc profilez_handler.cc http_handler.cc) 8 | cxx_link(http_server_lib absl::strings absl::time base http_beast_prebuilt http_utils 9 | metrics TRDP::gperf) 10 | 11 | add_executable(http_main http_main.cc) 12 | 13 | add_library(http_client_lib http_client.cc https_client_pool.cc) 14 | 15 | cxx_link(http_client_lib fibers2 http_beast_prebuilt http_utils tls_lib) 16 | cxx_link(http_main fibers2 html_lib http_server_lib TRDP::mimalloc) 17 | -------------------------------------------------------------------------------- /util/http/captain.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romange/helio/af218326b50fb831b05fdabbf6c2087057127729/util/http/captain.gif -------------------------------------------------------------------------------- /util/http/encoding.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #include "util/http/encoding.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace { 12 | 13 | inline bool IsValidUrlChar(char ch) { 14 | static constexpr std::array kvalid{'-', '_', '.', '!', '\'', '(', ')'}; 15 | return std::isalnum(ch) || std::find(kvalid.begin(), kvalid.end(), ch) != kvalid.end(); 16 | } 17 | 18 | } // namespace 19 | 20 | namespace util::http { 21 | 22 | std::string UrlEncode(std::string_view src) { 23 | static const char digits[] = "0123456789ABCDEF"; 24 | 25 | std::string out; 26 | out.reserve(src.size() * 2 + 1); 27 | for (char ch_c : src) { 28 | unsigned char ch = static_cast(ch_c); 29 | if (IsValidUrlChar(ch)) { 30 | out.push_back(ch_c); 31 | } else { 32 | out.push_back('%'); 33 | out.push_back(digits[(ch >> 4) & 0x0F]); 34 | out.push_back(digits[ch & 0x0F]); 35 | } 36 | } 37 | 38 | return out; 39 | } 40 | 41 | } // namespace util::http 42 | -------------------------------------------------------------------------------- /util/http/encoding.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | namespace util::http { 10 | 11 | // Replace all invalid characters with percent encoding. 12 | std::string UrlEncode(std::string_view part); 13 | 14 | } // namespace util::http 15 | -------------------------------------------------------------------------------- /util/http/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romange/helio/af218326b50fb831b05fdabbf6c2087057127729/util/http/favicon-32x32.png -------------------------------------------------------------------------------- /util/http/http_common.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2021, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | 5 | #include "util/http/http_common.h" 6 | 7 | #include 8 | 9 | #include "base/logging.h" 10 | 11 | namespace util { 12 | namespace http { 13 | using namespace std; 14 | 15 | const char kHtmlMime[] = "text/html"; 16 | const char kJsonMime[] = "application/json"; 17 | const char kSvgMime[] = "image/svg+xml"; 18 | const char kTextMime[] = "text/plain"; 19 | const char kXmlMime[] = "application/xml"; 20 | const char kBinMime[] = "application/octet-stream"; 21 | 22 | 23 | QueryParam ParseQuery(std::string_view str) { 24 | std::pair res; 25 | size_t pos = str.find('?'); 26 | res.first = str.substr(0, pos); 27 | if (pos != std::string_view::npos) { 28 | res.second = str.substr(pos + 1); 29 | } 30 | return res; 31 | } 32 | 33 | QueryArgs SplitQuery(std::string_view query) { 34 | vector args = absl::StrSplit(query, '&'); 35 | vector> res(args.size()); 36 | for (size_t i = 0; i < args.size(); ++i) { 37 | size_t pos = args[i].find('='); 38 | res[i].first = args[i].substr(0, pos); 39 | res[i].second = (pos == std::string_view::npos) ? std::string_view() : args[i].substr(pos + 1); 40 | } 41 | return res; 42 | } 43 | 44 | } // namespace http 45 | } // namespace util 46 | -------------------------------------------------------------------------------- /util/http/http_common.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace util { 12 | namespace http { 13 | 14 | template inline void SetMime(const char* mime, Fields* dest) { 15 | dest->set(::boost::beast::http::field::content_type, mime); 16 | } 17 | 18 | extern const char kHtmlMime[]; 19 | extern const char kJsonMime[]; 20 | extern const char kSvgMime[]; 21 | extern const char kTextMime[]; 22 | extern const char kXmlMime[]; 23 | extern const char kBinMime[]; 24 | 25 | // URL consists of path and query delimited by '?'. 26 | // query can be broken into query args delimited by '&'. 27 | // Each query arg can be a pair of "key=value" values. 28 | // In case there is not '=' delimiter, only the first field is filled. 29 | using QueryParam = std::pair; 30 | typedef std::vector QueryArgs; 31 | 32 | // Returns a pair of path and query. 33 | QueryParam ParseQuery(std::string_view str); 34 | 35 | // Splits query into key-value pairs. 36 | QueryArgs SplitQuery(std::string_view query); 37 | 38 | } // namespace http 39 | } // namespace util 40 | -------------------------------------------------------------------------------- /util/http/http_server_utils.h: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | #include "util/http/http_common.h" 11 | 12 | namespace util { 13 | namespace http { 14 | 15 | typedef ::boost::beast::http::response<::boost::beast::http::string_body> StringResponse; 16 | 17 | inline StringResponse MakeStringResponse( 18 | ::boost::beast::http::status st = ::boost::beast::http::status::ok) { 19 | return StringResponse(st, 11); 20 | } 21 | 22 | StringResponse BuildStatusPage(const QueryArgs& args, std::string_view resource_prefix); 23 | StringResponse ProfilezHandler(const QueryArgs& args); 24 | 25 | extern const char kProfilesFolder[]; 26 | 27 | } // namespace http 28 | } // namespace util 29 | -------------------------------------------------------------------------------- /util/http/http_status_code.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | #include "http_status_code.h" 5 | 6 | #include "base/logging.h" 7 | 8 | namespace http { 9 | 10 | const char* StatusStringFromCode(HttpStatusCode code) { 11 | switch (code) { 12 | case HTTP_OK: 13 | return "200 OK"; 14 | case HTTP_ACCEPTED: 15 | return "202 Accepted"; 16 | case HTTP_NO_CONTENT: 17 | return "204 No Content"; 18 | case HTTP_BAD_REQUEST: 19 | return "400 Bad Request"; 20 | case HTTP_UNAUTHORIZED: 21 | return "401 Unauthorized"; 22 | case HTTP_FORBIDDEN: 23 | return "403 Forbidden"; 24 | case HTTP_NOT_FOUND: 25 | return "404 Not Found"; 26 | default: 27 | LOG(FATAL) << "Not implemented " << code; 28 | } 29 | return nullptr; 30 | } 31 | } // namespace http 32 | -------------------------------------------------------------------------------- /util/http/http_status_code.h: -------------------------------------------------------------------------------- 1 | // Copyright 2013, Beeri 15. All rights reserved. 2 | // Author: Roman Gershman (romange@gmail.com) 3 | // 4 | 5 | #ifndef HTTP_STATUS_CODE_H 6 | #define HTTP_STATUS_CODE_H 7 | 8 | namespace http { 9 | 10 | // HTTP status codes. 11 | // Taken from RFC 2616 Section 10. 12 | enum HttpStatusCode { 13 | // Informational 1xx 14 | HTTP_CONTINUE = 100, 15 | HTTP_SWITCHING_PROTOCOLS = 101, 16 | 17 | // Successful 2xx 18 | HTTP_OK = 200, 19 | HTTP_CREATED = 201, 20 | HTTP_ACCEPTED = 202, 21 | HTTP_NON_AUTHORITATIVE_INFORMATION = 203, 22 | HTTP_NO_CONTENT = 204, 23 | HTTP_RESET_CONTENT = 205, 24 | HTTP_PARTIAL_CONTENT = 206, 25 | 26 | // Redirection 3xx 27 | HTTP_MULTIPLE_CHOICES = 300, 28 | HTTP_MOVED_PERMANENTLY = 301, 29 | HTTP_FOUND = 302, 30 | HTTP_SEE_OTHER = 303, 31 | HTTP_NOT_MODIFIED = 304, 32 | HTTP_USE_PROXY = 305, 33 | // 306 is no longer used. 34 | HTTP_TEMPORARY_REDIRECT = 307, 35 | 36 | // Client error 4xx 37 | HTTP_BAD_REQUEST = 400, 38 | HTTP_UNAUTHORIZED = 401, 39 | HTTP_PAYMENT_REQUIRED = 402, 40 | HTTP_FORBIDDEN = 403, 41 | HTTP_NOT_FOUND = 404, 42 | HTTP_METHOD_NOT_ALLOWED = 405, 43 | HTTP_NOT_ACCEPTABLE = 406, 44 | HTTP_PROXY_AUTHENTICATION_REQUIRED = 407, 45 | HTTP_REQUEST_TIMEOUT = 408, 46 | HTTP_CONFLICT = 409, 47 | HTTP_GONE = 410, 48 | HTTP_LENGTH_REQUIRED = 411, 49 | HTTP_PRECONDITION_FAILED = 412, 50 | HTTP_REQUEST_ENTITY_TOO_LARGE = 413, 51 | HTTP_REQUEST_URI_TOO_LONG = 414, 52 | HTTP_UNSUPPORTED_MEDIA_TYPE = 415, 53 | HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416, 54 | HTTP_EXPECTATION_FAILED = 417, 55 | 56 | // Server error 5xx 57 | HTTP_INTERNAL_SERVER_ERROR = 500, 58 | HTTP_NOT_IMPLEMENTED = 501, 59 | HTTP_BAD_GATEWAY = 502, 60 | HTTP_SERVICE_UNAVAILABLE = 503, 61 | HTTP_GATEWAY_TIMEOUT = 504, 62 | HTTP_VERSION_NOT_SUPPORTED = 505, 63 | }; 64 | 65 | // '200 OK' from HTTP_OK etc. 66 | const char* StatusStringFromCode(HttpStatusCode code); 67 | 68 | } // namespace http 69 | 70 | #endif // HTTP_STATUS_CODE_H 71 | -------------------------------------------------------------------------------- /util/http/https_client_pool.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #include "util/http/https_client_pool.h" 6 | 7 | #include "base/logging.h" 8 | #include "util/http/http_client.h" 9 | 10 | namespace util { 11 | 12 | namespace http { 13 | 14 | void ClientPool::HandleGuard::operator()(Client* client) { 15 | DVLOG(1) << "HandleGuard dtor " << client << " pool_ " << pool_; 16 | CHECK(client); 17 | 18 | CHECK_GT(pool_->existing_handles_, 0); 19 | 20 | if (client->IsConnected()) { 21 | CHECK(pool_); 22 | pool_->available_handles_.emplace_back(client); 23 | } else { 24 | VLOG(1) << "Deleting client "; 25 | --pool_->existing_handles_; 26 | delete client; 27 | } 28 | } 29 | 30 | ClientPool::ClientPool(const std::string& domain, SSL_CTX* ssl_ctx, fb2::ProactorBase* pb) 31 | : domain_(domain), ssl_cntx_(ssl_ctx), proactor_(*pb) { 32 | CHECK(pb); 33 | } 34 | 35 | ClientPool::~ClientPool() { 36 | DVLOG(1) << "ClientPool dtor " << this; 37 | DCHECK_EQ(unsigned(existing_handles_), available_handles_.size()); 38 | for (auto* ptr : available_handles_) { 39 | delete ptr; 40 | } 41 | } 42 | 43 | auto ClientPool::GetHandle() -> io::Result { 44 | while (!available_handles_.empty()) { 45 | // Pulling the oldest handles first. 46 | std::unique_ptr ptr{std::move(available_handles_.front())}; 47 | 48 | available_handles_.pop_front(); 49 | 50 | if (!ptr->IsConnected()) { 51 | continue; // we just throw a connection with error status. 52 | } 53 | 54 | VLOG(1) << "Reusing https client " << ptr->native_handle(); 55 | 56 | // pass it further with custom deleter. 57 | return ClientHandle(ptr.release(), HandleGuard{this}); 58 | } 59 | 60 | // available_handles_ are empty - create a new connection. 61 | VLOG(1) << "Creating a new https client"; 62 | 63 | // TODO: create tls/Non-tls clients based on whether ssl_cntx_ is null. 64 | std::unique_ptr client(new TlsClient{&proactor_}); 65 | client->set_retry_count(retry_cnt_); 66 | if (on_connect_) { 67 | client->AssignOnConnect(on_connect_); 68 | } 69 | auto ec = client->Connect(domain_, "443", ssl_cntx_); 70 | 71 | LOG_IF(WARNING, ec) << "ClientPool: Could not connect " << ec; 72 | if (ec) 73 | return nonstd::make_unexpected(ec); 74 | ++existing_handles_; 75 | 76 | return ClientHandle{client.release(), HandleGuard{this}}; 77 | } 78 | 79 | } // namespace http 80 | } // namespace util 81 | -------------------------------------------------------------------------------- /util/http/https_client_pool.h: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "io/io.h" 13 | 14 | typedef struct ssl_ctx_st SSL_CTX; 15 | 16 | namespace util { 17 | namespace fb2 { 18 | class ProactorBase; 19 | } // namespace fb2 20 | 21 | namespace http { 22 | 23 | class Client; 24 | 25 | // thread-local pool that manages a set of http(s) connections. 26 | class ClientPool { 27 | ClientPool(const ClientPool&) = delete; 28 | ClientPool& operator=(const ClientPool&) = delete; 29 | public: 30 | class HandleGuard { 31 | public: 32 | HandleGuard(ClientPool* pool = nullptr) : pool_(pool) { 33 | } 34 | 35 | void operator()(Client* client); 36 | 37 | private: 38 | ClientPool* pool_; 39 | }; 40 | 41 | using ClientHandle = std::unique_ptr; 42 | 43 | ClientPool(const std::string& domain, SSL_CTX* ssl_ctx, fb2::ProactorBase* pb); 44 | 45 | ~ClientPool(); 46 | 47 | /*! @brief Returns https client connection from the pool. 48 | * 49 | * Must be called withing Proactor thread. Once ClientHandle destructs, 50 | * the connection returns to the pool. GetHandle() might block the calling fiber for 51 | * connect_msec_ millis in case it creates a new connection. 52 | * Note that all allocated handles must be destroyed before destroying their parent pool. 53 | */ 54 | io::Result GetHandle(); 55 | 56 | void set_connect_timeout(unsigned msec) { 57 | connect_msec_ = msec; 58 | } 59 | 60 | //! Sets number of retries for https client handles. 61 | void set_retry_count(uint32_t cnt) { 62 | retry_cnt_ = cnt; 63 | } 64 | 65 | //! Number of existing handles created by this pool. 66 | unsigned handles_count() const { 67 | return existing_handles_; 68 | } 69 | 70 | const std::string& domain() const { 71 | return domain_; 72 | } 73 | 74 | fb2::ProactorBase& proactor() { 75 | return proactor_; 76 | } 77 | 78 | void SetOnConnect(std::function cb) { 79 | on_connect_ = std::move(cb); 80 | } 81 | 82 | private: 83 | std::string domain_; 84 | SSL_CTX* ssl_cntx_; 85 | fb2::ProactorBase& proactor_; 86 | 87 | unsigned connect_msec_ = 1000, retry_cnt_ = 1; 88 | int existing_handles_ = 0; 89 | 90 | std::deque available_handles_; // Using queue to allow round-robin access. 91 | std::function on_connect_; 92 | }; 93 | 94 | } // namespace http 95 | } // namespace util 96 | -------------------------------------------------------------------------------- /util/http/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romange/helio/af218326b50fb831b05fdabbf6c2087057127729/util/http/logo.png -------------------------------------------------------------------------------- /util/http/prebuilt_beast.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #if BOOST_VERSION >= 107000 4 | #include 5 | #endif 6 | -------------------------------------------------------------------------------- /util/http/status_page.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Roboto; 3 | font-size:16px; 4 | line-height: 30px; 5 | font-weight: normal; 6 | color: #4e534b; 7 | margin-left: 30px; 8 | margin-top: 0px; 9 | margin-right: 0px; 10 | margin-bottom: 0px; 11 | /*background-color: #ebebeb;*/ 12 | background-repeat: repeat-x; 13 | } 14 | 15 | .title_text { 16 | color: #09bc8d; 17 | margin-right: 20px; 18 | } 19 | 20 | .key_text, .key_text_bold { 21 | font-style:italic; 22 | margin-right: 7px; 23 | } 24 | 25 | .key_text_bold { 26 | font-weight: bold; 27 | } 28 | 29 | .value_text { 30 | font-weight: bold; 31 | color: #d60f96; 32 | margin-right: 10px; 33 | } 34 | 35 | .separator { 36 | border-top: 1px solid rgba(211, 211, 211, 1); 37 | border-bottom: 1px solid rgba(255, 255, 255, 1); 38 | margin-top: 10px; 39 | margin-bottom: 5px; 40 | } 41 | 42 | .styled_border{ 43 | font-size:14px; 44 | border-style: groove; 45 | border-width: 2px; 46 | border-color: #FFF; 47 | margin-left:80px; 48 | margin-top:30px; 49 | float:left; 50 | padding:15px; 51 | } 52 | 53 | .left_panel { 54 | width:700px; 55 | float:left; 56 | } 57 | -------------------------------------------------------------------------------- /util/http/status_page.js: -------------------------------------------------------------------------------- 1 | function JsonToHTML(json_obj) { 2 | var str = ''; 3 | Object.keys(json_obj).forEach(function (key) { 4 | value = json_obj[key]; 5 | str += "
" + span(key, 'title_text'); 6 | if (!isObject(value)) { 7 | str += value_text(key, value); 8 | } else { 9 | str += objectObjectToHTML(value); 10 | } 11 | str += "
"; 12 | }); 13 | 14 | return str; 15 | 16 | function objectObjectToHTML(objmap) { 17 | var s = ''; 18 | Object.keys(objmap).forEach(function (key) { 19 | value = objmap[key]; 20 | if (isObject(value)) { 21 | s += span(key + ':', 'key_text_bold') 22 | s += objectObjectToHTML(value); 23 | } else { 24 | s += key_text(key); 25 | s += value_text(key, value); 26 | } 27 | }); 28 | return s; 29 | } 30 | 31 | function isObject(o) { 32 | return Object.prototype.toString.call(o) === '[object Object]'; 33 | } 34 | 35 | function span(t, s) { return "" + t + "";} 36 | 37 | function key_text(t) { 38 | return span(t + ':', 'key_text'); 39 | } 40 | 41 | function value_text(k, t) { 42 | if (k.endsWith('time') && Number.isInteger(t)) { 43 | var date = new Date(t*1000); 44 | t = date.toISOString().slice(0, 19); 45 | } 46 | 47 | return span(t + ' ', 'value_text'); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /util/metrics/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(metrics family.cc metrics.cc) 2 | 3 | cxx_link(metrics fibers2) -------------------------------------------------------------------------------- /util/metrics/metrics.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2023, Roman Gershman. All rights reserved. 2 | // See LICENSE for licensing terms. 3 | // 4 | 5 | #include "util/metrics/metrics.h" 6 | 7 | #include "base/hash.h" 8 | #include "base/logging.h" 9 | #include "util/proactor_pool.h" 10 | 11 | namespace util { 12 | 13 | using fb2::ProactorBase; 14 | 15 | namespace metrics { 16 | using namespace std; 17 | 18 | namespace { 19 | thread_local XXH3_state_t xxh3_state; 20 | } 21 | 22 | namespace detail { 23 | 24 | void SingleFamily::Init(ProactorPool* pp, initializer_list
", v, "
"; 38 | } else { 39 | td_tag = absl::StrCat(""); 40 | } 41 | for (const auto& cell : row) { 42 | absl::StrAppend(&res, td_tag, cell, "