seiscomp.client¶
Modules are meant to be standalone programs doing a particular job. The seiscomp.client package focuses on three main aspects:
Communicate with other modules
Access station- and event metadata through a database
Fetch waveform data
Therefore a client package has been developed combining these concepts in an
easy way with only a couple of API calls. Since SeisComP has been developed in
C++ and uses the object oriented paradigm forcefully, modules build on the
Application (C++: Seiscomp::Client::Application
, Python:
seiscomp.client.Application
) class. It manages the messaging connection
and waveform sources in a transparent way.
The class Seiscomp::Client::Application
is the base class for
all SeisComP applications. It manages messaging and database
connections, provides access to command line options and configuration
parameters and also handles and interprets notifier messages.
Blocking network operations like reading messages are moved into threads that are synchronized in a single blocking message queue. This queue allows pushing elements from different threads and unblocks when a new element is ready to be popped. If the queue is full (currently 10 elements are allowed) the pushing threads also block until an element can be pushed again.
This way applications do not have to poll and thus do not burn CPU cycles.
The application class is event driven. It runs the event loop which pops the
message queue and dispatches events with their handlers. Handler methods are
prefixed with handle, e.g. handleMessage()
.
Note
When overriding handlers it is always good practise to call the base handlers before running custom code.
Application class¶
The application class is part of the seiscomp.client package. It needs to be imported first.
import seiscomp.client
A common strategy to write a module with that class is to derive from it and run it in a Python main method.
import sys
import seiscomp.client
# Class definition
class MyApp(seiscomp.client.Application):
def __init__(self, argc, argv):
seiscomp.client.Application.__init__(self, argc, argv)
# Main method to call the app
def main(argc, argv):
app = MyApp(argc, argv)
return app()
# Call the main method if run as script
if __name__ == "__main__":
sys.exit(main(len(sys.argv), sys.argv))
An application can be called with the parenthesis operator ()
which
returns the applications result code and serves as input to sys.exit()
.
Operator() is a wrapper for Application.exec()
.
The workflow of Application.exec()
looks as follows:
def exec(self):
self.returnCode = 1
if self.init() and self.run():
self.returnCode = 0
self.done()
return self.returnCode
init()
, run()
and done()
are explained in more detail in
the next sections.
Constructor¶
To create an application, derive from the seiscomp.client.Application class and configure it in the constructor.
1class MyApp(seiscomp.client.Application):
2 # MyApp constructor
3 def __init__(self, argc, argv):
4 # IMPORTANT: call the base class constructor
5 seiscomp.client.Application.__init__(self, argc, argv)
6 # Default is TRUE
7 self.setMessagingEnabled(False)
8 # Default is TRUE, TRUE
9 self.setDatabaseEnabled(False, False)
10 # Default is TRUE
11 self.setDaemonEnabled(False)
As marked in line 4, the call of the constructor of the base class is very important. It takes the command line parameters and sets up internal application variables. Without this call the application will either not run at all or show undefined/unexpected behaviour.
The constructor takes also the initial parameters of the application such as enabling a messaging connection and enabling database access.
Messaging, database and daemon mode is enabled by default. The daemon mode is
important if the application should be started as service and therefore should
support the option -D, --daemon
. Utilities and non daemon applications
should disable that mode.
Example calls to this options are shown in the highlighted lines of the above code block.
If messaging is enabled, the messaging username is derived from the binary called (not the class name). If the script is called test.py then the username selected is test. The username can be overridden either in the configuration file (Global parameters) or using the API.
self.setMessagingUsername("test")
Setting the username to an empty string results in a random username selected by the messaging server.
All application methods are defined in the C++ header file
src/trunk/libs/seiscomp/client/application.h
.
Init¶
The workflow of the init function looks like this:
init (virtual)
initConfiguration (virtual)
initCommandLine (virtual)
createCommandLineDescription (virtual)
parseCommandLine (virtual)
printUsage (virtual)
validateParameters (virtual)
loadPlugins
forkDaemon
initMessaging
initDatabase
loadInventory or loadStations
loadDBConfigModule
loadCities
Methods marked with virtual can be overridden. init()
itself calls
a lot of handlers that can be customized. Typical handlers are
initConfiguration()
, createCommandLineDescription()
and validateParameters()
.
initConfiguration()
is used to read parameters of the configuration files
and to populate the internal state. If something fails or if configured values
are out of bounds, False can be returned which causes init()
to return
False and to exit the application with a non-zero result code.
An example is show below:
def initConfiguration(self):
if not seiscomp.client.Application.initConfiguration(self):
return False
try:
self._directory = self.configGetString("directory")
except:
pass
return True
This method reads the directory parameter from the configuration file(s) and sets it internally. If the directory is not given in any of the modules configuration files, it logs an error and aborts the application by returning False.
createCommandLineDescription()
is used to add custom command line options.
This is a void function and does not return any value. It is also not necessary
to call the base class method although it does not hurt.
def createCommandLineDescription(self):
self.commandline().addGroup("Storage")
self.commandline().addStringOption("Storage", "directory,o", "Specify the storage directory")
A new command line option group is added with addGroup()
and then a new
option is added to this group which is a string option.
Four types can be added
as options: string, int, double and bool: addStringOption()
, addIntOption()
,
addDoubleOption()
and addBoolOption()
.
validateParameters()
can be used to fetch the values of previously added
command line options and to validate each parameter. If False is returned, the
application is aborted with a non-zero result code.
def validateParameters(self):
try:
self._directory = self.commandline().optionString("directory")
except:
pass
# The directory validity is checked to avoid duplicate checks in
# initConfiguration.
if not self._directory:
seiscomp.logging.error("directory not set")
return False
if not exists(self._directory):
seiscomp.logging.error(
"directory {} does not exist".format(self._directory))
return False
return True
Custom initialization code after checking all parameters can be placed in the
overridden method init()
.
But be aware that the process forked already if started as daemon. To run before
the fork, it needs to be put into validateParameters()
.
Run¶
The workflow of the run method looks like this:
run (virtual)
startMessageThread
messageLoop
readMessage
dispatchMessage (virtual)
handleMessage (virtual)
addObject (virtual)
updateObject (virtual)
removeObject (virtual)
handleReconnect (virtual)
handleDisconnect (virtual)
handleTimeout (virtual)
handleAutoShutdown (virtual)
The run method starts the event loop and waits for new events in the queue.
In case of messaging a thread is started that sits and waits for messages
and feeds them to the queue and to the event loop in run()
. Without
messaging the run loop would do nothing but waiting for SIGTERM or
a timer event enabled with enableTimer()
. If the event loop is not needed
because no timer and messages are needed, it should be overridden and the
code should be placed there. This will disable the event loop.
run()
is expected to return True on success and False otherwise. If False
is returned the application exists with a non-zero return code. Custom return
codes can always be set with Application.exit()
.
If the scmaster sends a message to the client it is received in the applications
message thread and pushed to the queue. The event loop pops the message from
the queue and calls handleMessage()
. The default implementation uses two
settings when handling a messages that can be controlled with
enableInterpretNotifier()
and enableAutoApplyNotifier()
.
enableInterpretNotifier()
controls whether the Application queries the
message type and extracts notifier objects. For each notifier it parses the
operation and dispatches the parentID and the object either to
addObject()
, updateObject()
or removeObject()
handler. This
behaviour is enabled by default. If disabled, a clients needs to parse the
messages by itself and implement this method.
enableAutoApplyNotifier()
controls whether incoming notifier objects are
applied automatically to objects in local memory. If the client has already
an object in memory and an update notifier for this object is received, the object
in the notifier is copied to the local object. This behaviour is enabled by default.
Done¶
The workflow of the done method looks like this:
done (virtual)
closeTimer
closeMessaging
closeDatabase
done()
is usually not overridden. If custom code and clean up procedures
need to be placed in done()
, the base class must be called. done()
is a
void function.
def done(self):
seiscomp.client.Application.done()
# Custom clean ups
closeMyDataFiles()
closeCustomConnections()
StreamApplication class¶
The application class has another occurrence: seiscomp.client.StreamApplication
.
The class StreamApplication
extends the Application
in terms of record acquisition. It spawns another thread that reads the records
from a configurable source and adds a new handler method
StreamApplication.handleRecord()
to handle these records.
Its workflow looks like this:
init (virtual)
+initRecordStream
run (virtual)
+startAcquisitionThread
+storeRecord
Application.messageLoop
dispatchMessage (virtual)
+handleRecord (virtual)
done (virtual)
+closeRecordStream
Received records can be handled with handleRecord()
.
def handleRecord(self, rec):
print rec.streamID()
The stream subscription should be done in init()
. recordStream()
returns the RecordStream instance which can be used to add stream requests.
def init(self):
if not seiscomp.client.StreamApplication.init(self):
return False
# Subscribe to some streams
self.recordStream().addStream("GE", "MORC", "", "BHZ")
return True
The record stream service is configured either with configuration files
(recordstream
) or
via command-line options -I`, ``--record-url
.
The application finishes if the record stream read EOF. Running a StreamApplication
with Seedlink would probably never terminate since it is a
real time connection and handles reconnects automatically.