GAUDI User Guide

Chapter 11
Framework services

11.1  Overview

In this chapter we give an overview of the various services, other than the data access services, which are available for use within the Gaudi framework. The Job Options service, the Message service and the Particle Properties service are available in this release.

We also describe how to implement new services for use within the Gaudi environment. We look at how to code up a service, what facilities the Service base class provides and how a service is managed by the application manager.

 

11.2  The Job Options Service

The Job Options Service is a mechanism which allows to configure an application at run time, without the need to recompile or relink. The options, or properties, are set via a job options file, which is read in when the Job Options Service is initialised. In what follows we describe the new format of the job options file, including some examples. Please note that this syntax is different to that used in previous releases of the Gaudi software.

11.2.1  Job options file format

The job options file has a well-defined syntax (similar to a simplified C++-Syntax) without datatypes. The datatypes are recognised by the "Job Options Compiler", which interprets the job options file according to the syntax (described in Appendix Appendix C together with possible compiler error codes).

The job options file is an ASCII-File, composed logically of a series of statements. The end of a statement is signaled by a semicolon (';') - as in C++.

Comments are the same as in C++, with '//' until the end of the line, or between '/*' and '*/'.

There are three constructs which can be used in a job options file:

Assignment statement

An assignment statement assigns a certain value (or a vector of values) to a property of an object or identifier. An assignment statement has the following structure:

 
<Object / Identifier> . < Propertyname > = < value >;

The first token (Object / Identifier) specifies the name of the object whose property is to be set. This must be followed by a dot ('.')

The next token (Propertyname) is the name of the option to be set, as declared in the declareProperty() method of the IProperty interface. This must be followed by an assign symbol ('=').

The final token (value) is the value to be assigned to the property. It can be a vector of values, in which case the values are enclosed in array brackets ('{`,'}`), and separated by commas (,). The token must be terminated by a semicolon (';').

The type of the value(s) must match that of the variable whose value is to be set, as declared in declareProperty(). The following types are recognised:

Boolean-type, written as true or false.
e.g. true; false;
Integer-type, written as an integer value (containing one or more of the digits '0', '1', '2', '3', '4', '5', '6', '7', '8', '9')
e.g.: 123; -923; or in scientific notation, e.g.: 12e2;
Real-type (similar to double in C++), written as a real value (containing one or more of the digits '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' followed by a dot '.' and optionally one or more of digits again)
e.g.: 123.; -123.45; or in scientific notation, e.g. 12.5e7;
String type, written within a pair of double quotes (` " ')
e.g.: "I am a string"; (Note: strings without double quotes are not allowed!)
Vector of the types above, within array-brackets ('{', '}'), separated by a comma (',')
e.g.: {true, false, true};
e.g.: {124, -124, 135e2};
e.g.: {123.53, -23.53, 123., 12.5e2};
e.g.: {"String 1", "String 2", "String 3"};
A single element which should be stored in a vector must be within array-brackets without a comma
e.g. {true};
e.g. {"String"};

Please note that it is not necessary any more to define in the job options file the type for the value which is used. The job options compiler will recognize by itself the type of the value and will handle this type correctly.

Append Statement

Because of the possibility of including other job option files (see below), it is sometimes necessary to extend a vector of values already defined in the other job option file. This functionality is provided be the append statement.

An append statement has the following syntax:

 
<Object / Identifier> . < Propertyname > += < value >;

