diff --git a/CMakeLists.txt b/CMakeLists.txt index 11ebc48..dbace11 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -136,8 +136,14 @@ set(SOURCES add_executable(weaseldb ${SOURCES}) add_dependencies(weaseldb generate_json_tokens) -target_link_libraries(weaseldb Threads::Threads toml11::toml11 weaseljson - simdutf::simdutf llhttp_static) +target_link_libraries( + weaseldb + Threads::Threads + toml11::toml11 + weaseljson + simdutf::simdutf + llhttp_static + perfetto) enable_testing() @@ -165,7 +171,7 @@ add_executable( test_http_handler tests/test_http_handler.cpp src/http_handler.cpp src/arena_allocator.cpp src/connection.cpp) target_link_libraries(test_http_handler doctest::doctest llhttp_static - Threads::Threads) + Threads::Threads perfetto) target_include_directories(test_http_handler PRIVATE src) target_compile_definitions(test_http_handler PRIVATE DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN) diff --git a/src/http_handler.cpp b/src/http_handler.cpp index db5ecdb..2498c39 100644 --- a/src/http_handler.cpp +++ b/src/http_handler.cpp @@ -1,5 +1,6 @@ #include "http_handler.hpp" #include "arena_allocator.hpp" +#include "perfetto_categories.hpp" #include #include #include @@ -213,6 +214,8 @@ void HttpHandler::handleGetMetrics(Connection &conn, void HttpHandler::handleGetOk(Connection &conn, const HttpConnectionState &state) { + TRACE_EVENT("http", "GET /ok", perfetto::Flow::Global(state.request_id)); + sendResponse(conn, 200, "text/plain", "OK", state.connection_close); } @@ -254,6 +257,8 @@ void HttpHandler::sendResponse(Connection &conn, int status_code, break; } + auto *state = static_cast(conn.user_data); + response += "\r\n"; response += "Content-Type: "; response += content_type; @@ -261,6 +266,9 @@ void HttpHandler::sendResponse(Connection &conn, int status_code, response += "Content-Length: "; response += std::to_string(body.size()); response += "\r\n"; + response += "X-Response-ID: "; + response += std::to_string(state->request_id); + response += "\r\n"; if (close_connection) { response += "Connection: close\r\n"; @@ -322,6 +330,20 @@ int HttpHandler::onHeaderValue(llhttp_t *parser, const char *at, } } + // Check for X-Request-Id header + if (state->current_header_field.size() == 12 && + strncasecmp(state->current_header_field.data(), "x-request-id", 12) == + 0) { + uint64_t id = 0; + for (int i = 0; i < int(length); ++i) { + auto c = at[i]; + if (c >= '0' && c <= '9') { + id = id * 10 + (c - '0'); + } + } + state->request_id = id; + } + return 0; } @@ -348,4 +370,4 @@ int HttpHandler::onMessageComplete(llhttp_t *parser) { auto *state = static_cast(parser->data); state->message_complete = true; return 0; -} \ No newline at end of file +} diff --git a/src/http_handler.hpp b/src/http_handler.hpp index 2e87b5c..e1d5603 100644 --- a/src/http_handler.hpp +++ b/src/http_handler.hpp @@ -42,6 +42,7 @@ struct HttpConnectionState { bool connection_close = false; // Client requested connection close HttpRoute route = HttpRoute::NotFound; std::string_view current_header_field; // Current header being parsed + uint64_t request_id = 0; // X-Request-Id header value explicit HttpConnectionState(ArenaAllocator &arena); }; @@ -95,4 +96,4 @@ private: static void sendErrorResponse(Connection &conn, int status_code, std::string_view message, bool close_connection = false); -}; \ No newline at end of file +}; diff --git a/src/main.cpp b/src/main.cpp index 5fa19c5..4590542 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,11 +2,14 @@ #include "connection.hpp" #include "connection_handler.hpp" #include "http_handler.hpp" +#include "perfetto_categories.hpp" #include "server.hpp" #include #include #include +PERFETTO_TRACK_EVENT_STATIC_STORAGE(); + // TODO this should be scoped to a particular Server, and it's definition should // be in server.cpp or connection.cpp std::atomic activeConnections{0}; @@ -41,6 +44,13 @@ void print_help(const char *program_name) { } int main(int argc, char *argv[]) { +#if ENABLE_PERFETTO + perfetto::TracingInitArgs args; + args.backends |= perfetto::kSystemBackend; + perfetto::Tracing::Initialize(args); + perfetto::TrackEvent::Register(); +#endif + std::string config_file = "config.toml"; // Parse command line arguments diff --git a/tests/test_http_handler.cpp b/tests/test_http_handler.cpp index c46fafd..cc97913 100644 --- a/tests/test_http_handler.cpp +++ b/tests/test_http_handler.cpp @@ -1,8 +1,12 @@ #include "arena_allocator.hpp" #include "http_handler.hpp" +#include "perfetto_categories.hpp" #include #include +// Perfetto static storage for tests +PERFETTO_TRACK_EVENT_STATIC_STORAGE(); + // Global variable needed by Connection std::atomic activeConnections{0}; @@ -34,6 +38,7 @@ TEST_CASE("HttpHandler route parsing") { CHECK(HttpHandler::parseRoute("GET", "/v1/retention") == HttpRoute::GET_retention); CHECK(HttpHandler::parseRoute("GET", "/metrics") == HttpRoute::GET_metrics); + CHECK(HttpHandler::parseRoute("GET", "/ok") == HttpRoute::GET_ok); } SUBCASE("POST routes") { diff --git a/tools/load_tester.cpp b/tools/load_tester.cpp index 2e0aa8e..326c9b5 100644 --- a/tools/load_tester.cpp +++ b/tools/load_tester.cpp @@ -23,22 +23,8 @@ #include -#ifndef __has_feature -#define __has_feature(x) 0 -#endif - -#define ENABLE_PERFETTO 1 - -#if ENABLE_PERFETTO -#include -#else -#define PERFETTO_DEFINE_CATEGORIES(...) -#define PERFETTO_TRACK_EVENT_STATIC_STORAGE(...) -#define TRACE_EVENT(...) -#endif - -PERFETTO_DEFINE_CATEGORIES( - perfetto::Category("network").SetDescription("Network")); +// Use shared perfetto categories +#include "../src/perfetto_categories.hpp" PERFETTO_TRACK_EVENT_STATIC_STORAGE(); @@ -264,7 +250,7 @@ struct Connection { request = request.substr(w, request.size() - w); if (request.empty()) { ++requestsSent; - TRACE_EVENT("network", "send request", + TRACE_EVENT("http", "Send request", perfetto::Flow::Global(currentRequestId)); if (requestsSent == g_config.requests_per_connection) { return true; @@ -307,8 +293,7 @@ private: } int on_message_complete() { - TRACE_EVENT("network", "receive response", - perfetto::Flow::Global(responseId)); + TRACE_EVENT("http", "Receive response", perfetto::Flow::Global(responseId)); responseId = 0; ++responsesReceived; initRequest();