Optimizing your PI SDK Applications - PROYTEK, S.A. - Offline Process Book Programming/vCampus...

29
Optimizing your PI SDK Applications OSIsoft vCampus White Paper

Transcript of Optimizing your PI SDK Applications - PROYTEK, S.A. - Offline Process Book Programming/vCampus...

Optimizing your PI SDK

Applications

OSIsoft vCampus White Paper

How to Contact Us

Email: [email protected]

Web: http://vCampus.osisoft.com > Contact Us

OSIsoft, LLC

777 Davis St., Suite 250

San Leandro, CA 94577 USA

Houston, TX

Johnson City, TN

Mayfield Heights, OH

Phoenix, AZ

Savannah, GA

Seattle, WA

Yardley, PA

Worldwide Offices

OSIsoft Australia Pty Ltd.

Perth, Australia

Auckland, New Zealand

OSIsoft Europe GmbH

Frankfurt am Main, Germany

OSI Software Asia Pte Ltd.

Singapore

OSIsoft Canada ULC

Montreal, Quebec

Calgary, Alberta

OSIsoft, LLC. Shanghai

Shanghai, People’s Republic of China

OSIsoft Japan KK

Tokyo, Japan

OSIsoft Mexico S. De R.L. de C.V.

Mexico City, Mexico

Sales Outlets and Distributors

Brazil

Middle East/North Africa

Republic of South Africa

Russia/Central Asia

South America/Caribbean

Southeast Asia

South Korea

Taiwan

WWW.OSISOFT.COM

OSIsoft, LLC is the owner of the following trademarks and registered trademarks: PI System, PI ProcessBook, Sequencia, Sigmafine, gRecipe, sRecipe, and RLINK. All terms mentioned in this book that are known to be trademarks or service marks have been appropriately capitalized. Any trademark that appears in this book that is not owned by OSIsoft, LLC is the property of its owner and use herein in no way indicates an endorsement, recommendation, or warranty of such party’s products or any affiliation with such party of any kind.

RESTRICTED RIGHTS LEGEND Use, duplication, or disclosure by the Government is subject to restrictions as set forth in subparagraph (c)(1)(ii) of the Rights in Technical Data and Computer Software clause at DFARS 252.227-7013

Unpublished – rights reserved under the copyright laws of the United States.

© 1998-2011 OSIsoft, LLC

ii

TABLE OF CONTENTS

Table of Contents ............................................................................................................................... ii

Overview ........................................................................................................................................... 1

About this Document ............................................................................................................................. 1

What You Need to Start ......................................................................................................................... 1

The OSIsoft SDKs ................................................................................................................................ 2

What are the OSIsoft SDKs? ................................................................................................................... 2

What OSIsoft SDKs are available? .......................................................................................................... 2

Other products and Data Access technologies....................................................................................... 2

Programming with the PI SDK ............................................................................................................. 3

Optimization Techniques ........................................................................................................................ 5

Connection Pooling (ServerManager) .............................................................................................. 5

Asynchronous Calls .......................................................................................................................... 8

ListData Functions .......................................................................................................................... 14

Event Pipes ..................................................................................................................................... 14

Multithreading Applications .......................................................................................................... 15

Multi-Threaded NamedValue (MTNV) and Multi-Threaded NamedValues (MTNVS ) .................. 18

IPIValues2.GetValueArrays ............................................................................................................ 20

Conclusion ....................................................................................................................................... 23

Resources ........................................................................................................................................ 24

Revisions ......................................................................................................................................... 25

1

OVERVIEW

ABOUT THIS DOCUMENT

This document is exclusive to the OSIsoft Virtual Campus (vCampus) and is available on its

online Library, located at http://vCampus.osisoft.com/Library/library.aspx. As such, it is

provided 'as is' and is not supported by OSIsoft's regular Technical Support.

Any question or comment related to this document should be posted in the appropriate

