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:
Be run in high rate experiments to reduce the rate of data recorded to disk.
Be run on a high performance system with many cores available for processing. In the future NSCLDAQ's frameworks will support cluster based software triggering.
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:
You can monitor the classification of events to ensure your classification software operates correctcly.
You can monitor which events are rejected as well as which events are accepted by the second stage of the filter.
The remainder of this chapter:
Describes how to write and build classification classes.
How the classification and filtering components work together, and how to run them.
See the 1daq reference section for complete reference information on the SoftwareTrigger and EventFilter programs that make up this subsystem.
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.
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<#include <CRingItem.h< #include <DataFormat.h< class LargeSmallClassifier : public CRingMarkingWorker::Classifier
{ public: virtual uint32_t operator()(CRingItem& item);
}; // Implementation of LargSmallClassifier::operator() goes here. extern "C" {
CRingMarkingWorker::Classifier* createClassifier(){
return new LargeSmallClassifier;
} }
Let's look at the contents of this file in detail:
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.
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.
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.
CRingMarkingWorker::Classifier
as its base class.
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.
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.
createClassifier
.
The SoftwareFilter
application will expect this symbol to be defined
in the shared object you create in the next section.
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);uint32_t LargeSmallClassifier::operator()(CRingItem& item) { size_t bodySize = item.getBodySize();
return (bodySize > LARGE) ? 1 : 0;
}
Pretty simple actually:
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.
CRingItem
provides a method
that computes the body size.
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.
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.