Source

GL Profile Suite / tests / util / report.cpp

Full commit

#include <algorithm>
#include <iterator>
#include <stdexcept>
#include <ostream>
#include <boost/container/flat_map.hpp>
#include <boost/variant.hpp>
#include <boost/array.hpp>
#include <boost/foreach.hpp>
#include <boost/range/irange.hpp>
#include <boost/range/iterator_range.hpp>
#include <boost/range/adaptors.hpp>

#include "report.h"

namespace util
{
	using boost::container::flat_map;

	typedef boost::variant<boost::blank, double> Datum;

	namespace
	{
		static const char *g_datumNames[] = {"cpu_time", "gpu_time", "gpu_latency"};

		BOOST_STATIC_ASSERT(sizeof(g_datumNames) / sizeof(g_datumNames[0]) == NUM_TOTAL_DATUM);
	}

	struct DatumHasValue : public boost::static_visitor<bool>
	{
		bool operator()(const boost::blank &) const {return false;}
		template<typename T> bool operator()(const T &) const {return true;}
	};

	struct TestDatumElement
	{
		boost::array<Datum, NUM_TOTAL_DATUM> data;
	};

	class ReportTest
	{
	public:
		ReportTest(const std::string &name, const std::string &description, int numInstances, int numExecutions)
			: m_name(name)
			, m_description(description)
			, m_numInstances(numInstances)
			, m_numExecutions(numExecutions)
		{}

		TestDatumElement &GetDatum(int instance, int execution)
		{
			if(m_data.empty())
				m_data.resize(m_numInstances * m_numExecutions);

			return m_data[instance * m_numExecutions + execution];
		}

		const TestDatumElement &GetDatum(int instance, int execution) const
		{
			//Never call this one first.
			return m_data[instance * m_numExecutions + execution];
		}

		const std::string &GetName() const {return m_name;}
		const std::string &GetDescription() const {return m_description;}

	private:
		std::string m_name;
		std::string m_description;
		int m_numInstances;
		int m_numExecutions;

		std::vector<TestDatumElement> m_data;
	};

	typedef flat_map<std::string, ReportTest> MapReportTest;

	struct ReportData
	{
		int numInstances;
		int numExecutions;

		std::vector<ParamDecl> params;
		std::vector<int> instanceValues;
		MapReportTest tests;
	};

	Report::Report(int numExecutions, const std::vector<int> &instanceValues,
		const std::vector<ParamDecl> &params,
		const std::vector<TestDesc> &tests )
		: m_pData(new ReportData)
	{
		m_pData->numInstances = instanceValues.size() / params.size();
		m_pData->numExecutions = numExecutions;

		m_pData->params.assign(params.begin(), params.end());
		m_pData->instanceValues.assign(instanceValues.begin(), instanceValues.end());

		m_pData->tests.reserve(tests.size());

		for(size_t loop = 0; loop < tests.size(); ++loop)
		{
			MapReportTest::iterator loc = m_pData->tests.find(tests[loop].name);
			if(loc != m_pData->tests.end())
				throw std::runtime_error(std::string(tests[loop].name) + " Already exists in the list of tests.\n");

			m_pData->tests.emplace(std::string(tests[loop].name),
				ReportTest(tests[loop].name, tests[loop].description, m_pData->numInstances, numExecutions));
		}
	}

	Report::~Report() {}

	TestDatumElement &Report::GetDatumElement( const std::string &testName, int instance, int execution )
	{
		MapReportTest::iterator loc = m_pData->tests.find(testName);
		if(loc == m_pData->tests.end())
			throw std::runtime_error(testName + " Unknown test name.\n");

		return loc->second.GetDatum(instance, execution);
	}

	struct StandardWriteDatumVisitor : public boost::static_visitor<>
	{
		StandardWriteDatumVisitor(std::ostream &_stream, TestDatumId _id) : stream(_stream), id(_id) {}

		void operator()(const boost::blank&) const {stream << "WTF!!!!";}
		void operator()(double value) const {stream << value;}

		std::ostream &stream;
		TestDatumId id;
	};

	std::ostream & Report::DisplayWriter( std::ostream &stream ) const
	{
		BOOST_FOREACH(const MapReportTest::value_type &testPair, m_pData->tests)
		{
			stream << "Test: \"" << testPair.first << "\"\n";

			const ReportTest &test = testPair.second;

			BOOST_FOREACH(int instance, boost::irange(0, m_pData->numInstances))
			{
				//Write parameter names.
				stream << "\t";

				BOOST_FOREACH(int paramIx, boost::irange((int)0, (int)m_pData->params.size()))
				{
					stream << m_pData->params[paramIx].name << ": ";
					stream << m_pData->instanceValues[paramIx + instance * m_pData->params.size()];
				}

				stream << "\n";

//				stream << "\tInstance: " << instance << "\n";
				const TestDatumElement &initial = test.GetDatum(instance, 0);

				//Write the actual values.
				BOOST_FOREACH(int datumId, boost::irange(0, (int)NUM_TOTAL_DATUM))
				{
					if(boost::apply_visitor(DatumHasValue(), initial.data[datumId]))
					{
						stream << "\t\tTiming: " << g_datumNames[datumId] << "\n";

						BOOST_FOREACH(int execution, boost::irange(0, m_pData->numExecutions))
						{
							stream << "\t\t\tRun: " << execution << " Value: ";
							boost::apply_visitor(StandardWriteDatumVisitor(stream, TestDatumId(datumId)),
								test.GetDatum(instance, execution).data[datumId]);
							stream << "\n";
						}
					}
				}
			}
		}

		stream << std::flush;
		return stream;
	}

