//****************************************************************************************************** // PerformanceMonitorBase.cs - Gbtc // // Copyright © 2021, 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://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: // ---------------------------------------------------------------------------------------------------- // 04/29/2021 - J. Ritchie Carroll // Generated original version of source code. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Text; using System.Timers; using Timer = System.Timers.Timer; namespace GSF.Diagnostics { /// /// Represents a base class for performance monitors, e.g., the process level /// and the system level . /// public abstract class PerformanceMonitorBase : IDisposable, IProvideStatus { #region [ Members ] /// /// Default interval for sampling the . /// public const double DefaultSamplingInterval = 1000.0D; // Fields private readonly List m_counters; private readonly Timer m_samplingTimer; private bool m_disposed; #endregion #region [ Constructors ] /// /// Initializes a new instance of the class. /// /// Interval, in milliseconds, at which the are to be sampled. protected PerformanceMonitorBase(double samplingInterval = DefaultSamplingInterval) { m_counters = new List(); m_samplingTimer = new Timer(samplingInterval); m_samplingTimer.Elapsed += SamplingTimer_Elapsed; m_samplingTimer.Start(); } /// /// Releases the unmanaged resources before the object is reclaimed by . /// ~PerformanceMonitorBase() => Dispose(false); #endregion #region [ Properties ] /// /// Gets or sets the interval, in milliseconds, at which the are to be sampled. /// public double SamplingInterval { get => m_samplingTimer.Interval; set => m_samplingTimer.Interval = value; } /// /// Gets a read-only list of the objects monitored by the object. /// public ReadOnlyCollection Counters { get { lock (m_counters) return new ReadOnlyCollection(m_counters); } } /// /// Gets the friendly name of the object. /// public virtual string Name => $"{GetType().Name}"; /// /// Gets the current status of the object. /// public virtual string Status { get { StringBuilder status = new StringBuilder(); // Status header. status.Append("Counter".PadRight(20)); status.Append(' '); status.Append("Last".CenterText(13)); status.Append(' '); status.Append("Average".CenterText(13)); status.Append(' '); status.Append("Maximum".CenterText(13)); status.Append(' '); status.Append("Units".CenterText(16)); status.AppendLine(); status.Append(new string('-', 20)); status.Append(' '); status.Append(new string('-', 13)); status.Append(' '); status.Append(new string('-', 13)); status.Append(' '); status.Append(new string('-', 13)); status.Append(' '); status.Append(new string('-', 16)); status.AppendLine(); lock (m_counters) { foreach (PerformanceCounter counter in m_counters) { // Counter status. status.Append(counter.AliasName.PadLeft(20)); status.Append(' '); status.Append(counter.LastValue.ToString("0.00").CenterText(13)); status.Append(' '); status.Append(counter.AverageValue.ToString("0.00").CenterText(13)); status.Append(' '); status.Append(counter.MaximumValue.ToString("0.00").CenterText(13)); status.Append(' '); status.Append(counter.ValueUnit); status.AppendLine(); } } // 1 2 3 4 5 6 7 8 // 12345678901234567890123456789012345678901234567890123456789012345678901234567890 // Statistics calculated using last 500 counter values sampled every 12.5 seconds. string samplingInterval = "second"; if (m_samplingTimer.Interval != 1000.0D) samplingInterval = (m_samplingTimer.Interval / 1000.0D).ToString("0.0") + " seconds"; status.AppendLine($"{Environment.NewLine}Statistics calculated using last {PerformanceCounter.DefaultSamplingWindow} counter values sampled every {samplingInterval}."); return status.ToString(); } } /// /// Gets the lifetime status statistics of the object. /// public virtual string LifetimeStatus { get { StringBuilder status = new StringBuilder(); long sampleCount = 0; // Status header. status.Append("Counter".PadRight(20)); status.Append(' '); // 1234567890123 status.Append("Lifetime Max.".CenterText(13)); status.Append(' '); // 1234567890123 status.Append("Lifetime Avg.".CenterText(13)); status.Append(' '); // 1234567890123 status.Append("Inv(Scalar)".CenterText(13)); status.Append(' '); status.Append("Units".CenterText(16)); status.AppendLine(); status.Append(new string('-', 20)); status.Append(' '); status.Append(new string('-', 13)); status.Append(' '); status.Append(new string('-', 13)); status.Append(' '); status.Append(new string('-', 13)); status.Append(' '); status.Append(new string('-', 16)); status.AppendLine(); lock (m_counters) { foreach (PerformanceCounter counter in m_counters) { // Counter status. status.Append(counter.AliasName.PadLeft(20)); status.Append(' '); status.Append(counter.LifetimeMaximumValue.ToString("0.00").CenterText(13)); status.Append(' '); status.Append(counter.LifetimeAverageValue.ToString("0.00").CenterText(13)); status.Append(' '); status.Append(counter.ValueDivisor.ToString().CenterText(13)); status.Append(' '); status.Append(counter.ValueUnit); status.AppendLine(); if (sampleCount == 0) sampleCount = counter.LifetimeSampleCount; } } // 1 2 3 4 5 6 7 8 // 12345678901234567890123456789012345678901234567890123456789012345678901234567890 // Statistics calculated using 121878905 counter values sampled every 5.0 seconds. string samplingInterval = "second"; if (m_samplingTimer.Interval != 1000.0D) samplingInterval = (m_samplingTimer.Interval / 1000.0D).ToString("0.0") + " seconds"; status.AppendLine($"{Environment.NewLine}Statistics calculated using {sampleCount} counter values sampled every {samplingInterval}."); 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) return; try { if (!disposing) return; if (!(m_samplingTimer is null)) { m_samplingTimer.Elapsed -= SamplingTimer_Elapsed; m_samplingTimer.Dispose(); } lock (m_counters) { foreach (PerformanceCounter counter in m_counters) counter.Dispose(); m_counters.Clear(); } } finally { m_disposed = true; // Prevent duplicate dispose. } } /// /// Adds a to be monitored. /// /// The name of the performance counter category (performance object) with which this performance counter is associated. /// The name of the performance counter. /// The name of the performance counter category instance, or an empty string (""), if the category contains a single instance. public void AddCounter(string categoryName, string counterName, string instanceName) => AddCounter(categoryName, counterName, instanceName, counterName); /// /// Adds a to be monitored. /// /// The name of the performance counter category (performance object) with which this performance counter is associated. /// The name of the performance counter. /// The name of the performance counter category instance, or an empty string (""), if the category contains a single instance. /// The alias name for the object. public void AddCounter(string categoryName, string counterName, string instanceName, string aliasName) => AddCounter(categoryName, counterName, instanceName, aliasName, PerformanceCounter.DefaultValueUnit); /// /// Adds a to be monitored. /// /// The name of the performance counter category (performance object) with which this performance counter is associated. /// The name of the performance counter. /// The name of the performance counter category instance, or an empty string (""), if the category contains a single instance. /// The alias name for the object. /// The measurement unit for the statistical values of the object. public void AddCounter(string categoryName, string counterName, string instanceName, string aliasName, string valueUnit) => AddCounter(categoryName, counterName, instanceName, aliasName, valueUnit, PerformanceCounter.DefaultValueDivisor); /// /// Adds a to be monitored. /// /// The name of the performance counter category (performance object) with which this performance counter is associated. /// The name of the performance counter. /// The name of the performance counter category instance, or an empty string (""), if the category contains a single instance. /// The alias name for the object. /// The measurement unit for the statistical values of the object. /// The divisor to be applied to the statistical values of the object. /// Flag that determines if this counter is read-only. /// Defines a custom sample adjustment function for the counter. /// Defines a custom sample filter function for the counter. public void AddCounter(string categoryName, string counterName, string instanceName, string aliasName, string valueUnit, float valueDivisor, bool readOnly = true, Func sampleAdjuster = null, Func sampleFilter = null) { try { AddCounter(new PerformanceCounter(categoryName, counterName, instanceName, aliasName, valueUnit, valueDivisor, readOnly) { SampleAdjuster = sampleAdjuster, SampleFilter = sampleFilter }); } catch { // Performance counter may not exist... } } /// /// Adds a to be monitored. /// /// The object to be monitored. public void AddCounter(PerformanceCounter counter) { lock (m_counters) m_counters.Add(counter); } /// /// Removes a being monitored. /// /// The object to be unmonitored. public void RemoveCounter(PerformanceCounter counter) { lock (m_counters) m_counters.Remove(counter); } /// /// Returns a object matching the specified counter name. /// /// Name of the to be retrieved. /// A object if a match is found; otherwise null. /// /// First with matching name is returned. If same name exists within /// multiple monitored categories, use overload instead. /// public PerformanceCounter FindCounter(string counterName) { lock (m_counters) { foreach (PerformanceCounter counter in m_counters) { if (string.Compare(counter.BaseCounter.CounterName, counterName, StringComparison.OrdinalIgnoreCase) == 0) return counter; // Return the match. } } return null; // No match found. } /// /// Returns a object matching the specified counter name. /// /// Category of the to be retrieved. /// Name of the to be retrieved. /// A object if a match is found; otherwise null. public PerformanceCounter FindCounter(string categoryName, string counterName) { lock (m_counters) { foreach (PerformanceCounter counter in m_counters) { if (string.Compare(counter.BaseCounter.CategoryName, categoryName, StringComparison.OrdinalIgnoreCase) == 0 && string.Compare(counter.BaseCounter.CounterName, counterName, StringComparison.OrdinalIgnoreCase) == 0) return counter; // Return the match. } } return null; // No match found. } /// /// Returns array matching the specified counter name. /// /// Name of the to be retrieved. /// A array of found matches, if any. public PerformanceCounter[] FindCounters(string counterName) { List counters = new List(); lock (m_counters) { counters.AddRange(m_counters.Where(counter => string.Compare(counter.BaseCounter.CounterName, counterName, StringComparison.OrdinalIgnoreCase) == 0)); } return counters.ToArray(); } /// /// Returns array matching the specified counter name. /// /// Category of the to be retrieved. /// Name of the to be retrieved. /// A array of found matches, if any. public PerformanceCounter[] FindCounters(string categoryName, string counterName) { List counters = new List(); lock (m_counters) { counters.AddRange(m_counters.Where(counter => string.Compare(counter.BaseCounter.CategoryName, categoryName, StringComparison.OrdinalIgnoreCase) == 0 && string.Compare(counter.BaseCounter.CounterName, counterName, StringComparison.OrdinalIgnoreCase) == 0)); } return counters.ToArray(); } /// /// Sample all defined counters. /// public void SampleCounters() { lock (m_counters) { foreach (PerformanceCounter counter in m_counters) counter.Sample(); SampleCustomCounters(); } } /// /// Derived class should override this method to sample any custom counters. /// protected virtual void SampleCustomCounters() { } private void SamplingTimer_Elapsed(object sender, ElapsedEventArgs e) => SampleCounters(); #endregion } }