Chapter 36. The state manager

The state manager is a program that manages and enforces the experiment state diagram. It also provides a mechanism for external programs to:

The state diagram that is enforced by the state manager is shown below:

Figure 36-1. Experiment (State manager) State Diagram

Here's a description of the states and the possible transitions out of each state.

NotReady

This state means that the experiment has been defined but that one or more pieces have not yet been started. The BOOT transition implies an intent to start all of the pieces that make up the experiment. The BOOT transition places the experiment in the Booting state.

Booting

This state implies the pieces that make up the experiment are being started. The Boot manager described in The Boot utility responds to entering this state by creating rings and starting programs defined by a list of experiment created by the experiment configuration editor described in Experiment Configuration utility.

The READY transition indicates a successful start of all of the components. the FAIL transition indicates that at least one component could not be created. If the boot manager is used it will initiate a FAIL transition if it detects an error while creating the experiment components. It will also initiate a READY transition if all of the components started correctly.

Ready

Indicates that the experiment is ready to take data but is not yet taking data. If the boot manager is being used, it monitors the programs it started and if any of them exit (normally or abnormally), it intiates a FAIL transition and shuts down all of the programs it is managing.

The BEGIN transition indicates a desire to start taking data.

Active

Means that the experiment is acquiring data. If any component fails or if any component was not able to start taking data due to an unrecoverable error, it can initiate a FAIL transition taking the program back to the NotReady state.

The END transition indicates a desire to stop taking data and makes the transition to the Ready state.

The state diagram enforces this diagram. For example, it will refuse to respond to a BOOT request while in the Ready state, returning an error condition if that request is made.

To run the state manager, you must first define some environment variables. This is done sourcing the dasetup.bash script from the root of the installation directory for your DAQ distribution.

If, for example, NSCLDAQ is installed in /usr/opt/daq/11.0:


. /usr/opt/daq/11.0/daqsetup.bash
            

makes the necessary environment variable definitions.

Once the daqsetup.bash script has been run, you can start up the experiment configuration tool via the command:


$DAQBIN/statemanager
            

The state manager obtains and registers two ports named StatePublish and StateRequest from the NSCLDAQ port manager.

36.1. Application Programming Interfaces to the state manager

By itself, the state manager does nothing. Its value is in its interactions with other programs.

Application programming interfaces make it easy for programs written in Tcl/Tk, C++ or Python to interact with the state manager. The remainder of this section will show some simple examples for each supported language.

36.1.1. Tcl/Tk state manager API

Below is an annotated example of a Tcl state manager client.

Example 36-1. Tcl client of the state manager


##
# @file tcltest1.tcl
# @brief Test the state monitor tcl callbacks.
# @author Ron Fox <fox@nscl.msu.edu>

lappend auto_path [file join $env(DAROOT) TclLibs]  (1)
package require statemanager

set reqURI [lindex $argv 0]                     (2)
set subURI [lindex $argv 1]

statemanager::statemonitor start $reqURI $subURI (3)





statemanager::statemonitor register NotReady test
statemanager::statemonitor register Ready    test  (4)
statemanager::statemonitor register Booting  test

proc test {from to} {                              (5)
    puts "Transition $from -> $to"            
    if {$to eq "Ready"} {
        statemanager::transition BEGIN             (6)
    }
}

vwait forever                                     (7)
                
(1)
Under the assumption the daqstart.bash script has been sourced, the environment variable DAQROOT is the path to the top level directory of the NSCLDAQ installation. This line and the next add the Tcl library directory tree to the auto_path variable and then incorporate the state manager API into the program.
(2)
The state manager identifies connection endpoints using Uniform Resource Identifiers. The program assumes the URI for the state manager's state transiton request port and state transition subscription port are supplied on the command line. These two lines extract those URI's from he command line parameters.

The URI's for the state manager are of the form> tcp://hostname:portnum where hostname is the name of the host running the state manager and portnum is the number of the TCP/IP port on which the state manager is listening for connection for that specific service.

(3)
Starts the state manager notifier. The notifier is a a component of the state manager that pumps transition and state notification received from the subscription port into the Tcl event loop.

The parameters are the request and subscription URIs that tell the state manager API how to connect with the state manager server.

(4)
These lines subscribe to specific states. In this trivial program the test command/proc is invoked when the indicated state is entered.

This can happen either because the program receives its first state broadcast before it ever sees a transition, or it sees a transition broadcast.