	namespace
	{
		size_t CalcNumEqualsToEscape( const char * val )
		{
			size_t lenVal = strlen(val);

			size_t maxConsecutive = 0;
			size_t currConsecutive = 0;
			BOOST_FOREACH(size_t loop, boost::irange(size_t(0), lenVal))
			{
				if(val[loop] == '=')
				{
					++currConsecutive;
					if(maxConsecutive < currConsecutive)
						maxConsecutive = currConsecutive;
				}
				else
				{
					currConsecutive = 0;
				}
			}

			//The number needed to escape is one greater than the maximum number of
			//consecutive '=' signs.
			return maxConsecutive + 1; 
		}

		std::ostream &OutputRawStringLua(std::ostream &stream, const char *val)
		{
			size_t numEquals = CalcNumEqualsToEscape(val);

			stream << "[" << std::string(numEquals, '=') << "[";
			stream << val;
			stream << "]" << std::string(numEquals, '=') << "]";

			return stream;
		}

		std::ostream &OutputNamedValueLua(std::ostream &stream, int numIndent,
			const char *name, const char *val)
		{
			stream << std::string(numIndent, '\t');
			stream << "[\"" << name << "\"] = ";

			OutputRawStringLua(stream, val);

			stream << ",\n";
			return stream;
		}

		template<typename T>
		std::ostream &OutputNamedValueLua(std::ostream &stream, int numIndent,
			const char *name, const T &val)
		{
			stream << std::string(numIndent, '\t');
			stream << "[\"" << name << "\"] = " << val << ",\n";
			return stream;
		}

	
		struct LuaTable
		{
			LuaTable(std::ostream &_stream, int _numIndent, bool _multiline = true)
				: stream(_stream)
				, numIndent(_numIndent)
				, multiline(_multiline)
			{
				if(multiline)
					stream << std::string(numIndent, '\t') << "{\n";
				else
					stream << "{";
			}

			~LuaTable()
			{
				if(multiline)
					stream << std::string(numIndent, '\t') << "},\n";
				else
					stream << "},\n";
			}

			std::ostream &stream;
			bool multiline;
			int numIndent;
		};

		struct LuaWriteDatumVisitor : public boost::static_visitor<>
		{
			LuaWriteDatumVisitor(std::ostream &_stream) : stream(_stream){}

			void operator()(const boost::blank&) const {stream << "WTF!!!!" << ", ";}
			void operator()(double value) const {stream << value << ", ";}

			std::ostream &stream;
		};

	}

	std::ostream & Report::WriteToLua( std::ostream &stream, const char *suite_name,
		const char *suite_desc, const char *gl_version, const char *gl_vendor,
		const char *gl_renderer ) const
	{
		stream << "return\n{\n";

		OutputNamedValueLua(stream, 1, "name", suite_name);
		OutputNamedValueLua(stream, 1, "description", suite_desc);
		OutputNamedValueLua(stream, 1, "opengl_version", gl_version);
		OutputNamedValueLua(stream, 1, "opengl_vendor", gl_vendor);
		OutputNamedValueLua(stream, 1, "opengl_renderer", gl_renderer);

		BOOST_FOREACH(const MapReportTest::value_type &testPair, m_pData->tests)
		{
			stream << "\t[\"" << testPair.first << "\"] = \n";

			LuaTable testTable(stream, 1);

			const ReportTest &test = testPair.second;

			OutputNamedValueLua(stream, 2, "name", test.GetName().c_str());
			OutputNamedValueLua(stream, 2, "description", test.GetDescription().c_str());

			BOOST_FOREACH(int instance, boost::irange(0, m_pData->numInstances))
			{
				LuaTable instTable(stream, 2);

				//Write parameter names.
				stream << "\t\t\t[params] = \n";
				{
					LuaTable paramTable(stream, 3);
					BOOST_FOREACH(int paramIx, boost::irange((int)0, (int)m_pData->params.size()))
					{
						OutputNamedValueLua(stream, 4, m_pData->params[paramIx].name,
							m_pData->instanceValues[paramIx + instance * m_pData->params.size()]);
					}
				}

				//Write list of data values for each available datum in this test instance.
				const TestDatumElement &initial = test.GetDatum(instance, 0);
				BOOST_FOREACH(int datumId, boost::irange(0, (int)NUM_TOTAL_DATUM))
				{
					if(boost::apply_visitor(DatumHasValue(), initial.data[datumId]))
					{
						stream << "\t\t\t[" << g_datumNames[datumId] << "] = ";
						LuaTable datumTable(stream, 4, false);

						BOOST_FOREACH(int execution, boost::irange(0, m_pData->numExecutions))
						{
							boost::apply_visitor(LuaWriteDatumVisitor(stream),
								test.GetDatum(instance, execution).data[datumId]);
						}
					}
				}

			}
		}

		stream << "}\n" << std::flush;
		return stream;
	}

	TestDatum::TestDatum( Report &report, const std::string &testName, int instance, int execution )
		: data(report.GetDatumElement(testName, instance, execution))
	{}

	void TestDatum::SetValue( TestDatumId eId, double value )
	{
		data.data[eId] = value;
	}

}