Skip to content
Snippets Groups Projects
Commit 49481410 authored by Martin Christoph Hierholzer's avatar Martin Christoph Hierholzer
Browse files

Improvements for MicroDAQ module:

- Make data decimation configurable
- Addded some documentation
- Some code cleaning (eliminate warnings, move internal classes into detail namespace)
parent d86ec347
No related branches found
No related tags found
No related merge requests found
......@@ -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
......
......@@ -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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment