Using RPC Metrics in your AF SDK has never been easier!


Occasionally you may have the need to view various metrics regarding your AF SDK code.  Recently I was given a bit of code to review performance metrics, which I have modified and will present below via a text file to download.  Note the code references PIServer.GetClientRpcMetrics, which is new to AF SDK 2.9.  If you are interested in the code but are working with an earlier version of AF, you will need to strip out any references to PIServer to get it to compile.


Modify App.Config

You will need to modify the <configuration> section of your App.Config file.  If the App.Config file does not exist, you will need to create it.  You will add the following lines:


    <performanceCounters enabled = "true" />
  </ settings >
</ >


This again would be in the <configuration> section, following the <startup> node.  If you have to create the App.Config file from scratch, the whole thing would look like:


<?xml version="1.0" encoding="utf-8" ?>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
      <performanceCounters enabled="true" />


For the metrics code further below, you may put it in a library project.  However, the library DLL as well as any application referencing the library should all have their App.Config modified as shown above.


MetricsTicker and MetricsSnapshot

I have 2 classes defined within one file named "Metrics.cs".  The MetricsTicker will track the starting and ending snapshot taken by MetricsSnapshot, and then neatly display the differences between the start and end.  MetricsSnapshot will take a snapshot of these metrics:

  • AF Server RPC Metrics (if any)
  • AF Client RPC Metrics (if any)
  • PI Client RPC Metrics (if any)
  • Network bytes sent  (a performance counter)
  • Network bytes received  (a performance counter)
  • Garbage collected memory
  • Allocated working set memory (a performance counter)
  • Allocated peak working set memory (a performance counter)
  • Timestamp (DateTime.UtcNow)


Simple Usage Example

Let's say I have some method named YourCustomMethod where I make lots of AF calls that I wish to review the metrics related to that method.  I could reference the classes by passing the Asset Server (PISystem) of interest:


var metrics = new MetricsTicker(assetServer);


Well, that seems easy enough so far!  If you want a high precision timer, you may optionally wrap a Stopwatch around your custom method call.  However, the last thing a starting snapshot does is grab DateTime.UtcNow, and the first thing an ending snapshot does is also grab DateTime.UtcNow so there is already a built-in way to measure the timespan between snapshots.  Plus if you want to focus just on the AF RPC calls, you can review the subtotals of the duration.


Sample Output

Okay, starting and stopping our metrics was easy.  What about reporting?  How difficult is that?  It too is easy.  To output the the difference in metrics, there is one simple command:




Which would produce something like:


AF Server RPC Metrics        Count   Duration(ms)  PerCall(ms)

-------------------------  --------  ------------  ------------

GetSDCollection                   1           0.5         0.484

GetElement                       11          81.4         7.400

GetTableList                      1           3.1         3.092

GetTable                          1           1.6         1.649

GetElementTemplate                2           9.3         4.634

GetCategory                       2          10.2         5.120

GetUOMDatabase                    1           2.0         2.020

SearchTotalCount                  1         246.4       246.412

SearchRefresh                     1           0.0         0.021

SearchObjectIds                   3         221.9        73.953

GetEventFrames                   24        5386.9       224.456

GetCategoryList                   3           9.3         3.102

GetAnalyses                      24         545.2        22.717

GetAnalysisTemplates              1           9.5         9.532

GetElementTemplates               1           3.7         3.720

GetAnalysisTemplateList           1           1.9         1.894

GetModels                        47       17257.7       367.185

-------------------------  --------  ------------  ------------

                 Subtotal       125       23790.8     23790.770


AF Client RPC Metrics        Count   Duration(ms)  PerCall(ms)

-------------------------  --------  ------------  ------------

GetTableList                      1          58.7        58.652

GetTable                          1          30.8        30.814

GetElementTemplate                2          51.2        25.599

GetCategory                       2          39.6        19.786

GetUOMDatabase                    1          68.5        68.490

SearchTotalCount                  1         269.2       269.233

SearchObjectIds                   3         328.5       109.488

GetEventFrames                   24       13932.8       580.535

GetCategoryList                   3          23.9         7.983

GetAnalyses                      24        1706.0        71.083

GetAnalysisTemplates              1          72.0        72.034

GetElementTemplates               1          15.0        15.026

GetAnalysisTemplateList           1          15.8        15.765

GetModels                        47       31414.9       668.402

SearchRefresh                     1           4.7         4.748

-------------------------  --------  ------------  ------------

                 Subtotal       113       48031.7     48031.692


Total GC Memory: 385.37 MB

Working Memory : 524.71 MB

Peak Wrk Memory: 539.85 MB

Network Sent   : 8.84 MB

Network Receivd: 181.44 MB

Elapsed Time   : 02:44.6


How easy is that to generate a report of your metrics?!!


There are different combinations to DisplayDelta() method since it's full signature is:


public string DisplayDelta(bool round = true, bool showServerRpcMetrics = true, bool showClientRpcMetrics = true)


If you notice the bottom of the above output neatly shows bytes in MB rounded to 2 decimal places, and the elapsed time span is to 1/10th of a second.  This is due to the round parameter defaulting to true.  You can get the full bytes and time span if you try DisplayDelta(round: false).  Here's an example of that:


Total GC Memory: 464,306,792 bytes

Working Memory : 600,330,240 bytes

Peak Wrk Memory: 601,427,968 bytes

Network Sent   : 9,271,104 bytes

Network Receivd: 190,250,782 bytes

Elapsed Time   : 00:02:47.1073888


Memory is a Guesstimation

The Network Bytes Sent and Received are fairly accurate, but the values for memory usage should be considered a ballpark value rather than a highly accurate value.  There's so much that goes on inside of .NET with garbage collection, the memory heap, locked pages, etc., that makes it tough to be precise.  Suffice to say that if you to have an app that routinely uses 400 MB of Total GC Memory, and then you make changes to your app to see the memory drop to 100 MB routinely, then you should have peace-of-mind that you've reduced your memory needs by 75%.  Note that I peppered that last sentence with "routinely".  That's because, thanks to the mysteries of garbage collection, you may run the same app 10 times in a row and 9 of those times the memory may hover around 400 MB and 1 of those times it may unexpectedly drop to 200 MB.  This should be considered a one-off due to GC doing something independent of your app but obviously affecting your app.


While the memory usage is not highly accurate, I personally find it to be an acceptable gauge for comparisons.


Download Files

Here's the "Metrics29.cs" file to add to your projects.  You may need to change the namespace accordingly.  Note that "Metrics29.cs" requires AF SDK 2.9 or better.  For AF versions 2.6 through AF 2.8, you may use the "Metrics28.cs" version.