22.5.

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

22.5.1. Using the logbook API from C++

Here's the C++ implementation of our toy program, kvexample.cpp:

Example 22-9. C++ example using logbook API


#include <LogBook.h>             (1)
#include <iostream>
#include <stdlib.h>
#include <stdexcept>

int main(int argc, char** argv)
{
  if (argc  != 3 ) {                 (2)
    std::cerr << "kvexmple logbookdatabase key\n";
    exit(EXIT_FAILURE);
  }
  try {                              (3)
    const char* key = argv[2];
    LogBook book(argv[1]);            (4)
    if (book.kvExists(key)) {         (5)
      std::cout << key << " : "
                <<  book.kvGet(key) (6)
                << std::endl;
    } else {
      std::cerr << key << " does not exist\n";
      exit (EXIT_FAILURE);
    }
  } catch (std::exception& e) {    (7)
    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:

(1)
The LogBook.h file contains a definition of much of the API. Specifically, it contains the definition of the LogBook class we'll need.
(2)
This chunk of code ensures we have a logbook filename and a key name to lookup. If not we exit with a message showing the user how to run the program.
(3)
Many API functions report errors by throwing a LogBook::Exception object as an exception. Therefore the meat of our example is wrapped in a try/catch block.
(4)
The 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.
(5)
The 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.
(6)
If the key does exist we can feth its value using\ 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.
(7)
The 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.

22.5.2. Using the logbook API from Tcl

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.

22.5.2.1. Low Level Tcl bindiings

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 \  (1)
exec tclsh "$0" ${1+"$@"}


##
#  kvexample for low level bindings.
#
lappend auto_path [file join $::env(DAQROOT) TclLibs]    (2)
package require logbook                                  (3)

if {[llength $argv] ne 2} {
    puts stderr "kvexamplelow.tcl filename key"         (4)
    exit -1
}

set book [lindex $argv 0]
set key  [lindex $argv 1]

if {[catch {logbook::logbook open $book} instance]} {    (5)
    puts stderr "Could not open logbook $book : $instance"
    exit -1
}

if {[$instance kvExists $key]} {                        (6)
    puts "$key : [$instance kvGet $key]"                (7)
    set status 0
} else {
    puts stderr "No such key: $key in $book"
    set status -1
}

$instance destroy                                      (8)
exit $status

               
(1)
If you just set a Tcl script executable but tclsh is not considered a valid shell, you won't be able to execute it. These three four lines make this script executable by sh and the last of these four then chains off to the Tcl shell.
(2)
This line adds the NSCLDAQ Tcl package library path to the set of paths searched by package require.
(3)
Incorporates the low level Tcl API package, logbook into the script. This defines Tcl commands in the ::logbook namespace. As we will see the top level command is the logbook::logbook command ensemble.
(4)
Our script requires two parameters, the logbook filename and a key in the key value store. This ensures both are present.
(5)
The logbook::logbook open command creates a Tcl command ensemble that allows access to the logbook. On failure the command raises an error. This use of the Tcl catch command returns 1 on error with the error message in the variable 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).

(6)
The subcommands of a logbook instance command ensemble are, essentially the same as methods for a C++ 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.
(7)
Similarly a logbook instance command ensemble's kvGet subcommand returns the value of a key.
(8)
Having gotten an logbook instance "object" from the API, we must destroy it. This is true of all object instance commands we get from the API. For example, the logbook instance API subcomand to list notes will create a list of note command ensembles, all of which must eventually be manually destroyed.

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.

22.5.2.2. Using the high-level or administrative Tcl API

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) TclLibs   (1)
    
package require logbookadmin

if {[llength $argv] != 1} {
    puts stderr "kvexamplehi.tcl  key"                (2)
    exit -1
}

set key [lindex $argv 0]
if {[kvExists $key]} {                               (3)
    puts "$key : [kvGet $key]"                       (4)
} else {
    puts stderr "There is no key named $key"
    exit -1
}
exit 0

               
(1)
This code appeared in kvexamplelow.tcl low level Tcl logbook API example. We won't explain it again here. Refer to that example if you need to understand it.
(2)
Unlike kvexamplelow.tcl low level Tcl logbook API example, we refer to the current logbook. Therefore we only need one parameter, the key to retrieve.
(3)
This checks that the key exists. It does this by creating a logbook instance for the current logbook, asking if the key exists and destroying that instance. In doing this, at the cost of some inefficiency (the logbook instance could be recycled), resource leaks are prevented at the level of your script.
(4)
The 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.

22.5.3. Using the logbook API from Python

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 sys                              (1)
from  LogBook import LogBook            (2)

if len(sys.argv) != 3 :
    print("kvexample.py logbook key")  (3)
    exit()

bookfile = sys.argv[1]                 (4)
key      = sys.argv[2]                 (5)

book = LogBook.LogBook(bookfile)       (6)
if book.kv_exists(key) :               (7)
    print(key + " : " + book.kv_get(key)) (8)
else :
    print(key + " does not exist")

exit()

            
(1)
This package provides access to the raw command line words via the sys.argv array.
(2)
Brings in the LogBook class from the LogBook module. This class is the top level API class. It allows us to create, and access logbooks and their contents.
(3)
The 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.
(4)
As described above we fish the logbook filename from the command arguments.
(5)
As described above, we fish the key to retrieve from the command arguments.

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