Files
pvDatabase/documentation/pvDatabaseCPP_20130828.html

1733 lines
66 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, 28-Aug-2013</h2>
<dl>
<dt>Latest version:</dt>
<dd><a
href="pvDatabaseCPP.html">pvDatabaseCPP.html</a>
</dd>
<dt>This version:</dt>
<dd><a
href= "pvDatabaseCPP_20130828.html">pvDatabaseCPP20130828.html</a>
</dd>
<dt>Previous version:</dt>
<dd><a href="pvDatabaseCPP_20130725.html">pvDatabaseCPP20130725.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 />
</div>
<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 28-Aug-2013 version of of pvDatabaseCPP.
<p><b>NOTE:</b>
This is built against pvDataCPP-md NOT against pvDataCPP.
To build you must also
checkout pvAccessCPP and build it against pvDataCPP-md.
</p>
</p>
<p>All channel methods except channelRPC, which is implemented
by pvAccess, have been implemented.
This project is ready for alpha users.
</p>
<p>Future enhancements in priority order:</p>
<dl>
<dt>Separate example that also has pvaSrv</dt>
<dd>Create a separate example that combines a V3IOC,
a pvDatabase, and pvaSrv.
This will not be done until pvDataCPP-md is merged into pvDataCPP.
</dd>
<dt>channelArray</dt>
<dd>The arguments that have type <b>int</b> should be changed to <b>size_t</b>
This will not be done until pvDataCPP-md is merged into pvDataCPP.
</dd>
<dt>arrayPerformanceMain</dt>
<dd>When this is run without a pvAccess client the performance is great.
But when a pvAccess client is monitoring then the preformance slows
more then expected.
I do not understand and must spend more time determining why.</dd>
<dt>Monitor Algorithms</dt>
<dd>Monitor algorithms have not been implemented.
Thus all monitors are onPut.</dd>
<dt>Testing</dt>
<dd>Needs more testing.
Also none of the examples that use pvAccess can be run with gdb.
</dd>
<dt>Termination issues.</dt>
<dd>longArrayMonitor has memory leaks when main terminates.</dt>
<dt>Create regression tests</dt>
<dd>Currently only examples exist and have been used for testing.</dd>
</dl>
<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>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 includes 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 tha later case the IOC has both a database of V3 Records
and a pvDatabase.</dd>
<dt>exampleCounter</dt>
<dd>This is a simple example showing how to create a PVRecord and
how to deploy it either as a standalone process or as part of a V3IOC.</dd>
<dt>exampleServer</dt>
<dd>This example has a set of PVRecords.
Again the records can be deployed either as a standalone process or
as part of a V3IOC.</dd>
<dt>examplePVADoubleArrayGet</dt>
<dd>This is an example PVRecord that uses channelGet to get an array of doubles from
another PVRecord via pvAccess. The example shows how to get the value either via
remote pvAccess or by directly accessing the local channelProvider.
Again the records can be deployed either as a standalone process or
as part of a V3IOC.</dd>
</dl>
<p><b>database</b> provides base classes that 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>
<h3>Getting started</h3>
<p>Included with this project are three examples that are useful for
seeing how pvDatabase can be used by clients.
Each can be deployed either as a standalone process or as part of a V3IOC.
The examples are:
<dl>
<dt>exampleCounterMain</dt>
<dd>This has a database consisting of a single record named exampleCounter:
The exampleCounter is discussed in a following section.</dd>
<dt>exampleCounter</dt>
<dd>This is exampleCounter as part of a V3IOC.</dd>
<dt>exampleServerMain</dt>
<dd>This has a database with several records.</dd>
<dt>exampleServer</dt>
<dd>This is exampleServer as part of a V3IOC</dd>
<dt>examplePVADoubleArrayGetMain</dt>
<dd>This is the example of how to use pvAccess to get data.</dd>
<dt>examplePVADoubleArrayGet</dt>
<dd>This is examplePVADoubleArrayGet as part of a V3IOC</dd>
</dt>
</p>
<h3>exampleCounter</h3>
<p>To start exampleCounterMain:
<pre>
mrk&gt; pwd
/home/hg/pvDatabaseCPP
mrk&gt; bin/linux-x86_64/exampleCounterMain
</pre></p>
<p>To start exampleCounter as part of a V3IOC:
<pre>
mrk&gt; pwd
/home/hg/pvDatabaseCPP/iocBoot/exampleCounter
mrk&gt; ../../../bin/linux-x86_64/exampleCounter st.cmd
</pre></p>
<p>You can then issue the commands dbl and pvdbl:
<pre>
epics&gt; dbl
double01
epics&gt; pvdbl
exampleCounter
epics&gt;
</pre>
double01 is a v3Record.
exampleCounter is a pvRecord.
</p>
<p>Starting exampleServer is similar.
After successfully running exampleCounterMain and exampleCounter then
try starting exampleServerMain and exampleServer.
</p>
<h3>exampleServer</h3>
<p>This example provides the exampleCounter record in addition to a number of other PVRecords.
Like exampleCounter it can be started either as a standalone main program or as
a part of a V3IOC.
The exampleServer pvDatabase has many records including the following:</p>
<dl>
<dt>exampleCounter</dt>
<dd>A record that is an instance of exampleCounter described below.
The most useful channel methods are channelGet, channelProcess,
and monitor.</dd>
<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>examplePowerSupply</dt>
<dd>Can be used by channelGet, channelPut, channelPutGet, and monitor.</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>
<h3>swtshell</h3>
<p>The Java program
<a
href="http://epics-pvdata.sourceforge.net/docbuild/swtshellJava/tip/documentation/swtshellJava.html">
swtshell</a>
can be used to access pvDatabase.</p>
<p>In particular read the sections "Getting Started" and "Simple Example".
They will work on the exampleServer with the following differences:
<dl>
<dt>startExample.zip</dt>
<dd>Do NOT use this. Instead run the exampleServer as described above.</dd>
<dt>channelList result</dt>
<dd>The result of channelList will show the list of records that
exampleServer has rather than the records from startExample.zip</dd>
</dl>
</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>Example PVRecord Extension</h3>
<p>The example implements a simple counter.
The example can be run on linux as follows:</p>
<pre>
mrk&gt; pwd
/home/hg/pvDatabaseCPP
mrk&gt; bin/linux-x86_64/exampleCounter
</pre>
<p>The example consists of four components:</p>
<dl>
<dt>ExampleCounter.h</dt>
<dd>The source code for the counter.
It is located in directory pvDatabaseCPP/example/src/exampleCounter.
</dd>
<dt>exampleCounterMain.cpp</dt>
<dd>A main program that runs the example so that it can be accessed
by a pvAccess client.
It is located in directory pvDatabaseCPP/example/exampleCounter.
</dd>
<dt>V3IOC/exampleCounter</dt>
<dd>This is a directory that packages exampleCounter to make it
part of a V3IOC.</dd>
<dt>iocBoot/exampleCounter</dt>
<dd>A place to start exampleCounter as part of a V3IOC.
It follows the normal iocCore conventions.</dd>
</dl>
<h4>ExampleCounter.h</h4>
<p>The example resides in src/database.
The complete implementation is in the header file.
A serious implementation might break the code into a header and an
implementation file.<p>
</p>The description consists of</p>
<pre>
class ExampleCounter;
typedef std::tr1::shared_ptr&lt;ExampleCounter&gt; ExampleCounterPtr;
class ExampleCounter :
public PVRecord
{
public:
POINTER_DEFINITIONS(ExampleCounter);
static ExampleCounterPtr create(
epics::pvData::String const &amp; recordName);
virtual ~ExampleCounter();
virtual void destroy();
virtual bool init();
virtual void process();
private:
ExampleCounter(epics::pvData::String const &amp; recordName,
epics::pvData::PVStructurePtr const &amp; pvStructure);
epics::pvData::PVLongPtr pvValue;
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>~ExampleCounter<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>ExampleCounter<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>
ExampleCounterPtr ExampleCounter::create(
epics::pvData::String const &amp; recordName)
{
epics::pvData::PVStructurePtr pvStructure =
epics::pvData::getStandardPVField()-&gt;scalar(
epics::pvData::pvDouble,"timeStamp,alarm"");
ExampleCounterPtr pvRecord(
new ExampleCounter(recordName,pvStructure));
if(!pvRecord-&gt;init()) pvRecord.reset();
return pvRecord;
}
</pre>
This:
<ul>
<li>Creates the top level structure.</li>
<li>Creates a ExampleCounterPtr 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>
ExampleCounter::ExampleCounter(
epics::pvData::String const &amp; recordName,
epics::pvData::PVStructurePtr const &amp; pvStructure)
: PVRecord(recordName,pvStructure)
{
pvTimeStamp.attach(pvStructure->getSubField("timeStamp"));
}
</pre>
The example is very simple. Note that it calls the base class constructor.
<p>The destructor and destroy methods are:</p>
<pre>
ExampleCounter::~ExampleCounter()
{
}
void ExampleCounter::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 ExampleCounter::init()
{
initPVRecord();
epics::pvData::PVFieldPtr pvField;
pvValue = getPVStructure()-&gt;getLongField("value");
if(pvValue==NULL) return false;
return true;
}
</pre>
This:
<ul>
<li>Calls initRecord which is implemented by the base class.
It MUST be called.</li>
<li>Calls getLongField to get the interface to the value field,
which must be a scalar with type long.</li>
<li>If a long value field was not found it returns false.</li>
<li>Returns true</li>
</ul>
<p>The implementation of process is:</p>
<pre>
void ExampleCounter::process()
{
pvValue-&gt;put(pvValue-&gt;get() + 1.0);
timeStamp.getCurrent();
pvTimeStamp.set(timeStamp);
}
</pre>
It adds 1.0 to the current value.
It then sets the timeStamp to the current time.
<h4>exampleCounterMain.cpp</h4>
<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("exampleCounter");
PVRecordPtr pvRecord = ExampleCounter::create(recordName);
bool result = master-&gt;addRecord(pvRecord);
cout &lt;&lt; "result of addRecord " &lt;&lt; recordName &lt;&lt; " " &lt;&lt; result &lt;&lt; endl;
pvRecord.reset();
startPVAServer(PVACCESS_ALL_PROVIDERS,0,true,true);
cout &lt;&lt; "exampleCounter\n";
string str;
while(true) {
cout &lt;&lt; "Type exit to stop: \n";
getline(cin,str);
if(str.compare("exit")==0) break;
}
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 a ExampleCounter record with the name exampleCounter
</li>
<li>Prints exampleCounter on standard out.</li>
<li>Runs forever until the user types exit on standard in.</li>
</ul>
<h4>V3IOC exampleCounter</h4>
<p>This has two subdirectories:
<dl>
<dt>Db</dt>
<dd>This has a template for a single v3Record.</dd>
<dt>src</dt>
<dd>This has code to allow exampleCounter to reside in a V3IOC.</dd>
</dl>
</p>
<p>The src directory has the following components:</p>
<dl>
<dt>exampleCounterMain.cpp</dt>
<dd>This is just a standard Main for a V3IOC.</dd>
<dt>exampleCounterInclude.dbd</dt>
<dd>This is:
<pre>
include "base.dbd"
include "PVAServerRegister.dbd"
registrar("exampleCounterRegister")
</pre>
This includes the dbd components required from base,
the dbd file for starting pvAccess and the local channelProvider,
and the dbd file for the example. The later is for the code from the
next file.
</dd>
<dt>exampleCounter.cpp</dt>
<dd>This is the code that registers a command the can be issued
via the iocsh, which is the console for a V3IOC.
The example supports a single command:
<pre>
exampleCounterCreateRecord recordName
</pre>
</dd>
</dl>
<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>pvCopy</dt>
<dd>Creates a PVStructure that contains a copy of an arbitary
subset of the fields of another top level PVStructure.
It can copy data between the two and maintains a bitSet that show
which fields are changed.<dd>
<dt>monitor</dt>
<dd>This provides the ability to monitor changes to fields of a record.</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 starting to implement 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>iocshell commands</h2>
<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>
<dt>pvdbl</dt>
<dd>Provides a list of all the pvRecords in database <b>master</b>
It is similar to the iocsh command <b>dbl</b></dd>
</dl>
<p>The client commands are provided via PVAClientRegister.dbd and the other commands
via PVAServerRegister.dbd.</p>
<p>In addition any code that implements a PVRecord must implement an ioc command.
The directory example has examples of how to implement the registration code.
See example/V3IOC/exampleCounter/src/ for a simple example.</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>powerSupplyRecordTest.h</dt>
<dd>
This provides code that simulates a power supply.
It computes the current from the voltage and power.
It is used for testing.
The complete implementation is provided in the header file.
Thus code will be generated only if other code includes the
header file and creates a record instance.
</dd>
<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 testExampleServer creates an instance via the following code:
<pre>
recordName = "laptoprecordListPGRPC";
pvRecord = RecordListRecord::create(recordName);
result = master-&gt;addRecord(pvRecord);
</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&lt;PVRecord&gt; PVRecordPtr;
typedef std::map&lt;epics::pvData::String,PVRecordPtr&gt; PVRecordMap;
class PVRecordField;
typedef std::tr1::shared_ptr&lt;PVRecordField&gt; PVRecordFieldPtr;
typedef std::vector&lt;PVRecordFieldPtr&gt; PVRecordFieldPtrArray;
typedef std::tr1::shared_ptr&lt;PVRecordFieldPtrArray&gt; PVRecordFieldPtrArrayPtr;
class PVRecordStructure;
typedef std::tr1::shared_ptr&lt;PVRecordStructure&gt; PVRecordStructurePtr;
class PVRecordClient;
typedef std::tr1::shared_ptr&lt;PVRecordClient&gt; PVRecordClientPtr;
class PVListener;
typedef std::tr1::shared_ptr&lt;PVListener&gt; PVListenerPtr;
class RecordPutRequester;
typedef std::tr1::shared_ptr&lt;RecordPutRequester&gt; RecordPutRequesterPtr;
class PVDatabase;
typedef std::tr1::shared_ptr&lt;PVDatabase&gt; 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&lt;PVRecord&gt;
{
public:
POINTER_DEFINITIONS(PVRecord);
virtual bool init() {initPVRecord(); return true;}
virtual void process() {}
virtual void destroy();
static PVRecordPtr create(
epics::pvData::String const &amp; recordName,
epics::pvData::PVStructurePtr const &amp; pvStructure);
virtual ~PVRecord();
epics::pvData::String getRecordName();
PVRecordStructurePtr getPVRecordStructure();
PVRecordFieldPtr findPVRecordField(
epics::pvData::PVFieldPtr const &amp; pvField);
bool addRequester(epics::pvData::RequesterPtr const &amp; requester);
bool removeRequester(epics::pvData::RequesterPtr const &amp; requester);
inline void lock_guard() { epics::pvData::Lock theLock(mutex); }
void lock();
void unlock();
bool tryLock();
void lockOtherRecord(PVRecordPtr const &amp; otherRecord);
bool addPVRecordClient(PVRecordClientPtr const &amp; pvRecordClient);
bool removePVRecordClient(PVRecordClientPtr const &amp; pvRecordClient);
void detachClients();
bool addListener(PVListenerPtr const &amp; pvListener);
bool removeListener(PVListenerPtr const &amp; pvListener);
void beginGroupPut();
void endGroupPut();
epics::pvData::String getRequesterName() {return getRecordName();}
virtual void message(
epics::pvData::String const &amp; message,
epics::pvData::MessageType messageType);
void message(
PVRecordFieldPtr const &amp; pvRecordField,
epics::pvData::String const &amp; message,
epics::pvData::MessageType messageType);
void toString(epics::pvData::StringBuilder buf);
void toString(epics::pvData::StringBuilder buf,int indentLevel);
int getTraceLevel();
void setTraceLevel(int level);
protected:
PVRecord(
epics::pvData::String const &amp; recordName,
epics::pvData::PVStructurePtr const &amp; 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>process</dt>
<dd>Virtual method.
Derived classes must implement this method.
The base implementation does nothing.
</dd>
<dt>destroy</dt>
<dd>This is a virtual method.
A derived class 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>addRequester</dt>
<dd>Add a requester to receive messages.</dd>
<dt>removeRequester</dt>
<dd>Remove a message requester.</dd>
<dt>lock_guard</dt>
<dd>This is an inline method that locks the record. The record will automatically
be unlocked when control leaves the block that has the call.
<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>getRequesterName</dt>
<dd>virtual method of Requester
</dd>
<dt>message</dt>
<dd>Can be called by implementation code.
The message will be sent to every requester.</dd>
<dt>toString</dt>
<dd>Just calls the top level PVStructure toString method.</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&lt;PVRecordField&gt;
public:
POINTER_DEFINITIONS(PVRecordField);
PVRecordField(
epics::pvData::PVFieldPtr const &amp; pvField,
PVRecordStructurePtr const &amp;parent,
PVRecordPtr const &amp; pvRecord);
virtual ~PVRecordField();
virtual void destroy();
PVRecordStructurePtr getParent();
epics::pvData::PVFieldPtr getPVField();
epics::pvData::String getFullFieldName();
epics::pvData::String getFullName();
PVRecordPtr getPVRecord();
bool addListener(PVListenerPtr const &amp; pvListener);
virtual void removeListener(PVListenerPtr const &amp; pvListener);
virtual void postPut();
virtual void message(
epics::pvData::String const &amp; message,
epics::pvData::MessageType messageType);
protected:
PVRecordFieldPtr getPtrSelf()
{
return shared_from_this();
}
virtual void init();
virtual void postParent(PVRecordFieldPtr const &amp; 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>
<dt>message</dt>
<dd>Called by implementation code. It calls PVRecord::message after prepending the full
fieldname.</dd>
</dl>
<h3>class PVRecordStructure</h3>
<pre>
class PVRecordStructure : public PVRecordField {
public:
POINTER_DEFINITIONS(PVRecordStructure);
PVRecordStructure(
epics::pvData::PVStructurePtr const &amp; pvStructure,
PVRecordFieldPtrArrayPtr const &amp; pvRecordField);
virtual ~PVRecordStructure();
virtual void destroy();
PVRecordFieldPtrArrayPtr getPVRecordFields();
epics::pvData::PVStructurePtr getPVStructure();
virtual void removeListener(PVListenerPtr const &amp; 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 &amp; 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 &amp; pvRecordField) = 0;
virtual void dataPut(
PVRecordStructurePtr const &amp;
requested,PVRecordFieldPtr const &amp; pvRecordField) = 0;
virtual void beginGroupPut(PVRecordPtr const &amp; pvRecord) = 0;
virtual void endGroupPut(PVRecordPtr const &amp; pvRecord) = 0;
virtual void unlisten(PVRecordPtr const &amp; pvRecord);
};
</pre>
<p>where</p>
<dl>
<dt>~PVListener</dt>
<dd>The destructor.</dd>
<dt>dataPut(PVRecordFieldPtr const &amp; pvRecordField)</dt>
<dd>pvField has been modified.
This is called if the listener has called PVRecordField::addListener for pvRecordField.</dd>
<dt>dataPut(
PVRecordStructurePtr const &amp;
requested,PVRecordFieldPtr const &amp; pvRecordField)</dt>
<dd>pvField has been modified.
Requested is the field to which the requester issued a pvField-&amp;addListener.
This is called if the listener has called PVRecordField-&amp;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(epics::pvData::String const&amp; recordName);
bool addRecord(PVRecordPtr const &amp; record);
epics::pvData::PVStringArrayPtr getRecordNames();
bool removeRecord(PVRecordPtr const &amp; record);
virtual epics::pvData::String getRequesterName();
virtual void message(
epics::pvData::String const &amp;message,
epics::pvData::MessageType messageType);
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>
<dt>message</dt>
<dd>Virtual message 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 access by the server side of remote pvAccess.</p>
<h3>channelProviderLocal</h3>
<p>This is a complete implementation of channelProvider and ,
except for channelRPC, provides a complete implementation of Channel
as defined by pvAccess.
For monitors it calls the code described in the following sections.</p>
<h3>pvCopy</h3>
<p>This provides code that creates a top level PVStructure that is an arbitrary
subset of the fields in the PVStructure from a PVRecord.
In addition it provides code that monitors changes to the fields in a PVRecord.
A client configures the desired set of subfields and monitoring options
via a pvRequest structure.
pvAccess provides a class CreatePVRequest that creates a pvRequest.
The pvCopy code provides the same functionality as the pvCopy code in pvIOCJava.
</p>
<h3>monitorAlgorithm</h3>
<p>Currently all that is implemented is a header file.
The only algorithm currently implemented is <b>onPut</b>
</p>
<h3>monitorFactory</h3>
<h4>Overview</h4>
<p><b>epics::pvData::monitor</b> defines the monitor interfaces
as seen by a client.
See
<a href="http://epics-pvdata.sourceforge.net/docbuild/pvDatabaseCPP/tip/documentation/pvDatabaseCPP.html">pvDatabaseCPP.html</a>
For details.</p>
<p>
monitorFactory implements the
monitoring interfaces for a PVRecord.
It implements queueSize=0 and queueSize&gt;=2.
</p>
<p>
The implementation uses PVCopy and PVCopyMonitor which are implemented in pvCopy.
When PVCopyMonitor tells monitor that changes
have occurred, monitor applies the appropriate algorithm to each changed field.</p>
<p>Currently only algorithm <b>onPut</b> is implemented but,
like pvIOCJava there are plans to support for the following monitor algorithms:</p>
<dl>
<dt>onPut</dt>
<dd>A monitor is issued whenever a put is issued to the field. This is the
default unless the record defines deadbands for a field. An exception is
the top level timeStamp which by default is made onChange and monitor
will not be raised.</dd>
<dt>onChange</dt>
<dd>This provides two options: 1) A monitor is raised whenever a field
changes value, and 2) A monitor will never be raised for the field.</dd>
<dt>deadband</dt>
<dd>The field must be a numeric scalar. Whenever the absolute or percentage
value of the field changes by more than a deadband a monitor is issued.
The record instance can also define deadbands.</dd>
<dt>periodic</dt>
<dd>A monitor is issued at a periodic rate if a put was issued to any field
being monitored.</dd>
</dl>
<h4>MonitorFactory</h4>
<p>MonitorFactory provides the following methods:</p>
<pre>class MonitorFactory
{
static MonitorPtr create(
PVRecordPtr const &amp; pvRecord,
MonitorRequester::shared_pointer const &amp; monitorRequester,
PVStructurePtr const &amp; pvRequest);
static void registerMonitorAlgorithmCreater(
MonitorAlgorithmCreatePtr const &amp; monitorAlgorithmCreate,
String const &amp; algorithmName);
}</pre>
<p>where</p>
<dl>
<dt>create</dt>
<dd>Create a monitor. The arguments are:
<dl>
<dt>pvRecord</dt>
<dd>The record being monitored.</dd>
<dt>monitorRequester</dt>
<dd>The monitor requester. This is the code to which monitot events
will be delivered.</dd>
<dt>pvRequest</dt>
<dd>The request options</dd>
</dl>
</dd>
<dt>registerMonitorAlgorithmCreater</dt>
<dd>Called by code that implements a monitor algorithm.</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 arguments
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>&gt;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-&gt;addRecord(pvRecord);
if(!result) cout&lt;&lt; "record " &lt;&lt; recordName &lt;&lt; " not added" &lt;&lt; endl;
</pre>
</p>
<h3>recordList</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 arguments
string database master
string regularExpression .*
structure result
string status
string[] names
</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>names</dt>
<dd>The list of record names.</dd>
</dl>
<p>Note that swtshell 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 = "laptoprecordListPGRPC";
pvRecord = RecordListRecord::create(recordName);
result = master-&gt;addRecord(pvRecord);
if(!result) cout&lt;&lt; "record " &lt;&lt; recordName &lt;&lt; " not added" &lt;&lt; endl;
</pre>
<h3>powerSupplyRecordTest</h3>
<p>This simulates a simple power supply record.
It is used for testing.</p>
<h2>Accessing Other PVRecords</h2>
<p>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
caused 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 lockimg 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();
ChannelAccess::shared_pointer channelAccess = getChannelAccess();
ChannelProvider::shared_pointer provider = channelAccess-&gt;getProvider(providerName);
Channel::shared_pointer channel = provider-&gt;createChannel(channelName,channelRequester);
</pre>
<h3>examplePVADoubleArrayGet</h3>
examplePVADoubleArrayGet shows how to use pvAccess to get data.</p>
<p>The code resides in three directories:</p>
<dl>
<dt>example/src/examplePVADoubleArrayGet</dt>
<dd>This is the implementation code for the example.</dd>
<dt>example/examplePVADoubleArrayGet</dt>
<dd>This is the code that starts the example as a main program.</dd>
<dt>example/V3IOC/examplePVADoubleArrayGet/src</dt>
<dd>This is the code for starting the example as part of a V3IOC.</dd>
<dt>iocBoot/examplePVADoubleArrayGet</dt>
<dd>This has the st.cmd files to start the example.</dd>
</dl>
<h4>examplePVADoubleArrayGet Implementation</h4>
<p>examplePVADoubleArrayGet.h contains the following:</p>
<pre>
...
class ExamplePVADoubleArrayGet :
public PVRecord,
public epics::pvAccess::ChannelRequester,
public epics::pvAccess::ChannelGetRequester
{
public:
POINTER_DEFINITIONS(ExamplePVADoubleArrayGet);
static ExamplePVADoubleArrayGetPtr create(
epics::pvData::String const &amp; recordName,
epics::pvData::String const &amp; providerName,
epics::pvData::String const &amp; channelName
);
virtual ~ExamplePVADoubleArrayGet() {}
virtual void destroy();
virtual bool init();
virtual void process();
virtual void channelCreated(
const epics::pvData::Status&amp; status,
epics::pvAccess::Channel::shared_pointer const &amp; channel);
virtual void channelStateChange(
epics::pvAccess::Channel::shared_pointer const &amp; channel,
epics::pvAccess::Channel::ConnectionState connectionState);
virtual void channelGetConnect(
const epics::pvData::Status&amp; status,
epics::pvAccess::ChannelGet::shared_pointer const &amp; channelGet,
epics::pvData::PVStructure::shared_pointer const &amp; pvStructure,
epics::pvData::BitSet::shared_pointer const &amp; bitSet);
virtual void getDone(const epics::pvData::Status&amp; 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>
ExamplePVADoubleArrayGetPtr ExamplePVADoubleArrayGet::create(
String const &amp; recordName,
String const &amp; providerName,
String const &amp; channelName)
{
PVStructurePtr pvStructure = getStandardPVField()-&gt;scalarArray(
pvDouble,"alarm.timeStamp");
ExamplePVADoubleArrayGetPtr pvRecord(
new ExamplePVADoubleArrayGet(
recordName,providerName,channelName,pvStructure));
if(!pvRecord-&gt;init()) pvRecord.reset();
return pvRecord;
}
</pre>
<p>This first creates a new ExamplePVADoubleArrayGet instance,
and then calls the init method and the returns a ExamplePVADoubleArrayGetPtr.
Note that if init returns false it returns a pointer to NULL.</p>
<p>The init method is:</p>
<pre>
bool ExamplePVADoubleArrayGet::init()
{
initPVRecord();
PVStructurePtr pvStructure = getPVRecordStructure()-&gt;getPVStructure();
pvTimeStamp.attach(pvStructure-&gt;getSubField("timeStamp"));
pvAlarm.attach(pvStructure-&gt;getSubField("alarm"));
pvValue = static_pointer_cast&lt;PVDoubleArray&gt;(
pvStructure-&gt;getScalarArrayField("value",pvDouble));
if(pvValue==NULL) {
return false;
}
ChannelAccess::shared_pointer channelAccess = getChannelAccess();
ChannelProvider::shared_pointer provider =
channelAccess-&gt;getProvider(providerName);
if(provider==NULL) {
cout &lt;&lt; getRecordName() &lt;&lt; " provider "
&lt;&lt; providerName &lt;&lt; " does not exist" &lt;&lt; endl;
return false;
}
ChannelRequester::shared_pointer channelRequester =
dynamic_pointer_cast&lt;ChannelRequester&gt;(getPtrSelf());
channel = provider-&gt;createChannel(channelName,channelRequester);
event.wait();
if(!status.isOK()) {
cout &lt;&lt; getRecordName() &lt;&lt; " createChannel failed "
&lt;&lt; status.getMessage() &lt;&lt; endl;
return false;
}
ChannelGetRequester::shared_pointer channelGetRequester =
dynamic_pointer_cast&lt;ChannelGetRequester&gt;(getPtrSelf());
PVStructurePtr pvRequest = getCreateRequest()-&gt;createRequest(
"value,alarm,timeStamp",getPtrSelf());
channelGet = channel-&gt;createChannelGet(channelGetRequester,pvRequest);
event.wait();
if(!status.isOK()) {
cout &lt;&lt; getRecordName() &lt;&lt; " createChannelGet failed "
&lt;&lt; status.getMessage() &lt;&lt; endl;
return false;
}
getPVValue = static_pointer_cast&lt;PVDoubleArray&gt;(
getPVStructure-&gt;getScalarArrayField("value",pvDouble));
if(getPVValue==NULL) {
cout &lt;&lt; getRecordName() &lt;&lt; " get value not PVDoubleArray" &lt;&lt; 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>
<h4>Starting the example as a main program</h4>
<pre>
...
int main(int argc,char *argv[])
{
PVDatabasePtr master = PVDatabase::getMaster();
ClientFactory::start();
ChannelProviderLocalPtr channelProvider = getChannelProviderLocal();
PVRecordPtr pvRecord;
bool result(false);
String recordName;
PVStructurePtr pvStructure = standardPVField-&gt;scalarArray(
pvDouble,"alarm,timeStamp");
recordName = "doubleArray";
pvRecord = PVRecord::create(recordName,pvStructure);
result = master-&gt;addRecord(pvRecord);
if(!result) cout&lt;&lt; "record " &lt;&lt; recordName &lt;&lt; " not added" &lt;&lt; endl;
ServerContext::shared_pointer serverContext = startPVAServer(PVACCESS_ALL_PROVIDERS,0,true,true);
recordName = "examplePVADoubleArrayGet";
if(argc&gt;1) recordName = argv[1];
String providerName("local");
if(argc&gt;2) providerName = argv[2];
String channelName("doubleArray");
if(argc&gt;3) channelName = argv[3];
pvRecord = ExamplePVADoubleArrayGet::create(
recordName,providerName,channelName);
if(pvRecord!=NULL) {
result = master-&gt;addRecord(pvRecord);
cout &lt;&lt; "result of addRecord " &lt;&lt; recordName &lt;&lt; " " &lt;&lt; result &lt;&lt; endl;
} else {
cout &lt;&lt; "ExamplePVADoubleArrayGet::create failed" &lt;&lt; endl;
}
string str;
while(true) {
cout &lt;&lt; "Type exit to stop: \n";
getline(cin,str);
if(str.compare("exit")==0) break;
}
serverContext-&gt;shutdown();
epicsThreadSleep(1.0);
serverContext-&gt;destroy();
ClientFactory::stop();
channelProvider-&gt;destroy();
return 0;
}
</pre>
<p>The first statements initializes the client factory and localChannelProvider.</p>
<p>
It then creates a PVRecord that has a double array as the value field.
The name of the record is <b>doubleArray</b>.
This is the record that can be accessed via pvAccess if the channelName is
<b>doubleArray</b>.
</p>
<p>It then creates a examplePVADoubleArrayGet instance.</p>
<p>It the runs forever until the exit is typed.</p>
<h4>Start the example as part of a V3IOC</h4>
The directory example/V3IOC/examplePVADoubleArrayGet has two subdirectories: Db and src.
These are like for any other V3IOC application.
The Db directory is for a regular ai record.
The src directory has code similar to any V3IOC application.
In particular it has definitions and code for creating the following iocsh commands:</p>
<pre>
examplePVADoubleArrayGetRegister recordName providerName channelName
</pre>
where
<dl>
<dt>recordName</dt>
<dt>providerName</dt>
<dt>channelName</dt>
</dl>
<p>The directory iocBoot/examplePVADoubleArrayGet has two st.cmd files:</p>
<dl>
<dt>st.local</dt>
<dd>This creates the example using localChannelProvider.</dd>
<dt>st.remote</dt>
<dd>This creates the example using the remote channel provider.</dd>
</dl>
<h2>Array Performance</h2>
<p>Two main programs are available for testing the performance of large
arrays: arrayPerformanceMain and longArrayMonitorMain.
Each has support for <b>-help</b>.</p>
<pre>
mrk&gt; pwd
/home/hg/pvDatabaseCPP-md
mrk&gt; bin/linux-x86_64/arrayPerformanceMain -help
arrayPerformanceMain recordName size delay providerName nMonitor useQueue
default
arrayPerformance arrayPerformance 50000000 0.001 local 1 false
mrk&gt; bin/linux-x86_64/longArrayMonitorMain -help
longArrayMonitorMain channelName useQueue
default
longArrayMonitorMain arrayPerformance false
mrk&gt;
</pre>
<h3>Example output</h3>
<pre>
mrk&gt; bin/linux-x86_64/arrayPerformanceMain
arrayPerformance arrayPerformance 50000000 0.01 local 1 false
...
first 0 last 0 sum 0 elements/sec 525.011million changed {1, 2} overrun {}
first 1 last 1 sum 50000000 elements/sec 494.511million changed {1, 2} overrun {}
first 2 last 2 sum 100000000 elements/sec 515.34million changed {1, 2} overrun {}
first 3 last 3 sum 150000000 elements/sec 154.402million changed {1, 2} overrun {}
first 4 last 4 sum 200000000 elements/sec 513.414million changed {1, 2} overrun {}
first 5 last 5 sum 250000000 elements/sec 473.672million changed {1, 2} overrun {}
first 6 last 6 sum 300000000 elements/sec 503.855million changed {1, 2} overrun {}
arrayPerformance value 8 time 1.66373 iterations/sec 4.80847 elements/sec 240.424million
...
</pre>
<h3>arrayPerformance</h3>
<p>
This 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>At least 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>
<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>useQueue</dt>
<dd>Should the longArrayMonitors use a queue?
This must be <b>true</b> or <b>false</b>.
</dd>
</dl>
<h3>longArrayMonitor</h3>
<p>This is a pvAccess client that monitors an arrayPerformance record.
It generates a report for each monitor event.
In the example output shown above each line starting with <b>first</b>
is a report.</p>
<p>The arguments for longArrayMonitorMain are:</p>
<dl>
<dt>channelName</dt>
<dt>useQueue</dt>
</dl>
<h3>Some results</h3>
<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&gt; pwd
/home/hg/pvDatabaseCPP-md
mrk&gt; bin/linux-x86_64/arrayPerformanceMain
</pre>
<p>This means that the array will hold 50 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 8 iterations per second
and is putting about 350million elements per second.
Since each element is an int64 this means about 2.8gigaBytes per second.
</p>
<p>When no monitors are requested and a remote longArrayMonitorMain is run:<p>
<pre>
mr&gt; pwd
/home/hg/pvDatabaseCPP-md
mrk&gt; bin/linux-x86_64/longArrayMonitorMain
</pre>
<p>The performance drops to about 90million elements per second.
In addition the time between reports varies from just over 1 second to 3 seconds.
I do not understand why.</p>
</div>
</body>
</html>