1#include "ReportBuilder.h"
2
3#include <algorithm>
4#include <regex>
5#include <sstream>
6#include <thread>
7
8#include <Common/getNumberOfPhysicalCPUCores.h>
9#include <Common/getFQDNOrHostName.h>
10#include <common/getMemoryAmount.h>
11#include <Common/StringUtils/StringUtils.h>
12
13#include "JSONString.h"
14
15namespace DB
16{
17
18namespace
19{
20std::string getMainMetric(const PerformanceTestInfo & test_info)
21{
22 std::string main_metric;
23 if (test_info.main_metric.empty())
24 if (test_info.exec_type == ExecutionType::Loop)
25 main_metric = "min_time";
26 else
27 main_metric = "rows_per_second";
28 else
29 main_metric = test_info.main_metric;
30 return main_metric;
31}
32
33bool isASCIIString(const std::string & str)
34{
35 return std::all_of(str.begin(), str.end(), isASCII);
36}
37
38String jsonString(const String & str, FormatSettings & settings)
39{
40 WriteBufferFromOwnString buffer;
41 writeJSONString(str, buffer, settings);
42 return std::move(buffer.str());
43}
44}
45
46ReportBuilder::ReportBuilder(const std::string & server_version_)
47 : server_version(server_version_)
48 , hostname(getFQDNOrHostName())
49 , num_cores(getNumberOfPhysicalCPUCores())
50 , num_threads(std::thread::hardware_concurrency())
51 , ram(getMemoryAmount())
52{
53}
54
55std::string ReportBuilder::getCurrentTime() const
56{
57 return DateLUT::instance().timeToString(time(nullptr));
58}
59
60std::string ReportBuilder::buildFullReport(
61 const PerformanceTestInfo & test_info,
62 std::vector<TestStats> & stats,
63 const std::vector<std::size_t> & queries_to_run) const
64{
65 FormatSettings settings;
66
67
68 JSONString json_output;
69
70 json_output.set("hostname", hostname);
71 json_output.set("num_cores", num_cores);
72 json_output.set("num_threads", num_threads);
73 json_output.set("ram", ram);
74 json_output.set("server_version", server_version);
75 json_output.set("time", getCurrentTime());
76 json_output.set("test_name", test_info.test_name);
77 json_output.set("path", test_info.path);
78 json_output.set("main_metric", getMainMetric(test_info));
79
80 if (!test_info.substitutions.empty())
81 {
82 JSONString json_parameters(2); /// here, 2 is the size of \t padding
83
84 for (auto & [parameter, values] : test_info.substitutions)
85 {
86 std::ostringstream array_string;
87 array_string << "[";
88 for (size_t i = 0; i != values.size(); ++i)
89 {
90 array_string << jsonString(values[i], settings);
91 if (i != values.size() - 1)
92 {
93 array_string << ", ";
94 }
95 }
96 array_string << ']';
97
98 json_parameters.set(parameter, array_string.str());
99 }
100
101 json_output.set("parameters", json_parameters.asString());
102 }
103
104 std::vector<JSONString> run_infos;
105 for (size_t query_index = 0; query_index < test_info.queries.size(); ++query_index)
106 {
107 if (!queries_to_run.empty() && std::find(queries_to_run.begin(), queries_to_run.end(), query_index) == queries_to_run.end())
108 continue;
109
110 for (size_t number_of_launch = 0; number_of_launch < test_info.times_to_run; ++number_of_launch)
111 {
112 size_t stat_index = number_of_launch * test_info.queries.size() + query_index;
113 TestStats & statistics = stats[stat_index];
114
115 if (!statistics.ready)
116 continue;
117
118 JSONString runJSON;
119
120 runJSON.set("query", jsonString(test_info.queries[query_index], settings), false);
121 runJSON.set("query_index", query_index);
122 if (!statistics.exception.empty())
123 {
124 if (isASCIIString(statistics.exception))
125 runJSON.set("exception", jsonString(statistics.exception, settings), false);
126 else
127 runJSON.set("exception", "Some exception occured with non ASCII message. This may produce invalid JSON. Try reproduce locally.");
128 }
129
130 if (test_info.exec_type == ExecutionType::Loop)
131 {
132 /// in seconds
133 runJSON.set("min_time", statistics.min_time / double(1000));
134
135 if (statistics.sampler.size() != 0)
136 {
137 JSONString quantiles(4); /// here, 4 is the size of \t padding
138 for (double percent = 10; percent <= 90; percent += 10)
139 {
140 std::string quantile_key = std::to_string(percent / 100.0);
141 while (quantile_key.back() == '0')
142 quantile_key.pop_back();
143
144 quantiles.set(quantile_key,
145 statistics.sampler.quantileInterpolated(percent / 100.0));
146 }
147 quantiles.set("0.95",
148 statistics.sampler.quantileInterpolated(95 / 100.0));
149 quantiles.set("0.99",
150 statistics.sampler.quantileInterpolated(99 / 100.0));
151 quantiles.set("0.999",
152 statistics.sampler.quantileInterpolated(99.9 / 100.0));
153 quantiles.set("0.9999",
154 statistics.sampler.quantileInterpolated(99.99 / 100.0));
155
156 runJSON.set("quantiles", quantiles.asString());
157 }
158
159 runJSON.set("total_time", statistics.total_time);
160
161 if (statistics.total_time != 0)
162 {
163 runJSON.set("queries_per_second", static_cast<double>(statistics.queries) / statistics.total_time);
164 runJSON.set("rows_per_second", static_cast<double>(statistics.total_rows_read) / statistics.total_time);
165 runJSON.set("bytes_per_second", static_cast<double>(statistics.total_bytes_read) / statistics.total_time);
166 }
167 }
168 else
169 {
170 runJSON.set("max_rows_per_second", statistics.max_rows_speed);
171 runJSON.set("max_bytes_per_second", statistics.max_bytes_speed);
172 runJSON.set("avg_rows_per_second", statistics.avg_rows_speed_value);
173 runJSON.set("avg_bytes_per_second", statistics.avg_bytes_speed_value);
174 }
175
176 runJSON.set("memory_usage", statistics.memory_usage);
177
178 run_infos.push_back(runJSON);
179 }
180 }
181
182 json_output.set("runs", run_infos);
183
184 return json_output.asString();
185}
186
187std::string ReportBuilder::buildCompactReport(
188 const PerformanceTestInfo & test_info,
189 std::vector<TestStats> & stats,
190 const std::vector<std::size_t> & queries_to_run) const
191{
192 FormatSettings settings;
193 std::ostringstream output;
194
195 for (size_t query_index = 0; query_index < test_info.queries.size(); ++query_index)
196 {
197 if (!queries_to_run.empty() && std::find(queries_to_run.begin(), queries_to_run.end(), query_index) == queries_to_run.end())
198 continue;
199
200 for (size_t number_of_launch = 0; number_of_launch < test_info.times_to_run; ++number_of_launch)
201 {
202 if (test_info.queries.size() > 1)
203 output << "query " << jsonString(test_info.queries[query_index], settings) << ", ";
204
205 output << "run " << std::to_string(number_of_launch + 1) << ": ";
206
207 std::string main_metric = getMainMetric(test_info);
208
209 output << main_metric << " = ";
210 size_t index = number_of_launch * test_info.queries.size() + query_index;
211 output << stats[index].getStatisticByName(main_metric);
212 output << "\n";
213 }
214 }
215 return output.str();
216}
217}
218