diff --git a/frameworks/C++/userver/benchmark_config.json b/frameworks/C++/userver/benchmark_config.json index 0b8fe8731ab..aba5e7d5933 100755 --- a/frameworks/C++/userver/benchmark_config.json +++ b/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", diff --git a/frameworks/C++/userver/config.toml b/frameworks/C++/userver/config.toml index 3e81ea3c3f9..316860f74a1 100644 --- a/frameworks/C++/userver/config.toml +++ b/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" diff --git a/frameworks/C++/userver/userver-bare.dockerfile b/frameworks/C++/userver/userver-bare.dockerfile index f5476ef9003..ca8e3703489 100644 --- a/frameworks/C++/userver/userver-bare.dockerfile +++ b/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 \ diff --git a/frameworks/C++/userver/userver.dockerfile b/frameworks/C++/userver/userver.dockerfile index a9cfdda3106..bb328284562 100644 --- a/frameworks/C++/userver/userver.dockerfile +++ b/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 \ diff --git a/frameworks/C++/userver/userver_benchmark/.clang-format b/frameworks/C++/userver/userver_benchmark/.clang-format new file mode 100644 index 00000000000..276eaf0a609 --- /dev/null +++ b/frameworks/C++/userver/userver_benchmark/.clang-format @@ -0,0 +1,3 @@ +BasedOnStyle: google +DerivePointerAlignment: false +IncludeBlocks: Preserve diff --git a/frameworks/C++/userver/userver_benchmark/bare/simple_connection.hpp b/frameworks/C++/userver/userver_benchmark/bare/simple_connection.hpp index 068c55cce89..9688398aa3c 100644 --- a/frameworks/C++/userver/userver_benchmark/bare/simple_connection.hpp +++ b/frameworks/C++/userver/userver_benchmark/bare/simple_connection.hpp @@ -24,4 +24,3 @@ class SimpleConnection final { }; } // namespace userver_techempower::bare - diff --git a/frameworks/C++/userver/userver_benchmark/bare/simple_response.hpp b/frameworks/C++/userver/userver_benchmark/bare/simple_response.hpp index 8ae9fd0db75..8b8c8eea79d 100644 --- a/frameworks/C++/userver/userver_benchmark/bare/simple_response.hpp +++ b/frameworks/C++/userver/userver_benchmark/bare/simple_response.hpp @@ -10,4 +10,3 @@ struct SimpleResponse final { }; } // namespace userver_techempower::bare - diff --git a/frameworks/C++/userver/userver_benchmark/bare/simple_router.cpp b/frameworks/C++/userver/userver_benchmark/bare/simple_router.cpp index 446a6df1b33..61e634a95c3 100644 --- a/frameworks/C++/userver/userver_benchmark/bare/simple_router.cpp +++ b/frameworks/C++/userver/userver_benchmark/bare/simple_router.cpp @@ -3,6 +3,7 @@ #include #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()}, multiple_queries_{context.FindComponent()}, updates_{context.FindComponent()}, - cached_queries_{context.FindComponent()} {} + cached_queries_{context.FindComponent()}, + fortunes_{context.FindComponent()} {} 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"}; diff --git a/frameworks/C++/userver/userver_benchmark/bare/simple_router.hpp b/frameworks/C++/userver/userver_benchmark/bare/simple_router.hpp index 66d7302d53f..d8aef97a673 100644 --- a/frameworks/C++/userver/userver_benchmark/bare/simple_router.hpp +++ b/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 - diff --git a/frameworks/C++/userver/userver_benchmark/controllers/fortunes/handler.cpp b/frameworks/C++/userver/userver_benchmark/controllers/fortunes/handler.cpp new file mode 100644 index 00000000000..f3b1a6251df --- /dev/null +++ b/frameworks/C++/userver/userver_benchmark/controllers/fortunes/handler.cpp @@ -0,0 +1,158 @@ +#include "handler.hpp" + +#include + +#include "../../common/db_helpers.hpp" + +#include +#include + +namespace userver_techempower::fortunes { + +namespace { + +struct Fortune final { + int id; + std::string message; +}; + +constexpr std::string_view kResultingHtmlHeader{ + "Fortunes"}; +constexpr std::string_view kResultingHtmlFooter{"
idmessage
"}; + +constexpr std::string_view kNewRowStart{""}; +constexpr std::string_view kColumnsSeparator{""}; +constexpr std::string_view kNewRowEnd{""}; + +constexpr std::string_view kEscapedQuote{"""}; +constexpr std::string_view kEscapedAmpersand{"&"}; +constexpr std::string_view kEscapedLessThanSign{"<"}; +constexpr std::string_view kEscapedMoreThanSign{">"}; + +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& 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( + 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>( + 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 diff --git a/frameworks/C++/userver/userver_benchmark/controllers/fortunes/handler.hpp b/frameworks/C++/userver/userver_benchmark/controllers/fortunes/handler.hpp new file mode 100644 index 00000000000..017ac46cab0 --- /dev/null +++ b/frameworks/C++/userver/userver_benchmark/controllers/fortunes/handler.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include +#include + +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 diff --git a/frameworks/C++/userver/userver_benchmark/userver_techempower.cpp b/frameworks/C++/userver/userver_benchmark/userver_techempower.cpp index 34b3d90d295..098778c0965 100644 --- a/frameworks/C++/userver/userver_benchmark/userver_techempower.cpp +++ b/frameworks/C++/userver/userver_benchmark/userver_techempower.cpp @@ -9,6 +9,7 @@ #include #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() .Append() .Append() + .Append() // bare .Append() .Append(); diff --git a/frameworks/C++/userver/userver_configs/static_config.yaml b/frameworks/C++/userver/userver_configs/static_config.yaml index d08fbbe2e48..1ed8aec8c26 100644 --- a/frameworks/C++/userver/userver_configs/static_config.yaml +++ b/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 +