diff --git a/pvDataApp/Makefile b/pvDataApp/Makefile index 50c3f87..992c394 100644 --- a/pvDataApp/Makefile +++ b/pvDataApp/Makefile @@ -92,9 +92,16 @@ LIBSRCS += bitSetUtil.cpp SRC_DIRS += $(PVDATA)/monitor INC += monitor.h +SRC_DIRS += $(PVDATA)/mb +INC += mb.h +LIBSRCS += mb.cpp -LIBRARY=pvData +PROD_HOST += mb_stat +mb_stat_CXXFLAGS += -DPV_MB +mb_stat_SRCS += mb_stat.cpp +mb_stat_LIBS += pvData Com +LIBRARY = pvData pvData_LIBS += Com include $(TOP)/configure/RULES diff --git a/pvDataApp/mb/mb.cpp b/pvDataApp/mb/mb.cpp new file mode 100644 index 0000000..b443e78 --- /dev/null +++ b/pvDataApp/mb/mb.cpp @@ -0,0 +1,301 @@ +#include "mb.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +#if defined(__APPLE__) +#include +uint64_t MBTime() +{ + return mach_absolute_time(); +} +#else +uint64_t MBTime() +{ + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + return static_cast(ts.tv_sec) * 1000000000 + static_cast(ts.tv_nsec); +} +#endif + + +void MBPointAdd(MBEntity &e, intptr_t id, uint8_t stage) +{ + // no copy and no MBPoint init solution + const std::size_t ix = ATOMIC_GET_AND_INCREMENT(e.pos); + MBPoint& p = e.points[ix]; + p.id = id; p.stage = stage; + p.time = MBTime(); +} + +void MBCSVExport(MBEntity &e, std::ostream &o) +{ + const std::size_t len = ATOMIC_GET(e.pos); + for (std::size_t i = 0; i < len; i++) + { + MBPoint& p = e.points[i]; + o << p.id << ',' << static_cast(p.stage) << ',' << p.time << std::endl; + } +} + +// NOTE: this method is not thread-safe +void MBCSVImport(MBEntity &e, std::istream &i) +{ + std::string line; + char c; + + e.points.resize(0); + size_t lc = 0; + while (getline(i,line)) + { + lc++; + std::istringstream is(line); + MBPoint p; + is >> p.id >> c; + uint32_t s; is >> s >> c; p.stage = s; + is >> p.time; + + if (is.good() || is.eof()) + e.points.push_back(p); + else + { + std::cerr << "failed to parse line " << lc << ": \"" << line << "\"" << std::endl; + } + } + e.pos = e.points.size(); +} + +void MBNormalize(MBEntity &e) +{ + std::map lastTime; + + const std::size_t len = ATOMIC_GET(e.pos); + for (std::size_t i = 0; i < len; i++) + { + MBPoint& p = e.points[i]; + if (p.stage == 0) + lastTime[p.id] = p.time; + + std::map::iterator last = lastTime.find(p.id); + if (last == lastTime.end()) + { + std::cerr << "no 0 stage for " << e.name << ", id = " << p.id << std::endl; + p.time = -1; // TODO error?! + } + else + { + uint64_t lt = last->second; + last->second = p.time; + p.time -= lt; + } + } +} + +struct MBStatistics +{ + std::size_t count; + uint64_t min; + uint64_t max; + uint64_t rms; + + MBStatistics() : + count(0), + min(-1), + max(0), + rms(0.0) + {} + + MBStatistics(uint64_t sample) : + count(1), + min(sample), + max(sample), + rms(sample*sample) + { + }; + + void addSample(uint64_t sample) + { + count++; + if (sample < min) min = sample; + if (sample > max) max = sample; + rms += sample*sample; + }; +}; + +typedef std::map StatsMapPerStage; + +void MBStats(MBEntity &e, std::ostream &o) +{ + MBNormalize(e); + + StatsMapPerStage stats; + + const std::size_t len = ATOMIC_GET(e.pos); + for (std::size_t i = 0; i < len; i++) + { + MBPoint& p = e.points[i]; + + // first stage is start time, skip + if (p.stage == 0) + continue; + + StatsMapPerStage::iterator s = stats.find(p.stage); + if (s == stats.end()) + stats[p.stage] = MBStatistics(p.time); + else + s->second.addSample(p.time); + } + + uint64_t smin = 0; + uint64_t smax = 0; + double srms = 0; + + for (StatsMapPerStage::iterator i = stats.begin(); + i != stats.end(); + i++) + { + smin += i->second.min; + smax += i->second.max; + double rrms = sqrt(i->second.rms/(double)i->second.count); + srms += rrms; + + o << "stage " << std::setw(4) << static_cast(i->first) + << ": min = " << std::setw(16) << i->second.min + << ", max = " << std::setw(16) << i->second.max + << ", rms = " << std::setw(16) << static_cast(rrms) << std::endl; + } + + o << std::string(82,'-') << std::endl; + + o << "stage " << std::setw(4) << "sum" + << ": min = " << std::setw(16) << smin + << ", max = " << std::setw(16) << smax + << ", rms = " << std::setw(16) << static_cast(srms) << std::endl; +} + +typedef std::vector EntitiesVector; + +static int nifty_counter; +static epicsMutex* MBMutex; + +// The counter is initialized at load-time, i.e., before any of the static objects are initialized. +MBMutexInitializer::MBMutexInitializer () +{ + if (0 == nifty_counter++) + { + // Initialize static members. + MBMutex = new epicsMutex(); + } +} + +MBMutexInitializer::~MBMutexInitializer () +{ + if (0 == --nifty_counter) + { + // Clean-up. + delete MBMutex; + } +} + +class MutexLock { +public: + + explicit MutexLock(epicsMutex &m) : + mutexPtr(m), + locked(true) + { + mutexPtr.lock(); + } + + ~MutexLock() + { + unlock(); + } + + void lock() + { + if (!locked) + { + mutexPtr.lock(); + locked = true; + } + } + void unlock() + { + if (locked) + { + mutexPtr.unlock(); + locked = false; + } + } + +private: + epicsMutex &mutexPtr; + bool locked; +}; + + +void MBEntityRegister(MBEntity *e) +{ + MutexLock lock(*MBMutex); + static EntitiesVector MBEntities; + + if (e) + { + MBEntities.push_back(e); + } + else + { + for(EntitiesVector::const_iterator i = MBEntities.begin(); + i != MBEntities.end(); + i++) + { + // skip empty entities + if ((*i)->pos) + { + char fileName[128]; + char* path = getenv("MB_OUTPUT_DIR"); + if (path == 0) path = const_cast("."); + snprintf(fileName, 128, "%s/mb_%s_%d.csv", path, (*i)->name.c_str(), getpid()); + std::ofstream out(fileName); + if (out.is_open()) + { + MBCSVExport(*(*i), out); + out.close(); + } + else + { + std::cerr << "failed to create a file " << fileName << ", skipping..." << std::endl; + } + } + } + } +} + +void MBAtExit() +{ + MBEntityRegister(0); +} + + +void MBInit() +{ + MutexLock lock(*MBMutex); + static bool inited = false; + if (!inited) + { + inited = true; + atexit(MBAtExit); + } +} + diff --git a/pvDataApp/mb/mb.h b/pvDataApp/mb/mb.h new file mode 100644 index 0000000..966e58f --- /dev/null +++ b/pvDataApp/mb/mb.h @@ -0,0 +1,130 @@ +#ifndef _MB_H_ +#define _MB_H_ + +#include +#include +#include + +#include + +#include + +#if EPICS_VERSION_INT >= VERSION_INT(3,15,0,0) +//#include +// epicsAtomic only support int and size_t type +// TODO fully fenced (not optimal) +#define ATOMIC_GET_AND_INCREMENT(VAR) __sync_fetch_and_add(&VAR, 1) +#define ATOMIC_SET_ZERO(VAR) __sync_fetch_and_and(&VAR, 0) +#define ATOMIC_GET(VAR) __sync_fetch_and_or(&VAR, 0) +#else +// try to get away with GCC internals, or build will simply fail +#define ATOMIC_GET_AND_INCREMENT(VAR) __sync_fetch_and_add(&VAR, 1) +#define ATOMIC_SET_ZERO(VAR) __sync_fetch_and_and(&VAR, 0) +#define ATOMIC_GET(VAR) __sync_fetch_and_or(&VAR, 0) +#endif + +static class MBMutexInitializer { + public: + MBMutexInitializer (); + ~MBMutexInitializer (); +} mbStaticMutexInitializer; // Note object here in the header. + +struct MBPoint +{ + intptr_t id; + uint8_t stage; + uint64_t time; + + MBPoint() {} + MBPoint(intptr_t _id, uint8_t _stage) : id(_id), stage(_stage) {} +}; + +struct MBEntity; + +extern void MBEntityRegister(MBEntity *e); + +typedef std::vector MBPointType; + +struct MBEntity +{ + std::string name; + MBPointType points; + volatile std::size_t pos; + volatile intptr_t auto_id; + + MBEntity(const std::string &name_, std::size_t size) : name(name_) + { + // init vector at the beginning + points.resize(size); + ATOMIC_SET_ZERO(pos); + ATOMIC_SET_ZERO(auto_id); + + MBEntityRegister(this); + } +}; + +extern uint64_t MBTime(); + +extern void MBPointAdd(MBEntity &e, intptr_t id, uint8_t stage); + +extern void MBCSVExport(MBEntity &e, std::ostream &o); +extern void MBCSVImport(MBEntity &e, std::istream &i); + +extern void MBStats(MBEntity &e, std::ostream &o); + +extern void MBNormalize(MBEntity &e); + + +extern void MBInit(); + +#if PV_MB + +#define MB_NAME(NAME) g_MB_##NAME + +#define MB_DECLARE(NAME, SIZE) MBEntity MB_NAME(NAME)(#NAME, SIZE) +#define MB_DECLARE_EXTERN(NAME) extern MBEntity MB_NAME(NAME) + +#define MB_POINT_ID(NAME, ID, STAGE) MBPointAdd(MB_NAME(NAME), ID, STAGE) + +#define MB_INC_AUTO_ID(NAME) ATOMIC_GET_AND_INCREMENT(MB_NAME(NAME).auto_id) +#define MB_POINT(NAME, STAGE) MBPointAdd(MB_NAME(NAME), MB_NAME(NAME).auto_id, STAGE) +#define MB_POINT_CONDITIONAL(NAME, STAGE, COND) if (COND) MBPointAdd(MB_NAME(NAME), MB_NAME(NAME).auto_id, STAGE) + +#define MB_NORMALIZE(NAME) MBNormalize(MB_NAME(NAME)) + +#define MB_STATS(NAME, STREAM) MBStats(MB_NAME(NAME), STREAM) + +#define MB_CSV_EXPORT(NAME, STREAM) MBCSVExport(MB_NAME(NAME), STREAM) +#define MB_CSV_IMPORT(NAME, STREAM) MBCSVImport(MB_NAME(NAME), STREAM) + +#define MB_PRINT(NAME, STREAM) MB_CSV_EXPORT(NAME, STREAM) + +#define MB_INIT MBInit() + + +#else + +#define MB_DECLARE(NAME, SIZE) +#define MB_DECLARE_EXTERN(NAME) + +#define MB_POINT_ID(NAME, ID, STAGE) + +#define MB_INC_AUTO_ID(NAME) +#define MB_POINT(NAME, STAGE) + +#define MB_POINT_CONDITIONAL(NAME, STAGE, COND) + +#define MB_NORMALIZE(NAME) + +#define MB_STATS(NAME, STREAM) + +#define MB_CSV_EXPORT(NAME, STREAM) +#define MB_CSV_IMPORT(NAME, STREAM) + +#define MB_PRINT(NAME, STREAM) + +#define MB_INIT + +#endif + +#endif diff --git a/pvDataApp/mb/mb_stat.cpp b/pvDataApp/mb/mb_stat.cpp new file mode 100644 index 0000000..e48fa8f --- /dev/null +++ b/pvDataApp/mb/mb_stat.cpp @@ -0,0 +1,50 @@ +#include +#include +#include "mb.h" +#include + +MB_DECLARE(e, 64000); + +// TODO command line options +int main(int argc, char** argv) +{ + // norm hack + bool normalizeOnly = false; + if (argc == 3 && strcmp(argv[2],"-n")==0) + { + argc = 2; + normalizeOnly = true; + } + + + if (argc != 2) + { + std::cerr << "usage: " << argv[0] << " " << std::endl; + return -1; + } + + char * fileName = argv[1]; + + std::ifstream in(fileName); + if (in.is_open()) + { + MB_CSV_IMPORT(e, in); + in.close(); + + if (normalizeOnly) + { + MB_NORMALIZE(e); + MB_PRINT(e, std::cout); + } + else + MB_STATS(e, std::cout); + } + else + { + std::cerr << "failed to open a file " << fileName << ", skipping..." << std::endl; + return -1; + } + + return 0; +} + diff --git a/testApp/Makefile b/testApp/Makefile index 2cb742e..f0afab6 100644 --- a/testApp/Makefile +++ b/testApp/Makefile @@ -5,5 +5,6 @@ DIRS += pv DIRS += property DIRS += monitor DIRS += capi +DIRS += mb include $(TOP)/configure/RULES_DIRS diff --git a/testApp/mb/Makefile b/testApp/mb/Makefile new file mode 100644 index 0000000..6fa2849 --- /dev/null +++ b/testApp/mb/Makefile @@ -0,0 +1,13 @@ +TOP=../.. + +include $(TOP)/configure/CONFIG + +PROD_HOST += mb_test +mb_test_CXXFLAGS += -DPV_MB +mb_test_SRCS += mb_test.cpp +mb_test_LIBS += pvData Com + +include $(TOP)/configure/RULES +#---------------------------------------- +# ADD RULES AFTER THIS LINE + diff --git a/testApp/mb/README b/testApp/mb/README new file mode 100644 index 0000000..16586e9 --- /dev/null +++ b/testApp/mb/README @@ -0,0 +1,49 @@ +channelGet +0 - client channelGet->get() +1 - client channelGet->serialize (start) +2 - client channelGet->serialize (end) +3 - server channelGet->deserialize request (start) +4 - server channelGet->deserialize request (end) +5 - server channelGet->getDone() +6 - server channelGet->serialize response (start) +7 - server channelGet->serialize response (end) +8 - client channelGet->deserialize (start) +9 - client channelGet->deserialize (end), just before channelGet->getDone() is called + + + +MB_DECLARE_EXPORT(channelGet); + +# 10000 - max size +MB_DECLARE(channelGet, 10000); + +# in main() to install atexit() hook +MB_INIT; + +MB_INC_AUTO_ID(channelGet); +MB_POINT(channelGet, 0); +MB_POINT(channelGet, 1); +MB_POINT(channelGet, 2); + +MB_INC_AUTO_ID(channelGet); +MB_POINT(channelGet, 3); +MB_POINT(channelGet, 4); +MB_POINT(channelGet, 5); +MB_POINT(channelGet, 6); +MB_POINT(channelGet, 7); + +MB_POINT(channelGet, 8); +MB_POINT(channelGet, 9); + + +---- + + +cat mb* | sort < mb.csv +mb_stat mb.csv + + +mb_stat mb.csv -n | grep ,2, | cut -d , -f 3 | gnuplot -p -e "plot '-'" + + +--- diff --git a/testApp/mb/mb_test.cpp b/testApp/mb/mb_test.cpp new file mode 100644 index 0000000..64ab697 --- /dev/null +++ b/testApp/mb/mb_test.cpp @@ -0,0 +1,17 @@ +#include +#include + +MB_DECLARE_EXTERN(test); +MB_DECLARE(test, 1000); + +int main() +{ + MB_INIT; + + for (int i = 0; i < 10; i++) + for (int j = 0; j < 100; j++) + MB_POINT_ID(test, i, j); + + return 0; +} +