#include "Event.h" #include "Time.h" #include "StateMachineDim.h" #include "ServiceList.h" #include #include //**************************************************************** /** @class DataLogger * * @brief Logs all message and infos between the services * * This is the main logging class facility. * It derives from StateMachineDim and DimInfoHandler. the first parent is here to enforce * a state machine behaviour, while the second one is meant to make the dataLogger receive * dim services to which it subscribed from. * The possible states and transitions of the machine are: * \dot * digraph datalogger { * node [shape=record, fontname=Helvetica, fontsize=10]; * e [label="Error" color="red"]; * r [label="Ready"] * d [label="DailyOpen"] * w [label="WaitingRun"] * l [label="Logging"] * b [label="BadDailyconfig" color="red"] * c [label="BadRunConfig" color="red"] * * e -> r * r -> e * r -> d * r -> b * d -> w * d -> r * w -> r * l -> r * l -> w * b -> d * w -> c * w -> l * b -> r * c -> r * c -> l * } * \enddot * * @todo * - A lot ****************************************************************/ class DataLogger : public StateMachineDim, DimInfoHandler { public: /// The list of existing states specific to the DataLogger enum { kSM_DailyOpen = 20, ///< Daily file openned and writing kSM_WaitingRun = 30, ///< waiting for the run number to open the run file kSM_Logging = 40, ///< both files openned and writing kSM_BadDailyConfig = 0x101, ///< the folder specified for daily logging does not exist or has bad permissions kSM_BadRunConfig = 0x102, ///< the folder specified for the run logging does not exist or has wrong permissions or no run number } localstates_t; DataLogger(); ~DataLogger(); private: //Define all the data structure specific to the DataLogger here std::ofstream fDailyFile; /// ofstream for the dailyfile std::ofstream fRunFile; /// ofstream for the run-specific file std::string fDailyFileName; /// base path of the dailyfile std::string fRunFileName; ///base path of the run file int runNumber; ///run number (-1 means no run number specified) ///Define all the static names static const char* configDay; static const char* configRun; static const char* configRunNumber; static const char* configLog; static const char* transStart; static const char* transStop; static const char* transStartRun; static const char* transStopRun; static const char* transReset; static const char* transWait; static const char* runNumberInfo; ///< This is the name of the dimInfo received to specify the run number. It must be updated once the final name will be defined int Execute(); ///Inherited from state machine impl int Transition(const Event& evt); ///Inherited from state machine impl int Configure(const Event& evt); ///Inherited from state machine impl //overloading of DIM's infoHandler function void infoHandler(); ///Inherited from DimInfoHandler ServiceList fServiceList;///for obtaining the name of the existing services std::map > fServiceSubscriptions; ///All the services to which we've subscribed to. Sorted by server name //internal methods void LogPlease(DimInfo* I); ///Logging method for the services info received std::string ToString(const char *format, const void *data, int size); ///Convertion from raw format to human-readable string //configure methods int ConfigureDailyFileName(const Event& evt); ///Configuration of the daily file path int ConfigureRunFileName(const Event& evt); ///Configuration fo the file name //TODO this will be given by an info, not a command. int ConfigureRunNumber(const Event& evt); ///DEPREC - configuration of the run number int LogMessagePlease(const Event& evt); ///logging method for the messages void CheckForServicesUpdate(); ///checks with fServiceList whether or not the services got updated void CheckForRunNumber(DimInfo* I); ///checks whether or not the current info being treated is a run number //transitions methods int StartPlease(); /// start transition int StopPlease(); ///stop transition int StartRunPlease(); ///from waiting to logging transition int StopRunPlease(); /// from logging to waiting transition int ResetPlease(); ///reset transition int DailyToWaitRunPlease(); ///from dailyOpen to waiting transition void InitServiceSubscription(); ///first subscription to the dim services. }; //DataLogger //static members initialization //since I do not check the transition/config names any longer, indeed maybe these could be hard-coded... but who knows what will happen in the future ? const char* DataLogger::configDay = "CONFIG_DAY"; const char* DataLogger::configRun = "CONFIG_RUN"; const char* DataLogger::configRunNumber = "CONFIG_RUN_NUMBER"; const char* DataLogger::configLog = "LOG"; const char* DataLogger::transStart = "START"; const char* DataLogger::transStop = "STOP"; const char* DataLogger::transStartRun = "START_RUN"; const char* DataLogger::transStopRun = "STOP_RUN"; const char* DataLogger::transReset = "RESET"; const char* DataLogger::transWait = "WAIT_RUN_NUMBER"; const char* DataLogger::runNumberInfo = "RUN_NUMBER"; /**************************************************************** * * DATA LOGGER CONSTRUCTOR * ****************************************************************/ //! Default constructor DataLogger::DataLogger() : StateMachineDim(std::cout, "DATA_LOGGER") { //initialize member data fDailyFileName = ""; fRunFileName = ""; runNumber = -1; //Give a name to this machine's specific states AddStateName(kSM_DailyOpen, "Daily_File_Openened"); AddStateName(kSM_WaitingRun, "Waiting_Run_Number"); AddStateName(kSM_Logging, "Logging data"); AddStateName(kSM_BadDailyConfig, "Folder_For_Daily_Logging_Badly_Configured"); AddStateName(kSM_BadRunConfig, "Folder_For_Run_Logging_Badly Configured"); /*Add the possible transitions for this machine*/ AddTransition(kSM_DailyOpen, transStart, kSM_Ready, kSM_BadDailyConfig) //start the daily logging. daily file location must be specified already ->AssignFunction(boost::bind(&DataLogger::StartPlease, this)); AddTransition(kSM_Ready, transStop, kSM_DailyOpen, kSM_WaitingRun, kSM_Logging) //stop the data logging ->AssignFunction(boost::bind(&DataLogger::StopPlease, this)); AddTransition(kSM_Logging, transStartRun, kSM_WaitingRun, kSM_BadRunConfig) //start the run logging. run file location must be specified already. ->AssignFunction(boost::bind(&DataLogger::StartRunPlease, this)); AddTransition(kSM_WaitingRun, transStopRun, kSM_Logging) ->AssignFunction(boost::bind(&DataLogger::StopRunPlease, this)); AddTransition(kSM_Ready, transReset, kSM_Error, kSM_BadDailyConfig, kSM_BadRunConfig, kSM_Error) //transition to exit error states. dunno if required or not, would close the daily file if already openned. ->AssignFunction(boost::bind(&DataLogger::ResetPlease, this)); AddTransition(kSM_WaitingRun, transWait, kSM_DailyOpen) ->AssignFunction(boost::bind(&DataLogger::DailyToWaitRunPlease, this)); /*Add the possible configurations for this machine*/ AddConfiguration(configDay, kSM_Ready, kSM_BadDailyConfig) //configure the daily file location. cannot be done before the file is actually opened ->AssignFunction(boost::bind(&DataLogger::ConfigureDailyFileName, this, _1)); AddConfiguration(configRun, kSM_Ready, kSM_BadDailyConfig, kSM_DailyOpen, kSM_WaitingRun, kSM_BadRunConfig) //configure the run file location. cannot be done before the file is actually opened, and not in a dailly related error. ->AssignFunction(boost::bind(&DataLogger::ConfigureRunFileName, this, _1)); /*wait for Dim to finish initializing*/ /*just a sleep for now. */ /*TODO: add a better way to wait: poll DIM for something I guess. Maybe get the configuration from conf server ? */ usleep(1000000); //10ms //Provide a logging command //I get the feeling that I should be going through the EventImp //instead of DimCommand directly, mainly because the commandHandler //is already done in StateMachineImp.cc //Thus I'll simply add a configuration, which I will treat as the logging command AddConfiguration(configRun, kSM_DailyOpen, kSM_Logging, kSM_WaitingRun, kSM_BadRunConfig) ->AssignFunction(boost::bind(&DataLogger::LogMessagePlease, this, _1)); InitServiceSubscription(); //All configuration done, callback funtions set, go to ready state and thus prevent the run loop from being executed SetReady(); } //! Initialization of the subscription to the services. void DataLogger::InitServiceSubscription() { //assign myself as the info handler fServiceList.SetHandler(this); //crawl through the list of services and subscribe to all of them //TODO I still haven't figured out how the update of services shouldbe performed //TODO: I don't think that the reference would work in this case because what is returned is a temporary. but should try anyway in case this works... const std::vector serverList = fServiceList.GetServerList(); for (unsigned int i=0;i::const_iterator j=fServiceList.begin(serverList[i]); j != fServiceList.end(serverList[i]); j++) {//and subscribe to each service ! fServiceSubscriptions[serverList[i]].push_back(DimStampedInfo(j->c_str(), const_cast(""), this)); } } } //! destructor DataLogger::~DataLogger() { if (fDailyFile.is_open()) fDailyFile.close(); if (fRunFile.is_open()) fRunFile.close(); ; } /**************************************************************** * * DATA LOGGER'S EXECUTE * ****************************************************************/ //! Execute //! Shouldn't be run as we use callbacks instead int DataLogger::Execute() { //due to the callback mecanism, this function should never be called return kSM_FatalError; switch (GetCurrentState()) { case kSM_Error: case kSM_Ready: case kSM_DailyOpen: case kSM_WaitingRun: case kSM_Logging: case kSM_BadDailyConfig: case kSM_BadRunConfig: return GetCurrentState(); } //this line below should never be hit. It here mainly to remove warnings at compilation return kSM_FatalError; } /**************************************************************** * * DATA LOGGER'S TRANSITION * ****************************************************************/ //! Shouldn't be run as we use callbacks instead int DataLogger::Transition(const Event& evt) { //due to the callback mecanism, this function should never be called return kSM_FatalError; switch (evt.GetTargetState()) { case kSM_Ready: /*here we must figure out whether the STOP or RESET command was sent*/ /*close opened files and go back to ready state*/ switch (GetCurrentState()) { case kSM_BadDailyConfig: case kSM_BadRunConfig: case kSM_Error: return ResetPlease(); case kSM_Logging: case kSM_WaitingRun: case kSM_DailyOpen: return StopPlease(); } break; case kSM_DailyOpen: /*Attempt to open the daily file */ switch (GetCurrentState()) { case kSM_Ready: case kSM_BadDailyConfig: return StartPlease(); } break; case kSM_WaitingRun: /*either close the run file, or just go to the waitingrun state (if coming from daily open*/ switch (GetCurrentState()) { case kSM_DailyOpen: return kSM_WaitingRun; case kSM_Logging: return StopRunPlease(); } break; case kSM_Logging: /*Attempt to open run file */ switch (GetCurrentState()) { case kSM_WaitingRun: case kSM_BadRunConfig: return StartRunPlease(); } break; } //Getting here means that an invalid transition has been asked. //TODO Log an error message //and return the fatal error state return kSM_FatalError; } /**************************************************************** * * DATA LOGGER'S CONFIGURE * ****************************************************************/ //! Shouldn't be run as we use callbacks instead int DataLogger::Configure(const Event& evt) { //due to the callback mecanism, this function should never be called return kSM_FatalError; switch (evt.GetTargetState()) { case kSM_Ready: case kSM_BadDailyConfig: return ConfigureDailyFileName(evt); break; case kSM_WaitingRun: case kSM_BadRunConfig: return ConfigureRunFileName(evt); break; case kSM_Logging: case kSM_DailyOpen: return LogMessagePlease(evt); break; } //Getting here means that an invalid configuration has been asked. //TODO Log an error message //and return the fatal error state return kSM_FatalError; } /**************************************************************** * * DATA LOGGER'S INFOHANDLER * ****************************************************************/ void DataLogger::infoHandler() { DimInfo* I = getInfo(); //there I should get the services updates. It remains unclear whether I will be getting the new services or not through here // CheckForServicesUpdate(); //in this function I will add/remove services subscription CheckForRunNumber(I); LogPlease(I); } //! Checks for changes in the existingn services //! @todo finish this function as most of it is still missing (plus what is already there isn't great) void DataLogger::CheckForServicesUpdate() {//TODO well... do it... //TODO handle the cases were one server/service was removed and one was added const std::vector serverList = fServiceList.GetServerList(); if (serverList.size() != fServiceSubscriptions.size()) {//some servers were added/deleted. Rebuild the list entirely std::cout << "Redoing the server list entirely " << serverList.size() << " " << fServiceSubscriptions.size() << std::endl; std::map >::iterator it; for (it=fServiceSubscriptions.begin(); it != fServiceSubscriptions.end(); it++) (*it).second.clear(); fServiceSubscriptions.clear(); InitServiceSubscription(); return; } //TODO crawl the list to see if any of the servers has updated its service list } //! Looks whether the currently being processed DimInfo indeed is a run number or not void DataLogger::CheckForRunNumber(DimInfo* I) { return; if (strstr(I->getName(), runNumberInfo) != NULL) {//assumes that the run number is an integer //TODO check the format here runNumber = I->getInt(); } } /**************************************************************** * * DATA LOGGER'S RUN LOG * * write info to run log. Go to error if anything goes wrong * ****************************************************************/ //! write info to logs. Go to error if anything goes wrong void DataLogger::LogPlease(DimInfo* I) { if (I->getSize() == 0) return; //TODO add service exclusion if (!fDailyFile.is_open()) return; //construct the header std::stringstream header; Time cTime((time_t)(I->getTimestamp()), 0); header << I->getName() << " " << cTime.Y() << " " << cTime.M() << " " << cTime.D() << " "; header << cTime.h() << " " << cTime.m() << " " << cTime.s() << " "; header << cTime.ms() << " " << I->getQuality() << " "; if (fDailyFile.is_open()) fDailyFile << header; if (fRunFile.is_open()) fRunFile << header; std::string text = ToString(I->getFormat(), I->getData(), I->getSize()); if (!text.empty()) {//replace bizarre characters by white space for (unsigned int i=0; i 0 && *((char *) data+size-1)=='\0') { return std::string((char *) data); } // Number array int ElementSize; switch (*format) { case 'C': ElementSize = sizeof(char); break; case 'I': case 'L': ElementSize = sizeof(int); break; case 'S': ElementSize = sizeof(short); break; case 'F': ElementSize = sizeof(float); break; case 'D': ElementSize = sizeof(double); break; case 'X': ElementSize = sizeof(long long); break; default: return std::string(); } for (int i=0; igetSize() == strlen(NO_LINK)+1) && (memcmp(item->getData(), NO_LINK, item->getSize()) == 0)); }*/ /**************************************************************** * * MAIN * ****************************************************************/ extern bool CheckDim(bool=true); int main() { std::cout << "Data Logger Starting" << std::endl; if (!CheckDim()) return -1; std::cout << "Continuing" << std::endl; DataLogger log; std::cout << "DataLogger created. Starting main loop" << std::endl; while (!log.IsRExitRequested()) { usleep(1); } return 0; }