Skip to content

Commit 71975bb

Browse files
authored
[C++] [userver] Implement Fortunes benchmark and bump userver commit (#8049)
* implement fortunes * a couple of typos * bump userver commit * remove unneeded if
1 parent 6080b20 commit 71975bb

File tree

13 files changed

+222
-12
lines changed

13 files changed

+222
-12
lines changed

frameworks/C++/userver/benchmark_config.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"query_url": "/queries?queries=",
1010
"update_url": "/updates?queries=",
1111
"cached_query_url": "/cached-queries?count=",
12+
"fortune_url": "/fortunes",
1213
"port": 8080,
1314
"approach": "Realistic",
1415
"classification": "Fullstack",
@@ -32,6 +33,7 @@
3233
"query_url": "/queries?queries=",
3334
"update_url": "/updates?queries=",
3435
"cached_query_url": "/cached-queries?count=",
36+
"fortune_url": "/fortunes",
3537
"port": 8081,
3638
"approach": "Realistic",
3739
"classification": "Micro",

frameworks/C++/userver/config.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ urls.db = "/db"
88
urls.query = "/queries?queries="
99
urls.update = "/updates?queries="
1010
urls.cached_query = "/cached-queries?count="
11+
urls.fortune = "/fortunes"
1112
approach = "Realistic"
1213
classification = "Fullstack"
1314
database = "Postgres"
@@ -25,6 +26,7 @@ urls.db = "/db"
2526
urls.query = "/queries?queries="
2627
urls.update = "/updates?queries="
2728
urls.cached_query = "/cached-queries?count="
29+
urls.fortune = "/fortunes"
2830
approach = "Realistic"
2931
classification = "Micro"
3032
database = "Postgres"

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
FROM ghcr.io/userver-framework/docker-userver-build-base:v1a AS builder
22
WORKDIR /src
33
RUN git clone https://github.com/userver-framework/userver.git && \
4-
cd userver && git checkout 151bc1e3df01807da9cd27f9677b80f4951b1f25
4+
cd userver && git checkout 5e33f7fe98604080b52208badef0d728c8d4aea0
55
COPY userver_benchmark/ ./
66
RUN mkdir build && cd build && \
7-
cmake -DUSERVER_IS_THE_ROOT_PROJECT=0 -DUSERVER_OPEN_SOURCE_BUILD=1 -DUSERVER_FEATURE_CRYPTOPP_BLAKE2=0 \
7+
cmake -DUSERVER_IS_THE_ROOT_PROJECT=0 -DUSERVER_FEATURE_CRYPTOPP_BLAKE2=0 \
88
-DUSERVER_FEATURE_REDIS=0 -DUSERVER_FEATURE_CLICKHOUSE=0 -DUSERVER_FEATURE_MONGODB=0 -DUSERVER_FEATURE_RABBITMQ=0 -DUSERVER_FEATURE_GRPC=0 \
99
-DUSERVER_FEATURE_UTEST=0 \
1010
-DUSERVER_FEATURE_POSTGRESQL=1 \

frameworks/C++/userver/userver.dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
FROM ghcr.io/userver-framework/docker-userver-build-base:v1a AS builder
22
WORKDIR /src
33
RUN git clone https://github.com/userver-framework/userver.git && \
4-
cd userver && git checkout 151bc1e3df01807da9cd27f9677b80f4951b1f25
4+
cd userver && git checkout 5e33f7fe98604080b52208badef0d728c8d4aea0
55
COPY userver_benchmark/ ./
66
RUN mkdir build && cd build && \
7-
cmake -DUSERVER_IS_THE_ROOT_PROJECT=0 -DUSERVER_OPEN_SOURCE_BUILD=1 -DUSERVER_FEATURE_CRYPTOPP_BLAKE2=0 \
7+
cmake -DUSERVER_IS_THE_ROOT_PROJECT=0 -DUSERVER_FEATURE_CRYPTOPP_BLAKE2=0 \
88
-DUSERVER_FEATURE_REDIS=0 -DUSERVER_FEATURE_CLICKHOUSE=0 -DUSERVER_FEATURE_MONGODB=0 -DUSERVER_FEATURE_RABBITMQ=0 -DUSERVER_FEATURE_GRPC=0 \
99
-DUSERVER_FEATURE_UTEST=0 \
1010
-DUSERVER_FEATURE_POSTGRESQL=1 \
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
BasedOnStyle: google
2+
DerivePointerAlignment: false
3+
IncludeBlocks: Preserve

frameworks/C++/userver/userver_benchmark/bare/simple_connection.hpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,3 @@ class SimpleConnection final {
2424
};
2525

2626
} // namespace userver_techempower::bare
27-

frameworks/C++/userver/userver_benchmark/bare/simple_response.hpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,3 @@ struct SimpleResponse final {
1010
};
1111

1212
} // namespace userver_techempower::bare
13-

