Program Listing for File XMLLogger.cu
↰ Return to documentation for file (src/flamegpu/io/XMLLogger.cu
)
#include "flamegpu/io/XMLLogger.h"
#include <sstream>
#include <cstdio>
#include <string>
#include "tinyxml2/tinyxml2.h" // downloaded from https:// github.com/leethomason/tinyxml2, the list of xml parsers : http:// lars.ruoff.free.fr/xmlcpp/
#include "flamegpu/simulation/RunPlan.h"
#include "flamegpu/simulation/LogFrame.h"
namespace flamegpu {
namespace io {
#ifndef XMLCheckResult
#define XMLCheckResult(a_eResult) if (a_eResult != tinyxml2::XML_SUCCESS) { exception::FLAMEGPUException::setLocation(__FILE__, __LINE__);\
switch (a_eResult) { \
case tinyxml2::XML_ERROR_FILE_NOT_FOUND : \
case tinyxml2::XML_ERROR_FILE_COULD_NOT_BE_OPENED : \
THROW exception::InvalidInputFile("TinyXML error: File could not be opened.\n Error code: %d", a_eResult); \
case tinyxml2::XML_ERROR_FILE_READ_ERROR : \
THROW exception::InvalidInputFile("TinyXML error: File could not be read.\n Error code: %d", a_eResult); \
case tinyxml2::XML_ERROR_PARSING_ELEMENT : \
case tinyxml2::XML_ERROR_PARSING_ATTRIBUTE : \
case tinyxml2::XML_ERROR_PARSING_TEXT : \
case tinyxml2::XML_ERROR_PARSING_CDATA : \
case tinyxml2::XML_ERROR_PARSING_COMMENT : \
case tinyxml2::XML_ERROR_PARSING_DECLARATION : \
case tinyxml2::XML_ERROR_PARSING_UNKNOWN : \
case tinyxml2::XML_ERROR_PARSING : \
THROW exception::TinyXMLError("TinyXML error: Error parsing file.\n Error code: %d", a_eResult); \
case tinyxml2::XML_ERROR_EMPTY_DOCUMENT : \
THROW exception::TinyXMLError("TinyXML error: XML_ERROR_EMPTY_DOCUMENT\n Error code: %d", a_eResult); \
case tinyxml2::XML_ERROR_MISMATCHED_ELEMENT : \
THROW exception::TinyXMLError("TinyXML error: XML_ERROR_MISMATCHED_ELEMENT\n Error code: %d", a_eResult); \
case tinyxml2::XML_CAN_NOT_CONVERT_TEXT : \
THROW exception::TinyXMLError("TinyXML error: XML_CAN_NOT_CONVERT_TEXT\n Error code: %d", a_eResult); \
case tinyxml2::XML_NO_TEXT_NODE : \
THROW exception::TinyXMLError("TinyXML error: XML_NO_TEXT_NODE\n Error code: %d", a_eResult); \
case tinyxml2::XML_ELEMENT_DEPTH_EXCEEDED : \
THROW exception::TinyXMLError("TinyXML error: XML_ELEMENT_DEPTH_EXCEEDED\n Error code: %d", a_eResult); \
case tinyxml2::XML_ERROR_COUNT : \
THROW exception::TinyXMLError("TinyXML error: XML_ERROR_COUNT\n Error code: %d", a_eResult); \
case tinyxml2::XML_NO_ATTRIBUTE: \
THROW exception::TinyXMLError("TinyXML error: XML_NO_ATTRIBUTE\n Error code: %d", a_eResult); \
case tinyxml2::XML_WRONG_ATTRIBUTE_TYPE : \
THROW exception::TinyXMLError("TinyXML error: XML_WRONG_ATTRIBUTE_TYPE\n Error code: %d", a_eResult); \
default: \
THROW exception::TinyXMLError("TinyXML error: Unrecognised error code\n Error code: %d", a_eResult); \
} \
}
#endif
XMLLogger::XMLLogger(const std::string &outPath, bool _prettyPrint, bool _truncateFile)
: out_path(outPath)
, prettyPrint(_prettyPrint)
, truncateFile(_truncateFile) { }
void XMLLogger::log(const RunLog &log, const RunPlan &plan, bool logSteps, bool logExit, bool logStepTime, bool logExitTime) const {
logCommon(log, &plan, false, logSteps, logExit, logStepTime, logExitTime);
}
void XMLLogger::log(const RunLog &log, bool logConfig, bool logSteps, bool logExit, bool logStepTime, bool logExitTime) const {
logCommon(log, nullptr, logConfig, logSteps, logExit, logStepTime, logExitTime);
}
void XMLLogger::logCommon(const RunLog &log, const RunPlan *plan, bool doLogConfig, bool doLogSteps, bool doLogExit, bool doLogStepTime, bool doLogExitTime) const {
tinyxml2::XMLDocument doc;
tinyxml2::XMLNode * pRoot = doc.NewElement("log");
doc.InsertFirstChild(pRoot);
// Log config
if (plan) {
pRoot->InsertEndChild(logConfig(doc, *plan));
} else if (doLogConfig) {
pRoot->InsertEndChild(logConfig(doc, log));
}
// Log performance specs
if (doLogStepTime || doLogExitTime) {
pRoot->InsertEndChild(logPerformanceSpecs(doc, log));
}
// Log step log
if (doLogSteps) {
pRoot->InsertEndChild(logSteps(doc, log, doLogStepTime));
}
// Log exit log
if (doLogExit) {
pRoot->InsertEndChild(logExit(doc, log, doLogExitTime));
}
// export
FILE *fptr = fopen(out_path.c_str(), truncateFile ? "w" : "a");
if (fptr == nullptr) {
THROW exception::TinyXMLError("Unable to open file '%s' for writing\n", out_path.c_str());
}
XMLCheckResult(doc.SaveFile(fptr, !prettyPrint));
fwrite("\n", sizeof(char), 1, fptr);
fclose(fptr);
}
tinyxml2::XMLNode *XMLLogger::logConfig(tinyxml2::XMLDocument &doc, const RunLog &log) const {
tinyxml2::XMLElement *pConfigElement = doc.NewElement("config");
{
tinyxml2::XMLElement *pListElement;
pListElement = doc.NewElement("random_seed");
pListElement->SetText(log.getRandomSeed());
pConfigElement->InsertEndChild(pListElement);
}
return pConfigElement;
}
tinyxml2::XMLNode *XMLLogger::logConfig(tinyxml2::XMLDocument &doc, const RunPlan &plan) const {
tinyxml2::XMLElement *pConfigElement = doc.NewElement("config");
{
tinyxml2::XMLElement *pListElement;
// Add static items
pListElement = doc.NewElement("random_seed");
pListElement->SetText(plan.getRandomSimulationSeed());
pConfigElement->InsertEndChild(pListElement);
pListElement = doc.NewElement("steps");
pListElement->SetText(plan.getSteps());
pConfigElement->InsertEndChild(pListElement);
// Add dynamic environment overrides
tinyxml2::XMLElement *pEnvElement = doc.NewElement("environment");
{
for (const auto &prop : plan.property_overrides) {
const EnvironmentData::PropData &env_prop = plan.environment->at(prop.first);
pListElement = doc.NewElement(prop.first.c_str());
writeAny(pListElement, prop.second, env_prop.data.elements);
pEnvElement->InsertEndChild(pListElement);
}
}
pConfigElement->InsertEndChild(pEnvElement);
}
return pConfigElement;
}
tinyxml2::XMLNode* XMLLogger::logPerformanceSpecs(tinyxml2::XMLDocument& doc, const RunLog& log) const {
tinyxml2::XMLElement* pConfigElement = doc.NewElement("performance_specs");
{
tinyxml2::XMLElement* pListElement;
// Add static items
pListElement = doc.NewElement("device_name");
pListElement->SetText(log.getPerformanceSpecs().device_name.c_str());
pConfigElement->InsertEndChild(pListElement);
pListElement = doc.NewElement("device_cc_major");
pListElement->SetText(log.getPerformanceSpecs().device_cc_major);
pConfigElement->InsertEndChild(pListElement);
pListElement = doc.NewElement("device_cc_minor");
pListElement->SetText(log.getPerformanceSpecs().device_cc_minor);
pConfigElement->InsertEndChild(pListElement);
pListElement = doc.NewElement("cuda_version");
pListElement->SetText(log.getPerformanceSpecs().cuda_version);
pConfigElement->InsertEndChild(pListElement);
pListElement = doc.NewElement("seatbelts");
pListElement->SetText(log.getPerformanceSpecs().seatbelts);
pConfigElement->InsertEndChild(pListElement);
pListElement = doc.NewElement("flamegpu_version");
pListElement->SetText(log.getPerformanceSpecs().flamegpu_version.c_str());
pConfigElement->InsertEndChild(pListElement);
}
return pConfigElement;
}
tinyxml2::XMLNode *XMLLogger::logSteps(tinyxml2::XMLDocument &doc, const RunLog &log, bool logTime) const {
tinyxml2::XMLElement *pStepsElement = doc.NewElement("steps");
{
for (const auto &step : log.getStepLog()) {
pStepsElement->InsertEndChild(writeLogFrame(doc, step, logTime));
}
}
return pStepsElement;
}
tinyxml2::XMLNode *XMLLogger::logExit(tinyxml2::XMLDocument &doc, const RunLog &log, bool logTime) const {
return writeLogFrame(doc, log.getExitLog(), logTime);
}
tinyxml2::XMLNode* XMLLogger::writeLogFrame(tinyxml2::XMLDocument& doc, const StepLogFrame& frame, bool logTime) const {
tinyxml2::XMLElement* pFrameElement = doc.NewElement("step");
{
if (logTime) {
tinyxml2::XMLElement* pListElement;
pListElement = doc.NewElement("step_time");
pListElement->SetText(frame.getStepTime());
pFrameElement->InsertEndChild(pListElement);
}
writeCommonLogFrame(doc, pFrameElement, frame);
}
return pFrameElement;
}
tinyxml2::XMLNode* XMLLogger::writeLogFrame(tinyxml2::XMLDocument & doc, const ExitLogFrame & frame, bool logTime) const {
tinyxml2::XMLElement* pFrameElement = doc.NewElement("exit");
{
if (logTime) {
tinyxml2::XMLElement* pListElement;
pListElement = doc.NewElement("rtc_time");
pListElement->SetText(frame.getRTCTime());
pFrameElement->InsertEndChild(pListElement);
pListElement = doc.NewElement("init_time");
pListElement->SetText(frame.getInitTime());
pFrameElement->InsertEndChild(pListElement);
pListElement = doc.NewElement("exit_time");
pListElement->SetText(frame.getExitTime());
pFrameElement->InsertEndChild(pListElement);
pListElement = doc.NewElement("total_time");
pListElement->SetText(frame.getTotalTime());
pFrameElement->InsertEndChild(pListElement);
}
writeCommonLogFrame(doc, pFrameElement, frame);
}
return pFrameElement;
}
void XMLLogger::writeCommonLogFrame(tinyxml2::XMLDocument &doc, tinyxml2::XMLElement* pFrameElement, const LogFrame & frame) const {
tinyxml2::XMLElement *pListElement;
// Add static items
pListElement = doc.NewElement("step_index");
pListElement->SetText(frame.getStepCount());
pFrameElement->InsertEndChild(pListElement);
// Add dynamic environment values
if (frame.getEnvironment().size()) {
tinyxml2::XMLElement *pEnvElement = doc.NewElement("environment");
{
for (const auto &prop : frame.getEnvironment()) {
pListElement = doc.NewElement(prop.first.c_str());
writeAny(pListElement, prop.second, prop.second.elements);
pEnvElement->InsertEndChild(pListElement);
}
}
pFrameElement->InsertEndChild(pEnvElement);
}
if (frame.getAgents().size()) {
// Add dynamic agent values
tinyxml2::XMLElement *pAgentsElement = doc.NewElement("agents");
{
// This assumes that sort order places all agents of same name, different state consecutively
std::string current_agent;
tinyxml2::XMLElement *pAgentsItemElement = nullptr;
for (const auto &agent : frame.getAgents()) {
// Start/end new agent
if (current_agent != agent.first.first) {
if (!current_agent.empty())
pAgentsElement->InsertEndChild(pAgentsItemElement);
current_agent = agent.first.first;
pAgentsItemElement = doc.NewElement(current_agent.c_str());
}
// Start new state
tinyxml2::XMLElement *pStateElement = doc.NewElement(agent.first.second.c_str());
{
// Log agent count if provided
if (agent.second.second != UINT_MAX) {
tinyxml2::XMLElement *pCountElement = doc.NewElement("count");
pCountElement->SetText(agent.second.second);
pStateElement->InsertEndChild(pCountElement);
}
if (agent.second.first.size()) {
tinyxml2::XMLElement *pVariablesBlock = doc.NewElement("variables");
// This assumes that sort order places all variables of same name, different reduction consecutively
std::string current_variable;
tinyxml2::XMLElement *pVariableElement = nullptr;
// Log each reduction
for (auto &var : agent.second.first) {
// Start/end new variable
if (current_variable != var.first.name) {
if (!current_variable.empty())
pVariablesBlock->InsertEndChild(pVariableElement);
current_variable = var.first.name;
pVariableElement = doc.NewElement(current_variable.c_str());
}
// Build name key for the variable & log value
tinyxml2::XMLElement *pValueElement = doc.NewElement(LoggingConfig::toString(var.first.reduction));
writeAny(pValueElement, var.second, 1);
pVariableElement->InsertEndChild(pValueElement);
}
if (!current_variable.empty())
pVariablesBlock->InsertEndChild(pVariableElement);
pStateElement->InsertEndChild(pVariablesBlock);
}
}
pAgentsItemElement->InsertEndChild(pStateElement);
}
if (!current_agent.empty())
pAgentsElement->InsertEndChild(pAgentsItemElement);
}
pFrameElement->InsertEndChild(pAgentsElement);
}
}
void XMLLogger::writeAny(tinyxml2::XMLElement *pElement, const detail::Any &value, const unsigned int elements) const {
std::stringstream ss;
// Loop through elements, to construct csv string
for (unsigned int el = 0; el < elements; ++el) {
if (value.type == std::type_index(typeid(float))) {
ss << static_cast<const float*>(value.ptr)[el];
} else if (value.type == std::type_index(typeid(double))) {
ss << static_cast<const double*>(value.ptr)[el];
} else if (value.type == std::type_index(typeid(int64_t))) {
ss << static_cast<const int64_t*>(value.ptr)[el];
} else if (value.type == std::type_index(typeid(uint64_t))) {
ss << static_cast<const uint64_t*>(value.ptr)[el];
} else if (value.type == std::type_index(typeid(int32_t))) {
ss << static_cast<const int32_t*>(value.ptr)[el];
} else if (value.type == std::type_index(typeid(uint32_t))) {
ss << static_cast<const uint32_t*>(value.ptr)[el];
} else if (value.type == std::type_index(typeid(int16_t))) {
ss << static_cast<const int16_t*>(value.ptr)[el];
} else if (value.type == std::type_index(typeid(uint16_t))) {
ss << static_cast<const uint16_t*>(value.ptr)[el];
} else if (value.type == std::type_index(typeid(int8_t))) {
ss << static_cast<int32_t>(static_cast<const int8_t*>(value.ptr)[el]); // Char outputs weird if being used as an integer
} else if (value.type == std::type_index(typeid(uint8_t))) {
ss << static_cast<uint32_t>(static_cast<const uint8_t*>(value.ptr)[el]); // Char outputs weird if being used as an integer
} else if (value.type == std::type_index(typeid(char))) {
ss << static_cast<int32_t>(static_cast<const char*>(value.ptr)[el]); // Char outputs weird if being used as an integer
} else {
THROW exception::TinyXMLError("Attempting to export value of unsupported type '%s', "
"in XMLLogger::writeAny()\n", value.type.name());
}
if (el + 1 != elements)
ss << ",";
}
pElement->SetText(ss.str().c_str());
}
} // namespace io
} // namespace flamegpu