Chapter 50. Event builder client framework

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

Reference material is also available for these and other topics:

50.1. Application specific code for the event builder

The application specific code must:

The framework programmer provides these actions by:

  1. Subclassing the CEVBClientApp class

  2. Writing implementations for all pure virtual methods of CEVBClientApp.

  3. Writing overrides for default implementations provided by CEVBClientApp if appropriate to the application.

Below is the definition of the CEVBClientApp abstract base class:

Example 50-1. CEVBClientApp definition


class CEVBClientApp {
public:
  CEVBClientApp();
  ~CEVBClientApp();

  // The interface:

public:
  virtual void initialize();        (1)
  virtual bool dataReady(int ms) = 0;     (2)
  virtual void getEvents() = 0;     (3)
  virtual void shutdown();          (4)
};
                
            

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:

(1)
Called to perform one-time initialization. This is called after the entire framework has been created and initialized. It is safe to invoke any support method at this time. This is not necessarily the case during construction.
(2)
This method is called from time to time to determine if there is any data. It should block at most 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.
(3)
Called at some point after 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.

(4)
Called just prior to the framework shutting down. At this time once more, all framweork services are still avaialble. This is not true when the object's destructor is called.

50.1.1. Sample ring 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 50-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 50-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 50-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 50-5. The EVBRingClientApp getEvents implementation.


void
CEVBRingClientApp::getEvents()
{
    CEVBEventList events;              (1)
    int           eventCount = 0;
    CAllButPredicate all;
    
    while(m_pRing->availableData()) {  (2)
                                       (3)
        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++;                (4)
        if (eventCount >= 100) break;
    }
    if (events.size()) {             (5)
        CEVBClientFramework::submitFragmentList(events);
        
        while(!events.empty()) {    (6)
            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.

(1)
The 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.
(2)
The main loop condition for our code is to run until we've pulled all the items from the ring. See below however.
(3)
This is the heart of the loop. Each pass through the loop, an event is gotten from the ring. A 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.
(4)
Once the limit of 100 events have been added to events, we break out of the loop to submit the events to the event builder framework.
(5)
This condition avoids doing anything if for some reason we never got any events. The call to CEVBClientFramework::submitFragmentList is what actually causes the fragments to be transmitted to the eventbuilder.
(6)
The 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.