#ifndef __HAYAI_JSONOUTPUTTER #define __HAYAI_JSONOUTPUTTER #include #include #include "hayai_outputter.hpp" #define JSON_OBJECT_BEGIN "{" #define JSON_OBJECT_END "}" #define JSON_ARRAY_BEGIN "[" #define JSON_ARRAY_END "]" #define JSON_STRING_BEGIN "\"" #define JSON_STRING_END "\"" #define JSON_NAME_SEPARATOR ":" #define JSON_VALUE_SEPARATOR "," #define JSON_TRUE "true" #define JSON_FALSE "false" namespace hayai { /// JSON outputter. /// Outputs the result of benchmarks in JSON format with the following /// structure: /// /// { /// "format_version": 1, /// "benchmarks": [{ /// "fixture": "DeliveryMan", /// "name": "DeliverPackage", /// "parameters": { /// "declaration": "std::size_t distance", /// "value": "1" /// }, /// "iterations_per_run": 10, /// "disabled": false, /// "runs": [{ /// "duration": 3801.889831 /// }, ..] /// }, { /// "fixture": "DeliveryMan", /// "name": "DisabledTest", /// "iterations_per_run": 10, /// "disabled": true /// }, ..] /// } /// /// All durations are represented as milliseconds. class JsonOutputter : public Outputter { public: /// Initialize JSON outputter. /// @param stream Output stream. Must exist for the entire duration of /// the outputter's use. JsonOutputter(std::ostream& stream) : _stream(stream), _firstTest(true) { } virtual void Begin(const std::size_t& enabledCount, const std::size_t& disabledCount) { (void)enabledCount; (void)disabledCount; _stream << JSON_OBJECT_BEGIN JSON_STRING_BEGIN "format_version" JSON_STRING_END JSON_NAME_SEPARATOR "1" JSON_VALUE_SEPARATOR JSON_STRING_BEGIN "benchmarks" JSON_STRING_END JSON_NAME_SEPARATOR JSON_ARRAY_BEGIN; } virtual void End(const std::size_t& executedCount, const std::size_t& disabledCount) { (void)executedCount; (void)disabledCount; _stream << JSON_ARRAY_END JSON_OBJECT_END; } virtual void BeginTest(const std::string& fixtureName, const std::string& testName, const TestParametersDescriptor& parameters, const std::size_t& runsCount, const std::size_t& iterationsCount) { BeginTestObject(fixtureName, testName, parameters, runsCount, iterationsCount, false); } virtual void SkipDisabledTest(const std::string& fixtureName, const std::string& testName, const TestParametersDescriptor& parameters, const std::size_t& runsCount, const std::size_t& iterationsCount) { BeginTestObject(fixtureName, testName, parameters, runsCount, iterationsCount, true); EndTestObject(); } virtual void EndTest(const std::string& fixtureName, const std::string& testName, const TestParametersDescriptor& parameters, const TestResult& result) { (void)fixtureName; (void)testName; (void)parameters; _stream << JSON_VALUE_SEPARATOR JSON_STRING_BEGIN "runs" JSON_STRING_END JSON_NAME_SEPARATOR JSON_ARRAY_BEGIN; const std::vector& runTimes = result.RunTimes(); for (std::vector::const_iterator it = runTimes.begin(); it != runTimes.end(); ++it) { if (it != runTimes.begin()) _stream << JSON_VALUE_SEPARATOR; _stream << JSON_OBJECT_BEGIN JSON_STRING_BEGIN "duration" JSON_STRING_END JSON_NAME_SEPARATOR << std::fixed << std::setprecision(6) << (double(*it) / 1000000.0) << JSON_OBJECT_END; } _stream << JSON_ARRAY_END; WriteDoubleProperty("mean", result.RunTimeAverage()); WriteDoubleProperty("std_dev", result.RunTimeStdDev()); WriteDoubleProperty("median", result.RunTimeMedian()); WriteDoubleProperty("quartile_1", result.RunTimeQuartile1()); WriteDoubleProperty("quartile_3", result.RunTimeQuartile3()); EndTestObject(); } private: void BeginTestObject(const std::string& fixtureName, const std::string& testName, const TestParametersDescriptor& parameters, const std::size_t& runsCount, const std::size_t& iterationsCount, bool disabled) { (void)runsCount; if (_firstTest) _firstTest = false; else _stream << JSON_VALUE_SEPARATOR; _stream << JSON_OBJECT_BEGIN JSON_STRING_BEGIN "fixture" JSON_STRING_END JSON_NAME_SEPARATOR; WriteString(fixtureName); _stream << JSON_VALUE_SEPARATOR JSON_STRING_BEGIN "name" JSON_STRING_END JSON_NAME_SEPARATOR; WriteString(testName); _stream << JSON_VALUE_SEPARATOR; const std::vector& descs = parameters.Parameters(); if (!descs.empty()) { _stream << JSON_STRING_BEGIN "parameters" JSON_STRING_END JSON_NAME_SEPARATOR JSON_ARRAY_BEGIN; for (std::size_t i = 0; i < descs.size(); ++i) { if (i) _stream << JSON_VALUE_SEPARATOR; const TestParameterDescriptor& desc = descs[i]; _stream << JSON_OBJECT_BEGIN JSON_STRING_BEGIN "declaration" JSON_STRING_END JSON_NAME_SEPARATOR; WriteString(desc.Declaration); _stream << JSON_VALUE_SEPARATOR JSON_STRING_BEGIN "value" JSON_STRING_END JSON_NAME_SEPARATOR; WriteString(desc.Value); _stream << JSON_OBJECT_END; } _stream << JSON_ARRAY_END JSON_VALUE_SEPARATOR; } _stream << JSON_STRING_BEGIN "iterations_per_run" JSON_STRING_END JSON_NAME_SEPARATOR << iterationsCount << JSON_VALUE_SEPARATOR JSON_STRING_BEGIN "disabled" JSON_STRING_END JSON_NAME_SEPARATOR << (disabled ? JSON_TRUE : JSON_FALSE); } inline void EndTestObject() { _stream << JSON_OBJECT_END; } /// Write an escaped string. /// The escaping is currently very rudimentary and assumes that names, /// parameters etc. are ASCII. /// /// @param str String to write. void WriteString(const std::string& str) { _stream << JSON_STRING_BEGIN; std::string::const_iterator it = str.begin(); while (it != str.end()) { char c = *it++; switch (c) { case '\\': case '"': case '/': _stream << "\\" << c; break; case '\b': _stream << "\\b"; break; case '\f': _stream << "\\f"; break; case '\n': _stream << "\\n"; break; case '\r': _stream << "\\r"; break; case '\t': _stream << "\\t"; break; default: _stream << c; break; } } _stream << JSON_STRING_END; } /// Write a property with a double value. /// @param key Property key. /// @param value Property value. void WriteDoubleProperty(const std::string& key, const double value) { _stream << JSON_VALUE_SEPARATOR << JSON_STRING_BEGIN << key << JSON_STRING_END << JSON_NAME_SEPARATOR << std::fixed << std::setprecision(6) << (value / 1000000.0); } std::ostream& _stream; bool _firstTest; }; } #undef JSON_OBJECT_BEGIN #undef JSON_OBJECT_END #undef JSON_ARRAY_BEGIN #undef JSON_ARRAY_END #undef JSON_STRING_BEGIN #undef JSON_STRING_END #undef JSON_NAME_SEPARATOR #undef JSON_VALUE_SEPARATOR #undef JSON_TRUE #undef JSON_FALSE #endif