vCampus discussion forum (http://vCampus.osisoft.com/forums) or sent to the vCampus

Team at [email protected].

About this White Paper

This document contains a loose collection of various examples that can prove useful to

optimize custom PI SDK applications for high performance and scalability.

WHAT YOU NEED TO START

Visual Studio 2008 or higher

o All samples in the paper are written in C#

PI SDK 2010 R2 or higher

PI Server 2010 or higher

2

THE OSISOFT SDKS

WHAT ARE THE OSISOFT SDKS?

The OSIsoft SDKs are libraries and shared components that allow developers to utilize our

proprietary technology in their applications, and allow them to create customized

interfaces, add user interactions, new functionalities to existing products, expose data in

new formats, perform advanced calculations, generate analysis, export reports, etc.

WHAT OSISOFT SDKS ARE AVAILABLE?

Being a member of the OSIsoft vCampus program, you have the development license of our

various SDKs, each of these pertaining to a specific area of the PI System Architecture. The

following SDKs are available:

PI SDK – PI Server

o PI Points Attributes and Data

o PI Server Message Log

o Various PI Databases (Users & Groups, etc.)

AF SDK – AF Server and PI Notifications

o AF Databases, Elements, Attributes

o AF Analysis Rules (ARs) and Data References (DRs)

o AF Connectivity Models and Cases

o Notification Configuration (including custom Delivery Channels)

o Notification instances

o Event Frames

OTHER PRODUCTS AND DATA ACCESS TECHNOLOGIES

There is a plethora of other Data Access technologies and programming tools provided by OSIsoft

which may fit your needs, please consider using some of the following:

Data Access Technologies

o PI Web Services

o PI OLEDB Provider

o PI OLEDB Enterprise

o PI OPC DA/HDA Server

o PI JDBC Driver

Other Products

o PI DataLink & PI DataLink Server

o PI Interfaces

o PI ACE (Advanced Computing Engine)

o PI for StreamInsight

o PI ProcessBook & Visual Basic for Applications

3

PROGRAMMING WITH THE PI SDK

There are four .NET assemblies (installed in the GAC – Global Assembly Cache) that can to be added as reference to all PI SDK .NET applications:

OSIsoft.PISDK

OSIsoft.PISDKCommon

OSIsoft.PISDKDlg (Common dialog windows such as TagSearch, Connections, etc.)

OSISoft.PITimeServer (PI Time Functions and conversions)

You can find those either in the .NET tab in VS2008 or your Recent tab in VS2010, if you can’t find them in those tabs you can go to the Browse tab and navigate your way to the PIPC\PISDK and PIPC\LIBRARY folders.

.NET BASED PISDK CONTROLS LIBRARY

PI SDK has a list of .NET based controls and dialogs (part of OSISoft.PISDK.Controls.dll) that you can deploy directly in .NET form applications, including:

Connection Manager control (ConnectionManager)

Server Pick List control (PIServerPickList)

Snapshot control (PISnapShotCtl)

Archive Editor control (PIArchiveEditor)

PI SDK Buffering configuration dialog (BufferingSetupForm)

These controls and dialogs can be inserted into your form applications directly. To add the controls into the toolbox on Visual Studio, you can right-click on the toolbox and select Choose Item

4

From there a dialog window will appear for you to choose the controls to include in the toolbox. If you look for the OSIsoft.PISDK.Controls under Assembly Name, you should find these controls

As for the dialogs like PI SDK Buffering Configuration dialog, you can instantiate the control and use ShowDialog method to bring up the dialog box

OSIsoft.PISDKControls.BufferingSetupForm bsf = new

OSIsoft.PISDKControls.BufferingSetupForm();

DialogResult dr = bsf.ShowDialog();

5

OPTIMIZATION TECHNIQUES

CONNECTION POOLING (SERVERMANAGER)

Characteristics

Single sign on inherits caller’s Windows identity

Supports PITrust and PIIdentity (Interactive Login not supported)

Designed for web-based applications with multiple users and multiple PI Servers

Description

The ServerManager is a collection object whose purpose is to provide a reusable pool of open server

connections to PI Servers version 3.3 (and higher). A good use case for this is web-based

applications (such as PI WebParts), which can provide improved performance while maintaining

security by creating a single ServerManager as an application level object. The ServerManager can

use either the PITrust table, maintained on the PI Server, to validate connected domain users or the

preferred windows authentication and provides open connections for validated users. The

ServerManager does not support explicit logins.

Typical PI SDK client/server applications maintain a small number of connections over the execution

of the application, reaping the benefits of cached data. In a "connectionless" Web environment,

reestablishing a PI SDK hierarchy with each client access is prohibitively slow. The ServerManager

solves this problem by storing open Server objects and providing secure access to them as required.

In the past, applications were able to manage the data cached by the PI SDK using the Refresh

method in the IRefresh interface on various members of the hierarchy, this can be done for Server,

PIUsers, PIPoints, PIProperties, PointAttributes, amongst others. In addition, the ServerManager can

be directed to delete connections that are not currently in use to manage memory and network

resources via the SlimFast method.

Example

This example is an active server page (ASP.NET) that uses a ServerManager object stored in the ASP

Application collection. Users connect to the page with a web browser and select a server and a tag

whose snapshot value is then displayed. The connection is made using a PI Trust and the resulting PI

User is also displayed.

This example is built by creating two pages in a virtual directory under Internet Information Server

(IIS).

0. Create a Web Application in your favorite language (Example granted in C#, Web site

solution named ServerManager_Demo).

1. Open the file called "global.asax". This page is the global code available to all ASP.NET pages

in this directory. Locate section "Application_Start" and insert the following code there:

Application["srvrmgr"] = new PISDK.ServerManager();

2. Now create the aspx page that the user will browse. Name it something appropriate (e.g.

servermgr.aspx). Insert the following code in the page.

6

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="servermgr.aspx.cs"

Inherits="ServerManager_Demo.servermgr" %>

<!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 runat="server">

<title>ServerManager Demo</title>

</head>

<body>

<font color="royalblue" size="4" face="Lucida Sans Unicode" ><strong>Server Manager

Test</strong></font><br>

<form id="form2" runat="server">

<P><font color="purple">Select Server<br /></font>

<asp:DropDownList runat="server" id="select1" style="height: 22px; width: 142px;">

<asp:ListItem Value="Server1" Selected="True">Server1</asp:ListItem>

<asp:ListItem Value="Server2">Server2</asp:ListItem>

<asp:ListItem Value="Server3">Server3</asp:ListItem>

<asp:ListItem Value="Server4">Server4</asp:ListItem>

<asp:ListItem Value="Server5">Server5</asp:ListItem>

</asp:DropDownList></P>

<p><font color="purple">

Select Tag:<br /></font>

<asp:DropDownList runat="server" ID="select2" style="HEIGHT: 22px; LEFT: 152px; TOP:

91px; WIDTH: 173px">

<asp:ListItem Value="sinusoid" Selected="True">sinusoid</asp:ListItem>

<asp:ListItem Value="cdm158">cdm158</asp:ListItem>

<asp:ListItem Value="cdt158">cdt158</asp:ListItem>

<asp:ListItem Value="sinusoidu">sinusoidu</asp:ListItem>

<asp:ListItem Value="ba:active.1">ba:active.1</asp:ListItem>

</asp:DropDownList></p>

<p>

<input type="hidden" name="srvrindx" />

<input type="hidden" name="tagindx" />

<input type="hidden" name="firstTime" value="0" />

<asp:Button ID="Button1" runat="server"

Text="Get Snapshot" />

</p>

<hr />

<font color="purple">

Server: <span runat="server" id="spanSrvrName" style="color: green"></span></font><font

color="purple"> Tag Name: </font><span runat="server" id="spanTagName" style="color:

green"></span><br /><hr />

Current snapshot value is: <span runat="server" id="spanSnapshot" class="foo"

style="color: red"></span><br />

Current logged on user is: <span runat="server" id="spanUser" class="foo" style="color:

blue"></span><br />

Current windows user is: <span runat="server" id="spanWinUser" class="foo"

style="color: Green"></span><br />

<span runat="server" style="color: Red;" id="spanError"></span>

</form>

</body>

</html>

3. In the code behind page (named servermgr.aspx.cs if you have followed this example’s

namings) insert the following code in the Page Load section:

string snapvalue, piuser, tagName, srvrName;

{

if (Page.IsPostBack)

{

try

{

PISDK.ServerManager srvrmgr;

srvrmgr = (PISDK.ServerManager) Application["srvrmgr"];

PISDK.Server srvr;

7

srvrName = select1.SelectedValue;

srvr = srvrmgr[srvrName];

PISDK.PIPoint pt;

tagName = select2.SelectedValue;

pt = srvr.PIPoints[tagName];

PISDK.PIValue val;

val = pt.Data.Snapshot;

snapvalue = val.Value.ToString();

piuser = srvr.CurrentUser;

//fill the html part

spanSrvrName.InnerText = srvrName;

spanTagName.InnerText = tagName;

spanSnapshot.InnerText = snapvalue;

spanUser.InnerText = piuser;

spanError.InnerText = string.Empty;

if (User.Identity.IsAuthenticated)

{

spanWinUser.InnerText = User.Identity.Name;

}

else

{

spanWinUser.InnerText = "Not authenticated.";

}

}

catch (Exception err)

{

spanError.InnerText = err.Message;

}

}

}

4. The ASP.NET page has a form that causes a postback when the button is pressed. The PI

servers in the select box (shown above as Server1 - Server5) should reflect servers available

to your application as entries in the Known Servers Table. In addition these Servers should

be configured to use windows authentication for the users who will be viewing the page. If

the default settings for the ASP.NET page are not changed then it will run under the

Application pool’s identity, so this is the user you should grant access to the PI Server.

5. You can create a link or button on this ASP.NET page that links to a different page which

displays the current count of connections in the global ServerManager object and provides a

means to call SlimFast to remove connections that are not currently in use. You can build

this page by creating a file called "mngmnt.aspx" and copying in the following contents:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="mngmnt.aspx.cs"

Inherits="ServerManager_Demo.mngmnt" %>

<!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>

<title>Server Management</title>

</head>

<body>

<form id="form1" runat="server"><p>

Current server/username connection count is: <span runat="server"

id="spanCount"></span>

&nbsp;

<asp:Button ID="doSlimFast" runat="server" onclick="doSlimFast_Click"

Text="Slimfast" />

8

<asp:Button ID="goBack" runat="server" onclick="goBack_Click" Text="Back" />

</p>

</form>

</body>

</html>

6. And in the code behind page (named mngmnt.aspx.cs) paste the following code: protected void Page_Load(object sender, EventArgs e)

{

spanCount.InnerText = ((PISDK.ServerManager)Application["srvrmgr"]).Count.ToString();

}

protected void doSlimFast_Click(object sender, EventArgs e)

{

((PISDK.ServerManager) Application["srvrmgr"]).SlimFast();

}

protected void goBack_Click(object sender, EventArgs e)

{

Server.Transfer("servermgr.aspx");

}

7. To fully secure access to the application, the virtual directory should be set to use

"Integrated Windows Authorization" in IIS. Using Windows Explorer, the ASP.NET file should

be made accessible only to the desired domain users. When configured in this manner,

browsers connecting to this page will be issued a challenge (often silently) and if the

authentication is successful the page will run in IIS impersonating the logged on user of the

browser. Behind the scenes, the PI SDK obtains the identity of the logged in user and passes

it to the PI Server. The PI Server validates this identity against the domain controller. This

means that users outside the domain and associated trusted domains cannot be validated

and will not be granted a connection.

ASYNCHRONOUS CALLS

Characteristics

Non-blocking data access calls

Designed to provide higher scalability by leveraging computing resources of the

PI Server.

Description

As you know, some programming operations take time. Imagine that you built a single-threaded application that is invoking a method on a remote object, performing a long-running database query, downloading a large document, or writing 1000 lines of text to an external file. While performing these operations, the application will appear to hang for some amount of time. This happens because until the task at hand has been processed, all other aspects of this program (such as menus, toolbars, or console output) will be unresponsive. In order to prevent this lock up we can call a function and instruct it to return the execution right away, making the interface responsive again. This could be done calling the locking instruction in a separated thread or by calling it asynchronously. When creating a different thread we can create a Delegate and sign up for an event or we could check the status of the asynchronous call periodically until it has finished.

9

The PIAsynchStatus object supports Windows events that can be used to track progress and status of

an asynchronous call. These Windows events, however, are only reliably delivered in a single

threaded application. Though a multi-threaded application can still use polling, checking the status

to determine when a call is complete, this interface exposes an alternate way to detect call

completion across threads without having to poll.

Synchronization primitives, in this case a Windows Event object (event is a heavily overloaded term),

can be tracked using the Win32 call WaitForSingleObject. With this call you can control how long

you wait for an answer so you can spawn a thread that waits until the object is signaled or you can

check if it has been signaled periodically.

Another benefit of using the synchronization event to check for call completion is that you can make

several asynchronous calls and wait for all of them to return using the Win32 call

WaitForMultipleObjects. This call can be used to wait for an array of event objects to complete

before returning or can return when any of the objects has been signaled.

There are various methods you could use to check the status of the Asynchronous call, a simple loop

to wait for one or more Asynchronous Calls to finish, creating a delegate or signing up for an event.

Example

Simple Loop

This method will work for all the functions that currently support Asynchronous calling, for a complete and updated list please visit the ‘PISDK Help file’ in the Reference section of this document.

PIAsynchStatus varPIAsynchStatus;

//Call your favorite method asynchronously

somePISDKClassMethodorInterface(…, varPIAsynchStatus);

while (varPIAsynchStatus.Status != CallStatusConstants.csComplete ||

varPIAsynchStatus.Status != CallStatusConstants.csCompleteWithErrors)

{ /* Do something while waiting for Async call to complete */ }

Essentially this allows us the flexibility to perform other task that requires less processing while waiting for the asynchronous call to complete in the background.

Another useful use case is also the flexibility to run multiple asynchronous calls concurrently. This allows us to issue calls in batches instead of one after another. This is helpful when each call would require a significant amount of processing time to complete for example getting the summaries values of 1000 tags for a year with 1 month intervals. We can use loops to block the program to wait for each batch to complete before progressing to the next step or issuing the next batch of asynchronous calls. An example of such call:

int iMaxtags; // total point count to process

int iAsyncGroup = 10; // number of points in 1 batch

PointList _PointList = server.GetPoints("tag='cd*'"); // Get PointList from

PI Server

iMaxtags = _PointList.Count;

PISDKCommon.NamedValues[] _nvsSum = new PISDKCommon.NamedValues[iMaxtags];

PISDKCommon.PIAsynchStatus[] _PIAsynchStatus = new

PISDKCommon.PIAsynchStatus[iMaxtags];

PISDK.PIPoint[] _PIPoint = new PISDK.PIPoint[iMaxtags];

PISDK.IPIData2 _ipiD2;

10

for(int i = 0; i < iMaxtags - 1; i++)

{

_PIPoint[i] = _PointList[i + 1];

_PIAsynchStatus[i] = new PISDKCommon.PIAsynchStatus();

try

{

_ipiD2 = (IPIData2) _PIPoint[i].Data;?

_nvsSum[i] = _ipiD2.Summaries2("*-2h",

"*",

"",

PISDK.ArchiveSummariesTypeConstants.asTotal,

PISDK.CalculationBasisConstants.cbEventWeightedIncludeBothEnds,

_PIAsynchStatus[i]);

}

catch(Exception ex)

{

// print out error

return;

}

// after a batch of asynchronous call is issued block to wait for all

// the issue calls to complete before proceeding

int j = (i + 1) % iAsyncGroup;

if ((j == 0) || (i == iMaxtags - 1))

{

for(int k = (i + 1 - iAsyncGroup); k < i; k++)

{

while(_PIAsynchStatus[k].ProgressPercent < 100)

{

}

}

}

}

Console.WriteLine("Process completed");

Using a Delegate

The main function of Delegates is to allow generic calling of functions based purely on its signature. So you could change what function gets executed or improve on the version that gets executed overtime without having to make a change into any of the other code or the original function. You’ll just need to alter the declaration of the delegate and create the new function.

A delegate will also allow you to use any function in an asynchronous way whether it natively supports it or not. The advantage of using delegates to do your asynchronous calls is that the .NET compiler will take care of creating all the supporting methods: BeginInvoke, EndInvoke, and WaitOne; it will also take care of updating the IsCompleted Attribute, which is part of the IAsyncResult class. You can always tailor the way your Delegate behaves and add more specific information to the State or the Delegate call back, but that is outside the scope of this whitepaper.

The BeingInvoke method is the responsible for calling the method asynchronously, we will need to call the EndInvoke method to fetch the result of the asynchronous, we’ll do that once the IsCompleted Attribute gets set to true; finally WaitOne will give us a chance to wait for the function to complete in a specified time lapse and if it does not it will return the execution to the main thread allowing us to continue some work on this side while we wait for the method being called asynchronously to finish.

11

Example

//Global variables

public delegate ReturnType DelegateName(type parameter1, …, type parameterN);

//Next line needs to be ‘static’ if this is used in a ConsoleApplication

DelegateName varDelegate;

//Main method

varDelegate = new DelegateName (varClass.DesiredMethod);

IAsyncResult varAsyncResult =

varDelegate.BeginInvoke(parameter1, …, parameterN, null, null);

//WaitHandle maybe required in a WindowsForm application

WaitHandle varWaitHandle = varAsyncResult.AsyncWaitHandle;

while (!varAsyncResult.IsCompleted){

varWaitHandle.WaitOne(1); //Wait n milliseconds or continue execution

/* Do something while we wait for the Async call to complete */

}

ReturnType varResult = varDelegate.EndInvoke(varAsyncResult);

Using a Delegate and Event Callbacks

The main extension of this method is that adding an Event sign up for the event when doing the BeingInvoke method, so instead of having to wait for it to return you can rely on the finalization calling the method for you. The changes you need to do are: first we need to create a function that will get called after the function has finished, second we’ll add that function to the BeginInvoke call.

/* Delegate function to call when the asyncrhonous call finishes, you will

need to declare this as static if your project is a console application */

public void DelegateCallback(IAsyncResult ar) {

ReturnType varResult = varDelegate.EndInvoke(varAsyncResult);

/* You can now process this depending on the return type of the fuction you

excecuted */

}

//Main method

varDelegate = new DelegateName (varClass.DesiredMethod);

IAsyncResult varAsyncResult =

varDelegate.BeginInvoke(parameter1, …, parameterN, new

AsyncCalBack(DelegateCallBack), null);

//You will only have to keep the application running.

Functions returning a PIValues object

PIData.RecordedValues

This method returns compressed values for the requested time range from the archive as a PIValues collection.

PIData.PlotValues

The PlotValues method is designed to take in start time, end time and interval as parameter and return a PIValues collection that will produce the most accurate plot while minimizing the amount of data returned. For each interval, the data available is examined and significant values are returned. Each interval can produce up to 5 values if they are unique, the value at the beginning of

12

the interval, the value at the end of the interval, the highest value, the lowest value and at most one exceptional point (digital state). Usually interval setting is defined based on the number of pixels, which gives an indication how much information is required to be display on the plot area.

IPICalculation.Calculate

This method returns a PIValues collection that contains the result of evaluating the passed expression over the passed time range.

IPICalculation.TimedCalculate

This method returns a PIValues collection that contains the result of evaluating the passed expression at the passed time points.

IPIData2.InterpolatedValues2

The method returns interpolated values from the archive over the specified time range as a PIValues collection. InterpolatedValues2 differs from PIData.InterpolatedValues only in the specification of the sampling interval. In PIData.InterpolatedValues, user specifies number of intervals and PISDK computes the interval length based on start time, end time and the number of intervals. The interval length is constant throughout the period. In InterpolatedValues2, user specifies the interval length as a variant in the SampleInterval argument. The interval length could change within the requested time period.

PIData.InterpolatedValues

The method returns interpolated values from the archive over the specified time range as a PIValues collection. This method differs from IPIData2.InterpolatedValues2 only in how the sampling intervals are specified. In InterpolatedValues, the caller specifies number of intervals and the PISDK computes the interval length based on start time, end time and the number of intervals. The interval length is constant throughout the period. In IPIData2.InterpolatedValues2, the caller specifies the interval length as a variant in the SampleInterval argument. The interval length could change within the requested time period.

PIData.TimedValues

This method returns a PIValues collection that contains the interpolated values from the archive at the passed time stamps.

Example

//Getting data from a PIValues variable

foreach (PIValue tmpPV in tmpNV.Value) {

if (tmpNV.Value is DigitalState) sText = tmpPV.Value.Name;

if (tmpNV.Value is PITime) sText += tmpPV.Value.LocalDate.ToString();

if (tmpNV.Value is Double) sText += tmpPV.Value.ToString();

}

Functions returning a NamedValues Collection

IPICalculation.ExpressionSummaries

This method creates a data stream from an arbitrary expression and calculates one or more summaries on the expression result. The method returns a NamedValues collection with the requested summaries.

IPIData2.Summaries2

The Summaries2 method retrieves several summary types in a single call over the specified range, and for each interval within the range. The method returns a NamedValues collection which contains

13

named summaries as PIValues. Summaries2 differs from PIData.Summaries only in the specification of the summary calculation duration. In PIData.Summaries, user specifies number of calculation intervals and PISDK computes the interval length based on start time, end time and the number of intervals. The interval length is constant throughout the period. In Summaries2, user specifies the interval length as a variant in the CalculationDuration argument. The interval length could change within the requested time period.

IPIData2.FilteredSummaries

This method, when supplied a filter expression that evaluates to true or false, evaluates it over the passed time range. For the time ranges where the expression evaluates to true, the method calculates the requested summaries on the source point. The method returns a NamedValues collection containing the results. This method is not supported for PI2 servers.

PIData.Summaries

The Summaries method retrieves several summary types in a single call over the specified range, and for each interval within the range. The method returns a NamedValues collection which contains named summaries as PIValues. The Summaries method differs from IPIData2.Summaries2 only in the specification of the summary calculation duration. In PIData.Summaries, user specifies number of calculation intervals and PISDK computes the interval length based on start time, end time and the number of intervals. The interval length is constant throughout the period. In Summaries2, user specifies the interval length as a variant in the CalculationDuration argument. The interval length could change within the requested time period.

Example

//Getting data from a NamedValues variable

foreach (NamedValue tmpNV in varNamedValues) {

if(tmpNV.Value is PIValues) {

foreach (PIValue tmpPV in tmpNV.Value) {

if (tmpNV.Value is DigitalState) sText = tmpPV.Value.Name;

if (tmpNV.Value is PITime) sText += tmpPV.Value.LocalDate.ToString();

if (tmpNV.Value is Double) sText += tmpPV.Value.ToString();

}

}

}

Functions returning a PointList

Server.GetPoints

This method accepts a query string, and returns a PointList object that contains PIPoint objects which satisfy the query. The query string syntax is a simplified form of SQL "where" clause.

Server.GetPointsSQL

Given a query string, this method returns a PointList collection that contains PIPoint objects which match the request. The query string is in the format of a SQL "where” clause.

Server.GetPoints versus Server.GetPointsSQL

The difference between the 2 methods lies in the query that the methods take in. You can find the

details of query string definition can be found in the PI SDK Programming Reference. To summarize

GetPointsSQL can implement complex queries, including joins with other PI tables. GetPoints cannot

do such complex queries, but it is faster, and can use custom point attributes, which GetPointsSQL

cannot.

Example

14

// Where server is a PISDK.Server object

// search condition is any tags with tagname that begins with 'sin'

PointList ptlist1 = server.GetPoints("tag='sin*'", null);

// GetPointsSQL allow us to search based on criteria other than the point

attributes

// like snapshot value of the tag is less than 80 (below)

NamedValues nvExtraTables = new NamedValues();

nvExtraTables.Add("picomp", "picomp");

PointList ptlist2 = server.GetPointsSQL("PIpoint.Tag = 'sin*' AND

PIcomp.Value < 80 AND PIpoint.Tag = PIcomp.Tag AND PIcomp.Time =

DATE(\"*\")", nvExtraTables, null);

//Accesing data from the PointList

foreach (PIPoint tmpPP in varPIPoints) {

//Now you can access the point via tmpPP

}

//You can also use index access to get to the points (one based)

varPIPoints[1].Name;

LISTDATA FUNCTIONS

Characteristics

Allows retrieving data on a list of PI Points rather than one at a time

Designed to improve performance by reducing the number of calls made from

the PI SDK to the PI Server

ListData.Snapshot

Description

Return a PointValues collection containing a single snapshot value for each point in the PointList.

EVENT PIPES

Characteristics

Designed to improve performance by getting notified of PI Server events (as

opposed to polling). The events the PI Server reports back include Permissions

Change, New Data events, Tag Configuration changes,

Description

Events were created to allow the application to react to things like a button press, a mouse click,

closing a form, moving a window, minimizing, etc. And for the PI Server that means that instead of

polling for a Tag for new data, the point will get back to you and tell you that there have been some

changes.

An EventPipe object contains items that have changed on the PI Server for its parent. When an

EventPipe property is retrieved through the parent it will return a unique event pipe that while kept

in scope will receive all the changed items for that parent. Multiple pipes on a single source will all

receive each event.

15

Different parents will contain different types of items but the interface to the event pipe remains the

same.

Parent Object Changed Items

PIData new and edit events

ListData new and edit events

IPIData2 (PIData) Archive data

ListData Archive data

Example

Look for the vCampus Exclusive Webinar on EventPipes in the reference section.

MULTITHREADING APPLICATIONS

Characteristics

Designed to provide higher scalability through parallel/concurrent execution

Susceptible to thread-locks/race/starve conditions

Increases application complexity

Description

The following discussion of threading and PI SDK applies mainly to programs written in C#/C++

where it is possible to create multiple threads of execution. Typical Visual Basic programs will create

PI SDK objects in a single apartment and a single thread will service the PI SDK calls. Calls to the

objects are synchronized (sequential). This eliminates problems associated with cross-thread calls.

C#/C++ programmers that require multi-threaded access to the PI SDK in their programs should

consider this section. With the advent of VB.Net it is also now relatively easy to build multi-threaded

applications using Visual Basic.

The PI SDK library is an in-process server (DLL) that gets loaded on first access into a program's

process space. Calls to the PI SDK in a program are generally all made within the same process

(assuming you have not configured your machine to access the PI SDK through DCOM), but may be

made across threads. Most PI SDK objects in this release are built using the Apartment threading

model and as such, when created, are instantiated in Single Threaded Apartments (STA). Given the

object hierarchy used in the PI SDK model, almost all PI SDK child objects will end up being

instantiated in a single apartment, the one where the main PISDK object is first created. "Creatable"

objects, such as NamedValues and PITime, may be created in other apartments dictated by their

threading model and the type of apartment where the creation call is made. COM dictates that an

object in an STA can only execute on one thread (STAs have thread affinity). Calls from other threads

must be marshaled to the object's STA, which results in these other threads receiving a proxy to the

object rather than an actual interface pointer.

Any C++ thread calling COM objects must first call CoInitialize, CoInitializeEx, or OLEInitialize. This call

determines the apartment type for the thread. CoInitialize and OLEInitialize always uses an STA.

CoInitializeEx allows the caller to specify the apartment type. Subsequent calls to create objects in

the thread, will create objects in that apartment. Access to objects created in other apartments (by

other threads) must be handled through proxies.

16

An interesting question is how a thread initially obtains a proxy to an object in another apartment. It

is a violation of COM rules to pass raw object/interface pointers from one STA thread to another.

Doing so can create conditions where multiple threads are simultaneously executing code in the

same object. Some objects may be thread-safe and be able to handle this type of access, but as a

rule, apartment threaded objects are not required to be thread-safe because the COM rules and

conventions ensure calls to the object are serialized. To safely pass pointers between apartments

they need to be "marshaled." A thread (or process) receiving a marshaled interface actually receives

a proxy to the object. Discussion of marshalling techniques is beyond the scope of this document.

Refer to Microsoft documentation for details.

The PISDK object is designed as a per-thread single point of access to an object hierarchy. The object

is built as a "per thread singleton," meaning only one instance of the object is created in any thread.

Multiple requests to create the object within a thread return references to the original object. The

first code to request the object, (typically a call to CreateInstance, using "new" in VB.Net, or a smart

pointer constructor), results in the object being created in that thread's apartment. Subsequent calls

to create the PISDK object in other threads will return a PISDK object for that particular thread. The

hierarchies obtained from these separate PISDK objects are separate, though they communicate

with the same PI servers. This behavior is useful for writing worker threads that operate

independently of each other. Threads written this way avoid the performance cost of inter-thread

marshaling.

Note however that it is still perfectly acceptable to marshal a reference to a PISDK object (or its

children) across threads using standard COM calls. This allows designs where multiple threads share

the same COM objects. In this configuration, however, cross thread calls will be marshaled with the

non-creating thread receiving a proxy to the object's interface. For example, thread 1 creates a

PISDK object and retrieves a Server object from its Servers collection. It marshals this Server object

to thread 2. Thread 2 then uses the marshaled server pointer to obtain a PIPoints collection. The

PIPoints interface pointer in thread 2 in this case is a proxy. Subsequent calls on the proxy that

return interfaces to other objects (walking the hierarchy) will also return proxies. COM automatically

generates these proxies, returned through calls on another proxy.

There are some important implications of this behavior in the PI SDK. Accessing an object through a

proxy as discussed above involves cross-thread marshalling, which can have performance impacts.

When calling methods that require significant processing (for example cross platform calls to a PI

Server) the contribution of the proxy may be insignificant. For quick local access calls (for example

retrieving cached properties) the proxy call and marshalling may have a larger contribution to total

processing time.

When a thread terminates, its STA is removed, and all objects created in that STA are deleted. If

some other thread is holding a proxy to one of those objects, that proxy is not deleted, but will

return an RPC disconnection error on any attempt to use it. This can be a problem when the thread

creating the initial PISDK object terminates. All other threads trying to use the SDK will begin to

report disconnection errors. To prevent this problem, the thread that creates the initial PISDK object

should not terminate until all worker threads have already terminated. You can wait for a thread to

be terminated by calling WaitForSingleObject or one of its derivatives passing the thread handle, or

you can use other Win32 synchronization primitives.

17

In addition, the creating thread should keep the PISDK object alive by holding a reference-counted

pointer to that object, which is not released until just before the thread terminates. If you do not do

this, the PISDK object will delete itself when there are no outstanding references to it. This is not a

problem in itself, but if a worker thread subsequently tries to get a reference to the PISDK object, a

new instance of it will be created in that worker thread's STA. But the worker thread, unlike the main

thread, will not be waiting for all other threads to terminate, resulting in the problem described

above.

One final consideration when programming multi-threaded COM applications is that STAs (such as

those created by calling CoCreateInstance) need to provide a message loop to handle COM calls

from other threads. COM handles the serialization in a single-threaded apartment by posting

Windows messages to the apartment's thread. If the thread does not have an active message loop to

process the message, the call will block, freezing the application. If an object is to be marshaled, the

initial thread creating the object requires a message loop to handle requests for the proxy generated

by subsequent threads expecting to marshal the object. If you can be sure that none of the objects in

a particular apartment will be called from another thread, you need not have a message loop in that

apartment's thread. Introducing an active message loop into a thread requires breaking up the work

to be done by the thread into reasonable execution chunks so that messages continue to be serviced

in a reasonable time frame.

With version 1.3.1 of the PI SDK a Windows message pump was added to the synchronization

routines managing concurrent thread access to shared resources. There are implications of this

change:

If while processing a Windows message in your application, you call a PI SDK method, it is possible

for other Windows messages to be received by your application before your processing of the

original method is complete. This is due to a message pump in the PI SDK that is run to marshal PI

SDK calls on proxies from other threads onto the main PI SDK apartment thread (STA) where they

execute. In a sense, entering a PI SDK call while processing a Windows message makes your message

handler reentrant.

18

MULTI-THREADED NAMEDVALUE (MTNV) AND MULTI-THREADED

NAMEDVALUES (MTNVS )

In PI SDK 2010 R2, we have introduced new objects into the PI SDK object model MTNVS and MTNV. MTNVS is a collection of MTNV objects. Each object in the collection has a name, which is a string, and a value, which is a VARIANT. MTNVS is a thread-safe version of NamedValues. It implements all the NamedValues and INamedValues2 methods and properties as well as native versions (prefaced by "MT") of the same. Thread-safe objects like MTNVS helps to improve performance of your multithreaded application because they can be instantiated in Multi-Threaded Apartment (MTA) which means that it can access and receive direct calls from multiple threads without cross-thread marshalling.

An example to illustrate this is the following console application:

namespace MTNVSPerformance

{

class Program

{

//[STAThread] // insert this to make it STA - console apps

default to MTA

static void Main(string[] args)

{

const int NbrLoop = 50; // number of loops

const int NbrItem = 100; // number of items

Stopwatch st1 = new Stopwatch(); // StopWatch for NVS

Stopwatch st2 = new Stopwatch(); // StopWatch for MTNVS

//

// Test the performance of MTNVS versus NVS

//

try

{

//

// Test NVS performance

//

Console.WriteLine("*** Adding & Iterating NVS collection of "

+ NbrItem + " PIValues, looping " + NbrLoop + " times.");

st1.Start();

//

// Loop many times to make it easier to measure the total

//

for (int i = 0; i < NbrLoop; i++)

{

PISDKCommon.NamedValues nvs = new

PISDKCommon.NamedValues();

// create this here so we get a new one each loop

for (int j = 1; j <= NbrItem; j++)

{ // 1 based collection

string itemName = "Item" + j;

object itemVal = j;

nvs.Add(itemName, ref itemVal);

};

//

// Now iterate through each item

// Don't use foreach because we can't use it for MTNVS

//

for (int j = 1; j <= NbrItem; j++)

{ // 1 based collection

object objInx = j;

PISDKCommon.NamedValue curNV = nvs.get_Item(ref

objInx);

19

string curName;

object curVal;

curName = curNV.Name;

curVal = curNV.Value;

}

}

st1.Stop(); // StopWatch End

Console.WriteLine(">>> NVS Elapsed time: " +

st1.Elapsed.ToString());

//

// Test MTNVS performance

//

Console.WriteLine("*** Adding & Iterating MTNVS collection of

" + NbrItem + " PIValues, looping " + NbrLoop + " times.");

st2.Start();

//

// Loop many times to make it easier to measure the total

//

for (int i = 0; i < NbrLoop; i++)

{

PISDKCommon.MTNVS mtNVS = new PISDKCommon.MTNVS();

// create this here so we get a new one each loop

for (int j = 1; j <= NbrItem; j++)

{ // 1 based collection

string itemName = "Item" + j;

object itemVal = j;

mtNVS.MTAdd(itemName, ref itemVal);

};

//

// Now iterate through each item

//

for (int j = 1; j <= NbrItem; j++)

{ // 1 based collection

object objInx = j;

PISDKCommon.MTNV curMTNV = mtNVS.get_MTItem(ref

objInx);

string curName;

object curVal;

curName = curMTNV.MTName;

curVal = curMTNV.MTValue;

}

}

st2.Stop(); // StopWatch End

Console.WriteLine(">>> MTNVS Elapsed time: " +

st2.Elapsed.ToString());

}

catch (Exception ex)

{

Console.WriteLine(ex.Message);

}

//

// Report ratio NVS/MTNVS

//

double dblNVSTime = st1.Elapsed.Ticks;

double dblMTNVSTime = st2.Elapsed.Ticks;

double nvsMTNVSRatio = dblNVSTime / dblMTNVSTime;

string strRatio = String.Format("{0:F2}", nvsMTNVSRatio);

Console.WriteLine("Ratio of NVS to MTNVS = " + strRatio);

//

// Wait for keypress

20

//

Console.WriteLine("Press ENTER to continue...");

Console.ReadLine();

}

}

}

A C# console application like above defaults to using MTA and this program compares the speed of

looping through NamedValues with MTNVS. A quick comparison of running the program in MTA and

STA in one of our test environment gives something like

This result basically illustrates to us that using MTNVS in cases like this clearly offers significant

performance improvement compared to NamedValues.

Do take note that in programs which utilize STA like Windows Form applications, NamedValues will

still have better performance compared to MTNVS. We can see this by setting the program to use

STA by setting the [STAThread] attribute located at the start of the method, which will give the

following performance:

So you can consider the type of application that you are developing and choose which of these

options will give you better performance.

In PI SDK 2010 R2 or later, ValueAttributes property of PIValue object will be returned as MTNVS

collection instead of NamedValues collection in previous versions. This would help you improve the

performance of free-threaded applications that retrieve additional information related to the values

from the PI System (like Annotation and Substituted flag).

IPIVALUES2.GETVALUEARRAYS

IPIValues2 interface is a secondary interface supported by PIValues collection to include additional features. One of which is the GetValuesArrays method is implemented to return all data in the PIValues collection into parallel arrays, representing the value, timestamp and flags.

This is a more effective way to get values from PI Server when we are deploying PI SDK Interop library in .NET framework. As PI SDK is a COM library, interop marshaling is involved when parameters and return values is passed between the managed and unmanaged code, which means additional overhead is incurred when we access objects across the interop boundary.

If we want to get timestamp and value of all archive values from a PIPoint for the last 2 hours using PIData.RecordedValues call, we have to loop through all the PIValue objects in the returned

21

PIValues object. This would involve marshaling PIValue COM objects and incurring marshaling overhead for every single PIValue object.

Using IPIValues2.GetValuesArrays method, we can bring the data returned as PIValues collection across the interop boundary to managed code as arrays in 1 call, without incurring more overhead as we access the values from the array.

A sample of using IPIValues2.GetValueArrays:

public long AddValuesToLB(PISDK.PIValues pivals, Int32 DisplayDig, ListBox

lstbox)

{

try

{

System.Array a1, a2, a3;

PISDK.IPIValues2 pivalarray = (PISDK.IPIValues2)pivals;

pivalarray.GetValueArrays(out a1, out a2, out a3);

object[] values = (object[])a1;

double[] timestamps = (double[])a2;

Int32[] pibits = (Int32[])a3;

PITimeServer.PITimeFormat pitm = new PITimeServer.PITimeFormatClass();

pitm.FormatString = "MM-dd hh:mm:ss.###";

for (int idx = 1; idx < values.Length; idx++)

{

pitm.UTCSeconds = timestamps[idx];

string strval = pitm.OutputString;

strval += " ";

strval += ValueToString(values[idx], DisplayDig);

if (pibits[idx] > 0)

{

if ((pibits[idx] &

(Int32)PISDK.PIValueFlagBitMask.eSubstitutedMask) ==

(Int32)PISDK.PIValueFlagBitMask.eSubstitutedMask)

strval += " S";

if ((pibits[idx] &

(Int32)PISDK.PIValueFlagBitMask.eAnnotatedMask) ==

(Int32)PISDK.PIValueFlagBitMask.eAnnotatedMask)

strval += " A";

if ((pibits[idx] &

(Int32)PISDK.PIValueFlagBitMask.eQuestionableMask) ==

(Int32)PISDK.PIValueFlagBitMask.eQuestionableMask)

strval += " Q";

}

lstbox.Items.Add(strval);

}

}

catch (Exception ex)

{

lstbox.Items.Add(ex.Message);

return 1;

}

return 0;

}

private string ValueToString(object varVal, Int32 DisplayDig)

{

string strtemp;

if (varVal.GetType().IsCOMObject)

// translate the COM object and get the string representation

else if (varVal.GetType().Name == "Double")

22

{

// Could use DisplayDigits...

strtemp = varVal.ToString();

}

else

strtemp = varVal.ToString();

return strtemp;

}

23

CONCLUSION

PI SDK applications can often provide the highest performance and scalability for accessing PI data, but this requires careful planning and development using the above techniques. As a developer working with PI SDK, it is also recommended to use the newer versions PI SDK as there are new features added as well as changes under the hood that can help you improve the performance of your PI SDK application, like the changes implemented with the introduction of MTNVS and MTNV in PI SDK 2010 R2.

For most users, it may be more efficient to leave these issues to OSIsoft and use PI Web Services instead. PI Web Services leverages all of these PI SDK optimizations and provides a simple query/response interface for programmers, allowing them to focus on what data to read/write to PI, instead of how to do it.

The efficiency and ease of development with PI Web Services may be more cost-effective than doing “raw” PI SDK programming, especially when coding in .NET languages or creating modern web-based applications. The OSIsoft vCampus development license (and the corresponding PI System Access runtime license) allows customers to choose the right tool for the job, so please consider which approach (PI SDK, PI Web Services, or another PI Data Access tool) makes the most sense for your particular situation, and for the long run.

24

RESOURCES

Find Application Bottlenecks with Visual Studio Profiler - http://msdn.microsoft.com/en-us/magazine/cc337887.aspx

MSDN Delegate help page because there is more to the delegates that what was showed here – http://msdn.microsoft.com/en-us/library/900fyy8e%28v=VS.100%29.aspx

MSDN Event help page, it is always useful to have a reference around – http://msdn.microsoft.com/en-us/library/8627sbea%28v=VS.100%29.aspx

Optimizing your PI SDK applications vCampus Webinar – http://vcampus.osisoft.com/auditorium/m/webinars/4554.aspx

Event Pipes vCampus Webinar - http://vcampus.osisoft.com/media/p/2109.aspx

Understanding and Using COM Threading Models – http://msdn.microsoft.com/en-us/library/ms809971.aspx

Description and Workings of OLE Threading Models – http://support.microsoft.com/kb/q150777/

Single-Threaded Apartments – http://msdn.microsoft.com/en-us/library/[email protected]%29.aspx

PI SDK Help – %ProgramFiles%\PIPC\HELP\pisdk.chm

For the list of calls that currently support true asynchronous behavior: PI SDK Documentation \ PI-SDK Programming reference \ PIAsynchStatus \ PIAsynchStatus Object

Interop Marshalling – http://msdn.microsoft.com/en-us/library/eaw10et3.aspx

25

REVISIONS

01-Oct-2010 Initial version by Cristobal Escamilla

05-Oct-2011 Updated version by Han Yong Lee to include section on MTNV and MTNVS

19-Oct-2011 Reviewed by Jay Lakumb

15-Dec-2011 Updated by Han Yong Lee on PIData.PlotValues as the call supports asynchronous

call now. Added more description on what the call does.

16-Dec-2011 Reviewed by Harri Talvala

22-Dec-2011 Reviewed by Ray Verhoeff

22-Dec-2011 Updated by Han Yong Lee on IPIValues2.GetValueArrays and GetPoints versus

GetPointsSQL