//******************************************************************************************************// PrecisionInputTimer.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:// ----------------------------------------------------------------------------------------------------// 07/25/2012 - J. Ritchie Carroll// Generated original version of source code.////****************************************************************************************************** using System;using System.Collections.Generic;using System.Threading; namespace GSF.TimeSeries{ /// <summary> /// Precision input timer. /// </summary> /// <remarks> /// This class is used to create highly accurate simulated data inputs aligned to the local clock.<br/> /// One static instance of this internal class is created per encountered frame rate. /// </remarks> public sealed class PrecisionInputTimer : IDisposable { #region [ Members ] // Fields private PrecisionTimer m_timer; private bool m_useWaitHandleA; private SpinLock m_timerTickLock; private ManualResetEventSlim m_frameWaitHandleA; private ManualResetEventSlim m_frameWaitHandleB; private readonly int m_framesPerSecond; private readonly int m_frameWindowSize; private readonly int[] m_frameMilliseconds; private int m_lastFrameIndex; private long m_lastFrameTime; private long m_missedPublicationWindows; private long m_lastMissedWindowTime; private long m_resynchronizations; private int m_referenceCount; private Action<Exception> m_exceptionHandler; private bool m_disposed; #endregion #region [ Constructors ] /// <summary> /// Create a new <see cref="PrecisionInputTimer"/> class. /// </summary> /// <param name="framesPerSecond">Desired frame rate for <see cref="PrecisionTimer"/>.</param> internal PrecisionInputTimer(int framesPerSecond) { // Create synchronization objects m_timerTickLock = new SpinLock(); m_frameWaitHandleA = new ManualResetEventSlim(false); m_frameWaitHandleB = new ManualResetEventSlim(false); m_useWaitHandleA = true; m_framesPerSecond = framesPerSecond; // Create a new precision timer for this timer state m_timer = new PrecisionTimer(); m_timer.Resolution = 1; m_timer.Period = 1; m_timer.AutoReset = true; // Attach handler for timer ticks m_timer.Tick += m_timer_Tick; m_frameWindowSize = (int)Math.Round(1000.0D / framesPerSecond) * 2; m_frameMilliseconds = new int[framesPerSecond]; for (int frameIndex = 0; frameIndex < framesPerSecond; frameIndex++) { m_frameMilliseconds[frameIndex] = (int)(1.0D / framesPerSecond * (frameIndex * 1000.0D)); } // Start high resolution timer on a separate thread so the start // time can synchronized to the top of the millisecond ThreadPool.QueueUserWorkItem(SynchronizeInputTimer); } /// <summary> /// Releases the unmanaged resources before the <see cref="PrecisionInputTimer"/> object is reclaimed by <see cref="GC"/>. /// </summary> ~PrecisionInputTimer() { Dispose(false); } #endregion #region [ Properties ] /// <summary> /// Gets frames per second for this <see cref="PrecisionInputTimer"/>. /// </summary> public int FramesPerSecond { get { return m_framesPerSecond; } } /// <summary> /// Gets array of frame millisecond times for this <see cref="PrecisionInputTimer"/>. /// </summary> public int[] FrameMilliseconds { get { return m_frameMilliseconds; } } /// <summary> /// Gets reference count for this <see cref="PrecisionInputTimer"/>. /// </summary> public int ReferenceCount { get { return m_referenceCount; } } /// <summary> /// Gets number of resynchronizations that have occurred for this <see cref="PrecisionInputTimer"/>. /// </summary> public long Resynchronizations { get { return m_resynchronizations; } } /// <summary> /// Gets time of last frame, in ticks. /// </summary> public long LastFrameTime { get { return m_lastFrameTime; } } /// <summary> /// Gets a reference to the frame wait handle. /// </summary> public ManualResetEventSlim FrameWaitHandle { get { if (m_useWaitHandleA) return m_frameWaitHandleA; return m_frameWaitHandleB; } } /// <summary> /// Gets or sets function used to handle exceptions. /// </summary> public Action<Exception> ExceptionHandler { get { return m_exceptionHandler; } set { m_exceptionHandler = value; } } #endregion #region [ Methods ] /// <summary> /// Releases all the resources used by the <see cref="PrecisionInputTimer"/> object. /// </summary> public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// <summary> /// Releases the unmanaged resources used by the <see cref="PrecisionInputTimer"/> 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> private void Dispose(bool disposing) { if (!m_disposed) { try { if (disposing) { if (m_timer != null) { m_timer.Tick -= m_timer_Tick; m_timer.Dispose(); } m_timer = null; if (m_frameWaitHandleA != null) { m_frameWaitHandleA.Set(); m_frameWaitHandleA.Dispose(); } m_frameWaitHandleA = null; if (m_frameWaitHandleB != null) { m_frameWaitHandleB.Set(); m_frameWaitHandleB.Dispose(); } m_frameWaitHandleB = null; } } finally { m_disposed = true; // Prevent duplicate dispose. } } } /// <summary> /// Adds a reference to this <see cref="PrecisionInputTimer"/>. /// </summary> public void AddReference() { m_referenceCount++; } /// <summary> /// Removes a reference to this <see cref="PrecisionInputTimer"/>. /// </summary> public void RemoveReference() { m_referenceCount--; } // This timer function is called every millisecond so that frames can be published at the exact desired time private void m_timer_Tick(object sender, EventArgs e) { // Slower systems or systems under stress may have trouble keeping up with a 1-ms timer, so // we only process this code if it's not already processing... bool locked = false; try { m_timerTickLock.TryEnter(2, ref locked); if (locked) { DateTime now = PrecisionTimer.UtcNow; int frameMilliseconds, milliseconds = now.Millisecond; long ticks = now.Ticks; bool releaseTimer = false, resync = false; // Make sure current time is reasonably close to current frame index if (Math.Abs(milliseconds - m_frameMilliseconds[m_lastFrameIndex]) > m_frameWindowSize) m_lastFrameIndex = 0; // See if it is time to publish for (int frameIndex = m_lastFrameIndex; frameIndex < m_frameMilliseconds.Length; frameIndex++) { frameMilliseconds = m_frameMilliseconds[frameIndex]; if (frameMilliseconds == milliseconds) { // See if system skipped a publication window if (m_lastFrameIndex != frameIndex) { // We monitor for missed windows in quick succession (within 1.5 seconds) if (ticks - m_lastMissedWindowTime > 15000000L) { // Threshold has passed since last missed window, so we reset counters m_lastMissedWindowTime = ticks; m_missedPublicationWindows = 0; } m_missedPublicationWindows++; // If the system is starting to skip publications it could need resynchronization, // so in this case we restart the high-resolution timer to get the timer started // closer to the top of the millisecond resync = (m_missedPublicationWindows > 4); } // Prepare index for next check, time moving forward m_lastFrameIndex = frameIndex + 1; if (m_lastFrameIndex >= m_frameMilliseconds.Length) m_lastFrameIndex = 0; if (resync) { if (m_timer != null) { m_timer.Stop(); ThreadPool.QueueUserWorkItem(SynchronizeInputTimer); m_resynchronizations++; } } releaseTimer = true; break; } else if (frameMilliseconds > milliseconds) { // Time has yet to pass, wait till the next tick break; } } if (releaseTimer) { // Baseline timestamp to the top of the millisecond for frame publication m_lastFrameTime = ticks - ticks % Ticks.PerMillisecond; // Pulse all waiting threads toggling between ready handles if (m_useWaitHandleA) { m_frameWaitHandleB.Reset(); m_useWaitHandleA = false; m_frameWaitHandleA.Set(); } else { m_frameWaitHandleA.Reset(); m_useWaitHandleA = true; m_frameWaitHandleB.Set(); } } } } catch (Exception ex) { ExceptionHandler(new InvalidOperationException("Exception thrown by precision input timer: " + ex.Message, ex)); } finally { if (locked) m_timerTickLock.Exit(); } } private void SynchronizeInputTimer(object state) { // Start timer at as close to the top of the millisecond as possible bool repeat = true; long last = 0, next; while (repeat) { next = PrecisionTimer.UtcNow.Ticks % Ticks.PerMillisecond % 1000; repeat = (next > last); last = next; } m_lastMissedWindowTime = 0; m_missedPublicationWindows = 0; if (m_timer != null) m_timer.Start(); } #endregion #region [ Static ] // Static Fields private static readonly Dictionary<int, PrecisionInputTimer> s_inputTimers = new Dictionary<int, PrecisionInputTimer>(); // Static Methods public static PrecisionInputTimer Attach(int framesPerSecond) { return Attach(framesPerSecond, null); } /// <summary> /// Attach to a <see cref="PrecisionInputTimer"/> for the specified <paramref name="framesPerSecond"/>. /// </summary> /// <param name="framesPerSecond">Desired frames per second for the input timer.</param> /// <param name="exceptionHandler">Optional delegate to handle exception reporting from the timer.</param> /// /// <returns>A <see cref="PrecisionInputTimer"/> that can be used for the specified <paramref name="framesPerSecond"/>.</returns> public static PrecisionInputTimer Attach(int framesPerSecond, Action<Exception> exceptionHandler) { PrecisionInputTimer timer; lock (s_inputTimers) { // Get static input timer for given frames per second creating it if needed if (!s_inputTimers.TryGetValue(framesPerSecond, out timer)) { try { // Create a new precision input timer timer = new PrecisionInputTimer(framesPerSecond); timer.ExceptionHandler = exceptionHandler; // Add timer state for given rate to static collection s_inputTimers.Add(framesPerSecond, timer); } catch (Exception ex) { // Process exception for logging if ((object)exceptionHandler != null) exceptionHandler(new InvalidOperationException("Failed to create precision input timer due to exception: " + ex.Message, ex)); else throw; } } // Increment reference count for input timer at given frame rate timer.AddReference(); } return timer; } /// <summary> /// Detatch from the <see cref="PrecisionInputTimer"/>. /// </summary> /// <param name="timer">Timer instance to detach from.</param> /// <remarks> /// Timer reference will be set to <c>null</c> after detatch. /// </remarks> public static void Detach(ref PrecisionInputTimer timer) { if ((object)timer != null) { lock (s_inputTimers) { // Verify static frame rate timer for given frames per second exists if (s_inputTimers.ContainsKey(timer.FramesPerSecond)) { // Decrement reference count timer.RemoveReference(); // If timer is no longer being referenced we stop it and remove it from static collection if (timer.ReferenceCount == 0) { timer.Dispose(); s_inputTimers.Remove(timer.FramesPerSecond); } } } } timer = null; } #endregion }}
//******************************************************************************************************
// PrecisionInputTimer.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:
// ----------------------------------------------------------------------------------------------------
// 07/25/2012 - J. Ritchie Carroll
// Generated original version of source code.
//
//******************************************************************************************************
using System;
using System.Collections.Generic;
using System.Threading;
namespace GSF.TimeSeries
{
/// <summary>
/// Precision input timer.
/// </summary>
/// <remarks>
/// This class is used to create highly accurate simulated data inputs aligned to the local clock.<br/>
/// One static instance of this internal class is created per encountered frame rate.
/// </remarks>
public sealed class PrecisionInputTimer : IDisposable
{
#region [ Members ]
// Fields
private PrecisionTimer m_timer;
private bool m_useWaitHandleA;
private SpinLock m_timerTickLock;
private ManualResetEventSlim m_frameWaitHandleA;
private ManualResetEventSlim m_frameWaitHandleB;
private readonly int m_framesPerSecond;
private readonly int m_frameWindowSize;
private readonly int[] m_frameMilliseconds;
private int m_lastFrameIndex;
private long m_lastFrameTime;
private long m_missedPublicationWindows;
private long m_lastMissedWindowTime;
private long m_resynchronizations;
private int m_referenceCount;
private Action<Exception> m_exceptionHandler;
private bool m_disposed;
#endregion
#region [ Constructors ]
/// <summary>
/// Create a new <see cref="PrecisionInputTimer"/> class.
/// </summary>
/// <param name="framesPerSecond">Desired frame rate for <see cref="PrecisionTimer"/>.</param>
internal PrecisionInputTimer(int framesPerSecond)
{
// Create synchronization objects
m_timerTickLock = new SpinLock();
m_frameWaitHandleA = new ManualResetEventSlim(false);
m_frameWaitHandleB = new ManualResetEventSlim(false);
m_useWaitHandleA = true;
m_framesPerSecond = framesPerSecond;
// Create a new precision timer for this timer state
m_timer = new PrecisionTimer();
m_timer.Resolution = 1;
m_timer.Period = 1;
m_timer.AutoReset = true;
// Attach handler for timer ticks
m_timer.Tick += m_timer_Tick;
m_frameWindowSize = (int)Math.Round(1000.0D / framesPerSecond) * 2;
m_frameMilliseconds = new int[framesPerSecond];
for (int frameIndex = 0; frameIndex < framesPerSecond; frameIndex++)
{
m_frameMilliseconds[frameIndex] = (int)(1.0D / framesPerSecond * (frameIndex * 1000.0D));
}
// Start high resolution timer on a separate thread so the start
// time can synchronized to the top of the millisecond
ThreadPool.QueueUserWorkItem(SynchronizeInputTimer);
}
/// <summary>
/// Releases the unmanaged resources before the <see cref="PrecisionInputTimer"/> object is reclaimed by <see cref="GC"/>.
/// </summary>
~PrecisionInputTimer()
{
Dispose(false);
}
#endregion
#region [ Properties ]
/// <summary>
/// Gets frames per second for this <see cref="PrecisionInputTimer"/>.
/// </summary>
public int FramesPerSecond
{
get
{
return m_framesPerSecond;
}
}
/// <summary>
/// Gets array of frame millisecond times for this <see cref="PrecisionInputTimer"/>.
/// </summary>
public int[] FrameMilliseconds
{
get
{
return m_frameMilliseconds;
}
}
/// <summary>
/// Gets reference count for this <see cref="PrecisionInputTimer"/>.
/// </summary>
public int ReferenceCount
{
get
{
return m_referenceCount;
}
}
/// <summary>
/// Gets number of resynchronizations that have occurred for this <see cref="PrecisionInputTimer"/>.
/// </summary>
public long Resynchronizations
{
get
{
return m_resynchronizations;
}
}
/// <summary>
/// Gets time of last frame, in ticks.
/// </summary>
public long LastFrameTime
{
get
{
return m_lastFrameTime;
}
}
/// <summary>
/// Gets a reference to the frame wait handle.
/// </summary>
public ManualResetEventSlim FrameWaitHandle
{
get
{
if (m_useWaitHandleA)
return m_frameWaitHandleA;
return m_frameWaitHandleB;
}
}
/// <summary>
/// Gets or sets function used to handle exceptions.
/// </summary>
public Action<Exception> ExceptionHandler
{
get
{
return m_exceptionHandler;
}
set
{
m_exceptionHandler = value;
}
}
#endregion
#region [ Methods ]
/// <summary>
/// Releases all the resources used by the <see cref="PrecisionInputTimer"/> object.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases the unmanaged resources used by the <see cref="PrecisionInputTimer"/> 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>
private void Dispose(bool disposing)
{
if (!m_disposed)
{
try
{
if (disposing)
{
if (m_timer != null)
{
m_timer.Tick -= m_timer_Tick;
m_timer.Dispose();
}
m_timer = null;
if (m_frameWaitHandleA != null)
{
m_frameWaitHandleA.Set();
m_frameWaitHandleA.Dispose();
}
m_frameWaitHandleA = null;
if (m_frameWaitHandleB != null)
{
m_frameWaitHandleB.Set();
m_frameWaitHandleB.Dispose();
}
m_frameWaitHandleB = null;
}
}
finally
{
m_disposed = true; // Prevent duplicate dispose.
}
}
}
/// <summary>
/// Adds a reference to this <see cref="PrecisionInputTimer"/>.
/// </summary>
public void AddReference()
{
m_referenceCount++;
}
/// <summary>
/// Removes a reference to this <see cref="PrecisionInputTimer"/>.
/// </summary>
public void RemoveReference()
{
m_referenceCount--;
}
// This timer function is called every millisecond so that frames can be published at the exact desired time
private void m_timer_Tick(object sender, EventArgs e)
{
// Slower systems or systems under stress may have trouble keeping up with a 1-ms timer, so
// we only process this code if it's not already processing...
bool locked = false;
try
{
m_timerTickLock.TryEnter(2, ref locked);
if (locked)
{
DateTime now = PrecisionTimer.UtcNow;
int frameMilliseconds, milliseconds = now.Millisecond;
long ticks = now.Ticks;
bool releaseTimer = false, resync = false;
// Make sure current time is reasonably close to current frame index
if (Math.Abs(milliseconds - m_frameMilliseconds[m_lastFrameIndex]) > m_frameWindowSize)
m_lastFrameIndex = 0;
// See if it is time to publish
for (int frameIndex = m_lastFrameIndex; frameIndex < m_frameMilliseconds.Length; frameIndex++)
{
frameMilliseconds = m_frameMilliseconds[frameIndex];
if (frameMilliseconds == milliseconds)
{
// See if system skipped a publication window
if (m_lastFrameIndex != frameIndex)
{
// We monitor for missed windows in quick succession (within 1.5 seconds)
if (ticks - m_lastMissedWindowTime > 15000000L)
{
// Threshold has passed since last missed window, so we reset counters
m_lastMissedWindowTime = ticks;
m_missedPublicationWindows = 0;
}
m_missedPublicationWindows++;
// If the system is starting to skip publications it could need resynchronization,
// so in this case we restart the high-resolution timer to get the timer started
// closer to the top of the millisecond
resync = (m_missedPublicationWindows > 4);
}
// Prepare index for next check, time moving forward
m_lastFrameIndex = frameIndex + 1;
if (m_lastFrameIndex >= m_frameMilliseconds.Length)
m_lastFrameIndex = 0;
if (resync)
{
if (m_timer != null)
{
m_timer.Stop();
ThreadPool.QueueUserWorkItem(SynchronizeInputTimer);
m_resynchronizations++;
}
}
releaseTimer = true;
break;
}
else if (frameMilliseconds > milliseconds)
{
// Time has yet to pass, wait till the next tick
break;
}
}
if (releaseTimer)
{
// Baseline timestamp to the top of the millisecond for frame publication
m_lastFrameTime = ticks - ticks % Ticks.PerMillisecond;
// Pulse all waiting threads toggling between ready handles
if (m_useWaitHandleA)
{
m_frameWaitHandleB.Reset();
m_useWaitHandleA = false;
m_frameWaitHandleA.Set();
}
else
{
m_frameWaitHandleA.Reset();
m_useWaitHandleA = true;
m_frameWaitHandleB.Set();
}
}
}
}
catch (Exception ex)
{
ExceptionHandler(new InvalidOperationException("Exception thrown by precision input timer: " + ex.Message, ex));
}
finally
{
if (locked)
m_timerTickLock.Exit();
}
}
private void SynchronizeInputTimer(object state)
{
// Start timer at as close to the top of the millisecond as possible
bool repeat = true;
long last = 0, next;
while (repeat)
{
next = PrecisionTimer.UtcNow.Ticks % Ticks.PerMillisecond % 1000;
repeat = (next > last);
last = next;
}
m_lastMissedWindowTime = 0;
m_missedPublicationWindows = 0;
if (m_timer != null)
m_timer.Start();
}
#endregion
#region [ Static ]
// Static Fields
private static readonly Dictionary<int, PrecisionInputTimer> s_inputTimers = new Dictionary<int, PrecisionInputTimer>();
// Static Methods
public static PrecisionInputTimer Attach(int framesPerSecond)
{
return Attach(framesPerSecond, null);
}
/// <summary>
/// Attach to a <see cref="PrecisionInputTimer"/> for the specified <paramref name="framesPerSecond"/>.
/// </summary>
/// <param name="framesPerSecond">Desired frames per second for the input timer.</param>
/// <param name="exceptionHandler">Optional delegate to handle exception reporting from the timer.</param>
/// /// <returns>A <see cref="PrecisionInputTimer"/> that can be used for the specified <paramref name="framesPerSecond"/>.</returns>
public static PrecisionInputTimer Attach(int framesPerSecond, Action<Exception> exceptionHandler)
{
PrecisionInputTimer timer;
lock (s_inputTimers)
{
// Get static input timer for given frames per second creating it if needed
if (!s_inputTimers.TryGetValue(framesPerSecond, out timer))
{
try
{
// Create a new precision input timer
timer = new PrecisionInputTimer(framesPerSecond);
timer.ExceptionHandler = exceptionHandler;
// Add timer state for given rate to static collection
s_inputTimers.Add(framesPerSecond, timer);
}
catch (Exception ex)
{
// Process exception for logging
if ((object)exceptionHandler != null)
exceptionHandler(new InvalidOperationException("Failed to create precision input timer due to exception: " + ex.Message, ex));
else
throw;
}
}
// Increment reference count for input timer at given frame rate
timer.AddReference();
}
return timer;
}
/// <summary>
/// Detatch from the <see cref="PrecisionInputTimer"/>.
/// </summary>
/// <param name="timer">Timer instance to detach from.</param>
/// <remarks>
/// Timer reference will be set to <c>null</c> after detatch.
/// </remarks>
public static void Detach(ref PrecisionInputTimer timer)
{
if ((object)timer != null)
{
lock (s_inputTimers)
{
// Verify static frame rate timer for given frames per second exists
if (s_inputTimers.ContainsKey(timer.FramesPerSecond))
{
// Decrement reference count
timer.RemoveReference();
// If timer is no longer being referenced we stop it and remove it from static collection
if (timer.ReferenceCount == 0)
{
timer.Dispose();
s_inputTimers.Remove(timer.FramesPerSecond);
}
}
}
}
timer = null;
}
#endregion
}
}