Browse Source

[C++] [userver] Restructure the benchmark a bit and add a separate lower-level configuration (#7819)

* move configs from build context to allow chanding them without triggering rebuild

* add bare implementation, restructure a bit

* + single-query

* add bare implementation for all other types; fix EXPOSE in dockerfiles

* tweak userver: don't set tracing headers

* tweak PG settings to not kill the DB in CI

* cleanup

* add some README

* + EOL

* reduce PG connections count drastically
itrofimow 2 years ago
parent
commit
1da2e9b8d2
33 changed files with 667 additions and 42 deletions
  1. 12 8
      frameworks/C++/userver/README.md
  2. 25 2
      frameworks/C++/userver/benchmark_config.json
  3. 36 0
      frameworks/C++/userver/config.toml
  4. 21 0
      frameworks/C++/userver/userver-bare.dockerfile
  5. 11 7
      frameworks/C++/userver/userver.dockerfile
  6. 1 0
      frameworks/C++/userver/userver_benchmark/CMakeLists.txt
  7. 230 0
      frameworks/C++/userver/userver_benchmark/bare/simple_connection.cpp
  8. 27 0
      frameworks/C++/userver/userver_benchmark/bare/simple_connection.hpp
  9. 13 0
      frameworks/C++/userver/userver_benchmark/bare/simple_response.hpp
  10. 81 0
      frameworks/C++/userver/userver_benchmark/bare/simple_router.cpp
  11. 43 0
      frameworks/C++/userver/userver_benchmark/bare/simple_router.hpp
  12. 29 0
      frameworks/C++/userver/userver_benchmark/bare/simple_server.cpp
  13. 34 0
      frameworks/C++/userver/userver_benchmark/bare/simple_server.hpp
  14. 33 6
      frameworks/C++/userver/userver_benchmark/common/db_helpers.cpp
  15. 2 0
      frameworks/C++/userver/userver_benchmark/common/db_helpers.hpp
  16. 6 2
      frameworks/C++/userver/userver_benchmark/controllers/cached_queries/handler.cpp
  17. 2 0
      frameworks/C++/userver/userver_benchmark/controllers/cached_queries/handler.hpp
  18. 0 0
      frameworks/C++/userver/userver_benchmark/controllers/cached_queries/world_cache_component.cpp
  19. 0 0
      frameworks/C++/userver/userver_benchmark/controllers/cached_queries/world_cache_component.hpp
  20. 4 0
      frameworks/C++/userver/userver_benchmark/controllers/json/handler.cpp
  21. 2 0
      frameworks/C++/userver/userver_benchmark/controllers/json/handler.hpp
  22. 6 2
      frameworks/C++/userver/userver_benchmark/controllers/multiple_queries/handler.cpp
  23. 2 0
      frameworks/C++/userver/userver_benchmark/controllers/multiple_queries/handler.hpp
  24. 4 0
      frameworks/C++/userver/userver_benchmark/controllers/plaintext/handler.cpp
  25. 2 0
      frameworks/C++/userver/userver_benchmark/controllers/plaintext/handler.hpp
  26. 4 0
      frameworks/C++/userver/userver_benchmark/controllers/single_query/handler.cpp
  27. 2 0
      frameworks/C++/userver/userver_benchmark/controllers/single_query/handler.hpp
  28. 7 3
      frameworks/C++/userver/userver_benchmark/controllers/updates/handler.cpp
  29. 2 0
      frameworks/C++/userver/userver_benchmark/controllers/updates/handler.hpp
  30. 7 1
      frameworks/C++/userver/userver_benchmark/userver_techempower.cpp
  31. 5 5
      frameworks/C++/userver/userver_configs/dynamic_config_fallback.json
  32. 0 0
      frameworks/C++/userver/userver_configs/secure_data.json
  33. 14 6
      frameworks/C++/userver/userver_configs/static_config.yaml

+ 12 - 8
frameworks/C++/userver/README.md

@@ -2,21 +2,25 @@
 
 This is the [userver](https://github.com/userver-framework/userver) portion of a [benchmarking test suite](https://github.com/TechEmpower/FrameworkBenchmarks) comparing a variety of web development platforms.
 
+This benchmarks comes in two configurations: **userver** and **userver-bare**, where both configurations use exactly the same handlers code, but **userver-bare** replaces default http implementation of **userver** with custom one.  
+You see, **userver** being feature-rich framework widely used in production comes with a lot of useful functionality built-in (metrics, dynamic configuring, logging/tracing, congestion control etc...) none of which is of any use in benchmarks; although most of that can be disabled via configs, some parts remain, and these parts aren't free.  
+The aim of **userver-bare** is to explore practical limits of lower-level **userver** functionality when performance is an absolute must, while still being idiomatic userver code.
+
 ### Test Type Implementation Source Code
 
-* [PLAINTEXT](userver_benchmark/src/controllers/plaintext/handler.cpp)
-* [JSON](userver_benchmark/src/controllers/json/handler.cpp)
-* [Single Database Query](userver_benchmark/src/controllers/single_query/handler.cpp)
-* [Multiple Database Queries](userver_benchmark/src/controllers/multiple_queries/handler.cpp)
-* [Database Updates](userver_benchmark/src/controllers/updates/handler.cpp)
-* [Cached Queries](userver_benchmark/src/controllers/cached_queries/handler.cpp)
+* [Plaintext](userver_benchmark/controllers/plaintext/handler.cpp)
+* [Json](userver_benchmark/controllers/json/handler.cpp)
+* [Single Database Query](userver_benchmark/controllers/single_query/handler.cpp)
+* [Multiple Database Queries](userver_benchmark/controllers/multiple_queries/handler.cpp)
+* [Database Updates](userver_benchmark/controllers/updates/handler.cpp)
+* [Cached Queries](userver_benchmark/controllers/cached_queries/handler.cpp)
 
 ## Test URLs
-### PLAINTEXT
+### Plaintext
 
 http://localhost:8080/plaintext
 
-### JSON
+### Json
 
 http://localhost:8080/json
 

+ 25 - 2
frameworks/C++/userver/benchmark_config.json

@@ -11,12 +11,12 @@
         "cached_query_url": "/cached-queries?count=",
         "port": 8080,
         "approach": "Realistic",
-        "classification": "Micro",
+        "classification": "Fullstack",
         "database": "postgres",
         "framework": "userver",
         "language": "C++",
         "flavor": "None",
-        "orm": "Raw",
+        "orm": "Micro",
         "platform": "None",
         "webserver": "None",
         "os": "Linux",
@@ -24,6 +24,29 @@
         "display_name": "userver",
         "notes": "",
         "versus": "None"
+      },
+      "bare": {
+        "json_url": "/json",
+        "plaintext_url": "/plaintext",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "update_url": "/updates?queries=",
+        "cached_query_url": "/cached-queries?count=",
+        "port": 8081,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "postgres",
+        "framework": "userver",
+        "language": "C++",
+        "flavor": "None",
+        "orm": "Micro",
+        "platform": "None",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "userver[bare]",
+        "notes": "",
+        "versus": "None"
       }
     }
   ]

