Why?
A question often heard from EPICS newcomers is "how can I tell what PVs are on an IOC?"
There's no simple answer. Nothing built into EPICS Base (at least not in version 3) will give a remote client that information. Many EPICS facilities develop their own methods for keeping track of what PV names exist in their systems, using everything from spreadsheets and complex databases that can list what SHOULD be on any given IOC, to simply having IOC startup scripts dump the output of a 'dbl' command to a file and using a cron script to do processing on these files later.
What PVs are on an IOC can be an important thing to know. Developers of control screens want to know what PVs exist for the systems they're working with, EPICS support staff need to know what PVs to include when configuring EPICS channel archiving. In my support work I found myself needing to know this sort of thing all the time.
I also found that I wanted know more than just the PV names. I wanted to use "info" fields to flag PVs for save/restore or for archiving, and for that I needed to be able to determine remotely what these fields were. I wanted to have access to the actual database files the IOCs were using so I could check whether the PVs used in record link fields actually existed, and for that I'd have to identify all the aliases that PV names might have. To parse database and substitution files I often needed to know what the values of environment variables were.
The obvious occurred to me: the IOC task has access to all this information. So why not write an EPICS IOC support module that allows external clients to access this information via a method convenient for scripting languages like Python, for example HTTP URLs and JSON encoding to make it easier for the poor script designer (me) to pull out the various bits desired?
That's what infoServ does. In addition to a PV list with alias and info-fields data it will also return the contents of files from the IOC application's directory, the values currently assigned to environment variables, and provide some (admittedly rather basic) information about the IOC like how long it's been up and what version of EPICS it's running. It also allows other support modules to register their own URL-handlers to provide any information they want to make available.
... and maybe Why Not
The problem with a service that delivers IOC information, of course, is that it allows people to get at information that you might not want them to have. The contents of a file in the IOC's directory might contain proprietary code or other information, or maybe even contain passwords. There could be intellectual property considerations with source files or stored data. Those sorts of things need to be considered when deploying this service.
With that out of the way, let's proceed...
The infoServ module has no special requirements and uses no external support modules. It should work equally well in any EPICS build environment with a c++ compiler. It has been testing on 32-bit and 64-bit Linux with EPICS R3.14.12.2, Sun/Solaris 5.8 with EPICS R3.14.9, and VxWorks 5.4 with EPICS 3.14.6.
Sites can create their own additions to the infoServ service via an URL-handling API it provides (see API for adding URLs). Requirements for those would of course depend on what these additions need.
For a demonstration of what an IOC's infoServ data looks like in a web browser, click here.
Adding the infoServ support module to an IOC application
Obtain the latest infoServ gzipped tarball (see the download section for the link) and unpack it in your IOC's support modules directory.
Edit the infoServ/configure/RELEASE file to set the EPICS_BASE environment variable to what your IOC is using.
Compile the infoServ module:
- (cd to infoServ top directory)
- make
Add a line for infoServ to your IOC's $(TOP)/configure/RELEASE file, pointing to where infoServ is at in your support modules directory tree. For example:
- INFOSERV = $(SUPPORT)/infoServ
Add lines to your IOC application's src/Makefile to include the infoServ.dbd file and library in the build. For example:
- $(PROD_IOC)_DBD += infoServ.dbd
- PROD_LIBS += infoServ
If you want to include the demonstration infoServ add-in testAddIn, then add this line also:
- $(PROD_IOC)_DBD += testAddIn.dbd
Add line(s) to your IOC's startup script for starting the infoServ service and (optionally) setting the port number the service should use. These lines can occur before or after iocInit() is called, it doesn't matter. Changing the port number while infoServ is running is allowed also.
- infoServSetParam("Port",7729)
- infoServStart()
If multiple IOCs are running on the same IP address then each one running infoServ needs to have a unique port number assigned. The default port number if none is specified is 7729.
Recompile your IOC to build infoServ into it:
- (cd to IOC's top directory)
- make clean
- make
Restart your IOC. Then point a web browser at the name/IP-address of the IOC and the infoServ port number used (the default is 7729). For example:
http://testioc.where.i.am:7729You should see the infoServ home page with a link to "help".
Built-in informational URLs
For a demonstration of what the data these URLs return looks like, see the What the data looks like section.
/help? Help page, essentially what you're seeing here... /info? (JSON data) server information /pvs?[fields=FIELD[,FIELD2...]] (JSON data) get list of records on IOC, and optionally field values /env?VARNAME[,VARNAME2...] (JSON data) get values of IOC environment variables /file?FILENAME (TEXT) get contents of a given file
absolute directory paths ONLY
MUST be subdir of $(TOP) or allowed by IOC call to infoServAllowFilePath()/fileexists?FILENAME (JSON) whether the given file exists, same limitations as /file? /filepaths? (JSON data) get list of allowed file paths for /file? and /fileexists? /startup? (JSON data) get IOC startup script info IOC shell commands
A number of IOC shell commands are provided to configure module parameters and help diagnose problems, these are detailed below.
- infoServStart()
Starts the infoServ service on the IOC. The service can stopped at any time with infoServStop().
- infoServStop()
Stops the infoServ service on the IOC. The service can be restarted at any time with infoServStart().
- infoServSetParam(parameter,value)
Sets a given module parameter, identified by a text string, to the given integer value. Invoking this function without any arguments displays the current values of all configurable parameters.
Unless specifically noted in their description, parameters can be changed at any time.
The following parameters are provided:
Parameter Default Description DebugLevel 0 Debugging level. The higher the level the more debug messages will be printed. Port 7729 The port number infoServ should listen for connections on.
Every IOC running on the same host/IP needs to have a unique port number assigned to it.
MaxClients 10 The maximum number of simultaneous client connections to allow.
SelectTimeoutSec 0 This (along with the SelectTimeoutUSec parameter) sets the timeout infoServ uses in its socket select() call. This is a fiddly bit that probably never needs to be touched, but is exposed in case anyone wants to do any tests with it.
SelectTimeoutUSec 100000 (see SelectTimeoutSec, this is the microseconds portion)
ClientLingerSec 5 How long client socket connections "linger" after all requested data has been sent to them (the SO_LINGER sockets option). This may need to be increased for very slow clients or very busy networks.
ClientTimeoutSec 10 How long clients have to complete sending their request after they initially connect before the connection is considered timed-out and infoServ drops the connection. This may need to be increased for very slow clients or very busy networks.
ThreadRecycleSec 30 A separate handling thread is created for each client connection. Rather than disposing of these immediately after a client disconnects, these are kept for ThreadRecycleSec seconds so if another client connects in that time it can be assigned to the currently-unused thread.
- infoServAllowFilePath(directory_path)
To prevent clients from getting at Very Important Files on IOCs (for example /etc/passwd ?) all requests to the /file? and /fileexists? URLs must be absolute path names (no ./ or ../ allowed) and are restricted to files within a set of allowed directory names.
The IOC's $(TOP) directory is always allowed by default. Any additional directories desired can be added to the list using this command.
The /filepaths? URL will return a JSON-encoded list of the allowed directories.
- infoServReport
Displays a short summary of the infoServ service's status. It looks something like this:
infoServ 1.0.0.2 server state : started up seconds : 10383 client stats: current client count : 0 highest client count : 1 created client count : 3 recycled client count : 4 deleted client count : 3API for adding URLs
This topic is a little too involved to cover in much detail. There is a demo add-in for infoServ called testAddIn if you want to see what a complete example with source code looks like (testAddIn.cpp in the infoServ src directory). To try that out:
Make sure the testAddIn.dbd line was added to your IOC's src/Makefile (see Adding the infoServ support module to an IOC application). The add-in's code is already built into the infoServ library, this just makes the IOC shell commands to start and stop it visibile.
call testAddInStart() from the IOC shell to initialize the add-in
call testAddInStop() from the IOC shell to terminate it
While testAddIn is running its two supported URLs (/bob? and /tim?) will appear in the infoServ /help? content and a line for it will appear in the /info? URL's "urlhandlers" section. If it's stopped these will no longer appear.
To create an add-in from scratch the bullet-points below should be enough to get started, feel free to email me (see the Support section) if there's anything you'd like to do but aren't certain how.
Examine the infoServ.h header file, in particular the infoServ_requestHandler class and the infoServ_registerUrlHandler() function.
Create a descendant of the infoServ_requestHandler class, providing override methods for AT LEAST these pure-virtual functions:
- virtual bool canHandleUrl(const char *urlText);
This function will be passed the trailing text of the URL after the host:port portion. For example for the URL:
'http://myhost:7729/something?key=value'
it would receive:
'/something?key=value'.
It should return TRUE (1) if the class can handle this URL and FALSE (0) otherwise.
- virtual bool handleUrl(SOCKET sock, const char *urlText);
If the request handler signals that it can handle the URL, this function will then be passed the client's socket and the trailing URL text (same as passed to the canHandleUrl method). It should send back whatever data the client has requested through the client's socket.
The data can be in whatever format makes the most sense for the type of data it is. The infoServ support library contains some basic convenience functions for sending JSON-encoded text/value pairs if that's what's desired, that will handle properly escaping special characters. It's up to the user code to make sure the complete reply winds up being properly JSON formatted with all appropriate commas, matching braces and brackets and etc.
This function should return TRUE (1) if the class handled this URL and sent the client some data (even if that is an error message) and FALSE (0) otherwise. If it returns FALSE a "404 Not Found" error will be sent back to the client.
Have your code create an instance of your infoServ_requestHandler descendant and register it with infoServ using the infoServ_registerUrlHandler() function.
Test, debug, fix, re-test, etc.
NOTE: User-registered URL handlers will be called in reverse-order of when they registered. This allows for user URLs to override the behavior of built-in URLs if desired.
This section provides a collection of issues discovered in using the infoServ service, with suggested work-arounds when available.
Relative path used for IOC startup
One of the many things I want to do with infoServ is get the IOC's startup script file, so I can parse it for what databases and substitution files it loads and then parse them as well. The built-in /startup? and /file? URLs are included with that in mind.
But there's a problem. The infoServ service can't easily tell what the startup script file name is or what directory it was launched from. On VxWorks it's easy, there's systems calls that will tell you. But on a Soft IOC you have to hope the user, the automatic boot-script, or whatever launched the IOC application used absolute paths in the command. For example you want to see something like:
not:/home/epics/R3.14.12.2/apps/testDev/iocBoot/iocFrib/st.cmd
./st.cmd
The solution of course is to make sure IOC's are always started with an absolute path in the command.
The most recent version of the infoServ support module can be downloaded from the NSCL/FRIB Control Software Group's website at:
https://groups.nscl.msu.edu/controls/
For questions, comments, suggestions for improvement or for anything else related to this module, please feel free to contact John Priller at:
priller@frib.msu.edu