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 | |
15 | namespace DB |
16 | { |
17 | |
18 | namespace |
19 | { |
20 | std::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 | |
33 | bool isASCIIString(const std::string & str) |
34 | { |
35 | return std::all_of(str.begin(), str.end(), isASCII); |
36 | } |
37 | |
38 | String jsonString(const String & str, FormatSettings & settings) |
39 | { |
40 | WriteBufferFromOwnString buffer; |
41 | writeJSONString(str, buffer, settings); |
42 | return std::move(buffer.str()); |
43 | } |
44 | } |
45 | |
46 | ReportBuilder::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 | |
55 | std::string ReportBuilder::getCurrentTime() const |
56 | { |
57 | return DateLUT::instance().timeToString(time(nullptr)); |
58 | } |
59 | |
60 | std::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 | |
187 | std::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 | |