311 lines
11 KiB
C++
311 lines
11 KiB
C++
#include "perftest_logger.h"
|
|
|
|
#include <gear_core/eal_utils/private/itextstream.h>
|
|
#include <gear_testing/core/stddef.h>
|
|
|
|
#if G4_PLATFORM != G4_PLATFORM_PS3_SPU
|
|
|
|
#define APP_LEVEL 1
|
|
#define MODULE_LEVEL APP_LEVEL+1
|
|
#define CLASS_LEVEL MODULE_LEVEL+1
|
|
#define TEST_LEVEL CLASS_LEVEL+1
|
|
|
|
#define NB_MAX_ATTRIBUTES 2
|
|
#define MAX_ATTRIBUTE_CHAR_SIZE 256
|
|
#define PERFTEST_LOGGER_XMLFILENAME "perftests_results.xml"
|
|
|
|
char s_attributes[NB_MAX_ATTRIBUTES*2][MAX_ATTRIBUTE_CHAR_SIZE];
|
|
|
|
namespace G4
|
|
{
|
|
G4_IMPLEMENT_DYNAMIC_SINGLETON(PerfTestLogger);
|
|
|
|
|
|
// Set attribute name and value for the nth attribute in the static char * array
|
|
static void SetAttribute(const char *name, const char *value, const int index)
|
|
{
|
|
#if G4_PLATFORM_MS
|
|
strcpy_s(s_attributes[2*index], MAX_ATTRIBUTE_CHAR_SIZE, name);
|
|
strcpy_s(s_attributes[2*index+1], MAX_ATTRIBUTE_CHAR_SIZE, value);
|
|
#else
|
|
strcpy(s_attributes[2*index], name);
|
|
strcpy(s_attributes[2*index+1], value);
|
|
#endif
|
|
}
|
|
|
|
void PerfTestLogger::SingletonConstructor()
|
|
{
|
|
m_currentFunction.Clear();
|
|
// initialize xml tree
|
|
m_xmlDoc.InsertFirstChild( m_xmlDoc.NewDeclaration() );
|
|
m_xmlDoc.SetBOM( true );
|
|
m_xmlDoc.InsertEndChild(m_xmlDoc.NewComment("PerfTest results are calculated in usec."));
|
|
|
|
// create root
|
|
tinyxml2::XMLNode *perfRootNode = m_xmlDoc.NewElement("PerformanceTestsRoot");
|
|
m_xmlDoc.InsertEndChild(perfRootNode);
|
|
m_xmlTree.push_back(perfRootNode);
|
|
}
|
|
|
|
void PerfTestLogger::SingletonDestructor()
|
|
{
|
|
// check if we need to save the xml file
|
|
if (m_xmlTree.size() > 2)
|
|
{
|
|
tinyxml2::XMLPrinter lXmlPrinter;
|
|
m_xmlDoc.Print(&lXmlPrinter);
|
|
G4::BaseErrorCode err;
|
|
G4::EalFile outputXmlFile(PERFTEST_LOGGER_XMLFILENAME, G4::EalFile::MODE_CREATE|G4::EalFile::MODE_WRITE, G4_TEST_EALID, err);
|
|
if(err == G4::ErrorGearCore::Ok)
|
|
{
|
|
G4::U32 writtenSize = 0;
|
|
err = outputXmlFile.Write(lXmlPrinter.CStr(), lXmlPrinter.CStrSize()-1, writtenSize);
|
|
if(err == G4::ErrorGearCore::Ok)
|
|
{
|
|
outputXmlFile.Close();
|
|
|
|
// display xml file to console
|
|
G4::U8 OBaseStreamBuffer[1024];
|
|
Private::ITextStream xmlStream(PERFTEST_LOGGER_XMLFILENAME, OBaseStreamBuffer, 1024);
|
|
if(xmlStream.IsValid())
|
|
{
|
|
G4::Char TextBuffer[256];
|
|
while(xmlStream.ReadLine(TextBuffer, 256) != 0)
|
|
{
|
|
::testing::internal::UniversalPrintf("%s\n", TextBuffer);
|
|
}
|
|
xmlStream.CloseFile();
|
|
}
|
|
else
|
|
{
|
|
::testing::internal::UniversalPrintf("ERROR: Could not open performance test XML log file \"%s\" for reading\n", PERFTEST_LOGGER_XMLFILENAME);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
::testing::internal::UniversalPrintf("ERROR: Could not write in performance test XML log file \"%s\"\n", PERFTEST_LOGGER_XMLFILENAME);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
::testing::internal::UniversalPrintf("ERROR: Could not open performance test XML log file \"%s\" for writing\n", PERFTEST_LOGGER_XMLFILENAME);
|
|
}
|
|
}
|
|
m_xmlTree.clear();
|
|
m_xmlDoc.Clear();
|
|
m_currentFunction.Clear();
|
|
}
|
|
|
|
Bool PerfTestLogger::AddXmlNode( unsigned char a_level, const char* name, const unsigned int nbAttributes )
|
|
{
|
|
const unsigned char currentHeight = static_cast<unsigned char>(m_xmlTree.size());
|
|
G4_EAL_ASSERT_MSG(EAL_ID_TG_GEAR_CORE, a_level <= currentHeight, "The max level is %i", currentHeight);
|
|
|
|
if(currentHeight < a_level)
|
|
return False;
|
|
|
|
// Add it to the tree if it does not exist already
|
|
if( currentHeight > a_level )
|
|
{
|
|
// Forward search
|
|
tinyxml2::XMLElement *tmpSibling = (a_level==0)?m_xmlDoc.FirstChildElement():m_xmlTree[a_level-1]->FirstChildElement();
|
|
while(tmpSibling)
|
|
{
|
|
if(tmpSibling->Attribute(s_attributes[0], s_attributes[1]) != 0)
|
|
{
|
|
m_xmlTree[a_level] = tmpSibling;
|
|
return True;
|
|
}
|
|
tmpSibling = tmpSibling->NextSiblingElement();
|
|
}
|
|
}
|
|
// Create the new node
|
|
tinyxml2::XMLElement* node = m_xmlDoc.NewElement(name);
|
|
for(unsigned int i=0; i<nbAttributes; i++)
|
|
node->SetAttribute(s_attributes[2*i], s_attributes[2*i+1]);
|
|
|
|
if(a_level == 0)
|
|
m_xmlDoc.FirstChildElement()->InsertEndChild(node);
|
|
else
|
|
{
|
|
m_xmlTree[a_level-1]->InsertEndChild(node);
|
|
}
|
|
|
|
if( currentHeight == a_level )
|
|
m_xmlTree.push_back(node);
|
|
else
|
|
m_xmlTree[a_level] = node;
|
|
|
|
return True;
|
|
}
|
|
|
|
// This function modifies classname in a way we obtain the same class name for Gear4 container than STL container
|
|
// it parses className ("UnitTest<struct G4::Container<class T<char, struct Tr<char> >, struct A, class B<> > >::SetUpTestCase") to obtain ("container<T>" or "mapcontainer<T,A>")
|
|
void GetTemplateClassname(GearString& className)
|
|
{
|
|
className.Replace("UnitTest<", "");
|
|
className.Replace("TYPE>::SetUpTestCase() [with TYPE = ", "");
|
|
className.Replace("]", "");
|
|
className.Replace("class ", "");
|
|
className.Replace("struct ", "");
|
|
className.Replace("G4::", "");
|
|
className.Replace("std::", "");
|
|
className.Replace(">::SetUpTestCase", "");
|
|
className.Replace("basicstring", "string");
|
|
className.ToLower();
|
|
|
|
// erase everything between <>
|
|
GearString::Iterator itClassName;
|
|
bool parseIsDone = false;
|
|
do
|
|
{
|
|
itClassName = className.Begin();
|
|
G4::S32 bracketCount = 0;
|
|
GearString::Iterator eraseBegin, eraseEnd;
|
|
while(!parseIsDone)
|
|
{
|
|
if (*itClassName == '<')
|
|
{
|
|
++bracketCount;
|
|
if (bracketCount == 2)
|
|
{
|
|
eraseBegin = itClassName;
|
|
}
|
|
}
|
|
else if (*itClassName == '>')
|
|
{
|
|
--bracketCount;
|
|
if (bracketCount == 1)
|
|
{
|
|
eraseEnd = itClassName;
|
|
break;
|
|
}
|
|
}
|
|
++itClassName;
|
|
parseIsDone = (itClassName == className.End());
|
|
}
|
|
if (!parseIsDone)
|
|
{
|
|
className.Erase(eraseBegin, ++eraseEnd);
|
|
}
|
|
} while (!parseIsDone);
|
|
|
|
// if className contains 'map', we let two template arguments, otherwise, we keep only one
|
|
G4::S32 commaIdx = className.Find(',');
|
|
G4::S32 mapIdx = className.Find("map");
|
|
if (mapIdx != GearString::NPOS)
|
|
{
|
|
// keep a second arg
|
|
commaIdx = className.Find(',', ++commaIdx);
|
|
}
|
|
if (commaIdx != GearString::NPOS)
|
|
{
|
|
className.Erase(commaIdx, className.Size() - commaIdx - 1);
|
|
}
|
|
className.Replace(" ", "");
|
|
}
|
|
|
|
/// This class is specific to your own needs. You can change it to set your default app name, module name and class name.
|
|
/// This is useful because these field are mandatory to be able to log a test result.
|
|
Bool PerfTestLogger::SetTestCase(const GearString& a_testCaseName)
|
|
{
|
|
typedef Vector<GearString, EalAllocator<EAL_ID_TG_GEAR_TESTING> > TestKeys;
|
|
TestKeys testKeys;
|
|
|
|
a_testCaseName.Split("_", testKeys);
|
|
TestKeys::const_iterator itKeys = testKeys.cbegin();
|
|
if (!itKeys->EndsWith("perf"))
|
|
return False;
|
|
++itKeys;
|
|
if (*itKeys == "havok") //Havok
|
|
{
|
|
SetAppName("Havok", G4_PLATFORM_ACRONYM);
|
|
SetModuleName("math");
|
|
SetClassName(testKeys[2].CStr());
|
|
}
|
|
else if (*itKeys == "stl")
|
|
{
|
|
SetAppName("STL", G4_PLATFORM_ACRONYM);
|
|
++itKeys;
|
|
}
|
|
else // default application
|
|
{
|
|
SetAppName("gear_core", G4_PLATFORM_ACRONYM);
|
|
}
|
|
SetModuleName(itKeys->CStr());
|
|
++itKeys;
|
|
// class name is either defined following the module name (testKeys[2]) or between <> after UnitTest
|
|
GearString className = *itKeys;
|
|
++itKeys;
|
|
while (!itKeys->StartsWith("UnitTest"))
|
|
{
|
|
className += "_";
|
|
className += *itKeys;
|
|
++itKeys;
|
|
}
|
|
// check if classname is template
|
|
if (itKeys->StartsWith("UnitTest<"))
|
|
{
|
|
// className is between <>
|
|
className = *itKeys;
|
|
while (++itKeys != testKeys.cend())
|
|
{
|
|
className += *itKeys; // we don't add '_' to make the same names between G4 and std
|
|
}
|
|
GetTemplateClassname(className);
|
|
}
|
|
SetClassName(className.CStr());
|
|
return True;
|
|
}
|
|
|
|
Bool PerfTestLogger::SetAppName(const char* a_appName, const char* a_version)
|
|
{
|
|
SetAttribute("name", a_appName, 0);
|
|
SetAttribute("version", a_version, 1);
|
|
return AddXmlNode(APP_LEVEL, "application", 2);
|
|
}
|
|
|
|
Bool PerfTestLogger::SetModuleName(const char* a_module)
|
|
{
|
|
SetAttribute("name", a_module, 0);
|
|
return AddXmlNode(MODULE_LEVEL, "module", 1);
|
|
}
|
|
|
|
Bool PerfTestLogger::SetClassName(const char* a_class)
|
|
{
|
|
SetAttribute("name", a_class, 0);
|
|
return AddXmlNode(CLASS_LEVEL, "class", 1);
|
|
}
|
|
|
|
Bool PerfTestLogger::LogTestName(const GearString& a_function)
|
|
{
|
|
// check if we are ready to add perftest node (level 4)
|
|
static const unsigned char level = TEST_LEVEL;
|
|
const unsigned char currentHeight = static_cast<unsigned char>(m_xmlTree.size());
|
|
G4_EAL_ASSERT_MSG(EAL_ID_TG_GEAR_CORE, !a_function.Empty(), "Function name is mandatory.");
|
|
// This means you are trying to log a test without having set the class name for this test
|
|
G4_EAL_ASSERT_MSG(EAL_ID_TG_GEAR_CORE, currentHeight >= level, "Can't add a node to level %i. Current height is %i. Use AddClassName before", level, currentHeight );
|
|
if (currentHeight < level)
|
|
return False;
|
|
|
|
// set the current function name
|
|
m_currentFunction = a_function;
|
|
return True;
|
|
}
|
|
|
|
Bool PerfTestLogger::LogTestResult(double a_testResult)
|
|
{
|
|
SetAttribute("name", m_currentFunction.CStr(), 0);
|
|
|
|
char buffer[MAX_ATTRIBUTE_CHAR_SIZE];
|
|
#if G4_PLATFORM_MS
|
|
sprintf_s(buffer, MAX_ATTRIBUTE_CHAR_SIZE, "%6.6f", a_testResult);
|
|
#else
|
|
sprintf(buffer, "%6.6f", a_testResult);
|
|
#endif
|
|
SetAttribute("result", buffer, 1);
|
|
return AddXmlNode(TEST_LEVEL, "perftest", 2);
|
|
}
|
|
}
|
|
|
|
#endif // #if G4_PLATFORM != G4_PLATFORM_PS3_SPU
|