add reftrack.h
Add global Reference Counter tracker
This commit is contained in:
@@ -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
149
src/misc/pv/reftrack.h
Normal 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
292
src/misc/reftrack.cpp
Normal 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());
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
102
testApp/misc/test_reftrack.cpp
Normal file
102
testApp/misc/test_reftrack.cpp
Normal 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();
|
||||
}
|
||||
Reference in New Issue
Block a user