Quellcode durchsuchen

[C++] [userver] Implement Fortunes benchmark and bump userver commit (#8049)

* implement fortunes

* a couple of typos

* bump userver commit

* remove unneeded if
itrofimow vor 2 Jahren
Ursprung
Commit
71975bb845

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

@@ -9,6 +9,7 @@
         "query_url": "/queries?queries=",
         "update_url": "/updates?queries=",
         "cached_query_url": "/cached-queries?count=",
+	"fortune_url": "/fortunes",
         "port": 8080,
         "approach": "Realistic",
         "classification": "Fullstack",
@@ -32,6 +33,7 @@
         "query_url": "/queries?queries=",
         "update_url": "/updates?queries=",
         "cached_query_url": "/cached-queries?count=",
+	"fortune_url": "/fortunes",
         "port": 8081,
         "approach": "Realistic",
         "classification": "Micro",

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

@@ -8,6 +8,7 @@ urls.db = "/db"
 urls.query = "/queries?queries="
 urls.update = "/updates?queries="
 urls.cached_query = "/cached-queries?count="
+urls.fortune = "/fortunes"
 approach = "Realistic"
 classification = "Fullstack"
 database = "Postgres"
@@ -25,6 +26,7 @@ urls.db = "/db"
 urls.query = "/queries?queries="
 urls.update = "/updates?queries="
 urls.cached_query = "/cached-queries?count="
+urls.fortune = "/fortunes"
 approach = "Realistic"
 classification = "Micro"
 database = "Postgres"

+ 2 - 2
frameworks/C++/userver/userver-bare.dockerfile

@@ -1,10 +1,10 @@
 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 151bc1e3df01807da9cd27f9677b80f4951b1f25
+    cd userver && git checkout 5e33f7fe98604080b52208badef0d728c8d4aea0
 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 \
+    cmake -DUSERVER_IS_THE_ROOT_PROJECT=0 -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 \

+ 2 - 2
frameworks/C++/userver/userver.dockerfile

@@ -1,10 +1,10 @@
 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 151bc1e3df01807da9cd27f9677b80f4951b1f25
+    cd userver && git checkout 5e33f7fe98604080b52208badef0d728c8d4aea0
 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 \
+    cmake -DUSERVER_IS_THE_ROOT_PROJECT=0 -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 \

+ 3 - 0
frameworks/C++/userver/userver_benchmark/.clang-format

@@ -0,0 +1,3 @@
+BasedOnStyle: google
+DerivePointerAlignment: false
+IncludeBlocks: Preserve

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

@@ -24,4 +24,3 @@ class SimpleConnection final {
 };
 
 }  // namespace userver_techempower::bare
-

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

@@ -10,4 +10,3 @@ struct SimpleResponse final {
 };
 
 }  // namespace userver_techempower::bare
-

+ 13 - 4
frameworks/C++/userver/userver_benchmark/bare/simple_router.cpp

@@ -3,6 +3,7 @@
 #include <userver/components/component_context.hpp>
 
 #include "../controllers/cached_queries/handler.hpp"
+#include "../controllers/fortunes/handler.hpp"
 #include "../controllers/json/handler.hpp"
 #include "../controllers/multiple_queries/handler.hpp"
 #include "../controllers/plaintext/handler.hpp"
