2112 lines
78 KiB
HTML
2112 lines
78 KiB
HTML
<?xml version="1.0" encoding="iso-8859-1"?>
|
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
<head>
|
|
<meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
|
|
<title>pvDatabaseCPP</title>
|
|
<link rel="stylesheet" type="text/css"
|
|
href="http://epics-pvdata.sourceforge.net/base.css" />
|
|
<link rel="stylesheet" type="text/css"
|
|
href="http://epics-pvdata.sourceforge.net/epicsv4.css" />
|
|
<style type="text/css">
|
|
/*<![CDATA[*/
|
|
.about { margin-left: 3em; margin-right: 3em; font-size: .83em}
|
|
table { margin-left: auto; margin-right: auto }
|
|
.diagram { text-align: center; margin: 2.5em 0 }
|
|
span.opt { color: grey }
|
|
span.nterm { font-style:italic }
|
|
span.term { font-family:courier }
|
|
span.user { font-family:courier }
|
|
span.user:before { content:"<" }
|
|
span.user:after { content:">" }
|
|
.nonnorm { font-style:italic }
|
|
p.ed { color: #AA0000 }
|
|
span.ed { color: #AA0000 }
|
|
p.ed.priv { display: inline; }
|
|
span.ed.priv { display: inline; }
|
|
/*]]>*/</style>
|
|
<!-- Script that generates the Table of Contents -->
|
|
<script type="text/javascript"
|
|
src="http://epics-pvdata.sourceforge.net/script/tocgen.js">
|
|
</script>
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<div class="head">
|
|
<h1>pvDatabaseCPP</h1>
|
|
<!-- Maturity: Working Draft or Request for Comments, or Recommendation, and date. -->
|
|
|
|
<h2 class="nocount">EPICS v4 Working Group, Working Draft, 11-August-2014</h2>
|
|
<dl>
|
|
<dt>Latest version:</dt>
|
|
<dd><a
|
|
href="pvDatabaseCPP.html">pvDatabaseCPP.html</a>
|
|
</dd>
|
|
<dt>This version:</dt>
|
|
<dd><a
|
|
href= "pvDatabaseCPP_20140710.html">pvDatabaseCPP20140710.html
|
|
</a> </dd>
|
|
<dt>Previous version:</dt>
|
|
<dd><a
|
|
href= "pvDatabaseCPP_20140219.html">pvDatabaseCPP20140219.html
|
|
</a> </dd>
|
|
<dt>Editors:</dt>
|
|
<dd>Marty Kraimer, BNL</dd>
|
|
</dl>
|
|
|
|
<p class="copyright">This product is made available subject to acceptance of the <a
|
|
href="http://epics-pvdata.sourceforge.net/LICENSE.html">EPICS open source license.</a></p>
|
|
<hr />
|
|
|
|
<h2 class="nocount">Abstract</h2>
|
|
|
|
<p>This document describes pvDatabaseCPP,
|
|
which is a framework for implementing a network accessable database of smart memory resident
|
|
records. Network access is via pvAccess. The data in each record is a top level PVStructure as defined by
|
|
pvData. The framework includes a complete implementation of ChannelProvider as defined by pvAccess.
|
|
The framework can be extended in order to create record instances that implements services.
|
|
The minimum that an extenson must provide is a top level PVStructure and a process method.
|
|
</p>
|
|
|
|
<p>EPICS version 4 is a set of related products in the EPICS
|
|
V4 control system programming environment:<br />
|
|
<a href="http://epics-pvdata.sourceforge.net/relatedDocumentsV4.html">relatedDocumentsV4.html</a>
|
|
</p>
|
|
|
|
|
|
<h2 class="nocount">Status of this Document</h2>
|
|
|
|
<p>This is the 11-August-2014 version of of pvDatabaseCPP.</p>
|
|
</p>
|
|
<p>This version is a complete implementation of what is described in this manual.
|
|
</div>
|
|
|
|
<div id="toc">
|
|
<h2 class="nocount" style="page-break-before: always">Table of Contents</h2>
|
|
</div>
|
|
<div id="contents" class="contents">
|
|
|
|
|
|
<h2>Introduction</h2>
|
|
<h3>Overview</h3>
|
|
<p>The main purpose of this project to make it easier to implement services that are accessed via pvAccess.
|
|
This project supplies is a complete implementation of the server side of pvAccess.
|
|
All that a service has to provide is a top level PVStructure and a process method.
|
|
A service can be run as a main process or can be part of a V3 IOC.
|
|
Thus services can be developed that interact with V3 records, asynDriver,
|
|
areaDetector, etc.
|
|
</p>
|
|
|
|
<p>A brief description of a pvDatabase is that it is a set of network accessible, smart,
|
|
memory resident records.
|
|
Each record has data composed of a top level PVStructure.
|
|
Each record has a name which is the channelName for pvAccess.
|
|
A local Channel Provider implements the complete ChannelProvider and
|
|
Channel interfaces as defined by pvAccess.
|
|
The local provider provides access to the records in the pvDatabase.
|
|
This local provider is accessed by the remote pvAccess server.
|
|
A record is smart because code can be attached to a record, which is accessed via a method named process.</p>
|
|
|
|
<p>This document describes components that provide the following features:
|
|
<dl>
|
|
<dt>database<dt>
|
|
<dd>This encapsulates the concept of a database of memory resident smart records.
|
|
The two main components are:
|
|
<dl>
|
|
<dt>pvRecord</dt>
|
|
<dd>This encapsulates the concept of a smart record. It can be processed.
|
|
Changes to field values can be trapped. A record can be locked.</dd>
|
|
<dt>pvDatabase<dt>
|
|
<dd>This is a database of pvRecords.
|
|
Records can be added and removed from a database.</dd>
|
|
</dl>
|
|
<dt>pvAccess</dt>
|
|
<dd>This is a complete implementation of ChannelProvider and Channel
|
|
as defined by pvAccess.
|
|
It is used by the server side of pvAccess to attach to pvRecords.
|
|
This component also provides a C++ implementation
|
|
of the monitor and pvCopy components from pvIOCJava</dd>
|
|
<dt>Main and V3IOC</dt>
|
|
<dd>The pvDatabase can be provided via a Main program or can be part
|
|
of a V3IOC. In the later case the IOC has both a database of V3 Records
|
|
and a pvDatabase.</dd>
|
|
</dl>
|
|
<p>Base classes make it easy to create record instances.
|
|
The code attached to each record must create the top
|
|
level PVStructure and the following three methods:</p>
|
|
<dl>
|
|
<dt>init</dt>
|
|
<dd>This is a method for initializing the support.
|
|
It returns true if successful and false otherwise.
|
|
</dd>
|
|
<dt>process</dt>
|
|
<dd>This is what makes a record smart.
|
|
</dd>
|
|
<dt>destroy</dt>
|
|
<dd>This releases and resources used by the implementation.</dd>
|
|
</dl>
|
|
<p>Doxygen documentation is available at <a
|
|
href="./html/index.html">doxygenDoc</a></p>
|
|
<h3>Getting started</h3>
|
|
<p>The first step is to build pvDatabaseCPP as described in the next section.</p>
|
|
<p>One of the examples is exampleServer.
|
|
It can be started either via a main program or as part of a V3 IOC.
|
|
<p>
|
|
<p>To start it as a main program do the following:</p>
|
|
<pre>
|
|
mrk> pwd
|
|
/home/hg/pvDatabaseCPP/exampleServer
|
|
mrk> bin/linux-x86_64/exampleServerMain
|
|
</pre>
|
|
<p>You should see something like the following:</p>
|
|
<pre>
|
|
result of addRecord exampleServer 1
|
|
VERSION : pvAccess Server v3.0.5-SNAPSHOT
|
|
PROVIDER_NAMES : local
|
|
BEACON_ADDR_LIST :
|
|
AUTO_BEACON_ADDR_LIST : 1
|
|
BEACON_PERIOD : 15
|
|
BROADCAST_PORT : 5076
|
|
SERVER_PORT : 5075
|
|
RCV_BUFFER_SIZE : 16384
|
|
IGNORE_ADDR_LIST:
|
|
STATE : INITIALIZED
|
|
exampleServer
|
|
Type exit to stop:
|
|
</pre>
|
|
<p>Then in another window execute a pvput and pvget as follows:</p>
|
|
<pre>
|
|
mrk> pvput -r "field(argument.value)" exampleServer World
|
|
...
|
|
mrk> pvget -r "record[process=true]field(result.value)" exampleServer
|
|
exampleServer
|
|
structure
|
|
string value Hello World
|
|
mrk>
|
|
</pre>
|
|
<p>To run the example as part of a V3 IOC do the following:</p>
|
|
<pre>
|
|
mrk> pwd
|
|
/home/hg/pvDatabaseCPP/exampleServer/iocBoot/exampleServer
|
|
mrk> ../../bin/linux-x86_64/exampleServer st.cmd
|
|
</pre>
|
|
<p>You will see the following:</p>
|
|
<pre>
|
|
> envPaths
|
|
epicsEnvSet("ARCH","linux-x86_64")
|
|
epicsEnvSet("IOC","exampleServer")
|
|
epicsEnvSet("TOP","/home/hg/pvDatabaseCPP/exampleServer")
|
|
epicsEnvSet("EPICS_BASE","/home/install/epics/base")
|
|
epicsEnvSet("EPICSV4HOME","/home/hg")
|
|
cd /home/hg/pvDatabaseCPP/exampleServer
|
|
## Register all support components
|
|
dbLoadDatabase("dbd/exampleServer.dbd")
|
|
exampleServer_registerRecordDeviceDriver(pdbbase)
|
|
## Load record instances
|
|
dbLoadRecords("db/dbScalar.db","name=pvdouble,type=ao")
|
|
dbLoadRecords("db/dbArray.db","name=pvdoubleArray,type=DOUBLE")
|
|
dbLoadRecords("db/dbStringArray.db","name=pvstringArray")
|
|
dbLoadRecords("db/dbEnum.db","name=pvenum")
|
|
dbLoadRecords("db/dbCounter.db","name=pvcounter");
|
|
cd /home/hg/pvDatabaseCPP/exampleServer/iocBoot/exampleServer
|
|
iocInit()
|
|
Starting iocInit
|
|
############################################################################
|
|
## EPICS R3.14.12.3 $Date: Mon 2012-12-17 14:11:47 -0600$
|
|
## EPICS Base built Dec 21 2013
|
|
############################################################################
|
|
iocRun: All initialization complete
|
|
dbl
|
|
pvdouble
|
|
pvcounter
|
|
pvenum
|
|
pvdoubleArray
|
|
pvstringArray
|
|
epicsThreadSleep(1.0)
|
|
exampleServerCreateRecord pvaServer
|
|
startPVAServer
|
|
VERSION : pvAccess Server v3.0.5-SNAPSHOT
|
|
PROVIDER_NAMES : dbPv local
|
|
BEACON_ADDR_LIST :
|
|
AUTO_BEACON_ADDR_LIST : 1
|
|
BEACON_PERIOD : 15
|
|
BROADCAST_PORT : 5076
|
|
SERVER_PORT : 5075
|
|
RCV_BUFFER_SIZE : 16384
|
|
IGNORE_ADDR_LIST:
|
|
STATE : INITIALIZED
|
|
pvdbl
|
|
pvaServer
|
|
epics>
|
|
</pre>
|
|
<p>Just like previously you can then execute a pvput and pvget and see Hello World.
|
|
</p>
|
|
<p>The examples, i. e. exampleServer, exampleLink, examplePowerSupply,
|
|
and exampleDatabase, are described in separate sections below.
|
|
In addition arrayPerformance can be used to measure that performance of big
|
|
arrays. It is also described in a later section.</p>
|
|
<p>Reading section exampleServer and looking at it's code is a good way
|
|
to learn how to implement a service.</p>
|
|
|
|
<h3>Relationship with pvIOCJava.</h3>
|
|
<p>This document descibes a C++ implementation of some of the components in pvIOCJava,
|
|
which also implements a pvDatabase.
|
|
PVDatabaseCPP implements the core components required to create a network accessible database of smart
|
|
memory resident records.
|
|
pvDatabaseCPP does not implement any of the specialized support that pvIOCJava
|
|
provides.
|
|
It is expected that many services will be created that do not require the full features provided
|
|
by pvIOCJava. In the future pvIOCJava should be split into multiple projects with one of
|
|
them named pvDatabaseJava.</p>
|
|
<p>Similar to epics base, pvIOCJava implements the concept of synchronous and asynchronous record processing.
|
|
For pvDatabaseCPP the process method is allowed to block.
|
|
Until a need is demonstrated this will remain true.
|
|
The main user of a pvDatabase is pvAccess, and in particular, remote pvAccess.
|
|
The server side of remote pvAccess creates two threads for each client and always accesses
|
|
a record via these threads.
|
|
It is expected that these threads will be sufficient to efficently handle all channel methods except
|
|
channelRPC. For channelRPC pvAccess provides (or will provide) a thread pool for channelRPC requests.
|
|
If, in the future, a scanning facility is provided by pvDatabaseCPP or some other facility,
|
|
then the scanning facility will have to provide some way of handling process requests that block.</p>
|
|
</p>
|
|
<h3>Phased Development</h3>
|
|
<p>This documentation describes the first phase of a phased implementation of pvDatabaseCPP:</pp>
|
|
<dl>
|
|
<dt>pvRecord</d>
|
|
<dd>Wrapper on PVStructure that implements methods required by Local Channel Provider.</dd>
|
|
<dt>pvDatabase</d>
|
|
<dd>Database of PVRecords. Has methods find, add, and remove.</dd>
|
|
<dt>Local Channel Provider</dt>
|
|
<dd>Complete implementation of ChannelProvider and Channel.
|
|
This means that pvCopy and monitor are also implemented.</dd>
|
|
</dl>
|
|
<p>Future phases of pvDatabaseCPP might include:</p>
|
|
<dl>
|
|
<dt>Install</dt>
|
|
<dd>This provides complete support for on-line add and delete
|
|
of sets of records.
|
|
With the first phase each "service" is responsible for it's own implementation.
|
|
All that is provided is addRecord and removeRecord.
|
|
</dd>
|
|
<dt>Field support</dt>
|
|
<dd>Add the ability to optionally add support to fields.
|
|
In addition some of the basic support defined in pvIOCJava could also be implemented.</dd>
|
|
<dt>XML parser</dt>
|
|
<dd>This provides the ability to create record instances without writing any code.</dd>
|
|
</dl>
|
|
<p>The completion of each phase provides useful features that can be used without waiting for the
|
|
completion of later phases.
|
|
The rest of this document discusses only the first phase.</p>
|
|
<h3>Features Required for localChannelProvider</h3>
|
|
<dl>
|
|
<dt>copy and monitor</dt>
|
|
<dd>pvDataCPP provides facilities copy and monitor.
|
|
This facilities allow a client to access an arbitrary subset
|
|
of the fields in the top level structure associated with a channel,
|
|
and to monitor changes in the top level structure.
|
|
pvDatabaseCPP uses what pvDataCPP provides and has code that
|
|
associates these facilities with a PVRecord.
|
|
</dd>
|
|
<dt>PVRecord and PVDatabase</dt>
|
|
<dd>Defined below.</dd>
|
|
<dt>The localChannelProvider itself</dt>
|
|
<dd>This is the C++ implementation of package pvAccess in pvIOCJava.
|
|
The localChannelProvider accesses data from PVRecords.
|
|
It implements all channel methods except channelRPC, which is implemented by pvAccessCPP.</dd>
|
|
</dl>
|
|
<h3>Minumum Features Required for pvRecord</h3>
|
|
<p>The first phase will only implement record processing, i. e.
|
|
the process method has to do everything itself without any generic field support.
|
|
This will be sufficient for implementing many services.
|
|
The following are the minimium features required</p>
|
|
<dl>
|
|
<dt>PVDatabase</dt>
|
|
<dd>This holds a set of PVRecords. It has methods to find, add, and remove records.</dd>
|
|
<dt>PVRecord</dt>
|
|
<dd>This, and a set of related interfaces, provides the following:
|
|
<dl>
|
|
<dt>Access to top level PVStructure</dt>
|
|
<dd>PVRecord is a wrapper on a top level pvStructure.</dd>
|
|
<dt>Record locking</dt>
|
|
<dd>A record can be locked and unlocked.
|
|
A record must be locked whenever data in the pvStructure is accessed.</dd>
|
|
<dt>Trapping data changes</dt>
|
|
<dd>A client can request to be notified when data in the pvStructure is modified.
|
|
It can do this on a field by field basis.</dd>
|
|
</dl>
|
|
</dd>
|
|
</dl>
|
|
<p>The following sections describes the classes required for the first phase.</p>
|
|
|
|
<h2>Building pvDatabaseCPP</h2>
|
|
<p>To build pvDatabaseCPP You must provide a file RELEASE.local
|
|
in directory configure.
|
|
Thus do the following:</p>
|
|
<pre>
|
|
mrk> pwd
|
|
/home/hg/pvDatabaseCPP/configure
|
|
mrk> cp ExampleRELEASE.local RELEASE.local
|
|
</pre>
|
|
<p>Then edit <b>RELEASE.local</b> so that it has the correct location of each
|
|
product pvDatabaseCPP requires.
|
|
Than at the top level just execute <b>make</b>:</p>
|
|
<pre>
|
|
mrk> cd ..
|
|
mrk> pwd
|
|
/home/hg/pvDatabaseCPP
|
|
mrk> make
|
|
</pre>
|
|
<p>This builds pvDatabaseCPP and also the tests and all examples.</p>
|
|
<p>Each example and arrayPerformance is a completely separate top,
|
|
but is also built when make is run in pvDatabaseCPP itself.</p>
|
|
<p>
|
|
Each is a separate top for the following reasons:</p>
|
|
<ol>
|
|
<li>
|
|
It is easier to understand each example including how it is built so that
|
|
it can be run as a main or as part of a V3 IOC.
|
|
</li>
|
|
<li>
|
|
Each example can be copied somewhere else and used as the basis
|
|
for creating a new service.
|
|
</li>
|
|
</ol>
|
|
<p>
|
|
If it is desired to build an example all by itself,
|
|
just follow the same instructions as for building pvDatabaseCPP itself.
|
|
For example:</p>
|
|
<pre>
|
|
mrk> pwd
|
|
/home/hg/pvDatabaseCPP/exampleServer/configure
|
|
mrk> cp ExampleRELEASE.local RELEASE.local
|
|
</pre>
|
|
<p>Then edit <b>RELEASE.local</b> so that it has the correct location of each
|
|
product the example requires.
|
|
Than at the top level of the example just execute <b>make</b>:</p>
|
|
<pre>
|
|
mrk> cd ..
|
|
mrk> pwd
|
|
/home/hg/pvDatabaseCPP/exampleServer
|
|
mrk> make
|
|
</pre>
|
|
<p>This builds the example.</p>
|
|
|
|
<h2>iocshell commands</h2>
|
|
<p>Shell commands are made available via the standard DBD include mechanism
|
|
provided by iocCore.
|
|
The following provide EPICS V4 shell commands:</p>
|
|
<dl>
|
|
<dt>pvAccessCPP</dt>
|
|
<dd>PVAClientRegister.dbd and PVAServerRegister.dbd</dd>
|
|
<dt>pvaSrv</dt>
|
|
<dd>dbPv.dbd</dd>
|
|
<dt>pvDatabaseCPP</dt>
|
|
<dd>registerChannelProviderLocal.dbd</dd>
|
|
</dl>
|
|
<p>
|
|
Look at exampleServer/ioc/src/exampleServerInclude.dbd for an example
|
|
of how an application can make the shell commands available.
|
|
</p>
|
|
<h3>Commands From pvAccessCPP</h3>
|
|
<p>The following iocsh commands are provided for a V3IOC:</p>
|
|
<dl>
|
|
<dt>startPVAClient</dt>
|
|
<dd>Starts the client side of pvAccess.s
|
|
It makes channel provider <b>pvAccess</b> available.
|
|
After startPVAServer is called the channel provider <b>local</b> will
|
|
also be available.
|
|
</dd>
|
|
<dt>stopPVAClient</dt>
|
|
<dd>Stops pvAccess.</dd>
|
|
<dt>startPVAServer</dt>
|
|
<dd>Starts the local channel provider</p>
|
|
<dt>stopPVAServer</dt>
|
|
<dd>Stop the local channel provider</dd>
|
|
</dl>
|
|
</p>
|
|
<h3>Commands implemented by pvDatabaseCPP</h3>
|
|
<p>The following iocsh commands are provided for a V3IOC:</p>
|
|
<dl>
|
|
<dt>pvdbl</dt>
|
|
<dd>Provides a list of all the pvRecords in database <b>master</b>
|
|
</dd>
|
|
</dl>
|
|
<p>In addition any code that implements a PVRecord must implement an ioc command.</p>
|
|
<p>Look at any of the examples to see how to implement shell commands.</p>
|
|
<h3>Commands implemented by pvaSrv</h3>
|
|
<p><b>pvaSrv</b> provides a pvAccess server that provides access to iocCore records.</p>
|
|
<p>pvaSrv does not provide any shell commands but it can be part of an IOC.
|
|
Just make sure your application configures pvaSrv and then include the following file:
|
|
<pre>
|
|
include "dbPv.dbd"
|
|
</pre>
|
|
</p>
|
|
|
|
<h2>database</h2>
|
|
<h3>src/database</h3>
|
|
<p>This Directory has the following files:</p>
|
|
<dl>
|
|
<dt>pvDatabase.h</dt>
|
|
<dd>
|
|
This is what is described in this section.
|
|
</dd>
|
|
<dt>pvDatabase.cpp</dt>
|
|
<dd>
|
|
The implementation of PVDatabase.
|
|
</dd>
|
|
<dt>pvRecord.cpp</dt>
|
|
<dd>
|
|
The implementation of the base class for PVRecord.
|
|
It can also implement record instances with a process
|
|
method does nothing.
|
|
This can be used to create a "dumb" record where all changes are
|
|
done by clients.
|
|
</dd>
|
|
</dl>
|
|
<h3>src/special</h3>
|
|
<p>This directory has the following files:</p>
|
|
<dl>
|
|
<dt>recordList.h</dt>
|
|
<dd>This implements a PVRecord that provides a list of the names
|
|
of the records in the PVDatabase.
|
|
It also serves as an example of how to implement a service.
|
|
The exampleDatabase creates an instance via the following code:
|
|
<pre>
|
|
recordName = "laptoprecordListPGRPC";
|
|
pvRecord = RecordListRecord::create(recordName);
|
|
if(pvRecord==NULL) {
|
|
cout << "RecordListRecord::create failed" << endl;
|
|
} else {
|
|
result = master->addRecord(pvRecord);
|
|
if(!result) cout<< "record " << recordName << " not added" << endl;
|
|
}
|
|
</pre>
|
|
</dd>
|
|
<dt>traceRecord.h</dt>
|
|
<dd>This implements a PVRecord that can set the trace level for
|
|
another record. See below for a discussion of trace level.</dd>
|
|
|
|
</dl>
|
|
<h3>pvDatabase.h</h3>
|
|
<p>The classes in pvDatabase.h describe a database of memory resident
|
|
smart records.
|
|
It describes the following classes:</p>
|
|
<dl>
|
|
<dt>PVRecord</dt>
|
|
<dd>This provides the methods required by localChannelProvider to implement Channel.</dd>
|
|
<dt>PVRecordField</dt>
|
|
<dt>PVRecordStructure</dt>
|
|
<dd>These wrap PVField and PVStructure so that pvCopy and monitor
|
|
can be implemented.</dd>
|
|
<dt>PVRecordClient</dt>
|
|
<dd>This is called by anything that acceses PVRecord.</dd>
|
|
<dt>PVListener</dt>
|
|
<dd>This is implemented by anything that wants to trap calls to PVRecord::message.</dd>
|
|
<dt>PVDatabase</dt>
|
|
<dd>This is a database of PVRecords.</dd>
|
|
</dl>
|
|
<p>Each class is described in a separate subsection.</p>
|
|
<h3>C++ namespace and typedefs</h3>
|
|
<pre>
|
|
namespace epics { namespace pvDatabase {
|
|
|
|
class PVRecord;
|
|
typedef std::tr1::shared_ptr<PVRecord> PVRecordPtr;
|
|
typedef std::map<epics::pvData::String,PVRecordPtr> PVRecordMap;
|
|
|
|
class PVRecordField;
|
|
typedef std::tr1::shared_ptr<PVRecordField> PVRecordFieldPtr;
|
|
typedef std::vector<PVRecordFieldPtr> PVRecordFieldPtrArray;
|
|
typedef std::tr1::shared_ptr<PVRecordFieldPtrArray> PVRecordFieldPtrArrayPtr;
|
|
|
|
class PVRecordStructure;
|
|
typedef std::tr1::shared_ptr<PVRecordStructure> PVRecordStructurePtr;
|
|
|
|
class PVRecordClient;
|
|
typedef std::tr1::shared_ptr<PVRecordClient> PVRecordClientPtr;
|
|
|
|
class PVListener;
|
|
typedef std::tr1::shared_ptr<PVListener> PVListenerPtr;
|
|
|
|
class RecordPutRequester;
|
|
typedef std::tr1::shared_ptr<RecordPutRequester> RecordPutRequesterPtr;
|
|
|
|
class PVDatabase;
|
|
typedef std::tr1::shared_ptr<PVDatabase> PVDatabasePtr;
|
|
</pre>
|
|
|
|
<h3>class PVRecord</h3>
|
|
<p>NOTES:
|
|
<ul>
|
|
<li>This section uses the name record instead of "an instance of PVRecord".</li>
|
|
<li>Most clients will access a record via the local channel provider,
|
|
i. e. via pvAccess.
|
|
Thus this section is mainly of interest to
|
|
the local channel provider and record implementers.</li>
|
|
<li>Most readers will not care about most of the PVRecord methods.
|
|
Most of the methods are used by the pvAccess code.
|
|
Service implementers will mostly be interested in methods init and process.
|
|
These are described first.
|
|
</ul>
|
|
<hr>PVRecord Methods</h4>
|
|
<pre>
|
|
class PVRecord
|
|
public epics::pvData::Requester,
|
|
public std::tr1::enable_shared_from_this<PVRecord>
|
|
{
|
|
public:
|
|
POINTER_DEFINITIONS(PVRecord);
|
|
|
|
virtual bool init() {initPVRecord(); return true;}
|
|
virtual void start() {}
|
|
virtual void process() {}
|
|
virtual void destroy();
|
|
|
|
static PVRecordPtr create(
|
|
std::string const & recordName,
|
|
epics::pvData::PVStructurePtr const & pvStructure);
|
|
virtual ~PVRecord();
|
|
std::string getRecordName();
|
|
PVRecordStructurePtr getPVRecordStructure();
|
|
PVRecordFieldPtr findPVRecordField(
|
|
epics::pvData::PVFieldPtr const & pvField);
|
|
void lock();
|
|
void unlock();
|
|
bool tryLock();
|
|
void lockOtherRecord(PVRecordPtr const & otherRecord);
|
|
bool addPVRecordClient(PVRecordClientPtr const & pvRecordClient);
|
|
bool removePVRecordClient(PVRecordClientPtr const & pvRecordClient);
|
|
void detachClients();
|
|
bool addListener(PVListenerPtr const & pvListener);
|
|
bool removeListener(PVListenerPtr const & pvListener);
|
|
void beginGroupPut();
|
|
void endGroupPut();
|
|
int getTraceLevel();
|
|
void setTraceLevel(int level);
|
|
protected:
|
|
PVRecord(
|
|
std::string const & recordName,
|
|
epics::pvData::PVStructurePtr const & pvStructure);
|
|
void initPVRecord();
|
|
epics::pvData::PVStructurePtr getPVStructure();
|
|
PVRecordPtr getPtrSelf()
|
|
{
|
|
return shared_from_this();
|
|
}
|
|
private:
|
|
...
|
|
}
|
|
</pre>
|
|
<p>The methods are:</p>
|
|
<dl>
|
|
<dt>init</dt>
|
|
<dd>Virtual method.
|
|
Derived classes must implement this method.
|
|
This method Must call initPVRecord.
|
|
</dd>
|
|
<dt>start</dt>
|
|
<dd>Virtual method.
|
|
Optional method for derived class.
|
|
It is called before record is added to database.
|
|
The base method does nothing.
|
|
</dd>
|
|
<dt>process</dt>
|
|
<dd>Virtual method.
|
|
Derived classes usually implement this method.
|
|
It implements the semantics for the record.
|
|
The base implementation does nothing.
|
|
</dd>
|
|
<dt>destroy</dt>
|
|
<dd>Virtual method.
|
|
Optional method for derived class.
|
|
If the derived class implements this it
|
|
must call the base class destroy method after it
|
|
has released any resources it uses.</dd>
|
|
<dt>create</dt>
|
|
<dd>Static method to create dumb records,
|
|
i.e. records with a process method that does nothing.
|
|
A derived class should have it's own static create method.
|
|
</dd>
|
|
<dt>~PVRecord</dt>
|
|
<dd>The destructor which must be virtual. A derived class must also have
|
|
a virtual destructor.</dd>
|
|
<dt>getRecordName</dt>
|
|
<dd>Return the recordName.</dd>
|
|
<dt>getPVRecordStructure</dt>
|
|
<dd>Get the top level PVStructure.</dd>
|
|
<dt>findPVRecordField</dt>
|
|
<dd>Given a PVFieldPtr return the PVRecordFieldPtr for the field.</dd>
|
|
<dt>lock</dt>
|
|
<dt>unlock</dt>
|
|
<dd>Lock and Unlock the record.
|
|
Any code accessing the data in the record or calling other PVRecord methods
|
|
must have the record locked.</dd>
|
|
<dt>tryLock</dt>
|
|
<dd>If true then just like lock.
|
|
If falseclient can not access record.
|
|
A client can try to simultaneously hold the lock for more than two records
|
|
by calling this method. But must be willing to accept failure.
|
|
</dd>
|
|
<dt>lockOtherRecord</dt>
|
|
<dd>A client that holds the lock for one record can lock one other record.
|
|
A client must not call this if the client already has the lock for
|
|
more then one record.
|
|
</dd>
|
|
<dt>addPVRecordClient</dt>
|
|
<dd>Every client that accesses the record must call this so that the client can be notified when the record is deleted.</dd>
|
|
<dt>removePVRecordClient</dt>
|
|
<dd>Client is no longer accessing the record.</dd>
|
|
<dt>detachClients</dt>
|
|
<dd>Ask all clients to detach from the record</dd>
|
|
<dt>addListener</dt>
|
|
<dd>Add a PVListener. This must be called before calling pvRecordField.addListener.</dd>
|
|
<dt>removeListener</dt>
|
|
<dd>Removes a listener. The listener will also be removed from all fields to which it is attached.</dd>
|
|
<dt>beginGroupPut</dt>
|
|
<dd>Begin a group of puts.
|
|
This results in all registered PVListeners being called</dd>
|
|
<dt>endGroupPut</dt>
|
|
<dd>End a group of puts.
|
|
This results in all registered PVListeners being called.</dd>
|
|
<dt>getTraceLevel</dt>
|
|
<dd>This can be used for debugging. There are currently three
|
|
levels that are used by existing code.
|
|
<dl>
|
|
<dt>0</dt>
|
|
<dd>Produce no trace messages.</dd>
|
|
<dt>1</dt>
|
|
<dd>Issue a message to std::cout whenever anything is created
|
|
or destroyed.</dd>
|
|
<dt>2</dt>
|
|
<dd>In addition to lifetime messages also issue a message
|
|
whenever the record is accessed by pvAccess client.</dd>
|
|
</dl>
|
|
</dd>
|
|
<dt>setTraceLevel</dt>
|
|
<dd>Set the trace level. Note that special, described below.
|
|
provides a record support that allows a pvAccess client
|
|
to set the trace level of a record.</dd>
|
|
</dl>
|
|
<p>The protected methods are:</p>
|
|
<dl>
|
|
<dt>PVRecord</dt>
|
|
<dd>The constructor. It requires a recordName and a top level PVStructure.</dd>
|
|
<dt>initPVRecord</dt>
|
|
<dd>This method must be called by derived class.</dd>
|
|
<dt>getPVStructure</dt>
|
|
<dd>Called by derived class.</dd>
|
|
</dl>
|
|
<h3>class PVRecordField</h3>
|
|
<pre>
|
|
class PVRecordField {
|
|
public virtual epics::pvData::PostHandler,
|
|
public std::tr1::enable_shared_from_this<PVRecordField>
|
|
public:
|
|
POINTER_DEFINITIONS(PVRecordField);
|
|
PVRecordField(
|
|
epics::pvData::PVFieldPtr const & pvField,
|
|
PVRecordStructurePtr const &parent,
|
|
PVRecordPtr const & pvRecord);
|
|
virtual ~PVRecordField();
|
|
virtual void destroy();
|
|
PVRecordStructurePtr getParent();
|
|
epics::pvData::PVFieldPtr getPVField();
|
|
std::string getFullFieldName();
|
|
std::string getFullName();
|
|
PVRecordPtr getPVRecord();
|
|
bool addListener(PVListenerPtr const & pvListener);
|
|
virtual void removeListener(PVListenerPtr const & pvListener);
|
|
virtual void postPut();
|
|
protected:
|
|
PVRecordFieldPtr getPtrSelf()
|
|
{
|
|
return shared_from_this();
|
|
}
|
|
virtual void init();
|
|
virtual void postParent(PVRecordFieldPtr const & subField);
|
|
virtual void postSubField();
|
|
private:
|
|
...
|
|
};
|
|
</pre>
|
|
<p>When PVRecord is created it creates a PVRecordField for every field in the PVStructure
|
|
that holds the data. It has the following methods:
|
|
</p>
|
|
|
|
<dl>
|
|
<dt>PVRecordField</dt>
|
|
<dd>The constructor.</dd>
|
|
<dt>~PVRecordField</dt>
|
|
<dd>The destructor.</dd>
|
|
<dt>destroy</dt>
|
|
<dd>Called by PVRecordStructure when it's destroy method is called.</dd>
|
|
<dt>getParent</dt>
|
|
<dd>Get the parent PVRecordStructure for this field.</dd>
|
|
<dt>getPVField</dt>
|
|
<dd>Get the PVField associated with this PVRecordField.</dd>
|
|
<dt>getFullFieldName</dt>
|
|
<dd>This gets the full name of the field, i.e. field,field,..</dd>
|
|
<dt>getFullName</dt>
|
|
<dd>This gets recordName plus the full name of the field, i.e. recordName.field,field,..</dd>
|
|
<dt>getPVRecord</dt>
|
|
<dd>Returns the PVRecord to which this field belongs.</dd>
|
|
<dt>addListener</dt>
|
|
<dd>Add A PVListener to this field.
|
|
Whenever this field or any subfield if this field is modified the listener will be notified.
|
|
PVListener is described below.
|
|
Before a listener can call addListener it must first call PVRecord.registerListener.</dd>
|
|
<dt>removeListener</dt>
|
|
<dd>Remove a PVListener.</dd>
|
|
<dt>postPut</dt>
|
|
<dd>This is called by the code that implements the data interface.
|
|
It is called whenever the put method is called.</dd>
|
|
</dl>
|
|
<h3>class PVRecordStructure</h3>
|
|
<pre>
|
|
class PVRecordStructure : public PVRecordField {
|
|
public:
|
|
POINTER_DEFINITIONS(PVRecordStructure);
|
|
PVRecordStructure(
|
|
epics::pvData::PVStructurePtr const & pvStructure,
|
|
PVRecordStructurePtr const & parent,
|
|
PVRecordPtr const & pvRecord);
|
|
virtual ~PVRecordStructure();
|
|
virtual void destroy();
|
|
PVRecordFieldPtrArrayPtr getPVRecordFields();
|
|
epics::pvData::PVStructurePtr getPVStructure();
|
|
virtual void removeListener(PVListenerPtr const & pvListener);
|
|
virtual void postPut();
|
|
protected:
|
|
virtual void init();
|
|
private:
|
|
...
|
|
};
|
|
</pre>
|
|
<p>When PVRecord is created it creates a PVRecordStructure for every structure field in the PVStructure
|
|
that holds the data. It has the following methods:
|
|
</p>
|
|
<dl>
|
|
<dt>PVRecordStructure</dt>
|
|
<dd>The constructor.</dd>
|
|
<dt>~PVRecordStructure</dt>
|
|
<dd>The destructor.</dd>
|
|
<dt>getPVRecordFields</dt>
|
|
<dd>Get the PVRecordField array for the subfields</dd>
|
|
<dt>getPVStructure</dt>
|
|
<dd>Get the PVStructure for this field.</dd>
|
|
<dt>removeListener</dt>
|
|
<dd>Remove a PVListener.</dd>
|
|
<dt>postPut</dt>
|
|
<dd>This is called by the code that implements the data interface.
|
|
It is called whenever the put method is called.</dd>
|
|
<h3>class PVRecordClient</h3>
|
|
<pre>
|
|
class PVRecordClient {
|
|
POINTER_DEFINITIONS(PVRecordClient);
|
|
virtual ~PVRecordClient();
|
|
virtual void detach(PVRecordPtr const & pvRecord);
|
|
};
|
|
</pre>
|
|
<p>where</p>
|
|
<dl>
|
|
<dt>~PVRecordClient</dt>
|
|
<dd>The destructor.</dd>
|
|
<dt>detach</dt>
|
|
<dd>The record is being removed from the master database,</dd>
|
|
</dl>
|
|
</dl>
|
|
<h3>class PVListener</h3>
|
|
<pre>
|
|
class PVListener {
|
|
virtual public PVRecordClient
|
|
public:
|
|
POINTER_DEFINITIONS(PVListener);
|
|
virtual ~PVListener();
|
|
virtual void dataPut(PVRecordFieldPtr const & pvRecordField) = 0;
|
|
virtual void dataPut(
|
|
PVRecordStructurePtr const &
|
|
requested,PVRecordFieldPtr const & pvRecordField) = 0;
|
|
virtual void beginGroupPut(PVRecordPtr const & pvRecord) = 0;
|
|
virtual void endGroupPut(PVRecordPtr const & pvRecord) = 0;
|
|
virtual void unlisten(PVRecordPtr const & pvRecord);
|
|
};
|
|
</pre>
|
|
<p>where</p>
|
|
<dl>
|
|
<dt>~PVListener</dt>
|
|
<dd>The destructor.</dd>
|
|
<dt>dataPut(PVRecordFieldPtr const & pvRecordField)</dt>
|
|
<dd>pvField has been modified.
|
|
This is called if the listener has called PVRecordField::addListener for pvRecordField.</dd>
|
|
<dt>dataPut(
|
|
PVRecordStructurePtr const &
|
|
requested,PVRecordFieldPtr const & pvRecordField)</dt>
|
|
<dd>pvField has been modified.
|
|
Requested is the field to which the requester issued a pvField-&addListener.
|
|
This is called if the listener has called PVRecordField-&addListener for requested.</dd>
|
|
<dt>beginGroupPut</dt>
|
|
<dd>A related set of changes is being started.</dd>
|
|
<dt>endGroupPut</dt>
|
|
<dd>A related set of changes is done.</dd>
|
|
<dt>unlisten</dt>
|
|
<dd>The record is being destroyed. The listener must release all
|
|
access to the record.</dd>
|
|
</dl>
|
|
<h3>class PVDatabase</h3>
|
|
<pre>
|
|
class PVDatabase : virtual public epics::pvData::Requester {
|
|
public:
|
|
POINTER_DEFINITIONS(PVDatabase);
|
|
static PVDatabasePtr getMaster();
|
|
virtual ~PVDatabase();
|
|
virtual void destroy();
|
|
PVRecordPtr findRecord(std::string const& recordName);
|
|
bool addRecord(PVRecordPtr const & record);
|
|
epics::pvData::PVStringArrayPtr getRecordNames();
|
|
bool removeRecord(PVRecordPtr const & record);
|
|
virtual std::string getRequesterName();
|
|
private:
|
|
PVDatabase();
|
|
};
|
|
</pre>
|
|
<p>where</p>
|
|
<dl>
|
|
<dt>getMaster</dt>
|
|
<dd>Get the master database. This is the database that localChannelProvider access.</dd>
|
|
<dt>~PVDatabase</dt>
|
|
<dd>The destructor.</dd>
|
|
<dt>destroy</dt>
|
|
<dd>This is called by remote channelAccess when process exits.
|
|
This destroys and removes all records in the PVDatabase.</dd>
|
|
<dt>findRecord</dt>
|
|
<dd>Find a record. An empty pointer is returned if the record is not in the database.</dd>
|
|
<dt>addRecord</dt>
|
|
<dd>Add a record to the database.
|
|
If the record already exists it is not modified and false is returned.</dd>
|
|
<dt>getRecordNames</dt>
|
|
<dd>Returns an array of all the record names.</dd>
|
|
<dt>removeRecord</dt>
|
|
<dd>Remove a record from the database.
|
|
If the record was not in the database false is returned.</dd>
|
|
<dt>getRequesterName</dt>
|
|
<dd>Virtual method of Requester</dd>
|
|
</dl>
|
|
<h2>pvAccess</h2>
|
|
<p>This is code that provides an implementation of channelProvider as
|
|
defined by pvAccess.
|
|
It provides access to PVRecords and is accessed by the server side of remote pvAccess.
|
|
It uses the copy and monitor facilities from pvDataCPP and connects
|
|
them to a PVRecord.
|
|
</p>
|
|
<p>The implementation is a complete implementation of channelProvider
|
|
and channel except for channelRPC, which is implement by pvAccess as a separate
|
|
channel provider.</p>
|
|
<p>The following provides a brief description of each channel method that
|
|
is implemented.</p>
|
|
<h3>channelProcessLocal</h3>
|
|
<p>Needs to be described.</p>
|
|
<h3>channelGetLocal</h3>
|
|
<p>Needs to be described.</p>
|
|
<h3>channelPutLocal</h3>
|
|
<p>Needs to be described.</p>
|
|
<h3>channelPutGetLocal</h3>
|
|
<p>Needs to be described.</p>
|
|
<h3>channelArrayLocal</h3>
|
|
<p>Needs to be described.</p>
|
|
<h3>MonitorLocal</h3>
|
|
<p>This is the code that implements monitors on changes to fields of a PVRecord.
|
|
Because it is called by pvAccess client (monitor methods) and by
|
|
PVRecord (when postPut is called), it must be careful to prevent deadlocks.
|
|
The implementation is via class MonitorLocal (implmented in monitorFactory.cpp)
|
|
and PVCopyMonitor.
|
|
MonitorLocal is the interface between pvAccess and PVCopyMonitor.
|
|
PVCopyMonitor is the interface between MonitorLocal and PVRecord.
|
|
MonitorLocal manages a MonitorElement queue.
|
|
While monitoring is active (between start and stop) it keeps an active element
|
|
for use by PVCopyMonitor.
|
|
While monitoring is active PVCopyMonitor updates the active monitor element whenever
|
|
a postPut is issued to any field being monitored.
|
|
</p>
|
|
<p>The following two sections provide a few more details about MonitorLocal
|
|
and PVCopyMonitor.<p>
|
|
<h4>MonitorLocal</h4>
|
|
<p>MonitorLocal implements the following abstract base classes:</p>
|
|
<dl>
|
|
<dt>Monitor</dt>
|
|
<dd>This is described by pvDataCPP.
|
|
It has methods start, stop, poll, and release.
|
|
These methods are called by the pvAccess client
|
|
</dd>
|
|
<dt>PVCopyMonitorRequester</dt>
|
|
<dd>This has methods releaseActiveElement and unlisten.
|
|
These methods are called by PVCopyMonitor.
|
|
</dd>
|
|
</dl>
|
|
MonitorLocal manages the following:
|
|
<dl>
|
|
<dt>MonitorElementQueue</dt>
|
|
<dd>This is a queue of monitor elements.
|
|
A Queue is implemented by pvDataCPP and used by MonitorLocal.
|
|
It is a finite queue.
|
|
A monitor element is described by pvDataCPP.
|
|
It has fields pvStructure, changedBitSet, and overrunBitSet.
|
|
The pvStructure holds data for a subset of the fields in a PVRecord.
|
|
The changedBitSet and overrunBitSet describe changes between
|
|
monitor event.
|
|
MonitorLocal creates an instance of PVCopy (implemented by pvDataCPP),
|
|
which manages the interaction between the set of fields being
|
|
monitored and the fields in the top level PVStructure of the PVRecord.
|
|
pvCopy is also used to create the pvStructure for each monitor element.
|
|
</dd>
|
|
<dt>activeElement</dt>
|
|
<dd>Whenever monitoring is active monitorLocal
|
|
keeps an active element for use by pvCopyMonitor.
|
|
It changes the active element based on calls to poll (by the
|
|
client) and calls to releaseActiveElement (by pvCopyMonitor).
|
|
If there are no free element when releaseActiveElement is
|
|
called the current active element is returned.
|
|
If a free element is available the client is notified that a new
|
|
monitor element is available and the free element becomes the
|
|
active element.
|
|
</dd>
|
|
</dl>
|
|
<p>A brief description on each method in MonitorLocal is:</p>
|
|
<dl>
|
|
<dt>start</dt>
|
|
<dd>
|
|
Called by client.
|
|
With a lock held it clears the monitorElement queue
|
|
and allocates an active element.
|
|
With no lock held calls pvCopyMonitor->startMonitoring(activeElement)
|
|
<dd>
|
|
<dt>stop</dt>
|
|
<dd>
|
|
Called by client.
|
|
With no lock held calls pvCopyMonitor->stopMonitoring(activeElement)
|
|
</dd>
|
|
<dt>poll</dt>
|
|
<dd>
|
|
Called by client.
|
|
With a lock held it calls queue->getUsed();
|
|
</dd>
|
|
<dt>release</dt>
|
|
<dd>
|
|
Called by client.
|
|
With a lock held it calls queue->releaseUsed();
|
|
</dd>
|
|
<dt>releaseActiveElement</dt>
|
|
<dd>
|
|
Called by PVCopyMonitor with no locks held.
|
|
With a lock held it tries to get a new free element.
|
|
If it can't it just returns the current active element.
|
|
Otherwise it does the following.
|
|
Using the activeElement it updates the pvStructure
|
|
and compresses the changed and overrun bitSet.
|
|
It then calls queue->setUsed(activeElement);
|
|
It then sets the active element to the new free element.
|
|
With no lock held it calls monitorRequester->monitorEvent(getPtrSelf())
|
|
and finally returns the new active element,
|
|
</dd>
|
|
<dt>unlisten</dt>
|
|
<dd>
|
|
With no lock held it calls monitorRequester->unlisten(getPtrSelf());
|
|
</dd>
|
|
</dl>
|
|
<h4>PVCopyMonitor</h4>
|
|
<p>
|
|
pvCopyMonitor is the code that manages changes to
|
|
fields in the record.
|
|
It is called by PVRecord whenever a postPut is issued to a field.
|
|
pvCopyMonitor uses the active monitor element provided by monitorFactory.
|
|
Note that this method is called with the record locked.
|
|
It only modifies the changedBitSet and overrunBitSet of the
|
|
active element but never modifies the pvStructure.
|
|
</p>
|
|
<p>A brief description of the pvCopyMonitor methods is:</p>
|
|
<dl>
|
|
<dt>startMonitoring</dt>
|
|
<dd>With no lock held it sets its monitorElement to the
|
|
startElement pased by monitorLocal and calls pvRecord->addListener(getPtrSelf()).
|
|
It locks the pvRecord.
|
|
It calls calls addListener for every field in the record that is being
|
|
monitored.
|
|
It clears the overrun and changed bit sets.
|
|
It sets bit 0 of the changed bit set and calls
|
|
pvCopyMonitorRequester->releaseActiveElement();
|
|
Thus the client will get the initial values for every field being monitored.
|
|
The record is unlocked and the method returns to the caller.
|
|
</dd>
|
|
<dt>stopMonitoring</dt>
|
|
<dd>
|
|
With no lock held it calls pvRecord->removeListener(getPtrSelf());
|
|
</dd>
|
|
<dt>dataPut</dt>
|
|
<dd>
|
|
This is called because of a call to postPut.
|
|
It is called with the record locked.
|
|
It updates the changed and overrun bitSets.
|
|
It isGroupPut is false it calls
|
|
pvCopyMonitorRequester->releaseActiveElement().
|
|
Otherwise it sets dataChanged true.
|
|
</dd>
|
|
<dt>beginGroupPut</dt>
|
|
<dd>
|
|
With a lock held it
|
|
sets isGroupPut true and dataChanged false.
|
|
</dd>
|
|
<dt>endGroupPut</dt>
|
|
<dd>
|
|
With a lock held it sets isGroupPut false.
|
|
With no lock held and dataChanged true it calls
|
|
pvCopyMonitorRequester->releaseActiveElement()
|
|
</dd>
|
|
<dt>unlisten</dt>
|
|
<dd>
|
|
Just calls pvCopyMonitorRequester->unlisten();
|
|
</dd>
|
|
</dl>
|
|
|
|
<h2>special</h2>
|
|
<p>This section provides two useful record support modules
|
|
and one that is used for testing.</p>
|
|
<h3>traceRecord</h3>
|
|
<p>This implements a PVRecord that allows a client to set
|
|
the trace level of a record. It follows the pattern of a channelPutGet
|
|
record:
|
|
<pre>
|
|
traceRecord
|
|
structure argument
|
|
string recordName
|
|
int level 0
|
|
structure result
|
|
string status
|
|
</pre>
|
|
where:
|
|
<dl>
|
|
<dt>recordName</dt>
|
|
<dd>The name of the record to set the trace level.</dd>
|
|
<dt>level</dt>
|
|
<dd>The level to set. The meaning is:
|
|
<dl>
|
|
<dt>0</dt>
|
|
<dd>No trace messages generated</dd>
|
|
<dt>1</dt>
|
|
<dd>Lifecycle messages will be generated.
|
|
This all channel create and destroy instances will be shown.</dd>
|
|
<dt>2</dt>
|
|
<dd>In addition to lifecycle messages a message will be generted
|
|
for each get and put request.</dd>
|
|
<dt>>2</dt>
|
|
<dd>Currently no definition</dd>
|
|
</dl>
|
|
</dd>
|
|
<dt>result</dt>
|
|
<dd>The result of a cannelPutGet request</dd>
|
|
</dl>
|
|
<p>testExampleServerMain.cpp has an example of how to create a traceRecord:
|
|
<pre>
|
|
PVDatabasePtr master = PVDatabase::getMaster();
|
|
PVRecordPtr pvRecord;
|
|
String recordName;
|
|
bool result(false);
|
|
recordName = "traceRecordPGRPC";
|
|
pvRecord = TraceRecord::create(recordName);
|
|
result = master->addRecord(pvRecord);
|
|
if(!result) cout<< "record " << recordName << " not added" << endl;
|
|
</pre>
|
|
</p>
|
|
<h3>recordList</h3>
|
|
<p>This implements a PVRecord that allows a client to
|
|
get the names of all the PVRecords in the PVDatabase.
|
|
It follows the pattern of a channelPutGet
|
|
record:
|
|
<pre>
|
|
traceRecord
|
|
structure argument
|
|
string database master
|
|
string regularExpression .*
|
|
structure result
|
|
string status
|
|
string[] name
|
|
</pre>
|
|
where:
|
|
<dl>
|
|
<dt>database</dt>
|
|
<dd>The name of the datbase. The default is "master"</dd>
|
|
<dt>regularExpression</dt>
|
|
<dd>For now this is ignored and the complete list of names is always
|
|
returned.</dd>
|
|
<dt>status</dt>
|
|
<dd>The status of a putGet request.</dd>
|
|
<dt>name</dt>
|
|
<dd>The array of record names.</dd>
|
|
</dl>
|
|
<p>Note that swtshell, which is a Java GUI tool, has a command <b>channelList</b> that
|
|
requires that a record of this type is present and calls it.
|
|
Thus user code does not have to use a channelGetPut to get the list
|
|
of record names.</p>
|
|
<p>testExampleServerMain.cpp has an example of how to create a traceRecord:
|
|
<pre>
|
|
recordName = "recordListPGRPC";
|
|
pvRecord = RecordListRecord::create(recordName);
|
|
result = master->addRecord(pvRecord);
|
|
if(!result) cout<< "record " << recordName << " not added" << endl;
|
|
</pre>
|
|
</p>
|
|
|
|
<h2>exampleServer</h2>
|
|
<h3>Overview</h3>
|
|
<p>The example implements a simple service that has a top level pvStructure:
|
|
<pre>
|
|
structure
|
|
structure argument
|
|
string value
|
|
structure result
|
|
string value
|
|
time_t timeStamp
|
|
long secondsPastEpoch
|
|
int nanoSeconds
|
|
int userTag
|
|
</pre>
|
|
<p>It is designed to be accessed via a channelPutGet request.
|
|
The client sets argument.value
|
|
When the record processes it sets result.value to "Hello "
|
|
concatenated with argument.value.
|
|
Thus if the client sets argument.value equal to "World"
|
|
result.value will be "Hello World".
|
|
In addition the timeStamp is set to the time when process is called.</p>
|
|
<p>
|
|
The example can be run on linux as follows:</p>
|
|
<pre>
|
|
mrk> pwd
|
|
/home/hg/pvDatabaseCPP/exampleService
|
|
mrk> bin/linux-x86_64/exampleService
|
|
</pre>
|
|
<h3>Directory Layout</h3>
|
|
<p>
|
|
The directory layout is:
|
|
</p>
|
|
<pre>
|
|
exampleServer
|
|
configure
|
|
ExampleRELEASE.local
|
|
...
|
|
src
|
|
exampleServer.h
|
|
exampleServer.cpp
|
|
exampleServerInclude.dbd
|
|
exampleServerMain.cpp
|
|
exampleServerRegister.cpp
|
|
exampleServerRegister.dbd
|
|
ioc
|
|
Db
|
|
...
|
|
src
|
|
exampleServerInclude.dbd
|
|
exampleServerMain.cpp
|
|
iocBoot
|
|
exampleServer
|
|
st.cmd
|
|
...
|
|
</pre>
|
|
where
|
|
<dl>
|
|
<dt>ExampleRELEASE.local</dt>
|
|
<dd>
|
|
If you make a copy of exampleServer and use it
|
|
to create a new server,
|
|
This is the file that must be copied to RELEASE.local
|
|
and edited.</dd>
|
|
<dt>exampleServer.h</dt>
|
|
<dd>The header file for the service.</dd>
|
|
<dt>exampleServer.cpp</dt>
|
|
<dd>The service implementation.</dd>
|
|
<dt>exampleServerMain.cpp</dt>
|
|
<dd>A main program that runs the example so that it can be accessed
|
|
by a pvAccess client.
|
|
</dd>
|
|
<dt>exampleServerInclude.dbd</dt>
|
|
<dd>This has a register command so that the service can be started
|
|
on a V3 IOC via iocsh.
|
|
</dd>
|
|
<dt>exampleServerRegister.cpp</dt>
|
|
<dd>This has the code to start the service via the following iocsh
|
|
command.
|
|
<dt>exampleServerRegister.dbd</dt>
|
|
<dd>This is the file that is used to create the shell command
|
|
exampleServerCreateRecord.</dd>
|
|
<pre>
|
|
exampleServerCreateRecord exampleServer
|
|
</pre>
|
|
Multiple commands can be issued to create multiple service records.
|
|
</dd>
|
|
<dt>ioc</dt>
|
|
<dd>This is for building a V3 IOC application.</dd>
|
|
<dt>ioc/Db</dt>
|
|
<dd>This has template files for creating V3 records.</dd>
|
|
<dt>ioc/src</dt>
|
|
<dd>The files for running a V3 IOC.</dd>
|
|
<dt>iocBoot/exampleServer</dt>
|
|
<dd>A place to start exampleServer as part of a V3IOC.
|
|
It has a st.cmd file that starts the ioc and also starts pvAccess
|
|
and the example.</dd>
|
|
</dl>
|
|
<p>If only a main program is desired then the directory layout is:</p>
|
|
<pre>
|
|
exampleServer
|
|
configure
|
|
ExampleRELEASE.local
|
|
...
|
|
src
|
|
exampleServer.h
|
|
exampleServer.cpp
|
|
exampleServerMain.cpp
|
|
</pre>
|
|
<p>Thus if only a main program is required the directory layout is simple.</p>
|
|
<p>Also many sites will want to build the src directory in an area
|
|
separate from where the iocs are build.</p>
|
|
<h3>exampleServer.h</h3>
|
|
<p>The example resides in src
|
|
The implementation is in exampleServer.cpp.
|
|
</p>
|
|
</p>The description consists of</p>
|
|
<pre>
|
|
class ExampleServer;
|
|
typedef std::tr1::shared_ptr<ExampleServer> ExampleServerPtr;
|
|
|
|
class ExampleServer :
|
|
public PVRecord
|
|
{
|
|
public:
|
|
POINTER_DEFINITIONS(ExampleServer);
|
|
static ExampleServerPtr create(
|
|
std::string const & recordName);
|
|
virtual ~ExampleServer();
|
|
virtual void destroy();
|
|
virtual bool init();
|
|
virtual void process();
|
|
private:
|
|
ExampleServer(std::string const & recordName,
|
|
epics::pvData::PVStructurePtr const & pvStructure);
|
|
|
|
epics::pvData::PVStringPtr pvArgumentValue;
|
|
epics::pvData::PVStringPtr pvResultValue;
|
|
epics::pvData::PVTimeStamp pvTimeStamp;
|
|
epics::pvData::TimeStamp timeStamp;
|
|
};
|
|
</pre>
|
|
<p>where</p>
|
|
<dl>
|
|
<dt>create<dt>
|
|
<dd>This is example specific but each support could provide
|
|
a similar static method.
|
|
</dd>
|
|
<dt>~ExampleServer<dt>
|
|
<dd>The destructor must be declared virtual.</dd>
|
|
<dt>destroy</dt>
|
|
<dd>Called when the record is being destroyed.
|
|
This must call the base class destroy method.
|
|
<dt>init<dt>
|
|
<dd>A method to initialize the support. It returns true
|
|
if initialization is successful and false if not.
|
|
NOTE that this is a virtual method of PVRecord itself.</dd>
|
|
<dt>process<dt>
|
|
<dd>
|
|
This again is a virtual method of PVRecord.
|
|
</dd>
|
|
<dt>ExampleServer<dt>
|
|
<dd>For the example this is private.</dd>
|
|
<dt>pvValue</dt>
|
|
<dd>This is the field of the top level structure that process
|
|
accesses.
|
|
</dd>
|
|
<dl>
|
|
<p>The implementation of create method is:</p>
|
|
<pre>
|
|
ExampleServerPtr ExampleServer::create(
|
|
std::string const & recordName)
|
|
{
|
|
StandardFieldPtr standardField = getStandardField();
|
|
FieldCreatePtr fieldCreate = getFieldCreate();
|
|
PVDataCreatePtr pvDataCreate = getPVDataCreate();
|
|
StructureConstPtr topStructure = fieldCreate->createFieldBuilder()->
|
|
addNestedStructure("argument")->
|
|
add("value",pvString)->
|
|
endNested()->
|
|
addNestedStructure("result") ->
|
|
add("value",pvString) ->
|
|
add("timeStamp",standardField->timeStamp()) ->
|
|
endNested()->
|
|
createStructure();
|
|
PVStructurePtr pvStructure = pvDataCreate->createPVStructure(topStructure);
|
|
|
|
ExampleServerPtr pvRecord(
|
|
new ExampleServer(recordName,pvStructure));
|
|
if(!pvRecord->init()) pvRecord.reset();
|
|
return pvRecord;
|
|
}
|
|
</pre>
|
|
This:
|
|
<ul>
|
|
<li>Creates the top level structure.</li>
|
|
<li>Creates a ExampleServerPtr via the constructor.</li>
|
|
<li>Calls init and if it fails resets the shared pointer.</li>
|
|
<li>Returns the shared pointer to the newly created record.</li>
|
|
</ul>
|
|
<p>The private constructor method is:</p>
|
|
<pre>
|
|
ExampleServer::ExampleServer(
|
|
std::string const & recordName,
|
|
epics::pvData::PVStructurePtr const & pvStructure)
|
|
: PVRecord(recordName,pvStructure)
|
|
{
|
|
}
|
|
</pre>
|
|
The example is very simple. Note that it calls the base class constructor.
|
|
<p>The destructor and destroy methods are:</p>
|
|
<pre>
|
|
ExampleServer::~ExampleServer()
|
|
{
|
|
}
|
|
|
|
void ExampleServer::destroy()
|
|
{
|
|
PVRecord::destroy();
|
|
}
|
|
</pre>
|
|
The destructor has nothing to do.
|
|
The destroy method, which is virtual, just calls the destroy method of the base class.
|
|
A more complicated example can clean up any resources it used but must call the base
|
|
class destroy method.
|
|
<p>The implementation of init is:</p>
|
|
<pre>
|
|
bool ExampleServer::init()
|
|
{
|
|
initPVRecord();
|
|
PVFieldPtr pvField;
|
|
pvArgumentValue = getPVStructure()->getStringField("argument.value");
|
|
if(pvArgumentValue.get()==NULL) return false;
|
|
pvResultValue = getPVStructure()->getStringField("result.value");
|
|
if(pvResultValue.get()==NULL) return false;
|
|
pvTimeStamp.attach(getPVStructure()->getSubField("result.timeStamp"));
|
|
return true;
|
|
}
|
|
</pre>
|
|
<p>The implementation of process is:</p>
|
|
<pre>
|
|
void ExampleServer::process()
|
|
{
|
|
pvResultValue->put(String("Hello ") + pvArgumentValue->get());
|
|
timeStamp.getCurrent();
|
|
pvTimeStamp.set(timeStamp);
|
|
}
|
|
</pre>
|
|
It gives a value to result.value and
|
|
then sets the timeStamp to the current time.
|
|
<h3>src/exampleServerMain.cpp</h3>
|
|
<p><b>NOTE:</b>
|
|
This is a shorter version of the actual code.
|
|
It shows the essential code.
|
|
The actual example shows how create an additional record.
|
|
</p>
|
|
<p>The main program is:</p>
|
|
<pre>
|
|
int main(int argc,char *argv[])
|
|
{
|
|
PVDatabasePtr master = PVDatabase::getMaster();
|
|
ChannelProviderLocalPtr channelProvider = ChannelProviderLocal::create();
|
|
String recordName("exampleServer");
|
|
PVRecordPtr pvRecord = ExampleServer::create(recordName);
|
|
bool result = master->addRecord(pvRecord);
|
|
if(!result) cout<< "record " << recordName << " not added" << endl;
|
|
recordName = "traceRecordPGRPC";
|
|
pvRecord = TraceRecord::create(recordName);
|
|
result = master->r;addRecord(pvRecord);
|
|
if(!result) cout<< "record " << recordName << " not added" << endl;
|
|
recordName = "recordListPGRPC";
|
|
pvRecord = RecordListRecord::create(recordName);
|
|
result = master->r;addRecord(pvRecord);
|
|
if(!result) cout<< "record " << recordName << " not added" << endl;
|
|
ServerContext::shared_pointer pvaServer =
|
|
startPVAServer(PVACCESS_ALL_PROVIDERS,0,true,true);
|
|
PVStringArrayPtr pvNames = master->r;getRecordNames();
|
|
shared_vector<const string>r; names = pvNames->r;view();
|
|
for(size_t i=0; i<names.size(); ++i) cout << names[i] << endl;
|
|
string str;
|
|
while(true) {
|
|
cout << "Type exit to stop: \n";
|
|
|
|
}
|
|
return 0;
|
|
}
|
|
</pre>
|
|
This:
|
|
<ul>
|
|
<li>Gets a pointer to the master database.</li>
|
|
<li>Creates the local Channel Provider. This starts the pvAccess server.</li>
|
|
<li>Creates record exampleServer </li>
|
|
<li>creates records traceRecordPGRPC and recordListPGRPC</li>
|
|
<li>lists all the records</li>
|
|
<li>Runs forever until the user types exit on standard in.</li>
|
|
</ul>
|
|
<h3>V3IOC exampleServer</h3>
|
|
<p>To start exampleServer as part of a V3IOC:
|
|
<pre>
|
|
mrk> pwd
|
|
/home/hg/pvDatabaseCPP/exampleServer/iocBoot/exampleServer
|
|
mrk> ../../../bin/linux-x86_64/exampleServer st.cmd
|
|
</pre></p>
|
|
<p>You can then issue the commands dbl and pvdbl:
|
|
<pre>
|
|
epics> dbl
|
|
pvdouble
|
|
pvcounter
|
|
pvenum
|
|
pvdoubleArray
|
|
pvstringArray
|
|
epics> pvdbl
|
|
exampleServer
|
|
epics>
|
|
</pre>
|
|
dbl shows the V3 records.
|
|
pvdbl shows the pvRecords.
|
|
</p>
|
|
<p>
|
|
It starts pvaSrv so that the V3 records can be accessed via Channel Access
|
|
or via PVAccess.</p>
|
|
|
|
<h2>exampleDatabase</h2>
|
|
<p>The exampleServer pvDatabase has many records including the following:</p>
|
|
<dl>
|
|
<dt>exampleDouble</dt>
|
|
<dd>A record that is an instance of a record with a process method
|
|
that does nothing. To test it start a channelPut and a channelGet and/or monitor.</dd>
|
|
<dt>exampleDoubleArray</dt>
|
|
<dd>An array record that is an instance of a record with a process method
|
|
that does nothing. It can be tested like exampleDouble. In addition channelArray can
|
|
also be used.</dd>
|
|
<dt>laptoprecordListPGRPC</dt>
|
|
<dd>Implements the record expected by swtshell channelList.
|
|
It can also be used via channelPutGet.</dd>
|
|
<dt>traceRecordPGRPC</dt>
|
|
<dd>This can be used via channelPutGet to set the trace level of another record.</dd>
|
|
</dl>
|
|
<p>It also has a number of other scalar and array records.</p>
|
|
<p>exampleDatabase can be started as a main program or as a V3 IOIC.
|
|
If started as a V3 IOC it also has a number of V3 records,
|
|
and starts pvaSrv so that the V3 records can be accessed via Channel Access
|
|
or via PVAccess.</p>
|
|
<h2>exampleLink</h2>
|
|
<p>This example show how a service can access other PVRecords.
|
|
This section 1) starts with a discussion of accessing data via pvAccess
|
|
and 2) gives a brief description of an example that gets data for an array of doubles.</p>
|
|
<h3>Discussion</h3>
|
|
<h4>Access Alternatives</h4>
|
|
<p>The process routine of a PVRecord can access other PVRecords in two ways:</p>
|
|
<dl>
|
|
<dt>Directly accessing local pvDatabase</dt>
|
|
<dd>
|
|
If the other PVRecord is accessed via the master PVDatabase then
|
|
threading issues are up to the implementation.
|
|
For now this method will not be discussed.</dd>
|
|
<dt>Access via pvAccess</dt>
|
|
<dd>
|
|
If access is via pvAccess then locking is handled by pvAccess.</dd>
|
|
</dl>
|
|
<p>Access via pvAccess can be done either by local or remote channel provider.</p>
|
|
</dl>
|
|
<dl>
|
|
<dt>Access via channelProviderLocal</dt>
|
|
<dd>
|
|
If the local pvAccess server is used the implementation must be careful that it does not
|
|
cause deadlocks.
|
|
When the process method is called the pvRecord for the process method is locked.
|
|
When it makes a pvAccess get, put, etc request the other record is locked.
|
|
Thus if a set of pvAccess links are implemented the possibility of deadlocks
|
|
exists. A simple example is two records that have links to each other.
|
|
More complex sets are easily created.
|
|
Unless the developer has complete control of the set of records then remote pvAccess should
|
|
be used.
|
|
But this results in more context switches.
|
|
</dd>
|
|
<dt>Access via remote pvAccess</dt>
|
|
<dd>If remote pvAccess is used then all locking issues are handled by pvAccess.
|
|
The linked channel can be a pvRecord in the local pvDatabase or can be implemented
|
|
by a remote pvAccess server.</dd>
|
|
</dl>
|
|
<h4>Data synchronization</h4>
|
|
<p>If pvAccess is used then it handles data synchronization.
|
|
This is done by making a copy of the data that is transfered between the two pvRecords.
|
|
This is true if either remote or local pvAccess is used.
|
|
Each get, put, etc request results in data being copied between the two records.</p>
|
|
<p>
|
|
If the linked channel is a local pvRecord then,
|
|
for scalar and structure arrays,
|
|
raw data is NOT copied for gets.
|
|
This is because pvData uses shared_vector to hold the raw data.
|
|
Instead of copying the raw data the reference count is incremented.</p>
|
|
<p>For puts the linked array will force a new allocation of the raw data in the linked record,
|
|
i. e. copy on write semantics are enforced. This is done automatically
|
|
by pvData and not by pvDatabase.</p>
|
|
<h4>Some details</h4>
|
|
<p>As mentioned before a pvDatabase server can be either a separate process,
|
|
i. e. a main program, or can be part of a V3IOC.</p>
|
|
<p>A main pvDatabase server issues the following calls:</p>
|
|
<pre>
|
|
ClientFactory::start();
|
|
ChannelProviderLocalPtr channelProvider = getChannelProviderLocal();
|
|
...
|
|
ServerContext::shared_pointer serverContext = startPVAServer(PVACCESS_ALL_PROVIDERS,0,true,true);
|
|
</pre>
|
|
<p>The first call is only necessary if some of the pvRecords
|
|
have pvAccess links.
|
|
These must be called before any code that uses links is initialized.
|
|
After these two calls there will be two channel providers: <b>local</b>, and <b>pvAccess</b>.
|
|
|
|
</p>
|
|
<p>A pvDatabase that is part of a V3IOC has the following in the st.cmd file.</p>
|
|
<pre>
|
|
...
|
|
iocInit()
|
|
startPVAClient
|
|
startPVAServer
|
|
## commands to create pvRecords
|
|
</pre>
|
|
<p>
|
|
Once the client and local provider code has started then the following creates a channel access link.
|
|
</p>
|
|
<pre>
|
|
PVDatabasePtr master = PVDatabase::getMaster();
|
|
ChannelProvider::shared_pointer provider =
|
|
getChannelProviderRegistry()->getProvider(providerName);
|
|
Channel::shared_pointer channel = provider->createChannel(channelName,channelRequester);
|
|
</pre>
|
|
|
|
<h3>Directory Layout</h3>
|
|
<pre>
|
|
exampleLink
|
|
configure
|
|
ExampleRELEASE.local
|
|
...
|
|
src
|
|
exampleLink.h
|
|
exampleLink.cpp
|
|
exampleLinkInclude.dbd
|
|
exampleLinkRegister.cpp
|
|
ioc
|
|
Db
|
|
src
|
|
exampleLinkInclude.dbd
|
|
exampleLinkMain.cpp
|
|
iocBoot
|
|
exampleLink
|
|
st.local
|
|
st.remote
|
|
...
|
|
</pre>
|
|
<p>This example is only built to be run as part of a V3 IOC.
|
|
Note that two startup files are available: st.local and st.remote.
|
|
st.local has two records: doubleArray and exampleLink.
|
|
doubleArray is a record that can be changed via a call to pvput.
|
|
exampleLink is a record that, when processed, gets the value from doubleArray and sets its value equal
|
|
to the value read.
|
|
st.local has both records.
|
|
st.remote has only one record named exampleLinkRemote.
|
|
<p>
|
|
<p>To start the example:</p>
|
|
<pre>
|
|
mrk> pwd
|
|
/home/hg/pvDatabaseCPP/exampleLink/iocBoot/exampleLink
|
|
mrk> ../../bin/linux-x86_64/exampleLink st.local
|
|
</pre>
|
|
<p>then in another window:</p>
|
|
<pre>
|
|
mrk> pvput doubleArray 4 100 200 300 400
|
|
Old : doubleArray 0
|
|
New : doubleArray 4 100 200 300 400
|
|
mrk> pvget -r "record[process=true]field(value)" exampleLink
|
|
exampleLink
|
|
structure
|
|
double[] value [100,200,300,400]
|
|
mrk>
|
|
</pre>
|
|
<h3>exampleLink Implementation</h3>
|
|
<p>exampleLink.h contains the following:</p>
|
|
<pre>
|
|
...
|
|
class ExampleLink :
|
|
public PVRecord,
|
|
public epics::pvAccess::ChannelRequester,
|
|
public epics::pvAccess::ChannelGetRequester
|
|
{
|
|
public:
|
|
POINTER_DEFINITIONS(ExampleLink);
|
|
static ExampleLinkPtr create(
|
|
std::string const & recordName,
|
|
std::string const & providerName,
|
|
std::string const & channelName
|
|
);
|
|
virtual ~ExampleLink() {}
|
|
virtual void destroy();
|
|
virtual bool init();
|
|
virtual void process();
|
|
virtual void channelCreated(
|
|
const epics::pvData::Status& status,
|
|
epics::pvAccess::Channel::shared_pointer const & channel);
|
|
virtual void channelStateChange(
|
|
epics::pvAccess::Channel::shared_pointer const & channel,
|
|
epics::pvAccess::Channel::ConnectionState connectionState);
|
|
virtual void channelGetConnect(
|
|
const epics::pvData::Status& status,
|
|
epics::pvAccess::ChannelGet::shared_pointer const & channelGet,
|
|
epics::pvData::PVStructure::shared_pointer const & pvStructure,
|
|
epics::pvData::BitSet::shared_pointer const & bitSet);
|
|
virtual void getDone(const epics::pvData::Status& status);
|
|
private:
|
|
...
|
|
</pre>
|
|
<p>All the non-static methods are either PVRecord, PVChannel, or PVChannelGet methods
|
|
and will not be discussed further.
|
|
The create method is called to create a new PVRecord instance with code that will issue
|
|
a ChannelGet::get request every time the process method of the instance is called.
|
|
Some other pvAccess client can issue a channelGet, to the record instance, with a request
|
|
to process in order to test the example.</p>
|
|
<p>All of the initialization is done by a combination of the create and init methods so
|
|
lets look at them:</p>
|
|
<pre>
|
|
ExampleLinkPtr ExampleLink::create(
|
|
String const & recordName,
|
|
String const & providerName,
|
|
String const & channelName)
|
|
{
|
|
PVStructurePtr pvStructure = getStandardPVField()->scalarArray(
|
|
pvDouble,"alarm.timeStamp");
|
|
ExampleLinkPtr pvRecord(
|
|
new ExampleLink(
|
|
recordName,providerName,channelName,pvStructure));
|
|
if(!pvRecord->init()) pvRecord.reset();
|
|
return pvRecord;
|
|
}
|
|
</pre>
|
|
<p>This first creates a new ExampleLink instance,
|
|
and then calls the init method and the returns a ExampleLinkPtr.
|
|
Note that if init returns false it returns a pointer to NULL.</p>
|
|
<p>The init method is:</p>
|
|
<pre>
|
|
bool ExampleLink::init()
|
|
{
|
|
initPVRecord();
|
|
|
|
PVStructurePtr pvStructure = getPVRecordStructure()->getPVStructure();
|
|
pvTimeStamp.attach(pvStructure->getSubField("timeStamp"));
|
|
pvAlarm.attach(pvStructure->getSubField("alarm"));
|
|
pvValue = static_pointer_cast<PVDoubleArray>(
|
|
pvStructure->getScalarArrayField("value",pvDouble));
|
|
if(pvValue==NULL) {
|
|
return false;
|
|
}
|
|
ChannelAccess::shared_pointer channelAccess = getChannelAccess();
|
|
ChannelProvider::shared_pointer provider =
|
|
channelAccess->getProvider(providerName);
|
|
if(provider==NULL) {
|
|
cout << getRecordName() << " provider "
|
|
<< providerName << " does not exist" << endl;
|
|
return false;
|
|
}
|
|
ChannelRequester::shared_pointer channelRequester =
|
|
dynamic_pointer_cast<ChannelRequester>(getPtrSelf());
|
|
channel = provider->createChannel(channelName,channelRequester);
|
|
event.wait();
|
|
if(!status.isOK()) {
|
|
cout << getRecordName() << " createChannel failed "
|
|
<< status.getMessage() << endl;
|
|
return false;
|
|
}
|
|
ChannelGetRequester::shared_pointer channelGetRequester =
|
|
dynamic_pointer_cast<ChannelGetRequester>(getPtrSelf());
|
|
PVStructurePtr pvRequest = getCreateRequest()->createRequest(
|
|
"value,alarm,timeStamp",getPtrSelf());
|
|
channelGet = channel->createChannelGet(channelGetRequester,pvRequest);
|
|
event.wait();
|
|
if(!status.isOK()) {
|
|
cout << getRecordName() << " createChannelGet failed "
|
|
<< status.getMessage() << endl;
|
|
return false;
|
|
}
|
|
getPVValue = static_pointer_cast<PVDoubleArray>(
|
|
getPVStructure->getScalarArrayField("value",pvDouble));
|
|
if(getPVValue==NULL) {
|
|
cout << getRecordName() << " get value not PVDoubleArray" << endl;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
</pre>
|
|
<p>This first makes sure the pvStructure has the fields it requires:</p>
|
|
<dl>
|
|
<dt>timeStamp</dt>
|
|
<dd>A timeStamp structure. This will be set to the current time when process is called.</dd>
|
|
<dt>alarm</dt>
|
|
<dd>An alarm structure. This will be used to pass status information to the client when
|
|
process is called.</dd>
|
|
<dt>value</dt>
|
|
<dd>This must be a scalarArray of type double.
|
|
It is where data is copied when the channelGet is issued.</dd>
|
|
</dl>
|
|
<p>Next it makes sure the channelProvider exists.</p>
|
|
<p>Next it creates the channel and waits until it connects.</p>
|
|
<p>Next it creates the channelGet and waits until it is created.</p>
|
|
<p>Next it makes sure it has connected to a double array field.</p>
|
|
<p>If anything goes wrong during initialization it returns false.
|
|
This a return of true means that it has successfully created a channelGet and is ready
|
|
to issue gets when process is called.</p>
|
|
<p>Look at the code for more details.</p>
|
|
<h2>examplePowerSupply</h2>
|
|
<p>This is an example of creating a service that requires a somewhat complicated
|
|
top level PVStructure.
|
|
It is similar to the powerSupply example that is provided with pvIOCJava.
|
|
Look at the code for details.
|
|
</p>
|
|
<h2>Array Performance and Memory Example</h2>
|
|
<p>This section describes main programs that demonstrate performance
|
|
of large arrays and can also be used to check for memory leaks.
|
|
Checking for memory leaks can be accomplished by running the programs with valgrind
|
|
or some other memory check program.
|
|
</p>
|
|
<h3>Brief Summary</h3>
|
|
<p>The programs are:</p>
|
|
<dl>
|
|
<dt>arrayPerformanceMain</dt>
|
|
<dd>This is server and also a configurable number of longArrayMonitor clients.
|
|
The clients can use either the local or
|
|
remote providers. The moitor code is the same code that is used by longArrayMonitorMain.
|
|
</dd>
|
|
<dt>longArrayMonitorMain</dt>
|
|
<dd>Remote client that monitors the array served by arrayPerformanceMain.</dd>
|
|
<dt>longArrayGetMain</dt>
|
|
<dd>Remote client that uses channelGet to access the array served by arrayPerformanceMain.</dd>
|
|
<dt>longArrayPutMain</dt>
|
|
<dd>Remote client that uses channelPut to access the array served by arrayPerformanceMain.</dd>
|
|
<dl>
|
|
<p>Each has support for <b>-help</b>.</p>
|
|
<pre>
|
|
mrk> pwd
|
|
/home/hg/pvDatabaseCPP-md
|
|
mrk> bin/linux-x86_64/arrayPerformanceMain -help
|
|
arrayPerformanceMain recordName size delay providerName nMonitor queueSize waitTime
|
|
default
|
|
arrayPerformance arrayPerformance 10000000 0.0001 local 1 2 0.0
|
|
|
|
mrk> bin/linux-x86_64/longArrayMonitorMain -help
|
|
longArrayMonitorMain channelName queueSize waitTime
|
|
default
|
|
longArrayMonitorMain arrayPerformance 2 0.0
|
|
|
|
mrk> bin/linux-x86_64/longArrayGetMain -help
|
|
longArrayGetMain channelName iterBetweenCreateChannel iterBetweenCreateChannelGet delayTime
|
|
default
|
|
longArrayGetMain arrayPerformance 0 0 1
|
|
|
|
mrk> bin/linux-x86_64/longArrayPutMain -help
|
|
longArrayPutMain channelName arraySize iterBetweenCreateChannel iterBetweenCreateChannelPut delayTime
|
|
default
|
|
longArrayPutMain arrayPerformance 10 0 0 1
|
|
|
|
mrk>
|
|
</pre>
|
|
<h3>Example output</h3>
|
|
<p><b>Note:</b> These may fail if run on a platform that does not have sufficent memory,</p>
|
|
<p>To see an example just execute the following commands in four different terminal windows:</p>
|
|
<pre>
|
|
bin/linux/<arch>/arrayPerformanceMain
|
|
bin/linux/<arch>/longArrayMonitorMain
|
|
bin/linux/<arch>/longArrayGetMain
|
|
bin/linux/<arch>/longArrayPutMain
|
|
</pre>
|
|
<p>Each program generates a report every second when it has somthing to report.
|
|
Examples are:
|
|
<pre>
|
|
mrk> bin/linux-x86_64/arrayPerformanceMain
|
|
arrayPerformance arrayPerformance 10000000 0.0001 local 1 2 0
|
|
...
|
|
monitors/sec 66 first 131 last 131 changed {1, 2} overrun {} megaElements/sec 656.999
|
|
arrayPerformance value 132 time 1.00486 Iterations/sec 65.681 megaElements/sec 656.81
|
|
monitors/sec 66 first 197 last 197 changed {1, 2} overrun {} megaElements/sec 656.304
|
|
arrayPerformance value 198 time 1.00563 Iterations/sec 65.6307 megaElements/sec 656.307
|
|
monitors/sec 66 first 263 last 263 changed {1, 2} overrun {} megaElements/sec 654.824
|
|
...
|
|
</pre>
|
|
<pre>
|
|
mrk> bin/linux-x86_64/longArrayMonitorMain
|
|
longArrayMonitorMain arrayPerformance 2 0
|
|
...
|
|
monitors/sec 6 first 2357 last 2357 changed {1, 2} overrun {} megaElements/sec 68.6406
|
|
monitors/sec 13 first 2385 last 2385 changed {1, 2} overrun {} megaElements/sec 118.72
|
|
monitors/sec 9 first 2418 last 2418 changed {1, 2} overrun {1, 2} megaElements/sec 85.0984
|
|
...
|
|
</pre>
|
|
<pre>
|
|
mrk> bin/linux-x86_64/longArrayPutMain
|
|
longArrayPutMain arrayPerformance 10 0 0 1
|
|
...
|
|
put numChannelPut 0 time 1.00148 Elements/sec 79.8819
|
|
put numChannelPut 1 time 1.00176 Elements/sec 79.8598
|
|
...
|
|
</pre>
|
|
<pre>
|
|
mrk> bin/linux-x86_64/longArrayGetMain
|
|
longArrayGetMain arrayPerformance 0 0 1
|
|
...
|
|
get kiloElements/sec 7384.61
|
|
get kiloElements/sec 8726.34
|
|
...
|
|
</pre>
|
|
|
|
<h3>arrayPerformance</h3>
|
|
<p>The arguments for arrayPerforamanceMain are:</p>
|
|
<dl>
|
|
<dt>recordName</dt>
|
|
<dd>The name for the arrayPerform record.</dd>
|
|
<dt>size</dt>
|
|
<dd>The size for the long array of the value field.</dd>
|
|
<dt>delay</dt>
|
|
<dd>The time in seconds to sleep after each iteration.</dd>
|
|
<dt>providerName</dt>
|
|
<dd>The name of the channel provider for the longArrayMonitors
|
|
created by the main program. This must be either <b>local</b>
|
|
or <b>pvAccess</b>.
|
|
</dd>
|
|
<dt>nMonitor</dt>
|
|
<dd>The number of longArrayMonitors to create.</dd>
|
|
<dt>queueSize</dt>
|
|
<dd>The queueSize for the element queue.
|
|
A value less than 1 will become 1.
|
|
</dd>
|
|
<dt>waitTime</dt>
|
|
<dd>The time that longArrayMonitor will sleep after poll returns a monitorElement.</dd>
|
|
</dl>
|
|
<p>
|
|
arrayPerformance creates a PVRecord that has the structure:.
|
|
<pre>
|
|
recordName
|
|
long[] value
|
|
timeStamp timeStamp
|
|
alarm alarm
|
|
</pre>
|
|
Thus it holds an array of 64 bit integers.</p>
|
|
<p>The record has support that consists of a separate thread that runs
|
|
until the record is destroyed executing the following algorithm:</p>
|
|
<dl>
|
|
<dt>report</dt>
|
|
<dd>Once a second it produces a report.
|
|
In the above example output each line starting with
|
|
<b>ArrayPerformance</b> is an arrayPerformance report.
|
|
</dd>
|
|
<dt>create array</dt>
|
|
<dd>A new shared_vector is created and each element is set equal
|
|
to the interation count.</dd>
|
|
<dt>lock</dt>
|
|
<dd>The arrayPerformance record is locked.</dd>
|
|
<dt>Begin group put</dt>
|
|
<dd>beginGroupReport is called.</dd>
|
|
<dt>replace</dt>
|
|
<dd>The value field of the record is replaced
|
|
with the newly created shared_vector.</dd>
|
|
<dt>process</dt>
|
|
<dd>The record is then processed. This causes the timeStamp to
|
|
be set to the current time.</dd>
|
|
<dt>End group put</dt>
|
|
<dd>endGroupPut is called.</dd>
|
|
<dt>unlock</dt>
|
|
<dd>The arrayPerformance record is unlocked.</dd>
|
|
<dt>delay</dt>
|
|
<dd>If delay is greater than zero epicsThreadSleep is called.</dd>
|
|
</dl>
|
|
<h3>longArrayMonitor</h3>
|
|
<p>This is a pvAccess client that monitors an arrayPerformance record.
|
|
It generates a report every second showing how many elements has received.
|
|
For every monitor it also checks that the number of alements is >0 and the
|
|
the first element equals the last element. It reports an error if either
|
|
of these conditions is not true.</p>
|
|
<p>The arguments for longArrayMonitorMain are:</p>
|
|
<dl>
|
|
<dt>channelName</dt>
|
|
<dd>The name for the arrayPerform record.</dd>
|
|
<dt>queueSize</dt>
|
|
<dd>The queueSize. Note that any size <2 is made 2.</dd>
|
|
<dt>waitTime</dt>
|
|
<dd>The time to wait after a poll request returns a monitorElement.
|
|
This can be used to force an overrun of the client even if there is no
|
|
overrun on the server.</dd>
|
|
</dl>
|
|
<h3>longArrayGet</h3>
|
|
<p>This is a pvAccess client that uses channelGet to access an arrayPerformance record.
|
|
Every second it produces a report.</p>
|
|
|
|
<p>The arguments for longArrayGetMain are:</p>
|
|
<dl>
|
|
<dt>channelName</dt>
|
|
<dd>The name for the arrayPerform record.</dd>
|
|
<dt>iterBetweenCreateChannel</dt>
|
|
<dd>The number of iterations between destroying and recreating the channel.
|
|
A value of 0 means never destroy and recreate.
|
|
</dd>
|
|
<dt>iterBetweenCreateChannelGet</dt>
|
|
<dd>The number of iterations between destroying and recreating the channelGet.
|
|
A value of 0 means never destroy and recreate.
|
|
</dd>
|
|
<dt>delayTime</dt>
|
|
<dd>The time to dalay between gets.</dd>
|
|
</dl>
|
|
<h3>longArrayPut</h3>
|
|
<p>This is a pvAccess client that uses channelPut to access an arrayPerformance record.
|
|
Every second it produces a report.</p>
|
|
|
|
<p>The arguments for longArrayPutMain are:</p>
|
|
<dl>
|
|
<dt>channelName</dt>
|
|
<dd>The name for the arrayPerform record.</dd>
|
|
<dt>arraySize</dt>
|
|
<dd>The capacity and length of the array to put to the server.</dd>
|
|
<dt>iterBetweenCreateChannel</dt>
|
|
<dd>The number of iterations between destroying and recreating the channel.
|
|
A value of 0 means never destroy and recreate.
|
|
</dd>
|
|
<dt>iterBetweenCreateChannelPut</dt>
|
|
<dd>The number of iterations between destroying and recreating the channelPut.
|
|
A value of 0 means never destroy and recreate.
|
|
</dd>
|
|
<dt>delayTime</dt>
|
|
<dd>The time to dalay between gets.</dd>
|
|
</dl>
|
|
|
|
<h3>Some results</h3>
|
|
<h4>array performance</h4>
|
|
<p>The results were from my laptop.
|
|
It has a 2.2Ghz intel core i7 with 4Gbytes of memory.
|
|
The operating system is linux fedora 16.</p>
|
|
<p>When test are performed with large arrays it is a good idea to also
|
|
run a system monitor facility and check memory and swap history.
|
|
If a test configuration causes physical memory to be exhausted
|
|
then performance becomes <b>very</b> poor.
|
|
You do not want to do this.</p>
|
|
<h4>arrayPerformance results</h4>
|
|
<p>The simplest test to run arrayPerformance with the defaults:</p>
|
|
<pre>
|
|
mrk> pwd
|
|
/home/hg/pvDatabaseCPP-md
|
|
mrk> bin/linux-x86_64/arrayPerformanceMain
|
|
</pre>
|
|
<p>This means that the array will hold 10 million elements.
|
|
The delay will be a millisecond.
|
|
There will be a single monitor and it will connect directly
|
|
to the local channelProvider, i. e. it will not use any network
|
|
connection.</p>
|
|
<p>The report shows that arrayPerformance can perform about 50 iterations per second
|
|
and is putting about 500million elements per second.
|
|
Since each element is an int64 this means about 4gigaBytes per second.
|
|
</p>
|
|
<p>When no monitors are requested and a remote longArrayMonitorMain is run:<p>
|
|
<pre>
|
|
mr> pwd
|
|
/home/hg/pvDatabaseCPP-md
|
|
mrk> bin/linux-x86_64/longArrayMonitorMain
|
|
</pre>
|
|
<p>The performance drops to about 25 interations per second and 250 million elements per second.
|
|
The next section has an example that demonstrates what happens.
|
|
Note that if the array size is small enough to fit in the local cache then running longArrayMonitor
|
|
has almost no effect of arrayPerforance.
|
|
</p>
|
|
<h4>memory leaks</h4>
|
|
<p>Running longArrayMonitorMain, longArrayPutMain, and longArrayGetMain
|
|
under valgrind shows no memory leaks.</p>
|
|
<p>arrayPerformanceMain shows the following:</p>
|
|
<pre>
|
|
==9125== LEAK SUMMARY:
|
|
==9125== definitely lost: 0 bytes in 0 blocks
|
|
==9125== indirectly lost: 0 bytes in 0 blocks
|
|
==9125== possibly lost: 576 bytes in 2 blocks
|
|
</pre>
|
|
<p>The possibly leaked is either 1 or 2 blocks.
|
|
It seems to be the same if clients are connected.
|
|
</p>
|
|
<h2>Vector Performance</h2>
|
|
<p>This example demonstrates how array size effects performance.
|
|
The example is run as:</p>
|
|
<pre>
|
|
bin/linux-x86_64/vectorPerformanceMain -help
|
|
vectorPerformanceMain size delay nThread
|
|
default
|
|
vectorPerformance 50000000 0.01 1
|
|
</pre>
|
|
<p>Consider the following:</p>
|
|
<pre>
|
|
bin/linux-x86_64/vectorPerformanceMain 50000000 0.00 1
|
|
...
|
|
thread0 value 20 time 1.01897 iterations/sec 19.6277 elements/sec 981.383million
|
|
thread0 value 40 time 1.01238 iterations/sec 19.7554 elements/sec 987.772million
|
|
thread0 value 60 time 1.00878 iterations/sec 19.826 elements/sec 991.299million
|
|
...
|
|
bin/linux-x86_64/vectorPerformanceMain 50000000 0.00 2
|
|
...
|
|
thread0 value 21 time 1.00917 iterations/sec 9.90911 elements/sec 495.455million
|
|
thread1 value 31 time 1.05659 iterations/sec 9.46443 elements/sec 473.221million
|
|
thread0 value 31 time 1.07683 iterations/sec 9.28648 elements/sec 464.324million
|
|
thread1 value 41 time 1.0108 iterations/sec 9.89312 elements/sec 494.656million
|
|
...
|
|
bin/linux-x86_64/vectorPerformanceMain 50000000 0.00 3
|
|
thread0 value 7 time 1.0336 iterations/sec 6.77244 elements/sec 338.622million
|
|
thread1 value 7 time 1.03929 iterations/sec 6.73534 elements/sec 336.767million
|
|
thread2 value 7 time 1.04345 iterations/sec 6.70852 elements/sec 335.426million
|
|
thread0 value 14 time 1.03335 iterations/sec 6.77406 elements/sec 338.703million
|
|
thread1 value 14 time 1.03438 iterations/sec 6.76734 elements/sec 338.367million
|
|
thread2 value 14 time 1.04197 iterations/sec 6.71805 elements/sec 335.903million
|
|
...
|
|
bin/linux-x86_64/vectorPerformanceMain 50000000 0.00 4
|
|
thread2 value 5 time 1.00746 iterations/sec 4.96298 elements/sec 248.149million
|
|
thread1 value 5 time 1.02722 iterations/sec 4.86751 elements/sec 243.376million
|
|
thread3 value 5 time 1.032 iterations/sec 4.84496 elements/sec 242.248million
|
|
thread0 value 6 time 1.18882 iterations/sec 5.04703 elements/sec 252.351million
|
|
thread2 value 10 time 1.00388 iterations/sec 4.98068 elements/sec 249.034million
|
|
thread3 value 10 time 1.02755 iterations/sec 4.86592 elements/sec 243.296million
|
|
thread1 value 10 time 1.04836 iterations/sec 4.76936 elements/sec 238.468million
|
|
thread0 value 11 time 1.01575 iterations/sec 4.92249 elements/sec 246.124million
|
|
</pre>
|
|
<p>As more threads are running the slower each thread runs.</p>
|
|
<p>But now consider a size that fits in a local cache.<p>
|
|
<pre>
|
|
bin/linux-x86_64/vectorPerformanceMain 5000 0.00n/linux-x86_64/vectorPerformanceMain 5000 0.00 1
|
|
...
|
|
thread0 value 283499 time 1 iterations/sec 283498 elements/sec 1417.49million
|
|
thread0 value 569654 time 1 iterations/sec 286154 elements/sec 1430.77million
|
|
thread0 value 856046 time 1 iterations/sec 286392 elements/sec 1431.96million
|
|
...
|
|
bin/linux-x86_64/vectorPerformanceMain 5000 0.00 2
|
|
...
|
|
thread0 value 541790 time 1 iterations/sec 271513 elements/sec 1357.56million
|
|
thread1 value 541798 time 1 iterations/sec 271418 elements/sec 1357.09million
|
|
thread0 value 813833 time 1 iterations/sec 272043 elements/sec 1360.21million
|
|
thread1 value 813778 time 1 iterations/sec 271979 elements/sec 1359.89million
|
|
thread0 value 541790 time 1 iterations/sec 271513 elements/sec 1357.56million
|
|
thread1 value 541798 time 1 iterations/sec 271418 elements/sec 1357.09million
|
|
thread0 value 813833 time 1 iterations/sec 272043 elements/sec 1360.21million
|
|
thread1 value 813778 time 1 iterations/sec 271979 elements/sec 1359.89million
|
|
...
|
|
bin/linux-x86_64/vectorPerformanceMain 5000 0.00 3
|
|
...
|
|
thread0 value 257090 time 1 iterations/sec 257089 elements/sec 1285.45million
|
|
thread1 value 256556 time 1 iterations/sec 256556 elements/sec 1282.78million
|
|
thread2 value 514269 time 1 iterations/sec 257839 elements/sec 1289.19million
|
|
thread0 value 514977 time 1 iterations/sec 257887 elements/sec 1289.43million
|
|
thread1 value 514119 time 1 iterations/sec 257563 elements/sec 1287.81million
|
|
thread2 value 770802 time 1 iterations/sec 256532 elements/sec 1282.66million
|
|
</pre>
|
|
<p>Now the number of threads has a far smaller effect on the performance of each thread.
|
|
</p>
|
|
|
|
</div>
|
|
</body>
|
|
</html>
|