add reftrack.h

Add global Reference Counter tracker
This commit is contained in:
Michael Davidsaver
2017-08-29 16:22:52 -05:00
parent 3597fbe382
commit 34896560ea
5 changed files with 549 additions and 0 deletions

View File

@@ -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

149
src/misc/pv/reftrack.h Normal file
View File

@@ -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 <map>
#include <string>
#include <ostream>
#include <stdlib.h>
#include <epicsVersion.h>
#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 <epicsAtomic.h>
# 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 <shareLib.h>
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<std::string, Count> 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

292
src/misc/reftrack.cpp Normal file
View File

@@ -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 <iostream>
#include <sstream>
#include <stdexcept>
#include <memory>
#include <stdlib.h>
#include <string.h>
#include <epicsString.h>
#include <epicsGuard.h>
#include <epicsMutex.h>
#include <epicsEvent.h>
#include <epicsThread.h>
#include <epicsTime.h>
#define epicsExportSharedSymbols
#include <pv/pvdVersion.h>
#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<epicsMutex> Guard;
typedef epicsGuardRelease<epicsMutex> UnGuard;
struct refgbl_t {
epicsMutex lock;
typedef std::map<std::string, const size_t*> 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 :"<<e.what()<<"\n";
}
}
epicsThreadOnceId refgbl_once = EPICS_THREAD_ONCE_INIT;
void refgbl_setup()
{
epicsThreadOnce(&refgbl_once, &refgbl_init, 0);
if(!refgbl)
throw std::runtime_error("Failed to initialize global ref. counter registry");
}
} // namespace
namespace epics {
void registerRefCounter(const char *name, const size_t* counter)
{
refgbl_setup();
Guard G(refgbl->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<<it->first<<":\t"<<it->second.current<<" (delta "<<it->second.delta<<")\n";
}
return strm;
}
struct RefMonitor::Impl : public epicsThreadRunable
{
RefMonitor& owner;
std::auto_ptr<epicsThread> 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<epicsThread> 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<<buf<<" : References\n";
for(RefSnapshot::const_iterator it = snap.begin(), end = snap.end(); it!=end; ++it)
{
// print if delta!=0 or (complete && current!=0)
if(it->second.delta==0 && (!complete || it->second.current==0)) continue;
std::cerr<<it->first<<":\t"<<it->second.current<<" (delta "<<it->second.delta<<")\n";
}
}
} // namespace epics
char* epicsRefSnapshotCurrent()
{
try {
epics::RefSnapshot snap;
snap.update();
std::ostringstream strm;
strm<<snap;
const char *str = strm.str().c_str();
char *ret = (char*)malloc(strlen(str)+1);
if(ret)
strcpy(ret, str);
return ret;
}catch(std::exception& e){
return epicsStrDup(e.what());
}
}

View File

@@ -76,3 +76,7 @@ TESTS += testUnitTest
TESTPROD_HOST += testjson
testjson_SRCS += testjson.cpp
TESTS += testjson
TESTPROD_HOST += test_reftrack
test_reftrack_SRCS += test_reftrack.cpp
TESTS += test_reftrack

View File

@@ -0,0 +1,102 @@
/*
* Copyright information and license terms for this software can be
* found in the file LICENSE that is included with the distribution
*/
#include <stdexcept>
#include <epicsUnitTest.h>
#include <testMain.h>
#include <pv/epicsException.h>
#include <pv/reftrack.h>
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();
}