This program allows users to edit the complete body of an event built event as a single unit. User code can extend, replace or use any of the data following the uint32_t byte count for the event at the first 32 bits of the event body. The framework will touch up the event's ring item header as well as that byte count. User code is responsible for updating internal sizes such as
Fragment header sizes.
Fragment ring item header size field.s
Fragment body header size fields.
OPTIONS describes the program options while User editor code describes how to write the user editing code that modifies each event. The code can run with workers editing blocks of events in parallel using either threaded or MPI parallelism. The output event stream is re-sored in time-stamp order so that output events are in the same order as the un-edited input events.
--source
= >URISpecifies the source of data. The data source must be event built data where the ring items and the fragments of the events have body headers.
The URI can be either a file or ring URI. Ring buffers can be remote if the system running the distributer task is runnning NSCLDAQ.
--sink
=URISpecifies where output data are written. This can be a file or ring URI. If a ring URI, the host must evaluate to the local host and the system actually doing the writing must run NSCLDAQ. Note that knowing which this is for MPI Parallelism can be difficult.
--workers
=integer
Specifies the number of parallel workers that will operate
on the data. Each worker gets clumps of events whose
as determined by --clump-size
and works
on clumps in parallel to all other workers.
--clump-size
=integerSpecifies the number of events in each work item that a worker gets per work item request.
--paralle-strategy
=thread | mpi
Specifies how the program will parallelize. Note that if
mpi is chosen the program must
be run with mpirun and that program's
-np
value will determine the actual
number of workers that will be used (np - 3 workers will
be used).
--editorlib
=libpathSpecifies the path to a shared object library that contains the code to edit events and a factory function to produce editor objects.
For more information, see User editor code below.
The FullEventEditor program depends on application specific user code to actually edit the event. This code can modify all data following the size field of the event. The size field, while accessible, will be automatically updated by FullEventEditor to reflect the size of the modified event body. Furthermore, the size field of the event's ring item header will be updated as well to reflect the updated event size. Note, however that it is the responsibility of the user editing code to update any size fields at the fragment level. These are at least:
The fragment header size field for modified fragments.
The fragment ring item size field for modified fragments.
The sizes of any fragment body headers that are extended.
Note that the bodies of fragments may also have sizes that require modification. This is application specific and won't be covered here.
The user event editing code is supplied to the
FullEventEditor as a shared object
library. The library must contain an extern "C"
entry point named: createFullEventEditor
.
That entry is a function that takes no parameters and returns a new
instance of an object from a class derived from
CFullEventEditor::Editor
. That object
will be used by a worker to edit events.
It is important that each call to
createFullEventEditor
return a new object.
Only in this way is thread-safety maintained in threaded parallelism.
The objects created will live for the program's lifetime.
Let's examine the definition of the
CFullEventEditor::Editor
class. It is defined
in the CFullEventEditor.h header and
has the following, pure virtual, methods:
virtual std::vector<SegmentDescriptor> operator()((void* pBody);
This method is called once for each event in the work units the worker processes. The application's parallelism ensures this is called once for every event in some instance of the user's code.
pBody
is a pointer to the body
of the event. For the event built data this
application is intended to edit, this consists of a
uint32_t containing the self-inclusive
byte count of the body. While the remainder of the body
can be freely edited, once this method returns, the
application will modify this field to represent the
number of bytes in the modified event body (self-inclusive).
The method returns a vector of SegmentDescriptor structs. Once we've finished documenting this class we'll turn our attention to that struct.
virtual void free(iovec& desc);
Elements of the return vector may be describe either dynamic or or static storage. When the event description is no longer needed, this method is called for each descriptor that's marked its storage as dynamic. This method is expected to free that storage.
When editing an event, the new event will most likely consist largely
of the original event with some supplemental data added. Rather than
requiring that your code create a new event laid out in memory. The
application asks you to describe the new event in terms of blocks of
memory that it will gather into a new event. This description
is the return value from operator()
.
operator()
returns a vector of
SegmentDescriptor structs. Each of those, in turn,
describes a block of the event and consist of the following
fields:
s_description
Describes the memory extent of the block. iovec
is a struct defined in the header
sys/uio.h. It's fields are
iov_len
, the length of the
block being described and
iov_base
a pointer to the
block being described.
s_dynamic
This should be true if your
code needs free
to be called
when the memory described by
s_description
is no longer
needed. You can us this to free any dynamically
allocated memory that was used to create this
block.