Chapter 72. Software Triggering NEW IN 11.4

Software triggering is appropriate when you want to impose a complex trigger to filter data that is unimportant to the experiment. The trigger may be difficult to perform in hardware. Software triggers should, in general:

In NSCLDAQ, software triggering is a two step process; In the first step, events are classified according to some criteria you establish in a classification class. This could be compute intensive and therefore runs in a parallelized framework. In the second stage, events that match specific classifications are accepted and rejected.

NSCLDAQ supports the following troubleshooting of software triggers:

The remainder of this chapter:

See the 1daq reference section for complete reference information on the SoftwareTrigger and EventFilter programs that make up this subsystem.

72.1. Creating classification libraries.

To perform software triggering/filtering you need to provide a class that takes CRingItem objects that are PHYSICS_EVENTS, and provides a uint32_t classification value for each event.

You must also provide a factory function that produces objects from your classification class. Finally, you need to compile and link all of this into a shared library that can bey dynamically loaded into the SoftwareTrigger application.

72.1.1. Writing the classification class and factory function

For the purpose of this example, we'll build a classifier that classifies the events as large or small where a large event is considered to be one who's body is larger than 500bytes. The classifier will return a 1 for large events and 0 for small events.

All our code will be in the file sillyclassifier.cpp. Keep that in mind when we describe how to build the classifier shared library in the next section.

Classifiers are derived from the class CRingMarkingWorker::Classifier. Classifiers are functors which means they must implement an operator(), function call method. Here's te overall structure of sillyclassifier.cpp:


#include <CRingItemMarkingWorker.h<    (1)
#include <CRingItem.h<
#include <DataFormat.h<

class LargeSmallClassifier : public CRingMarkingWorker::Classifier (2)
{
public:
  virtual uint32_t operator()(CRingItem& item);  (3)
};
// Implementation  of LargSmallClassifier::operator() goes here.

extern "C" {                                         (4)
  CRingMarkingWorker::Classifier* createClassifier(){ (5)
    return new LargeSmallClassifier;                  (6)
  }
}

                

Let's look at the contents of this file in detail:

(1)
As usual at the top of the file we include the header files we'll need. These are:

CRingItemMarkingWorker.h

This defines the worker class in which our classifier will be embedded. Most notably, it contains the definition of CRingMarkingWorker::Classifier which is the base class of our classifier.

CRingItem.h

Defines the methods available for the CRingItem object our classification method gets. CRingItemMarkingWorker.h just defines that as an opaque class but we need to actually call methods CRingItem objects so we need the full definition.

DataFormat.h

Defines the format of raw ring items. In order to do our work, our classifier will need to get into the details of ring item formats.

(2)
Defines our class. Notice that our class has CRingMarkingWorker::Classifier as its base class.
(3)
Defines out operator() method. We'll look at implementing this method in a bit. Note that CRingMarkingWorker::Classifier is an abstract base class that does not implement operator(), therefore all usable classification classes must implement this method.

An instance of our classifier will be created for each of the worker threads that are classifying events in parallel. Note very well that the fact that these classifier objects are embedded in parallel workers means that, unless you really know what you're doning, your classifiers should not have or access global data.

(4)
We have to provide a findable factor for our classifier. In C++, the actual name of a function is decorated or mangled to include information about its return value and parameter types. This is how C++ implements function overloading, by making each function overload a unique actual function as seen by the linker.

While this is useful the actual manner in which this mangling is done is up to the compiler and, possibly, even the compiler version. In order to find the factory function in the shared library we turn off this mangling by declaring that it has C rather than C++ linkage. Function defined in an

extern "C" {}
block have that mangling disabled.

(5)
The factory function must have the name createClassifier. The SoftwareFilter application will expect this symbol to be defined in the shared object you create in the next section.
(6)
What the factory actuall does is simple. It uses new to create a new instance of the classifier object and returns the pointer generated to the caller.

Now let's look at how to implement our classifier's function call method. Our implementation must:

  • Figure out the size of the event's body.

  • Return 1 if that body is larger than 500 bytes, otherwise zero.

Before we start, note that our caller is responsible for ensuring that the ring items we get are all PHYSICS_EVENT items and that they also have a body header.

Here's the implementation:


static const uint32_t LARGE(500);                (1)

uint32_t
LargeSmallClassifier::operator()(CRingItem& item)
{
  size_t bodySize = item.getBodySize();           (2)
  return (bodySize > LARGE) ?  1 : 0;          (3)
}
                

Pretty simple actually:

(1)
The constant LARGE defines what we mean by a large event body. It's a good idea not to use magic number in your code. Defining LARGE makes it clear the purpose of this number.
(2)
The CRingItem provides a method that computes the body size.
(3)
This code returns 1 if the body size is greater than LARGE (500) and 0 otherwise.

72.1.2. Building the classification shared library.

This section continues the example in the previous section. We have a file: sillyclassifier.cpp. We need to turn that into a shared library which the SoftwareTrigger program will dynamically load and use to create instances of our classifier to bind into its worker threads.


g++ -olibSilly.so -I$DAQINC -shared -fPIC sillyclassifier.cpp                    
                

In addition to the compiler options you already probably know:

  • -shared tells the compiler to build a shared object library as output.

  • -fPIC tells the compiler to build position independednt code. This is needed for shared libraries as the code in such a library does not know where in process address space it will be loaded.