Event builder client API describes a low level API to the NSCL Event builder. In many cases if you write software directly to this you will find that code patterns repeat over and over again. The Event builder client framwework captures this and allows you to concentrate on getting data from your data source, turning the logic of the event builder interface to the framework itself.
Recall that a typical library is a set of functions and classes used by a program while an application framework is a program that needs a user written library to perform application specific functions. This chapter will describe
The 'library' that must be provided to the framework to produce a new data source.
How to build a new event builder data source from your library and the framework. This section will also describe some features of the framework that support slightly more advanced applications.
How to run an event builder client that was built on the framework.
Reference material is also available for these and other topics:
CEvbClientApp provides reference material that describes the base class on which the application code for the event builder is based.
Event builder client framework provides reference material that desribes the event builder command options and parameters.
The application specific code must:
Perform one-time initialization
Determine if data are ready to be read from the source
Provide events to the framework along with their timestamps
Perform any required shutdown operations
The framework programmer provides these actions by:
Subclassing the CEVBClientApp
class
Writing implementations for all pure virtual
methods of CEVBClientApp
.
Writing overrides for default implementations provided
by CEVBClientApp
if appropriate
to the application.
Below is the definition of the CEVBClientApp
abstract base class:
Example 57-1. CEVBClientApp
definition
class CEVBClientApp { public: CEVBClientApp(); ~CEVBClientApp(); // The interface: public: virtual void initialize(); virtual bool dataReady(int ms) = 0; virtual void getEvents() = 0; virtual void shutdown(); };
In the example above, data members private to CEVBClientApp
are not shown for the sake of brevity. The interface methods
that are application specific are virtual and are used as follows:
ms
milliseconds. If at the
end of that time no data are avaialble on the data source,
return false. If data becomes available,
return true.
dataReady
returns true. Events that are available
from the data source should be read by this method and
submitted to the framework for transmission to the
event builder.
The mechanism for providing events to the framework is described in the next set of examples.
EVBClientApp
class.This section incrementally develops an application that can take data from a ring buffer and submit it to the event builder. This is not really a production quality effort, it is designed to illustrate several points. Little bits of code and #include directives are also not shown in order to keep these examples understandable.
Let's start by writing a definition of the
CEVBRingClientApp
class. For this
example the ring URL will be gotten from the RINGURL
environment variable (a production version would probably
use a command line option but that's beyond the scope of this example.
Example 57-2. The CEVBRingClientApp
class
definition.
class CRingBuffer; class CEVBRingClientApp : public CEVBClientApp { private: CRingBuffer* m_pRing; public: virtual void initialize(); virtual bool dataReady(int ms) = 0; virtual void getEvents() = 0; virtual void shutdown(); };
There should be no surprises here. All methods have been
declared as concrete rather than leaving dataReady
and getEvents
as pure virtual. Furthermore,
m_pRing
has been declared as a pointer to
a ring buffer object. For illustrative purpose, we'll
create the CRingBuffer
in the
initialize
method and
destroy it in shutdown
.
These operations could just as easily be done in a
constructor/destructor pair.
Having said this, the initialize
and shutdown
methods should
be pretty easy to write:
Example 57-3. The CEVBRingClientApp
initialize
and shutdown
methods.
void CEVBRingClientApp::intialize() { const char* pRingName = getenv("RINGURL"); m_pRing = CRingAccess::daqConsumeFrom(std::string(pRingName)); } void CEVBRingClientApp::shutdown() { delete m_pRing; }
There should clearly be more error checking in
initialize
however these should also
be pretty clear. initialize
translates the RINGURL environment variable
and opens a ring using using the URL in that environment
variable.
The dataReady
method is a bit tricky.
We'd rather not burn the CPU by polling. What we'll here is
first check to see if there's at least a ring header in the
ring. If so, we return true if not,
we block for the number of requested milliseconds and
check again.
Example 57-4. The CEVBRingClientApp
dataReady
method.
bool CEVBRingClientApp::dataReady(int ms) { if (haveHeader()) return true; usleep(ms*1000); return haveHeader(); } // Added utility function: bool CEVBRingClientApp::haveHeader() { return m_pRing->availableData() >= sizeof(RingItemHeader); }
Note that we have invented a helper method that determines if there's at least a ring header worth of data in the ring for us.
For getEvents
we will get the minimum
of 100 events or until there is no more
data left in the ring. The events will be bundled into
a EventFragmentList and submitted to the event builder
framework using the CEVBClientFramework::submitFragmentList
.
Note that each element of the list is a ClientEventFragment
and contains:
uint64_t s_timestamp;
The timestamp associated with the event fragment. This must be extracted from the event itself.
uint32_t s_sourceId;
The data source id associated with the fragment. A client can submit data from more than one data source. The source Id is used to tag the fragments in events that are finally assembled from the ordered event fragments.
uint32_t s_size;
The size of the event fragment payload
void* s_pPayload;
Pointer to the payload itself. In our case, the payload will be the full RingItem.
See CEVBClientFramework for reference information on the framework API methods and data structures.
For simplicity we are not going to worry about the heterogeneous nature of ring items.
Example 57-5. The EVBRingClientApp
getEvents
implementation.
void CEVBRingClientApp::getEvents() { CEVBEventList events; int eventCount = 0; CAllButPredicate all; while(m_pRing->availableData()) { CRingItem* pItem = CRingItem::getFromRing(*m_pRing, all); ClientEventFragment frag; frag.s_timestamp = getTimestamp(pItem); frag.s_sourceId = 1; frag.s_size = pItem->getItemPointer()->s_header.s_size; frag.s_payload = pItem->getItemPointer(); events.push_back(frag); eventCount++; if (eventCount >= 100) break; } if (events.size()) { CEVBClientFramework::submitFragmentList(events); while(!events.empty()) { EventFragmentList fragment = events.front(); CRingItem* pItem = reinterpret_cast<CRingItem*>(fragment.s_pPayload); delete pItem; events.pop_front(); } } }
Since getEvents
is the most complicated
method we've seen so far, lets break it down into its component
pieces.
CEVBEventList
is a list
of ClientEventFragment
structs.
By list we mean list in the same sense as
std::list
. Since we are using
CEVBClientFramework::submitFragmentList
we need to put our fragments into one of these.
ClientEventFragment
is filled in.
The s_timestamp
is gotten
via a utility method that we will not show here.
The s_sourceId
has been
hard coded to 1.
events
, we break out of
the loop to submit the events to the event builder framework.
CEVBClientFramework::submitFragmentList
is what actually causes the fragments to be transmitted
to the eventbuilder.
CRingItem
objects we got
from the ring are dynamically allocated. This
loop destroys them. The list elements themselves
are destroyed automatically when the function
returns.