This section shows how to program the database via the API in the context of a very simple program. Our goal is not so much an exhaustive coverage of the API, that's available in the reference pages. The goal is to show how to build and how to run software that makes use of the logbook API.
In the following sections, we present a simple program that can extract the value of a key in the key value store and output it to stdout. The program will be presented in C++, Tcl and Python along with build instructions. The source code for these examples are available in $DAQROOT/share/examples/logbook
Here's the C++ implementation of our toy program, kvexample.cpp:
Example 22-9. C++ example using logbook API
#include <LogBook.h>#include <iostream> #include <stdlib.h> #include <stdexcept> int main(int argc, char** argv) { if (argc != 3 ) {
std::cerr << "kvexmple logbookdatabase key\n"; exit(EXIT_FAILURE); } try {
const char* key = argv[2]; LogBook book(argv[1]);
if (book.kvExists(key)) {
std::cout << key << " : " << book.kvGet(key)
<< std::endl; } else { std::cerr << key << " does not exist\n"; exit (EXIT_FAILURE); } } catch (std::exception& e) {
std::cerr << e.what() << std::endl; exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); }
This program accepts two command line parameters. The first is a logbook database file. The concept of a selected logbook is only maintained by the lg_xxxx utilties and Tcl administrative API. As an exercise for the reader, check for the file ~/.nscl-logbook-current and read the logbook database filename from there.
Let's break this down step by step:
LogBook
class we'll need.
LogBook::Exception
object as an
exception. Therefore the meat of our example is wrapped
in a try/catch block.
LogBook
class provides the
top levels of the API to logbooks. Instances of this
class are constructed on logbook database files. We construct one
here. Methods of this instance then provide the
interface to the database. If the file we provide does not
exist, an exception is thrown.
LogBook
::kvExists
method returns true if the key passed to it
exists in the key/value store and false
if not. We use this to determine if the key the user has
passed us actually exists. If not we report an error.
LogBook
::kvGet
,
which returns the value associated with the key from the key value store.
Note that, since this method throws an exception if the key
does not exist, we could have skipped the call to
kvExist
if we were willing to
live with the error message in the exception that throws.
LogBook::Exception
is derived from
std::exception
. Therefore catching
exceptions of that class will also catch errors thrown by the
LogBook API. This catch block outputs the error message
encapsulated by statndard exception objects and then exits
with an error status.
To compile this toy program we need to include compiler options to find the LogBook.h header, locate and include the API libraries both at link-time and, since they are shared libraries, at run-time:
Example 22-10. Compiling the C++ Logbook API example.
g++ -o kvexample -I$DAQINC -L$DAQLIB -lLogbook kvexample.cpp -Wl,-rpath=$DAQLIB
This produces an executable named kvexample which
can be run directly. The -I$DAQINC adds the
NSCLDAQ header directory to the include file search path. The
-L$DAQLIB option adds the NSCLDAQ library directory
to the link time library search path. -lLogbook
specifies that we need the logbook C++ API and
the -Wl,-rpath=$DAQLIB
part of this command
adds the DAQ library directory to the search path for shared libraries
at run time.
There are two levels of Tcl API. This section will demonstate both the low level Tcl bindings and discuss why care is when using them. We'll then write the program to use the so-called administrative level bindings which hide a lot of the complexity of the low-level bindings at the cost of some performance and a more restrictive interface.
This section describes kvexamplelow.tcl The kvexample program from thep revious section but written with the low level Tcl bindings.
Example 22-11. kvexamplelow.tcl low level Tcl logbook API example
#!/bin/sh # -*- tcl -*- # The next line is executed by /bin/sh, but not tcl \exec tclsh "$0" ${1+"$@"} ## # kvexample for low level bindings. # lappend auto_path [file join $::env(DAQROOT) TclLibs]
package require logbook
if {[llength $argv] ne 2} { puts stderr "kvexamplelow.tcl filename key"
exit -1 } set book [lindex $argv 0] set key [lindex $argv 1] if {[catch {logbook::logbook open $book} instance]} {
puts stderr "Could not open logbook $book : $instance" exit -1 } if {[$instance kvExists $key]} {
puts "$key : [$instance kvGet $key]"
set status 0 } else { puts stderr "No such key: $key in $book" set status -1 } $instance destroy
exit $status
instance
, or 0
on success with the value returned from the command
(the name of the commane ensemble created) in
instance
.
On failure, therefore, this block of code outputs the
error message to stderr and exits with an error status.
On success, the program continues with the logbook
instance command ensemble name in the instance
variable.
This use of object instance command ensembles to produce an object oriented interface to the logbook that closely matches the C++ API, while convenient, places the burden of object destruction on the script author, one Tcl scripters are not used to. It is this burden to explicitly destroy objects when no longer needed that drove the construction of the administrative, or high level API which takes care of this for the scripter at the price of some performance loss (the destruction of objects that could be recycled for future calls).
LogBook
object. So the
$instance kvExists command checks to see
if a key exsists in the key value store of the logbook
encapsulated by the command ensemble in the
instance
variable, returning
boolean
true if so and false
if not.
All instance command ensembles include a destroy subcommand that destroys the objects they represent.
Running the script is just a matter of setting up the NSCLDAQ environment variables, setting the script file to executable and running it as you would any other program.
As we've seen in the previous section, the low level Tcl API imposes a burden of manual object management. This can be tricky even for experienced programmers. It can be downright impossible to get right for most script writers that are not experienced at that issue.
The administrative level or high level Tcl API does this storage management for you by making objects that have only a very short, well defined lifetime. All objects created during the execution of an API call are destroyed before that call exits or raises an error. This provides a much simpler procedural interface at the cost of excessive object construction/destruction (for example each call will open access to the database and destroy it),
It is at this level, that the API provides the concept of a currently selected logbook, that we have seen in previous sections of this chapter. The script we provide below will operate on the currently selected logbook rather than requiring a logbook on the command line. It will raise an error if there is not a currently selected logbook (any API call that requires a selected logbook will raise that error for us).
Let's look at the resulting script:
Example 22-12. kvexamplehi.tcl Using the high level Tcl logbook API
#!/bin/sh # -*- tcl -*- # The next line is executed by /bin/sh, but not tcl \ exec tclsh "$0" ${1+"$@"} lappend auto_path [file join $::env(DAQROOT) TclLibspackage require logbookadmin if {[llength $argv] != 1} { puts stderr "kvexamplehi.tcl key"
exit -1 } set key [lindex $argv 0] if {[kvExists $key]} {
puts "$key : [kvGet $key]"
} else { puts stderr "There is no key named $key" exit -1 } exit 0
kvGet
proc returns the value of
a key if it exists.
As you can see it's much simpler to program against the high level Tcl interface. If that API does not provide everything you need you can certainly mix high and low level calls.
The Python API provides an object oriented Python interface to the Logbook API. Unlike the Tcl low level API, Python's object reference counting and garbage collection can, and are, used to do the storage management the Tcl low level API burdens you with.
Here is the equivalent program in python. Since we can't access the current logbook directly (an excersise for the reader is to pull the logbook filename from ~/.nscl-logbook-current) rather than from the command line.
Note that the API is supported for Python 3 not Python 2. You must use that interpreter when running your scripts.
Example 22-13. kvexample.py programming the Python logbook API
import sysfrom LogBook import LogBook
if len(sys.argv) != 3 : print("kvexample.py logbook key")
exit() bookfile = sys.argv[1]
key = sys.argv[2]
book = LogBook.LogBook(bookfile)
if book.kv_exists(key) :
print(key + " : " + book.kv_get(key))
else : print(key + " does not exist") exit()
sys.argv
array.
sys.args
array is exactly the
C++ argv parameter to main. Its first element is the name
of the script, as fed to Python interpreter.
The following two parameters must be the logbook filename
and key we want to retrieve from it.
Running this script takes a bit of effort. Here's a sample command line that prints the name of the experiment from the KV store:
Example 22-14. Running Python scripts that use the logbook API
PYTHONPATH=$DAQROOT/pythonLibs/nscldaq python3 kvexample.py junk.log experiment
The definition of PYTHONPATH on the command line tells python to look for importable modules in the DAQ python module directory tree.
Note the explicit invocation of python3. Our logbook modules for Python are only available for Python version 3. At the time I'm writing this many Linux distributions install both but make Python version 2 the interpreter invoked by the python command.