The only difference from the assignment statement is that the append statement requires the '+=' symbol instead of the `=' symbol to separate the Propertyname and value tokens.

The value must be an array of one or more values

e.g. {true};
e.g. {"String"};
e.g.: {true, false, true};
e.g.: {124, -124, 135e2};
e.g.: {123.53, -23.53, 123., 12.5e2};
e.g.: {"String 1", "String 2", "String 3"};

The job options compiler itself tests if the object or identifier already exists (i.e. has already been defined in an included file) and the type of the existing property. If the type is compatible and the object exists the compiler appends the value to the existing property.

Including other Job Option Files

It is possible to include other job option files in order to use pre-defined options for certain objects. This is done using the #include directive:

 
#include "filename.ext"

The "filename.ext" can also contain the path where this file is located. The include directive can be placed anywhere in the job option file, but it is strongly recommended to place it at the very top of the file (as in C++).

As mentioned above, you can append values to vectors defined in an included job option file. The interpreter creates these vectors at the moment he interprets the included file, so you can only append elements defined in a file included before the append-statement!

As in C/C++, an included job option file can include other job option files. The compiler checks itself whether the include file is already included or not, so there is no need for #ifndef statements as in C or C++ (and they are in any case not supported!).

Sometimes it is necessary to over-ride a value defined previously (maybe in an include file). This is done by using an assign statement with the same object and propertyname. The last value assigned is the valid value!

Examples

We have already seen an example of a job options file in Listing 2 in Chapter 4 . That example was the job options file distributed with the GaudiExamples/Histograms example application. Note the inclusion of a system dependent standard job file. These are shown in Listing 29 and Listing 30 for WNT and Unix respectively, and are intended to set up a set of default options common to all of the distributed examples.

Listing 29 The Standard Include Job Option File for NT Systems

 
//************************************************************** 
// 
// Win32 standard job options file 
// 
//============================================================== 
 
 
//-------------------------------------------------------------- 
// General Application Configuration options 
//-------------------------------------------------------------- 
 
ApplicationMgr.ExtSvc = { "SicbEventCnvSvc",  
                          "SicbEventSelector/EventSelector" }; 
 
ApplicationMgr.DLLs = { "S:\LHCb\LHCBSoft\SicbCnv\v6\Win32Debug\SicbCnvshr",  
"S:\LHCb\LHCBSoft\HbookCnv\v5\Win32Debug\HbookCnvshr" };  
 
//-------------------------------------------------------------- 
// Persistency services 
//-------------------------------------------------------------- 
 
EventPersistencySvc.CnvServices     = { "SicbEventCnvSvc" };   
 
 
//-------------------------------------------------------------- 
// Detector related parameters 
//-------------------------------------------------------------- 
// We use CDF_StorageType 0x5 
//ApplicationMgr.DetStorageType = 5; 
 
// Detector description input file 
//CdfCnvSvc.cdfDbFile = "DDDB.txt"; 
 
// Persistency service setup:  
//DetectorPersistencySvc.CnvServices = { "CdfCnvSvc " }; 
 
 
//-------------------------------------------------------------- 
// Bookkeeping database file 
//-------------------------------------------------------------- 
 
// For Windows/NT: 
EventSelector.TapesDataBase = "F:\cern.ch\lhcb\mcdbase\mcmain.db"; 
 
//============================================================== 
// 
// End of Win32 standard job options file 
// 
//**************************************************************

Listing 30 The Standard Include Job Option File for UNIX Systems

 
//************************************************************** 
// 
// Unix standard job options file 
// 
//============================================================== 
 
 
//-------------------------------------------------------------- 
// General Application Configuration options 
//-------------------------------------------------------------- 
 
ApplicationMgr.ExtSvc = { "SicbEventCnvSvc",  
                          "SicbEventSelector/EventSelector" }; 
 
 
//-------------------------------------------------------------- 
// Persistency services 
//-------------------------------------------------------------- 
 
EventPersistencySvc.CnvServices     = { "SicbEventCnvSvc" };   
 
 
//-------------------------------------------------------------- 
// Detector related parameters 
//-------------------------------------------------------------- 
// We use CDF_StorageType 0x5 
//ApplicationMgr.DetStorageType = 5; 
 
// Detector description input file 
//CdfCnvSvc.cdfDbFile = "DDDB.txt"; 
 
// Persistency service setup:  
//DetectorPersistencySvc.CnvServices = { "CdfCnvSvc " }; 
 
 
//-------------------------------------------------------------- 
// Bookkeeping database file 
//-------------------------------------------------------------- 
 
EventSelector.TapesDataBase = "/afs/cern.ch/lhcb/mcdbase/mcmain.db"; 
 
//============================================================== 
// 
// End of Unix standard job options file 
// 
//**************************************************************

 

11.3  The Standard Message Service

One of the components directly visible to an algorithm object is the message service. The purpose of this service is to provide facilities for the logging of information, warnings, errors etc. The advantage of introducing such a component, as opposed to using the standard std::cout and std::cerr streams available in C++ is that we have more control over what is printed and where it is printed. These considerations are particularly important in an online environment.

The Message Service is configurable via the job options file to only output messages if their "activation level" is equal to or above a given "output level". The output level can be configured with a global default for the whole application:

// Set output level threshold (2=DEBUG, 3=INFO, 4=WARNING, 5=ERROR, 6=FATAL)

MessageSvc.OutputLevel = 4;

and/or locally for a given client object (e.g. myAlgorithm):

myAlgorithm.OutputLevel = 2;

Any object wishing to print some output should (must) use the message service. A pointer to the IMessageSvc interface of the message service is available to an algorithm via the accessor method messageService(), see section 5.2 . It is of course possible to use this interface directly, but a utility class called MsgStream is provided which should be used instead.

11.3.1  The MsgStream utility

The MsgStream class is responsible for constructing a Message object which it then passes onto the message service. Where the message is ultimately sent to is decided by the message service.

In order to avoid formatting messages which will not be sent because the verboseness level is too high, a MsgStream object first checks to see that a message will be printed before actually constructing it. However the threshold for a MsgStream object is not dynamic, i.e. it is set at creation time and remains the same. Thus in order to keep synchronized with the message service, which in principle could change its printout level at any time, MsgStream objects should be made locally on the stack when needed. For example, if you look at the listing of the HistoAlgorithm class (see also Listing 31 below) you will note that MsgStream objects are instantiated locally (i.e. not using new) in all three of the IAlgorithm methods and thus are destructed when the methods return. If this is not done messages may be lost, or too many messages may be printed.

The MsgStream class has been designed to resemble closely a normal stream class such as std::cout, and in fact internally uses an ostrstream object. All of the MsgStream member functions write unformatted data; formatted output is handled by the insertion operators.

An example use of the MsgStream class is shown below.

Listing 31 Use of a MsgStream object.

1: #include "Gaudi/MessageSvc/MgsStream.h"
2:
3: StatusCode myAlgo::finalize() {
4:   StatusCode status = Algorithm::finalise();
5:   MsgStream log(messageService(), name());
6:   if ( status.isFailure() ) {
7:     // Print a two line message in case of failure.
8:     log << MSG::ERROR << " Finalize failed" << endl
9:         << "Error initializing Base class." << endreq;
10:   }
11:   else {
12:     log << MSG::DEBUG << "Finalize completed successfully" << endreq;
13:   }
14:   return status;
15: }

When using the MsgStream class just think of it as a configurable output stream whose activation is actually controlled by the first word (message level) and which actually prints only when "endreq" is supplied. For all other functionality simply refer to the C++ ostream class.

The "activation level" of the MsgStream object is controlled by the first expression, e.g. MSG::ERROR or MSG::DEBUG in the example above. Possible values are given by the enumeration below:

 
enum MSG::Level { VERBOUS, DEBUG, INFO, WARNING, ERROR, FATAL };

Thus the code in Listing 31 will produce NO output if the print level of the message service is set higher than MSG::ERROR. In addition if the service's print level is lower than or equal to MSG::DEBUG the "Finalize completed successfully" message will be printed (assuming of course it was successful).

User interface

What follows is a technical description of the part of the MsgStream user interface most often seen by application developers. Please refer to the header file for the complete interface.

Insertion Operator
 

The MsgStream class overloads the '<<` operator as described below.

