2019-01-25 18:35:16 +00:00
|
|
|
#include "ReportBuilder.h"
|
2019-01-28 16:20:29 +00:00
|
|
|
|
2019-01-25 18:35:16 +00:00
|
|
|
#include <algorithm>
|
|
|
|
#include <regex>
|
2019-01-28 16:20:29 +00:00
|
|
|
#include <sstream>
|
2019-01-29 10:43:35 +00:00
|
|
|
#include <thread>
|
2019-01-28 16:20:29 +00:00
|
|
|
|
2019-01-25 18:35:16 +00:00
|
|
|
#include <Common/getNumberOfPhysicalCPUCores.h>
|
|
|
|
#include <Common/getFQDNOrHostName.h>
|
|
|
|
#include <common/getMemoryAmount.h>
|
2019-07-29 09:05:37 +00:00
|
|
|
#include <Common/StringUtils/StringUtils.h>
|
2019-01-25 18:35:16 +00:00
|
|
|
|
2019-01-28 16:20:29 +00:00
|
|
|
#include "JSONString.h"
|
2019-01-25 18:35:16 +00:00
|
|
|
|
|
|
|
namespace DB
|
|
|
|
{
|
2019-01-28 16:20:29 +00:00
|
|
|
|
2019-01-25 18:35:16 +00:00
|
|
|
namespace
|
|
|
|
{
|
|
|
|
const std::regex QUOTE_REGEX{"\""};
|
2019-02-06 11:44:00 +00:00
|
|
|
std::string getMainMetric(const PerformanceTestInfo & test_info)
|
|
|
|
{
|
|
|
|
std::string main_metric;
|
|
|
|
if (test_info.main_metric.empty())
|
|
|
|
if (test_info.exec_type == ExecutionType::Loop)
|
|
|
|
main_metric = "min_time";
|
|
|
|
else
|
|
|
|
main_metric = "rows_per_second";
|
|
|
|
else
|
|
|
|
main_metric = test_info.main_metric;
|
|
|
|
return main_metric;
|
|
|
|
}
|
2019-07-29 09:05:37 +00:00
|
|
|
bool isASCIIString(const std::string & str)
|
|
|
|
{
|
|
|
|
return std::all_of(str.begin(), str.end(), isASCII);
|
|
|
|
}
|
2019-01-25 18:35:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ReportBuilder::ReportBuilder(const std::string & server_version_)
|
|
|
|
: server_version(server_version_)
|
|
|
|
, hostname(getFQDNOrHostName())
|
|
|
|
, num_cores(getNumberOfPhysicalCPUCores())
|
|
|
|
, num_threads(std::thread::hardware_concurrency())
|
|
|
|
, ram(getMemoryAmount())
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string ReportBuilder::getCurrentTime() const
|
|
|
|
{
|
|
|
|
return DateLUT::instance().timeToString(time(nullptr));
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string ReportBuilder::buildFullReport(
|
|
|
|
const PerformanceTestInfo & test_info,
|
2019-02-04 17:37:55 +00:00
|
|
|
std::vector<TestStats> & stats,
|
|
|
|
const std::vector<std::size_t> & queries_to_run) const
|
2019-01-25 18:35:16 +00:00
|
|
|
{
|
|
|
|
JSONString json_output;
|
|
|
|
|
|
|
|
json_output.set("hostname", hostname);
|
|
|
|
json_output.set("num_cores", num_cores);
|
|
|
|
json_output.set("num_threads", num_threads);
|
|
|
|
json_output.set("ram", ram);
|
|
|
|
json_output.set("server_version", server_version);
|
|
|
|
json_output.set("time", getCurrentTime());
|
|
|
|
json_output.set("test_name", test_info.test_name);
|
2019-01-31 13:46:43 +00:00
|
|
|
json_output.set("path", test_info.path);
|
2019-02-06 11:44:00 +00:00
|
|
|
json_output.set("main_metric", getMainMetric(test_info));
|
2019-01-25 18:35:16 +00:00
|
|
|
|
|
|
|
if (test_info.substitutions.size())
|
|
|
|
{
|
|
|
|
JSONString json_parameters(2); /// here, 2 is the size of \t padding
|
|
|
|
|
|
|
|
for (auto it = test_info.substitutions.begin(); it != test_info.substitutions.end(); ++it)
|
|
|
|
{
|
2019-01-28 16:20:29 +00:00
|
|
|
std::string parameter = it->first;
|
|
|
|
Strings values = it->second;
|
2019-01-25 18:35:16 +00:00
|
|
|
|
2019-01-28 16:20:29 +00:00
|
|
|
std::ostringstream array_string;
|
|
|
|
array_string << "[";
|
2019-01-25 18:35:16 +00:00
|
|
|
for (size_t i = 0; i != values.size(); ++i)
|
|
|
|
{
|
2019-01-28 16:20:29 +00:00
|
|
|
array_string << '"' << std::regex_replace(values[i], QUOTE_REGEX, "\\\"") << '"';
|
2019-01-25 18:35:16 +00:00
|
|
|
if (i != values.size() - 1)
|
|
|
|
{
|
2019-01-28 16:20:29 +00:00
|
|
|
array_string << ", ";
|
2019-01-25 18:35:16 +00:00
|
|
|
}
|
|
|
|
}
|
2019-01-28 16:20:29 +00:00
|
|
|
array_string << ']';
|
2019-01-25 18:35:16 +00:00
|
|
|
|
2019-01-28 16:20:29 +00:00
|
|
|
json_parameters.set(parameter, array_string.str());
|
2019-01-25 18:35:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
json_output.set("parameters", json_parameters.asString());
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<JSONString> run_infos;
|
|
|
|
for (size_t query_index = 0; query_index < test_info.queries.size(); ++query_index)
|
|
|
|
{
|
2019-02-04 17:37:55 +00:00
|
|
|
if (!queries_to_run.empty() && std::find(queries_to_run.begin(), queries_to_run.end(), query_index) == queries_to_run.end())
|
|
|
|
continue;
|
|
|
|
|
2019-01-25 18:35:16 +00:00
|
|
|
for (size_t number_of_launch = 0; number_of_launch < test_info.times_to_run; ++number_of_launch)
|
|
|
|
{
|
|
|
|
size_t stat_index = number_of_launch * test_info.queries.size() + query_index;
|
|
|
|
TestStats & statistics = stats[stat_index];
|
|
|
|
|
|
|
|
if (!statistics.ready)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
JSONString runJSON;
|
|
|
|
|
|
|
|
auto query = std::regex_replace(test_info.queries[query_index], QUOTE_REGEX, "\\\"");
|
|
|
|
runJSON.set("query", query);
|
2019-02-04 17:37:55 +00:00
|
|
|
runJSON.set("query_index", query_index);
|
2019-01-25 18:35:16 +00:00
|
|
|
if (!statistics.exception.empty())
|
2019-07-29 09:05:37 +00:00
|
|
|
{
|
|
|
|
if (isASCIIString(statistics.exception))
|
|
|
|
runJSON.set("exception", std::regex_replace(statistics.exception, QUOTE_REGEX, "\\\""));
|
|
|
|
else
|
|
|
|
runJSON.set("exception", "Some exception occured with non ASCII message. This may produce invalid JSON. Try reproduce locally.");
|
|
|
|
}
|
2019-01-25 18:35:16 +00:00
|
|
|
|
|
|
|
if (test_info.exec_type == ExecutionType::Loop)
|
|
|
|
{
|
|
|
|
/// in seconds
|
2019-02-06 11:44:00 +00:00
|
|
|
runJSON.set("min_time", statistics.min_time / double(1000));
|
2019-01-25 18:35:16 +00:00
|
|
|
|
2019-02-06 13:12:48 +00:00
|
|
|
if (statistics.sampler.size() != 0)
|
2019-01-25 18:35:16 +00:00
|
|
|
{
|
2019-02-06 13:12:48 +00:00
|
|
|
JSONString quantiles(4); /// here, 4 is the size of \t padding
|
|
|
|
for (double percent = 10; percent <= 90; percent += 10)
|
|
|
|
{
|
|
|
|
std::string quantile_key = std::to_string(percent / 100.0);
|
|
|
|
while (quantile_key.back() == '0')
|
|
|
|
quantile_key.pop_back();
|
|
|
|
|
|
|
|
quantiles.set(quantile_key,
|
|
|
|
statistics.sampler.quantileInterpolated(percent / 100.0));
|
|
|
|
}
|
|
|
|
quantiles.set("0.95",
|
|
|
|
statistics.sampler.quantileInterpolated(95 / 100.0));
|
|
|
|
quantiles.set("0.99",
|
|
|
|
statistics.sampler.quantileInterpolated(99 / 100.0));
|
|
|
|
quantiles.set("0.999",
|
|
|
|
statistics.sampler.quantileInterpolated(99.9 / 100.0));
|
|
|
|
quantiles.set("0.9999",
|
|
|
|
statistics.sampler.quantileInterpolated(99.99 / 100.0));
|
|
|
|
|
|
|
|
runJSON.set("quantiles", quantiles.asString());
|
2019-01-25 18:35:16 +00:00
|
|
|
}
|
|
|
|
|
2019-02-06 11:44:00 +00:00
|
|
|
runJSON.set("total_time", statistics.total_time);
|
2019-01-25 18:35:16 +00:00
|
|
|
|
2019-02-06 15:48:53 +00:00
|
|
|
if (statistics.total_time != 0)
|
|
|
|
{
|
|
|
|
runJSON.set("queries_per_second", static_cast<double>(statistics.queries) / statistics.total_time);
|
|
|
|
runJSON.set("rows_per_second", static_cast<double>(statistics.total_rows_read) / statistics.total_time);
|
|
|
|
runJSON.set("bytes_per_second", static_cast<double>(statistics.total_bytes_read) / statistics.total_time);
|
|
|
|
}
|
2019-01-25 18:35:16 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-02-06 11:44:00 +00:00
|
|
|
runJSON.set("max_rows_per_second", statistics.max_rows_speed);
|
|
|
|
runJSON.set("max_bytes_per_second", statistics.max_bytes_speed);
|
|
|
|
runJSON.set("avg_rows_per_second", statistics.avg_rows_speed_value);
|
|
|
|
runJSON.set("avg_bytes_per_second", statistics.avg_bytes_speed_value);
|
2019-01-25 18:35:16 +00:00
|
|
|
}
|
|
|
|
|
2019-07-08 13:23:30 +00:00
|
|
|
runJSON.set("memory_usage", statistics.memory_usage);
|
2019-07-05 16:50:44 +00:00
|
|
|
|
2019-01-25 18:35:16 +00:00
|
|
|
run_infos.push_back(runJSON);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
json_output.set("runs", run_infos);
|
|
|
|
|
|
|
|
return json_output.asString();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string ReportBuilder::buildCompactReport(
|
|
|
|
const PerformanceTestInfo & test_info,
|
2019-02-04 17:37:55 +00:00
|
|
|
std::vector<TestStats> & stats,
|
|
|
|
const std::vector<std::size_t> & queries_to_run) const
|
2019-01-25 18:35:16 +00:00
|
|
|
{
|
|
|
|
|
2019-01-28 16:20:29 +00:00
|
|
|
std::ostringstream output;
|
2019-01-25 18:35:16 +00:00
|
|
|
|
|
|
|
for (size_t query_index = 0; query_index < test_info.queries.size(); ++query_index)
|
|
|
|
{
|
2019-02-04 17:37:55 +00:00
|
|
|
if (!queries_to_run.empty() && std::find(queries_to_run.begin(), queries_to_run.end(), query_index) == queries_to_run.end())
|
|
|
|
continue;
|
|
|
|
|
2019-01-25 18:35:16 +00:00
|
|
|
for (size_t number_of_launch = 0; number_of_launch < test_info.times_to_run; ++number_of_launch)
|
|
|
|
{
|
|
|
|
if (test_info.queries.size() > 1)
|
2019-01-28 16:20:29 +00:00
|
|
|
output << "query \"" << test_info.queries[query_index] << "\", ";
|
2019-01-25 18:35:16 +00:00
|
|
|
|
2019-01-28 16:20:29 +00:00
|
|
|
output << "run " << std::to_string(number_of_launch + 1) << ": ";
|
2019-02-06 11:44:00 +00:00
|
|
|
|
|
|
|
std::string main_metric = getMainMetric(test_info);
|
|
|
|
|
|
|
|
output << main_metric << " = ";
|
2019-01-25 18:35:16 +00:00
|
|
|
size_t index = number_of_launch * test_info.queries.size() + query_index;
|
2019-02-06 11:44:00 +00:00
|
|
|
output << stats[index].getStatisticByName(main_metric);
|
2019-01-28 16:20:29 +00:00
|
|
|
output << "\n";
|
2019-01-25 18:35:16 +00:00
|
|
|
}
|
|
|
|
}
|
2019-01-28 16:20:29 +00:00
|
|
|
return output.str();
|
2019-01-25 18:35:16 +00:00
|
|
|
}
|
|
|
|
}
|