779 lines · 31.9 KB · C#
RawDownload
//******************************************************************************************************//  AlarmAdapter.cs - Gbtc////  Copyright © 2012, 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 MIT License (MIT), 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/MIT////  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://  ----------------------------------------------------------------------------------------------------//  01/31/2012 - Stephen C. Wills//       Generated original version of source code.//  12/13/2012 - Starlynn Danyelle Gilliam//       Modified Header.////****************************************************************************************************** using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Linq;using System.Text;using System.Threading;using DataQualityMonitoring.Services;using GSF;using GSF.Collections;using GSF.Data;using GSF.Threading;using GSF.TimeSeries;using GSF.TimeSeries.Adapters;using GSF.TimeSeries.Statistics; namespace DataQualityMonitoring{    /// <summary>    /// Action adapter that generates alarm measurements based on alarm definitions from the database.    /// </summary>    [Description("Alarm: Generates alarm events for alarms defined in the database")]    public class AlarmAdapter : FacileActionAdapterBase    {        #region [ Members ]         // Nested Types        private class SignalAlarms        {            public string Name;            public List<Alarm> Alarms;            public AlarmStatistics Statistics;        }         private class StateChange        {            public Guid SignalID;            public int? OldStateID;            public int? NewStateID;            public DateTime Timestamp;            public double Value;        }         // Constants        private const int UpToDate = 0;        private const int Modified = 1;         // Fields        private object m_alarmLock;         private DoubleBufferedQueue<IMeasurement> m_measurementQueue;        private MixedSynchronizedOperation m_processMeasurementsOperation;        private DataSet m_alarmDataSet;        private int m_dataSourceState;         private Dictionary<Guid, SignalAlarms> m_alarmLookup;        private AlarmService m_alarmService;        private long m_eventCount;         private LongSynchronizedOperation m_alarmLogOperation;        private DoubleBufferedQueue<StateChange> m_stateChanges;        private bool m_useAlarmLog;        private int m_bulkInsertLimit;        private int m_logProcessingDelay;         private bool m_supportsTemporalProcessing;        private bool m_disposed;         #endregion         #region [ Constructors ]         /// <summary>        /// Creates a new instance of the <see cref="AlarmAdapter"/> class.        /// </summary>        public AlarmAdapter()        {            m_alarmLock = new object();            m_alarmLookup = new Dictionary<Guid, SignalAlarms>();             m_measurementQueue = new DoubleBufferedQueue<IMeasurement>();            m_processMeasurementsOperation = new MixedSynchronizedOperation(ProcessMeasurements, OnProcessException);             m_alarmLogOperation = new LongSynchronizedOperation(LogStateChanges, OnProcessException);            m_stateChanges = new DoubleBufferedQueue<StateChange>();            m_alarmLogOperation.IsBackground = true;        }         #endregion         #region [ Properties ]         /// <summary>        /// Gets or sets <see cref="DataSet"/> based data source available to this <see cref="AdapterBase"/>.        /// </summary>        public override DataSet DataSource        {            get            {                return base.DataSource;            }            set            {                base.DataSource = value;                 if (Interlocked.CompareExchange(ref m_dataSourceState, Modified, UpToDate) == UpToDate && Initialized)                {                    m_processMeasurementsOperation.AsynchronousExecutionMode = AsynchronousExecutionMode.Long;                    m_processMeasurementsOperation.RunOnceAsync();                }            }        }         /// <summary>        /// Gets the flag indicating if this adapter supports temporal processing.        /// </summary>        [ConnectionStringParameter,        Description("Define the flag indicating if this adapter supports temporal processing."),        DefaultValue(false)]        public override bool SupportsTemporalProcessing        {            get            {                return m_supportsTemporalProcessing;            }        }         /// <summary>        /// Gets or sets the flag indicating whether the alarm adapter should        /// use the alarm log to track recently modified alarm states.        /// </summary>        [ConnectionStringParameter,        Description("Define the flag indicating whether to use the alarm log to track recently modified alarm states."),        DefaultValue(true)]        public bool UseAlarmLog        {            get            {                return m_useAlarmLog;            }            set            {                m_useAlarmLog = value;            }        }         /// <summary>        /// Gets or sets the maximum number of alarm state changes to pack into one alarm log insert query.        /// </summary>        [ConnectionStringParameter,        Description("Define the maximum number of alarm state changes to pack into one alarm log insert query."),        DefaultValue(300)]        public int BulkInsertLimit        {            get            {                return m_bulkInsertLimit;            }            set            {                m_bulkInsertLimit = value;            }        }         /// <summary>        /// Gets or sets the amount of time, in seconds, to wait for alarm log state changes between bulk inserts.        /// </summary>        [ConnectionStringParameter,        Description("Define the amount of time, in seconds, to wait for alarm log state changes between bulk inserts."),        DefaultValue(1.0D)]        public double LogProcessingDelay        {            get            {                return m_logProcessingDelay / 1000.0D;            }            set            {                m_logProcessingDelay = (int)(value * 1000.0D);            }        }         /// <summary>        /// Returns the detailed status of the data input source.        /// </summary>        public override string Status        {            get            {                StringBuilder statusBuilder = new StringBuilder(base.Status);                statusBuilder.Append(m_alarmService.Status);                return statusBuilder.ToString();            }        }         #endregion         #region [ Methods ]         /// <summary>        /// Initializes the <see cref="AlarmAdapter"/>.        /// </summary>        public override void Initialize()        {            Dictionary<string, string> settings;            string setting;            double logProcessingDelay;             // Run base class initialization            base.Initialize();            settings = Settings;             // Load optional parameters            if (settings.TryGetValue("supportsTemporalProcessing", out setting))                m_supportsTemporalProcessing = setting.ParseBoolean();            else                m_supportsTemporalProcessing = false;             if (settings.TryGetValue("useAlarmLog", out setting))                m_useAlarmLog = setting.ParseBoolean();            else                m_useAlarmLog = true;             if (!settings.TryGetValue("bulkInsertLimit", out setting) || !int.TryParse(setting, out m_bulkInsertLimit))                m_bulkInsertLimit = 300;             if (settings.TryGetValue("logProcessingDelay", out setting) && double.TryParse(setting, out logProcessingDelay))                m_logProcessingDelay = (int)(logProcessingDelay * 1000.0D);            else                m_logProcessingDelay = 1000;             try            {                // Set up alarm service                m_alarmService = new AlarmService(this);                m_alarmService.SettingsCategory = base.Name.Replace("!", "").ToLower() + m_alarmService.SettingsCategory;                m_alarmService.ServiceProcessException += AlarmService_ServiceProcessException;                m_alarmService.PersistSettings = true;                m_alarmService.Initialize();            }            catch (Exception ex)            {                string message = string.Format("Unable to initialize alarm service due to exception: {0}", ex.Message);                OnProcessException(new InvalidOperationException(message, ex));            }             // Run the process measurements operation to ensure that the alarm configuration is up to date            if (Interlocked.CompareExchange(ref m_dataSourceState, Modified, Modified) == Modified)            {                m_processMeasurementsOperation.AsynchronousExecutionMode = AsynchronousExecutionMode.Long;                m_processMeasurementsOperation.RunOnceAsync();            }        }         /// <summary>        /// Starts the <see cref="AlarmAdapter"/>, or restarts it if it is already running.        /// </summary>        public override void Start()        {            base.Start();            Interlocked.Exchange(ref m_eventCount, 0L);        }         /// <summary>        /// Queues a collection of measurements for processing.        /// </summary>        /// <param name="measurements">Measurements to queue for processing.</param>        public override void QueueMeasurementsForProcessing(IEnumerable<IMeasurement> measurements)        {            m_measurementQueue.Enqueue(measurements);            m_processMeasurementsOperation.RunOnceAsync();        }         /// <summary>        /// Gets a short one-line status of this <see cref="AdapterBase"/>.        /// </summary>        /// <param name="maxLength">Maximum number of available characters for display.</param>        /// <returns>A short one-line summary of the current status of this <see cref="AdapterBase"/>.</returns>        public override string GetShortStatus(int maxLength)        {            return string.Format("{0} events processed since last start", Interlocked.Read(ref m_eventCount)).CenterText(maxLength);        }         /// <summary>        /// Gets a collection containing all the raised alarms in the system.        /// </summary>        /// <returns>A collection containing all the raised alarms.</returns>        [AdapterCommand("Gets a collection containing all the raised alarms in the system.", "Administrator", "Editor", "Viewer")]        public ICollection<Alarm> GetRaisedAlarms()        {            lock (m_alarmLock)            {                return m_alarmLookup                    .SelectMany(kvp => kvp.Value.Alarms)                    .Where(alarm => alarm.State == AlarmState.Raised)                    .Select(alarm => alarm.Clone())                    .ToList();            }        }         /// <summary>        /// Gets a collection containing raised alarms with the highest severity for each signal in the system.         /// </summary>        /// <returns>A collection containing all the highest severity raised alarms.</returns>        [AdapterCommand("Gets a collection containing raised alarms with the highest severity for each signal in the system.", "Administrator", "Editor", "Viewer")]        public ICollection<Alarm> GetHighestSeverityAlarms()        {            lock (m_alarmLock)            {                return m_alarmLookup                    .Select(kvp => kvp.Value.Alarms.FirstOrDefault(alarm => alarm.State == AlarmState.Raised))                    .Where(alarm => (object)alarm != null)                    .Select(alarm => alarm.Clone())                    .ToList();            }        }         /// <summary>        /// Releases the unmanaged resources used by the <see cref="AlarmAdapter"/> 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 override void Dispose(bool disposing)        {            if (!m_disposed)            {                try                {                    if (disposing)                    {                        if (m_alarmService != null)                        {                            m_alarmService.ServiceProcessException -= AlarmService_ServiceProcessException;                            m_alarmService.Dispose();                        }                         foreach (SignalAlarms signalAlarms in m_alarmLookup.Values)                            StatisticsEngine.Unregister(signalAlarms.Statistics);                    }                }                finally                {                    m_disposed = true;          // Prevent duplicate dispose.                    base.Dispose(disposing);    // Call base class Dispose().                }            }        }          // Creates an alarm using data defined in the database.        private Alarm CreateAlarm(DataRow row)        {            object associatedMeasurementId = row.Field<object>("AssociatedMeasurementID");             return new Alarm((AlarmOperation)row.ConvertField<int>("Operation"))            {                ID = row.ConvertField<int>("ID"),                TagName = row.Field<object>("TagName").ToString(),                SignalID = Guid.Parse(row.Field<object>("SignalID").ToString()),                AssociatedMeasurementID = ((object)associatedMeasurementId != null) ? Guid.Parse(associatedMeasurementId.ToString()) : (Guid?)null,                Description = row.Field<object>("Description").ToNonNullString(),                Severity = row.ConvertField<int>("Severity").GetEnumValueOrDefault<AlarmSeverity>(AlarmSeverity.None),                SetPoint = row.ConvertNullableField<double>("SetPoint"),                Tolerance = row.ConvertNullableField<double>("Tolerance"),                Delay = row.ConvertNullableField<double>("Delay"),                Hysteresis = row.ConvertNullableField<double>("Hysteresis")            };        }         // Dequeues measurements from the measurement queue and processes them.        private void ProcessMeasurements()        {            IList<IMeasurement> measurements;            int dataSourceState;             // Get the current state of the data source            dataSourceState = Interlocked.CompareExchange(ref m_dataSourceState, Modified, Modified);             // If the data source has been modified, elevate the synchronized operation            // to a dedicated thread and ensure that it runs at least one more time            if (dataSourceState == Modified)            {                if (m_processMeasurementsOperation.CurrentExecutionMode != AsynchronousExecutionMode.Long)                {                    m_processMeasurementsOperation.AsynchronousExecutionMode = AsynchronousExecutionMode.Long;                    m_processMeasurementsOperation.RunOnceAsync();                    return;                }                 Interlocked.Exchange(ref m_dataSourceState, UpToDate);                UpdateAlarmDefinitions();            }             // Attempt to dequeue measurements from the measurement queue and,            // if there are measurements left in the queue, ensure that the            // process measurements operation runs again            if (m_measurementQueue.TryDequeue(out measurements) > 0)                m_processMeasurementsOperation.RunOnceAsync();             // If items were successfully dequeued, process them;            // otherwise, switch to short asynchronous execution mode so that            // we can keep attempting to dequeue in an asynchronous loop            if (measurements.Count > 0)                ProcessMeasurements(measurements);            else                m_processMeasurementsOperation.AsynchronousExecutionMode = AsynchronousExecutionMode.Short;             // If there are no pending operations, switch back to the thread            // pool so that the next operation will start up quicker            if (!m_processMeasurementsOperation.IsPending)                m_processMeasurementsOperation.AsynchronousExecutionMode = AsynchronousExecutionMode.Short;        }         // Updates alarm definitions when the data source has been updated.        private void UpdateAlarmDefinitions()        {            DateTime now;            DataSet alarmDataSet;             Dictionary<int, Alarm> definedAlarmsLookup;            Dictionary<Guid, SignalAlarms> newAlarmLookup;            List<IMeasurement> alarmEvents;             // Get the current time in case we need            // to generate alarm events for cleared alarms            now = DateTime.UtcNow;             // Get the latest version of the table of defined alarms            alarmDataSet = new DataSet();            alarmDataSet.Tables.Add(DataSource.Tables["Alarms"].Copy());             // Compare the latest alarm table with the previous            // alarm table to determine if anything needs to be done            if (DataSetEqualityComparer.Default.Equals(m_alarmDataSet, alarmDataSet))                return;             m_alarmDataSet = alarmDataSet;             // Get list of alarms defined in the latest version of the alarm table            definedAlarmsLookup = alarmDataSet.Tables[0].Rows.Cast<DataRow>()                .Where(row => row.ConvertField<bool>("Enabled"))                .Select(CreateAlarm)                .ToDictionary(alarm => alarm.ID);             // Create a list to store alarm events generated by this process            alarmEvents = new List<IMeasurement>();             foreach (Alarm existingAlarm in m_alarmLookup.SelectMany(kvp => kvp.Value.Alarms))            {                Alarm definedAlarm;                 // Attempt to locate the defined alarm corresponding to the existing alarm                definedAlarmsLookup.TryGetValue(existingAlarm.ID, out definedAlarm);                 // Determine if a change to the alarm's                // configuration has changed the alarm's behavior                if (BehaviorChanged(existingAlarm, definedAlarm))                {                    // Clone the existing alarm so that changes to alarm                    // states can be observed later in this process                    Alarm clone = existingAlarm.Clone();                     // Clear the alarm and create an event                    clone.State = AlarmState.Cleared;                    alarmEvents.Add(CreateAlarmEvent(now, clone));                }                else if ((object)definedAlarm != null)                {                    // Update functionally irrelevant configuration info                    existingAlarm.TagName = definedAlarm.TagName;                    existingAlarm.Description = definedAlarm.Description;                     // Use the existing alarm since the alarm is functionally the same                    definedAlarmsLookup[definedAlarm.ID] = existingAlarm;                }            }

Lines 1–500 of 779.

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