start adding JSON output -- it's easier to use data in that format
authorJacob Lifshay <programmerjake@gmail.com>
Tue, 26 Jul 2022 09:08:16 +0000 (02:08 -0700)
committerJacob Lifshay <programmerjake@gmail.com>
Tue, 26 Jul 2022 09:08:16 +0000 (02:08 -0700)
src/harness.h
src/json.h [new file with mode: 0644]
src/main.cpp

index aacd27ae491f9bc540f777f237e56fa49b1bc3f7..f623114ba4d862891686979af67b8100f6680f95 100644 (file)
@@ -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 <typename Fn, typename Input> class BenchHarness;
diff --git a/src/json.h b/src/json.h
new file mode 100644 (file)
index 0000000..63abd3a
--- /dev/null
@@ -0,0 +1,385 @@
+#pragma once
+
+#include <charconv>
+#include <cmath>
+#include <cstddef>
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <initializer_list>
+#include <ios>
+#include <memory>
+#include <optional>
+#include <ostream>
+#include <string>
+#include <string_view>
+#include <system_error>
+#include <unordered_map>
+#include <variant>
+#include <vector>
+
+struct JsonValue;
+
+using JsonString = std::string;
+using JsonFloat = double;
+using JsonNull = std::nullptr_t;
+using JsonArray = std::vector<JsonValue>;
+using JsonMap = std::vector<std::pair<std::string, JsonValue>>;
+
+struct JsonValue final
+{
+    std::variant<JsonString, JsonFloat, JsonNull, std::unique_ptr<JsonArray>,
+                 std::unique_ptr<JsonMap>>
+        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<JsonArray> value) noexcept
+        : value(std::move(value))
+    {
+    }
+    JsonValue(JsonArray value) noexcept
+        : value(std::make_unique<JsonArray>(std::move(value)))
+    {
+    }
+    JsonValue(std::unique_ptr<JsonMap> value) noexcept
+        : value(std::move(value))
+    {
+    }
+    JsonValue(JsonMap value) noexcept
+        : value(std::make_unique<JsonMap>(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<std::uint16_t> 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<std::uint16_t> 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 <typename WriteStringView>
+    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<char>(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<char>(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<char, 32>;
+                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<std::string_view> 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<JsonArray> &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<JsonMap> &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;
+    }
+};
index feafbb13f8175ad1e98b36961d4d22f3bdc27d65..4944a0d907cbbe2dc801b8ce18ef83ed436bdc26 100644 (file)
@@ -1,5 +1,6 @@
 #include "all_benchmarks.h"
 #include "harness.h"
+#include "json.h"
 #include <charconv>
 #include <cstdlib>
 #include <functional>
@@ -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<std::string> 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;