From ba5adc25f8ca9da9faad01a00a4a9ced90d83dd7 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Tue, 26 Jul 2022 02:08:16 -0700 Subject: [PATCH] start adding JSON output -- it's easier to use data in that format --- src/harness.h | 1 + src/json.h | 385 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.cpp | 26 +++- 3 files changed, 408 insertions(+), 4 deletions(-) create mode 100644 src/json.h diff --git a/src/harness.h b/src/harness.h index aacd27a..f623114 100644 --- a/src/harness.h +++ b/src/harness.h @@ -17,6 +17,7 @@ struct Config final std::uint32_t log2_memory_location_count = 0; std::uint32_t log2_stride = 0; static constexpr std::uint32_t max_sum_log2_mem_loc_count_and_stride = 28; + bool use_json = false; }; template class BenchHarness; diff --git a/src/json.h b/src/json.h new file mode 100644 index 0000000..63abd3a --- /dev/null +++ b/src/json.h @@ -0,0 +1,385 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct JsonValue; + +using JsonString = std::string; +using JsonFloat = double; +using JsonNull = std::nullptr_t; +using JsonArray = std::vector; +using JsonMap = std::vector>; + +struct JsonValue final +{ + std::variant, + std::unique_ptr> + value; + constexpr JsonValue() noexcept : value(nullptr) + { + } + constexpr JsonValue(JsonNull) noexcept : value(nullptr) + { + } + constexpr JsonValue(JsonFloat value) noexcept : value(value) + { + } + JsonValue(JsonString value) noexcept : value(std::move(value)) + { + } + JsonValue(std::unique_ptr value) noexcept + : value(std::move(value)) + { + } + JsonValue(JsonArray value) noexcept + : value(std::make_unique(std::move(value))) + { + } + JsonValue(std::unique_ptr value) noexcept + : value(std::move(value)) + { + } + JsonValue(JsonMap value) noexcept + : value(std::make_unique(std::move(value))) + { + } + /// decode a JsonString from WTF-8 to the encoding logically used in JSON: + /// WTF-16 aka. potentially ill-formed UTF-16 + /// + /// https://simonsapin.github.io/wtf-8/ + class JsonStringDecoder final + { + private: + std::string_view remaining; + bool last_was_lead_surrogate = false; + std::optional trail_surrogate; + + public: + constexpr JsonStringDecoder() noexcept : JsonStringDecoder("") + { + } + constexpr explicit JsonStringDecoder( + std::string_view json_string) noexcept + : remaining(json_string) + { + } + + private: + [[noreturn]] void throw_wtf8_err() + { + *this = JsonStringDecoder(); + throw std::ios_base::failure("Invalid WTF-8"); + } + + public: + constexpr std::optional next() + { + if (trail_surrogate) + { + auto retval = *trail_surrogate; + trail_surrogate = std::nullopt; + return retval; + } + if (remaining.empty()) + { + return std::nullopt; + } + std::uint32_t code_point = 0; + struct min_max final + { + std::uint8_t min = 0x80; + std::uint8_t max = 0xBF; + }; + auto get = [&]() -> std::uint8_t { + if (remaining.empty()) + { + throw_wtf8_err(); + } + std::uint8_t retval = remaining[0]; + remaining.remove_prefix(1); + return retval; + }; + auto cont = [&](min_max min_max = {}) { + std::uint8_t v = get(); + if (v < min_max.min || v > min_max.max) + { + throw_wtf8_err(); + } + code_point <<= 6; + code_point |= v & 0x3F; + }; + std::uint8_t initial = get(); + if (initial < 0x80) + { + code_point = initial; + } + else if (initial < 0xC2) + { + throw_wtf8_err(); + } + else if (initial < 0xE0) + { + code_point = initial & 0x1F; + cont(); + } + else if (initial == 0xE0) + { + code_point = initial & 0xF; + cont({.min = 0xA0}); + cont(); + } + else if (initial < 0xF0) + { + code_point = initial & 0xF; + cont(); + cont(); + } + else if (initial == 0xF0) + { + code_point = initial & 0x7; + cont({.min = 0x90}); + cont(); + cont(); + } + else if (initial < 0xF4) + { + code_point = initial & 0x7; + cont(); + cont(); + cont(); + } + else if (initial == 0xF4) + { + code_point = initial & 0x7; + cont({.max = 0x8F}); + cont(); + cont(); + } + else + { + throw_wtf8_err(); + } + if (last_was_lead_surrogate && code_point >= 0xDC00 && + code_point <= 0xDFFF) + { + // got lead surrogate followed by trail surrogate -- + // invalid in WTF-8 + throw_wtf8_err(); + } + last_was_lead_surrogate = + (code_point >= 0xD800 && code_point <= 0xDBFF); + bool is_supplementary_code_point = code_point >= 0x10000; + if (is_supplementary_code_point) + { + auto value = code_point - 0x10000; + std::uint16_t retval = (value >> 10) + 0xD800; + trail_surrogate = (value & 0x3FF) + 0xDC00; + return retval; + } + else + { + return code_point; + } + } + }; + template + void write(WriteStringView &&write_fn) const + { + struct Visitor final + { + WriteStringView &write_fn; + void write(std::string_view str) + { + write_fn(str); + } + void write(char ch) + { + write_fn(std::string_view(&ch, 1)); + } + void operator()(const JsonString &value) + { + write('\"'); + JsonStringDecoder decoder(value); + while (auto value_opt = decoder.next()) + { + std::uint16_t value = *value_opt; + switch (value) + { + case '\"': + case '\\': + write('\\'); + write(static_cast(value)); + break; + case '\b': + write("\\b"); + break; + case '\f': + write("\\f"); + break; + case '\n': + write("\\n"); + break; + case '\r': + write("\\r"); + break; + case '\t': + write("\\t"); + break; + default: + if (value >= 0x20 && value <= 0x7E) + { + write(static_cast(value)); + } + else + { + static constexpr char hex_digits[] = + "0123456789ABCDEF"; + write("\\u"); + for (int i = 0; i < 4; i++) + { + write(hex_digits[value >> 12]); + value <<= 4; + } + } + break; + } + } + write('"'); + } + void operator()(JsonFloat value) + { + if (std::isnan(value)) + { + write("NaN"); + return; + } + if (std::signbit(value)) + { + write('-'); + value = -value; + } + if (std::isinf(value)) + { + write("Infinity"); + return; + } + if (value == 0) + { + write("0"); + return; + } + using Buf = std::array; + auto try_format = [&](Buf &buf, bool e_format, + int prec) -> bool { + int result; + if (e_format) + { + result = std::snprintf(&buf[0], buf.size(), "%1.*e", + prec, value); + } + else + { + result = std::snprintf(&buf[0], buf.size(), "%1.*f", + prec, value); + } + if (result <= 0) + return false; + double parsed_value = std::strtod(&buf[0], nullptr); + if (parsed_value != value) + { + // not precise enough + return false; + } + return true; + }; + Buf final_buf = {}; + std::optional final; + std::size_t end_prec = final_buf.size(); + for (std::size_t prec = 0; + prec < final_buf.size() && prec < end_prec; prec++) + { + Buf buf; + if (try_format(buf, true, prec)) + { + std::string_view str(&buf[0]); + if (!final || str.size() < final->size()) + { + final_buf = buf; + final = + std::string_view(&final_buf[0], str.size()); + end_prec = prec + 3; + } + } + if (try_format(buf, false, prec)) + { + std::string_view str(&buf[0]); + if (!final || str.size() < final->size()) + { + final_buf = buf; + final = + std::string_view(&final_buf[0], str.size()); + end_prec = prec + 3; + } + } + } + if (final_buf[0] == '.') + { + write('0'); + } + write(*final); + } + void operator()(JsonNull) + { + write("null"); + } + void operator()(const std::unique_ptr &value) + { + write('['); + std::string_view sep{}; + for (auto &i : *value) + { + write(sep); + sep = ","; + std::visit(*this, i.value); + } + write(']'); + } + void operator()(const std::unique_ptr &value) + { + write('{'); + std::string_view sep{}; + for (auto &[k, v] : *value) + { + write(sep); + sep = ","; + operator()(k); + write(':'); + std::visit(*this, v.value); + } + write('}'); + } + }; + std::visit(Visitor{.write_fn = write_fn}, value); + } + friend std::ostream &operator<<(std::ostream &os, const JsonValue &self) + { + self.write([&](std::string_view str) { os << str; }); + return os; + } +}; diff --git a/src/main.cpp b/src/main.cpp index feafbb1..4944a0d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,6 @@ #include "all_benchmarks.h" #include "harness.h" +#include "json.h" #include #include #include @@ -463,6 +464,10 @@ int main(int, char **argv) } enabled_benchmarks->emplace(value.value_or("")); }}, + Option{.long_name = "json", + .description = "Write the output in JSON format", + .value_kind = OptionValueKind::None, + .parse_value = [&](auto &, auto) { config.use_json = true; }}, }; OptionsParser parser(options, argv); auto args = parser.parse(); @@ -478,12 +483,24 @@ int main(int, char **argv) enabled_benchmarks->erase("list"); if (enabled_benchmarks->empty()) { - std::cout << "Available Benchmarks:\n"; - for (auto &benchmark : benchmarks) + if (config.use_json) { - std::cout << benchmark.name() << "\n"; + JsonArray names; + for (auto &benchmark : benchmarks) + { + names.push_back(benchmark.name()); + } + std::cout << JsonValue(std::move(names)) << std::endl; + } + else + { + std::cout << "Available Benchmarks:\n"; + for (auto &benchmark : benchmarks) + { + std::cout << benchmark.name() << "\n"; + } + std::cout << std::endl; } - std::cout << std::endl; return 0; } std::unordered_set unknown_benchmarks = @@ -502,6 +519,7 @@ int main(int, char **argv) auto thread_cache = BenchHarnessBase::get_thread_cache(); for (auto &benchmark : benchmarks) { + // FIXME: change output to use JSON when selected if (enabled_benchmarks && !enabled_benchmarks->count(benchmark.name())) { continue; -- 2.30.2