Files
pvDatabase/documentation/pvDatabaseCPP_20140811.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_20140811.html">pvDatabaseCPP_20140811.html
</a> </dd>
<dt>Previous version:</dt>
<dd><a
href= "pvDatabaseCPP_20140710.html">pvDatabaseCPP_20140710.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 accessible 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 extension 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&gt; pwd
/home/hg/pvDatabaseCPP/exampleServer
mrk&gt; 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&gt; pvput -r "field(argument.value)" exampleServer World
...
mrk&gt; pvget -r "record[process=true]field(result.value)" exampleServer
exampleServer
structure
string value Hello World
mrk&gt;
</pre>
<p>To run the example as part of a V3 IOC do the following:</p>
<pre>
mrk&gt; pwd
/home/hg/pvDatabaseCPP/exampleServer/iocBoot/exampleServer
mrk&gt; ../../bin/linux-x86_64/exampleServer st.cmd
</pre>
<p>You will see the following:</p>
<pre>
&gt; 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&gt;
</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 describes 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 efficiently 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>Minimum 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 minimum 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&gt; pwd
/home/hg/pvDatabaseCPP/configure
mrk&gt; 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&gt; cd ..
mrk&gt; pwd
/home/hg/pvDatabaseCPP
mrk&gt; 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&gt; pwd
/home/hg/pvDatabaseCPP/exampleServer/configure
mrk&gt; 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&gt; cd ..
mrk&gt; pwd
/home/hg/pvDatabaseCPP/exampleServer
mrk&gt; 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 &lt;&lt; "RecordListRecord::create failed" &lt;&lt; endl;
} else {
result = master->addRecord(pvRecord);
if(!result) cout&lt;&lt; "record " &lt;&lt; recordName &lt;&lt; " not added" &lt;&lt; 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 accesses 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 start() {}
virtual void process() {}
virtual void destroy();
static PVRecordPtr create(
std::string const &amp; recordName,
epics::pvData::PVStructurePtr const &amp; pvStructure);
virtual ~PVRecord();
std::string getRecordName();
PVRecordStructurePtr getPVRecordStructure();
PVRecordFieldPtr findPVRecordField(
epics::pvData::PVFieldPtr const &amp; pvField);
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();
int getTraceLevel();
void setTraceLevel(int level);
protected:
PVRecord(
std::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>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 false client 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&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();
std::string getFullFieldName();
std::string getFullName();
PVRecordPtr getPVRecord();
bool addListener(PVListenerPtr const &amp; pvListener);
virtual void removeListener(PVListenerPtr const &amp; pvListener);
virtual void postPut();
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>
</dl>
<h3>class PVRecordStructure</h3>
<pre>
class PVRecordStructure : public PVRecordField {
public:
POINTER_DEFINITIONS(PVRecordStructure);
PVRecordStructure(
epics::pvData::PVStructurePtr const &amp; pvStructure,
PVRecordStructurePtr const &amp; parent,
PVRecordPtr const &amp; pvRecord);
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(std::string const&amp; recordName);
bool addRecord(PVRecordPtr const &amp; record);
epics::pvData::PVStringArrayPtr getRecordNames();
bool removeRecord(PVRecordPtr const &amp; 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 (implemented 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-&gt;startMonitoring(activeElement)
<dd>
<dt>stop</dt>
<dd>
Called by client.
With no lock held calls pvCopyMonitor-&gt;stopMonitoring(activeElement)
</dd>
<dt>poll</dt>
<dd>
Called by client.
With a lock held it calls queue-&gt;getUsed();
</dd>
<dt>release</dt>
<dd>
Called by client.
With a lock held it calls queue-&gt;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-&gt;setUsed(activeElement);
It then sets the active element to the new free element.
With no lock held it calls monitorRequester-&gt;monitorEvent(getPtrSelf())
and finally returns the new active element,
</dd>
<dt>unlisten</dt>
<dd>
With no lock held it calls monitorRequester-&gt;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 passed by monitorLocal and calls pvRecord-&gt;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-&gt;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-&gt;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-&gt;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-&gt;releaseActiveElement()
</dd>
<dt>unlisten</dt>
<dd>
Just calls pvCopyMonitorRequester-&gt;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 generated
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
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 database. 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-&gt;addRecord(pvRecord);
if(!result) cout&lt;&lt; "record " &lt;&lt; recordName &lt;&lt; " not added" &lt;&lt; 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&gt; pwd
/home/hg/pvDatabaseCPP/exampleService
mrk&gt; 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&lt;ExampleServer&gt; ExampleServerPtr;
class ExampleServer :
public PVRecord
{
public:
POINTER_DEFINITIONS(ExampleServer);
static ExampleServerPtr create(
std::string const &amp; recordName);
virtual ~ExampleServer();
virtual void destroy();
virtual bool init();
virtual void process();
private:
ExampleServer(std::string const &amp; recordName,
epics::pvData::PVStructurePtr const &amp; 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 &amp; recordName)
{
StandardFieldPtr standardField = getStandardField();
FieldCreatePtr fieldCreate = getFieldCreate();
PVDataCreatePtr pvDataCreate = getPVDataCreate();
StructureConstPtr topStructure = fieldCreate-&gt;createFieldBuilder()-&gt;
addNestedStructure("argument")-&gt;
add("value",pvString)-&gt;
endNested()-&gt;
addNestedStructure("result") -&gt;
add("value",pvString) -&gt;
add("timeStamp",standardField-&gt;timeStamp()) -&gt;
endNested()-&gt;
createStructure();
PVStructurePtr pvStructure = pvDataCreate-&gt;createPVStructure(topStructure);
ExampleServerPtr pvRecord(
new ExampleServer(recordName,pvStructure));
if(!pvRecord-&gt;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 &amp; recordName,
epics::pvData::PVStructurePtr const &amp; 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()-&gt;getStringField("argument.value");
if(pvArgumentValue.get()==NULL) return false;
pvResultValue = getPVStructure()-&gt;getStringField("result.value");
if(pvResultValue.get()==NULL) return false;
pvTimeStamp.attach(getPVStructure()-&gt;getSubField("result.timeStamp"));
return true;
}
</pre>
<p>The implementation of process is:</p>
<pre>
void ExampleServer::process()
{
pvResultValue-&gt;put(String("Hello ") + pvArgumentValue-&gt;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-&gt;addRecord(pvRecord);
if(!result) cout&lt;&lt; "record " &lt;&lt; recordName &lt;&lt; " not added" &lt;&lt; endl;
recordName = "traceRecordPGRPC";
pvRecord = TraceRecord::create(recordName);
result = master-&gtr;addRecord(pvRecord);
if(!result) cout&lt;&lt; "record " &lt;&lt; recordName &lt;&lt; " not added" &lt;&lt; endl;
recordName = "recordListPGRPC";
pvRecord = RecordListRecord::create(recordName);
result = master-&gtr;addRecord(pvRecord);
if(!result) cout&lt;&lt; "record " &lt;&lt; recordName &lt;&lt; " not added" &lt;&lt; endl;
ServerContext::shared_pointer pvaServer =
startPVAServer(PVACCESS_ALL_PROVIDERS,0,true,true);
PVStringArrayPtr pvNames = master-&gtr;getRecordNames();
shared_vector&lt;const string&gtr; names = pvNames-&gtr;view();
for(size_t i=0; i&lt;names.size(); ++i) cout &lt;&lt; names[i] &lt;&lt; endl;
string str;
while(true) {
cout &lt;&lt; "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&gt; pwd
/home/hg/pvDatabaseCPP/exampleServer/iocBoot/exampleServer
mrk&gt; ../../../bin/linux-x86_64/exampleServer st.cmd
</pre></p>
<p>You can then issue the commands dbl and pvdbl:
<pre>
epics&gt; dbl
pvdouble
pvcounter
pvenum
pvdoubleArray
pvstringArray
epics&gt; pvdbl
exampleServer
epics&gt;
</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 transferred 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()-&gt;getProvider(providerName);
Channel::shared_pointer channel = provider-&gt;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&gt; pwd
/home/hg/pvDatabaseCPP/exampleLink/iocBoot/exampleLink
mrk&gt; ../../bin/linux-x86_64/exampleLink st.local
</pre>
<p>then in another window:</p>
<pre>
mrk&gt; pvput doubleArray 4 100 200 300 400
Old : doubleArray 0
New : doubleArray 4 100 200 300 400
mrk&gt; pvget -r "record[process=true]field(value)" exampleLink
exampleLink
structure
double[] value [100,200,300,400]
mrk&gt;
</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 &amp; recordName,
std::string const &amp; providerName,
std::string const &amp; channelName
);
virtual ~ExampleLink() {}
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>
ExampleLinkPtr ExampleLink::create(
String const &amp; recordName,
String const &amp; providerName,
String const &amp; channelName)
{
PVStructurePtr pvStructure = getStandardPVField()-&gt;scalarArray(
pvDouble,"alarm.timeStamp");
ExampleLinkPtr pvRecord(
new ExampleLink(
recordName,providerName,channelName,pvStructure));
if(!pvRecord-&gt;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()-&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>
<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 monitor 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&gt; pwd
/home/hg/pvDatabaseCPP-md
mrk&gt; 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&gt; bin/linux-x86_64/longArrayMonitorMain -help
longArrayMonitorMain channelName queueSize waitTime
default
longArrayMonitorMain arrayPerformance 2 0.0
mrk&gt; bin/linux-x86_64/longArrayGetMain -help
longArrayGetMain channelName iterBetweenCreateChannel iterBetweenCreateChannelGet delayTime
default
longArrayGetMain arrayPerformance 0 0 1
mrk&gt; bin/linux-x86_64/longArrayPutMain -help
longArrayPutMain channelName arraySize iterBetweenCreateChannel iterBetweenCreateChannelPut delayTime
default
longArrayPutMain arrayPerformance 10 0 0 1
mrk&gt;
</pre>
<h3>Example output</h3>
<p><b>Note:</b> These may fail if run on a platform that does not have sufficient memory,</p>
<p>To see an example just execute the following commands in four different terminal windows:</p>
<pre>
bin/linux/&lt;arch&gt;/arrayPerformanceMain
bin/linux/&lt;arch&gt;/longArrayMonitorMain
bin/linux/&lt;arch&gt;/longArrayGetMain
bin/linux/&lt;arch&gt;/longArrayPutMain
</pre>
<p>Each program generates a report every second when it has something to report.
Examples are:
<pre>
mrk&gt; 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&gt; 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&gt; 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&gt; 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 iteration 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 elements is &gt;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 &lt;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 delay 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 delay 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&gt; pwd
/home/hg/pvDatabaseCPP-md
mrk&gt; 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&gt; pwd
/home/hg/pvDatabaseCPP-md
mrk&gt; bin/linux-x86_64/longArrayMonitorMain
</pre>
<p>The performance drops to about 25 iterations 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>