Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .github/workflows/benchmarks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Benchmark Smoke

on:
push:
branches: [ main, master ]
paths:
- 'include/**'
- 'src/**'
- 'benchmarks/**'
- 'cmake/packages/google-benchmark.cmake'
- 'CMakeLists.txt'
- '.github/workflows/benchmarks.yml'
pull_request:
branches: [ main, master ]
paths:
- 'include/**'
- 'src/**'
- 'benchmarks/**'
- 'cmake/packages/google-benchmark.cmake'
- 'CMakeLists.txt'
- '.github/workflows/benchmarks.yml'

jobs:
benchmark-smoke:
name: Benchmark Smoke (${{ matrix.os }})
runs-on: ${{ matrix.os }}

strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]

steps:
- name: Checkout Repository
uses: actions/checkout@v4

- name: Configure Release Benchmarks
shell: bash
run: |
cmake -B build-bench \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_TESTING=OFF \
-DCPP_REQUEST_BUILD_BENCHMARKS=ON

- name: Build and Smoke Benchmarks
shell: bash
run: |
cmake --build build-bench \
--config Release \
--target cpp_request_benchmark_smoke
6 changes: 5 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

option(CPP_REQUEST_BUILD_BENCHMARKS "Build cpp_request benchmarks" OFF)

include(CTest)
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
Expand All @@ -23,7 +25,9 @@ add_subdirectory(src)
if(BUILD_TESTING)
add_subdirectory(tests)
endif()
add_subdirectory(benchmarks)
if(CPP_REQUEST_BUILD_BENCHMARKS)
add_subdirectory(benchmarks)
endif()
add_subdirectory(examples)

