Event building: programming
This page covers writing an event builder and configuring frontends so that their data can be consumed by an event builder.
For the concepts, see Event building: overview.
There are two ways to write an event builder:
- Use the
mevbframework. Midas provides an example event builder in$MIDASSYS/examples/eventbuilder. It matches fragments by serial number and you only need to write code for checking/modifying each built event. See Writing the event builder. - Write your own. Many experiments write their own event builder, often as a normal frontend, which gives full control over how fragments are matched. See Writing your own event builder instead.
Setting up the fragment frontends
The fragment frontends can be written using any frontend framework.
Rules that the fragment frontends need to follow
Whichever event builder you use, each fragment frontend should:
- write its events to its own event buffer, rather than to
SYSTEM(see below) - produce exactly one event per trigger, in the same order as the other frontends
- use bank names that are unique across all the fragment frontends, so the banks can still be told apart once they are merged into one event
If you use the mevb framework, each fragment frontend also needs:
- the
EQ_EBflag in its equipment type (e.g.EQ_POLLED | EQ_EB) - a buffer name that starts with a common prefix (e.g.
BUF01,BUF02), as themevbframework selects its source buffers by prefix - the
MIDASdata format (the only format themevbframework supports)
Why to use a separate buffer for each fragment frontend
This applies whichever event builder you use. If several frontends write to the same buffer, the event builder has to work out which frontend each fragment came from, and hold on to fragments that arrive early.
The mevb framework can't do this: it reads each source buffer through a single handle, so fragments from a shared buffer end up assigned to the wrong source. To use a common buffer with the mevb framework, you would need to give each fragment frontend a distinct event ID, and modify mevb.cxx so that it reads all events from the shared buffer and uses the event ID or trigger mask to decide which source each one belongs to. You would also need to add a queue for each source: frontends write to the shared buffer at different speeds, so the event builder will often receive the next fragments from a fast frontend before the current fragment from a slow one, and has to hold them until they are needed. A custom event builder would need the same logic.
This isn't recommended. As well as the extra code, all the frontends would compete for the lock on a single buffer, a stalled frontend would make fragments pile up in the event builder's memory rather than in its own buffer, and you could no longer see which frontend is falling behind on the Buffers page. With separate buffers, each source's fragments arrive in order without any extra work.
Running several copies of a frontend
It is common to run several copies of the same frontend program, one per piece of hardware. The frontend index (-i flag) lets each copy have its own name and buffer. This is a feature of the frontend frameworks rather than of the mevb framework, so it is useful whichever event builder you use. In mfe frontends, any % format in the equipment name and buffer name is replaced by the index, and the index is appended to the frontend name.
Example: equipment declaration in ebfe.cxx
const char *frontend_name = "ebfe";
EQUIPMENT equipment[] = {
{"Eqp%0d", /* equipment name */
{2, 0, /* event ID, trigger mask */
"BUF%0d", /* event buffer */
EQ_POLLED | EQ_EB, /* equipment type, including EQ_EB */
LAM_SOURCE(0, 0xFFFFFF), /* event source */
"MIDAS", /* format */
TRUE, /* enabled */
RO_RUNNING | RO_ODB, /* read only when running */
500, /* poll for 500ms */
0, /* stop run after this event limit */
0, /* number of sub events */
0, /* don't log history */
"", "", "",},
read_trigger_event, /* readout routine */
},
{""}
};
Running ebfe -i 1 and ebfe -i 2 gives:
| Command | Frontend name | Equipment name | Buffer |
|---|---|---|---|
ebfe -i 1 |
ebfe01 |
Eqp1 |
BUF1 |
ebfe -i 2 |
ebfe02 |
Eqp2 |
BUF2 |
The example also uses get_frontend_index() to give each copy unique bank names (AD01, TC01, ...), so the banks can still be told apart once they are merged into one event.
The above example uses the mfe frontend framework. The other frontend frameworks (TMFE and python) have similar systems for tweaking names based on the -i flag.
Writing the event builder
Midas provides an example event builder framework, mevb, in $MIDASSYS/examples/eventbuilder. It is a template rather than a ready-to-use program: you copy it to your experiment, adapt the user code, and compile it.
This framework is optional
You do not have to use this specific framework for writing an event builder. See writing your own event builder for advice on writing your own from scratch, which may give you more flexibility.
The examples/eventbuilder directory contains three parts:
| File | Role | Do you edit it? |
|---|---|---|
mevb.cxx, mevb.h |
The event builder framework. It finds the fragment sources, collects the fragments, checks their serial numbers, assembles the built event and sends it on. It calls the user functions described below. | Not normally |
ebuser.cxx |
Example user code. It declares the event builder's equipment and implements the user functions that the framework calls. | Yes - this is where your code goes |
ebfe.cxx |
Example fragment frontend, written using the mfe frontend framework. | Use it as a starting point for your own frontends |
On this page, "the mevb framework" means behaviour you get regardless of your user code, and "the example" means the code in ebuser.cxx or ebfe.cxx (which you are expected to change).
Synchronisation is your responsibility
The event builder only checks that the serial numbers match. It is up to you (usually via a hardware trigger distribution system) to ensure that every frontend produces exactly one event per trigger, in the same order. If one frontend misses or duplicates an event, every subsequent event will have mismatched serial numbers.
How it works
The mevb framework calls your user functions at these points:
flowchart LR
init["ebuilder_init()"] -- "Run<br>starts" --> bor["eb_begin_of_run()"]
bor --> user["eb_user()<br/>for each set of fragments<br/>(see chart below)"]
user -- "Run<br>stops" --> eor["eb_end_of_run()"]
eor -- "Next run" --> bor
eor -- "Program<br>exits" --> exit["ebuilder_exit()"]
The flowchart below shows the main loop of the mevb framework in more detail. Run transitions (including the call to eb_begin_of_run()) are handled inside cm_yield().
flowchart LR
FE(["Fragment<br/>frontends"]) -.-> recv
state{"Running?"} -- Yes --> recv["Receive<br/>fragments"]
state -- No --> yield["Update statistics,<br/>cm_yield()<br/>(about once a second)"]
recv --> all{"All<br/>received?"}
all -- Yes --> user["Compare serial<br/>numbers, call<br/><b>eb_user()</b>"]
user -- EB_SUCCESS --> send["Append fragments<br/>(unless User build),<br/>send event"]
user -- EB_SKIP --> yield
user -- EB_USER_ERROR --> abort["Stop run,<br/><b>eb_end_of_run()</b>,<br/><b>ebuilder_exit()</b>"]
all -- No --> yield
all -- "No, and run<br/>is stopping" --> eor["<b>eb_end_of_run()</b>"]
send --> yield
yield --> state
eor --> state
abort --> state
In more detail:
- Each fragment frontend sends its events to its own event buffer (e.g.
BUF01,BUF02, ...), rather than to theSYSTEMbuffer. - When the event builder starts, the
mevbframework scans/Equipmentfor equipment that has theEQ_EBflag set and whose buffer name matches the one given on the command line. Each match becomes one fragment source. - At the start of each run, the framework calls your
eb_begin_of_run()function, then connects to all the source buffers and empties them. It does this before the frontends start (its start transition has sequence number 400; most frontends use 500 by default). - While the run is in progress, the framework polls all the source buffers until it has received one fragment from each required source.
- It then compares the serial numbers of the fragments, and calls your
eb_user()function with the fragments and a flag saying whether the serial numbers matched. - Unless your code says otherwise, the framework appends the banks from each fragment to the built event and sends it to its destination buffer (normally
SYSTEM, where mlogger will pick it up). - About once per second, the framework updates its statistics in the ODB and handles midas system calls (run transitions etc.).
- When the run stops, the framework waits for the frontends to stop (its stop transition has sequence number 600), collects the remaining fragments, then calls your
eb_end_of_run()function.
The built event has the event ID and trigger mask of the event builder's own equipment, and the serial number of the matched fragments.
The mevb framework also registers the event builder's own equipment (called EB in the example), so its event rate and data rate appear on the midas status page like any other frontend.
User functions
The mevb framework calls these functions, which you must provide in ebuser.cxx:
| Function | When it is called |
|---|---|
ebuilder_init() |
Once, when the event builder starts. |
ebuilder_exit() |
When the event builder exits, and after a run has been stopped by EB_USER_ERROR. |
eb_begin_of_run(run_number, user_field, error) |
At the start of each run, before any fragments are collected. user_field is the User Field ODB setting. Return anything other than EB_SUCCESS to abort the run start. |
eb_end_of_run(run_number, error) |
After the run has stopped and the remaining fragments have been collected, and also when the framework stops the run itself (e.g. after eb_user() returns EB_USER_ERROR). |
eb_user(nfrag, mismatch, ebch, pheader, pevent, dest_size) |
Once for each set of fragments, before the built event is sent. |
eb_user() receives:
nfrag- the number of fragment sources.mismatch-TRUEif the serial numbers of the fragments don't all match.ebch- an array ofEBUILDER_CHANNELstructures, one per source.ebch[i].pfragmentpoints to the fragment (starting with itsEVENT_HEADER).pheader- the header of the built event, already filled in with the event ID, trigger mask and serial number.pevent- the start of the data area of the built event, which is still empty.dest_size- where to store the size of any data you add.
The return value tells the mevb framework what to do with the event:
| Return value | Effect |
|---|---|
EB_SUCCESS |
Build and send the event. |
EB_SKIP |
Discard this set of fragments and carry on with the next one. |
EB_USER_ERROR |
Stop the run. If /Logger/Auto restart is enabled, the framework then starts a new run. |
Handling serial number mismatches
The mevb framework doesn't act on a serial number mismatch itself - it just passes the mismatch flag to eb_user(). If your code returns EB_SUCCESS, the event is built anyway.
What the example user code does
The example ebuser.cxx is intended to show what is possible, and you will probably want to change most of it:
eb_begin_of_run()convertsUser Fieldto an integer, but doesn't use the value for anything.eb_user()returnsEB_USER_ERROR(stopping the run) if the serial numbers don't match.eb_user()adds a bank calledMYOWcontaining the serial number and timestamp of each fragment.eb_user()also copies theTC01bank from the first fragment. With theUser buildODB setting set ton(the default), the framework then appends all the fragments' banks as well, so the built event containsTC01twice.
Adding your own banks
Any banks you create in eb_user() appear in the built event before the banks from the fragments. The function below is based on the example's eb_user():
INT eb_user(INT nfrag, BOOL mismatch, EBUILDER_CHANNEL *ebch,
EVENT_HEADER *pheader, void *pevent, INT *dest_size)
{
if (mismatch)
return EB_USER_ERROR;
DWORD *pdata;
bk_init(pevent);
bk_create(pevent, "MYOW", TID_DWORD, (void **)&pdata);
for (int i = 0; i < nfrag; i++) {
if (ebset.preqfrag[i]) { // "Fragment Required" ODB setting
*pdata++ = ((EVENT_HEADER *) ebch[i].pfragment)->serial_number;
*pdata++ = ((EVENT_HEADER *) ebch[i].pfragment)->time_stamp;
}
}
bk_close(pevent, pdata);
pheader->data_size = bk_size(pevent);
*dest_size = pheader->data_size;
return EB_SUCCESS;
}
Setting the event size
The example ebuser.cxx sets pheader->data_size to the value returned by bk_close() plus sizeof(EVENT_HEADER). That is only correct by coincidence, for a single 16-bit bank: bk_close() returns the size of the last bank's data, not of the whole event. Use bk_size(pevent) as shown above.
Use the same bank format as the frontends
If your frontends use 32-bit banks (bk_init32()), you must use bk_init32() in eb_user() too. You can't mix bank formats within one event.
Composing the event yourself
If the User build ODB setting is set to y, the mevb framework doesn't copy the fragments, and only the banks you add in eb_user() end up in the built event. Use bk_copy() to copy individual banks from the fragments:
bk_init(pevent);
int status = bk_copy((char *)pevent, ebch[0].pfragment, "TC01");
if (status == EB_BANK_NOT_FOUND)
printf("Bank TC01 not found in fragment 0\n");
Error handling
The mevb framework handles these errors itself, regardless of your user code:
- Missing fragments. If some fragments of an event have arrived but one source sends nothing for 5 minutes, the framework assumes that frontend has stopped working. It reports "timeout waiting for fragment" and restarts the run using mtransition, honouring
/Logger/Auto restartand/Logger/Auto restart delay. - Stopping the run. If
eb_user()returnsEB_USER_ERROR, or an event can't be composed or sent, the framework stops the run. After anEB_USER_ERRORthe event builder carries on running (and starts a new run if/Logger/Auto restartis enabled); after other errors it exits.
Building and running the example
Copy the example to your experiment directory and build the mevb framework and example user code with CMake:
mkdir -p $HOME/online/evb
cd $HOME/online/evb
cp $MIDASSYS/examples/eventbuilder/* .
mkdir build
cd build
cmake ..
make
The CMake build only creates the event builder executable, mevb. The example fragment frontend, ebfe.cxx, uses CAMAC calls and is intended as a starting point for your own frontend; the Makefile in the same directory shows how to build it with the dummy camacnul CAMAC driver.
Start the fragment frontends (each with its own index), then the event builder, telling it to read from the BUF buffers (see command-line arguments):
ebfe -i 1 -D
ebfe -i 2 -D
mevb -b BUF -D
Things to be aware of when starting the event builder
- The
mevbframework only scans/Equipmentwhen the event builder starts. Start (or at least run once) all the fragment frontends before starting the event builder, and restart it if you add or remove a frontend. - The event builder refuses to start if a run is in progress, or if another copy is already running.
Starting a run should then show the EB equipment on the status page, with the same event rate as the fragment frontends.
Setting up the event builder
Equipment
The event builder's own equipment is declared in ebuser.cxx, in the same way as for an mfe frontend. The mevb framework only uses the first equipment in the list, and requires that:
- the event ID is not 0
- the equipment type is 0
- the format is
MIDAS, matching the fragments - a destination buffer is given. This is where the built events are sent -
SYSTEMif you want mlogger to write them to disk.
The built events get this equipment's event ID and trigger mask. The frontend_name variable sets the name that the event builder uses as a midas client.
Example: equipment declaration in ebuser.cxx
const char *frontend_name = "Ebuilder";
EQUIPMENT equipment[] = {
{"EB", /* equipment name */
{1, 0, /* event ID, trigger mask of the built event */
"SYSTEM", /* destination buffer */
0, /* equipment type (should be 0) */
0, /* event source */
"MIDAS", /* format (must match the fragments) */
TRUE, /* enabled */
},
},
{""}
};
Command-line arguments
The mevb framework accepts these arguments:
| Argument | Meaning |
|---|---|
-b <buffer> |
Prefix of the source buffers to read fragments from. Default is SYSTEM. |
-e <experiment> |
Experiment name, if more than one experiment is defined. |
-h <hostname> |
Host where the experiment is running. |
-d |
Print debugging output. |
-w |
Show a "wheel" activity indicator (see below). |
-D |
Run as a daemon. |
The -b argument is compared against the start of each equipment's buffer name, so mevb -b BUF collects fragments from BUF1, BUF2, BUF10 etc. Always specify -b: with the default of SYSTEM, the event builder would read fragments from the same buffer that mlogger records, so the unbuilt fragments would be logged as well.
Activity indicator |\-/
With -w, the event builder prints a status line to the terminal, which is overwritten each time it is updated. Each update advances the character after the ... through |, \, - and /. The line is only updated while the event builder is idle or waiting.
While no run is in progress (or the run is paused), the line is updated about twice a second:
.../ Snoring
If a run is in progress and the event builder has been waiting for a while for a fragment from one of the sources, it shows the total number of built events sent so far:
...- Timing on 1234
This is normal between triggers, but if it persists while the frontends are producing data, one of the sources isn't sending fragments.
The wheel stops while events are being built
While fragments are arriving normally, the line isn't updated at all. The last "Snoring" or "Timing on" line stays on the screen and the wheel stops turning. This means the event builder is busy, not that it has hung.
The indicator isn't shown when running as a daemon (-D), as there is no terminal to print to.
ODB settings
The mevb framework creates these keys in /Equipment/<eb-equipment-name>/Settings (/Equipment/EB/Settings in the example):
| ODB key | Meaning |
|---|---|
Number of Fragment |
Number of fragment sources that were found. Written by the framework at the start of each run. |
Fragment Required |
Array with one boolean per fragment source. Sources set to n are ignored, so you can build events from a subset of the frontends. If the size of the array doesn't match the number of sources, it is reset to all y at the start of the run. A run can't start if no fragments are required. Your code can read this array as ebset.preqfrag[]. |
User build |
If y, the framework doesn't append the fragments to the built event automatically - your eb_user() function must compose the event itself. |
User Field |
Free-form string passed to your eb_begin_of_run() function, e.g. to pass parameters to your code. |
Writing your own event builder instead
Using the mevb framework is optional. An event builder is just a midas program that reads events from some buffers and writes events to another, so many experiments write their own instead - often as a normal frontend whose equipment produces the built events. This gives you full control over how fragments are matched (e.g. using a hardware trigger counter or timestamp in the data rather than the midas serial number) and what happens when they don't match.
A custom event builder doesn't use the eb_ user functions or the mevb framework's settings described above, and the fragment frontends don't need the EQ_EB flag (though you may use it if you wish). It should follow the same principles:
- Each fragment frontend writes to its own event buffer rather than to
SYSTEM. - The event builder reads events from each of those buffers, and collects one fragment from each source.
- It checks that the fragments belong together, merges their data into a single event, and sends it to the
SYSTEMbuffer for mlogger to record. If the event builder is a frontend, the framework sends the event and keeps the statistics on the status page for you.
Some lessons from the mevb framework that are worth copying:
- Register the event builder's start transition with a lower sequence number than the fragment frontends (e.g. 400 if they use the default 500), and empty the source buffers at that point, so no stale fragments from a previous run are left over.
- Similarly, register its stop transition with a higher sequence number (e.g. 600), so it can collect the last fragments after the frontends have stopped.
- Decide what to do if a source stops sending fragments, rather than waiting forever.