MsgStream& operator <<(TYPE arg);
Insertion operator for various types. The argument is only formatted by the stream object if the print level is sufficiently high and the stream is active. Otherwise the insertion operators simply return. Through this mechanism extensive debug printout does not cause large run-time overheads. All common base types such as char, unsigned char, int, float, etc. are supported
MsgStream& operator <<(MSG::Level level);
This insertion operator does not format any output, but rather (de)activates the stream's formatting and forwarding engine depending on the value of level.
 
 
Accepted Stream Manipulators
 

The MsgStream specific manipulators are presented below, e.g. endreq: MsgStream& endreq(MsgStream& stream). Besides these, the common ostream and ios manipulators such as std::ends, std::endl,... are also accepted.

endl
Inserts a newline sequence. Opposite to the ostream behaviour this manipulator does not flush the buffer. Full name: MsgStream& endl(MsgStream& s)
ends
Inserts a null character to terminate a string. Full name: MsgStream& ends(MsgStream& s)
flush
Flushes the stream's buffer but does not produce any output! Full name: MsgStream& flush(MsgStream& s)
endreq
Terminates the current message formatting and forwards the message to the message service. If no message service is assigned the output is sent to std::cout. Full name: MsgStream& endreq(MsgStream& s)

11.4  The Particle Properties Service

The Particle Property service is a utility to find information about a named particle's Geant3 ID, Jetset/Pythia ID, Geant3 tracking type, charge, mass or lifetime. The database used by the service can be changed, but by default is the same as that used by SICb.

11.4.1   Initialising and Accessing the Service

This service is created by default by the application manager, with the name "ParticlePropertySvc."

In order to use the service, a client must access its interface, IParticlePropertySvc . From an algorithm this is done as follows:

Listing 32 Code to access the IParticlePropertySvc interface from an Algorithm.

 
#include "Gaudi/Interfaces/IParticlePropertySvc.h" 
... 
IParticlePropertySvc* m_ppSvc; 
StatusCode sc = serviceLocator()->getService( 
"ParticlePropertySvc", 
IID_IParticlePropertySvc, 
reinterpret_cast<IInterface*&>( m_ppSvc )); 
 
