-
Campbell, Stuart authoredCampbell, Stuart authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
LogParser.cpp 10.98 KiB
//----------------------------------------------------------------------
// Includes
//----------------------------------------------------------------------
#include "MantidKernel/LogParser.h"
#include "MantidKernel/Strings.h"
#include "MantidKernel/Logger.h"
#include "MantidKernel/PropertyWithValue.h"
#include "MantidKernel/TimeSeriesProperty.h"
#include <fstream>
// constants for the new style icp event commands
const char *START_COLLECTION = "START_COLLECTION";
const char *STOP_COLLECTION = "STOP_COLLECTION";
using std::size_t;
namespace Mantid {
namespace Kernel {
namespace {
/// static logger
Logger g_log("LogParser");
}
/// @returns the name of the log created that defines the status during a run
const std::string LogParser::statusLogName() { return std::string("running"); }
/// @returns the name of the log that contains all of the periods
const std::string LogParser::periodsLogName() { return std::string("periods"); }
/** Reads in log data from a log file and stores them in a TimeSeriesProperty.
@param logFName :: The name of the log file
@param name :: The name of the property
@return A pointer to the created property.
*/
Kernel::Property *LogParser::createLogProperty(const std::string &logFName,
const std::string &name) {
std::ifstream file(logFName.c_str());
if (!file) {
g_log.warning() << "Cannot open log file " << logFName << "\n";
return 0;
}
// Change times and new values read from file
std::multimap<std::string, std::string> change_times;
// Read in the data and determin if it is numeric
std::string str, old_data;
bool isNumeric(false);
std::string stime, sdata;
// MG 22/09/09: If the log file was written on a Windows machine and then read
// on a Linux machine, std::getline will
// leave CR at the end of the string and this causes problems when reading out
// the log values. Mantid::extractTOEOL
// extracts all EOL characters
while (Mantid::Kernel::Strings::extractToEOL(file, str)) {
if (str.empty() || str[0] == '#') {
continue;
}
if (!Kernel::TimeSeriesProperty<double>::isTimeString(str)) {
// if the line doesn't start with a time treat it as a continuation of the
// previous data
if (change_times.empty() || isNumeric) { // if there are no previous data
std::string mess =
"Cannot parse log file " + logFName + ". Line:" + str;
g_log.error(mess);
throw std::logic_error(mess);
}
auto range = change_times.equal_range(stime);
if (range.first != range.second) {
auto last = range.first;
for (auto it = last; it != range.second; ++it) {
last = it;
}
last->second += std::string(" ") + str;
old_data = last->second;
continue;
}
}
stime = str.substr(0, 19);
sdata = str.substr(19);
if (sdata == old_data)
continue; // looking for a change in the data
// check if the data is numeric
std::istringstream istr(sdata);
double tmp;
istr >> tmp;
isNumeric = !istr.fail();
old_data = sdata;
change_times.insert(std::make_pair(stime, sdata));
}
if (change_times.empty())
return 0;
if (isNumeric) {
Kernel::TimeSeriesProperty<double> *logv =
new Kernel::TimeSeriesProperty<double>(name);
auto it = change_times.begin();
for (; it != change_times.end(); ++it) {
std::istringstream istr(it->second);
double d;
istr >> d;
logv->addValue(it->first, d);
}
return logv;
} else {
Kernel::TimeSeriesProperty<std::string> *logv =
new Kernel::TimeSeriesProperty<std::string>(name);
auto it = change_times.begin();
for (; it != change_times.end(); ++it) {
logv->addValue(it->first, it->second);
}
return logv;
}
return 0;
}
/**
* Destructor
*/
LogParser::~LogParser() {}
/**
Common creational method for generating a command map.
Better ensures that the same command mapping is available for any constructor.
@param newStyle Command style selector.
@return fully constructed command map.
*/
LogParser::CommandMap LogParser::createCommandMap(bool newStyle) const {
CommandMap command_map;
if (newStyle) {
command_map["START_COLLECTION"] = BEGIN;
command_map["STOP_COLLECTION"] = END;
command_map["CHANGE"] = CHANGE_PERIOD;
command_map["CHANGE_PERIOD"] = CHANGE_PERIOD;
} else {
command_map["BEGIN"] = BEGIN;
command_map["RESUME"] = BEGIN;
command_map["END_SE_WAIT"] = BEGIN;
command_map["PAUSE"] = END;
command_map["END"] = END;
command_map["ABORT"] = END;
command_map["UPDATE"] = END;
command_map["START_SE_WAIT"] = END;
command_map["CHANGE"] = CHANGE_PERIOD;
command_map["CHANGE_PERIOD"] = CHANGE_PERIOD;
}
return command_map;
}
/**
Try to pass the periods
@param scom : The command corresponding to a change in period.
@param time : The time
@param idata : stream of input data
@param periods : periods data to update
*/
void LogParser::tryParsePeriod(const std::string &scom, const DateAndTime &time,
std::istringstream &idata,
Kernel::TimeSeriesProperty<int> *const periods) {
int ip = -1;
bool shouldAddPeriod = false;
// Handle the version where log flag is CHANGE PERIOD
if (scom == "CHANGE") {
std::string s;
idata >> s >> ip;
if (ip > 0 && s == "PERIOD") {
shouldAddPeriod = true;
}
}
// Handle the version where log flat is CHANGE_PERIOD
else if (scom == "CHANGE_PERIOD") {
idata >> ip;
if (ip > 0) {
shouldAddPeriod = true;
}
}
// Common for either variant of the log flag.
if (shouldAddPeriod) {
if (ip > m_nOfPeriods) {
m_nOfPeriods = ip;
}
periods->addValue(time, ip);
}
}
/** Create given the icpevent log property.
* @param log :: A pointer to the property
*/
LogParser::LogParser(const Kernel::Property *log) : m_nOfPeriods(1) {
Kernel::TimeSeriesProperty<int> *periods =
new Kernel::TimeSeriesProperty<int>(periodsLogName());
Kernel::TimeSeriesProperty<bool> *status =
new Kernel::TimeSeriesProperty<bool>(statusLogName());
m_periods.reset(periods);
m_status.reset(status);
const Kernel::TimeSeriesProperty<std::string> *icpLog =
dynamic_cast<const Kernel::TimeSeriesProperty<std::string> *>(log);
if (!icpLog || icpLog->size() == 0) {
periods->addValue(Kernel::DateAndTime(), 1);
status->addValue(Kernel::DateAndTime(), true);
g_log.warning()
<< "Cannot process ICPevent log. Period 1 assumed for all data.\n";
return;
}
std::multimap<Kernel::DateAndTime, std::string> logm =
icpLog->valueAsMultiMap();
CommandMap command_map =
createCommandMap(LogParser::isICPEventLogNewStyle(logm));
m_nOfPeriods = 1;
auto it = logm.begin();
for (; it != logm.end(); ++it) {
std::string scom;
std::istringstream idata(it->second);
idata >> scom;
commands com = command_map[scom];
if (com == CHANGE_PERIOD) {
tryParsePeriod(scom, it->first, idata, periods);
} else if (com == BEGIN) {
status->addValue(it->first, true);
} else if (com == END) {
status->addValue(it->first, false);
}
};
if (periods->size() == 0)
periods->addValue(icpLog->firstTime(), 1);
if (status->size() == 0)
status->addValue(icpLog->firstTime(), true);
}
/** Creates a TimeSeriesProperty<bool> showing times when a particular period
* was active.
* @param period :: The data period
* @return times requested period was active
*/
Kernel::TimeSeriesProperty<bool> *LogParser::createPeriodLog(int period) const {
Kernel::TimeSeriesProperty<int> *periods =
dynamic_cast<Kernel::TimeSeriesProperty<int> *>(m_periods.get());
if (!periods) {
throw std::logic_error("Failed to cast periods to TimeSeriesProperty");
}
std::ostringstream ostr;
ostr << period;
Kernel::TimeSeriesProperty<bool> *p =
new Kernel::TimeSeriesProperty<bool>("period " + ostr.str());
std::map<Kernel::DateAndTime, int> pMap = periods->valueAsMap();
auto it = pMap.begin();
if (it->second != period)
p->addValue(it->first, false);
for (; it != pMap.end(); ++it)
p->addValue(it->first, (it->second == period));
return p;
}
/**
* Create a log vale for the current period.
* @param period: The period number to create the log entry for.
*/
Kernel::Property *LogParser::createCurrentPeriodLog(const int &period) const {
Kernel::PropertyWithValue<int> *currentPeriodProperty =
new Kernel::PropertyWithValue<int>("current_period", period);
return currentPeriodProperty;
}
/// Ctreates a TimeSeriesProperty<int> with all data periods
Kernel::Property *LogParser::createAllPeriodsLog() const {
return m_periods->clone();
}
/// Creates a TimeSeriesProperty<bool> with running status
Kernel::TimeSeriesProperty<bool> *LogParser::createRunningLog() const {
return m_status->clone();
}
namespace {
/// Define operator for checking for new-style icp events
struct hasNewStyleCommands {
bool
operator()(const std::pair<Mantid::Kernel::DateAndTime, std::string> &p) {
return p.second.find(START_COLLECTION) != std::string::npos ||
p.second.find(STOP_COLLECTION) != std::string::npos;
}
};
}
/**
* Check if the icp log commands are in the new style. The new style is the one
* that uses START_COLLECTION and STOP_COLLECTION commands for changing periods
* and running status.
* @param logm :: A log map created from a icp-event log.
*/
bool LogParser::isICPEventLogNewStyle(
const std::multimap<Kernel::DateAndTime, std::string> &logm) {
hasNewStyleCommands checker;
return std::find_if(logm.begin(), logm.end(), checker) != logm.end();
}
/**
* Returns the time-weighted mean value if the property is
* TimeSeriesProperty<double>.
*
* TODO: Make this more efficient.
*
* @param p :: Property with the data. Will throw if not
* TimeSeriesProperty<double>.
* @return The mean value over time.
* @throw runtime_error if the property is not TimeSeriesProperty<double>
*/
double timeMean(const Kernel::Property *p) {
const Kernel::TimeSeriesProperty<double> *dp =
dynamic_cast<const Kernel::TimeSeriesProperty<double> *>(p);
if (!dp) {
throw std::runtime_error("Property of a wrong type. Cannot be cast to a "
"TimeSeriesProperty<double>.");
}
// Special case for only one value - the algorithm
if (dp->size() == 1) {
return dp->nthValue(1);
}
double res = 0.;
Kernel::time_duration total(0, 0, 0, 0);
size_t dp_size = dp->size();
for (size_t i = 0; i < dp_size; i++) {
Kernel::TimeInterval t = dp->nthInterval(static_cast<int>(i));
Kernel::time_duration dt = t.length();
total += dt;
res += dp->nthValue(static_cast<int>(i)) *
Kernel::DateAndTime::secondsFromDuration(dt);
}
double total_seconds = Kernel::DateAndTime::secondsFromDuration(total);
// If all the time stamps were the same, just return the first value.
if (total_seconds == 0.0)
res = dp->nthValue(1);
if (total_seconds > 0)
res /= total_seconds;
return res;
}
} // namespace Geometry
} // namespace Mantid