6 Replies Latest reply on Dec 7, 2014 7:05 PM by Goodtech

    PIPagingConfiguration

    richardmarcell

      Has anyone "played with" the PIPagingConfiguration argument in the PIPointList data access methods added in AF 2.6?

       

      I'm encountering frequent PINET: Timeout on PI RPC or System Call errors and this argument seems to imply the ability to get partial results returned from these calls "proactively" (term used in one of the help file topics) before a timeout occurs, but there's no examples on how to handle (or even detect) a partial results return or continue such a request.

        • Re: PIPagingConfiguration
          rgilbert

          The result from all of the list data access calls is an IEnumerable; most of the calls return IEnumerable<AFValues>. We chose this return type, because internally we can take two different paths that have different return types, and it allows us to provide results as they are available. We did our best to abstract the partial result handling away from the developers. The developer simply iterates the results via a foreach loop, and the loop proceeds as results are available.

           

          First, when a list call is made, we have to partition the points in the list by PI Server, and then execute the data access call against each PI Server. If the PI Server version is >= 390 (PI Server 2012), then we know it supports the bulk list data access calls. If it is less than 390, then we call the singular data access equivalent in parallel instead. I'm going to focus only on the bulk data access code path in this discussion.

           

          Underneath the hood we construct a bulk query to the PI Server using the parameters in the PIPagingConfiguration object. The most commonly used parameters are:

          1. PageType
          2. PageSize
          3. OperationTimeoutOverride

           

           
          The PageType specifies how you want partial results "paged" back to the client. You can either page by Tag Count or Event Count, but there is a catch that you should be aware of. The PI Server will never return partial results for a single tag even if you page by Event Count; it will always finish collecting events for the current tag even if that threshold has been reached. Assume you make a list data access call for RecordedValues for the last 24 hours and each tag has 20,000 events for that 24 hour period. If you page by Event Count with a PageSize of 10,000, then you actually end up with a page for each Tag containing 20,000 events instead of the 10,000 you desired to page by. You need to take this into consideration when determining your PageSize and PageType.

           

           
          Now that we know how paging works, let's talk about the timeouts. There are two timeouts on PIPagingConfiguration: OperationTimeoutOverride and KeepAliveTimeout. You should not have to worry about the KeepAliveTimeout. It represents the amount of time you have to request the next page from the PI Server, before it "forgets" about your query. The AF SDK automatically requests the next page, in the background, as soon as the current one arrives. The OperationTimeoutOverride is more useful to you. It represents the amount of time the PI Server can spend trying to collect the current page of events before timing out. For example, if you set your PageType to Tag Count and PageSize to 100 and you are using the same tags I mentioned earlier, then the PI Server needs to collect 2,000,000 events for each page which might take more than the default OperationTimeout. The OperationTimeout defaults to 60 seconds and can be modified through the PSE or About PI-SDK Connections dialogs. You can temporarily override it for the duration of a bulk list call instead using the PIPagingConfiguration object's OperationTimeoutOverride property. The timeout you are experiencing is because this interval has elapsed without the page being fully collected. You can either increase the OperationTimeoutOverride property's time span or change your PageType and PageSize to reduce the size of the pages. The latter is probably better.

          The code sample we'll be using for the list data access methods didn't make it into the beta, but here is one of them below for RecordedValues. They all follow the same pattern. 

           
          // Holds the results keyed on the associated point
          Dictionary<PIPoint, AFValues> resultsMap = new Dictionary<PIPoint, AFValues>();
          
          // Results should be sent back for 100 tags in each page.
          PIPagingConfiguration config = new PIPagingConfiguration(PIPageType.TagCount, 100);
          
          try
          {
              IEnumerable<AFValues> listResults = pointList.RecordedValues(
                  timeRange, AFBoundaryType.Inside, null, false, config);
          
              foreach (AFValues pointResults in listResults)
              {
                  // Map the results back to the point
                  resultsMap[pointResults.PIPoint] = pointResults;
              }
          }
          catch (OperationCanceledException)
          {
              // Errors that occur during bulk calls get trapped here
              // The actual error is stored on the PIPagingConfiguration object
              Console.WriteLine(config.Error.Message);
          }
          catch (Exception otherEx)
          {
              // Errors that occur in an iterative fallback method get trapped here
              Console.WriteLine(otherEx.Message);
          }
          

           You will want to have at least two catch blocks to cover both code paths I mentioned earlier. When a bulk method fails, an OperationCanceledException is thrown. This is the result of an internal .NET task being canceled. The actual error that caused the cancellation is stored on the PIPagingConfiguration'sError property. If the cancellation was the result of multiple errors, then the Error property will be an AggregateException containing all of the exceptions. The other catch block covers the case where an error occurs against a pre-390 PI Server. Your timeout exception would be caught in the OperationCanceledExceptioncatch block.

           

          Now lets look at how we get you those partial results. Eric Lippert has a great blog. He worked on the C# compiler while he was at Microsoft. Here he shows what a foreach loop actually expands to.

           

          This:

           
          foreach (V v in x) 
            // embedded-statement
          

          Becomes this:

           
          {
            E e = ((C)(x)).GetEnumerator();
            try 
            {
              V v;  // Inside the while in C# 5.
              while (e.MoveNext()) 
              {
                v = (V)e.Current;
                embedded-statement
              }
            }
            finally 
            {
              // necessary code to dispose e
            }
          }
          

           So basically, it calls GetEnumerator on the IEnumerable, and then uses a while loop to continuously call MoveNext() and iterate through. When your code calls MoveNext() on our enumerator, there are a few things that can happen:

          1. A collection of AFValues is already ready for your code to process, so the function returns immediately and your code continues along.
          2. The current page has not been received and converted, so the MoveNext() function blocks until the next page arrives.
          3. You reached the end of the results, so MoveNext() returns false and you exit the loop.
          4. An exception occurs, the task processing the pages internally cancels, and an exception is thrown. This is what happens when your timeout occurs.

           The great part is that your code is kept simple. You simply iterate the results in a loop, and we make them available as soon as they are ready via a background task.

           

          So to answer your question, we abstract the partial results away from you to keep your coding patterns simple. Even if you use the esoteric BulkPayloadPercentThreshold, remember that the PI Server will still need to collect the results for at least one tag before completing a page. Even if you lower that threshold for "proactive paging," you might still end up with a timeout, if it needs to collect a large number of events for a single tag. 

           

          My recommendation would be smaller pages or a larger OperationTimeoutOverride.

           

          Hope this helps.

          5 of 5 people found this helpful
            • Re: PIPagingConfiguration
              Rhys Kirk

              Ryan, your post lacks detail....joking! Awesome post!

              • Re: PIPagingConfiguration
                richardmarcell

                Sorry for my belated reply. I agree with Rhys. Extremely helpful. Though I kind of had some idea of how the IEnumerable played into the answer, my understanding is now crystal clear, for which I thank you.

                • Re: PIPagingConfiguration
                  Goodtech

                  Ryan Gilbert

                  remember that the PI Server will still need to collect the results for at least one tag before completing a page. Even if you lower that threshold for "proactive paging," you might still end up with a timeout, if it needs to collect a large number of events for a single tag. 

                   

                  Sorry about the thread necromancy, but I believe the quoted section of your reply is relevant to my current issue.

                   

                  I have a PIPointList that I'm calling RecordedValues on, and I'm noticing that if the PIPointList is empty the enumeration fails because the IEnumerable<AFValues> contains 0 pages.

                   

                  I'll add a check to see if there are any PIPoints in the PIPointList before calling RecordedValues, but I was still wondering if this is by design or an error.

                   

                  I also notice that the IEnumerable is resolved to a OSIsoft.AF.PI.PIMultiPageEnumerable<OSIsoft.AF.Asset.AFValues> at runtime, but I can't find anything about the PIMultiPageEnumerable in the documentation. Does anyone know more about this type?

                   

                  Thanks in advance for all help!

                    • Re: PIPagingConfiguration
                      rgilbert

                      Thanks Eivind, I'll add a work item for that bug. That is not by design.

                       

                      PIMultiPageEnumerable is an internal class that implements IEnumerable<TResult> where TResult is the result type. In the case of RecordedValues it is AFValues. It is used when your PIPointList contains PI Points from multiple PI Servers. In that scenario we have to partition the list by PI Server and issue a bulk query to each PI Server individually in parallel. Each bulk query gets a "Page Processor" assigned to it that is responsible for churning through the pages and doing any post processing on the results. In the end, we want to give you a single IEnumerable that you can simply iterate through without having to put the results together. PIMultiPageEnumerable issues the bulk queries in the parallel to the different PI Servers and concatenates the resulting IEnumerable results from each page processor making it easier for consumers of the SDK to enumerate the results without having knowledge of what is actually going on behind the scenes.

                       

                      It isn't in the documentation, because it is an internal class and we just expect you to enumerate the results as you would with any IEnumerable. You'll actually the notice the returned type differs depending on the bulk call being made and whether or not your list has points from multiple PI Servers. We have several page processor types that can get returned. We just guarantee to you that the return type implements IEnumerable<TResult>.