434 lines
15 KiB
C++
434 lines
15 KiB
C++
/* monitor.h */
|
|
/*
|
|
* Copyright information and license terms for this software can be
|
|
* found in the file LICENSE that is included with the distribution
|
|
*/
|
|
/**
|
|
* @author mrk
|
|
*/
|
|
#ifndef MONITOR_H
|
|
#define MONITOR_H
|
|
|
|
#include <list>
|
|
#include <ostream>
|
|
|
|
#ifdef epicsExportSharedSymbols
|
|
# define monitorEpicsExportSharedSymbols
|
|
# undef epicsExportSharedSymbols
|
|
#endif
|
|
|
|
#include <epicsMutex.h>
|
|
#include <pv/status.h>
|
|
#include <pv/pvData.h>
|
|
#include <pv/sharedPtr.h>
|
|
#include <pv/bitSet.h>
|
|
#include <pv/createRequest.h>
|
|
|
|
#ifdef monitorEpicsExportSharedSymbols
|
|
# define epicsExportSharedSymbols
|
|
# undef monitorEpicsExportSharedSymbols
|
|
#endif
|
|
|
|
#include <pv/requester.h>
|
|
#include <pv/destroyable.h>
|
|
|
|
#include <shareLib.h>
|
|
|
|
namespace epics { namespace pvAccess {
|
|
|
|
class MonitorRequester;
|
|
class MonitorElement;
|
|
typedef std::tr1::shared_ptr<MonitorElement> MonitorElementPtr;
|
|
typedef std::vector<MonitorElementPtr> MonitorElementPtrArray;
|
|
|
|
class Monitor;
|
|
typedef std::tr1::shared_ptr<Monitor> MonitorPtr;
|
|
|
|
|
|
/**
|
|
* @brief An element for a monitorQueue.
|
|
*
|
|
* Class instance representing monitor element.
|
|
* @author mrk
|
|
*/
|
|
class epicsShareClass MonitorElement {
|
|
public:
|
|
POINTER_DEFINITIONS(MonitorElement);
|
|
MonitorElement(epics::pvData::PVStructurePtr const & pvStructurePtr);
|
|
const epics::pvData::PVStructurePtr pvStructurePtr;
|
|
const epics::pvData::BitSet::shared_pointer changedBitSet;
|
|
const epics::pvData::BitSet::shared_pointer overrunBitSet;
|
|
|
|
class Ref;
|
|
};
|
|
|
|
/** Access to Monitor subscription and queue
|
|
*
|
|
* Downstream interface to access a monitor queue (via poll() and release() )
|
|
*/
|
|
class epicsShareClass Monitor : public virtual Destroyable{
|
|
public:
|
|
POINTER_DEFINITIONS(Monitor);
|
|
typedef MonitorRequester requester_type;
|
|
|
|
virtual ~Monitor(){}
|
|
/**
|
|
* Start monitoring.
|
|
* @return completion status.
|
|
*/
|
|
virtual epics::pvData::Status start() = 0;
|
|
/**
|
|
* Stop Monitoring.
|
|
* @return completion status.
|
|
*/
|
|
virtual epics::pvData::Status stop() = 0;
|
|
/**
|
|
* If monitor has occurred return data.
|
|
* @return monitorElement for modified data.
|
|
* Must call get to determine if data is available.
|
|
*
|
|
* May recursively call MonitorRequester::unlisten()
|
|
*/
|
|
virtual MonitorElementPtr poll() = 0;
|
|
/**
|
|
* Release a MonitorElement that was returned by poll.
|
|
* A poll() must be called after the release() to check the presence of any modified data.
|
|
* @param monitorElement
|
|
*/
|
|
virtual void release(MonitorElementPtr const & monitorElement) = 0;
|
|
|
|
struct Stats {
|
|
size_t nfilled; //!< # of elements ready to be poll()d
|
|
size_t noutstanding; //!< # of elements poll()d but not released()d
|
|
size_t nempty; //!< # of elements available for new remote data
|
|
};
|
|
|
|
virtual void getStats(Stats& s) const {
|
|
s.nfilled = s.noutstanding = s.nempty = 0;
|
|
}
|
|
|
|
/**
|
|
* Report remote queue status.
|
|
* @param freeElements number of free elements.
|
|
*/
|
|
virtual void reportRemoteQueueStatus(epics::pvData::int32 freeElements) {}
|
|
};
|
|
|
|
/** A (single ownership) smart pointer to extract a MonitorElement from a Monitor queue
|
|
*
|
|
* To fetch a single element
|
|
@code
|
|
epics::pvAccess::Monitor::shared_pointer mon(....);
|
|
epics::pvAccess::MonitorElement::Ref elem(mon);
|
|
if(elem) {
|
|
// do something with element
|
|
assert(elem->pvStructurePtr->getSubField("foo"));
|
|
} else {
|
|
// queue was empty
|
|
}
|
|
@endcode
|
|
* To fetch all available elements (c++11)
|
|
@code
|
|
epics::pvAccess::Monitor::shared_pointer mon(....);
|
|
for(auto& elem : *mon) {
|
|
assert(elem.pvStructurePtr->getSubField("foo"));
|
|
}
|
|
@endcode
|
|
* To fetch all available elements (c++98)
|
|
@code
|
|
epics::pvAccess::Monitor::shared_pointer mon(....);
|
|
for(epics::pvAccess::MonitorElement::Ref it(mon); it; ++it) {
|
|
MonitorElement& elem(*it);
|
|
assert(elem.pvStructurePtr->getSubField("foo"));
|
|
}
|
|
@endcode
|
|
*/
|
|
class MonitorElement::Ref
|
|
{
|
|
Monitor* mon;
|
|
MonitorElementPtr elem;
|
|
public:
|
|
Ref() :mon(0), elem() {}
|
|
Ref(Monitor& M) :mon(&M), elem(mon->poll()) {}
|
|
Ref(const Monitor::shared_pointer& M) :mon(M.get()), elem(mon->poll()) {}
|
|
~Ref() { reset(); }
|
|
#if __cplusplus>=201103L
|
|
Ref(Ref&& o) :mon(o.mon), elem(o.elem) {
|
|
o.mon = 0;
|
|
o.elem.reset();
|
|
}
|
|
#endif
|
|
void swap(Ref& o) {
|
|
std::swap(mon , o.mon);
|
|
std::swap(elem, o.elem);
|
|
}
|
|
//! analogous to auto_ptr<>::release() but given a different name
|
|
//! to avoid being confused with Monitor::release()
|
|
MonitorElementPtr letGo() {
|
|
MonitorElementPtr ret;
|
|
elem.swap(ret);
|
|
return ret;
|
|
}
|
|
void attach(Monitor& M) {
|
|
reset();
|
|
mon = &M;
|
|
}
|
|
void attach(const Monitor::shared_pointer& M) {
|
|
reset();
|
|
mon = M.get();
|
|
}
|
|
bool next() {
|
|
if(elem) mon->release(elem);
|
|
elem = mon->poll();
|
|
return !!elem;
|
|
}
|
|
void reset() {
|
|
if(elem && mon) mon->release(elem);
|
|
elem.reset();
|
|
}
|
|
Ref& operator++() {// prefix increment. aka "++(*this)"
|
|
next();
|
|
return *this;
|
|
}
|
|
#if __cplusplus>=201103L
|
|
inline explicit operator bool() const { return elem.get(); }
|
|
#else
|
|
private:
|
|
typedef const Monitor* const * hidden_bool_type;
|
|
public:
|
|
operator hidden_bool_type() const { return elem.get() ? &mon : 0; }
|
|
#endif
|
|
inline MonitorElement* operator->() { return elem.get(); }
|
|
inline MonitorElement& operator*() { return *elem; }
|
|
inline MonitorElement* get() { return elem.get(); }
|
|
|
|
inline bool operator==(const Ref& o) const { return elem==o.elem; }
|
|
inline bool operator!=(const Ref& o) const { return !(*this==o); }
|
|
|
|
EPICS_NOT_COPYABLE(Ref)
|
|
};
|
|
|
|
#if __cplusplus>=201103L
|
|
// used by c++11 for-range
|
|
inline MonitorElement::Ref begin(Monitor& mon) { return MonitorElement::Ref(mon); }
|
|
inline MonitorElement::Ref end(Monitor& mon) { return MonitorElement::Ref(); }
|
|
#endif // __cplusplus<201103L
|
|
|
|
/** Utility implementation of Monitor.
|
|
*
|
|
* The Monitor interface defines the downstream (consumer facing) side
|
|
* of a FIFO. This class is a concrete implementation of this FIFO,
|
|
* including the upstream (producer facing) side.
|
|
*
|
|
* In addition to MonitorRequester, which provides callbacks to the downstream side,
|
|
* The MonitorFIFO::Source class provides callbacks to the upstream side.
|
|
*
|
|
* The simplest usage is to create (as shown below), then put update into the FIFO
|
|
* using post() and tryPost(). These methods behave the same when the queue is
|
|
* not full, but differ when it is. Additionally, tryPost() has an argument 'force'.
|
|
* Together there are three actions
|
|
*
|
|
* # post(value, changed) - combines the new update with the last (most recent) in the FIFO.
|
|
* # tryPost(value, changed, ..., false) - Makes no change to the FIFO and returns false.
|
|
* # tryPost(value, changed, ..., true) - Over-fills the FIFO with the new element, then returns false.
|
|
*
|
|
* @note Calls to post() or tryPost() __must__ be followed with a call to notify().
|
|
* Callers of notify() __must__ not hold any locks, or a deadlock is possible.
|
|
*
|
|
* The intent of tryPost() with force=true is to aid code which is transferring values from
|
|
* some upstream buffer and this FIFO. Such code can be complicated if an item is removed
|
|
* from the upstream buffer, but can't be put into this downstream FIFO. Rather than
|
|
* being forced to effectivly maintain a third FIFO, code can use force=true.
|
|
*
|
|
* In either case, tryPost()==false indicates the the FIFO is full.
|
|
*
|
|
* eg. simple usage in a sub-class for Channel named MyChannel.
|
|
@code
|
|
pva::Monitor::shared_pointer
|
|
MyChannel::createMonitor(const pva::MonitorRequester::shared_pointer &requester,
|
|
const pvd::PVStructure::shared_pointer &pvRequest)
|
|
{
|
|
std::tr1::shared_ptr<pva::MonitorFIFO> ret(new pva::MonitorFIFO(requester, pvRequest));
|
|
ret->open(spamtype);
|
|
ret->notify();
|
|
// ret->post(...); // maybe initial update
|
|
}
|
|
@endcode
|
|
*/
|
|
class epicsShareClass MonitorFIFO : public Monitor,
|
|
public std::tr1::enable_shared_from_this<MonitorFIFO>
|
|
{
|
|
public:
|
|
POINTER_DEFINITIONS(MonitorFIFO);
|
|
//! Source methods may be called with downstream mutex locked.
|
|
//! Do not call notify(). This is done automatically after return in a way
|
|
//! which avoids locking and recursion problems.
|
|
struct epicsShareClass Source {
|
|
POINTER_DEFINITIONS(Source);
|
|
virtual ~Source();
|
|
//! Called when MonitorFIFO::freeCount() rises above the level computed
|
|
//! from MonitorFIFO::setFreeHighMark().
|
|
//! @param numEmpty The number of empty slots in the FIFO.
|
|
virtual void freeHighMark(MonitorFIFO *mon, size_t numEmpty) {}
|
|
};
|
|
struct epicsShareClass Config {
|
|
size_t maxCount, //!< upper limit on requested FIFO size
|
|
defCount, //!< FIFO size when client makes no request
|
|
actualCount; //!< filled in with actual FIFO size
|
|
bool dropEmptyUpdates; //!< default true. Drop updates which don't include an field values.
|
|
epics::pvData::PVRequestMapper::mode_t mapperMode; //!< default Mask. @see epics::pvData::PVRequestMapper::mode_t
|
|
Config();
|
|
};
|
|
|
|
/**
|
|
* @param requester Downstream/consumer callbacks
|
|
* @param pvRequest Downstream provided options
|
|
* @param source Upstream/producer callbacks
|
|
* @param conf Upstream provided options. Updated with actual values used. May be NULL to use defaults.
|
|
*/
|
|
MonitorFIFO(const std::tr1::shared_ptr<MonitorRequester> &requester,
|
|
const pvData::PVStructure::const_shared_pointer &pvRequest,
|
|
const Source::shared_pointer& source = Source::shared_pointer(),
|
|
Config *conf=0);
|
|
virtual ~MonitorFIFO();
|
|
|
|
void show(std::ostream& strm) const;
|
|
|
|
virtual void destroy() OVERRIDE FINAL;
|
|
|
|
// configuration
|
|
|
|
//! Level, as a percentage of empty buffer slots, at which to call Source::freeHighMark().
|
|
//! Trigger condition is when number of free buffer slots goes above this level.
|
|
//! In range [0.0, 1.0)
|
|
void setFreeHighMark(double level);
|
|
|
|
// up-stream interface (putting data into FIFO)
|
|
//! Mark subscription as "open" with the associated structure type.
|
|
void open(const epics::pvData::StructureConstPtr& type);
|
|
//! Abnormal closure (eg. due to upstream dis-connection)
|
|
void close();
|
|
//! Successful closure (eg. RDB query done)
|
|
void finish();
|
|
//! Consume a free slot if available. otherwise ...
|
|
//! if !force take no action and return false.
|
|
//! if force then attempt to allocate and fill a new slot, then return false.
|
|
//! The extra slot will be free'd after it is consumed.
|
|
bool tryPost(const pvData::PVStructure& value,
|
|
const epics::pvData::BitSet& changed,
|
|
const epics::pvData::BitSet& overrun = epics::pvData::BitSet(),
|
|
bool force =false);
|
|
//! Consume a free slot if available, otherwise squash with most recent
|
|
void post(const pvData::PVStructure& value,
|
|
const epics::pvData::BitSet& changed,
|
|
const epics::pvData::BitSet& overrun = epics::pvData::BitSet());
|
|
//! Call after calling any other upstream interface methods (open()/close()/finish()/post()/...)
|
|
//! when no upstream mutexes are locked.
|
|
//! Do not call from Source::freeHighMark(). This is done automatically.
|
|
//! Call any MonitorRequester methods.
|
|
void notify();
|
|
|
|
// down-stream interface (taking data from FIFO)
|
|
virtual epics::pvData::Status start() OVERRIDE FINAL;
|
|
virtual epics::pvData::Status stop() OVERRIDE FINAL;
|
|
virtual MonitorElementPtr poll() OVERRIDE FINAL;
|
|
virtual void release(MonitorElementPtr const & monitorElement) OVERRIDE FINAL; // may call Source::freeHighMark()
|
|
virtual void getStats(Stats& s) const OVERRIDE FINAL;
|
|
virtual void reportRemoteQueueStatus(epics::pvData::int32 freeElements) OVERRIDE FINAL;
|
|
|
|
//! Number of unused FIFO slots at this moment, which may changed in the next.
|
|
size_t freeCount() const;
|
|
private:
|
|
size_t _freeCount() const;
|
|
|
|
friend void providerRegInit(void*);
|
|
static size_t num_instances;
|
|
|
|
// const after ctor
|
|
Config conf;
|
|
|
|
// locking here is complicated...
|
|
// our entry points which make callbacks are:
|
|
// notify() -> MonitorRequester::monitorConnect()
|
|
// -> MonitorRequester::monitorEvent()
|
|
// -> MonitorRequester::unlisten()
|
|
// -> ChannelBaseRequester::channelDisconnect()
|
|
// start() -> MonitorRequester::monitorEvent()
|
|
// release() -> Source::freeHighMark()
|
|
// -> notify() -> ...
|
|
// reportRemoteQueueStatus() -> Source::freeHighMark()
|
|
// -> notify() -> ...
|
|
mutable epicsMutex mutex;
|
|
|
|
// ownership is archored at the downstream (consumer) end.
|
|
// strong refs are:
|
|
// downstream -> MonitorFIFO -> Source
|
|
// weak refs are:
|
|
// MonitorRequester <- MonitorFIFO <- upstream
|
|
|
|
// so we expect that downstream will hold a strong ref to us,
|
|
// and we keep a weak ref to downstream's MonitorRequester
|
|
const std::tr1::weak_ptr<MonitorRequester> requester;
|
|
|
|
const epics::pvData::PVStructure::const_shared_pointer pvRequest;
|
|
|
|
// then we expect to keep a strong ref to upstream (Source)
|
|
// and expect that upstream will have only a weak ref to us.
|
|
const Source::shared_pointer upstream;
|
|
|
|
enum state_t {
|
|
Closed, // not open()'d
|
|
Opened, // successful open()
|
|
Error, // unsuccessful open()
|
|
} state;
|
|
bool pipeline; // const after ctor
|
|
bool running; // start() vs. stop()
|
|
bool finished; // finish() called
|
|
epics::pvData::BitSet scratch, oscratch; // using during post to avoid re-alloc
|
|
|
|
bool needConnected;
|
|
bool needEvent;
|
|
bool needUnlisten;
|
|
bool needClosed;
|
|
|
|
epics::pvData::Status error; // Set when entering Error state
|
|
|
|
size_t freeHighLevel;
|
|
epicsInt32 flowCount;
|
|
|
|
epics::pvData::PVRequestMapper mapper;
|
|
|
|
typedef std::list<MonitorElementPtr> buffer_t;
|
|
// we allocate one extra buffer element to hold data when post()
|
|
// while all elements poll()'d. So there will always be one
|
|
// element on either the empty or inuse lists
|
|
buffer_t inuse, empty, returned;
|
|
/* our elements are in one of 4 states
|
|
* Empty - on empty list
|
|
* In Use - on inuse list
|
|
* Polled - Returnedd from poll(). Not tracked
|
|
* Returned - only if pipeline==true, release()'d but not ack'd
|
|
*/
|
|
|
|
EPICS_NOT_COPYABLE(MonitorFIFO)
|
|
};
|
|
|
|
static inline
|
|
std::ostream& operator<<(std::ostream& strm, const MonitorFIFO& fifo) {
|
|
fifo.show(strm);
|
|
return strm;
|
|
}
|
|
|
|
}}
|
|
|
|
namespace epics { namespace pvData {
|
|
|
|
using epics::pvAccess::MonitorElement;
|
|
using epics::pvAccess::MonitorElementPtr;
|
|
using epics::pvAccess::MonitorElementPtrArray;
|
|
using epics::pvAccess::Monitor;
|
|
using epics::pvAccess::MonitorPtr;
|
|
}}
|
|
|
|
#endif /* MONITOR_H */
|