595 lines · 24.2 KB · C#
RawDownload
//******************************************************************************************************//  FrameQueue.cs - Gbtc////  Copyright © 2010, Grid Protection Alliance.  All Rights Reserved.////  Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See//  the NOTICE file distributed with this work for additional information regarding copyright ownership.//  The GPA licenses this file to you under the Eclipse Public License -v 1.0 (the "License"); you may//  not use this file except in compliance with the License. You may obtain a copy of the License at:////      http://www.opensource.org/licenses/eclipse-1.0.php////  Unless agreed to in writing, the subject software distributed under the License is distributed on an//  "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the//  License for the specific language governing permissions and limitations.////  Code Modification History://  ----------------------------------------------------------------------------------------------------//  09/02/2010 - J. Ritchie Carroll//       Generated original version of source code.//  11/18/2010 - Mehulbhai P Thakkar//       Fixed bug in the Pop() method by checking if m_frameQueue has any element in it.//  02/08/2011 - J. Ritchie Carroll//       Added ExamineQueueState method to analyze real-time queue state.//  05/10/2011 - J. Ritchie Carroll//       Updated frame queue locks to use a ReaderWriterSpinLock as an optimization.////****************************************************************************************************** using System;using System.Collections.Concurrent;using System.Collections.Generic;using System.Text;using System.Threading; namespace GSF.TimeSeries{    /// <summary>    /// Represents a real-time queue of <see cref="TrackingFrame"/> instances used by the <see cref="ConcentratorBase"/> class.    /// </summary>    internal class FrameQueue : IDisposable    {        #region [ Members ]         // Delegates        internal delegate IFrame CreateNewFrameFunction(Ticks timestamp);         // Fields        private CreateNewFrameFunction m_createNewFrame;                // IFrame creation function        private LinkedList<TrackingFrame> m_frameList;                  // We keep this list sorted by timestamp so frames are processed in order        private ConcurrentDictionary<long, TrackingFrame> m_frameHash;  // Fast frame lookup dictionary        private SpinLock m_queueLock;                                   // Spinning lock used for synchronizing access to frame list        private long m_publishedTicks;                                  // Timstamp of last published frame        private volatile TrackingFrame m_head;                          // Reference to current top of the frame collection        private volatile TrackingFrame m_last;                          // Reference to last published frame        private int m_framesPerSecond;                                  // Cached frames per second        private double m_ticksPerFrame;                                 // Cached ticks per frame        private long m_timeResolution;                                  // Cached time resolution (max sorting resolution in ticks)        private DownsamplingMethod m_downsamplingMethod;                // Cached downsampling method        private bool m_disposed;                                        // Object disposed flag         #endregion         #region [ Constructors ]         /// <summary>        /// Creates a new <see cref="FrameQueue"/>.        /// </summary>        internal FrameQueue(CreateNewFrameFunction createNewFrame)        {            m_createNewFrame = createNewFrame;            m_frameList = new LinkedList<TrackingFrame>();            m_frameHash = new ConcurrentDictionary<long, TrackingFrame>();            m_queueLock = new SpinLock();            m_downsamplingMethod = DownsamplingMethod.LastReceived;        }         /// <summary>        /// Releases the unmanaged resources before the <see cref="FrameQueue"/> object is reclaimed by <see cref="GC"/>.        /// </summary>        ~FrameQueue()        {            Dispose(false);        }         #endregion         #region [ Properties ]          /// <summary>        /// Gets or sets the number of frames per second to be used by <see cref="FrameQueue"/>.        /// </summary>        /// <remarks>        /// Valid frame rates are greater than 0 frames per second.        /// </remarks>        public int FramesPerSecond        {            get            {                return m_framesPerSecond;            }            set            {                if (value < 1)                    throw new ArgumentOutOfRangeException("value", "Frames per second must be greater than 0");                 m_framesPerSecond = value;                m_ticksPerFrame = Ticks.PerSecond / (double)m_framesPerSecond;            }        }         /// <summary>        /// Gets or sets the maximum time resolution to use when sorting measurements by timestamps into their proper destination frame.        /// </summary>        public long TimeResolution        {            get            {                return m_timeResolution;            }            set            {                m_timeResolution = value;            }        }         /// <summary>        /// Gets or sets the <see cref="GSF.TimeSeries.DownsamplingMethod"/> to be used by the <see cref="FrameQueue"/>.        /// </summary>        public DownsamplingMethod DownsamplingMethod        {            get            {                return m_downsamplingMethod;            }            set            {                m_downsamplingMethod = value;            }        }         /// <summary>        /// Returns the next <see cref="TrackingFrame"/> in the <see cref="FrameQueue"/>, if any.        /// </summary>        /// <remarks>        /// This property is tracked separately from the internal frame collection, as a        /// result this property may be called at any time without a locking penalty.        /// </remarks>        public TrackingFrame Head        {            get            {                // We track the head separately to avoid lock on frame list to safely access first item...                return m_head;            }        }         /// <summary>        /// Gets the last processed <see cref="TrackingFrame"/> in the <see cref="FrameQueue"/>.        /// </summary>        /// <remarks>        /// This property is tracked separately from the internal frame collection, as a        /// result this property may be called at any time without a locking penalty.        /// </remarks>        public TrackingFrame Last        {            get            {                return m_last;            }        }         /// <summary>        /// Returns the total number of frames in the <see cref="FrameQueue"/>.        /// </summary>        public int Count        {            get            {                return m_frameList.Count;            }        }         #endregion         #region [ Methods ]         /// <summary>        /// Releases all the resources used by the <see cref="FrameQueue"/> object.        /// </summary>        public void Dispose()        {            Dispose(true);            GC.SuppressFinalize(this);        }         /// <summary>        /// Releases the unmanaged resources used by the <see cref="FrameQueue"/> object and optionally releases the managed resources.        /// </summary>        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>        protected virtual void Dispose(bool disposing)        {            if (!m_disposed)            {                try                {                    if (disposing)                    {                        Clear();                        m_frameList = null;                        m_frameHash = null;                         m_createNewFrame = null;                        m_head = null;                        m_last = null;                    }                }                finally                {                    m_disposed = true;  // Prevent duplicate dispose.                }            }        }         /// <summary>        /// Examines and returns the status of the <see cref="FrameQueue"/>.        /// </summary>        /// <param name="expectedMeasurements">Number of expected measurements per frame.</param>        public string ExamineQueueState(int expectedMeasurements)        {            StringBuilder status = new StringBuilder();            bool locked = false;             try            {                m_queueLock.Enter(ref locked);                 status.AppendLine("Concentrator frame queue detail:");                status.AppendLine();                status.AppendLine();                status.AppendFormat(" Ordered frame queue count: {0}", m_frameList.Count);                status.AppendLine();                status.AppendFormat("     Frame hashtable count: {0}", m_frameHash.Count);                status.AppendLine();                 if (m_frameList.Count > 0)                {                    LinkedListNode<TrackingFrame> node = m_frameList.First;                    IFrame frame;                    status.AppendLine();                     for (int i = 0; i < m_frameList.Count; i++)                    {                        if (node.Value != null)                            frame = node.Value.SourceFrame;                        else                            frame = null;                         if (frame == null)                            status.AppendFormat("Frame {0} @ <null frame>", i.ToString().PadLeft(4, '0'));                        else                            status.AppendFormat("Frame {0} @ {1} - {2} measurements, {3} received",                                i.ToString().PadLeft(4, '0'),                                (new DateTime(frame.Timestamp)).ToString("dd-MMM-yyyy HH:mm:ss.fff"),                                frame.Measurements.Count,                                (frame.Measurements.Count / (double)expectedMeasurements).ToString("##0.00%"));                         status.AppendLine();                        node = node.Next;                    }                }                 return status.ToString();            }            finally            {                if (locked)                    m_queueLock.Exit();            }        }         /// <summary>        /// Clears the <see cref="FrameQueue"/>.        /// </summary>        public void Clear()        {            bool locked = false;             try            {                m_queueLock.Enter(ref locked);                 if (m_frameList != null)                    m_frameList.Clear();                 if (m_frameHash != null)                    m_frameHash.Clear();            }            finally            {                if (locked)                    m_queueLock.Exit();            }        }         /// <summary>        /// Removes current <see cref="Head"/> frame from the <see cref="FrameQueue"/> after it has been processed and assigns a new <see cref="Head"/>.        /// </summary>        public void Pop()        {            // We track latest published ticks - don't want to allow slow moving measurements            // to inject themselves after a certain publication timeframe has passed - this            // avoids any possible out-of-sequence frame publication...            m_last = m_head;            m_head = null;            long publishedTicks = m_last.Timestamp;            Thread.VolatileWrite(ref m_publishedTicks, publishedTicks);            bool locked = false;             // Assign next node, if any, as quickly as possible. Still have to wait for queue            // lock - tick-tock, time's-a-wastin' and user function needs a frame to publish.            try            {                m_queueLock.Enter(ref locked);                 if (m_frameList.Count > 0)                {                    LinkedListNode<TrackingFrame> nextNode = m_frameList.First.Next;                     // If next frame is available, go ahead and assign it...                    if (nextNode != null)                        m_head = nextNode.Value;                     // Clean up frame queues                    m_frameList.RemoveFirst();                }            }            finally            {                if (locked)                    m_queueLock.Exit();            }             TrackingFrame frame;            m_frameHash.TryRemove(publishedTicks, out frame);        }         /// <summary>        /// Gets <see cref="TrackingFrame"/> from the queue with the specified timestamp, in ticks.  If no frame exists for        /// the specified timestamp, one will be created.        /// </summary>        /// <param name="ticks">Timestamp, in ticks, for which to get or create <see cref="TrackingFrame"/>.</param>        /// <remarks>        /// Ticks can be any point in time so long time requested is greater than time of last published frame; this queue is        /// used in a real-time scenario with time moving forward.  If a frame is requested for an old timestamp, null will        /// be returned. Note that frame returned will be "best-fit" for given timestamp based on <see cref="FramesPerSecond"/>.        /// </remarks>        /// <returns>An existing or new <see cref="TrackingFrame"/> from the queue for the specified timestamp.</returns>        public TrackingFrame GetFrame(long ticks)        {            // Calculate destination ticks for this frame            TrackingFrame frame = null;            bool locked = false, nodeAdded = false;            long baseTicks, ticksBeyondSecond, frameIndex, destinationTicks, nextDestinationTicks;             // Baseline timestamp to the top of the second            baseTicks = ticks - ticks % Ticks.PerSecond;             // Remove the seconds from ticks            ticksBeyondSecond = ticks - baseTicks;             // Calculate a frame index between 0 and m_framesPerSecond-1, corresponding to ticks            // rounded down to the nearest frame            frameIndex = (long)(ticksBeyondSecond / m_ticksPerFrame);             // Calculate the timestamp of the nearest frame rounded up            nextDestinationTicks = (frameIndex + 1) * Ticks.PerSecond / m_framesPerSecond;             // Determine whether the desired frame is the nearest            // frame rounded down or the nearest frame rounded up            if (m_timeResolution <= 1)            {                if (nextDestinationTicks <= ticksBeyondSecond)                    destinationTicks = nextDestinationTicks;                else                    destinationTicks = frameIndex * Ticks.PerSecond / m_framesPerSecond;            }            else            {                // If, after translating nextDestinationTicks to the time resolution, it is less than                // or equal to ticks, nextDestinationTicks corresponds to the desired frame                if ((nextDestinationTicks / m_timeResolution) * m_timeResolution <= ticksBeyondSecond)                    destinationTicks = nextDestinationTicks;                else                    destinationTicks = frameIndex * Ticks.PerSecond / m_framesPerSecond;            }             // Recover the seconds that were removed            destinationTicks += baseTicks;             // Make sure ticks are newer than latest published ticks...            if (destinationTicks > Thread.VolatileRead(ref m_publishedTicks))            {                // See if requested frame is already available (can do this outside lock with concurrent dictionary)                if (m_frameHash.TryGetValue(destinationTicks, out frame))                    return frame;                 // Didn't find frame for this timestamp so we need to add a new one to the queue                try                {                    m_queueLock.Enter(ref locked);                     // Another thread may have gotten to this task already, so check for this contingency...                    if (m_frameHash.TryGetValue(destinationTicks, out frame))                        return frame;                     // Create a new frame for this timestamp                    frame = new TrackingFrame(m_createNewFrame(destinationTicks), m_downsamplingMethod);                     if (m_frameList.Count > 0)                    {                        // Insert frame into proper sorted position...                        LinkedListNode<TrackingFrame> node = m_frameList.Last;                         do                        {                            if (destinationTicks > node.Value.Timestamp)                            {                                m_frameList.AddAfter(node, frame);                                nodeAdded = true;                                break;                            }                             node = node.Previous;                        }                        while (node != null);                    }                     if (!nodeAdded)                    {                        m_frameList.AddFirst(frame);                        m_head = frame;                    }                     // Since we'll be requesting this frame over and over, we'll use                    // a hash table for quick frame lookups by timestamp                    m_frameHash[destinationTicks] = frame;                }                finally                {                    if (locked)                        m_queueLock.Exit();                }            }             return frame;        }         #region [ GetFrame Testing Algorithm ]         // Copy this code into a console application and reference GSF.Core.dll to test.         //using System;        //using System.Collections.Generic;        //using System.Linq;        //using System.Text;        //using GSF;         //namespace FrameTimestampTest        //{        //    public class Program        //    {        //        private static long s_timeResolution;        //        private static long s_framesPerSecond;        //        private static double s_ticksPerFrame;        //        private static double s_millisecondsPerFrame;         //        public static void Main(string[] args)        //        {        //            s_framesPerSecond = 30;        //            s_ticksPerFrame = Ticks.PerSecond / (double)s_framesPerSecond;        //            s_timeResolution = Ticks.PerMillisecond;        //            s_millisecondsPerFrame = 1000.0 / s_framesPerSecond;         //            // Test BPA PDCstream style milliseconds        //            DateTime currentTime = DateTime.Now;        //            int[] bpaMilliseconds = new int[] { 000, 033, 066, 100, 133, 166, 200, 233, 266, 300, 333, 366, 400, 433, 466, 500, 533, 566, 599, 633, 666, 699, 733, 766, 800, 833, 866, 900, 933, 966 };         //            s_timeResolution = Ticks.PerMillisecond * 33;        //            Console.WriteLine("BPA millisecond sorting test, TimeResolution = {0}", s_timeResolution);         //            for (int i = 0; i < bpaMilliseconds.Length; i++)        //            {        //                long longTicks = (new DateTime(currentTime.Year, currentTime.Month, currentTime.Day, currentTime.Hour, currentTime.Minute, currentTime.Second, bpaMilliseconds[i])).Ticks;        //                long destination = GetFrame(longTicks);        //                Console.WriteLine(string.Format("{0:000} ms : {1:000} ms", bpaMilliseconds[i], (new DateTime(destination)).Millisecond));        //            }         //            s_timeResolution = Ticks.PerMillisecond;

Lines 1–500 of 595.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t