//******************************************************************************************************
// 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
}
}