//****************************************************************************************************** // IaonSession.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: // ---------------------------------------------------------------------------------------------------- // 08/23/2011 - J. Ritchie Carroll // Generated original version of source code. // //****************************************************************************************************** using GSF.Collections; using GSF.Configuration; using System; using System.Collections.Concurrent; using System.Data; using System.Linq; using System.Text; using System.Threading; namespace GSF.TimeSeries.Adapters { /// /// Represents a new Input, Action, Output interface session. /// public class IaonSession : IProvideStatus, IDisposable { #region [ Members ] // Constants /// /// Default value for property. /// public const bool DefaultUseMeasurementRouting = true; // Events /// /// Provides status messages to consumer. /// /// /// is the new status message.
/// is the message . ///
public event EventHandler> StatusMessage; /// /// Event is raised when there is an exception encountered while processing. /// /// /// is the exception that was thrown. /// public event EventHandler> ProcessException; /// /// Event is raised when are updated. /// public event EventHandler InputMeasurementKeysUpdated; /// /// Event is raised when are updated. /// public event EventHandler OutputMeasurementsUpdated; /// /// Event is raised every five seconds allowing consumer to track current number of unpublished seconds of data in the queue. /// /// /// is the total number of unpublished seconds of data. /// public event EventHandler> UnpublishedSamples; /// /// Event is raised every five seconds allowing host to track total number of unprocessed measurements. /// /// /// /// Each implementation reports its current queue size of unprocessed /// measurements so that if queue size reaches an unhealthy threshold, host can take evasive action. /// /// /// is total number of unprocessed measurements. /// /// public event EventHandler> UnprocessedMeasurements; /// /// Indicates to the host that processing for one of the input adapters has completed. /// /// /// This event is expected to only be raised when an input adapter has been designed to process /// a finite amount of data, e.g., reading a historical range of data during temporal procesing. /// public event EventHandler ProcessingComplete; /// /// Event is raised when this is disposed or an in the collection is disposed. /// public event EventHandler Disposed; // Fields private Guid m_nodeID; private RoutingTables m_routingTables; private AllAdaptersCollection m_allAdapters; private InputAdapterCollection m_inputAdapters; private ActionAdapterCollection m_actionAdapters; private OutputAdapterCollection m_outputAdapters; private ConcurrentDictionary m_waitHandles; private ConcurrentDictionary m_derivedNameCache; private MeasurementKey[] m_inputMeasurementKeysRestriction; private bool m_useMeasurementRouting; private int m_measurementWarningThreshold; private int m_measurementDumpingThreshold; private int m_defaultSampleSizeWarningThreshold; private string m_name; private bool m_disposed; #endregion #region [ Constructors ] /// /// Creates a new . /// public IaonSession() { //ConfigurationFile configFile = ConfigurationFile.Current; // Initialize system settings //CategorizedSettingsElementCollection systemSettings = configFile.Settings["systemSettings"]; //systemSettings.Add("NodeID", Guid.NewGuid().ToString(), "Unique Node ID"); //systemSettings.Add("UseMeasurementRouting", DefaultUseMeasurementRouting, "Set to true to use optimized adapter measurement routing."); //m_nodeID = systemSettings["NodeID"].ValueAs(); //m_useMeasurementRouting = systemSettings["UseMeasurementRouting"].ValueAsBoolean(IaonSession.DefaultUseMeasurementRouting); m_nodeID = Guid.NewGuid(); m_useMeasurementRouting = true; //// Initialize threshold settings //CategorizedSettingsElementCollection thresholdSettings = configFile.Settings["thresholdSettings"]; //thresholdSettings.Add("MeasurementWarningThreshold", "100000", "Number of unarchived measurements allowed in any output adapter queue before displaying a warning message"); //thresholdSettings.Add("MeasurementDumpingThreshold", "500000", "Number of unarchived measurements allowed in any output adapter queue before taking evasive action and dumping data"); //thresholdSettings.Add("DefaultSampleSizeWarningThreshold", "10", "Default number of unpublished samples (in seconds) allowed in any action adapter queue before displaying a warning message"); //m_measurementWarningThreshold = thresholdSettings["MeasurementWarningThreshold"].ValueAsInt32(); //m_measurementDumpingThreshold = thresholdSettings["MeasurementDumpingThreshold"].ValueAsInt32(); //m_defaultSampleSizeWarningThreshold = thresholdSettings["DefaultSampleSizeWarningThreshold"].ValueAsInt32(); m_measurementWarningThreshold = 100000; m_measurementDumpingThreshold = 500000; m_defaultSampleSizeWarningThreshold = 10; // Create a common wait handle dictionary for all adapters in this session m_waitHandles = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); // Create a cache for derived adapter names m_derivedNameCache = new ConcurrentDictionary(); // Create a new set of routing tables m_routingTables = new RoutingTables(); m_routingTables.ProcessException += m_routingTables_ProcessException; // Create a collection to manage all input, action and output adapter collections as a unit m_allAdapters = new AllAdaptersCollection(m_waitHandles); // Attach to common adapter events m_allAdapters.StatusMessage += StatusMessageHandler; m_allAdapters.ProcessException += ProcessExceptionHandler; m_allAdapters.InputMeasurementKeysUpdated += InputMeasurementKeysUpdatedHandler; m_allAdapters.OutputMeasurementsUpdated += OutputMeasurementsUpdatedHandler; m_allAdapters.Disposed += DisposedHandler; // Create input adapters collection m_inputAdapters = new InputAdapterCollection(m_waitHandles); if (m_useMeasurementRouting) m_inputAdapters.NewMeasurements += m_routingTables.RoutedMeasurementsHandler; else m_inputAdapters.NewMeasurements += m_routingTables.BroadcastMeasurementsHandler; m_inputAdapters.ProcessMeasurementFilter = !m_useMeasurementRouting; m_inputAdapters.ProcessingComplete += ProcessingCompleteHandler; // Create action adapters collection m_actionAdapters = new ActionAdapterCollection(m_waitHandles); if (m_useMeasurementRouting) m_actionAdapters.NewMeasurements += m_routingTables.RoutedMeasurementsHandler; else m_actionAdapters.NewMeasurements += m_routingTables.BroadcastMeasurementsHandler; m_actionAdapters.ProcessMeasurementFilter = !m_useMeasurementRouting; m_actionAdapters.UnpublishedSamples += UnpublishedSamplesHandler; // Create output adapters collection m_outputAdapters = new OutputAdapterCollection(m_waitHandles); m_outputAdapters.ProcessMeasurementFilter = !m_useMeasurementRouting; m_outputAdapters.UnprocessedMeasurements += UnprocessedMeasurementsHandler; // Associate adapter collections with routing tables m_routingTables.InputAdapters = m_inputAdapters; m_routingTables.ActionAdapters = m_actionAdapters; m_routingTables.OutputAdapters = m_outputAdapters; // We group these adapters such that they are initialized in the following order: output, input, action. This // is done so that the archival capabilities will be setup before we start receiving input and the input data // will be flowing before any actions get established for the input - at least generally. m_allAdapters.Add(m_outputAdapters); m_allAdapters.Add(m_inputAdapters); m_allAdapters.Add(m_actionAdapters); } /// /// Releases the unmanaged resources before the object is reclaimed by . /// ~IaonSession() { Dispose(false); } #endregion #region [ Properties ] /// /// Gets the all adapters collection for this . /// public virtual AllAdaptersCollection AllAdapters { get { return m_allAdapters; } } /// /// Gets the input adapter collection for this . /// public virtual InputAdapterCollection InputAdapters { get { return m_inputAdapters; } } /// /// Gets the action adapter collection for this . /// public virtual ActionAdapterCollection ActionAdapters { get { return m_actionAdapters; } } /// /// Gets the output adapter collection for this . /// public virtual OutputAdapterCollection OutputAdapters { get { return m_outputAdapters; } } /// /// Gets or sets flag that determines if measurement routing should be used. /// public virtual bool UseMeasurementRouting { get { return m_useMeasurementRouting; } set { if (m_useMeasurementRouting != value) { if (m_useMeasurementRouting) m_inputAdapters.NewMeasurements -= m_routingTables.RoutedMeasurementsHandler; else m_inputAdapters.NewMeasurements -= m_routingTables.BroadcastMeasurementsHandler; if (m_useMeasurementRouting) m_actionAdapters.NewMeasurements -= m_routingTables.RoutedMeasurementsHandler; else m_actionAdapters.NewMeasurements -= m_routingTables.BroadcastMeasurementsHandler; m_useMeasurementRouting = value; if (m_useMeasurementRouting) m_inputAdapters.NewMeasurements += m_routingTables.RoutedMeasurementsHandler; else m_inputAdapters.NewMeasurements += m_routingTables.BroadcastMeasurementsHandler; if (m_useMeasurementRouting) m_actionAdapters.NewMeasurements += m_routingTables.RoutedMeasurementsHandler; else m_actionAdapters.NewMeasurements += m_routingTables.BroadcastMeasurementsHandler; m_inputAdapters.ProcessMeasurementFilter = !m_useMeasurementRouting; m_actionAdapters.ProcessMeasurementFilter = !m_useMeasurementRouting; m_outputAdapters.ProcessMeasurementFilter = !m_useMeasurementRouting; } } } /// /// Gets the routing tables for this . /// public virtual RoutingTables RoutingTables { get { return m_routingTables; } } /// /// Gets or sets a routing table restriction for a collection of input measurement keys. /// public virtual MeasurementKey[] InputMeasurementKeysRestriction { get { return m_inputMeasurementKeysRestriction; } set { m_inputMeasurementKeysRestriction = value; } } /// /// Gets or sets the configuration for this . /// public virtual DataSet DataSource { get { return m_allAdapters.DataSource; } set { m_allAdapters.DataSource = value; if (value != null) DetermineTemporalSupport(); } } /// /// Gets or sets the node ID for this . /// public virtual Guid NodeID { get { return m_nodeID; } set { m_nodeID = value; } } /// /// Gets name assigned to this , if any. /// public virtual string Name { get { return m_name.ToNonNullString(); } set { m_name = value; } } /// /// Gets the combined status of the adapters in this . /// public virtual string Status { get { StringBuilder status = new StringBuilder(); status.AppendLine(); status.AppendLine(">> Input Adapters:"); status.AppendLine(); if (m_inputAdapters != null) status.AppendLine(m_inputAdapters.Status); status.AppendLine(); status.AppendLine(">> Action Adapters:"); status.AppendLine(); if (m_actionAdapters != null) status.AppendLine(m_actionAdapters.Status); status.AppendLine(); status.AppendLine(">> Output Adapters:"); status.AppendLine(); if (m_outputAdapters != null) status.AppendLine(m_outputAdapters.Status); return status.ToString(); } } #endregion #region [ Methods ] /// /// Releases all the resources used by the object. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases the unmanaged resources used by the object and optionally releases the managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool disposing) { if (!m_disposed) { try { if (disposing) { DataSet dataSource = this.DataSource; // Dispose input adapters collection if (m_inputAdapters != null) { m_inputAdapters.Stop(); if (m_useMeasurementRouting) m_inputAdapters.NewMeasurements -= m_routingTables.RoutedMeasurementsHandler; else m_inputAdapters.NewMeasurements -= m_routingTables.BroadcastMeasurementsHandler; m_inputAdapters.ProcessingComplete -= ProcessingCompleteHandler; m_inputAdapters.Dispose(); } m_inputAdapters = null; // Dispose action adapters collection if (m_actionAdapters != null) { m_actionAdapters.Stop(); if (m_useMeasurementRouting) m_actionAdapters.NewMeasurements -= m_routingTables.RoutedMeasurementsHandler; else m_actionAdapters.NewMeasurements -= m_routingTables.BroadcastMeasurementsHandler; m_actionAdapters.UnpublishedSamples -= UnpublishedSamplesHandler; m_actionAdapters.Dispose(); } m_actionAdapters = null; // Dispose output adapters collection if (m_outputAdapters != null) { m_outputAdapters.Stop(); m_outputAdapters.UnprocessedMeasurements -= UnprocessedMeasurementsHandler; m_outputAdapters.Dispose(); } m_outputAdapters = null; // Dispose all adapters collection if (m_allAdapters != null) { m_allAdapters.StatusMessage -= StatusMessageHandler; m_allAdapters.ProcessException -= ProcessExceptionHandler; m_allAdapters.InputMeasurementKeysUpdated -= InputMeasurementKeysUpdatedHandler; m_allAdapters.OutputMeasurementsUpdated -= OutputMeasurementsUpdatedHandler; m_allAdapters.Disposed -= DisposedHandler; m_allAdapters.Dispose(); } m_allAdapters = null; // Dispose of routing tables if (m_routingTables != null) { m_routingTables.ProcessException -= m_routingTables_ProcessException; m_routingTables.Dispose(); } m_routingTables = null; // Dispose of wait handle dictionary if (m_waitHandles != null) { foreach (AutoResetEvent waitHandle in m_waitHandles.Values) { if (waitHandle != null) waitHandle.Close(); } m_waitHandles.Clear(); } m_waitHandles = null; if ((object)dataSource != null) dataSource.Dispose(); } } finally { m_disposed = true; // Prevent duplicate dispose. if (Disposed != null) Disposed(this, EventArgs.Empty); } } } public void Initialize() { Initialize(true); } /// /// Initialize and start adapters. /// /// Sets flag that determines if adapters should be automatically started. public virtual void Initialize(bool autoStart) { // Initialize all adapters m_allAdapters.Initialize(); // Spawn a task to establish temporal support after the adapters have initialized ThreadPool.QueueUserWorkItem(EstablishTemporalSupport); if (autoStart) { // Start all adapters m_allAdapters.Start(); // Spawn routing table calculation RecalculateRoutingTables(); } } private void EstablishTemporalSupport(object state) { int count = 0; // Wait for all adapters to initialize (up to one second) while (!m_allAdapters.SelectMany(col => col.Select(adapter => adapter.Initialized)).All(b => b) && count < 10) { Thread.Sleep(100); count++; } // Initialize temporal support tables DetermineTemporalSupport(); } /// /// Determines which adapters in the support temporal processing. /// protected virtual void DetermineTemporalSupport() { // Create a new temporal support identification table DataTable temporalSupport = new DataTable("TemporalSupport"); temporalSupport.Columns.Add("Source", typeof(string)); temporalSupport.Columns.Add("ID", typeof(uint)); // Add rows for each Iaon adapter collection to identify which adapters support temporal processing lock (m_allAdapters) { foreach (IAdapterCollection collection in m_allAdapters) { foreach (IAdapter adapter in collection.Where(adapter => adapter.SupportsTemporalProcessing)) { temporalSupport.Rows.Add(collection.DataMember, adapter.ID); } } if (m_allAdapters.DataSource.Tables.Contains("TemporalSupport")) m_allAdapters.DataSource.Tables.Remove("TemporalSupport"); m_allAdapters.DataSource.Tables.Add(temporalSupport.Copy()); } } public bool TemporalProcessingSupportExists() { return TemporalProcessingSupportExists(null); } /// /// Gets flag that determines if temporal processing is supported in this . /// /// Name of collection over which to check support (e.g., "InputAdapters"); or null for all collections. /// Flag that determines if temporal processing is supported in this . public virtual bool TemporalProcessingSupportExists(string collection) { try { DataTable temporalSupport = m_allAdapters.DataSource.Tables["TemporalSupport"]; if (collection.IsNullOrWhiteSpace()) return temporalSupport.Rows.Count > 0; return temporalSupport.Select(string.Format("Source = '{0}'", collection)).Length > 0; } catch { return false; } } /// /// Gets derived name of specified object. /// /// Sending object from which to derive name. /// Derived name of specified object. public virtual string GetDerivedName(object sender) { return m_derivedNameCache.GetOrAdd(sender, key => { string name = null; IProvideStatus statusProvider = key as IProvideStatus; if (statusProvider != null) name = statusProvider.Name; else if (key != null && key is string) name = (string)key; if (name.IsNullOrWhiteSpace()) name = key.GetType().Name; if (!m_name.IsNullOrWhiteSpace()) name += "#" + m_name; return name; }); } /// /// Recalculates routing tables as long as all adapters have been initialized. /// public virtual void RecalculateRoutingTables() { if (m_useMeasurementRouting && m_routingTables != null && m_allAdapters != null && m_allAdapters.Initialized) m_routingTables.CalculateRoutingTables(m_inputMeasurementKeysRestriction); } protected void OnStatusMessage(object sender, string status) { OnStatusMessage(sender, status, UpdateType.Information); } /// /// Raises the event. /// /// Object source raising the event. /// New status message. /// of status message. protected virtual void OnStatusMessage(object sender, string status, UpdateType type) { if (StatusMessage != null) { // When using default informational update type, see if an update type code was embedded in the status message - this allows for compatibility for event // handlers that are normally unware of the update type if (type == UpdateType.Information && (object)status != null && status.Length > 3 && status.StartsWith("0x") && type.TryParse(status[2].ToString(), out type)) status = status.Substring(3); StatusMessage(sender, new EventArgs(status, type)); } } /// /// Raises the event with a formatted status message. /// /// Object source raising the event. /// Formatted status message. /// of status message. /// Arguments for . /// /// This overload combines string.Format and SendStatusMessage for convienence. /// protected virtual void OnStatusMessage(object sender, string formattedStatus, UpdateType type, params object[] args) { if (StatusMessage != null) OnStatusMessage(sender, string.Format(formattedStatus, args), type); } /// /// Raises event. /// /// Object source raising the event. /// Processing . protected virtual void OnProcessException(object sender, Exception ex) { if (ProcessException != null) ProcessException(sender, new EventArgs(ex)); } /// /// Raises event. /// /// Object source raising the event. protected virtual void OnInputMeasurementKeysUpdated(object sender) { if (InputMeasurementKeysUpdated != null) InputMeasurementKeysUpdated(sender, EventArgs.Empty); } /// /// Raises event. /// /// Object source raising the event. protected virtual void OnOutputMeasurementsUpdated(object sender) { if (OutputMeasurementsUpdated != null) OutputMeasurementsUpdated(sender, EventArgs.Empty); } /// /// Raises the event. /// /// Object source raising the event. /// Total number of unpublished seconds of data. protected virtual void OnUnpublishedSamples(object sender, int seconds) { if (UnpublishedSamples != null) UnpublishedSamples(sender, new EventArgs(seconds)); } /// /// Raises the event. /// /// Object source raising the event. /// Total measurements in the queue that have not been processed. protected virtual void OnUnprocessedMeasurements(object sender, int unprocessedMeasurements) { if (UnprocessedMeasurements != null) UnprocessedMeasurements(sender, new EventArgs(unprocessedMeasurements)); } protected void OnProcessingComplete(object sender) { OnProcessingComplete(sender, null); } /// /// Raises the event. /// /// Object source raising the event. /// , if any. protected virtual void OnProcessingComplete(object sender, EventArgs e) { if (ProcessingComplete != null) ProcessingComplete(sender, e ?? EventArgs.Empty); } /// /// Raises the event. /// /// Object source raising the event. protected virtual void OnDisposed(object sender) { if (Disposed != null) Disposed(sender, EventArgs.Empty); } /// /// Event handler for reporting status messages. /// /// Event source of the status message. /// Event arguments containing the status message to report. public virtual void StatusMessageHandler(object sender, EventArgs e) { // Bubble message up to any event subscribers OnStatusMessage(sender, "[{0}] {1}", UpdateType.Information, GetDerivedName(sender), e.Argument); } /// /// Event handler for processing reported exceptions. /// /// Event source of the exception. /// Event arguments containing the exception to report. public virtual void ProcessExceptionHandler(object sender, EventArgs e) { OnStatusMessage(sender, "[{0}] {1}", UpdateType.Alarm, GetDerivedName(sender), e.Argument.Message); // Bubble message up to any event subscribers OnProcessException(sender, e.Argument); } /// /// Event handler for updates to adapter input measurement key definitions. /// /// Sending object. /// Event arguments, if any. public virtual void InputMeasurementKeysUpdatedHandler(object sender, EventArgs e) { // When adapter measurement keys are dynamically updated, routing tables need to be updated RecalculateRoutingTables(); // Bubble message up to any event subscribers OnInputMeasurementKeysUpdated(sender); } /// /// Event handler for updates to adapter output measurement definitions. /// /// Sending object. /// Event arguments, if any. public virtual void OutputMeasurementsUpdatedHandler(object sender, EventArgs e) { // When adapter measurement keys are dynamically updated, routing tables need to be updated RecalculateRoutingTables(); // Bubble message up to any event subscribers OnOutputMeasurementsUpdated(sender); } /// /// Event handler for monitoring unpublished samples. /// /// Event source reference to adapter, typically an action adapter, that is reporting the number of unpublished data samples. /// Event arguments containing number of samples, in seconds of data, of unpublished data in the source adapter. /// /// Time-series framework uses this handler to monitor the number of unpublished samples, in seconds of data, in action adapters.
/// This method is typically called once every five seconds. ///
public virtual void UnpublishedSamplesHandler(object sender, EventArgs e) { int secondsOfData = e.Argument; int threshold = m_defaultSampleSizeWarningThreshold; int processingInterval = -1; ConcentratorBase concentrator = sender as ConcentratorBase; IAdapter adapter = sender as IAdapter; // Most action adapters will be based on a concentrator, if so we monitor the unpublished sample queue size compared to the defined // lag time - if the queue size is over twice the lag size, the action adapter could be falling behind if ((object)concentrator != null) threshold = (int)(2 * Math.Ceiling(concentrator.LagTime)); // Get processing interval for adapter if ((object)adapter != null) processingInterval = adapter.ProcessingInterval; // Allow much more time before warning for when a fast processing interval has been defined if (processingInterval > -1 && processingInterval < 100) threshold *= 4; if (secondsOfData > threshold) OnStatusMessage(sender, "[{0}] There are {1} seconds of unpublished data in the action adapter concentration queue.", UpdateType.Warning, GetDerivedName(sender), secondsOfData); // Bubble message up to any event subscribers OnUnpublishedSamples(sender, e.Argument); } /// /// Event handler for monitoring unprocessed measurements. /// /// Event source reference to adapter, typically an output adapter, that is reporting the number of unprocessed measurements. /// Event arguments containing number of queued (i.e., unprocessed) measurements in the source adapter. /// /// Time-series framework uses this handler to monitor the number of unprocessed measurements in output adapters.
/// This method is typically called once every five seconds. ///
public virtual void UnprocessedMeasurementsHandler(object sender, EventArgs e) { int unprocessedMeasurements = e.Argument; if (unprocessedMeasurements > m_measurementDumpingThreshold) { IOutputAdapter outputAdapter = sender as IOutputAdapter; if (outputAdapter != null) { // If an output adapter queue size exceeds the defined measurement dumping threshold, // then the queue will be truncated before system runs out of memory outputAdapter.RemoveMeasurements(m_measurementDumpingThreshold); OnStatusMessage(sender, "[{0}] System exercised evasive action to conserve memory and dumped {1} unprocessed measurements from the output queue :(", UpdateType.Alarm, outputAdapter.Name, m_measurementDumpingThreshold); OnStatusMessage(sender, "[{0}] NOTICE: Please adjust measurement threshold settings and/or increase amount of available system memory.", UpdateType.Warning, outputAdapter.Name); } else { // It is only expected that output adapters will be mapped to this handler, but in case // another adapter type uses this handler we will still display a message OnStatusMessage(sender, "[{0}] CRITICAL: There are {1} unprocessed measurements in the adapter queue - but sender \"{2}\" is not an IOutputAdapter, so no evasive action can be exercised.", UpdateType.Warning, GetDerivedName(sender), unprocessedMeasurements, sender.GetType().Name); } } else if (unprocessedMeasurements > m_measurementWarningThreshold) { if (unprocessedMeasurements >= m_measurementDumpingThreshold - m_measurementWarningThreshold) OnStatusMessage(sender, "[{0}] CRITICAL: There are {1} unprocessed measurements in the output queue.", UpdateType.Warning, GetDerivedName(sender), unprocessedMeasurements); else OnStatusMessage(sender, "[{0}] There are {1} unprocessed measurements in the output queue.", UpdateType.Warning, GetDerivedName(sender), unprocessedMeasurements); } // Bubble message up to any event subscribers OnUnprocessedMeasurements(sender, e.Argument); } /// /// Event handler for processing complete notifications from input adapters. /// /// Event source reference to input adapter that is reporting processing completion. /// Event arguments for event, if any; otherwise . public virtual void ProcessingCompleteHandler(object sender, EventArgs e) { OnStatusMessage(sender, "[{0}] Processing completed.", UpdateType.Information, GetDerivedName(sender)); // Bubble message up to any event subscribers OnProcessingComplete(sender, e); } /// /// Event handler for disposed events from all adapters. /// /// Sending object. /// Event arguments, if any. public virtual void DisposedHandler(object sender, EventArgs e) { OnStatusMessage(sender, "[{0}] Disposed.", UpdateType.Information, GetDerivedName(sender)); // Bubble message up to any event subscribers OnDisposed(sender); } // Bubble routing table exceptions out through Iaon session private void m_routingTables_ProcessException(object sender, EventArgs e) { ProcessExceptionHandler(sender, e); } #endregion #region [ Static ] // Static Methods /// /// Extracts a configuration that supports temporal processing from an existing real-time configuration. /// /// Real-time configuration. /// A new configuration for adapters that support temporal processing. public static DataSet ExtractTemporalConfiguration(DataSet realtimeConfiguration) { // Duplicate current run-time session configuration that has temporal support DataSet temporalConfiguration = new DataSet("IaonTemporal"); DataTable temporalSupport = realtimeConfiguration.Tables["TemporalSupport"]; string tableName; foreach (DataTable table in realtimeConfiguration.Tables) { tableName = table.TableName; switch (tableName.ToLower()) { case "inputadapters": case "actionadapters": case "outputadapters": // For Iaon adapter tables, we only copy in adapters that support temporal processing temporalConfiguration.Tables.Add(table.Clone()); // Check for adapters with temporal support in this adapter collection DataRow[] temporalAdapters = temporalSupport.Select(string.Format("Source = '{0}'", tableName)); // If any adapters support temporal processing, add them to the temporal configuration if (temporalAdapters.Length > 0) { DataTable realtimeTable = realtimeConfiguration.Tables[tableName]; DataTable temporalTable = temporalConfiguration.Tables[tableName]; foreach (DataRow row in realtimeTable.Select(string.Format("ID IN ({0})", temporalAdapters.Select(row => row["ID"].ToString()).ToDelimitedString(',')))) { DataRow newRow = temporalTable.NewRow(); for (int x = 0; x < realtimeTable.Columns.Count; x++) { newRow[x] = row[x]; } temporalConfiguration.Tables[tableName].Rows.Add(newRow); } } break; default: // For all other tables we add configuration information as-is temporalConfiguration.Tables.Add(table.Copy()); break; } } return temporalConfiguration; } #endregion } }