(5)
This is the handler for state changes. The state manager API appends the prior state as well as the new current state to the command. These become the from and to parameters of the proc. The first time the state becomes known (either via a state broadcast or if a transition is received prior to a state broadcast), the prior state is unknown and the from parameter is set to the empty string.
(6)
This line shows how to request a transition from the state manager. In this case as soon as the state becomes ready, we request the state transition to Active via the BEGIN transition.

Note this is just a toy application and this code is nonesensical as it means that whenever the run stops it will start again.

(7)
vwait command enters the event loop until the forever variable is modified (it never is). This allows the program to respond to state transitions that are posted as events. A Tcl/Tk application automatically gets an event loop and does not need this statement.

36.1.2. C++ State manager API

The C++ state manager api lives in the $DAQLIB/libStateMonitor.so shared library. The header <CStateMonitor.h>. The DAQLIB environment variable is defined by the daqsetup.bash script in the top level directory of NSCLDAQ 11.0 or later.

Let's look at an annotated C++ program that is a client to the state manager:

Example 36-2. C++ client for the state manager.


#include "CStateMonitor.h"                    (1)
#include <iostream>

void
NotReady(CStateMonitor* pMonitor, std::string from, std::string to, void* arg) (2)
{
    const char* p =  reinterpret_cast<const char*>(arg);                 (3)
    
    std::cout << "NotReady transition " << from << "->" << to << " (" << p << ")\n";
}

void
Ready(CStateMonitor* pMonitor, std::string from, std::string to, void* arg)
{
    const char* p =  reinterpret_cast<const char*>(arg);
    
    std::cout << "Ready transition " << from << "->" << to << " (" << p << ")\n";
}

int main(int argc, char**argv)
{
    std::string reqURI   = argv[1];                                      (4)
    std::string stateURI = argv[2];
    
    CStateMonitor mon(reqURI, stateURI);                                 (5)

    mon.Register("NotReady", NotReady, const_cast<char*>("Some text")); (6)
    mon.Register("Ready", Ready, const_cast<char*>("Different text"));
    
    mon.run();                                                         (7)
}
                
(1)
This line includes the header that defines the classes that make up the C++ API to the state manager.
(2)
NotReady will be a state handler. State handlers are registered to be called when one or more of a set of states has been entered. State handlers are passed a pointer to the state monitor, the state names involved in the transition (from is the empty string if the prior state is not known), and an application specific parameters.
(3)
As we will see, the client data used in this simple program is a character string. This line makes the client data (arg), usable as a char* again.
(4)
The state manager's ports are specified as Universal Resource Identifiers (URIs). Inthe as of this program, the URIs of the state transition request port and the state publication port are passeed in as command line parameters. This naive code (no error checking) pulls in those URI's from the command line.
(5)
The CStateMonitor is a class that encapsulates the API to the state manager. This line creates an instanc eof that class, providing it the URI's it needs to connect to the state manager ports.

Note that while this program does not demonstrate that fact, the class has a method requestTransition tht can request state transition from the state manager.

(6)
State monitoring works by registering states our out application is interested. When the interesting state is entered, a callback is invoked. The callback is passed the state manager object, the prior state, the new state and an application specific parameter that is not interpreted by the CStateMonitor object.

These lines register intereste in the NotReady and Ready states.

(7)
Invoking the run method on a CStateMonitor object enters its event loop. The monitor processes data from the state manager and invokes callbacks for registered states as they are entered. This method won't exit, however there are classes that do allow you to interleave a state manager event loop with other processing. See the reference material for more information.

36.1.3. Python state manager API

This section shows two sample Python programs that interface with the state manager. The first program monitors state transitions. The second program simply takes lines from stdin and pushes them as state transition requests to the statemanager, outputing the status of the request to stdout.

Both of the examples assume that the environment variables defined by $DAQROOT/daqsetup.bash have been incorporated into you shell environment. This is necessary as the PYTHONPATH variable is modified to allow NSCLDAQ specific packages to be imported.

Example 36-3. Monitoring the state manager in Python


from nscldaq.statemanager import StateMonitor    (1)
import sys

## usage:
#  python StateMonitorTest1 statemanager-req-uri state-manager-pub-uri
#

requestUri = sys.argv[1]                        (2)
pubUri     = sys.argv[2]


# We'll just use unbound methods for our callbacks:

def NotReadyHandler(monitor, fromState, toState, cbarg):  (3)
    print('Not Ready state entered: %r -> %r  (%s)' %(fromState, toState, cbarg))


