| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <perftest/results.h> |
| |
| #include <errno.h> |
| #include <inttypes.h> |
| #include <math.h> |
| |
| #include <fbl/algorithm.h> |
| #include <zircon/assert.h> |
| |
| #include <numeric> |
| #include <utility> |
| |
| namespace perftest { |
| namespace { |
| |
| double Mean(const fbl::Vector<double>& values) { |
| double sum = std::accumulate(values.begin(), values.end(), 0.0); |
| return sum / static_cast<double>(values.size()); |
| } |
| |
| double Min(const fbl::Vector<double>& values) { |
| return *fbl::min_element(values.begin(), values.end()); |
| } |
| |
| double Max(const fbl::Vector<double>& values) { |
| return *fbl::max_element(values.begin(), values.end()); |
| } |
| |
| double StdDev(const fbl::Vector<double>& values, double mean) { |
| double sum_of_squared_diffs = 0.0; |
| for (double value : values) { |
| double diff = value - mean; |
| sum_of_squared_diffs += diff * diff; |
| } |
| return sqrt(sum_of_squared_diffs / static_cast<double>(values.size())); |
| } |
| |
| // Comparison function for use with qsort(). |
| int CompareDoubles(const void* ptr1, const void* ptr2) { |
| double val1 = *reinterpret_cast<const double*>(ptr1); |
| double val2 = *reinterpret_cast<const double*>(ptr2); |
| if (val1 < val2) { |
| return -1; |
| } |
| if (val1 > val2) { |
| return 1; |
| } |
| return 0; |
| } |
| |
| double Median(const fbl::Vector<double>& values) { |
| // Make a sorted copy of the vector. |
| fbl::Vector<double> copy; |
| copy.reserve(values.size()); |
| for (double value : values) { |
| copy.push_back(value); |
| } |
| qsort(copy.data(), copy.size(), sizeof(copy[0]), CompareDoubles); |
| |
| size_t index = copy.size() / 2; |
| // Interpolate two values if necessary. |
| if (copy.size() % 2 == 0) { |
| return (copy[index - 1] + copy[index]) / 2; |
| } |
| return copy[index]; |
| } |
| |
| } // namespace |
| |
| SummaryStatistics TestCaseResults::GetSummaryStatistics() const { |
| ZX_ASSERT(values.size() > 0); |
| double mean = Mean(values); |
| return SummaryStatistics{ |
| .min = Min(values), |
| .max = Max(values), |
| .mean = mean, |
| .std_dev = StdDev(values, mean), |
| .median = Median(values), |
| }; |
| } |
| |
| void WriteJSONString(FILE* out_file, const char* string) { |
| fputc('"', out_file); |
| for (const char* ptr = string; *ptr; ptr++) { |
| uint8_t c = *ptr; |
| if (c == '"') { |
| fputs("\\\"", out_file); |
| } else if (c == '\\') { |
| fputs("\\\\", out_file); |
| } else if (c < 32 || c >= 128) { |
| // Escape non-printable characters (<32) and top-bit-set |
| // characters (>=128). |
| // |
| // TODO(TO-824): Handle top-bit-set characters better. Ideally |
| // we should treat the input string as UTF-8 and preserve the |
| // encoded Unicode in the JSON. We could interpret the UTF-8 |
| // sequences and convert them to \uXXXX escape sequences. |
| // Alternatively we could pass through UTF-8, but if we do |
| // that, we ought to block overlong UTF-8 sequences to prevent |
| // closing quotes from being encoded as overlong UTF-8 |
| // sequences. |
| // |
| // The current code treats the input string as a byte array |
| // rather than UTF-8, which isn't *necessarily* what we want, |
| // but will at least result in valid JSON and make the data |
| // recoverable. |
| fprintf(out_file, "\\u%04x", c); |
| } else { |
| fputc(c, out_file); |
| } |
| } |
| fputc('"', out_file); |
| } |
| |
| void TestCaseResults::WriteJSON(FILE* out_file) const { |
| fprintf(out_file, "{\"label\":"); |
| WriteJSONString(out_file, label.c_str()); |
| fprintf(out_file, ",\"test_suite\":"); |
| WriteJSONString(out_file, test_suite.c_str()); |
| fprintf(out_file, ",\"unit\":"); |
| WriteJSONString(out_file, unit.c_str()); |
| if (bytes_processed_per_run) { |
| fprintf(out_file, ",\"bytes_processed_per_run\":%" PRIu64, bytes_processed_per_run); |
| } |
| fprintf(out_file, ",\"values\":["); |
| bool first = true; |
| for (const auto value : values) { |
| if (!first) { |
| fprintf(out_file, ","); |
| } |
| fprintf(out_file, "%f", value); |
| first = false; |
| } |
| fprintf(out_file, "]}"); |
| } |
| |
| TestCaseResults* ResultsSet::AddTestCase(const fbl::String& test_suite, const fbl::String& label, |
| const fbl::String& unit) { |
| TestCaseResults test_case(test_suite, label, unit); |
| results_.push_back(std::move(test_case)); |
| return &results_[results_.size() - 1]; |
| } |
| |
| void ResultsSet::WriteJSON(FILE* out_file) const { |
| fprintf(out_file, "["); |
| bool first = true; |
| for (const auto& test_case_results : results_) { |
| if (!first) { |
| fprintf(out_file, ",\n"); |
| } |
| test_case_results.WriteJSON(out_file); |
| first = false; |
| } |
| fprintf(out_file, "]"); |
| } |
| |
| bool ResultsSet::WriteJSONFile(const char* output_filename) const { |
| FILE* fh = fopen(output_filename, "w"); |
| if (!fh) { |
| fprintf(stderr, "Failed to open output file \"%s\": %s\n", output_filename, strerror(errno)); |
| return false; |
| } |
| WriteJSON(fh); |
| fclose(fh); |
| return true; |
| } |
| |
| void ResultsSet::PrintSummaryStatistics(FILE* out_file) const { |
| // Print table headings row. |
| fprintf(out_file, "%10s %10s %10s %10s %10s %-12s %15s %s\n", "Mean", "Std dev", "Min", "Max", |
| "Median", "Unit", "Mean Mbytes/sec", "Test case"); |
| if (results_.size() == 0) { |
| fprintf(out_file, "(No test results)\n"); |
| } |
| for (const auto& test : results_) { |
| SummaryStatistics stats = test.GetSummaryStatistics(); |
| fprintf(out_file, "%10.0f %10.0f %10.0f %10.0f %10.0f %-12s", stats.mean, stats.std_dev, |
| stats.min, stats.max, stats.median, test.unit.c_str()); |
| // Output the throughput column. |
| if (test.bytes_processed_per_run != 0 && test.unit == "nanoseconds") { |
| double bytes_per_second = |
| static_cast<double>(test.bytes_processed_per_run) / stats.mean * 1e9; |
| double mbytes_per_second = bytes_per_second / (1024 * 1024); |
| fprintf(out_file, " %15.3f", mbytes_per_second); |
| } else { |
| fprintf(out_file, " %15s", "N/A"); |
| } |
| fprintf(out_file, " %s\n", test.label.c_str()); |
| } |
| } |
| |
| } // namespace perftest |