-
Notifications
You must be signed in to change notification settings - Fork 9
feat(wish): add no-TLS benchmark #26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,12 +6,31 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) | |
|
|
||
| include(FetchContent) | ||
|
|
||
| set(ABSL_ENABLE_INSTALL OFF CACHE BOOL "" FORCE) | ||
| set(ABSL_PROPAGATE_CXX_STD ON) | ||
| FetchContent_Declare( | ||
| abseil | ||
| GIT_REPOSITORY https://github.com/abseil/abseil-cpp.git | ||
| GIT_TAG 20260107.1 | ||
| ) | ||
|
|
||
| set(WSLAY_TESTS OFF CACHE BOOL "" FORCE) | ||
| set(WSLAY_EXAMPLES OFF CACHE BOOL "" FORCE) | ||
| FetchContent_Declare( | ||
| wslay | ||
| GIT_REPOSITORY https://github.com/tatsuhiro-t/wslay.git | ||
| GIT_TAG master | ||
| ) | ||
|
|
||
| # Disable OpenSSL and Mbed TLS. We use BoringSSL instead. | ||
| set(EVENT__DISABLE_OPENSSL ON CACHE BOOL "" FORCE) | ||
| set(EVENT__DISABLE_MBEDTLS ON CACHE BOOL "" FORCE) | ||
|
|
||
| set(EVENT__DISABLE_BENCHMARKS ON CACHE BOOL "" FORCE) | ||
| set(EVENT__DISABLE_TESTS ON CACHE BOOL "" FORCE) | ||
| set(EVENT__DISABLE_REGRESS ON CACHE BOOL "" FORCE) | ||
| set(EVENT__DISABLE_SAMPLES ON CACHE BOOL "" FORCE) | ||
|
Comment on lines
+25
to
+32
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. moved |
||
|
|
||
| FetchContent_Declare( | ||
| libevent | ||
| GIT_REPOSITORY https://github.com/libevent/libevent.git | ||
|
|
@@ -21,20 +40,24 @@ FetchContent_Declare( | |
| FetchContent_Declare( | ||
| boringssl | ||
| GIT_REPOSITORY https://boringssl.googlesource.com/boringssl | ||
| GIT_TAG main | ||
| GIT_TAG 0.20260211.0 | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. changed to use the latest release as faced a build error |
||
| ) | ||
|
|
||
| # Disable OpenSSL and Mbed TLS. We use BoringSSL instead. | ||
| set(EVENT__DISABLE_OPENSSL ON CACHE BOOL "" FORCE) | ||
| set(EVENT__DISABLE_MBEDTLS ON CACHE BOOL "" FORCE) | ||
| set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE) | ||
| set(BENCHMARK_ENABLE_GTEST_TESTS OFF CACHE BOOL "" FORCE) | ||
| set(BENCHMARK_ENABLE_INSTALL OFF CACHE BOOL "" FORCE) | ||
| set(BENCHMARK_INSTALL_DOCS OFF CACHE BOOL "" FORCE) | ||
| set(BENCHMARK_DOWNLOAD_DEPENDENCIES OFF CACHE BOOL "" FORCE) | ||
| FetchContent_Declare( | ||
| googlebenchmark | ||
| GIT_REPOSITORY https://github.com/google/benchmark.git | ||
| GIT_TAG main | ||
| ) | ||
|
|
||
| # Exclude tests, sample files, etc. that we don't need. | ||
| set(EVENT__DISABLE_BENCHMARKS ON CACHE BOOL "" FORCE) | ||
| set(EVENT__DISABLE_TESTS ON CACHE BOOL "" FORCE) | ||
| set(EVENT__DISABLE_REGRESS ON CACHE BOOL "" FORCE) | ||
| set(EVENT__DISABLE_SAMPLES ON CACHE BOOL "" FORCE) | ||
| # Exclude tests, benchmarks, sample files, etc. from dependencies. | ||
| set(BUILD_TESTING OFF CACHE BOOL "" FORCE) | ||
|
|
||
| FetchContent_MakeAvailable(wslay libevent boringssl) | ||
| FetchContent_MakeAvailable(wslay libevent boringssl googlebenchmark abseil) | ||
|
|
||
| include_directories(${wslay_SOURCE_DIR}/lib/includes) | ||
| include_directories(${libevent_SOURCE_DIR}/include) | ||
|
|
@@ -67,3 +90,5 @@ target_link_libraries(hello_client wish_handler) | |
|
|
||
| add_executable(benchmark_client examples/benchmark_client.cc) | ||
| target_link_libraries(benchmark_client wish_handler) | ||
|
|
||
| add_subdirectory(benchmark) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| add_executable(wish_echo_server | ||
| server.cc | ||
| ) | ||
| target_link_libraries(wish_echo_server | ||
| wish_handler | ||
| absl::flags | ||
| absl::flags_parse | ||
| absl::log | ||
| absl::log_initialize | ||
| "$<LINK_LIBRARY:WHOLE_ARCHIVE,absl::log_flags>" | ||
| ) | ||
|
|
||
| add_executable(wish_bench_client | ||
| client.cc | ||
| ) | ||
| target_link_libraries(wish_bench_client | ||
| wish_handler | ||
| absl::flags | ||
| absl::flags_parse | ||
| absl::log | ||
| absl::log_initialize | ||
| "$<LINK_LIBRARY:WHOLE_ARCHIVE,absl::log_flags>" | ||
| benchmark::benchmark | ||
| ) |
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. copied from |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| import argparse | ||
| import shlex | ||
| import subprocess | ||
| import time | ||
| import os | ||
| import signal | ||
|
|
||
| BUILD_DIR = "./build" | ||
| SERVER_BINARY_NAME = "benchmark/wish_echo_server" | ||
| CLIENT_BINARY_NAME = "benchmark/wish_bench_client" | ||
| SERVER_BINARY_PATH = os.path.join(BUILD_DIR, SERVER_BINARY_NAME) | ||
| CLIENT_BINARY_PATH = os.path.join(BUILD_DIR, CLIENT_BINARY_NAME) | ||
|
|
||
|
|
||
| def _client_host_from_remote_target(remote_host): | ||
| if "@" in remote_host: | ||
| return remote_host.split("@", 1)[1] | ||
| return remote_host | ||
|
|
||
|
|
||
| def _start_server(remote_host): | ||
| if not remote_host: | ||
| return subprocess.Popen([SERVER_BINARY_PATH]) | ||
|
|
||
| remote_binary_path = f"/tmp/{SERVER_BINARY_NAME}" | ||
|
|
||
| subprocess.run( | ||
| ["ssh", remote_host, f"rm -f {shlex.quote(remote_binary_path)}"], | ||
| check=True, | ||
| ) | ||
| subprocess.run( | ||
| ["scp", SERVER_BINARY_PATH, f"{remote_host}:{remote_binary_path}"], | ||
| check=True, | ||
| ) | ||
|
|
||
| remote_command = ( | ||
| f"chmod +x {shlex.quote(remote_binary_path)} && " | ||
| f"{shlex.quote(remote_binary_path)}" | ||
| ) | ||
|
|
||
| # Pass the `-t` option multiple times to ensure a pseudo-terminal is allocated, so that we can send a SIGTERM signal to the remote process, not the `ssh` process itself. This allows us to properly terminate the remote server when the benchmark is done. | ||
| return subprocess.Popen(["ssh", "-tt", remote_host, remote_command], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0) | ||
|
|
||
|
|
||
| def run_benchmark(remote_host=None): | ||
| print("Running benchmark ...") | ||
|
|
||
| client_host = "127.0.0.1" | ||
| if remote_host: | ||
| client_host = _client_host_from_remote_target(remote_host) | ||
| print(f"Starting remote server via SSH on {remote_host} ...") | ||
| else: | ||
| print("Starting local server ...") | ||
|
|
||
| server_process = _start_server(remote_host) | ||
| time.sleep(2) # wait for server to start | ||
|
|
||
| if server_process.poll() is not None: | ||
| raise RuntimeError(f"{SERVER_BINARY_NAME} failed to start") | ||
|
|
||
| try: | ||
| subprocess.run([ | ||
| CLIENT_BINARY_PATH, | ||
| "--stderrthreshold=0", | ||
| "--benchmark_counters_tabular=true", | ||
| "--benchmark_min_time=5.0s", | ||
| f"--host={client_host}", | ||
| ], capture_output=False, text=True, check=True) | ||
| except subprocess.CalledProcessError as e: | ||
| print(f"Error running client: {e}") | ||
| except Exception as e: | ||
| print(f"Failed to start/run client: {e}") | ||
| finally: | ||
| if server_process.poll() is None: | ||
| server_process.terminate() | ||
| server_process.wait() | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| parser = argparse.ArgumentParser(description="Run benchmark") | ||
| parser.add_argument( | ||
| "--remote-host", | ||
| help="SSH target for remote server, e.g. user@10.0.0.5", | ||
| ) | ||
| args = parser.parse_args() | ||
|
|
||
| if not os.path.isdir(BUILD_DIR): | ||
| print("Error: 'build' directory not found. Please compile the project first.") | ||
| exit(1) | ||
|
|
||
| if not os.path.isfile(SERVER_BINARY_PATH): | ||
| print(f"Error: '{SERVER_BINARY_PATH}' not found. Please compile the project first.") | ||
| exit(1) | ||
|
|
||
| if not os.path.isfile(CLIENT_BINARY_PATH): | ||
| print(f"Error: '{CLIENT_BINARY_PATH}' not found. Please compile the project first.") | ||
| exit(1) | ||
|
|
||
| print("Starting benchmarks...") | ||
| run_benchmark(remote_host=args.remote_host) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,192 @@ | ||
| #include <arpa/inet.h> | ||
| #include <event2/bufferevent.h> | ||
| #include <event2/event.h> | ||
| #include <netinet/in.h> | ||
| #include <netinet/tcp.h> | ||
| #include <sys/socket.h> | ||
|
|
||
| #include <algorithm> | ||
| #include <chrono> | ||
| #include <cstring> | ||
| #include <string> | ||
| #include <vector> | ||
|
|
||
| #include "../src/wish_handler.h" | ||
| #include "absl/flags/flag.h" | ||
| #include "absl/flags/parse.h" | ||
| #include "absl/log/initialize.h" | ||
| #include "absl/log/log.h" | ||
| #include "benchmark/benchmark.h" | ||
|
|
||
| ABSL_FLAG(std::string, host, "127.0.0.1", "Server host to connect to"); | ||
| ABSL_FLAG(int, port, 8080, "Server port to connect to"); | ||
|
|
||
| namespace { | ||
|
|
||
| struct ClientState { | ||
| struct event_base* base = nullptr; | ||
| struct bufferevent* bev = nullptr; | ||
| WishHandler* handler = nullptr; | ||
|
|
||
| bool connected = false; | ||
| bool awaiting_response = false; | ||
| std::chrono::steady_clock::time_point request_start; | ||
| std::vector<double> latencies_us; | ||
| }; | ||
|
|
||
| double PercentileFromSorted(const std::vector<double>& values, double p) { | ||
| if (values.empty()) { | ||
| return 0.0; | ||
| } | ||
| const size_t idx = static_cast<size_t>(p * (values.size() - 1)); | ||
| return values[idx]; | ||
| } | ||
|
|
||
| bool InitConnection(ClientState* client) { | ||
| client->base = event_base_new(); | ||
| if (!client->base) { | ||
| LOG(ERROR) << "event_base_new() failed"; | ||
| return false; | ||
| } | ||
|
|
||
| struct sockaddr_in sin; | ||
| std::memset(&sin, 0, sizeof(sin)); | ||
| sin.sin_family = AF_INET; | ||
| sin.sin_port = htons(absl::GetFlag(FLAGS_port)); | ||
|
|
||
| const std::string host = absl::GetFlag(FLAGS_host); | ||
|
|
||
| if (inet_pton(AF_INET, host.c_str(), &sin.sin_addr) != 1) { | ||
| LOG(ERROR) << "Invalid IPv4 host: " << host; | ||
| return false; | ||
| } | ||
|
|
||
| client->bev = bufferevent_socket_new(client->base, -1, BEV_OPT_CLOSE_ON_FREE); | ||
| if (!client->bev) { | ||
| LOG(ERROR) << "bufferevent_socket_new() failed"; | ||
| return false; | ||
| } | ||
|
|
||
| if (bufferevent_socket_connect(client->bev, | ||
| reinterpret_cast<struct sockaddr*>(&sin), | ||
| sizeof(sin)) < 0) { | ||
| LOG(ERROR) << "bufferevent_socket_connect() failed"; | ||
| return false; | ||
| } | ||
|
|
||
| client->handler = new WishHandler(client->bev, false); | ||
| client->handler->SetOnOpen([client]() { | ||
| const int fd = bufferevent_getfd(client->bev); | ||
| if (fd >= 0) { | ||
| int nodelay = 1; | ||
| setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay)); | ||
| } | ||
|
|
||
| client->connected = true; | ||
| event_base_loopexit(client->base, nullptr); | ||
| }); | ||
|
|
||
| client->handler->SetOnMessage([client](uint8_t opcode, const std::string& msg) { | ||
| (void)opcode; | ||
| (void)msg; | ||
|
|
||
| if (!client->awaiting_response) { | ||
| return; | ||
| } | ||
|
|
||
| const auto end = std::chrono::steady_clock::now(); | ||
| const double latency_us = | ||
| std::chrono::duration<double, std::micro>(end - client->request_start) | ||
| .count(); | ||
| client->latencies_us.push_back(latency_us); | ||
| client->awaiting_response = false; | ||
| event_base_loopexit(client->base, nullptr); | ||
| }); | ||
|
|
||
| client->handler->Start(); | ||
|
|
||
| event_base_dispatch(client->base); | ||
| return client->connected; | ||
| } | ||
|
|
||
| void CleanupConnection(ClientState* client) { | ||
| if (client->handler) { | ||
| delete client->handler; | ||
| client->handler = nullptr; | ||
| client->bev = nullptr; | ||
| } | ||
|
|
||
| if (client->base) { | ||
| event_base_free(client->base); | ||
| client->base = nullptr; | ||
| } | ||
| } | ||
|
|
||
| static void BM_WiSHClient(benchmark::State& state) { | ||
| const int payload_size = static_cast<int>(state.range(0)); | ||
| const std::string payload(payload_size, 'A'); | ||
|
|
||
| ClientState client; | ||
| if (!InitConnection(&client)) { | ||
| state.SkipWithError("Failed to establish WiSH connection"); | ||
| CleanupConnection(&client); | ||
| return; | ||
| } | ||
|
|
||
| for (auto _ : state) { | ||
| (void)_; | ||
| client.awaiting_response = true; | ||
| client.request_start = std::chrono::steady_clock::now(); | ||
|
|
||
| const int send_result = client.handler->SendBinary(payload); | ||
| if (send_result != 0) { | ||
| state.SkipWithError("WishHandler::SendBinary failed"); | ||
| break; | ||
| } | ||
|
|
||
| event_base_dispatch(client.base); | ||
| if (client.awaiting_response) { | ||
| state.SkipWithError("Connection closed before response"); | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| if (!client.latencies_us.empty()) { | ||
| std::sort(client.latencies_us.begin(), client.latencies_us.end()); | ||
|
|
||
| state.counters["p10_latency_us"] = | ||
| PercentileFromSorted(client.latencies_us, 0.10); | ||
| state.counters["p50_latency_us"] = | ||
| PercentileFromSorted(client.latencies_us, 0.50); | ||
| state.counters["p90_latency_us"] = | ||
| PercentileFromSorted(client.latencies_us, 0.90); | ||
| state.counters["p99_latency_us"] = | ||
| PercentileFromSorted(client.latencies_us, 0.99); | ||
|
|
||
| state.SetItemsProcessed(client.latencies_us.size()); | ||
| } | ||
|
|
||
| CleanupConnection(&client); | ||
| } | ||
|
|
||
| BENCHMARK(BM_WiSHClient) | ||
| ->UseRealTime() | ||
| ->Unit(benchmark::kMicrosecond) | ||
| ->Args({0}) | ||
| ->RangeMultiplier(2) | ||
| ->Range(1 << 10, 128 << 10); | ||
|
|
||
| } // namespace | ||
|
|
||
| int main(int argc, char** argv) { | ||
| benchmark::MaybeReenterWithoutASLR(argc, argv); | ||
| benchmark::Initialize(&argc, argv); | ||
|
|
||
| absl::ParseCommandLine(argc, argv); | ||
| absl::InitializeLog(); | ||
|
|
||
| benchmark::RunSpecifiedBenchmarks(); | ||
| benchmark::Shutdown(); | ||
|
|
||
| return 0; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added using this opportunity