def ReadyHandler(monitor, fromState, toState, cbarg):
    print('Ready state entered: %r -> %r  (%s)' %(fromState, toState, cbarg))


mon = StateMonitor.StateMonitor(requestUri, pubUri)    (4)
mon.register('NotReady', NotReadyHandler, 'poof')      (5)
mon.register('Ready', ReadyHandler, 'poof poof')

mon.run()                                              (6)
                
(1)
This line imports the Statemonitor class from the nscldaq.statemanager package. The StateMonitor class encapsulates the highest level API to the state manager server. Lower level interfaces exist and may be necessary for more complex examples. See the reference material for more information.

All nscldaq related packages for Python are sub packages in the nscldaq package tree to ensure their names don't collide with other packages you might use within your scripts.

(2)
Connection endpoints for the state manager are expressed as Uniform Resource Identifiers (URIs). These URIs are of the form tcp://hostname:port. Where hostname is the host in which the server is running and port is the TCP/IP port on which the monitor is waiting for connections.

This program accepts those URI's on the command line The first URI is the URI that corresponds to the state transition request port, the second corresponds to the state/transition publication port.

As we will see in the second example, if you know which host the state manager is running under, it is possible to ask the nscl port manager for the ports it use and, from them, construct the approprate URIs

(3)
This def and the next are state handlers that will be registered with the state manager. A state manager is a python method that (in addition to self if it is an object method), has the following parameters passed in:

monitor

The state monitor API object.

fromState

The prior state. This is None if the previous state is not known. It is the string state name of the previous state otherwise.

toState

String containing the state that has just been entered.

cbarg

A parameter that is provided by the application when the state handler is registered and is not interpreted/modified in any way by the API.

(4)
The StateMonitor class represents the API that accesses the state manager server. This line creates an instance of that class providing it with the URI's that describe how to connect to both the transition request and state/transition publication ports.
(5)
The state monitoring part of the state manager API works by registering callback handlers for states of interest. When the system enters a state for which you have declared interest, the callback associated with that state is invoked.

This line of code and the next line register interest, and callback handlers, for the NotReady and Ready states.

(6)
Enters the event handling loop of the StateMonitor. This loop processes state and transition publications from the state manager server dispatching interesting ones to registered callback handlers.

Example 36-4. Command line state changer


from nscldaq.statemanager import StateMonitor       (1)
from nscldaq.statemanager import Utilities
import sys
import getpass

username        = getpass.getuser()                 (2)
requestPort     = Utilities.getPort('localhost', 'StateRequest', username)
transitionPort  = Utilities.getPort('localhost', 'StatePublish', username)

requestUri    = 'tcp://localhost:%d' % (requestPort)
transitionUri = 'tcp://localhost:%d' %(transitionPort)

mon = StateMonitor.StateMonitor(requestUri, transitionUri)  (3)

while 1:
    line = sys.stdin.readline()                   (4)
    if not line:
        break
    
    reply = mon.requestTransition(line[0:-1])     (5)
    print("Reply: %s" % (reply))                  (6)
                
(1)
Imports the packages required for the application. The first two lines are the ones that are interesting to us. All NSCLDAQ python packages are provided inside the toplevel nsclddaq package and the state manager related packages are themselves inside of nscldaq.statemanager

The StateMonitor package provides a high level API to the state manager. The Utiltities package provides several utilities useful both to the state manager and its clients.

The sys and getpass packages are standard Python packages that are also used by this application.

(2)
This section of code constructs URIs that describe the state transition request and state/transition publication ports. In this case, the state manager is assumed to be running on the same host as the program.

The first three linse of this section of code use the utility functions to get the ports the state manager has requested from the port manager. The StateRequest service handles requests for transitions while the StatePublish service publishes states and transitions. The statemanager is also assumed to be run by the same user as the one running this program. The getPort function will throw a RuntimeException if it is not able to locate the requested services.

The next two lines encode the port into URIs so that they can be passed to the constructor for the StateMonitor class.

(3)
Creates an instance of the StateMonitor class. This object provides a method requestTransition that will be used to request state transitions.
(4)
The main loop of the program gets lines from stdin. sys.stdin.readline() returns an empty string when the end of file is reached on stdin. When that happens we exit. Note that realine includes the newline line terminator in the data it returns.
(5)
This line makes the transition request. The line is sliced so that the newline is not passsed as part of the transition request.
(6)
This prints the reply to the state transition request from the state manager. On success, the return value is OK. On failure the return value is FAIL followed by an error message.