frameworks/C++/userver/userver_benchmark/bare/simple_router.cpp

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <userver/components/component_context.hpp>
44

55
#include "../controllers/cached_queries/handler.hpp"
6+
#include "../controllers/fortunes/handler.hpp"
67
#include "../controllers/json/handler.hpp"
78
#include "../controllers/multiple_queries/handler.hpp"
89
#include "../controllers/plaintext/handler.hpp"
@@ -14,16 +15,19 @@ namespace userver_techempower::bare {
1415
namespace {
1516

1617
constexpr std::string_view kPlainTextUrlPrefix{"/plaintext"};
17-
constexpr std::string_view kJsontUrlPrefix{"/json"};
18+
constexpr std::string_view kJsonUrlPrefix{"/json"};
1819
constexpr std::string_view kSingleQueryUrlPrefix{"/db"};
1920
constexpr std::string_view kMultipleQueriesUrlPrefix{"/queries"};
2021
constexpr std::string_view kUpdatesUrlPrefix{"/updates"};
2122
constexpr std::string_view kCachedQueriesUrlPrefix{"/cached-queries"};
23+
constexpr std::string_view kFortunesUrlPrefix{"/fortunes"};
2224

2325
// NOLINTNEXTLINE
2426
const std::string kContentTypePlain{"text/plain"};
2527
// NOLINTNEXTLINE
2628
const std::string kContentTypeJson{"application/json"};
29+
// NOLINTNEXTLINE
30+
const std::string kContentTypeTextHtml{"text/html; charset=utf-8"};
2731

2832
bool StartsWith(std::string_view source, std::string_view pattern) {
2933
return source.substr(0, pattern.length()) == pattern;
@@ -37,7 +41,8 @@ SimpleRouter::SimpleRouter(const userver::components::ComponentConfig& config,
3741
single_query_{context.FindComponent<single_query::Handler>()},
3842
multiple_queries_{context.FindComponent<multiple_queries::Handler>()},
3943
updates_{context.FindComponent<updates::Handler>()},
40-
cached_queries_{context.FindComponent<cached_queries::Handler>()} {}
44+
cached_queries_{context.FindComponent<cached_queries::Handler>()},
45+
fortunes_{context.FindComponent<fortunes::Handler>()} {}
4146

4247
SimpleRouter::~SimpleRouter() = default;
4348

@@ -46,7 +51,7 @@ SimpleResponse SimpleRouter::RouteRequest(std::string_view url) const {
4651
return {plaintext::Handler::GetResponse(), kContentTypePlain};
4752
}
4853

49-
if (StartsWith(url, kJsontUrlPrefix)) {
54+
if (StartsWith(url, kJsonUrlPrefix)) {
5055
return {ToString(json::Handler::GetResponse()), kContentTypeJson};
5156
}
5257

@@ -72,7 +77,11 @@ SimpleResponse SimpleRouter::RouteRequest(std::string_view url) const {
7277
const auto count = db_helpers::ParseParamFromQuery(
7378
url.substr(kCachedQueriesUrlPrefix.size()), "count");
7479

75-
return {ToString(cached_queries_.GetResponse(count)), "application/json"};
80+
return {ToString(cached_queries_.GetResponse(count)), kContentTypeJson};
81+
}
82+
83+
if (StartsWith(url, kFortunesUrlPrefix)) {
84+
return {fortunes_.GetResponse(), kContentTypeTextHtml};
7685
}
7786

7887
throw std::runtime_error{"No handler found for url"};

frameworks/C++/userver/userver_benchmark/bare/simple_router.hpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ class Handler;
1818
namespace cached_queries {
1919
class Handler;
2020
}
21+
namespace fortunes {
22+
class Handler;
23+
}
2124

2225
namespace bare {
2326

@@ -36,8 +39,8 @@ class SimpleRouter final : public userver::components::LoggableComponentBase {
3639
const multiple_queries::Handler& multiple_queries_;
3740
const updates::Handler& updates_;
3841
const cached_queries::Handler& cached_queries_;
42+
const fortunes::Handler& fortunes_;
3943
};
4044

4145
} // namespace bare
4246
} // namespace userver_techempower
43-
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
#include "handler.hpp"
2+
3+
#include <vector>
4+
5+
#include "../../common/db_helpers.hpp"
6+
7+
#include <userver/components/component_context.hpp>
8+
#include <userver/storages/postgres/postgres.hpp>
9+
10+
namespace userver_techempower::fortunes {
11+
12+
namespace {
13+
14+
struct Fortune final {
15+
int id;
16+
std::string message;
17+
};
18+
19+
constexpr std::string_view kResultingHtmlHeader{
20+
"<!DOCTYPE "
21+
"html><html><head><title>Fortunes</title></head><body><table><tr><th>id</"
22+
"th><th>message</th></tr>"};
23+
constexpr std::string_view kResultingHtmlFooter{"</table></body></html>"};
24+
25+
constexpr std::string_view kNewRowStart{"<tr><td>"};
26+
constexpr std::string_view kColumnsSeparator{"</td><td>"};
27+
constexpr std::string_view kNewRowEnd{"</td></tr>"};
28+
29+
constexpr std::string_view kEscapedQuote{"&quot;"};
30+
constexpr std::string_view kEscapedAmpersand{"&amp;"};
31+
constexpr std::string_view kEscapedLessThanSign{"&lt;"};
32+
constexpr std::string_view kEscapedMoreThanSign{"&gt;"};
33+
34+
void AppendFortune(std::string& result, const Fortune& fortune) {
35+
{
36+
auto old_size = result.size();
37+
const auto fortune_id = std::to_string(fortune.id);
38+
39+
const auto first_step_size =
40+
kNewRowStart.size() + fortune_id.size() + kColumnsSeparator.size();
41+
42+
result.resize(old_size + first_step_size);
43+
char* append_position = result.data() + old_size;
44+
45+
// this is just faster than std::string::append if we know the resulting
46+
// size upfront, because there are a lot of not inlined calls otherwise
47+
const auto append = [&append_position](std::string_view what) {
48+
std::memcpy(append_position, what.data(), what.size());
49+
append_position += what.size();
50+
};
51+
append(kNewRowStart);
52+
append(fortune_id);
53+
append(kColumnsSeparator);
54+
}
55+
56+
{
57+
std::string_view message{fortune.message};
58+
59+
const auto do_append = [&result](std::string_view unescaped,
60+
std::string_view escaped) {
61+
const auto old_size = result.size();
62+
const auto added_size = unescaped.size() + escaped.size();
63+
64+
result.resize(result.size() + added_size);
65+
char* append_position = result.data() + old_size;
66+
if (!unescaped.empty()) {
67+
std::memcpy(append_position, unescaped.data(), unescaped.size());
68+
append_position += unescaped.size();
69+
}
70+
std::memcpy(append_position, escaped.data(), escaped.size());
71+
};
72+
73+
std::size_t unescaped_len = 0;
74+
const auto append = [&unescaped_len, &message,
75+
&do_append](std::string_view escaped) {
76+
do_append(message.substr(0, unescaped_len), escaped);
77+
message = message.substr(std::exchange(unescaped_len, 0) + 1);
78+
};
79+
80+
while (unescaped_len < message.size()) {
81+
const auto c = message[unescaped_len];
82+
switch (c) {
83+
case '"': {
84+
append(kEscapedQuote);
85+
break;
86+
}
87+
case '&': {
88+
append(kEscapedAmpersand);
89+
break;
90+
}
91+
case '<': {
92+
append(kEscapedLessThanSign);
93+
break;
94+
}
95+
case '>': {
96+
append(kEscapedMoreThanSign);
97+
break;
98+
}
99+
default:
100+
++unescaped_len;
101+
}
102+
}
103+
result.append(message);
104+
}
105+
106+
{ result.append(kNewRowEnd); }
107+
}
108+
109+
std::string FormatFortunes(const std::vector<Fortune>& fortunes) {
110+
std::string result{};
111+
// Wild guess, seems reasonable. Could be the exact value needed, but that
112+
// looks kinda cheating.
113+
result.reserve(2048);
114+
115+
result.append(kResultingHtmlHeader);
116+
for (const auto& fortune : fortunes) {
117+
AppendFortune(result, fortune);
118+
}
119+
result.append(kResultingHtmlFooter);
120+
121+
return result;
122+
}
123+
124+
} // namespace
125+
126+
Handler::Handler(const userver::components::ComponentConfig& config,
127+
const userver::components::ComponentContext& context)
128+
: userver::server::handlers::HttpHandlerBase{config, context},
129+
pg_{context
130+
.FindComponent<userver::components::Postgres>(
131+
db_helpers::kDbComponentName)
132+
.GetCluster()},
133+
select_all_fortunes_query_{"SELECT id, message FROM Fortune"} {}
134+
135+
std::string Handler::HandleRequestThrow(
136+
const userver::server::http::HttpRequest& request,
137+
userver::server::request::RequestContext&) const {
138+
request.GetHttpResponse().SetContentType("text/html; charset=utf-8");
139+
return GetResponse();
140+
}
141+
142+
std::string Handler::GetResponse() const {
143+
auto fortunes =
144+
pg_->Execute(db_helpers::kClusterHostType, select_all_fortunes_query_)
145+
.AsContainer<std::vector<Fortune>>(
146+
userver::storages::postgres::kRowTag);
147+
148+
fortunes.push_back({0, "Additional fortune added at request time."});
149+
150+
std::sort(fortunes.begin(), fortunes.end(),
151+
[](const auto& lhs, const auto& rhs) {
152+
return lhs.message < rhs.message;
153+
});
154+
155+
return FormatFortunes(fortunes);
156+
}
157+
158+
} // namespace userver_techempower::fortunes

0 commit comments

Comments
 (0)