set(CPP_REQUEST_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/cpp_request")
Expand Down
75 changes: 75 additions & 0 deletions benchmarks/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
include(${PROJECT_SOURCE_DIR}/cmake/packages/google-benchmark.cmake)
find_package(Threads REQUIRED)

function(cpp_request_add_benchmark target source)
add_executable(${target} ${source})
target_include_directories(${target}
PRIVATE
${PROJECT_SOURCE_DIR}/src
${CMAKE_CURRENT_SOURCE_DIR}
)
target_link_libraries(${target}
PRIVATE
cpp_request::internal
benchmark::benchmark_main
Threads::Threads
)
target_compile_features(${target} PRIVATE cxx_std_17)

if(WIN32)
target_link_libraries(${target} PRIVATE ws2_32)
endif()
endfunction()

cpp_request_add_benchmark(
cpp_request_bench_request_serializer
request_serializer_benchmark.cpp
)
cpp_request_add_benchmark(
cpp_request_bench_response_parser
response_parser_benchmark.cpp
)
cpp_request_add_benchmark(
cpp_request_bench_chunked_decoder
chunked_decoder_benchmark.cpp
)
cpp_request_add_benchmark(
cpp_request_bench_loopback_client
loopback_client_benchmark.cpp
)
cpp_request_add_benchmark(
cpp_request_bench_connection_reuse
connection_reuse_benchmark.cpp
)

set(CPP_REQUEST_BENCHMARK_TARGETS
cpp_request_bench_request_serializer
cpp_request_bench_response_parser
cpp_request_bench_chunked_decoder
cpp_request_bench_loopback_client
cpp_request_bench_connection_reuse
)

add_custom_target(cpp_request_benchmarks
DEPENDS ${CPP_REQUEST_BENCHMARK_TARGETS}
)

add_custom_target(cpp_request_benchmark_smoke
COMMAND $<TARGET_FILE:cpp_request_bench_request_serializer>
--benchmark_min_time=0.01
--benchmark_repetitions=1
COMMAND $<TARGET_FILE:cpp_request_bench_response_parser>
--benchmark_min_time=0.01
--benchmark_repetitions=1
COMMAND $<TARGET_FILE:cpp_request_bench_chunked_decoder>
--benchmark_min_time=0.01
--benchmark_repetitions=1
COMMAND $<TARGET_FILE:cpp_request_bench_loopback_client>
--benchmark_min_time=0.01
--benchmark_repetitions=1
COMMAND $<TARGET_FILE:cpp_request_bench_connection_reuse>
--benchmark_min_time=0.01
--benchmark_repetitions=1
DEPENDS ${CPP_REQUEST_BENCHMARK_TARGETS}
USES_TERMINAL
)
63 changes: 63 additions & 0 deletions benchmarks/chunked_decoder_benchmark.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#include <benchmark/benchmark.h>

#include "http/chunked_decoder.hpp"

#include <cstdint>
#include <sstream>
#include <string>

namespace {

std::string make_chunked_body(std::size_t body_size, std::size_t chunk_size) {
std::string encoded;
std::size_t remaining = body_size;

while (remaining != 0) {
const std::size_t amount = remaining < chunk_size ? remaining : chunk_size;
std::ostringstream line;
line << std::hex << amount;
encoded += line.str();
encoded += "\r\n";
encoded.append(amount, 'x');
encoded += "\r\n";
remaining -= amount;
}

encoded += "0\r\nX-Benchmark: done\r\n\r\n";
return encoded;
}

void BM_ChunkedDecoder(benchmark::State& state) {
const std::size_t body_size = static_cast<std::size_t>(state.range(0));
const std::string encoded = make_chunked_body(body_size, 4096);
std::string input;
std::string output;

for (auto _ : state) {
state.PauseTiming();
input = encoded;
output.clear();
output.reserve(body_size);
state.ResumeTiming();

cpp_request::detail::http::ChunkedDecoder decoder;
auto result = decoder.process(input, output);
if (!result || output.size() != body_size) {
state.SkipWithError("chunked decoding failed");
break;
}

benchmark::DoNotOptimize(output.data());
benchmark::ClobberMemory();
}

state.SetBytesProcessed(
state.iterations() * static_cast<std::int64_t>(body_size));
}

BENCHMARK(BM_ChunkedDecoder)
->Arg(4 * 1024)
->Arg(64 * 1024)
->Arg(1024 * 1024);

} // namespace
73 changes: 73 additions & 0 deletions benchmarks/connection_reuse_benchmark.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#include <benchmark/benchmark.h>

#include "loopback_benchmark_server.hpp"

#include <cpp_request/client.hpp>

#include <chrono>
#include <string>

namespace {

void configure(cpp_request::Client& client) {
client.set_connect_timeout(std::chrono::seconds{2});
client.set_read_timeout(std::chrono::seconds{2});
client.set_write_timeout(std::chrono::seconds{2});
}

void BM_ConnectionReuse(benchmark::State& state) {
cpp_request::bench::LoopbackBenchmarkServer server{32};
if (!server.valid()) {
state.SkipWithError("failed to create loopback server");
return;
}

cpp_request::Client client;
configure(client);
const std::string url = server.url("/reuse");

auto warmup = client.get(url);
if (!warmup) {
state.SkipWithError("keep-alive warmup request failed");
return;
}

for (auto _ : state) {
auto result = client.get(url);
if (!result) {
state.SkipWithError("keep-alive request failed");
break;
}
benchmark::DoNotOptimize(result.value().body().data());
}

state.SetItemsProcessed(state.iterations());
}

void BM_ConnectionReconnect(benchmark::State& state) {
cpp_request::bench::LoopbackBenchmarkServer server{32};
if (!server.valid()) {
state.SkipWithError("failed to create loopback server");
return;
}

const std::string url = server.url("/reconnect");

for (auto _ : state) {
cpp_request::Client client;
configure(client);
auto result = client.get(url);
if (!result) {
state.SkipWithError("reconnect request failed");
break;
}
benchmark::DoNotOptimize(result.value().body().data());
}

state.SetItemsProcessed(state.iterations());
}

BENCHMARK(BM_ConnectionReuse)->UseRealTime();
BENCHMARK(BM_ConnectionReconnect)->UseRealTime();

} // namespace
Loading
Loading