//******************************************************************************************************
// 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
{
///
/// Action adapter that generates alarm measurements based on alarm definitions from the database.
///
[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 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 m_measurementQueue;
private MixedSynchronizedOperation m_processMeasurementsOperation;
private DataSet m_alarmDataSet;
private int m_dataSourceState;
private Dictionary m_alarmLookup;
private AlarmService m_alarmService;
private long m_eventCount;
private LongSynchronizedOperation m_alarmLogOperation;
private DoubleBufferedQueue 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 ]
///
/// Creates a new instance of the class.
///
public AlarmAdapter()
{
m_alarmLock = new object();
m_alarmLookup = new Dictionary();
m_measurementQueue = new DoubleBufferedQueue();
m_processMeasurementsOperation = new MixedSynchronizedOperation(ProcessMeasurements, OnProcessException);
m_alarmLogOperation = new LongSynchronizedOperation(LogStateChanges, OnProcessException);
m_stateChanges = new DoubleBufferedQueue();
m_alarmLogOperation.IsBackground = true;
}
#endregion
#region [ Properties ]
///
/// Gets or sets based data source available to this .
///
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();
}
}
}
///
/// Gets the flag indicating if this adapter supports temporal processing.
///
[ConnectionStringParameter,
Description("Define the flag indicating if this adapter supports temporal processing."),
DefaultValue(false)]
public override bool SupportsTemporalProcessing
{
get
{
return m_supportsTemporalProcessing;
}
}
///
/// Gets or sets the flag indicating whether the alarm adapter should
/// use the alarm log to track recently modified alarm states.
///
[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;
}
}
///
/// Gets or sets the maximum number of alarm state changes to pack into one alarm log insert query.
///
[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;
}
}
///
/// Gets or sets the amount of time, in seconds, to wait for alarm log state changes between bulk inserts.
///
[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);
}
}
///
/// Returns the detailed status of the data input source.
///
public override string Status
{
get
{
StringBuilder statusBuilder = new StringBuilder(base.Status);
statusBuilder.Append(m_alarmService.Status);
return statusBuilder.ToString();
}
}
#endregion
#region [ Methods ]
///
/// Initializes the .
///
public override void Initialize()
{
Dictionary 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();
}
}
///
/// Starts the , or restarts it if it is already running.
///
public override void Start()
{
base.Start();
Interlocked.Exchange(ref m_eventCount, 0L);
}
///
/// Queues a collection of measurements for processing.
///
/// Measurements to queue for processing.
public override void QueueMeasurementsForProcessing(IEnumerable measurements)
{
m_measurementQueue.Enqueue(measurements);
m_processMeasurementsOperation.RunOnceAsync();
}
///
/// Gets a short one-line status of this .
///
/// Maximum number of available characters for display.
/// A short one-line summary of the current status of this .
public override string GetShortStatus(int maxLength)
{
return string.Format("{0} events processed since last start", Interlocked.Read(ref m_eventCount)).CenterText(maxLength);
}
///
/// Gets a collection containing all the raised alarms in the system.
///
/// A collection containing all the raised alarms.
[AdapterCommand("Gets a collection containing all the raised alarms in the system.", "Administrator", "Editor", "Viewer")]
public ICollection GetRaisedAlarms()
{
lock (m_alarmLock)
{
return m_alarmLookup
.SelectMany(kvp => kvp.Value.Alarms)
.Where(alarm => alarm.State == AlarmState.Raised)
.Select(alarm => alarm.Clone())
.ToList();
}
}
///
/// Gets a collection containing raised alarms with the highest severity for each signal in the system.
///
/// A collection containing all the highest severity raised alarms.
[AdapterCommand("Gets a collection containing raised alarms with the highest severity for each signal in the system.", "Administrator", "Editor", "Viewer")]
public ICollection 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();
}
}
///
/// 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 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