diff --git a/src/misc/Makefile b/src/misc/Makefile index 346a9a5..06d0dd5 100644 --- a/src/misc/Makefile +++ b/src/misc/Makefile @@ -24,6 +24,7 @@ INC += pv/sharedVector.h INC += pv/templateMeta.h INC += pv/current_function.h INC += pv/pvUnitTest.h +INC += pv/reftrack.h LIBSRCS += byteBuffer.cpp LIBSRCS += bitSet.cpp @@ -38,3 +39,4 @@ LIBSRCS += localStaticLock.cpp LIBSRCS += typeCast.cpp LIBSRCS += parseToPOD.cpp LIBSRCS += debugPtr.cpp +LIBSRCS += reftrack.cpp diff --git a/src/misc/pv/reftrack.h b/src/misc/pv/reftrack.h new file mode 100644 index 0000000..d4b8052 --- /dev/null +++ b/src/misc/pv/reftrack.h @@ -0,0 +1,149 @@ +/* + * Copyright information and license terms for this software can be + * found in the file LICENSE that is included with the distribution + */ +#ifndef REFTRACK_H +#define REFTRACK_H + +#ifdef __cplusplus + +#include +#include +#include + +#include + +#include + +#ifndef VERSION_INT +# define VERSION_INT(V,R,M,P) ( ((V)<<24) | ((R)<<16) | ((M)<<8) | (P)) +#endif + +#ifndef EPICS_VERSION_INT +# define EPICS_VERSION_INT VERSION_INT(EPICS_VERSION, EPICS_REVISION, EPICS_MODIFICATION, EPICS_PATCH_LEVEL) +#endif + +#if EPICS_VERSION_INT>=VERSION_INT(3,15,1,0) +# include +# define REFTRACK_USE_ATOMIC +#endif + +#ifdef REFTRACK_USE_ATOMIC +# define REFTRACE_INCREMENT(counter) ::epics::atomic::increment(counter) +# define REFTRACE_DECREMENT(counter) ::epics::atomic::decrement(counter) +#else +# define REFTRACE_INCREMENT(counter) do{}while(0) +# define REFTRACE_DECREMENT(counter) do{}while(0) +#endif + +#include + +namespace epics { + +//! Register new global reference counter +epicsShareFunc +void registerRefCounter(const char *name, const size_t* counter); + +//! Remove registration of global reference counter (if dynamically allocated) +epicsShareFunc +void unregisterRefCounter(const char *name, const size_t* counter); + +//! Fetch current value of single reference counter +epicsShareFunc +size_t readRefCounter(const char *name); + +//! Represent a snapshot of many reference counters +class epicsShareClass RefSnapshot +{ +public: + //! A single count + struct Count { + size_t current; + ssize_t delta; //!< current - previous + Count() :current(0u), delta(0) {} + explicit Count(size_t c, ssize_t d) :current(c), delta(d) {} + bool operator==(const Count& o) const + { return current==o.current && delta==o.delta; } + }; + +private: + static const Count zero; + typedef std::map cnt_map_t; + cnt_map_t counts; +public: + typedef cnt_map_t::const_iterator iterator; + typedef cnt_map_t::const_iterator const_iterator; + + /** Fetch values of all reference counters. + * + * This involves many atomic reads, not a single operation. + */ + void update(); + + const Count& operator[](const std::string& name) const + { + cnt_map_t::const_iterator it(counts.find(name)); + return it==counts.end() ? zero : it->second; + } + + iterator begin() const { return counts.begin(); } + iterator end() const { return counts.end(); } + size_t size() const { return counts.size(); } + + inline void swap(RefSnapshot& o) + { + counts.swap(o.counts); + } + + friend RefSnapshot operator-(const RefSnapshot& lhs, const RefSnapshot& rhs); +}; + +/** Compute the difference lhs - rhs + * + * Returned RefSnapshot has Count::current=lhs.current + * and Count::delta= lhs.current - rhs.current + */ +epicsShareFunc +RefSnapshot operator-(const RefSnapshot& lhs, const RefSnapshot& rhs); + +//! Print all counters with a non-zero delta +epicsShareFunc +std::ostream& operator<<(std::ostream& strm, const RefSnapshot& snap); + +//! Helper to run a thread which periodically prints (via show() ) +//! global reference counter deltas. +class epicsShareClass RefMonitor +{ + struct Impl; + Impl *impl; +public: + RefMonitor(); + ~RefMonitor(); + + void start(double period=10.0); + void stop(); + bool running() const; + + //! call show() with current snapshot + void current(); +protected: + //! Default prints to stderr + //! @param complete when false show only non-zero delta, when true show non-zero count or delta + virtual void show(const RefSnapshot& snap, bool complete=false); +}; + +} // namespace epics + +extern "C" { +#endif /* __cplusplus */ + +/** Fetch and print current snapshot + * @return NULL or a char* which must be free()'d + */ +char* epicsRefSnapshotCurrent(); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif // REFTRACK_H diff --git a/src/misc/reftrack.cpp b/src/misc/reftrack.cpp new file mode 100644 index 0000000..2031620 --- /dev/null +++ b/src/misc/reftrack.cpp @@ -0,0 +1,292 @@ +/* + * Copyright information and license terms for this software can be + * found in the file LICENSE that is included with the distribution + */ + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#define epicsExportSharedSymbols +#include +#include "pv/reftrack.h" + +namespace { + +#ifndef REFTRACK_USE_ATOMIC +static inline +size_t readref(const size_t *ref) +{ + volatile const size_t *vref = ref; + return *vref; +} +#endif + +typedef epicsGuard Guard; +typedef epicsGuardRelease UnGuard; + +struct refgbl_t { + epicsMutex lock; + typedef std::map counters_t; + counters_t counters; +} *refgbl; + +void refgbl_init(void *) +{ + try { + refgbl = new refgbl_t; + } catch(std::exception& e) { + std::cerr<<"Failed to initialize global ref. counter registry :"<lock); + refgbl->counters[name] = counter; +} + +void unregisterRefCounter(const char *name, const size_t* counter) +{ + refgbl_setup(); + Guard G(refgbl->lock); + refgbl_t::counters_t::iterator it(refgbl->counters.find(name)); + if(it!=refgbl->counters.end() && it->second==counter) + refgbl->counters.erase(it); +} + +size_t readRefCounter(const char *name) +{ + refgbl_setup(); + Guard G(refgbl->lock); + refgbl_t::counters_t::iterator it(refgbl->counters.find(name)); + if(it==refgbl->counters.end()) + return 0; +#ifdef REFTRACK_USE_ATOMIC + return atomic::get(*it->second); +#else + return readref(it->second); +#endif +} + +const RefSnapshot::Count RefSnapshot::zero; + +void RefSnapshot::update() +{ + refgbl_t::counters_t counters; + { + refgbl_setup(); + Guard G(refgbl->lock); + counters = refgbl->counters; // copy + } + + counts.clear(); + + for(refgbl_t::counters_t::const_iterator it=counters.begin(), + end=counters.end(); + it!=end; ++it) + { +#ifdef REFTRACK_USE_ATOMIC + size_t cnt = atomic::get(*it->second); +#else + size_t cnt = readref(it->second); +#endif + + counts[it->first] = Count(cnt, 0); + } +} + +RefSnapshot operator-(const RefSnapshot& lhs, const RefSnapshot& rhs) +{ + RefSnapshot ret; + + RefSnapshot::cnt_map_t::const_iterator lit = lhs.counts.begin(), + lend= lhs.counts.end(), + rit = rhs.counts.begin(), + rend= rhs.counts.end(); + + while(lit!=lend || rit!=rend) + { + if(lit==lend || (rit!=rend && lit->first > rit->first)) { + ret.counts[rit->first] = RefSnapshot::Count(0, -ssize_t(rit->second.current)); + ++rit; + + } else if(rit==rend || lit->first < rit->first) { + ret.counts[lit->first] = RefSnapshot::Count(lit->second.current, lit->second.current); + ++lit; + + } else { // !end and lit->first == rit->first + ret.counts[lit->first] = RefSnapshot::Count(lit->second.current, + ssize_t(lit->second.current) - ssize_t(rit->second.current)); + ++lit; + ++rit; + } + } + + return ret; +} + +std::ostream& operator<<(std::ostream& strm, const RefSnapshot& snap) +{ + for(RefSnapshot::const_iterator it = snap.begin(), end = snap.end(); it!=end; ++it) + { + if(it->second.delta==0) continue; + strm<first<<":\t"<second.current<<" (delta "<second.delta<<")\n"; + } + return strm; +} + +struct RefMonitor::Impl : public epicsThreadRunable +{ + RefMonitor& owner; + std::auto_ptr worker; + epicsMutex lock; + epicsEvent wakeup; + RefSnapshot prev; + bool done; + double period; + Impl(RefMonitor* owner) :owner(*owner), done(false), period(10.0) {} + virtual ~Impl() {} + + virtual void run() + { + Guard G(lock); + while(!done) { + RefSnapshot current, P; + P = prev; // copy + { + UnGuard U(G); + + current.update(); + + owner.show(current-P); + } + + prev.swap(current); + + { + UnGuard U(G); + wakeup.wait(period); + } + } + } +}; + +RefMonitor::RefMonitor() + :impl(new Impl(this)) +{} +RefMonitor::~RefMonitor() +{ + stop(); + delete impl; +} + +void RefMonitor::start(double period) +{ + Guard G(impl->lock); + if(impl->worker.get()) return; + + impl->done = false; + impl->period = period; + impl->worker.reset(new epicsThread(*impl, + "RefMonitor", + epicsThreadGetStackSize(epicsThreadStackSmall), + epicsThreadPriorityMin)); + impl->worker->start(); +} + +void RefMonitor::stop() +{ + std::auto_ptr W; + { + Guard G(impl->lock); + if(!impl->worker.get()) return; + W = impl->worker; + impl->done = true; + } + + impl->wakeup.signal(); + W->exitWait(); + + W.reset(); +} + +bool RefMonitor::running() const +{ + Guard G(impl->lock); + return !!impl->worker.get(); +} + +void RefMonitor::current() +{ + RefSnapshot current, P; + current.update(); + + { + Guard G(impl->lock); + P = impl->prev; // copy + } + + show(current-P, true); +} + +void RefMonitor::show(const RefSnapshot &snap, bool complete) +{ + epicsTime now(epicsTime::getCurrent()); + char buf[80]; + epicsTime::getCurrent().strftime(buf, sizeof(buf), "%a %b %d %Y %H:%M:%S.%f"); + buf[sizeof(buf)-1] = '\0'; + std::cerr<second.delta==0 && (!complete || it->second.current==0)) continue; + std::cerr<first<<":\t"<second.current<<" (delta "<second.delta<<")\n"; + } +} + +} // namespace epics + + +char* epicsRefSnapshotCurrent() +{ + try { + epics::RefSnapshot snap; + snap.update(); + std::ostringstream strm; + strm< + +#include +#include + +#include +#include + +namespace { + +void testReg() +{ + testDiag("testReg()"); + + static size_t cnt1 = 1; + + epics::registerRefCounter("cnt1", &cnt1); + + testOk1(epics::readRefCounter("cnt1")==1); + + cnt1 = 2; + + testOk1(epics::readRefCounter("cnt1")==2); + + epics::unregisterRefCounter("cnt1", &cnt1); + + testOk1(epics::readRefCounter("cnt1")==0); +} + +void testSnap() +{ + testDiag("testSnap()"); + + static size_t cnt1 = 1, + cnt2 = 10; + + epics::registerRefCounter("cnt1", &cnt1); + epics::registerRefCounter("cnt2", &cnt2); + + epics::RefSnapshot snap1; + snap1.update(); + + cnt1 = 2; + cnt2 = 11; + static size_t cnt3 = 21; + epics::registerRefCounter("cnt3", &cnt3); + + epics::RefSnapshot snap2; + snap2.update(); + + cnt1 = 4; + cnt2 = 13; + cnt3 = 23; + epics::unregisterRefCounter("cnt1", &cnt1); + + epics::RefSnapshot snap3; + snap3.update(); + + testOk1(snap1.size()>=2); + testOk1(snap2.size()>=3); + testOk1(snap3.size()>=2); + + epics::RefSnapshot delta12(snap2-snap1); + epics::RefSnapshot delta13(snap3-snap1); + epics::RefSnapshot delta23(snap3-snap2); + + testOk1(delta12.size()>=3); + testOk1(delta13.size()>=3); + testOk1(delta23.size()>=3); + + testOk1(delta12["cnt1"]==epics::RefSnapshot::Count(2, 1)); + testOk1(delta12["cnt2"]==epics::RefSnapshot::Count(11, 1)); + testOk1(delta12["cnt3"]==epics::RefSnapshot::Count(21, 21)); + + testOk1(delta23["cnt1"]==epics::RefSnapshot::Count(0, -2)); + testOk1(delta23["cnt2"]==epics::RefSnapshot::Count(13, 2)); + testOk1(delta23["cnt3"]==epics::RefSnapshot::Count(23, 2)); + + testOk1(delta13["cnt1"]==epics::RefSnapshot::Count(0, -1)); + testOk1(delta13["cnt2"]==epics::RefSnapshot::Count(13, 3)); + testOk1(delta13["cnt3"]==epics::RefSnapshot::Count(23, 23)); +} + +} // namespace + +MAIN(test_reftrack) +{ + testPlan(18); + try { + testReg(); + testSnap(); + }catch(std::exception& e){ + PRINT_EXCEPTION(e); + testAbort("Unexpected exception: %s", e.what()); + } + return testDone(); +}