From 49481410e678478cc386c14592c6168abafdf016 Mon Sep 17 00:00:00 2001 From: Martin Hierholzer <martin.hierholzer@desy.de> Date: Fri, 17 Aug 2018 11:27:47 +0200 Subject: [PATCH] Improvements for MicroDAQ module: - Make data decimation configurable - Addded some documentation - Some code cleaning (eliminate warnings, move internal classes into detail namespace) --- Modules/include/MicroDAQ.h | 41 ++- Modules/src/MicroDAQ.cc | 524 +++++++++++++++++++------------------ 2 files changed, 306 insertions(+), 259 deletions(-) diff --git a/Modules/include/MicroDAQ.h b/Modules/include/MicroDAQ.h index 982398e5..2822592f 100644 --- a/Modules/include/MicroDAQ.h +++ b/Modules/include/MicroDAQ.h @@ -10,8 +10,33 @@ namespace ChimeraTK { + namespace detail { + struct AccessorAttacher; + struct H5storage; + struct DataSpaceCreator; + struct DataWriter; + } + + /** + * MicroDAQ module for logging data to HDF5 files. This can be usefull in enviromenents where no sufficient logging + * of data is possible through the control system. Any ChimeraTK::Module can act as a data source. Which variables + * should be logged can be selected through EntityOwner::findTag(). + */ struct MicroDAQ : public ApplicationModule { - using ApplicationModule::ApplicationModule; + + /** + * Constructor. decimationFactor and decimationThreshold are configuration constants which determine how the + * data reduction is working. Arrays with a size bigger than decimationThreshold will be decimated by + * decimationFactor before writing to the HDF5 file. + */ + MicroDAQ(EntityOwner *owner, const std::string &name, const std::string &description, uint32_t decimationFactor=10, + uint32_t decimationThreshold=1000, bool eliminateHierarchy=false, + const std::unordered_set<std::string> &tags={}) + : ApplicationModule(owner, name, description, eliminateHierarchy, tags), + decimationFactor_(decimationFactor), decimationThreshold_(decimationThreshold) {} + + /** Default constructor, creates a non-working module. Can be used for late initialisation. */ + MicroDAQ() : decimationFactor_(0), decimationThreshold_(0) {} ScalarPushInput<int> trigger{this, "trigger", "", "When written, the MicroDAQ write snapshot of all variables " "to the file", {"MicroDAQ.CONFIG"}}; @@ -26,11 +51,13 @@ namespace ChimeraTK { ScalarOutput<uint32_t> currentFile{this, "currentFile", "", "File number currently written to.", {"MicroDAQ.CONFIG"}}; - void mainLoop() override; - /** Add a Module as a source to this DAQ. */ void addSource(const Module &source, const std::string &namePrefix=""); + protected: + + void mainLoop() override; + template<typename UserType> VariableNetworkNode getAccessor(const std::string &variableName); @@ -50,6 +77,14 @@ namespace ChimeraTK { /** Overall variable name list, used to detect name collisions */ std::list<std::string> overallVariableList; + /** Parameters for the data decimation */ + uint32_t decimationFactor_, decimationThreshold_; + + friend struct detail::AccessorAttacher; + friend struct detail::H5storage; + friend struct detail::DataSpaceCreator; + friend struct detail::DataWriter; + }; } // namespace ChimeraTK diff --git a/Modules/src/MicroDAQ.cc b/Modules/src/MicroDAQ.cc index ef8b3155..9c0bf4b5 100644 --- a/Modules/src/MicroDAQ.cc +++ b/Modules/src/MicroDAQ.cc @@ -9,46 +9,50 @@ namespace ChimeraTK { /*********************************************************************************************************************/ - /** Callable class for use with boost::fusion::for_each: Attach the given accessor to the MicroDAQ with proper - * handling of the UserType. */ - struct AccessorAttacher { - AccessorAttacher(VariableNetworkNode& feeder, MicroDAQ *owner, const std::string &name) - : _feeder(feeder), _owner(owner), _name(name) {} - - template<typename PAIR> - void operator()(PAIR&) const { - - // only continue if the call is for the right type - if(typeid(typename PAIR::first_type) != _feeder.getValueType()) return; - - // register connection - _feeder >> _owner->template getAccessor<typename PAIR::first_type>(_name); + namespace detail { - } + /** Callable class for use with boost::fusion::for_each: Attach the given accessor to the MicroDAQ with proper + * handling of the UserType. */ + struct AccessorAttacher { + AccessorAttacher(VariableNetworkNode& feeder, MicroDAQ *owner, const std::string &name) + : _feeder(feeder), _owner(owner), _name(name) {} + + template<typename PAIR> + void operator()(PAIR&) const { + + // only continue if the call is for the right type + if(typeid(typename PAIR::first_type) != _feeder.getValueType()) return; + + // register connection + _feeder >> _owner->template getAccessor<typename PAIR::first_type>(_name); - VariableNetworkNode &_feeder; - MicroDAQ *_owner; - const std::string &_name; - }; + } + + VariableNetworkNode &_feeder; + MicroDAQ *_owner; + const std::string &_name; + }; + + } /*********************************************************************************************************************/ void MicroDAQ::addSource(const Module &source, const std::string &namePrefix) { - + // for simplification, first create a VirtualModule containing the correct hierarchy structure (obeying eliminate // hierarchy etc.) auto dynamicModel = source.findTag(".*"); /// @todo use virtualise() instead - + // add all accessors on this hierarchy level for(auto &acc : dynamicModel.getAccessorList()) { - boost::fusion::for_each(accessorListMap.table, AccessorAttacher(acc, this, namePrefix+"/"+acc.getName())); + boost::fusion::for_each(accessorListMap.table, detail::AccessorAttacher(acc, this, namePrefix+"/"+acc.getName())); } - + // recurse into submodules for(auto mod : dynamicModel.getSubmoduleList()) { addSource(*mod, namePrefix+"/"+mod->getName()); } - + } /*********************************************************************************************************************/ @@ -77,76 +81,81 @@ namespace ChimeraTK { /*********************************************************************************************************************/ - struct H5storage { - H5storage(MicroDAQ *owner) : _owner(owner) {} - - H5::H5File outFile; - std::string currentGroupName; - - /** Unique list of groups, used to create the groups in the file */ - std::list <std::string> groupList; - - /** boost::fusion::map of UserTypes to std::lists containing the H5::DataSpace objects. */ - template<typename UserType> - using dataSpaceList = std::list<H5::DataSpace>; - TemplateUserTypeMap<dataSpaceList> dataSpaceListMap; - - /** boost::fusion::map of UserTypes to std::lists containing decimation factors. */ - template<typename UserType> - using decimationFactorList = std::list<size_t>; - TemplateUserTypeMap<decimationFactorList> decimationFactorListMap; - - uint32_t currentBuffer{0}; - uint32_t nFillsInBuffer{0}; - bool isOpened{false}; - bool firstTrigger{true}; - - void processTrigger(); - void writeData(); - - MicroDAQ *_owner; - }; - - /*********************************************************************************************************************/ - - struct DataSpaceCreator { - DataSpaceCreator(H5storage &storage) : _storage(storage) {} - - template<typename PAIR> - void operator()(PAIR &pair) const { - typedef typename PAIR::first_type UserType; - - // get the lists for the UserType - auto &accessorList = pair.second; - auto &decimationFactorList = boost::fusion::at_key<UserType>(_storage.decimationFactorListMap.table); - auto &dataSpaceList = boost::fusion::at_key<UserType>(_storage.dataSpaceListMap.table); - auto &nameList = boost::fusion::at_key<UserType>(_storage._owner->nameListMap.table); - - // iterate through all accessors for this UserType - auto name = nameList.begin(); - for(auto accessor = accessorList.begin() ; accessor != accessorList.end() ; ++accessor , ++name) { - // determine decimation factor - /// @todo make it configurable! - int factor = 1; - if(accessor->getNElements() > 1000) factor = 10; - decimationFactorList.push_back(factor); - - // define data space - hsize_t dimsf[1]; // dataset dimensions - dimsf[0] = accessor->getNElements()/factor; - dataSpaceList.push_back( H5::DataSpace(1, dimsf) ); - - // put all group names in list (each hierarchy level separately) - size_t idx = 0; - while( (idx = name->find('/', idx+1)) != std::string::npos ) { - std::string groupName = name->substr(0,idx); - _storage.groupList.push_back(groupName); + namespace detail { + + struct H5storage { + H5storage(MicroDAQ *owner) : _owner(owner) {} + + H5::H5File outFile; + std::string currentGroupName; + + /** Unique list of groups, used to create the groups in the file */ + std::list <std::string> groupList; + + /** boost::fusion::map of UserTypes to std::lists containing the H5::DataSpace objects. */ + template<typename UserType> + using dataSpaceList = std::list<H5::DataSpace>; + TemplateUserTypeMap<dataSpaceList> dataSpaceListMap; + + /** boost::fusion::map of UserTypes to std::lists containing decimation factors. */ + template<typename UserType> + using decimationFactorList = std::list<size_t>; + TemplateUserTypeMap<decimationFactorList> decimationFactorListMap; + + uint32_t currentBuffer{0}; + uint32_t nFillsInBuffer{0}; + bool isOpened{false}; + bool firstTrigger{true}; + + void processTrigger(); + void writeData(); + + MicroDAQ *_owner; + }; + + /*********************************************************************************************************************/ + + struct DataSpaceCreator { + DataSpaceCreator(H5storage &storage) : _storage(storage) {} + + template<typename PAIR> + void operator()(PAIR &pair) const { + typedef typename PAIR::first_type UserType; + + // get the lists for the UserType + auto &accessorList = pair.second; + auto &decimationFactorList = boost::fusion::at_key<UserType>(_storage.decimationFactorListMap.table); + auto &dataSpaceList = boost::fusion::at_key<UserType>(_storage.dataSpaceListMap.table); + auto &nameList = boost::fusion::at_key<UserType>(_storage._owner->nameListMap.table); + + // iterate through all accessors for this UserType + auto name = nameList.begin(); + for(auto accessor = accessorList.begin() ; accessor != accessorList.end() ; ++accessor , ++name) { + // determine decimation factor + int factor = 1; + if(accessor->getNElements() > _storage._owner->decimationThreshold_) { + factor = _storage._owner->decimationFactor_; + } + decimationFactorList.push_back(factor); + + // define data space + hsize_t dimsf[1]; // dataset dimensions + dimsf[0] = accessor->getNElements()/factor; + dataSpaceList.push_back( H5::DataSpace(1, dimsf) ); + + // put all group names in list (each hierarchy level separately) + size_t idx = 0; + while( (idx = name->find('/', idx+1)) != std::string::npos ) { + std::string groupName = name->substr(0,idx); + _storage.groupList.push_back(groupName); + } } } - } - H5storage &_storage; - }; + H5storage &_storage; + }; + + } /*********************************************************************************************************************/ @@ -154,15 +163,15 @@ namespace ChimeraTK { std::cout << "Initialising MicroDAQ system..."; // storage object - H5storage storage(this); + detail::H5storage storage(this); // create the data spaces - boost::fusion::for_each(accessorListMap.table, DataSpaceCreator(storage)); - + boost::fusion::for_each(accessorListMap.table, detail::DataSpaceCreator(storage)); + // sort group list and make unique to make sure lower levels get created first storage.groupList.sort(); storage.groupList.unique(); - + // loop: process incoming triggers while(true) { trigger.read(); @@ -174,190 +183,193 @@ namespace ChimeraTK { /*********************************************************************************************************************/ - void H5storage::processTrigger() { - - // update configuration variables - _owner->enable.readLatest(); - _owner->nMaxFiles.readLatest(); - _owner->nTriggersPerFile.readLatest(); - - // need to open or close file? - if(!isOpened && _owner->enable != 0) { - std::fstream bufferNumber; - - // some things to be done only on first trigger - if(firstTrigger) { - // create sub-directory - boost::filesystem::create_directory("uDAQ"); - - // determine current buffer number - bufferNumber.open("uDAQ/currentBuffer", std::ofstream::in); - bufferNumber.seekg(0); - if(!bufferNumber.eof()) { - bufferNumber >> currentBuffer; - char filename[64]; - std::sprintf(filename, "uDAQ/data%04d.h5", currentBuffer); - if(boost::filesystem::exists(filename) && boost::filesystem::file_size(filename) > 1000) currentBuffer++; - if(currentBuffer >= _owner->nMaxFiles) currentBuffer = 0; - } - else { - currentBuffer = 0; + namespace detail { + + void H5storage::processTrigger() { + + // update configuration variables + _owner->enable.readLatest(); + _owner->nMaxFiles.readLatest(); + _owner->nTriggersPerFile.readLatest(); + + // need to open or close file? + if(!isOpened && _owner->enable != 0) { + std::fstream bufferNumber; + + // some things to be done only on first trigger + if(firstTrigger) { + // create sub-directory + boost::filesystem::create_directory("uDAQ"); + + // determine current buffer number + bufferNumber.open("uDAQ/currentBuffer", std::ofstream::in); + bufferNumber.seekg(0); + if(!bufferNumber.eof()) { + bufferNumber >> currentBuffer; + char filename[64]; + std::sprintf(filename, "uDAQ/data%04d.h5", currentBuffer); + if(boost::filesystem::exists(filename) && boost::filesystem::file_size(filename) > 1000) currentBuffer++; + if(currentBuffer >= _owner->nMaxFiles) currentBuffer = 0; + } + else { + currentBuffer = 0; + } + bufferNumber.close(); } + + // store current buffer number to disk + char filename[64]; + std::sprintf(filename, "uDAQ/data%04d.h5", currentBuffer); + std::cout << "uDAQ: Starting with file: " << filename << std::endl; + bufferNumber.open("uDAQ/currentBuffer", std::ofstream::out); + bufferNumber << currentBuffer << std::endl; bufferNumber.close(); - } - // store current buffer number to disk - char filename[64]; - std::sprintf(filename, "uDAQ/data%04d.h5", currentBuffer); - std::cout << "uDAQ: Starting with file: " << filename << std::endl; - bufferNumber.open("uDAQ/currentBuffer", std::ofstream::out); - bufferNumber << currentBuffer << std::endl; - bufferNumber.close(); - - // update file number process variables - _owner->currentFile = currentBuffer; - _owner->currentFile.write(); - - // open file - try { - outFile = H5::H5File(filename, H5F_ACC_TRUNC); - } - catch(H5::FileIException &e) { - return; + // update file number process variables + _owner->currentFile = currentBuffer; + _owner->currentFile.write(); + + // open file + try { + outFile = H5::H5File(filename, H5F_ACC_TRUNC); + } + catch(H5::FileIException &) { + return; + } + isOpened = true; + } - isOpened = true; - - } - else if(isOpened && _owner->enable == 0) { - outFile.close(); - isOpened = false; - } - - // if file is opened, this trigger should be included in the DAQ - if(isOpened) { - - // write data - writeData(); - - // increment counter, after 100 triggers written to the same file, switch the file - /// @todo make all this configurable! - nFillsInBuffer++; - if(nFillsInBuffer > _owner->nTriggersPerFile) { - - // increment file number. use at most 1000 files, overwrite old files - currentBuffer++; - if(currentBuffer >= _owner->nMaxFiles) currentBuffer = 0; - nFillsInBuffer = 0; - - // just close the file here, will re-open on next trigger + else if(isOpened && _owner->enable == 0) { outFile.close(); isOpened = false; } - } - } - /*********************************************************************************************************************/ + // if file is opened, this trigger should be included in the DAQ + if(isOpened) { - struct DataWriter { - DataWriter(H5storage &storage) : _storage(storage) {} - - template<typename PAIR> - void operator()(PAIR &pair) const { - typedef typename PAIR::first_type UserType; - - // get the lists for the UserType - auto &accessorList = pair.second; - auto &decimationFactorList = boost::fusion::at_key<UserType>(_storage.decimationFactorListMap.table); - auto &dataSpaceList = boost::fusion::at_key<UserType>(_storage.dataSpaceListMap.table); - auto &nameList = boost::fusion::at_key<UserType>(_storage._owner->nameListMap.table); - - // iterate through all accessors for this UserType - auto decimationFactor = decimationFactorList.begin(); - auto dataSpace = dataSpaceList.begin(); - auto name = nameList.begin(); - for(auto accessor = accessorList.begin() ; accessor != accessorList.end() ; ++accessor , ++decimationFactor, ++dataSpace, ++name) { - - // form full path name of data set - std::string dataSetName = _storage.currentGroupName+"/"+*name; - - // write to file (this is mainly a function call to allow template specialisations at this point) - try { - write2hdf<UserType>(*accessor, dataSetName, *decimationFactor, *dataSpace); + // write data + writeData(); + + // increment counter, after nTriggersPerFile triggers written to the same file, switch the file + nFillsInBuffer++; + if(nFillsInBuffer > _owner->nTriggersPerFile) { + + // increment file number. use at most nMaxFiles files, overwrite old files + currentBuffer++; + if(currentBuffer >= _owner->nMaxFiles) currentBuffer = 0; + nFillsInBuffer = 0; + + // just close the file here, will re-open on next trigger + outFile.close(); + isOpened = false; } - catch(H5::FileIException&) { - std::cout << "MicroDAQ: ERROR writing data set " << dataSetName << std::endl; - throw; + } + } + + /*********************************************************************************************************************/ + + struct DataWriter { + DataWriter(detail::H5storage &storage) : _storage(storage) {} + + template<typename PAIR> + void operator()(PAIR &pair) const { + typedef typename PAIR::first_type UserType; + + // get the lists for the UserType + auto &accessorList = pair.second; + auto &decimationFactorList = boost::fusion::at_key<UserType>(_storage.decimationFactorListMap.table); + auto &dataSpaceList = boost::fusion::at_key<UserType>(_storage.dataSpaceListMap.table); + auto &nameList = boost::fusion::at_key<UserType>(_storage._owner->nameListMap.table); + + // iterate through all accessors for this UserType + auto decimationFactor = decimationFactorList.begin(); + auto dataSpace = dataSpaceList.begin(); + auto name = nameList.begin(); + for(auto accessor = accessorList.begin() ; accessor != accessorList.end() ; ++accessor , ++decimationFactor, ++dataSpace, ++name) { + + // form full path name of data set + std::string dataSetName = _storage.currentGroupName+"/"+*name; + + // write to file (this is mainly a function call to allow template specialisations at this point) + try { + write2hdf<UserType>(*accessor, dataSetName, *decimationFactor, *dataSpace); + } + catch(H5::FileIException&) { + std::cout << "MicroDAQ: ERROR writing data set " << dataSetName << std::endl; + throw; + } } } + + template<typename UserType> + void write2hdf(ArrayPollInput<UserType> &accessor, std::string &name, size_t decimationFactor, H5::DataSpace &dataSpace) const; + + H5storage &_storage; + }; + + /*********************************************************************************************************************/ + + template<typename UserType> + void DataWriter::write2hdf(ArrayPollInput<UserType> &accessor, std::string &dataSetName, size_t decimationFactor, H5::DataSpace &dataSpace) const { + + // prepare decimated buffer + size_t n = accessor.getNElements()/decimationFactor; + std::vector<float> buffer(n); + for(size_t i=0; i<n; ++i) { + buffer[i] = accessor[i*decimationFactor]; } - - template<typename UserType> - void write2hdf(ArrayPollInput<UserType> &accessor, std::string &name, size_t decimationFactor, H5::DataSpace &dataSpace) const; - H5storage &_storage; - }; + // write data from internal buffer to data set in HDF5 file + H5::DataSet dataset = _storage.outFile.createDataSet(dataSetName, H5::PredType::NATIVE_FLOAT, dataSpace); + dataset.write(buffer.data(), H5::PredType::NATIVE_FLOAT); + } - /*********************************************************************************************************************/ + /*********************************************************************************************************************/ - template<typename UserType> - void DataWriter::write2hdf(ArrayPollInput<UserType> &accessor, std::string &dataSetName, size_t decimationFactor, H5::DataSpace &dataSpace) const { + template<> + void DataWriter::write2hdf<std::string>(ArrayPollInput<std::string> &accessor, std::string &dataSetName, size_t, H5::DataSpace &dataSpace) const { - // prepare decimated buffer - size_t n = accessor.getNElements()/decimationFactor; - std::vector<float> buffer(n); - for(size_t i=0; i<n; ++i) { - buffer[i] = accessor[i*decimationFactor]; + // write data from internal buffer to data set in HDF5 file + H5::DataSet dataset = _storage.outFile.createDataSet(dataSetName, H5::PredType::C_S1, dataSpace); + dataset.write(accessor[0].c_str(), H5::PredType::NATIVE_FLOAT); } - - // write data from internal buffer to data set in HDF5 file - H5::DataSet dataset = _storage.outFile.createDataSet(dataSetName, H5::PredType::NATIVE_FLOAT, dataSpace); - dataset.write(buffer.data(), H5::PredType::NATIVE_FLOAT); - } - /*********************************************************************************************************************/ + /*********************************************************************************************************************/ - template<> - void DataWriter::write2hdf<std::string>(ArrayPollInput<std::string> &accessor, std::string &dataSetName, size_t, H5::DataSpace &dataSpace) const { - - // write data from internal buffer to data set in HDF5 file - H5::DataSet dataset = _storage.outFile.createDataSet(dataSetName, H5::PredType::C_S1, dataSpace); - dataset.write(accessor[0].c_str(), H5::PredType::NATIVE_FLOAT); - } + void H5storage::writeData() { - /*********************************************************************************************************************/ + // format current time + struct timeval tv; + gettimeofday(&tv, nullptr); + time_t t = tv.tv_sec; + if(t == 0) t = time(nullptr); + struct tm *tmp = localtime(&t); + char timeString[64]; + std::sprintf(timeString, "%04d-%02d-%02d %02d:%02d:%02d.%03d", 1900+tmp->tm_year, tmp->tm_mon+1, tmp->tm_mday, + tmp->tm_hour, tmp->tm_min, tmp->tm_sec, static_cast<int>(tv.tv_usec / 1000)); - void H5storage::writeData() { - - // format current time - struct timeval tv; - gettimeofday(&tv, NULL); - time_t t = tv.tv_sec; - if(t == 0) t = time(NULL); - struct tm *tmp = localtime(&t); - char timeString[64]; - std::sprintf(timeString, "%04d-%02d-%02d %02d:%02d:%02d.%03d", 1900+tmp->tm_year, tmp->tm_mon+1, tmp->tm_mday, - tmp->tm_hour, tmp->tm_min, tmp->tm_sec, (int)(tv.tv_usec / 1000)); - - // create groups - currentGroupName = std::string("/")+std::string(timeString); - try { - outFile.createGroup(currentGroupName); - for(auto &group : groupList) outFile.createGroup(currentGroupName+"/"+group); - } - catch(H5::FileIException &e) { - outFile.close(); - isOpened = false; // will re-open file on next trigger - return; - } - - // read all input data - _owner->readAllLatest(); + // create groups + currentGroupName = std::string("/")+std::string(timeString); + try { + outFile.createGroup(currentGroupName); + for(auto &group : groupList) outFile.createGroup(currentGroupName+"/"+group); + } + catch(H5::FileIException &) { + outFile.close(); + isOpened = false; // will re-open file on next trigger + return; + } - // write all data to file - boost::fusion::for_each(_owner->accessorListMap.table, DataWriter(*this)); - - } + // read all input data + _owner->readAllLatest(); - /*********************************************************************************************************************/ + // write all data to file + boost::fusion::for_each(_owner->accessorListMap.table, DataWriter(*this)); + + } + + /*********************************************************************************************************************/ + + } // namespace detail } // namespace ChimeraTK -- GitLab