//****************************************************************************************************** // PerformanceCounter.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: // ---------------------------------------------------------------------------------------------------- // 06/04/2007 - Pinal C. Patel // Generated original version of source code. // 09/22/2008 - J. Ritchie Carroll // Converted to C#. // 09/30/2008 - Pinal C. Patel // Entered code comments. // 09/14/2009 - Stephen C. Wills // Added new header and license agreement. // 06/21/2010 - Stephen C. Wills // Fixed issue with counters not disposing properly. // 01/03/2011 - J. Ritchie Carroll // Added total lifetime counter statistics. // 01/04/2011 - J. Ritchie Carroll // Fixed issue with application of value divisor on lifetime statistics. // 12/14/2012 - Starlynn Danyelle Gilliam // Modified Header. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.Linq; namespace GSF.Diagnostics { /// /// Represents an extension of the basic providing additional statistical logic. /// /// /// This example shows how to create a performance counter for processor utilization: /// /// using System; /// using System.Threading; /// using GSF.Diagnostics; /// /// class Program /// { /// static void Main(string[] args) /// { /// PerformanceCounter counter = new PerformanceCounter("Processor", "% Processor Time", "_Total"); /// while (true) /// { /// Thread.Sleep(1000); /// counter.Sample(); /// Console.WriteLine(string.Format("Last value: {0}", counter.LastValue)); /// Console.WriteLine(string.Format("Minimum value: {0}", counter.MinimumValue)); /// Console.WriteLine(string.Format("Maximum value: {0}", counter.MaximumValue)); /// Console.WriteLine(string.Format("Average value: {0}", counter.AverageValue)); /// Console.WriteLine(new string('-', 30)); /// } /// } /// } /// /// public class PerformanceCounter : IDisposable { #region [ Members ] // Constants /// /// Default measurement unit of the statistical values. /// public const string DefaultValueUnit = "Unknown"; /// /// Default divisor to be applied to the statistical value. /// public const float DefaultValueDivisor = 1.0F; /// /// Default number of samples over which statistical values are to be calculated. /// public const int DefaultSamplingWindow = 120; // Fields private string m_aliasName; private string m_valueUnit; private float m_valueDivisor; private int m_samplingWindow; private System.Diagnostics.PerformanceCounter m_counter; private readonly object m_samplesLock; private readonly List m_samples; private float m_lifetimeMaximum; private decimal m_lifetimeTotal; private long m_lifetimeSampleCount; private bool m_disposed; #endregion #region [ Constructors ] /// /// Initializes a new instance of the class. /// /// 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 PerformanceCounter(string categoryName, string counterName, string instanceName) : this(categoryName, counterName, instanceName, counterName) { } /// /// Initializes a new instance of the class. /// /// 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 PerformanceCounter(string categoryName, string counterName, string instanceName, string aliasName) : this(categoryName, counterName, instanceName, aliasName, DefaultValueUnit) { } /// /// Initializes a new instance of the class. /// /// 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 PerformanceCounter(string categoryName, string counterName, string instanceName, string aliasName, string valueUnit) : this(categoryName, counterName, instanceName, aliasName, valueUnit, DefaultValueDivisor) { } /// /// Initializes a new instance of the class. /// /// 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. public PerformanceCounter(string categoryName, string counterName, string instanceName, string aliasName, string valueUnit, float valueDivisor, bool readOnly = true) { AliasName = aliasName; ValueUnit = valueUnit; ValueDivisor = valueDivisor; m_samplingWindow = DefaultSamplingWindow; m_samplesLock = new object(); m_samples = new List(); m_counter = new System.Diagnostics.PerformanceCounter(categoryName, counterName, instanceName); if (!readOnly) m_counter.ReadOnly = false; Reset(); } // Create a combined performance counter from multiple similar sources internal PerformanceCounter(PerformanceCounter[] sources) { if (sources is null) throw new ArgumentNullException(nameof(sources)); if (sources.Length < 1) throw new ArgumentOutOfRangeException(nameof(sources)); PerformanceCounter initialCounter = sources[0]; if (initialCounter is null) throw new InvalidOperationException("No valid performance counters available"); m_aliasName = initialCounter.m_aliasName; m_valueUnit = initialCounter.m_valueUnit; m_valueDivisor = initialCounter.m_valueDivisor; m_samplingWindow = initialCounter.m_samplingWindow; m_counter = initialCounter.m_counter; m_samplesLock = new object(); m_samples = new List(Enumerable.Repeat(0.0F, sources.Max(c => c.m_samples.Count))); for (int i = 0; i < m_samples.Count; i++) { foreach (PerformanceCounter source in sources) { if (source.m_samples.Count > i) m_samples[i] += source.m_samples[i]; } } m_lifetimeMaximum = sources.Max(c => c.m_lifetimeMaximum); m_lifetimeTotal = sources.Sum(c => c.m_lifetimeTotal); m_lifetimeSampleCount = sources.Max(c => c.m_lifetimeSampleCount); } /// /// Releases the unmanaged resources before the object is reclaimed by . /// ~PerformanceCounter() => Dispose(false); #endregion #region [ Properties ] /// /// Gets or sets an alias name for the . /// public string AliasName { get => m_aliasName; set { if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(value)); m_aliasName = value; } } /// /// Gets or sets the measurement unit of , , /// and /// /// The value being assigned is a null or empty string. public string ValueUnit { get => m_valueUnit; set { if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(value)); m_valueUnit = value; } } /// /// Gets or sets the divisor to be applied to the , , /// and . /// /// The value being assigned is not greater than 0. public float ValueDivisor { get => m_valueDivisor; set { if (value <= 0) throw new ArgumentOutOfRangeException(nameof(value), "Value must be greater than 0"); m_valueDivisor = value; } } /// /// Gets or sets the number of samples to use to determine the , /// , and . /// /// The value being assigned is not greater than 0. public int SamplingWindow { get => m_samplingWindow; set { if (value <= 0) throw new ArgumentOutOfRangeException(nameof(value), "Value must be greater than 0"); m_samplingWindow = value; } } /// /// Gets a list of sampled values from the /// public List Samples { get { lock (m_samplesLock) return new List(m_samples); } } /// /// Gets the last sample value from the samples of the . /// public float LastValue { get { lock (m_samplesLock) { if (m_samples.Count <= 0) return float.NaN; return m_samples[m_samples.Count - 1] / m_valueDivisor; } } } /// /// Gets the minimum sample value from the samples of the . /// public float MinimumValue { get { lock (m_samplesLock) { if (m_samples.Count <= 0) return float.NaN; return m_samples.Min() / m_valueDivisor; } } } /// /// Gets the maximum sample value from the samples of the . /// public float MaximumValue { get { lock (m_samplesLock) { if (m_samples.Count <= 0) return float.NaN; return m_samples.Max() / m_valueDivisor; } } } /// /// Gets the average value from the samples of the . /// public float AverageValue { get { lock (m_samplesLock) { if (m_samples.Count <= 0) return float.NaN; return m_samples.Average() / m_valueDivisor; } } } /// /// Gets the maximum sample value over the entire lifetime of the . /// public float LifetimeMaximumValue => m_lifetimeMaximum / m_valueDivisor; /// /// Gets the average sample value over the entire lifetime of the . /// public float LifetimeAverageValue { get { if (m_lifetimeSampleCount > 0) return (float)(m_lifetimeTotal / m_lifetimeSampleCount) / m_valueDivisor; return float.NaN; } } /// /// Gets the total values sampled over the entire lifetime of the . /// public long LifetimeSampleCount => m_lifetimeSampleCount; /// /// Gets the object that this objects wraps. /// public System.Diagnostics.PerformanceCounter BaseCounter => m_counter; /// /// Gets or sets an optional custom sample adjustment function. Can be used to apply linear adjustments to sampled values. /// public Func SampleAdjuster { get; set; } /// /// Gets or sets an optional custom sample filter function. Can be used to skip sampled values that are unreasonable. /// /// /// Return true if sample should be filtered; otherwise, false. /// public Func SampleFilter { get; set; } #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 { // This will be done regardless of whether the object is finalized or disposed. if (disposing) { // This will be done only when the object is disposed by calling Dispose(). if (!(m_counter is null)) m_counter.Dispose(); m_counter = null; } Reset(); } finally { m_disposed = true; // Prevent duplicate dispose. } } /// /// Obtains a sample value from the . /// public void Sample() { try { if (m_counter is null) return; float currentSample = m_counter.NextValue(); if (SampleFilter?.Invoke(currentSample) ?? false) return; if (!(SampleAdjuster is null)) currentSample = SampleAdjuster(currentSample); lock (m_samplesLock) { // Update counter sample set m_samples.Add(currentSample); // Maintain counter samples rolling window size while (m_samples.Count > m_samplingWindow) m_samples.RemoveAt(0); } // Track lifetime maximum value if (currentSample > m_lifetimeMaximum) m_lifetimeMaximum = currentSample; // Track lifetime average components checked { try { m_lifetimeSampleCount++; m_lifetimeTotal += (decimal)currentSample; } catch (OverflowException) { // If we overflow lifetime total, we restart total with current sample m_lifetimeSampleCount = 1; m_lifetimeTotal = (decimal)currentSample; } } } catch { // Not failing if counters cannot be sampled } } /// /// Resets the object to its initial state. /// public void Reset() { lock (m_samplesLock) m_samples.Clear(); } #endregion } }