From 2d21f6043b9a50482e4f7e6310c57ec8f4602e74 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 27 Jul 2022 00:07:56 -0700 Subject: [PATCH] finish adding json output --- src/harness.cpp | 101 ++++-- src/harness.h | 95 +++--- src/json.h | 795 ++++++++++++++++++++++++++++++++++++++---------- src/main.cpp | 58 +++- 4 files changed, 821 insertions(+), 228 deletions(-) diff --git a/src/harness.cpp b/src/harness.cpp index e28eac9..e6fbda3 100644 --- a/src/harness.cpp +++ b/src/harness.cpp @@ -1,4 +1,5 @@ #include "harness.h" +#include "json.h" #include #include #include @@ -11,6 +12,7 @@ #include #include #include +#include #ifdef NDEBUG // assert needs to work even in release mode #undef NDEBUG @@ -171,8 +173,68 @@ struct WriteDuration final } }; -void BenchHarnessBase::base_run( - const Config &config, +struct BenchmarkResultInner final +{ + struct Result final + { + std::chrono::duration total_dur; + std::chrono::duration iter_dur; + operator JsonValue() const + { + return JsonValue::Object{ + {"total_dur", total_dur.count()}, + {"iter_dur", iter_dur.count()}, + }; + } + }; + std::string name; + JsonValue config_json; + std::uint64_t iteration_count; + Result average; + std::vector threads; +}; + +struct BenchmarkResultImpl final : public BenchmarkResult +{ + BenchmarkResultInner inner; + BenchmarkResultImpl(BenchmarkResultInner inner) : inner(std::move(inner)) + { + } + virtual void print() const override + { + std::cout << inner.name << ":\n"; + if (inner.threads.size() > 1) + { + for (std::size_t i = 0; i < inner.threads.size(); i++) + { + std::cout << "Thread #" << i << " took " + << WriteDuration{inner.threads[i].total_dur} + << " for " << inner.iteration_count + << " iterations -- " + << WriteDuration{inner.threads[i].iter_dur} + << "/iter.\n"; + } + } + std::cout << "Average elapsed time: " + << WriteDuration{inner.average.total_dur} << " for " + << inner.iteration_count << " iterations -- " + << WriteDuration{inner.average.iter_dur} << "/iter.\n" + << std::endl; + } + virtual operator JsonValue() const override + { + return JsonValue::Object{ + {"name", inner.name}, + {"config", inner.config_json}, + {"iteration_count", inner.iteration_count}, + {"average", inner.average}, + {"threads", inner.threads}, + }; + } +}; + +std::shared_ptr BenchHarnessBase::base_run( + const Config &config, const std::string &name, void (*fn)(BenchHarnessBase *bench_harness_base, std::uint64_t iteration_count, std::uint32_t thread_num)) { @@ -214,6 +276,7 @@ void BenchHarnessBase::base_run( if (config.iteration_count) { iteration_count = *config.iteration_count; + assert(iteration_count > 0); run(iteration_count); } else @@ -227,7 +290,8 @@ void BenchHarnessBase::base_run( total_elapsed += i; } auto target_average_elapsed = std::chrono::milliseconds(500); - if (total_elapsed > thread_count * target_average_elapsed) + if (total_elapsed > thread_count * target_average_elapsed || + iteration_count >= (1ULL << 63)) { break; } @@ -235,25 +299,28 @@ void BenchHarnessBase::base_run( } } steady_clock::duration total_elapsed{}; + BenchmarkResultInner retval = { + .name = name, + .config_json = config, + .iteration_count = iteration_count, + .average = {}, + .threads = {}, + }; for (std::uint32_t thread_num = 0; thread_num < thread_count; thread_num++) { total_elapsed += elapsed[thread_num]; - if (thread_count > 1) - { - auto dur = std::chrono::duration(elapsed[thread_num]); - std::cout << "Thread #" << thread_num << " took " - << WriteDuration{dur} << " for " << iteration_count - << " iterations -- " - << WriteDuration{dur / iteration_count} << "/iter.\n"; - } + auto dur = std::chrono::duration(elapsed[thread_num]); + retval.threads.push_back({ + .total_dur = dur, + .iter_dur = dur / iteration_count, + }); } auto total = std::chrono::duration(total_elapsed); - std::cout << "Average elapsed time: " - << WriteDuration{total / thread_count} << " for " - << iteration_count << " iterations -- " - << WriteDuration{total / thread_count / iteration_count} - << "/iter.\n" - << std::endl; + retval.average = { + .total_dur = total / thread_count, + .iter_dur = total / thread_count / iteration_count, + }; + return std::make_shared(retval); } std::shared_ptr BenchHarnessBase::get_thread_cache() diff --git a/src/harness.h b/src/harness.h index 72ee8d1..b9ab39d 100644 --- a/src/harness.h +++ b/src/harness.h @@ -1,5 +1,6 @@ #pragma once +#include "json.h" #include #include #include @@ -18,10 +19,28 @@ struct Config final std::uint32_t log2_stride = 0; static constexpr std::uint32_t max_sum_log2_mem_loc_count_and_stride = 28; bool use_json = false; + operator JsonValue() const + { + return JsonValue::Object{ + {"thread_count", thread_count}, + {"iteration_count", iteration_count}, + {"log2_memory_location_count", log2_memory_location_count}, + {"log2_stride", log2_stride}, + {"use_json", use_json}, + }; + } }; template class BenchHarness; +struct BenchmarkResult +{ + BenchmarkResult() = default; + virtual ~BenchmarkResult() = default; + virtual void print() const = 0; + virtual operator JsonValue() const = 0; +}; + class BenchHarnessBase { template friend class BenchHarness; @@ -30,10 +49,10 @@ class BenchHarnessBase std::shared_ptr thread_cache; class ThreadCache; friend class ThreadCache; - void base_run(const Config &config, - void (*fn)(BenchHarnessBase *bench_harness_base, - std::uint64_t iteration_count, - std::uint32_t thread_num)); + std::shared_ptr base_run( + const Config &config, const std::string &name, + void (*fn)(BenchHarnessBase *bench_harness_base, + std::uint64_t iteration_count, std::uint32_t thread_num)); public: static std::shared_ptr get_thread_cache(); @@ -51,36 +70,38 @@ class BenchHarness final : private BenchHarnessBase : fn(std::move(fn)), input(std::move(input)) { } - void run(const Config &config) + std::shared_ptr run(const Config &config, + const std::string &name) { - base_run(config, [](BenchHarnessBase *bench_harness_base, - std::uint64_t iteration_count, - std::uint32_t thread_num) { - auto self = static_cast(bench_harness_base); - auto &fn = self->fn; - // copy for repeatability, also so optimization barrier is on copy, - // not self - auto input = self->input; - for (std::uint64_t i = 0; i < iteration_count; i++) - { - // optimization barrier - asm("" : : "r"(std::addressof(input)) : "memory"); - - if constexpr (std::is_void_v>) + return base_run( + config, name, + [](BenchHarnessBase *bench_harness_base, + std::uint64_t iteration_count, std::uint32_t thread_num) { + auto self = static_cast(bench_harness_base); + auto &fn = self->fn; + // copy for repeatability, also so optimization barrier is on + // copy, not self + auto input = self->input; + for (std::uint64_t i = 0; i < iteration_count; i++) { - fn(input, i, thread_num); - } - else - { - auto output = fn(input, i, thread_num); - // optimization barrier - asm("" : : "r"(std::addressof(output)) : "memory"); + asm("" : : "r"(std::addressof(input)) : "memory"); + + if constexpr (std::is_void_v>) + { + fn(input, i, thread_num); + } + else + { + auto output = fn(input, i, thread_num); + + // optimization barrier + asm("" : : "r"(std::addressof(output)) : "memory"); + } } - } - }); + }); } }; @@ -88,19 +109,23 @@ class Benchmark final { private: std::string m_name; - std::function m_run; + std::function(const Config &config, + const std::string &name)> + m_run; public: template explicit Benchmark(Fn fn, Input input, std::string name) - : m_name(std::move(name)), m_run([fn, input](const Config &config) { - return BenchHarness(std::move(fn), std::move(input)).run(config); + : m_name(std::move(name)), + m_run([fn, input](const Config &config, const std::string &name) { + return BenchHarness(std::move(fn), std::move(input)) + .run(config, name); }) { } - void run(const Config &config) + std::shared_ptr run(const Config &config) const { - return m_run(config); + return m_run(config, m_name); } const std::string &name() const { diff --git a/src/json.h b/src/json.h index 63abd3a..5aee708 100644 --- a/src/json.h +++ b/src/json.h @@ -1,62 +1,472 @@ #pragma once +#include #include #include #include #include #include #include +#include +#include #include #include +#include #include #include #include #include #include #include +#include #include #include #include -struct JsonValue; +enum class ResolveResult : bool +{ + Finished = false, + MoreToResolve = true, +}; + +template class LazyVec final +{ + private: + struct Internals + { + std::vector resolved_values; + explicit Internals(std::vector &&resolved_values) + : resolved_values(std::move(resolved_values)) + { + } + explicit Internals(std::initializer_list resolved_values = {}) + : resolved_values(resolved_values) + { + } + virtual ~Internals() = default; + virtual ResolveResult resolve_more() + { + return ResolveResult::Finished; + } + }; + template < + typename Iterator, typename Sentinel, + typename = std::enable_if_t< + std::is_base_of_v< + std::input_iterator_tag, + typename std::iterator_traits::iterator_category> && + std::is_same_v< + const typename std::iterator_traits::value_type, + const T>>> + struct LazyInternalsIter final : public Internals + { + struct State final + { + Iterator cur; + Sentinel end; + State(Iterator &&cur, Sentinel &&end) noexcept + : cur(std::move(cur)), end(std::move(end)) + { + } + }; + std::optional state; + LazyInternalsIter(Iterator &&cur, Sentinel &&end) noexcept + : state(std::in_place, std::move(cur), std::move(end)) + { + if (state->cur == state->end) + { + state = std::nullopt; + } + } + LazyInternalsIter(const LazyInternalsIter &) = delete; + LazyInternalsIter &operator=(const LazyInternalsIter &) = delete; + virtual ResolveResult resolve_more() override + { + if (!state) + { + return ResolveResult::Finished; + } + this->resolved_values.emplace_back(*state->cur++); + if (state->cur == state->end) + { + state = std::nullopt; + return ResolveResult::Finished; + } + return ResolveResult::MoreToResolve; + } + }; + template < + typename Container, + typename Iterator = decltype(std::begin(std::declval())), + typename Sentinel = decltype(std::end(std::declval())), + typename = decltype(LazyInternalsIter::cur)> + struct LazyInternalsContainer final : public Internals + { + struct State final + { + Container container; + Iterator cur; // must come after container + Sentinel end; // must come after container + explicit State(Container &&container) noexcept + : container(std::move(container)), + cur(std::begin(this->container)), + end(std::begin(this->container)) + { + } + State(const State &) = delete; + State &operator=(const State &) = delete; + }; + std::optional state; + explicit LazyInternalsContainer(Container &&container) noexcept + : state(std::in_place, std::move(container)) + { + if (state->cur == state->end) + { + state = std::nullopt; + } + } + virtual ResolveResult resolve_more() override + { + if (!state) + { + return ResolveResult::Finished; + } + this->resolved_values.emplace_back(*state->cur++); + if (state->cur == state->end) + { + state = std::nullopt; + return ResolveResult::Finished; + } + return ResolveResult::MoreToResolve; + } + }; + template ()()), std::optional>>> + struct LazyInternalsFn final : public Internals + { + std::optional fn; + explicit LazyInternalsFn(Fn &&fn) noexcept + : fn(std::in_place, std::move(fn)) + { + } + virtual ResolveResult resolve_more() override + { + if (!fn) + { + return ResolveResult::Finished; + } + if (std::optional value = (*fn)()) + { + this->resolved_values.emplace_back(std::move(*value)); + return ResolveResult::MoreToResolve; + } + fn = std::nullopt; + return ResolveResult::Finished; + } + }; + std::shared_ptr internals; + + public: + LazyVec() noexcept : internals(std::make_shared()) + { + } + LazyVec(std::vector values) noexcept + : internals(std::make_shared(std::move(values))) + { + } + LazyVec(std::initializer_list values) noexcept + : internals(std::make_shared(values)) + { + } + template ::cur)> + LazyVec(Iterator begin, Sentinel end) noexcept + : internals(std::make_shared>( + std::move(begin), std::move(end))) + { + } + template ::container)> + LazyVec(Container container) noexcept + : internals(std::make_shared>( + std::move(container))) + { + } + template ::fn)> + LazyVec(Fn fn) noexcept + : internals(std::make_shared>(std::move(fn))) + { + } + + private: + static bool resolve_at(const std::shared_ptr &internals, + std::size_t index) noexcept + { + while (index >= internals->resolved_values.size()) + { + switch (internals->resolve_more()) + { + case ResolveResult::Finished: + goto end; + case ResolveResult::MoreToResolve: + continue; + } + } + end: + return index < internals->resolved_values.size(); + } + + public: + bool resolve_at(std::size_t index) noexcept + { + return resolve_at(internals, index); + } + const T &at(std::size_t index) noexcept + { + if (!resolve_at(index)) + { + assert(!"index out of bounds"); + std::terminate(); + } + return internals->resolved_values[index]; + } + const T &operator[](std::size_t index) noexcept + { + return at(index); + } + std::size_t size_lower_bound() noexcept + { + return internals->resolved_values.size(); + } + ResolveResult resolve_more() + { + return internals->resolve_more(); + } + const std::vector &resolve() + { + while (true) + { + switch (resolve_more()) + { + case ResolveResult::Finished: + return internals->resolved_values; + case ResolveResult::MoreToResolve: + continue; + } + } + } + class const_iterator final + { + friend class LazyVec; + + private: + std::shared_ptr internals; + std::size_t index = 0; + bool resolve() const noexcept + { + assert(internals); + if (!internals) + { + std::terminate(); + } + return resolve_at(internals, index); + } + explicit const_iterator( + const std::shared_ptr &internals) noexcept + : internals(internals) + { + } + + public: + using difference_type = std::ptrdiff_t; + using value_type = T; + using pointer = const T *; + using reference = const T &; + using iterator_category = std::forward_iterator_tag; + + const_iterator() noexcept = default; + const_iterator &operator++() + { + assert(internals); + ++index; + return *this; + } + const_iterator operator++(int) + { + auto retval = *this; + operator++(); + return retval; + } + pointer operator->() const noexcept + { + if (at_end()) + { + assert(!"tried to dereference an end() iterator"); + std::terminate(); + } + return std::addressof(internals->resolved_values[index]); + } + reference operator*() const noexcept + { + return *operator->(); + } + bool at_end() const noexcept + { + return !resolve(); + } + bool operator==(const const_iterator &rhs) const noexcept + { + if (rhs.internals) + { + if (internals) + { + assert(internals == rhs.internals); + return index == rhs.index; + } + return rhs.at_end(); + } + if (internals) + { + return at_end(); + } + return true; + } + bool operator!=(const const_iterator &rhs) const noexcept + { + return !operator==(rhs); + } + }; + const_iterator begin() const noexcept + { + return const_iterator(internals); + } + const_iterator cbegin() const noexcept + { + return const_iterator(internals); + } + const_iterator end() const noexcept + { + return const_iterator(); + } + const_iterator cend() const noexcept + { + return const_iterator(); + } + template LazyVec filter_map(Fn &&fn) noexcept + { + struct FilterMapper final + { + const_iterator iter; + Fn fn; + std::optional operator()() noexcept + { + while (iter != const_iterator()) + { + if (std::optional retval = fn(*iter++)) + { + return retval; + } + } + return std::nullopt; + } + }; + return LazyVec(FilterMapper{.iter = begin(), .fn = std::move(fn)}); + } + template LazyVec map(Fn fn) noexcept + { + return filter_map( + [fn](const T &value) -> std::optional { return fn(value); }); + } + template LazyVec filter(Fn fn) noexcept + { + return filter_map([fn](const T &value) -> std::optional { + if (fn(value)) + return value; + return std::nullopt; + }); + } +}; -using JsonString = std::string; -using JsonFloat = double; -using JsonNull = std::nullptr_t; -using JsonArray = std::vector; -using JsonMap = std::vector>; +struct JsonValue; struct JsonValue final { - std::variant, - std::unique_ptr> - value; + using String = std::string; + using Number = double; + using Object = LazyVec>; + using Array = LazyVec; + using Bool = bool; + using Null = std::nullptr_t; + std::variant value; constexpr JsonValue() noexcept : value(nullptr) { } - constexpr JsonValue(JsonNull) noexcept : value(nullptr) + JsonValue(String value) noexcept : value(std::move(value)) + { + } + JsonValue(const char *value) noexcept : value(std::string(value)) { } - constexpr JsonValue(JsonFloat value) noexcept : value(value) + JsonValue(std::string_view value) noexcept : value(std::string(value)) { } - JsonValue(JsonString value) noexcept : value(std::move(value)) + constexpr JsonValue(Number value) noexcept : value(value) { } - JsonValue(std::unique_ptr value) noexcept - : value(std::move(value)) + JsonValue(Object value) noexcept : value(std::move(value)) { } - JsonValue(JsonArray value) noexcept - : value(std::make_unique(std::move(value))) + JsonValue(Array value) noexcept : value(std::move(value)) { } - JsonValue(std::unique_ptr value) noexcept - : value(std::move(value)) + constexpr JsonValue(Bool value) noexcept : value(value) { } - JsonValue(JsonMap value) noexcept - : value(std::make_unique(std::move(value))) + constexpr JsonValue(Null) noexcept : value(nullptr) + { + } + constexpr JsonValue(char) noexcept = delete; + constexpr JsonValue(char16_t) noexcept = delete; + constexpr JsonValue(char32_t) noexcept = delete; +#define JSON_VALUE_NUM(T) \ + constexpr JsonValue(T value) noexcept \ + : JsonValue(static_cast(value)) \ + { \ + } + JSON_VALUE_NUM(float) + JSON_VALUE_NUM(long double) + JSON_VALUE_NUM(unsigned char) + JSON_VALUE_NUM(signed char) + JSON_VALUE_NUM(unsigned short) + JSON_VALUE_NUM(short) + JSON_VALUE_NUM(unsigned int) + JSON_VALUE_NUM(int) + JSON_VALUE_NUM(unsigned long) + JSON_VALUE_NUM(long) + JSON_VALUE_NUM(unsigned long long) + JSON_VALUE_NUM(long long) +#undef JSON_VALUE_NUM + template ()))> + constexpr JsonValue(std::optional value) noexcept : value(nullptr) + { + if (value) + { + *this = JsonValue(std::move(*value)); + } + } + template ()))> + JsonValue(LazyVec value) noexcept + : value(value.template map( + [](const T &value) { return JsonValue(value); })) + { + } + template ()))> + JsonValue(std::vector value) noexcept + : JsonValue(LazyVec(std::move(value))) { } /// decode a JsonString from WTF-8 to the encoding logically used in JSON: @@ -198,188 +608,249 @@ struct JsonValue final } } }; - template - void write(WriteStringView &&write_fn) const + + private: + template struct Visitor final { - struct Visitor final + WriteStringView &write_fn; + bool pretty; + std::size_t indent = 0; + void write(std::string_view str) { - WriteStringView &write_fn; - void write(std::string_view str) - { - write_fn(str); - } - void write(char ch) + write_fn(str); + } + void write(char ch) + { + write_fn(std::string_view(&ch, 1)); + } + void write_indent() + { + for (std::size_t i = 0; i < indent; i++) { - write_fn(std::string_view(&ch, 1)); + write(" "); } - void operator()(const JsonString &value) + } + void operator()(const String &value) + { + write('\"'); + JsonStringDecoder decoder(value); + while (auto value_opt = decoder.next()) { - write('\"'); - JsonStringDecoder decoder(value); - while (auto value_opt = decoder.next()) + std::uint16_t value = *value_opt; + switch (value) { - 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) { - 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 + } + else + { + static constexpr char hex_digits[] = + "0123456789ABCDEF"; + write("\\u"); + for (int i = 0; i < 4; i++) { - static constexpr char hex_digits[] = - "0123456789ABCDEF"; - write("\\u"); - for (int i = 0; i < 4; i++) - { - write(hex_digits[value >> 12]); - value <<= 4; - } + write(hex_digits[value >> 12]); + value <<= 4; } - break; } + break; } - write('"'); } - void operator()(JsonFloat value) + write('"'); + } + void operator()(Number value) + { + if (std::isnan(value)) + { + write("NaN"); + return; + } + if (std::signbit(value)) { - if (std::isnan(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) { - write("NaN"); - return; + result = std::snprintf(&buf[0], buf.size(), "%1.*e", prec, + value); } - if (std::signbit(value)) + else { - write('-'); - value = -value; + result = std::snprintf(&buf[0], buf.size(), "%1.*f", prec, + value); } - if (std::isinf(value)) + if (result <= 0) + return false; + double parsed_value = std::strtod(&buf[0], nullptr); + if (parsed_value != value) { - write("Infinity"); - return; + // not precise enough + return false; } - if (value == 0) + 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)) { - write("0"); - return; - } - using Buf = std::array; - auto try_format = [&](Buf &buf, bool e_format, - int prec) -> bool { - int result; - if (e_format) + std::string_view str(&buf[0]); + if (!final || str.size() < final->size()) { - result = std::snprintf(&buf[0], buf.size(), "%1.*e", - prec, value); + final_buf = buf; + final = std::string_view(&final_buf[0], str.size()); + end_prec = prec + 3; } - 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++) + } + if (try_format(buf, false, prec)) { - Buf buf; - if (try_format(buf, true, prec)) + std::string_view str(&buf[0]); + if (!final || str.size() < final->size()) { - 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; - } + 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) + if (final_buf[0] == '.') { - write("null"); + write('0'); } - void operator()(const std::unique_ptr &value) + write(*final); + } + void operator()(Bool value) + { + write(value ? "true" : "false"); + } + void operator()(Null) + { + write("null"); + } + template + void write_container_inner(const LazyVec &value, Fn fn) + { + indent++; + std::string_view sep{}; + bool any = false; + for (auto &v : value) { - write('['); - std::string_view sep{}; - for (auto &i : *value) + any = true; + write(sep); + if (pretty) { - write(sep); - sep = ","; - std::visit(*this, i.value); + write('\n'); + write_indent(); } - write(']'); + sep = ","; + fn(v); } - void operator()(const std::unique_ptr &value) + indent--; + if (pretty && any) { - write('{'); - std::string_view sep{}; - for (auto &[k, v] : *value) - { - write(sep); - sep = ","; - operator()(k); - write(':'); - std::visit(*this, v.value); - } - write('}'); + write('\n'); + write_indent(); } - }; - std::visit(Visitor{.write_fn = write_fn}, value); + } + void operator()(const Array &value) + { + write('['); + write_container_inner(value, [&](const JsonValue &value) { + std::visit(*this, value.value); + }); + write(']'); + } + void operator()(const Object &value) + { + write('{'); + write_container_inner( + value, [&](const std::pair &value) { + operator()(value.first); + write(':'); + if (pretty) + { + write(' '); + } + std::visit(*this, value.second.value); + }); + write('}'); + } + }; + + public: + template + void write(WriteStringView &&write_fn, bool pretty = false) const + { + std::visit( + Visitor{ + .write_fn = write_fn, + .pretty = pretty, + }, + value); } + struct PrettyJsonValue; + PrettyJsonValue pretty(bool pretty = true) const noexcept; friend std::ostream &operator<<(std::ostream &os, const JsonValue &self) { self.write([&](std::string_view str) { os << str; }); return os; } }; + +struct JsonValue::PrettyJsonValue final +{ + JsonValue value; + bool pretty; + friend std::ostream &operator<<(std::ostream &os, + const PrettyJsonValue &self) + { + self.value.write([&](std::string_view str) { os << str; }, + self.pretty); + return os; + } +}; + +inline auto JsonValue::pretty(bool pretty) const noexcept -> PrettyJsonValue +{ + return {.value = *this, .pretty = pretty}; +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 4944a0d..f9bde51 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,6 +2,7 @@ #include "harness.h" #include "json.h" #include +#include #include #include #include @@ -401,6 +402,7 @@ int main(int, char **argv) { Config config{}; std::optional> enabled_benchmarks; + bool json_pretty = false; Options options{ Option{ .short_name = 'h', @@ -424,7 +426,8 @@ int main(int, char **argv) .value_kind = OptionValueKind::Required, .parse_value = [&](OptionsParser &parser, auto value) { - parser.parse_int(value, config.iteration_count); + parser.parse_int(value, config.iteration_count, true, + {.min_value = 1}); }}, Option{.long_name = "log2-mem-loc-count", .description = @@ -468,6 +471,14 @@ int main(int, char **argv) .description = "Write the output in JSON format", .value_kind = OptionValueKind::None, .parse_value = [&](auto &, auto) { config.use_json = true; }}, + Option{.long_name = "json-pretty", + .description = "Write the output in pretty JSON format", + .value_kind = OptionValueKind::None, + .parse_value = + [&](auto &, auto) { + config.use_json = true; + json_pretty = true; + }}, }; OptionsParser parser(options, argv); auto args = parser.parse(); @@ -475,7 +486,7 @@ int main(int, char **argv) { parser.help_and_exit("unexpected argument"); } - auto benchmarks = all_benchmarks(config); + auto benchmarks = LazyVec(all_benchmarks(config)); if (enabled_benchmarks) { enabled_benchmarks->erase(""); @@ -485,12 +496,12 @@ int main(int, char **argv) { if (config.use_json) { - JsonArray names; - for (auto &benchmark : benchmarks) - { - names.push_back(benchmark.name()); - } - std::cout << JsonValue(std::move(names)) << std::endl; + std::cout << JsonValue(benchmarks.map( + [](const Benchmark &benchmark) { + return benchmark.name(); + })) + .pretty(json_pretty) + << std::endl; } else { @@ -517,15 +528,34 @@ int main(int, char **argv) } } auto thread_cache = BenchHarnessBase::get_thread_cache(); - for (auto &benchmark : benchmarks) + auto benchmark_results = + benchmarks.filter_map>( + [&](const Benchmark &benchmark) + -> std::optional> { + if (enabled_benchmarks && + !enabled_benchmarks->count(benchmark.name())) + { + return std::nullopt; + } + std::cout.flush(); + return benchmark.run(config); + }); + if (config.use_json) + { + std::cout << JsonValue(benchmark_results.map( + [](const std::shared_ptr + &benchmark_results) -> JsonValue { + return *benchmark_results; + })) + .pretty(json_pretty); + } + else { - // FIXME: change output to use JSON when selected - if (enabled_benchmarks && !enabled_benchmarks->count(benchmark.name())) + for (const std::shared_ptr &benchmark_results : + benchmark_results) { - continue; + benchmark_results->print(); } - std::cout << "Running: " << benchmark.name() << std::endl; - benchmark.run(config); } std::cout << std::endl; return 0; -- 2.30.2