+ 36 - 0
frameworks/C++/userver/config.toml

@@ -0,0 +1,36 @@
+[framework]
+name = "userver"
+
+[main]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+urls.db = "/db"
+urls.query = "/queries?queries="
+urls.update = "/updates?queries="
+urls.cached_query = "/cached-queries?count="
+approach = "Realistic"
+classification = "Fullstack"
+database = "Postgres"
+database_os = "Linux"
+os = "Linux"
+orm = "Micro"
+platform = "None"
+webserver = "None"
+versus = "None"
+
+[bare]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+urls.db = "/db"
+urls.query = "/queries?queries="
+urls.update = "/updates?queries="
+urls.cached_query = "/cached-queries?count="
+approach = "Realistic"
+classification = "Micro"
+database = "Postgres"
+database_os = "Linux"
+os = "Linux"
+orm = "Micro"
+platform = "None"
+webserver = "None"
+versus = "None"

+ 21 - 0
frameworks/C++/userver/userver-bare.dockerfile

@@ -0,0 +1,21 @@
+FROM ghcr.io/userver-framework/docker-userver-build-base:v1a AS builder
+WORKDIR /src
+RUN git clone https://github.com/userver-framework/userver.git && \
+    cd userver && git checkout b69a8db23844d3abbb68e40a502eae0ecd2e4b62
+COPY userver_benchmark/ ./
+RUN mkdir build && cd build && \
+    cmake -DUSERVER_IS_THE_ROOT_PROJECT=0 -DUSERVER_OPEN_SOURCE_BUILD=1 -DUSERVER_FEATURE_CRYPTOPP_BLAKE2=0 \
+          -DUSERVER_FEATURE_REDIS=0 -DUSERVER_FEATURE_CLICKHOUSE=0 -DUSERVER_FEATURE_MONGODB=0 -DUSERVER_FEATURE_RABBITMQ=0 -DUSERVER_FEATURE_GRPC=0 \
+          -DUSERVER_FEATURE_UTEST=0 \
+          -DUSERVER_FEATURE_POSTGRESQL=1 \
+          -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-march=native" .. && \
+    make -j $(nproc)
+
+FROM builder AS runner
+WORKDIR /app
+COPY userver_configs/* ./
+COPY --from=builder /src/build/userver_techempower ./
+
+EXPOSE 8081
+CMD ./userver_techempower -c ./static_config.yaml
+

+ 11 - 7
frameworks/C++/userver/userver.dockerfile

@@ -1,8 +1,8 @@
-FROM ghcr.io/userver-framework/docker-userver-build-base:v1a
-WORKDIR src
-COPY userver_benchmark/src/ ./
+FROM ghcr.io/userver-framework/docker-userver-build-base:v1a AS builder
+WORKDIR /src
 RUN git clone https://github.com/userver-framework/userver.git && \
-    cd userver && git checkout ec3b7109054713b42ed9b4742d5f31e1adc43f96
+    cd userver && git checkout b69a8db23844d3abbb68e40a502eae0ecd2e4b62
+COPY userver_benchmark/ ./
 RUN mkdir build && cd build && \
     cmake -DUSERVER_IS_THE_ROOT_PROJECT=0 -DUSERVER_OPEN_SOURCE_BUILD=1 -DUSERVER_FEATURE_CRYPTOPP_BLAKE2=0 \
           -DUSERVER_FEATURE_REDIS=0 -DUSERVER_FEATURE_CLICKHOUSE=0 -DUSERVER_FEATURE_MONGODB=0 -DUSERVER_FEATURE_RABBITMQ=0 -DUSERVER_FEATURE_GRPC=0 \
@@ -11,7 +11,11 @@ RUN mkdir build && cd build && \
           -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS="-march=native" .. && \
     make -j $(nproc)
 
-COPY userver_benchmark/configs/* ./
+FROM builder AS runner
+WORKDIR /app
+COPY userver_configs/* ./
+COPY --from=builder /src/build/userver_techempower ./
+
+EXPOSE 8080
+CMD ./userver_techempower -c ./static_config.yaml
 
-EXPOSE 8090
-CMD ./build/userver_techempower -c /src/static_config.yaml

+ 1 - 0
frameworks/C++/userver/userver_benchmark/src/CMakeLists.txt → frameworks/C++/userver/userver_benchmark/CMakeLists.txt

@@ -6,6 +6,7 @@ set(CMAKE_CXX_STANDARD 17)
 file(GLOB_RECURSE SOURCES
   ${CMAKE_CURRENT_SOURCE_DIR}/controllers/*.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/common/*.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/bare/*.cpp
 )
 
 include(userver/cmake/SetupEnvironment.cmake)

+ 230 - 0
frameworks/C++/userver/userver_benchmark/bare/simple_connection.cpp

@@ -0,0 +1,230 @@
+#include "simple_connection.hpp"
+
+#include <array>
+
+#include <cctz/time_zone.h>
+#include <http_parser.h>
+#include <boost/container/small_vector.hpp>
+
+#include "simple_server.hpp"
+
+#include <userver/engine/async.hpp>
+#include <userver/utils/datetime/wall_coarse_clock.hpp>
+#include <userver/utils/scope_guard.hpp>
+
+namespace userver_techempower::bare {
+
+namespace {
+
+template <std::size_t N>
+class SmallString final {
+ public:
+  SmallString() = default;
+
+  void Append(const char* data, std::size_t length) {
+    const auto old_size = Size();
+    data_.resize(old_size + length);
+    std::memcpy(Data() + old_size, data, length);
+  }
+
+  void Append(std::string_view sw) { Append(sw.data(), sw.size()); }
+
+  [[nodiscard]] std::string_view AsSw() const { return {Data(), Size()}; }
+
+  [[nodiscard]] char* Data() { return data_.data(); }
+  [[nodiscard]] const char* Data() const { return data_.data(); }
+
+  [[nodiscard]] std::size_t Size() const { return data_.size(); }
+
+  void Clear() { data_.resize(0); }
+
+ private:
+  boost::container::small_vector<char, N> data_;
+};
+
+struct HttpParser final {
+  http_parser parser{};
+  http_parser_settings parser_settings{};
+
+  std::function<void(std::string_view)> on_request_cb{};
+
+  SmallString<50> url;
+
+  explicit HttpParser(std::function<void(std::string_view)> on_request_cb)
+      : on_request_cb{std::move(on_request_cb)} {
+    http_parser_init(&parser, HTTP_REQUEST);
+    parser.data = this;
+
+    http_parser_settings_init(&parser_settings);
+    parser_settings.on_url = HttpOnUrl;
+    parser_settings.on_message_begin = HttpOnMessageBegin;
+    parser_settings.on_message_complete = HttpOnMessageComplete;
+  }
+
+  void Execute(const char* data, std::size_t length) {
+    http_parser_execute(&parser, &parser_settings, data, length);
+  }
+
+  static int HttpOnUrl(http_parser* parser, const char* data,
+                       std::size_t length) {
+    auto* self = static_cast<HttpParser*>(parser->data);
+    self->url.Append(data, length);
+    return 0;
+  }
+
+  static int HttpOnMessageBegin(http_parser* parser) {
+    auto* self = static_cast<HttpParser*>(parser->data);
+    self->url.Clear();
+    return 0;
+  }
+
+  static int HttpOnMessageComplete(http_parser* parser) {
+    auto* self = static_cast<HttpParser*>(parser->data);
+    self->on_request_cb(self->url.AsSw());
+    return 0;
+  }
+};
+
+class ResponseBuffers final {
+ public:
+  using HeadersString = SmallString<200>;
+
+  HeadersString& Next(userver::engine::io::Socket& socket, std::string&& body) {
+    if (Size() == kMaxResponses) {
+      Send(socket);
+    }
+
+    auto& response = responses_.emplace_back();
+    response.body = std::move(body);
+    return response.headers;
+  }
+
+  void Send(userver::engine::io::Socket& socket) {
+    if (Size() == 0) {
+      return;
+    }
+
+    boost::container::small_vector<userver::engine::io::IoData,
+                                   kMaxResponses * 2>
+        iovec(Size() * 2);
+
+    std::size_t index = 0;
+    std::size_t total_size = 0;
+    for (const auto& response : responses_) {
+      iovec[index++] = {response.headers.Data(), response.headers.Size()};
+      iovec[index++] = {response.body.data(), response.body.size()};
+      total_size += response.headers.Size() + response.body.size();
+    }
+
+    if (socket.SendAll(iovec.data(), iovec.size(), {}) != total_size) {
+      throw std::runtime_error{"Socket closed by remote"};
+    }
+
+    responses_.clear();
+  }
+
+ private:
+  static constexpr std::size_t kMaxResponses = 16;
+
+  [[nodiscard]] std::size_t Size() const { return responses_.size(); }
+
+  struct Response final {
+    HeadersString headers;
+    std::string body;
+  };
+
+  boost::container::small_vector<Response, kMaxResponses> responses_;
+};
+
+constexpr std::string_view kCommonHeaders{"HTTP/1.1 200 OK\r\nServer: us\r\n"};
+constexpr std::string_view kHeadersEnd{"\r\n\r\n"};
+
+std::string MakeHttpDate(std::chrono::system_clock::time_point date) {
+  static const std::string kFormatString = "%a, %d %b %Y %H:%M:%S %Z";
+  static const auto tz = cctz::utc_time_zone();
+
+  return cctz::format(kFormatString, date, tz);
+}
+
+std::string_view GetCachedDate() {
+  constexpr size_t kMaxDateHeaderLength = 128;
+
+  static thread_local std::chrono::seconds::rep last_second = 0;
+  static thread_local char last_time_string[kMaxDateHeaderLength]{};
+  static thread_local std::string_view result_view{};
+
+  const auto now = userver::utils::datetime::WallCoarseClock::now();
+  const auto now_seconds =
+      std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch())
+          .count();
+  if (now_seconds != last_second) {
+    last_second = now_seconds;
+
+    const auto time_str = MakeHttpDate(now);
+
+    std::memcpy(last_time_string, time_str.c_str(), time_str.size());
+    result_view = std::string_view{last_time_string, time_str.size()};
+  }
+
+  return result_view;
+}
+
+}  // namespace
+
+SimpleConnection::SimpleConnection(SimpleServer& server,
+                                   userver::engine::io::Socket&& socket)
+    : server_{server},
+      socket_{std::move(socket)},
+      processing_task_{userver::engine::AsyncNoSpan([this] { Process(); })} {}
+
+SimpleConnection::~SimpleConnection() { processing_task_.SyncCancel(); }
+
+void SimpleConnection::Process() {
+  constexpr std::size_t kBufferSize = 4096;
+  std::array<char, kBufferSize> buffer{};
+
+  userver::utils::ScopeGuard close_guard{[this] { socket_.Close(); }};
+
+  ResponseBuffers buffers{};
+  const auto handle_request = [this, &buffers](std::string_view url) {
+    auto response = server_.HandleRequest(url);
+    const auto content_length_str = std::to_string(response.body.size());
+    auto& headers = buffers.Next(socket_, std::move(response.body));
+
+    headers.Append(kCommonHeaders);
+    headers.Append("Content-Type: ");
+    headers.Append(response.content_type);
+
+    headers.Append("\r\nContent-Length: ");
+    headers.Append(content_length_str);
+
+    headers.Append("\r\nDate: ");
+    headers.Append(GetCachedDate());
+
+    headers.Append(kHeadersEnd);
+  };
+  HttpParser parser{handle_request};
+
+  std::size_t last_bytes_read = 0;
+  while (true) {
+    bool is_readable = true;
+    if (last_bytes_read < kBufferSize) {
+      is_readable = socket_.WaitReadable({});
+    }
+
+    last_bytes_read =
+        is_readable ? socket_.RecvSome(buffer.data(), kBufferSize, {}) : 0;
+    if (last_bytes_read == 0) {
+      break;
+    }
+
+    parser.Execute(buffer.data(), last_bytes_read);
+    if (parser.parser.http_errno != 0) {
+      break;
+    }
+
+    buffers.Send(socket_);
+  }
+}
+
+}  // namespace userver_techempower::bare

+ 27 - 0
frameworks/C++/userver/userver_benchmark/bare/simple_connection.hpp

@@ -0,0 +1,27 @@
+#pragma once
+
+#include <userver/engine/io/socket.hpp>
+#include <userver/engine/task/task_with_result.hpp>
+
+namespace userver_techempower::bare {
+
+class SimpleServer;
+
+class SimpleConnection final {
+ public:
+  explicit SimpleConnection(SimpleServer& server,
+                            userver::engine::io::Socket&& socket);
+  ~SimpleConnection();
+
+ private:
+  void Process();
+
+  const SimpleServer& server_;
+
+  userver::engine::io::Socket socket_;
+
+  userver::engine::TaskWithResult<void> processing_task_;
+};
+
+}  // namespace userver_techempower::bare
+

+ 13 - 0
frameworks/C++/userver/userver_benchmark/bare/simple_response.hpp

@@ -0,0 +1,13 @@
+#pragma once
+
+#include <string>
+
+namespace userver_techempower::bare {
+
+struct SimpleResponse final {
+  std::string body;
+  std::string content_type;
+};
+
+}  // namespace userver_techempower::bare
+

+ 81 - 0
frameworks/C++/userver/userver_benchmark/bare/simple_router.cpp

@@ -0,0 +1,81 @@
+#include "simple_router.hpp"
+
+#include <userver/components/component_context.hpp>
+
+#include "../controllers/cached_queries/handler.hpp"
+#include "../controllers/json/handler.hpp"
+#include "../controllers/multiple_queries/handler.hpp"
+#include "../controllers/plaintext/handler.hpp"
+#include "../controllers/single_query/handler.hpp"
+#include "../controllers/updates/handler.hpp"
+
+namespace userver_techempower::bare {
+
+namespace {
+
+constexpr std::string_view kPlainTextUrlPrefix{"/plaintext"};
+constexpr std::string_view kJsontUrlPrefix{"/json"};
+constexpr std::string_view kSingleQueryUrlPrefix{"/db"};
+constexpr std::string_view kMultipleQueriesUrlPrefix{"/queries"};
+constexpr std::string_view kUpdatesUrlPrefix{"/updates"};
+constexpr std::string_view kCachedQueriesUrlPrefix{"/cached-queries"};
+
+// NOLINTNEXTLINE
+const std::string kContentTypePlain{"text/plain"};
+// NOLINTNEXTLINE
+const std::string kContentTypeJson{"application/json"};
+
+bool StartsWith(std::string_view source, std::string_view pattern) {
+  return source.substr(0, pattern.length()) == pattern;
+}
+
+}  // namespace
+
+SimpleRouter::SimpleRouter(const userver::components::ComponentConfig& config,
+                           const userver::components::ComponentContext& context)
+    : userver::components::LoggableComponentBase{config, context},
+      single_query_{context.FindComponent<single_query::Handler>()},
+      multiple_queries_{context.FindComponent<multiple_queries::Handler>()},
+      updates_{context.FindComponent<updates::Handler>()},
+      cached_queries_{context.FindComponent<cached_queries::Handler>()} {}
+
+SimpleRouter::~SimpleRouter() = default;
+
+SimpleResponse SimpleRouter::RouteRequest(std::string_view url) const {
+  if (StartsWith(url, kPlainTextUrlPrefix)) {
+    return {plaintext::Handler::GetResponse(), kContentTypePlain};
+  }
+
+  if (StartsWith(url, kJsontUrlPrefix)) {
+    return {ToString(json::Handler::GetResponse()), kContentTypeJson};
+  }
+
+  if (StartsWith(url, kSingleQueryUrlPrefix)) {
+    return {ToString(single_query_.GetResponse()), kContentTypeJson};
+  }
+
+  if (StartsWith(url, kMultipleQueriesUrlPrefix)) {
+    const auto queries = db_helpers::ParseParamFromQuery(
+        url.substr(kMultipleQueriesUrlPrefix.size()), "queries");
+
+    return {ToString(multiple_queries_.GetResponse(queries)), kContentTypeJson};
+  }
+
+  if (StartsWith(url, kUpdatesUrlPrefix)) {
+    const auto queries = db_helpers::ParseParamFromQuery(
+        url.substr(kMultipleQueriesUrlPrefix.size()), "queries");
+
+    return {ToString(updates_.GetResponse(queries)), kContentTypeJson};
+  }
+
+  if (StartsWith(url, kCachedQueriesUrlPrefix)) {
+    const auto count = db_helpers::ParseParamFromQuery(
+        url.substr(kCachedQueriesUrlPrefix.size()), "count");
+
+    return {ToString(cached_queries_.GetResponse(count)), "application/json"};
+  }
+
+  throw std::runtime_error{"No handler found for url"};
+}
+
+}  // namespace userver_techempower::bare

+ 43 - 0
frameworks/C++/userver/userver_benchmark/bare/simple_router.hpp

@@ -0,0 +1,43 @@
+#pragma once
+
+#include <userver/components/loggable_component_base.hpp>
+
+#include "simple_response.hpp"
+
+namespace userver_techempower {
+
+namespace single_query {
+class Handler;
+}
+namespace multiple_queries {
+class Handler;
+}
+namespace updates {
+class Handler;
+}
+namespace cached_queries {
+class Handler;
+}
+
+namespace bare {
+
+class SimpleRouter final : public userver::components::LoggableComponentBase {
+ public:
+  static constexpr std::string_view kName{"simple-router"};
+
+  SimpleRouter(const userver::components::ComponentConfig& config,
+               const userver::components::ComponentContext& context);
+  ~SimpleRouter() final;
+
+  [[nodiscard]] SimpleResponse RouteRequest(std::string_view url) const;
+
+ private:
+  const single_query::Handler& single_query_;
+  const multiple_queries::Handler& multiple_queries_;
+  const updates::Handler& updates_;
+  const cached_queries::Handler& cached_queries_;
+};
+
+}  // namespace bare
+}  // namespace userver_techempower
+

+ 29 - 0
frameworks/C++/userver/userver_benchmark/bare/simple_server.cpp

@@ -0,0 +1,29 @@
+#include "simple_server.hpp"
+
+#include <userver/components/component_context.hpp>
+#include <userver/engine/io/socket.hpp>
+
+#include "simple_connection.hpp"
+#include "simple_router.hpp"
+
+namespace userver_techempower::bare {
+
+SimpleServer::SimpleServer(const userver::components::ComponentConfig& config,
+                           const userver::components::ComponentContext& context)
+    : userver::components::TcpAcceptorBase(config, context),
+      router_{context.FindComponent<SimpleRouter>()} {}
+
+SimpleServer::~SimpleServer() = default;
+
+void SimpleServer::ProcessSocket(userver::engine::io::Socket&& socket) {
+  const auto fd = socket.Fd();
+  connections_[fd] =
+      std::make_unique<SimpleConnection>(*this, std::move(socket));
+}
+
+SimpleResponse SimpleServer::HandleRequest(std::string_view url) const {
+  return router_.RouteRequest(url);
+}
+
+}  // namespace userver_techempower::bare
+

+ 34 - 0
frameworks/C++/userver/userver_benchmark/bare/simple_server.hpp

@@ -0,0 +1,34 @@
+#pragma once
+
+#include <array>
+#include <memory>
+
+#include <userver/components/tcp_acceptor_base.hpp>
+
+#include "simple_response.hpp"
+
+namespace userver_techempower::bare {
+
+class SimpleConnection;
+class SimpleRouter;
+
+class SimpleServer final : public userver::components::TcpAcceptorBase {
+ public:
+  static constexpr std::string_view kName{"simple-server"};
+
+  SimpleServer(const userver::components::ComponentConfig& config,
+               const userver::components::ComponentContext& context);
+  ~SimpleServer() final;
+
+ private:
+  void ProcessSocket(userver::engine::io::Socket&& socket) final;
+
+  friend class SimpleConnection;
+  [[nodiscard]] SimpleResponse HandleRequest(std::string_view url) const;
+
+  const SimpleRouter& router_;
+
+  static constexpr std::size_t kMaxFd = 65536;
+  std::array<std::unique_ptr<SimpleConnection>, kMaxFd> connections_;
+};
+}  // namespace userver_techempower::bare

+ 33 - 6
frameworks/C++/userver/userver_benchmark/src/common/db_helpers.cpp → frameworks/C++/userver/userver_benchmark/common/db_helpers.cpp

@@ -1,10 +1,31 @@
 #include "db_helpers.hpp"
 
+#include <charconv>
+#include <cctype>
+
 #include <userver/formats/json/inline.hpp>
 #include <userver/utils/rand.hpp>
 
 namespace userver_techempower::db_helpers {
 
+namespace {
+
+int ParseFromQueryVal(std::string_view query_val) {
+  if (query_val.empty()) {
+    return 1;
+  }
+
+  int parse_result{};
+  const auto [ptr, err] = std::from_chars(query_val.data(), query_val.data() + query_val.size(), parse_result);
+  if (err != std::errc{} || ptr != query_val.data() + query_val.size()) {
+    return 1;
+  }
+
+  return std::min(500, std::max(1, parse_result));
+}
+
+}
+
 int GenerateRandomId() {
   return userver::utils::RandRange(1, kMaxWorldRows + 1);
 }
@@ -23,16 +44,22 @@ userver::formats::json::Value Serialize(
 int ParseParamFromQuery(const userver::server::http::HttpRequest& request,
                         const std::string& name) {
   const auto& arg_str = request.GetArg(name);
-  if (arg_str.empty()) {
+  return ParseFromQueryVal(arg_str);
+}
+
+int ParseParamFromQuery(std::string_view url, std::string_view name) {
+  auto pos = url.find(name);
+  if (pos == std::string_view::npos) {
     return 1;
   }
+  pos += name.size() + 1; // +1 for '='
 
-  try {
-    int value = std::stoi(arg_str);
-    return std::min(500, std::max(1, value));
-  } catch (const std::invalid_argument&) {
-    return 1;
+  std::size_t len = 0;
+  while (pos + len < url.size() && std::isdigit(url[pos + len])) {
+    ++len;
   }
+
+  return ParseFromQueryVal(url.substr(pos, len));
 }
 
 }  // namespace userver_techempower::db_helpers

+ 2 - 0
frameworks/C++/userver/userver_benchmark/src/common/db_helpers.hpp → frameworks/C++/userver/userver_benchmark/common/db_helpers.hpp

@@ -29,4 +29,6 @@ userver::formats::json::Value Serialize(
 int ParseParamFromQuery(const userver::server::http::HttpRequest& request,
                         const std::string& name);
 
+int ParseParamFromQuery(std::string_view url, std::string_view name);
+
 }  // namespace userver_techempower::db_helpers

+ 6 - 2
frameworks/C++/userver/userver_benchmark/src/controllers/cached_queries/handler.cpp → frameworks/C++/userver/userver_benchmark/controllers/cached_queries/handler.cpp

@@ -16,11 +16,15 @@ userver::formats::json::Value Handler::HandleRequestJsonThrow(
     const userver::server::http::HttpRequest& request,
     const userver::formats::json::Value&,
     userver::server::request::RequestContext&) const {
-  const auto queries_count =
+  const auto queries =
       db_helpers::ParseParamFromQuery(request, query_arg_name_);
 
+  return GetResponse(queries);
+}
+
+userver::formats::json::Value Handler::GetResponse(int queries) const {
   boost::container::small_vector<db_helpers::WorldTableRow, 500> result(
-      queries_count);
+      queries);
 
   const auto cache_ptr = cache_.Get();
   const auto& cache = *cache_ptr;

+ 2 - 0
frameworks/C++/userver/userver_benchmark/src/controllers/cached_queries/handler.hpp → frameworks/C++/userver/userver_benchmark/controllers/cached_queries/handler.hpp

@@ -18,6 +18,8 @@ class Handler final : public userver::server::handlers::HttpHandlerJsonBase {
       const userver::formats::json::Value&,
       userver::server::request::RequestContext&) const final;
 
+  userver::formats::json::Value GetResponse(int queries) const;
+
  private:
   const WorldCacheComponent& cache_;
 

+ 0 - 0
frameworks/C++/userver/userver_benchmark/src/controllers/cached_queries/world_cache_component.cpp → frameworks/C++/userver/userver_benchmark/controllers/cached_queries/world_cache_component.cpp


+ 0 - 0
frameworks/C++/userver/userver_benchmark/src/controllers/cached_queries/world_cache_component.hpp → frameworks/C++/userver/userver_benchmark/controllers/cached_queries/world_cache_component.hpp


+ 4 - 0
frameworks/C++/userver/userver_benchmark/src/controllers/json/handler.cpp → frameworks/C++/userver/userver_benchmark/controllers/json/handler.cpp

@@ -6,6 +6,10 @@ userver::formats::json::Value Handler::HandleRequestJsonThrow(
     const userver::server::http::HttpRequest&,
     const userver::formats::json::Value&,
     userver::server::request::RequestContext&) const {
+  return GetResponse();
+}
+
+userver::formats::json::Value Handler::GetResponse() {
   return userver::formats::json::MakeObject("message", "Hello, World!");
 }
 

+ 2 - 0
frameworks/C++/userver/userver_benchmark/src/controllers/json/handler.hpp → frameworks/C++/userver/userver_benchmark/controllers/json/handler.hpp

@@ -14,6 +14,8 @@ class Handler final : public userver::server::handlers::HttpHandlerJsonBase {
       const userver::server::http::HttpRequest&,
       const userver::formats::json::Value&,
       userver::server::request::RequestContext&) const final;
+
+  static userver::formats::json::Value GetResponse();
 };
 
 }  // namespace userver_techempower::json

+ 6 - 2
frameworks/C++/userver/userver_benchmark/src/controllers/multiple_queries/handler.cpp → frameworks/C++/userver/userver_benchmark/controllers/multiple_queries/handler.cpp

@@ -23,10 +23,14 @@ userver::formats::json::Value Handler::HandleRequestJsonThrow(
     const userver::server::http::HttpRequest& request,
     const userver::formats::json::Value&,
     userver::server::request::RequestContext&) const {
-  const auto queries_count =
+  const auto queries =
       db_helpers::ParseParamFromQuery(request, query_arg_name_);
 
-  boost::container::small_vector<int, 500> random_ids(queries_count);
+  return GetResponse(queries);
+}
+
+userver::formats::json::Value Handler::GetResponse(int queries) const {
+  boost::container::small_vector<int, 500> random_ids(queries);
   std::generate(random_ids.begin(), random_ids.end(),
                 db_helpers::GenerateRandomId);
 

+ 2 - 0
frameworks/C++/userver/userver_benchmark/src/controllers/multiple_queries/handler.hpp → frameworks/C++/userver/userver_benchmark/controllers/multiple_queries/handler.hpp

@@ -17,6 +17,8 @@ class Handler final : public userver::server::handlers::HttpHandlerJsonBase {
       const userver::formats::json::Value&,
       userver::server::request::RequestContext&) const final;
 
+  userver::formats::json::Value GetResponse(int queries) const;
+
  private:
   const userver::storages::postgres::ClusterPtr pg_;
 

+ 4 - 0
frameworks/C++/userver/userver_benchmark/src/controllers/plaintext/handler.cpp → frameworks/C++/userver/userver_benchmark/controllers/plaintext/handler.cpp

@@ -6,6 +6,10 @@ std::string Handler::HandleRequestThrow(
     const userver::server::http::HttpRequest& request,
     userver::server::request::RequestContext&) const {
   request.GetHttpResponse().SetContentType("text/plain");
+  return GetResponse();
+}
+
+std::string Handler::GetResponse() {
   return "Hello, World!";
 }
 

+ 2 - 0
frameworks/C++/userver/userver_benchmark/src/controllers/plaintext/handler.hpp → frameworks/C++/userver/userver_benchmark/controllers/plaintext/handler.hpp

@@ -13,6 +13,8 @@ class Handler final : public userver::server::handlers::HttpHandlerBase {
   std::string HandleRequestThrow(
       const userver::server::http::HttpRequest& request,
       userver::server::request::RequestContext&) const final;
+
+  static std::string GetResponse();
 };
 
 }  // namespace userver_techempower::plaintext

+ 4 - 0
frameworks/C++/userver/userver_benchmark/src/controllers/single_query/handler.cpp → frameworks/C++/userver/userver_benchmark/controllers/single_query/handler.cpp

@@ -19,6 +19,10 @@ userver::formats::json::Value Handler::HandleRequestJsonThrow(
     const userver::server::http::HttpRequest&,
     const userver::formats::json::Value&,
     userver::server::request::RequestContext&) const {
+  return GetResponse();
+}
+
+userver::formats::json::Value Handler::GetResponse() const {
   const auto row =
       pg_->Execute(db_helpers::kClusterHostType, db_helpers::kSelectRowQuery,
                    db_helpers::GenerateRandomId())

+ 2 - 0
frameworks/C++/userver/userver_benchmark/src/controllers/single_query/handler.hpp → frameworks/C++/userver/userver_benchmark/controllers/single_query/handler.hpp

@@ -18,6 +18,8 @@ class Handler final : public userver::server::handlers::HttpHandlerJsonBase {
       const userver::formats::json::Value&,
       userver::server::request::RequestContext&) const final;
 
+  userver::formats::json::Value GetResponse() const;
+
  private:
   const userver::storages::postgres::ClusterPtr pg_;
 };

+ 7 - 3
frameworks/C++/userver/userver_benchmark/src/controllers/updates/handler.cpp → frameworks/C++/userver/userver_benchmark/controllers/updates/handler.cpp

@@ -36,10 +36,14 @@ userver::formats::json::Value Handler::HandleRequestJsonThrow(
     const userver::server::http::HttpRequest& request,
     const userver::formats::json::Value&,
     userver::server::request::RequestContext&) const {
-  const auto queries_count =
+  const auto queries =
       db_helpers::ParseParamFromQuery(request, query_arg_name_);
 
-  std::vector<int> random_ids(queries_count);
+  return GetResponse(queries);
+}
+
+userver::formats::json::Value Handler::GetResponse(int queries) const {
+  std::vector<int> random_ids(queries);
   std::generate(random_ids.begin(), random_ids.end(),
                 db_helpers::GenerateRandomId);
   std::sort(random_ids.begin(), random_ids.end());
@@ -52,7 +56,7 @@ userver::formats::json::Value Handler::HandleRequestJsonThrow(
                              userver::storages::postgres::kRowTag));
   }
 
-  std::vector<int> random_numbers(queries_count);
+  std::vector<int> random_numbers(queries);
   std::generate(random_numbers.begin(), random_numbers.end(),
                 db_helpers::GenerateRandomValue);
 

+ 2 - 0
frameworks/C++/userver/userver_benchmark/src/controllers/updates/handler.hpp → frameworks/C++/userver/userver_benchmark/controllers/updates/handler.hpp

@@ -18,6 +18,8 @@ class Handler final : public userver::server::handlers::HttpHandlerJsonBase {
       const userver::formats::json::Value&,
       userver::server::request::RequestContext&) const final;
 
+  userver::formats::json::Value GetResponse(int queries) const;
+
  private:
   const userver::storages::postgres::ClusterPtr pg_;
 

+ 7 - 1
frameworks/C++/userver/userver_benchmark/src/userver_techempower.cpp → frameworks/C++/userver/userver_benchmark/userver_techempower.cpp

@@ -12,6 +12,9 @@
 #include "controllers/single_query/handler.hpp"
 #include "controllers/updates/handler.hpp"
 
+#include "bare/simple_router.hpp"
+#include "bare/simple_server.hpp"
+
 namespace userver_techempower {
 
 int Main(int argc, char* argv[]) {
@@ -26,7 +29,10 @@ int Main(int argc, char* argv[]) {
           .Append<multiple_queries::Handler>()
           .Append<updates::Handler>()
           .Append<cached_queries::WorldCacheComponent>()
-          .Append<cached_queries::Handler>();
+          .Append<cached_queries::Handler>()
+          // bare
+          .Append<bare::SimpleRouter>()
+          .Append<bare::SimpleServer>();
 
   return userver::utils::DaemonMain(argc, argv, component_list);
 }

+ 5 - 5
frameworks/C++/userver/userver_benchmark/configs/dynamic_config_fallback.json → frameworks/C++/userver/userver_configs/dynamic_config_fallback.json

@@ -38,15 +38,15 @@
   "POSTGRES_CONNECTION_PIPELINE_ENABLED": false,
   "POSTGRES_CONNECTION_POOL_SETTINGS": {
     "hello_world": {
-      "max_pool_size": 512,
-      "max_queue_size": 512,
-      "min_pool_size": 256
+      "min_pool_size": 28,
+      "max_pool_size": 28,
+      "max_queue_size": 512
     }
   },
   "POSTGRES_CONNECTION_SETTINGS": {},
   "POSTGRES_DEFAULT_COMMAND_CONTROL": {
-    "network_timeout_ms": 1750,
-    "statement_timeout_ms": 1500
+    "network_timeout_ms": 7000,
+    "statement_timeout_ms": 7000
   },
   "POSTGRES_HANDLERS_COMMAND_CONTROL": {},
   "POSTGRES_QUERIES_COMMAND_CONTROL": {}

+ 0 - 0
frameworks/C++/userver/userver_benchmark/configs/secure_data.json → frameworks/C++/userver/userver_configs/secure_data.json


+ 14 - 6
frameworks/C++/userver/userver_benchmark/configs/static_config.yaml → frameworks/C++/userver/userver_configs/static_config.yaml

@@ -1,5 +1,7 @@
 # yaml
 components_manager:
+    event_thread_pool:
+        threads: 3
     coro_pool:
         initial_size: 10000             # Preallocate 10000 coroutines at startup.
         max_size: 65536                 # Do not keep more than 65536 preallocated coroutines.
@@ -9,7 +11,7 @@ components_manager:
 
         main-task-processor:            # Make a task processor for CPU-bound couroutine tasks.
             thread_name: main-worker    # OS will show the threads of this task processor with 'main-worker' prefix.
-            worker_threads: 26
+            worker_threads: 25
             guess-cpu-limit: true
 
         fs-task-processor:              # Make a separate task processor for filesystem bound tasks.
@@ -23,7 +25,13 @@ components_manager:
             listener:                   # configuring the main listening socket...
                 port: 8080              # ...to listen on this port and...
                 task_processor: main-task-processor    # ...process incoming requests on this task processor.
+                handler-defaults:
+                    set_tracing_headers: false
             server-name: us
+        simple-router:
+        simple-server:
+            port: 8081
+            task_processor: main-task-processor
         logging:
             fs-task-processor: fs-task-processor
             loggers:
@@ -38,12 +46,12 @@ components_manager:
         dynamic-config:                      # Dynamic config storage options, do nothing
             fs-cache-path: ''
         dynamic-config-fallbacks:            # Load options from file and push them into the dynamic config storage.
-            fallback-path: /src/dynamic_config_fallback.json
+            fallback-path: /app/dynamic_config_fallback.json
 
         testsuite-support:
 
         secdist: # Component that stores configuration of hosts and passwords
-            config: /src/secure_data.json  # Values are supposed to be stored in this file
+            config: /app/secure_data.json  # Values are supposed to be stored in this file
             missing-ok: false                             # ... but if the file is missing it is still ok
 
         plaintext-handler:
@@ -59,9 +67,9 @@ components_manager:
         hello-world-db:
             dbalias: hello_world
             blocking_task_processor: fs-task-processor
-            min_pool_size: 20
-            max_pool_size: 520
-            max_queue_size: 500
+            min_pool_size: 28
+            max_pool_size: 28
+            max_queue_size: 512
 
         single-query-handler:
             path: /db