diff --git a/test/boost/pretty_printers_test.cc b/test/boost/pretty_printers_test.cc index aae4200d1b..5bd2260f01 100644 --- a/test/boost/pretty_printers_test.cc +++ b/test/boost/pretty_printers_test.cc @@ -8,6 +8,7 @@ #define BOOST_TEST_MODULE utils +#include #include #include #include "utils/pretty_printers.hh" @@ -18,6 +19,7 @@ BOOST_AUTO_TEST_CASE(test_print_data_size) { std::string_view formatted; } sizes[] = { {0ULL, "0 bytes"}, + {1ULL, "1 byte"}, {42ULL, "42 bytes"}, {10'000ULL, "10kB"}, {10'000'000ULL, "10MB"}, @@ -31,6 +33,10 @@ BOOST_AUTO_TEST_CASE(test_print_data_size) { out << utils::pretty_printed_data_size{n}; auto actual = out.str(); BOOST_CHECK_EQUAL(actual, expected); + + std::string s; + fmt::format_to(std::back_inserter(s), "{}", utils::pretty_printed_data_size{n}); + BOOST_CHECK_EQUAL(s, expected); } } @@ -52,5 +58,9 @@ BOOST_AUTO_TEST_CASE(test_print_throughput) { out << utils::pretty_printed_throughput{n, std::chrono::duration(seconds)}; auto actual = out.str(); BOOST_CHECK_EQUAL(actual, expected); + + std::string s; + fmt::format_to(std::back_inserter(s), "{}", utils::pretty_printed_throughput{n, std::chrono::duration(seconds)}); + BOOST_CHECK_EQUAL(s, expected); } } diff --git a/utils/pretty_printers.cc b/utils/pretty_printers.cc index 1addcd86b8..71b5fe2733 100644 --- a/utils/pretty_printers.cc +++ b/utils/pretty_printers.cc @@ -7,29 +7,91 @@ */ #include "pretty_printers.hh" +#include +#include + +template +static constexpr std::tuple +do_format(size_t n, Suffixes suffixes, unsigned scale, bool bytes) { + size_t factor = n; + const char* suffix = ""; + for (auto next_suffix : suffixes) { + size_t next_factor = factor / scale; + if (next_factor == 0) { + break; + } + factor = next_factor; + suffix = next_suffix; + } + if (!bytes) { + return {factor, suffix, ""}; + } + if (factor == n) { + if (n == 1) { + return {factor, suffix, " byte"}; + } else { + return {factor, suffix, " bytes"}; + } + } else { + return {factor, suffix, "B"}; + } +} + +template +auto fmt::formatter::format(utils::pretty_printed_data_size data_size, + FormatContext& ctx) const -> decltype(ctx.out()) { + if (_prefix == prefix_type::IEC) { + // ISO/IEC units + static constexpr auto suffixes = {"Ki", "Mi", "Gi", "Ti", "Pi"}; + auto [n, suffix, bytes] = do_format(data_size._size, suffixes, 1024, _bytes); + return fmt::format_to(ctx.out(), "{}{}{}", n, suffix, bytes); + } else { + // SI units + static constexpr auto suffixes = {"k", "M", "G", "T", "P"}; + auto [n, suffix, bytes] = do_format(data_size._size, suffixes, 1000, _bytes); + return fmt::format_to(ctx.out(), "{}{}{}", n, suffix, bytes); + } +} + +template +auto fmt::formatter::format( + utils::pretty_printed_data_size, + fmt::format_context& ctx) const + -> decltype(ctx.out()); +template +auto fmt::formatter::format, char>>( + utils::pretty_printed_data_size, + fmt::basic_format_context, char>& ctx) const + -> decltype(ctx.out()); + +template +auto fmt::formatter::format(const utils::pretty_printed_throughput& tp, + FormatContext& ctx) const -> decltype(ctx.out()) { + uint64_t throughput = tp._duration.count() > 0 ? tp._size / tp._duration.count() : 0; + auto out = size_formatter::format(utils::pretty_printed_data_size{throughput}, ctx); + return fmt::format_to(out, "{}", "/s"); +} + +template +auto fmt::formatter::format( + const utils::pretty_printed_throughput&, + fmt::format_context& ctx) const + -> decltype(ctx.out()); +template +auto fmt::formatter::format, char>>( + const utils::pretty_printed_throughput&, + fmt::basic_format_context, char>& ctx) const + -> decltype(ctx.out()); namespace utils { std::ostream& operator<<(std::ostream& os, pretty_printed_data_size data) { - static constexpr const char * suffixes[] = {" bytes", "kB", "MB", "GB", "TB", "PB"}; - - const char* suffix = nullptr; - uint64_t size = data._size; - uint64_t next_size = size; - for (auto s : suffixes) { - suffix = s; - size = next_size; - next_size = size / 1000; - if (next_size == 0) { - break; - } - } - return os << size << suffix; + fmt::print(os, "{}", data); + return os; } std::ostream& operator<<(std::ostream& os, pretty_printed_throughput tp) { - uint64_t throughput = tp._duration.count() > 0 ? tp._size / tp._duration.count() : 0; - os << pretty_printed_data_size(throughput) << "/s"; + fmt::print(os, "{}", tp); return os; } diff --git a/utils/pretty_printers.hh b/utils/pretty_printers.hh index e90e1c5164..0bf00cafe3 100644 --- a/utils/pretty_printers.hh +++ b/utils/pretty_printers.hh @@ -10,6 +10,7 @@ #include #include +#include namespace utils { @@ -19,6 +20,7 @@ public: pretty_printed_data_size(uint64_t size) : _size(size) {} friend std::ostream& operator<<(std::ostream&, pretty_printed_data_size); + friend fmt::formatter; }; class pretty_printed_throughput { @@ -28,6 +30,63 @@ public: pretty_printed_throughput(uint64_t size, std::chrono::duration dur) : _size(size), _duration(std::move(dur)) {} friend std::ostream& operator<<(std::ostream&, pretty_printed_throughput); + friend fmt::formatter; }; + } + +// print data_size using IEC or SI binary prefix annotation with optional "B" +// or " bytes" unit postfix. +// +// usage: +// fmt::print("{}", 10'024); // prints "10kB", using SI and add the "B" unit +// // postfix by default +// fmt::print("{:i}", 42); // prints "42 bytes" +// fmt::print("{:ib}", 10'024); // prints "10Ki", IEC unit is used, without +// // the " bytes" or "B" unit +// fmt::print("{:s}", 10); // prints "10 bytes", SI unit is used +// fmt::print("{:sb}", 10'000); // prints "10k", SI unit is used, without +// // the unit postfix +template <> +struct fmt::formatter { + enum class prefix_type { + SI, + IEC, + }; + prefix_type _prefix = prefix_type::SI; + bool _bytes = true; + constexpr auto parse(format_parse_context& ctx) { + auto it = ctx.begin(); + auto end = ctx.end(); + if (it != end) { + if (*it == 's') { + _prefix = prefix_type::SI; + ++it; + } else if (*it == 'i') { + _prefix = prefix_type::IEC; + ++it; + } + if (*it == 'b') { + _bytes = false; + ++it; + } + } + if (it != end && *it != '}') { + ctx.on_error("invalid format"); + } + return it; + } + template + auto format(utils::pretty_printed_data_size, FormatContext& ctx) const -> decltype(ctx.out()); +}; + +template <> +struct fmt::formatter + : private fmt::formatter { + using size_formatter = fmt::formatter; +public: + using size_formatter::parse; + template + auto format(const utils::pretty_printed_throughput&, FormatContext& ctx) const -> decltype(ctx.out()); +};