diff --git a/http2/.gitignore b/http2/.gitignore new file mode 100644 index 0000000..62199d9 --- /dev/null +++ b/http2/.gitignore @@ -0,0 +1,10 @@ +h2_bench_client +h2_echo_server + +CMakeFiles +CMakeCache.txt +Makefile + +build + +*.log \ No newline at end of file diff --git a/http2/CMakeLists.txt b/http2/CMakeLists.txt new file mode 100644 index 0000000..27cb713 --- /dev/null +++ b/http2/CMakeLists.txt @@ -0,0 +1,42 @@ +cmake_minimum_required(VERSION 3.15) +project(http2_benchmark) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +include(FetchContent) + +set(FETCHCONTENT_QUIET OFF) + +# Abseil +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 +) + +FetchContent_MakeAvailable(abseil) + +find_package(PkgConfig REQUIRED) +pkg_check_modules(LIBEVENT REQUIRED libevent) +pkg_check_modules(LIBNGHTTP2 REQUIRED libnghttp2) + +include_directories( + ${LIBEVENT_INCLUDE_DIRS} + ${LIBNGHTTP2_INCLUDE_DIRS} + ${CMAKE_CURRENT_BINARY_DIR} +) + +link_directories( + ${LIBEVENT_LIBRARY_DIRS} + ${LIBNGHTTP2_LIBRARY_DIRS} +) + +add_executable(h2_echo_server server.cc) +target_link_libraries(h2_echo_server ${LIBEVENT_LIBRARIES} ${LIBNGHTTP2_LIBRARIES} absl::log absl::log_initialize absl::flags absl::flags_parse "$") + +add_executable(h2_bench_client client.cc) +target_link_libraries(h2_bench_client ${LIBEVENT_LIBRARIES} ${LIBNGHTTP2_LIBRARIES} absl::log absl::log_initialize absl::flags absl::flags_parse "$") diff --git a/http2/benchmark.py b/http2/benchmark.py new file mode 100644 index 0000000..cc2a3de --- /dev/null +++ b/http2/benchmark.py @@ -0,0 +1,48 @@ +import subprocess +import time +import os + +TOTAL_DATA_BYTES = 128 * 1024 * 1024 + +PAYLOAD_SIZES = { + "1KiB": 1024, + "128KiB": 131072, + "1MiB": 1048576, +} + +def run_benchmark(): + with open("results.txt", "w") as f: + for name, size in PAYLOAD_SIZES.items(): + num_requests = TOTAL_DATA_BYTES // size + f.write(f"=== Benchmark for {name} payload ({size} bytes), {num_requests} requests ===\n\n") + + f.write("--- Raw HTTP/2 ---\n") + print(f"Running Raw HTTP/2 bench for {name}...") + server_process = subprocess.Popen(["./build/h2_echo_server"]) + time.sleep(1) # wait for server to start + try: + client_output = subprocess.check_output([ + "./build/h2_bench_client", + f"--payload_size={size}", + f"--num_requests={num_requests}", + "--stderrthreshold=0", + ], stderr=subprocess.STDOUT, text=True) + f.write(client_output) + except subprocess.CalledProcessError as e: + f.write(f"Error running raw HTTP/2 client:\n{e.output}\n") + except Exception as e: + f.write(f"Failed to start/run HTTP/2 client: {e}\n") + finally: + server_process.terminate() + server_process.wait() + + f.write("\n\n") + +if __name__ == "__main__": + if not os.path.isdir("build"): + print("Error: 'build' directory not found. Please compile the project first.") + exit(1) + + print("Starting benchmarks...") + run_benchmark() + print("Benchmark complete! Results saved to results.txt.") diff --git a/http2/client.cc b/http2/client.cc new file mode 100644 index 0000000..3b76495 --- /dev/null +++ b/http2/client.cc @@ -0,0 +1,293 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "absl/flags/flag.h" +#include "absl/flags/parse.h" +#include "absl/log/flags.h" +#include "absl/log/initialize.h" +#include "absl/log/log.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"); +ABSL_FLAG(int, num_requests, 5000, "Total number of requests to send"); +ABSL_FLAG(int, payload_size, 1024 * 1024, "Size of the request payload in bytes"); +ABSL_FLAG(int, report_interval, 1000, "Frequency of progress reports"); +ABSL_FLAG(int, concurrent_requests, 100, "Number of concurrent requests"); + +#define MAKE_NV(name, value) \ + { \ + (uint8_t*)(name), (uint8_t*)(value), strlen(name), strlen(value), \ + NGHTTP2_NV_FLAG_NONE} + +std::string request_payload_data; + +struct ClientSession { + struct bufferevent* bev; + nghttp2_session* session; + struct event_base* evbase; + int requests_completed; + int requests_in_flight; + int total_requests_submitted; + struct timeval start_time; + std::map request_start_times; + std::vector latencies; +}; + +static void submit_request(struct ClientSession* client); + +static ssize_t send_callback(nghttp2_session* session, const uint8_t* data, size_t length, int flags, void* user_data) { + struct ClientSession* client = (struct ClientSession*)user_data; + struct bufferevent* bev = client->bev; + struct evbuffer* output = bufferevent_get_output(bev); + evbuffer_add(output, data, length); + return (ssize_t)length; +} + +static int on_frame_recv_callback(nghttp2_session* session, const nghttp2_frame* frame, void* user_data) { + struct ClientSession* client = (struct ClientSession*)user_data; + + VLOG(2) << "Client received frame type " << static_cast(frame->hd.type) << " on stream " << frame->hd.stream_id; + + if (frame->hd.type == NGHTTP2_DATA && (frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) { + client->requests_completed++; + client->requests_in_flight--; + + int report_interval = absl::GetFlag(FLAGS_report_interval); + if (report_interval > 0 && client->requests_completed % report_interval == 0) { + struct timeval now; + gettimeofday(&now, NULL); + double elapsed = (now.tv_sec - client->start_time.tv_sec) + + (now.tv_usec - client->start_time.tv_usec) / 1000000.0; + LOG(INFO) << "Completed " << client->requests_completed << " requests. RPS: " + << client->requests_completed / elapsed; + } + + auto it = client->request_start_times.find(frame->hd.stream_id); + if (it != client->request_start_times.end()) { + auto end_time = std::chrono::steady_clock::now(); + double duration = std::chrono::duration(end_time - it->second).count(); + client->latencies.push_back(duration); + client->request_start_times.erase(it); + } + + if (client->requests_completed < absl::GetFlag(FLAGS_num_requests)) { // Limit total requests for bench + submit_request(client); + } else if (client->requests_in_flight == 0) { + event_base_loopexit(client->evbase, NULL); + } + } else if (frame->hd.type == NGHTTP2_HEADERS && (frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) { + // If server sent end stream on headers (e.g., error without body) + LOG(INFO) << "Client received END_STREAM on headers"; + } + return 0; +} + +static ssize_t data_provider_callback(nghttp2_session* session, int32_t stream_id, uint8_t* buf, size_t length, uint32_t* data_flags, nghttp2_data_source* source, void* user_data) { + int* bytes_sent = (int*)source->ptr; + size_t payload_len = request_payload_data.length(); + + if (*bytes_sent >= payload_len) { + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + return 0; + } + + size_t remaining = payload_len - *bytes_sent; + size_t send_len = remaining < length ? remaining : length; + memcpy(buf, request_payload_data.data() + *bytes_sent, send_len); + + *bytes_sent += send_len; + + if (*bytes_sent >= payload_len) { + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + } + + return send_len; +} + +static int on_stream_close_callback(nghttp2_session* session, int32_t stream_id, uint32_t error_code, void* user_data) { + int* bytes_sent = (int*)nghttp2_session_get_stream_user_data(session, stream_id); + if (bytes_sent) { + delete bytes_sent; + } + return 0; +} + +static void submit_request(struct ClientSession* client) { + const nghttp2_nv hdrs[] = { + MAKE_NV(":method", "POST"), + MAKE_NV(":path", "/"), + MAKE_NV(":scheme", "http"), + MAKE_NV(":authority", "127.0.0.1:8080"), + }; + + int* bytes_sent = new int; + *bytes_sent = 0; + + nghttp2_data_provider data_prd; + data_prd.source.ptr = bytes_sent; + data_prd.read_callback = data_provider_callback; + + int32_t stream_id = nghttp2_submit_request(client->session, NULL, hdrs, 4, &data_prd, bytes_sent); + if (stream_id < 0) { + LOG(ERROR) << "nghttp2_submit_request error"; + delete bytes_sent; + } else { + client->requests_in_flight++; + client->total_requests_submitted++; + client->request_start_times[stream_id] = std::chrono::steady_clock::now(); + } + nghttp2_session_send(client->session); +} + +static void setup_nghttp2_callbacks(nghttp2_session_callbacks* callbacks) { + nghttp2_session_callbacks_set_send_callback(callbacks, send_callback); + nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, on_frame_recv_callback); + nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, on_stream_close_callback); +} + +static void readcb(struct bufferevent* bev, void* ptr) { + struct ClientSession* client = (struct ClientSession*)ptr; + + struct evbuffer* input = bufferevent_get_input(bev); + size_t datalen = evbuffer_get_length(input); + VLOG(2) << "Client readcb: got " << datalen << " bytes"; + + if (datalen == 0) return; + + unsigned char* data = evbuffer_pullup(input, -1); + ssize_t readlen = nghttp2_session_mem_recv(client->session, data, datalen); + if (readlen < 0) { + LOG(ERROR) << "Client: nghttp2_session_mem_recv error: " << nghttp2_strerror(static_cast(readlen)); + return; + } + + VLOG(2) << "Client readcb: consumed " << readlen << " bytes"; + + evbuffer_drain(input, readlen); + nghttp2_session_send(client->session); +} + +static void eventcb(struct bufferevent* bev, short events, void* ptr) { + struct ClientSession* client = (struct ClientSession*)ptr; + + if (events & BEV_EVENT_CONNECTED) { + LOG(INFO) << "Connected to server."; + + nghttp2_session_callbacks* callbacks; + nghttp2_session_callbacks_new(&callbacks); + setup_nghttp2_callbacks(callbacks); + + nghttp2_session_client_new(&client->session, callbacks, client); + + nghttp2_session_callbacks_del(callbacks); + + // Send connection preface and initial settings + nghttp2_settings_entry iv[2] = { + {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, static_cast(absl::GetFlag(FLAGS_concurrent_requests))}, + {NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, 128 * 1024 * 1024}}; + nghttp2_submit_settings(client->session, NGHTTP2_FLAG_NONE, iv, 2); + nghttp2_submit_window_update(client->session, NGHTTP2_FLAG_NONE, 0, 128 * 1024 * 1024); + + gettimeofday(&client->start_time, NULL); + + for (int i = 0; i < absl::GetFlag(FLAGS_concurrent_requests); i++) { + submit_request(client); + } + + return; + } + + if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) { + LOG(INFO) << "Connection closed or error."; + event_base_loopexit(client->evbase, NULL); + } +} + +int main(int argc, char** argv) { + absl::ParseCommandLine(argc, argv); + + absl::InitializeLog(); + + request_payload_data = std::string(absl::GetFlag(FLAGS_payload_size), 'A'); + + LOG(INFO) << "Starting HTTP/2 benchmark client..."; + + ClientSession client = {}; + client.requests_completed = 0; + client.requests_in_flight = 0; + client.total_requests_submitted = 0; + + struct event_base* base = event_base_new(); + client.evbase = base; + + struct sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + + const char* host = absl::GetFlag(FLAGS_host).c_str(); + int port = absl::GetFlag(FLAGS_port); + + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = inet_addr(host); + sin.sin_port = htons(port); + + struct bufferevent* bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE); + client.bev = bev; + + bufferevent_setcb(bev, readcb, NULL, eventcb, &client); + bufferevent_enable(bev, EV_READ | EV_WRITE); + + if (bufferevent_socket_connect(bev, (struct sockaddr*)&sin, sizeof(sin)) < 0) { + LOG(ERROR) << "Connect failed"; + return 1; + } + + event_base_dispatch(base); + + struct timeval end_time; + gettimeofday(&end_time, NULL); + double total_elapsed = (end_time.tv_sec - client.start_time.tv_sec) + + (end_time.tv_usec - client.start_time.tv_usec) / 1000000.0; + + LOG(INFO) << "Finished benchmark."; + LOG(INFO) << "Total RPS: " << client.requests_completed / total_elapsed; + + if (!client.latencies.empty()) { + double sum = std::accumulate(client.latencies.begin(), client.latencies.end(), 0.0); + double avg = sum / client.latencies.size(); + + double sq_sum = std::inner_product(client.latencies.begin(), client.latencies.end(), client.latencies.begin(), 0.0); + double stdev = std::sqrt(sq_sum / client.latencies.size() - avg * avg); + + LOG(INFO) << "Latency Stats (ms):"; + LOG(INFO) << " Average: " << avg; + LOG(INFO) << " StdDev: " << stdev; + LOG(INFO) << " Min: " << *std::min_element(client.latencies.begin(), client.latencies.end()); + LOG(INFO) << " Max: " << *std::max_element(client.latencies.begin(), client.latencies.end()); + LOG(INFO) << " Samples: " << client.latencies.size(); + } + + if (client.session) nghttp2_session_del(client.session); + bufferevent_free(bev); + event_base_free(base); + + return 0; +} diff --git a/http2/results.txt b/http2/results.txt new file mode 100644 index 0000000..56d6d11 --- /dev/null +++ b/http2/results.txt @@ -0,0 +1,177 @@ +=== Benchmark for 1KiB payload (1024 bytes), 131072 requests === + +--- Raw HTTP/2 --- +I0309 01:38:11.316409 1084347 client.cc:232] Starting HTTP/2 benchmark client... +I0309 01:38:11.316674 1084347 client.cc:193] Connected to server. +I0309 01:38:11.344276 1084347 client.cc:80] Completed 1000 requests. RPS: 36306.9 +I0309 01:38:11.368931 1084347 client.cc:80] Completed 2000 requests. RPS: 38315.6 +I0309 01:38:11.399415 1084347 client.cc:80] Completed 3000 requests. RPS: 36284 +I0309 01:38:11.447078 1084347 client.cc:80] Completed 4000 requests. RPS: 30688 +I0309 01:38:11.481294 1084347 client.cc:80] Completed 5000 requests. RPS: 30383.9 +I0309 01:38:11.526778 1084347 client.cc:80] Completed 6000 requests. RPS: 28565.3 +I0309 01:38:11.580935 1084347 client.cc:80] Completed 7000 requests. RPS: 26495 +I0309 01:38:11.617604 1084347 client.cc:80] Completed 8000 requests. RPS: 26589.5 +I0309 01:38:11.641957 1084347 client.cc:80] Completed 9000 requests. RPS: 27673.3 +I0309 01:38:11.670096 1084347 client.cc:80] Completed 10000 requests. RPS: 28299.6 +I0309 01:38:11.711401 1084347 client.cc:80] Completed 11000 requests. RPS: 27871.6 +I0309 01:38:11.745560 1084347 client.cc:80] Completed 12000 requests. RPS: 27983.3 +I0309 01:38:11.773481 1084347 client.cc:80] Completed 13000 requests. RPS: 28462.1 +I0309 01:38:11.802961 1084347 client.cc:80] Completed 14000 requests. RPS: 28793.1 +I0309 01:38:11.829263 1084347 client.cc:80] Completed 15000 requests. RPS: 29266.6 +I0309 01:38:11.861379 1084347 client.cc:80] Completed 16000 requests. RPS: 29376.9 +I0309 01:38:11.888942 1084347 client.cc:80] Completed 17000 requests. RPS: 29709.4 +I0309 01:38:11.913996 1084347 client.cc:80] Completed 18000 requests. RPS: 30137.5 +I0309 01:38:11.931903 1084347 client.cc:80] Completed 19000 requests. RPS: 30885.8 +I0309 01:38:11.948686 1084347 client.cc:80] Completed 20000 requests. RPS: 31648 +I0309 01:38:11.966771 1084347 client.cc:80] Completed 21000 requests. RPS: 32305.8 +I0309 01:38:11.984211 1084347 client.cc:80] Completed 22000 requests. RPS: 32959.9 +I0309 01:38:12.000964 1084347 client.cc:80] Completed 23000 requests. RPS: 33614.4 +I0309 01:38:12.017795 1084347 client.cc:80] Completed 24000 requests. RPS: 34233.8 +I0309 01:38:12.033360 1084347 client.cc:80] Completed 25000 requests. RPS: 34885.7 +I0309 01:38:12.050673 1084347 client.cc:80] Completed 26000 requests. RPS: 35425.3 +I0309 01:38:12.069318 1084347 client.cc:80] Completed 27000 requests. RPS: 35876.4 +I0309 01:38:12.089540 1084347 client.cc:80] Completed 28000 requests. RPS: 36231.6 +I0309 01:38:12.108889 1084347 client.cc:80] Completed 29000 requests. RPS: 36609 +I0309 01:38:12.126869 1084347 client.cc:80] Completed 30000 requests. RPS: 37030.8 +I0309 01:38:12.143628 1084347 client.cc:80] Completed 31000 requests. RPS: 37489.7 +I0309 01:38:12.164345 1084347 client.cc:80] Completed 32000 requests. RPS: 37753.2 +I0309 01:38:12.186789 1084347 client.cc:80] Completed 33000 requests. RPS: 37928.6 +I0309 01:38:12.204939 1084347 client.cc:80] Completed 34000 requests. RPS: 38279.5 +I0309 01:38:12.221726 1084347 client.cc:80] Completed 35000 requests. RPS: 38674.3 +I0309 01:38:12.238410 1084347 client.cc:80] Completed 36000 requests. RPS: 39059.3 +I0309 01:38:12.255635 1084347 client.cc:80] Completed 37000 requests. RPS: 39407.7 +I0309 01:38:12.272982 1084347 client.cc:80] Completed 38000 requests. RPS: 39738.6 +I0309 01:38:12.290762 1084347 client.cc:80] Completed 39000 requests. RPS: 40039.9 +I0309 01:38:12.308500 1084347 client.cc:80] Completed 40000 requests. RPS: 40332.1 +I0309 01:38:12.325485 1084347 client.cc:80] Completed 41000 requests. RPS: 40644.3 +I0309 01:38:12.342309 1084347 client.cc:80] Completed 42000 requests. RPS: 40952.6 +I0309 01:38:12.367638 1084347 client.cc:80] Completed 43000 requests. RPS: 40917.1 +I0309 01:38:12.383718 1084347 client.cc:80] Completed 44000 requests. RPS: 41237.7 +I0309 01:38:12.408543 1084347 client.cc:80] Completed 45000 requests. RPS: 41216 +I0309 01:38:12.425364 1084347 client.cc:80] Completed 46000 requests. RPS: 41492.7 +I0309 01:38:12.445683 1084347 client.cc:80] Completed 47000 requests. RPS: 41631.6 +I0309 01:38:12.468352 1084347 client.cc:80] Completed 48000 requests. RPS: 41680.5 +I0309 01:38:12.484380 1084347 client.cc:80] Completed 49000 requests. RPS: 41964.8 +I0309 01:38:12.507327 1084347 client.cc:80] Completed 50000 requests. RPS: 41995.8 +I0309 01:38:12.526601 1084347 client.cc:80] Completed 51000 requests. RPS: 42153.4 +I0309 01:38:12.543781 1084347 client.cc:80] Completed 52000 requests. RPS: 42378.1 +I0309 01:38:12.563729 1084347 client.cc:80] Completed 53000 requests. RPS: 42502.1 +I0309 01:38:12.580247 1084347 client.cc:80] Completed 54000 requests. RPS: 42738.2 +I0309 01:38:12.597458 1084347 client.cc:80] Completed 55000 requests. RPS: 42944.4 +I0309 01:38:12.615114 1084347 client.cc:80] Completed 56000 requests. RPS: 43130.7 +I0309 01:38:12.631972 1084347 client.cc:80] Completed 57000 requests. RPS: 43338.1 +I0309 01:38:12.648757 1084347 client.cc:80] Completed 58000 requests. RPS: 43542.8 +I0309 01:38:12.666518 1084347 client.cc:80] Completed 59000 requests. RPS: 43710.7 +I0309 01:38:12.683978 1084347 client.cc:80] Completed 60000 requests. RPS: 43883.9 +I0309 01:38:12.700365 1084347 client.cc:80] Completed 61000 requests. RPS: 44086.9 +I0309 01:38:12.717972 1084347 client.cc:80] Completed 62000 requests. RPS: 44246.6 +I0309 01:38:12.734537 1084347 client.cc:80] Completed 63000 requests. RPS: 44434.9 +I0309 01:38:12.750835 1084347 client.cc:80] Completed 64000 requests. RPS: 44627.2 +I0309 01:38:12.768283 1084347 client.cc:80] Completed 65000 requests. RPS: 44779.7 +I0309 01:38:12.785473 1084347 client.cc:80] Completed 66000 requests. RPS: 44936.5 +I0309 01:38:12.803571 1084347 client.cc:80] Completed 67000 requests. RPS: 45062.1 +I0309 01:38:12.819944 1084347 client.cc:80] Completed 68000 requests. RPS: 45236.5 +I0309 01:38:12.838765 1084347 client.cc:80] Completed 69000 requests. RPS: 45334.2 +I0309 01:38:12.862160 1084347 client.cc:80] Completed 70000 requests. RPS: 45294.9 +I0309 01:38:12.885057 1084347 client.cc:80] Completed 71000 requests. RPS: 45271.3 +I0309 01:38:12.902658 1084347 client.cc:80] Completed 72000 requests. RPS: 45399.4 +I0309 01:38:12.932614 1084347 client.cc:80] Completed 73000 requests. RPS: 45176.6 +I0309 01:38:12.971535 1084347 client.cc:80] Completed 74000 requests. RPS: 44718.3 +I0309 01:38:13.027554 1084347 client.cc:80] Completed 75000 requests. RPS: 43838.6 +I0309 01:38:13.064090 1084347 client.cc:80] Completed 76000 requests. RPS: 43494.3 +I0309 01:38:13.098461 1084347 client.cc:80] Completed 77000 requests. RPS: 43216.5 +I0309 01:38:13.125763 1084347 client.cc:80] Completed 78000 requests. RPS: 43117 +I0309 01:38:13.146262 1084347 client.cc:80] Completed 79000 requests. RPS: 43180.5 +I0309 01:38:13.171788 1084347 client.cc:80] Completed 80000 requests. RPS: 43125.4 +I0309 01:38:13.197563 1084347 client.cc:80] Completed 81000 requests. RPS: 43066.1 +I0309 01:38:13.221295 1084347 client.cc:80] Completed 82000 requests. RPS: 43054.5 +I0309 01:38:13.242748 1084347 client.cc:80] Completed 83000 requests. RPS: 43094.2 +I0309 01:38:13.263156 1084347 client.cc:80] Completed 84000 requests. RPS: 43156.1 +I0309 01:38:13.286358 1084347 client.cc:80] Completed 85000 requests. RPS: 43155.4 +I0309 01:38:13.314562 1084347 client.cc:80] Completed 86000 requests. RPS: 43046.7 +I0309 01:38:13.339529 1084347 client.cc:80] Completed 87000 requests. RPS: 43009.8 +I0309 01:38:13.359740 1084347 client.cc:80] Completed 88000 requests. RPS: 43073.8 +I0309 01:38:13.387961 1084347 client.cc:80] Completed 89000 requests. RPS: 42969.7 +I0309 01:38:13.411426 1084347 client.cc:80] Completed 90000 requests. RPS: 42965.7 +I0309 01:38:13.438110 1084347 client.cc:80] Completed 91000 requests. RPS: 42896.7 +I0309 01:38:13.462065 1084347 client.cc:80] Completed 92000 requests. RPS: 42883.8 +I0309 01:38:13.481994 1084347 client.cc:80] Completed 93000 requests. RPS: 42950.9 +I0309 01:38:13.508650 1084347 client.cc:80] Completed 94000 requests. RPS: 42884.9 +I0309 01:38:13.530142 1084347 client.cc:80] Completed 95000 requests. RPS: 42920.2 +I0309 01:38:13.558636 1084347 client.cc:80] Completed 96000 requests. RPS: 42820.8 +I0309 01:38:13.578881 1084347 client.cc:80] Completed 97000 requests. RPS: 42879.6 +I0309 01:38:13.601910 1084347 client.cc:80] Completed 98000 requests. RPS: 42885.1 +I0309 01:38:13.626068 1084347 client.cc:80] Completed 99000 requests. RPS: 42869.5 +I0309 01:38:13.649168 1084347 client.cc:80] Completed 100000 requests. RPS: 42873.6 +I0309 01:38:13.674367 1084347 client.cc:80] Completed 101000 requests. RPS: 42839.6 +I0309 01:38:13.701760 1084347 client.cc:80] Completed 102000 requests. RPS: 42766.8 +I0309 01:38:13.720911 1084347 client.cc:80] Completed 103000 requests. RPS: 42842.1 +I0309 01:38:13.741318 1084347 client.cc:80] Completed 104000 requests. RPS: 42894 +I0309 01:38:13.770517 1084347 client.cc:80] Completed 105000 requests. RPS: 42791.1 +I0309 01:38:13.794428 1084347 client.cc:80] Completed 106000 requests. RPS: 42781.7 +I0309 01:38:13.812194 1084347 client.cc:80] Completed 107000 requests. RPS: 42877.8 +I0309 01:38:13.828350 1084347 client.cc:80] Completed 108000 requests. RPS: 43000.2 +I0309 01:38:13.844116 1084347 client.cc:80] Completed 109000 requests. RPS: 43127.6 +I0309 01:38:13.866128 1084347 client.cc:80] Completed 110000 requests. RPS: 43147.5 +I0309 01:38:13.892945 1084347 client.cc:80] Completed 111000 requests. RPS: 43086.5 +I0309 01:38:13.914110 1084347 client.cc:80] Completed 112000 requests. RPS: 43120.4 +I0309 01:38:13.931606 1084347 client.cc:80] Completed 113000 requests. RPS: 43214.3 +I0309 01:38:13.948516 1084347 client.cc:80] Completed 114000 requests. RPS: 43316.6 +I0309 01:38:13.967486 1084347 client.cc:80] Completed 115000 requests. RPS: 43383.9 +I0309 01:38:13.984928 1084347 client.cc:80] Completed 116000 requests. RPS: 43475.1 +I0309 01:38:14.001044 1084347 client.cc:80] Completed 117000 requests. RPS: 43586.6 +I0309 01:38:14.018342 1084347 client.cc:80] Completed 118000 requests. RPS: 43677.7 +I0309 01:38:14.036181 1084347 client.cc:80] Completed 119000 requests. RPS: 43758.9 +I0309 01:38:14.051993 1084347 client.cc:80] Completed 120000 requests. RPS: 43871.5 +I0309 01:38:14.067472 1084347 client.cc:80] Completed 121000 requests. RPS: 43988.2 +I0309 01:38:14.083918 1084347 client.cc:80] Completed 122000 requests. RPS: 44088.1 +I0309 01:38:14.103264 1084347 client.cc:80] Completed 123000 requests. RPS: 44140.9 +I0309 01:38:14.118195 1084347 client.cc:80] Completed 124000 requests. RPS: 44262.6 +I0309 01:38:14.135454 1084347 client.cc:80] Completed 125000 requests. RPS: 44346.4 +I0309 01:38:14.153009 1084347 client.cc:80] Completed 126000 requests. RPS: 44424.5 +I0309 01:38:14.168640 1084347 client.cc:80] Completed 127000 requests. RPS: 44531.6 +I0309 01:38:14.183018 1084347 client.cc:80] Completed 128000 requests. RPS: 44657.1 +I0309 01:38:14.198552 1084347 client.cc:80] Completed 129000 requests. RPS: 44763.4 +I0309 01:38:14.214050 1084347 client.cc:80] Completed 130000 requests. RPS: 44869.1 +I0309 01:38:14.228382 1084347 client.cc:80] Completed 131000 requests. RPS: 44991.7 +I0309 01:38:14.233653 1084347 client.cc:270] Finished benchmark. +I0309 01:38:14.233726 1084347 client.cc:271] Total RPS: 44969 +I0309 01:38:14.239788 1084347 client.cc:280] Latency Stats (ms): +I0309 01:38:14.239865 1084347 client.cc:281] Average: 2.22203 +I0309 01:38:14.239897 1084347 client.cc:282] StdDev: 1.57251 +I0309 01:38:14.239915 1084347 client.cc:283] Min: 0.64824 +I0309 01:38:14.242263 1084347 client.cc:284] Max: 17.2912 +I0309 01:38:14.244520 1084347 client.cc:285] Samples: 131171 + + +=== Benchmark for 128KiB payload (131072 bytes), 1024 requests === + +--- Raw HTTP/2 --- +I0309 01:38:15.261922 1084463 client.cc:232] Starting HTTP/2 benchmark client... +I0309 01:38:15.262270 1084463 client.cc:193] Connected to server. +I0309 01:38:16.058908 1084463 client.cc:80] Completed 1000 requests. RPS: 1255.4 +I0309 01:38:16.125849 1084463 client.cc:270] Finished benchmark. +I0309 01:38:16.125925 1084463 client.cc:271] Total RPS: 1300.52 +I0309 01:38:16.126006 1084463 client.cc:280] Latency Stats (ms): +I0309 01:38:16.126027 1084463 client.cc:281] Average: 74.4612 +I0309 01:38:16.126046 1084463 client.cc:282] StdDev: 23.7478 +I0309 01:38:16.126062 1084463 client.cc:283] Min: 49.5049 +I0309 01:38:16.126099 1084463 client.cc:284] Max: 153.833 +I0309 01:38:16.126136 1084463 client.cc:285] Samples: 1123 + + +=== Benchmark for 1MiB payload (1048576 bytes), 128 requests === + +--- Raw HTTP/2 --- +I0309 01:38:17.134278 1084518 client.cc:232] Starting HTTP/2 benchmark client... +I0309 01:38:17.134553 1084518 client.cc:193] Connected to server. +I0309 01:38:18.988779 1084518 client.cc:270] Finished benchmark. +I0309 01:38:18.988857 1084518 client.cc:271] Total RPS: 122.883 +I0309 01:38:18.988948 1084518 client.cc:280] Latency Stats (ms): +I0309 01:38:18.988965 1084518 client.cc:281] Average: 754.383 +I0309 01:38:18.988985 1084518 client.cc:282] StdDev: 289.523 +I0309 01:38:18.989000 1084518 client.cc:283] Min: 427.25 +I0309 01:38:18.989021 1084518 client.cc:284] Max: 1328.87 +I0309 01:38:18.989042 1084518 client.cc:285] Samples: 227 + + diff --git a/http2/server.cc b/http2/server.cc new file mode 100644 index 0000000..753b8a8 --- /dev/null +++ b/http2/server.cc @@ -0,0 +1,245 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "absl/log/initialize.h" +#include "absl/log/log.h" + +#define MAKE_NV(name, value) \ + { \ + (uint8_t*)(name), (uint8_t*)(value), strlen(name), strlen(value), \ + NGHTTP2_NV_FLAG_NONE} + +#define PORT 8080 + +struct app_context; +struct stream_data; + +struct ClientSession { + struct bufferevent* bev; + nghttp2_session* session; + struct app_context* app_ctx; +}; + +struct stream_data { + int32_t stream_id; + struct evbuffer* req_body; +}; + +struct app_context { + struct event_base* evbase; +}; + +static ssize_t send_callback(nghttp2_session* session, const uint8_t* data, size_t length, int flags, void* user_data) { + struct ClientSession* client = (struct ClientSession*)user_data; + struct bufferevent* bev = client->bev; + struct evbuffer* output = bufferevent_get_output(bev); + evbuffer_add(output, data, length); + return (ssize_t)length; +} + +static ssize_t echo_data_provider_callback(nghttp2_session* session, int32_t stream_id, uint8_t* buf, size_t length, uint32_t* data_flags, nghttp2_data_source* source, void* user_data); + +static int on_frame_recv_callback(nghttp2_session* session, const nghttp2_frame* frame, void* user_data) { + struct ClientSession* client = (struct ClientSession*)user_data; +// LOG(INFO) << "Server received frame type " << static_cast(frame->hd.type); + switch (frame->hd.type) { + case NGHTTP2_HEADERS: + if (frame->headers.cat == NGHTTP2_HCAT_REQUEST) { + struct stream_data* sd = new stream_data; + sd->stream_id = frame->hd.stream_id; + sd->req_body = evbuffer_new(); + nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, sd); + } + break; + case NGHTTP2_DATA: + // LOG(INFO) << "Server logic: DATA frame on stream " << frame->hd.stream_id << ", flags " << static_cast(frame->hd.flags); + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + // LOG(INFO) << "Server logic: END_STREAM seen"; + const nghttp2_nv hdrs[] = { + MAKE_NV(":status", "200")}; + struct stream_data* sd = (struct stream_data*)nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); + if (sd) { + nghttp2_data_provider data_prd; + data_prd.source.ptr = sd; + data_prd.read_callback = echo_data_provider_callback; + int rv = nghttp2_submit_response(session, frame->hd.stream_id, hdrs, 1, &data_prd); + // LOG(INFO) << "nghttp2_submit_response returned " << rv; + nghttp2_session_send(session); // flush to output + } else { + LOG(ERROR) << "Server logic: No stream data found!"; + } + } + break; + } + return 0; +} + +static int on_data_chunk_recv_callback(nghttp2_session* session, uint8_t flags, int32_t stream_id, const uint8_t* data, size_t len, void* user_data) { + struct stream_data* sd = (struct stream_data*)nghttp2_session_get_stream_user_data(session, stream_id); + if (sd) { + evbuffer_add(sd->req_body, data, len); + } + return 0; +} + +static ssize_t echo_data_provider_callback(nghttp2_session* session, int32_t stream_id, uint8_t* buf, size_t length, uint32_t* data_flags, nghttp2_data_source* source, void* user_data) { + struct stream_data* sd = (struct stream_data*)source->ptr; + + size_t avail = evbuffer_get_length(sd->req_body); + size_t send_len = avail < length ? avail : length; + + if (send_len > 0) { + evbuffer_remove(sd->req_body, buf, send_len); + } + + if (evbuffer_get_length(sd->req_body) == 0) { + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + evbuffer_free(sd->req_body); + delete sd; + nghttp2_session_set_stream_user_data(session, stream_id, NULL); + } + + return send_len; +} + +static int on_stream_close_callback(nghttp2_session* session, int32_t stream_id, uint32_t error_code, void* user_data) { + struct stream_data* sd = (struct stream_data*)nghttp2_session_get_stream_user_data(session, stream_id); + if (sd) { + evbuffer_free(sd->req_body); + delete sd; + nghttp2_session_set_stream_user_data(session, stream_id, NULL); + } + return 0; +} + +static int on_header_callback(nghttp2_session* session, const nghttp2_frame* frame, const uint8_t* name, size_t namelen, const uint8_t* value, size_t valuelen, uint8_t flags, void* user_data) { + // We could inspect headers here if needed + return 0; +} + +static int on_begin_headers_callback(nghttp2_session* session, const nghttp2_frame* frame, void* user_data) { + // Only care about request + return 0; +} + +// nghttp2 callbacks +static void setup_nghttp2_callbacks(nghttp2_session_callbacks* callbacks) { + nghttp2_session_callbacks_set_send_callback(callbacks, send_callback); + nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, on_frame_recv_callback); + nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, on_data_chunk_recv_callback); + nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, on_stream_close_callback); + nghttp2_session_callbacks_set_on_header_callback(callbacks, on_header_callback); + nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks, on_begin_headers_callback); +} + +// Read from network +static void readcb(struct bufferevent* bev, void* ptr) { + struct ClientSession* client = (struct ClientSession*)ptr; + struct evbuffer* input = bufferevent_get_input(bev); + + size_t datalen = evbuffer_get_length(input); +// LOG(INFO) << "Server readcb: got " << datalen << " bytes"; + if (datalen == 0) return; + + unsigned char* data = evbuffer_pullup(input, -1); + + ssize_t readlen = nghttp2_session_mem_recv(client->session, data, datalen); + if (readlen < 0) { + LOG(ERROR) << "Server: nghttp2_session_mem_recv error: " << nghttp2_strerror(static_cast(readlen)); + bufferevent_free(bev); + return; + } +// LOG(INFO) << "Server readcb: consumed " << readlen << " bytes"; + evbuffer_drain(input, readlen); + nghttp2_session_send(client->session); +} + +static void eventcb(struct bufferevent* bev, short events, void* ptr) { + struct ClientSession* client = (struct ClientSession*)ptr; + if (events & BEV_EVENT_ERROR) { + LOG(ERROR) << "Error from bufferevent"; + } + if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) { + nghttp2_session_del(client->session); + bufferevent_free(bev); + delete client; + } +} + +static void acceptcb(struct evconnlistener* listener, evutil_socket_t fd, struct sockaddr* a, int slen, void* p) { + struct app_context* app_ctx = (struct app_context*)p; + struct event_base* base = app_ctx->evbase; + struct bufferevent* bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE); + + struct ClientSession* client = new ClientSession; + client->bev = bev; + client->app_ctx = app_ctx; + + nghttp2_session_callbacks* callbacks; + nghttp2_session_callbacks_new(&callbacks); + setup_nghttp2_callbacks(callbacks); + + nghttp2_session_server_new(&client->session, callbacks, client); + nghttp2_session_callbacks_del(callbacks); + + nghttp2_settings_entry iv[2] = { + {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}, + {NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, 128 * 1024 * 1024}}; + nghttp2_submit_settings(client->session, NGHTTP2_FLAG_NONE, iv, 2); + nghttp2_submit_window_update(client->session, NGHTTP2_FLAG_NONE, 0, 128 * 1024 * 1024); + nghttp2_session_send(client->session); + + bufferevent_setcb(bev, readcb, NULL, eventcb, client); + bufferevent_enable(bev, EV_READ | EV_WRITE); +} + +int main(int argc, char** argv) { + absl::InitializeLog(); + + struct event_base* base; + struct evconnlistener* listener; + struct sockaddr_in sin; + struct app_context app_ctx; + + base = event_base_new(); + if (!base) { + LOG(FATAL) << "Could not initialize libevent!"; + return 1; + } + + app_ctx.evbase = base; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = htonl(0); + sin.sin_port = htons(PORT); + + listener = evconnlistener_new_bind(base, acceptcb, (void*)&app_ctx, + LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, -1, + (struct sockaddr*)&sin, sizeof(sin)); + if (!listener) { + LOG(FATAL) << "Could not create a listener!"; + return 1; + } + + LOG(INFO) << "Listening on port " << PORT << "..."; + + event_base_dispatch(base); + + evconnlistener_free(listener); + event_base_free(base); + + return 0; +}