@@ -14,16 +15,19 @@ namespace userver_techempower::bare {
 namespace {
 
 constexpr std::string_view kPlainTextUrlPrefix{"/plaintext"};
-constexpr std::string_view kJsontUrlPrefix{"/json"};
+constexpr std::string_view kJsonUrlPrefix{"/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"};
+constexpr std::string_view kFortunesUrlPrefix{"/fortunes"};
 
 // NOLINTNEXTLINE
 const std::string kContentTypePlain{"text/plain"};
 // NOLINTNEXTLINE
 const std::string kContentTypeJson{"application/json"};
+// NOLINTNEXTLINE
+const std::string kContentTypeTextHtml{"text/html; charset=utf-8"};
 
 bool StartsWith(std::string_view source, std::string_view pattern) {
   return source.substr(0, pattern.length()) == pattern;
@@ -37,7 +41,8 @@ SimpleRouter::SimpleRouter(const userver::components::ComponentConfig& config,
       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>()} {}
+      cached_queries_{context.FindComponent<cached_queries::Handler>()},
+      fortunes_{context.FindComponent<fortunes::Handler>()} {}
 
 SimpleRouter::~SimpleRouter() = default;
 
@@ -46,7 +51,7 @@ SimpleResponse SimpleRouter::RouteRequest(std::string_view url) const {
     return {plaintext::Handler::GetResponse(), kContentTypePlain};
   }
 
-  if (StartsWith(url, kJsontUrlPrefix)) {
+  if (StartsWith(url, kJsonUrlPrefix)) {
     return {ToString(json::Handler::GetResponse()), kContentTypeJson};
   }
 
@@ -72,7 +77,11 @@ SimpleResponse SimpleRouter::RouteRequest(std::string_view url) const {
     const auto count = db_helpers::ParseParamFromQuery(
         url.substr(kCachedQueriesUrlPrefix.size()), "count");
 
-    return {ToString(cached_queries_.GetResponse(count)), "application/json"};
+    return {ToString(cached_queries_.GetResponse(count)), kContentTypeJson};
+  }
+
+  if (StartsWith(url, kFortunesUrlPrefix)) {
+    return {fortunes_.GetResponse(), kContentTypeTextHtml};
   }
 
   throw std::runtime_error{"No handler found for url"};

+ 4 - 1
frameworks/C++/userver/userver_benchmark/bare/simple_router.hpp

@@ -18,6 +18,9 @@ class Handler;
 namespace cached_queries {
 class Handler;
 }
+namespace fortunes {
+class Handler;
+}
 
 namespace bare {
 
@@ -36,8 +39,8 @@ class SimpleRouter final : public userver::components::LoggableComponentBase {
   const multiple_queries::Handler& multiple_queries_;
   const updates::Handler& updates_;
   const cached_queries::Handler& cached_queries_;
+  const fortunes::Handler& fortunes_;
 };
 
 }  // namespace bare
 }  // namespace userver_techempower
-

+ 158 - 0
frameworks/C++/userver/userver_benchmark/controllers/fortunes/handler.cpp

@@ -0,0 +1,158 @@
+#include "handler.hpp"
+
+#include <vector>
+
+#include "../../common/db_helpers.hpp"
+
+#include <userver/components/component_context.hpp>
+#include <userver/storages/postgres/postgres.hpp>
+
+namespace userver_techempower::fortunes {
+
+namespace {
+
+struct Fortune final {
+  int id;
+  std::string message;
+};
+
+constexpr std::string_view kResultingHtmlHeader{
+    "<!DOCTYPE "
+    "html><html><head><title>Fortunes</title></head><body><table><tr><th>id</"
+    "th><th>message</th></tr>"};
+constexpr std::string_view kResultingHtmlFooter{"</table></body></html>"};
+
+constexpr std::string_view kNewRowStart{"<tr><td>"};
+constexpr std::string_view kColumnsSeparator{"</td><td>"};
+constexpr std::string_view kNewRowEnd{"</td></tr>"};
+
+constexpr std::string_view kEscapedQuote{"&quot;"};
+constexpr std::string_view kEscapedAmpersand{"&amp;"};
+constexpr std::string_view kEscapedLessThanSign{"&lt;"};
+constexpr std::string_view kEscapedMoreThanSign{"&gt;"};
+
+void AppendFortune(std::string& result, const Fortune& fortune) {
+  {
+    auto old_size = result.size();
+    const auto fortune_id = std::to_string(fortune.id);
+
+    const auto first_step_size =
+        kNewRowStart.size() + fortune_id.size() + kColumnsSeparator.size();
+
+    result.resize(old_size + first_step_size);
+    char* append_position = result.data() + old_size;
+
+    // this is just faster than std::string::append if we know the resulting
+    // size upfront, because there are a lot of not inlined calls otherwise
+    const auto append = [&append_position](std::string_view what) {
+      std::memcpy(append_position, what.data(), what.size());
+      append_position += what.size();
+    };
+    append(kNewRowStart);
+    append(fortune_id);
+    append(kColumnsSeparator);
+  }
+
+  {
+    std::string_view message{fortune.message};
+
+    const auto do_append = [&result](std::string_view unescaped,
+                                     std::string_view escaped) {
+      const auto old_size = result.size();
+      const auto added_size = unescaped.size() + escaped.size();
+
+      result.resize(result.size() + added_size);
+      char* append_position = result.data() + old_size;
+      if (!unescaped.empty()) {
+        std::memcpy(append_position, unescaped.data(), unescaped.size());
+        append_position += unescaped.size();
+      }
+      std::memcpy(append_position, escaped.data(), escaped.size());
+    };
+
+    std::size_t unescaped_len = 0;
+    const auto append = [&unescaped_len, &message,
+                         &do_append](std::string_view escaped) {
+      do_append(message.substr(0, unescaped_len), escaped);
+      message = message.substr(std::exchange(unescaped_len, 0) + 1);
+    };
+
+    while (unescaped_len < message.size()) {
+      const auto c = message[unescaped_len];
+      switch (c) {
+        case '"': {
+          append(kEscapedQuote);
+          break;
+        }
+        case '&': {
+          append(kEscapedAmpersand);
+          break;
+        }
+        case '<': {
+          append(kEscapedLessThanSign);
+          break;
+        }
+        case '>': {
+          append(kEscapedMoreThanSign);
+          break;
+        }
+        default:
+          ++unescaped_len;
+      }
+    }
+    result.append(message);
+  }
+
+  { result.append(kNewRowEnd); }
+}
+
+std::string FormatFortunes(const std::vector<Fortune>& fortunes) {
+  std::string result{};
+  // Wild guess, seems reasonable. Could be the exact value needed, but that
+  // looks kinda cheating.
+  result.reserve(2048);
+
+  result.append(kResultingHtmlHeader);
+  for (const auto& fortune : fortunes) {
+    AppendFortune(result, fortune);
+  }
+  result.append(kResultingHtmlFooter);
+
+  return result;
+}
+
+}  // namespace
+
+Handler::Handler(const userver::components::ComponentConfig& config,
+                 const userver::components::ComponentContext& context)
+    : userver::server::handlers::HttpHandlerBase{config, context},
+      pg_{context
+              .FindComponent<userver::components::Postgres>(
+                  db_helpers::kDbComponentName)
+              .GetCluster()},
+      select_all_fortunes_query_{"SELECT id, message FROM Fortune"} {}
+
+std::string Handler::HandleRequestThrow(
+    const userver::server::http::HttpRequest& request,
+    userver::server::request::RequestContext&) const {
+  request.GetHttpResponse().SetContentType("text/html; charset=utf-8");
+  return GetResponse();
+}
+
+std::string Handler::GetResponse() const {
+  auto fortunes =
+      pg_->Execute(db_helpers::kClusterHostType, select_all_fortunes_query_)
+          .AsContainer<std::vector<Fortune>>(
+              userver::storages::postgres::kRowTag);
+
+  fortunes.push_back({0, "Additional fortune added at request time."});
+
+  std::sort(fortunes.begin(), fortunes.end(),
+            [](const auto& lhs, const auto& rhs) {
+              return lhs.message < rhs.message;
+            });
+
+  return FormatFortunes(fortunes);
+}
+
+}  // namespace userver_techempower::fortunes

+ 28 - 0
frameworks/C++/userver/userver_benchmark/controllers/fortunes/handler.hpp

@@ -0,0 +1,28 @@
+#pragma once
+
+#include <userver/server/handlers/http_handler_base.hpp>
+
+#include <userver/storages/postgres/postgres_fwd.hpp>
+#include <userver/storages/postgres/query.hpp>
+
+namespace userver_techempower::fortunes {
+
+class Handler final : public userver::server::handlers::HttpHandlerBase {
+ public:
+  static constexpr std::string_view kName = "fortunes-handler";
+
+  Handler(const userver::components::ComponentConfig& config,
+          const userver::components::ComponentContext& context);
+
+  std::string HandleRequestThrow(
+      const userver::server::http::HttpRequest& request,
+      userver::server::request::RequestContext&) const final;
+
+  std::string GetResponse() const;
+
+ private:
+  const userver::storages::postgres::ClusterPtr pg_;
+  const userver::storages::postgres::Query select_all_fortunes_query_;
+};
+
+}  // namespace userver_techempower::fortunes

+ 2 - 0
frameworks/C++/userver/userver_benchmark/userver_techempower.cpp

@@ -9,6 +9,7 @@
 #include <userver/storages/secdist/provider_component.hpp>
 
 #include "controllers/cached_queries/handler.hpp"
+#include "controllers/fortunes/handler.hpp"
 #include "controllers/json/handler.hpp"
 #include "controllers/multiple_queries/handler.hpp"
 #include "controllers/plaintext/handler.hpp"
@@ -35,6 +36,7 @@ int Main(int argc, char* argv[]) {
           .Append<updates::Handler>()
           .Append<cached_queries::WorldCacheComponent>()
           .Append<cached_queries::Handler>()
+          .Append<fortunes::Handler>()
           // bare
           .Append<bare::SimpleRouter>()
           .Append<bare::SimpleServer>();

+ 6 - 1
frameworks/C++/userver/userver_configs/static_config.yaml

@@ -56,7 +56,7 @@ components_manager:
         secdist: {} # Component that stores configuration of hosts and passwords
         default-secdist-provider:
             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
+            missing-ok: false
 
         plaintext-handler:
             path: /plaintext
@@ -101,3 +101,8 @@ components_manager:
             method: GET
             task_processor: main-task-processor
 
+        fortunes-handler:
+            path: /fortunes
+            method: GET
+            task_processor: main-task-processor
+