if ( sc.isFailure) { 
...

11.4.2  Service Properties

The Particle Property Service currently only has one property: ParticlePropertiesFile . This string property is the name of the database file that should be used by the service to build up its list of particle properties. The default value of this property, on all platforms, is $LHCBDBASE/cdf/particle.cdf

11.4.3  Service Interface

As mentioned earlier, the service implements the IParticlePropertySvc interface which must be included when a client wishes to use the service - the file to include is Gaudi/Interfaces/IParticlePropertySvc.h

The service itself consists of one STL vector to access all of the existing particle properties, and three STL maps, one to map particles by name, one to map particles by Geant3 ID and one to map particles by stdHep ID.

Although there are three maps, there is only one copy of each particle property and thus each property must have a unique particle name and a unique Geant3 ID. The third map does not contain all particles contained in the other two maps; this is because there are particles known to Geant but not to stdHep, such as Deuteron or Cerenkov. Although retrieving particles by name should be sufficient, the second and third maps are there because most of the data generated by SICb stores a particle's Geant3 ID or stdHep ID, and not the particle's name. These maps speed up searches using the IDs.

The IParticlePropertySvc interface provides the following functions:

Listing 33 The IParticlePropertySvc interface.

 
// IParticlePropertySvc interface: 
// Create a new particle property. 
// Input: particle, String name of the particle. 
// Input: geantId, Geant ID of the particle. 
// Input: jetsetId, Jetset ID of the particle. 
// Input: type, Particle type. 
// Input: charge, Particle charge (/e). 
// Input: mass, Particle mass (GeV). 
// Input: tlife, Particle lifetime (s).    
// Return: StatusCode - SUCCESS if the particle property was added. 
virtual StatusCode push_back( const std::string& particle, 				int geantId, int jetsetId, int type, double charge, double mass, double tlife ); 
 
// Create a new particle property. 
// Input: pp, a particle property class. 
// Return: StatusCode - SUCCESS if the particle property was added. 
virtual StatusCode push_back( ParticleProperty* pp ); 
   
// Get a const reference to the begining of the map. 
virtual const_iterator begin() const; 
   
// Get a const reference to the end of the map. 
virtual const_iterator end() const;   
 
// Get the number of properties in the map. 
virtual int size() const; 
 
// Retrieve a property by geant id. 
// Pointer is 0 if no property found. 
virtual ParticleProperty* find( int geantId ); 
 
// Retrieve a property by particle name. 
// Pointer is 0 if no property found. 
virtual ParticleProperty* find( const std::string& name ); 
 
// Retrieve a property by StdHep id 
// Pointer is 0 if no property found. 
virtual ParticleProperty* findByStdHepID( int stdHepId ); 
 
// Erase a property by geant id. 
virtual StatusCode erase( int geantId ); 
 
// Erase a property by particle name. 
virtual StatusCode erase( const std::string& name ); 
 
// Erase a property by StdHep id 
virtual StatusCode eraseByStdHepID( int stdHepId );

The IParticlePropertySvc interface also provides some typedefs for easier coding:

 
typedef ParticleProperty* mapped_type; 
typedef std::map< int, mapped_type, std::less<int> > MapID; 
typedef std::map< std::string, mapped_type, std::less<std::string> > MapName; 
typedef std::map< int, mapped_type, std::less<int> > MapStdHepID; 
typedef IParticlePropertySvc::VectPP VectPP; 
typedef IParticlePropertySvc::const_iterator const_iterator; 
typedef IParticlePropertySvc::iterator iterator;

11.4.4  Examples

Below are some extracts of code from the example in GaudiExamples/ParticleProperties, to show how one might use the service:

Listing 34 Code fragment to find particle properties by particle name.

 
 // Try finding particles by the different methods 
  log << MSG::INFO << "Trying to find properties by Geant3 ID..." << endreq; 
  ParticleProperty* pp1 = m_ppSvc->find( 1 ); 
  if ( pp1 ) log << MSG::INFO << *pp1 << endreq;  
  log << MSG::INFO << "Trying to find properties by name..." << endreq; 
  ParticleProperty* pp2 = m_ppSvc->find( "e+" ); 
  if ( pp2 ) log << MSG::INFO << *pp2 << endreq;  
  log << MSG::INFO << "Trying to find properties by StdHep ID..." << endreq; 
  ParticleProperty* pp3 = m_ppSvc->findByStdHepID( 521 ); 
  if ( pp3 ) log << MSG::INFO << *pp3 << endreq;  

Listing 35 Code fragment showing how to use the map iterators to access particle properties.

 
// List all properties  
log << MSG::DEBUG << "Listing all properties..." << endreq; 
for( IParticlePropertySvc::const_iterator i = m_ppSvc->begin();  
i != m_ppSvc->end(); i++ ) { 
if ( *i ) log << *(*i) << endreq; 
}

11.5  Developing new services

11.5.1  The Service base class

Within Gaudi we use the term "Service" to refer to a class whose job is to provide a set of facilities or utilities to be used by other components. In fact we mean more than this because a concrete service must derive from the Service base class and thus has a certain amount of predefined behaviour; for example it has intialize() and finalize() methods which are invoked by the application manager at well defined times.

Figure 15 Implementation of a concrete service class. Though not shown in the figure, both of the IConcreteSvcType interfaces are derived from IInterface.

 

Figure 15 shows the inheritance structure for an example service called SpecificService (!). The key idea is that a service should derive from the Service base class and additionally implement one or more pure abstract classes (interfaces) such as IConcreteSvcType1 and IConcreteSvcType2 in the figure.

As discussed above, it is necessary to derive from the Service base class so that the concrete service may be made accessible to other Gaudi components. The actual facilities provided by the service are available via the interfaces that it provides. For example the ParticleProperties service implements an interface which provides methods for retrieving, for example, the mass of a given particle. In figure 15 the service implements two interfaces each of two methods.

A component which wishes to make use of a service makes a request to the application manager. Services are requested by a combination of name, and interface type, i.e. an algorithm would request specifically either IConcreteSvcType1 or IConcreteSvcType2.

The identification of what interface types are implemented by a particular class is done via the queryInterface method of the IInterface interface. This method must be implemented in the concrete service class. In addition the initialize() and finalize() methods should be implemented. After initialization the service should be in a state where it may be used by other components.

The service base class offers a number of facilities itself which may be used by derived concrete service classes:

 

11.5.2  Implemention details

The following is essentially a checklist of the minimal code required for a service.

  1. Define the interfaces:

 

Listing 36 An interface class

 
#include "Gaudi/interfaces/IInterface.h" 
 
class IConcreteSvcType1 : virtual public IInterface { 
public: 
  void method1() = 0; 
  int method2() = 0; 
} 
 
#include "IConcreteSvcType1.h" 
 
const IID& IID_IConcreteSvcType1 = 143; // UNIQUE within LHCb !!
  1. Derive the concrete service class from the Service base class.
  2. Implement the queryInterface() method.
  3. Implement the initialize() method. Within this method you should make a call to Service::initialize() as the first statement in the method and also make an explicit call to setProperties() in order to read the service's properties from the job options (note that this is different from Algorithms, where the call to setProperties() is done in the base class).
 

 

Listing 37 A minimal service implementation

 
#include "Gaudi/Kernel/Service.h" 
#include "IConcreteSvcType1.h" 
#include "IConcreteSvcType2.h" 
 
class SpecificService : public Service,  
                        virtual public IConcreteSvcType1, 
                        virtual public IConcreteSvcType2 { 
public: 
  // Constructor of this form required: 
  SpecificService(const std::string& name, ISvcLocator* sl); 
 
  queryInterface(constIID& riid, void** ppvIF); 
}; 
 
// Factory for instantiation of service objects 
static SvcFactory<SpecificService> s_factory; 
const ISvcFactory& SpecificServiceFactory = s_factory; 
 
// UNIQUE Interface identifiers defined elsewhere 
extern const IID& IID_IConcreteSvcType1; 
extern const IID& IID_IConcreteSvcType2; 
 
// queryInterface 
StatusCode SpecificService::queryInterface(const IID& riid, void** ppvIF) { 
  if(IID_IConcreteSvcType1 == riid) { 
    *ppvIF = dynamic_cast<IConcreteSvcType1*> (this); 
    return StatusCode::SUCCESS; 
  } else if(IID_IConcreteSvcType2 == riid) { 
    *ppvIF = dynamic_cast<IConcreteSvcType2*> (this); 
    return StatusCode::SUCCESS; 
  } else { 
    return Service::queryInterface(riid, ppvIF); 
  } 
} 
 
StatusCode SpecificService::initialize() { ... } 
StatusCode SpecificService::finalize() { ... } 
 
// Implement the specifics ... 
SpecificService::method1() {...} 
SpecificService::method2() {...} 
SpecificService::method3() {...} 
SpecificService::method4() {...}