445 lines · 16.0 KB · C#
RawDownload
//******************************************************************************************************//  FlatlineTest.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://  ----------------------------------------------------------------------------------------------------//  11/10/2009 - Stephen C. Wills//       Generated original version of source code.//  12/11/2009 - Pinal C. Patel//       Added thread synchronization.//       Replaced the use of LatestTimestamp with base.RealTime.//  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.Timers;using DataQualityMonitoring.Services;using GSF;using GSF.Diagnostics;using GSF.Net.Smtp;using GSF.TimeSeries;using GSF.TimeSeries.Adapters; namespace DataQualityMonitoring{    /// <summary>    /// Tests measurements to determine whether they have flat-lined.    /// </summary>    [Description("Flat-line Test: Notifies when measurements are received whose values do not change")]    public class FlatlineTest : ActionAdapterBase    {        #region [ Members ]         // Fields        private Ticks m_minFlatline;        private Ticks m_warnInterval;        private Ticks m_emailInterval;        private string m_adminEmailAddress;        private string m_smtpServer;         private readonly Dictionary<MeasurementKey, IMeasurement> m_lastChange;        private readonly Dictionary<MeasurementKey, Ticks> m_lastNotified;        private readonly Timer m_warningTimer;        private FlatlineService m_flatlineService;         private bool m_disposed;         #endregion         #region [ Constructors ]         /// <summary>        /// Creates a new instance of the <see cref="FlatlineTest"/> class.        /// </summary>        public FlatlineTest()        {            m_minFlatline = Ticks.FromSeconds(4);            m_warnInterval = Ticks.FromSeconds(4);            m_emailInterval = Ticks.FromSeconds(3600);            m_smtpServer = Mail.DefaultSmtpServer;             m_lastChange = new Dictionary<MeasurementKey, IMeasurement>();            m_lastNotified = new Dictionary<MeasurementKey, Ticks>();            m_warningTimer = new Timer();        }         #endregion         #region [ Properties ]         /// <summary>        /// Gets or sets the length of time, in seconds, that a measurement's value must remain stale to be considered flat-lined.        /// </summary>        [ConnectionStringParameter,        Description("Define the length of time, in seconds, that a measurement's value must remain stale to be considered flat-lined."),        DefaultValue(4)]        public double MinFlatline        {            get            {                return m_minFlatline.ToSeconds();            }            set            {                m_minFlatline = Ticks.FromSeconds(value);            }        }         /// <summary>        /// Gets or sets the amount of time, in seconds, between console updates.        /// </summary>        [ConnectionStringParameter,        Description("Define the amount of time, in seconds, between console updates."),        DefaultValue(4)]        public double WarnInterval        {            get            {                return m_warnInterval.ToSeconds();            }            set            {                m_warnInterval = Ticks.FromSeconds(value);            }        }         /// <summary>        /// Gets or sets the email address or address list to which notifications will be sent.        /// </summary>        [ConnectionStringParameter,        Description("Define the email address or address list to which notifications will be sent."),        DefaultValue(null)]        public string AdminEmailAddress        {            get            {                return m_adminEmailAddress;            }            set            {                m_adminEmailAddress = value;            }        }         /// <summary>        /// Gets or sets the amount of time, in minutes, between email notifications.        /// </summary>        [ConnectionStringParameter,        Description("Define the amount of time, in minutes, between email notifications."),        DefaultValue(60)]        public long EmailInterval        {            get            {                return (long)m_emailInterval.ToSeconds() / 60L;            }            set            {                m_emailInterval = Ticks.FromSeconds(value * 60L);            }        }         /// <summary>        /// Gets or sets the host name or IP address of the SMTP server used to send email notifications.        /// </summary>        [ConnectionStringParameter,        Description("Define the host name or IP address of the SMTP server used to send email notifications."),        DefaultValue(Mail.DefaultSmtpServer)]        public string SmtpServer        {            get            {                return m_smtpServer;            }            set            {                m_smtpServer = value;            }        }         /// <summary>        /// Gets the flag indicating if this adapter supports temporal processing.        /// </summary>        public override bool SupportsTemporalProcessing => false;         #endregion         #region [ Methods ]         /// <summary>        /// Initializes <see cref="FlatlineTest"/>.        /// </summary>        public override void Initialize()        {            base.Initialize();             Dictionary<string, string> settings = Settings;            string setting;             if (settings.TryGetValue("minFlatline", out setting))                m_minFlatline = Ticks.FromSeconds(double.Parse(setting));             if (settings.TryGetValue("warnInterval", out setting))                m_warnInterval = Ticks.FromSeconds(double.Parse(setting));             if (settings.TryGetValue("adminEmailAddress", out m_adminEmailAddress))            {                // emailInterval is entered in minutes.                if (settings.TryGetValue("emailInterval", out setting))                    m_emailInterval = Ticks.FromSeconds(long.Parse(setting) * 60L);            }             if (settings.TryGetValue("smtpServer", out setting))                m_smtpServer = setting;             m_warningTimer.Interval = m_warnInterval.ToMilliseconds();            m_warningTimer.Elapsed += m_warningTimer_Elapsed;             m_flatlineService = new FlatlineService(this);            m_flatlineService.SettingsCategory = base.Name + m_flatlineService.SettingsCategory;            m_flatlineService.ServiceProcessException += m_flatlineService_ServiceProcessException;            m_flatlineService.Initialize();             InitializeLastChange();        }         /// <summary>        /// Starts the <see cref="FlatlineTest"/>, if it is not already running.        /// </summary>        public override void Start()        {            base.Start();            m_warningTimer.Start();        }         /// <summary>        /// Stops the <see cref="FlatlineTest"/>.        /// </summary>        public override void Stop()        {            m_warningTimer.Stop();            base.Stop();        }         /// <summary>        /// Publish <see cref="IFrame"/> of time-aligned collection of <see cref="IMeasurement"/> values that arrived within the        /// concentrator's defined <see cref="ConcentratorBase.LagTime"/>.        /// </summary>        /// <param name="frame"><see cref="IFrame"/> of measurements with the same timestamp that arrived within <see cref="ConcentratorBase.LagTime"/> that are ready for processing.</param>        /// <param name="index">Index of <see cref="IFrame"/> within a second ranging from zero to <c><see cref="ConcentratorBase.FramesPerSecond"/> - 1</c>.</param>        protected override void PublishFrame(IFrame frame, int index)        {            IMeasurement measurement;             lock (m_lastChange)            {                foreach (MeasurementKey key in frame.Measurements.Keys)                {                    measurement = frame.Measurements[key];                     if (!m_lastChange.ContainsKey(key))                        m_lastChange.Add(key, measurement);                    else if (m_lastChange[key].Value != measurement.Value)                        m_lastChange[key] = measurement;                }            }             SendEmailNotifications();        }         /// <summary>        /// Returns a collection of measurements that are flat-lined.        /// </summary>        /// <returns>A collection of flat-lined measurements.</returns>        public ICollection<IMeasurement> GetFlatlinedMeasurements()        {            ICollection<IMeasurement> flatlinedMeasurements = new List<IMeasurement>();             lock (m_lastChange)            {                IMeasurement measurement;                 foreach (MeasurementKey key in m_lastChange.Keys)                {                    measurement = m_lastChange[key];                     Ticks timeDiff = base.RealTime - measurement.Timestamp;                    if (timeDiff >= m_minFlatline)                        flatlinedMeasurements.Add(measurement);                }            }             return flatlinedMeasurements;        }         /// <summary>        /// Releases the unmanaged resources used by the <see cref="FlatlineTest"/> 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_flatlineService != null)                        {                            m_flatlineService.ServiceProcessException -= m_flatlineService_ServiceProcessException;                            m_flatlineService.Dispose();                        }                    }                }                finally                {                    m_disposed = true;          // Prevent duplicate dispose.                    base.Dispose(disposing);    // Call base class Dispose().                }            }        }         // Initialize m_lastChange with dummy measurements so the adapter        // can detect measurements that have never been published        private void InitializeLastChange()        {            Ticks timestamp = base.RealTime;             foreach (MeasurementKey key in InputMeasurementKeys)            {                // Try to find the signal ID and add the measurement to the m_lastChange collection                if (DataSource.Tables.Contains("ActiveMeasurements"))                {                    DataRow[] measurementRows = DataSource.Tables["ActiveMeasurements"].Select($"ID = '{key}'");                     if (measurementRows.Length > 0)                    {                        string signalID = measurementRows[0]["SignalID"].ToNonNullString();                         if (signalID != "")                        {                            m_lastChange.Add(key, new Measurement                            {                                //Assumption made that key.SignalID == signalId                                Metadata = key.Metadata,                                Value = double.NaN,                                Timestamp = timestamp                            });                        }                    }                }                 // If the signal ID could not be found, add the measurement without it                if (!m_lastChange.ContainsKey(key))                {                    m_lastChange.Add(key, new Measurement                    {                        Metadata = key.Metadata,                        Value = double.NaN,                        Timestamp = timestamp                    });                }            }        }         // Sends email notifications about changes in the flat-lined status of measurements.        private void SendEmailNotifications()        {            Ticks now = DateTime.UtcNow.Ticks;            ICollection<IMeasurement> allFlatlinedMeasurements = GetFlatlinedMeasurements();            IEnumerable<IMeasurement> flatlined, noLongerFlatlined;             lock (m_lastChange)            {                flatlined = m_lastNotified                    .Where(pair => now - pair.Value > m_emailInterval)                    .Select(pair => m_lastChange[pair.Key])                    .Concat(allFlatlinedMeasurements.Where(measurement => !m_lastNotified.Keys.Contains(measurement.Key)))                    .ToList();                 noLongerFlatlined = m_lastNotified                    .Where(pair => allFlatlinedMeasurements.All(measurement => pair.Key != measurement.Key))                    .Select(pair => m_lastChange[pair.Key])                    .ToList();            }             if (flatlined.Any())                SendEmailNotification(flatlined, true);             if (noLongerFlatlined.Any())                SendEmailNotification(noLongerFlatlined, false);        }         // Send an email address notifying the admin of a changes in the flat-lined status of measurements.        private void SendEmailNotification(IEnumerable<IMeasurement> measurements, bool flatlined)        {            Ticks now = DateTime.Now.Ticks;            Mail message = new Mail("notifications@company.com", m_adminEmailAddress, m_smtpServer);            StringBuilder body = new StringBuilder();             body.AppendLine("Measurement Key, Value, Timestamp");             foreach (IMeasurement measurement in measurements)            {                body.Append(measurement.Key);                body.Append(", ");                body.Append(measurement.AdjustedValue);                body.Append(", ");                body.Append(measurement.Timestamp);                body.AppendLine();                 if (flatlined)                    m_lastNotified[measurement.Key] = now;                else                    m_lastNotified.Remove(measurement.Key);            }             if (flatlined)                message.Subject = "Flat-lined measurements";            else                message.Subject = "No longer flat-lined measurements";             message.Body = body.ToString();            message.Send();            message.Dispose();        }         private void m_warningTimer_Elapsed(object sender, ElapsedEventArgs e)        {            ICollection<IMeasurement> flatlinedMeasurements = GetFlatlinedMeasurements();             foreach (IMeasurement measurement in flatlinedMeasurements)            {                Ticks timeDiff = RealTime - measurement.Timestamp;                OnStatusMessage(MessageLevel.Info, $"{measurement} flat-lined for {(int)timeDiff.ToSeconds()} seconds.");            }        }         private void m_flatlineService_ServiceProcessException(object sender, EventArgs<Exception> e)        {            OnProcessException(MessageLevel.Warning, e.Argument);        }         #endregion    }}

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