//******************************************************************************************************
// PerformanceMonitor.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/01/2007 - Pinal C. Patel
// Generated original version of source code.
// 08/08/2007 - J. Ritchie Carroll
// Added lock contention rate and datagram / sec performance counters.
// 09/04/2007 - Pinal C. Patel
// Added Status property.
// 09/22/2008 - J. Ritchie Carroll
// Converted to C#.
// 10/01/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 monitor not disposing of counters properly.
// 01/03/2011 - J. Ritchie Carroll
// Added counters for CLR memory consumption, IPv6 incoming / outgoing rates and lifetime status.
// 01/04/2011 - J. Ritchie Carroll
// Made addition of default counters optional in case user wants a custom monitor.
// Added new and reorganized default counters.
// 12/14/2012 - Starlynn Danyelle Gilliam
// Modified Header.
//
//******************************************************************************************************
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Text;
#if !MONO
using System.Threading;
#endif
using System.Timers;
using GSF.Units;
using Timer = System.Timers.Timer;
namespace GSF.Diagnostics
{
///
/// Represents a process performance monitor that operates similar to the Windows Performance Monitor utility
/// that can be used to monitor system performance.
///
///
/// This example shows how to use for monitoring application performance:
///
/// using System;
/// using System.Threading;
/// using GSF.Diagnostics;
///
/// class Program
/// {
/// static void Main(string[] args)
/// {
/// PerformanceMonitor perfMon = new PerformanceMonitor();
/// while (true)
/// {
/// // Display process performance.
/// Thread.Sleep(5000);
/// Console.WriteLine("");
/// Console.Write(perfMon.Status);
/// }
/// }
/// }
///
///
///
public class PerformanceMonitor : IDisposable, IProvideStatus
{
#region [ Members ]
// Constants
#if !MONO
///
/// Name of the custom thread pool counters category.
///
public const string ThreadPoolCountersCategoryName = "GSF Thread Pool Counters";
#endif
///
/// Default interval for sampling the .
///
public const double DefaultSamplingInterval = 1000.0D;
// Fields
private string m_processName;
private readonly List m_counters;
private readonly Timer m_samplingTimer;
private bool m_disposed;
#endregion
#region [ Constructors ]
///
/// Initializes a new instance of the class.
///
public PerformanceMonitor()
: this(DefaultSamplingInterval)
{
}
#if MONO
///
/// Initializes a new instance of the class.
///
/// Interval, in milliseconds, at which the are to be sampled.
/// Set to true to add default counters; otherwise false.
// Process based performance counters on Mono are initialized via current process ID
public PerformanceMonitor(double samplingInterval, bool addDefaultCounters = true)
: this(Process.GetCurrentProcess().Id.ToString(), samplingInterval, addDefaultCounters)
{
}
#else
///
/// Initializes a new instance of the class.
///
/// Interval, in milliseconds, at which the are to be sampled.
/// Set to true to add default counters; otherwise false.
public PerformanceMonitor(double samplingInterval, bool addDefaultCounters = true)
: this(Process.GetCurrentProcess().ProcessName, samplingInterval, addDefaultCounters)
{
}
#endif
///
/// Initializes a new instance of the class.
///
/// Name of the whose performance is to be monitored.
/// Set to true to add default counters; otherwise false.
public PerformanceMonitor(string processName, bool addDefaultCounters = true)
: this(processName, DefaultSamplingInterval, addDefaultCounters)
{
}
///
/// Initializes a new instance of the class.
///
/// Name of the whose performance is to be monitored.
/// Interval, in milliseconds, at which the are to be sampled.
/// Set to true to add default counters; otherwise false.
public PerformanceMonitor(string processName, double samplingInterval, bool addDefaultCounters = true)
{
if ((object)processName == null)
throw new ArgumentNullException("processName");
m_processName = processName;
m_counters = new List();
if (addDefaultCounters)
{
// Add default process and .NET counters
AddCounter("Process", "% Processor Time", m_processName, "CPU Utilization", "Average % / CPU", Environment.ProcessorCount);
#if !MONO
AddCounter("Process", "IO Data Bytes/sec", m_processName, "I/O Data Rate", "Kilobytes / sec", SI2.Kilo);
AddCounter("Process", "IO Data Operations/sec", m_processName, "I/O Activity Rate", "Operations / sec", 1);
AddCounter("Process", "Handle Count", m_processName, "Process Handle Count", "Total Handles", 1);
#endif
AddCounter("Process", "Thread Count", m_processName, "Process Thread Count", "System Threads", 1);
AddCounter(".NET CLR LocksAndThreads", "# of current logical Threads", m_processName, "CLR Thread Count", "Managed Threads", 1);
#if MONO
// Add Mono thread pool counters
AddCounter("Mono Threadpool", "# of Threads", m_processName, "Worker Threads", "Active in Pool", 1);
AddCounter("Mono Threadpool", "# of IO Threads", m_processName, "I/O Port Threads", "Active in Pool", 1);
#else
if (PerformanceCounterCategory.Exists(ThreadPoolCountersCategoryName))
{
// Add custom thread pool counters 1234567890123456
AddCounter(ThreadPoolCountersCategoryName, "Worker Threads", m_processName, "Worker Threads", "Active in Pool", 1, false);
// 12345678901234567890
AddCounter(ThreadPoolCountersCategoryName, "Completion Port Threads", m_processName, "I/O Port Threads", "Active in Pool", 1, false);
}
#endif
AddCounter(".NET CLR LocksAndThreads", "Current Queue Length", m_processName, "Thread Queue Size", "Waiting Threads", 1);
AddCounter(".NET CLR LocksAndThreads", "Contention Rate / sec", m_processName, "Lock Contention Rate", "Attempts / sec", 1);
AddCounter("Process", "Working Set", m_processName, "Process Memory Usage", "Megabytes", SI2.Mega);
AddCounter(".NET CLR Memory", "# Bytes in all Heaps", m_processName, "CLR Memory Usage", "Megabytes", SI2.Mega);
AddCounter(".NET CLR Memory", "Large Object Heap size", m_processName, "Large Object Heap", "Megabytes", SI2.Mega);
// 1234567890123456
AddCounter(".NET CLR Exceptions", "# of Exceps Thrown", m_processName, "Exception Count", "Total Exceptions", 1);
#if MONO
AddCounter(".NET CLR Exceptions", "# of Exceps Thrown/Sec", m_processName, "Exception Rate", "Exceptions / sec", 1);
#else
AddCounter(".NET CLR Exceptions", "# of Exceps Thrown / sec", m_processName, "Exception Rate", "Exceptions / sec", 1);
#endif
// Add default networking counters
#if MONO
PerformanceCounterCategory category = new PerformanceCounterCategory("Network Interface");
foreach (string instance in category.GetInstanceNames())
{
// 12345678901234567890
// "IP Outgoing (eth0)"
// "IP Incoming (eth0)"
AddCounter("Network Interface", "Bytes Sent/sec", instance, string.Format("IP Outgoing ({0})", instance).TruncateRight(20), "Bytes / sec", 1);
AddCounter("Network Interface", "Bytes Received/sec", instance, string.Format("IP Incoming ({0})", instance).TruncateRight(20), "Bytes / sec", 1);
}
#else
if (PerformanceCounterCategory.Exists("IPv4"))
{
// 12345678901234567890
AddCounter("IPv4", "Datagrams Sent/sec", "", "IPv4 Outgoing Rate", "Datagrams / sec", 1);
// 12345678901234567890
AddCounter("IPv4", "Datagrams Received/sec", "", "IPv4 Incoming Rate", "Datagrams / sec", 1);
}
else if (PerformanceCounterCategory.Exists("IP"))
{
AddCounter("IP", "Datagrams Sent/sec", "", "IP Outgoing Rate", "Datagrams / sec", 1);
AddCounter("IP", "Datagrams Received/sec", "", "IP Incoming Rate", "Datagrams / sec", 1);
}
if (PerformanceCounterCategory.Exists("IPv6"))
{
AddCounter("IPv6", "Datagrams Sent/sec", "", "IPv6 Outgoing Rate", "Datagrams / sec", 1);
AddCounter("IPv6", "Datagrams Received/sec", "", "IPv6 Incoming Rate", "Datagrams / sec", 1);
}
#endif
}
m_samplingTimer = new Timer(samplingInterval);
m_samplingTimer.Elapsed += m_samplingTimer_Elapsed;
m_samplingTimer.Start();
}
///
/// Releases the unmanaged resources before the object is reclaimed by .
///
~PerformanceMonitor()
{
Dispose(false);
}
#endregion
#region [ Properties ]
///
/// Gets or sets the name of the to be monitored.
///
public string ProcessName
{
get
{
return m_processName;
}
set
{
m_processName = value;
lock (m_counters)
{
foreach (PerformanceCounter counter in m_counters)
{
// Only update the InstanceName for counters that had it set.
if (!string.IsNullOrEmpty(counter.BaseCounter.InstanceName))
counter.BaseCounter.InstanceName = m_processName;
}
}
}
}
///
/// Gets or sets the interval, in milliseconds, at which the are to be sampled.
///
public double SamplingInterval
{
get
{
return 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 that monitors the processor utilization of the monitored process.
///
/// This is added by default.
public PerformanceCounter CPUUsage
{
get
{
return FindCounter("% Processor Time");
}
}
///
/// Gets the that monitors the IP based datagrams sent / second of the system.
///
/// This is added by default.
public PerformanceCounter IPDataSendRate
{
get
{
try
{
PerformanceCounter[] sources;
#if MONO
sources = FindCounters("Bytes Sent/sec");
#else
sources = FindCounters("Datagrams Sent/sec");
#endif
if ((object)sources != null && sources.Length > 0)
return new PerformanceCounter(sources);
return null;
}
catch
{
// Not failing if performance counter cannot be created
return null;
}
}
}
///
/// Gets the that monitors the IP based datagrams received / second of the system.
///
/// This is added by default.
public PerformanceCounter IPDataReceiveRate
{
get
{
try
{
PerformanceCounter[] sources;
#if MONO
sources = FindCounters("Bytes Received/sec");
#else
sources = FindCounters("Datagrams Received/sec");
#endif
if ((object)sources != null && sources.Length > 0)
return new PerformanceCounter(sources);
return null;
}
catch
{
// Not failing if performance counter cannot be created
return null;
}
}
}
///
/// Gets the that monitors the .NET threading contention rate / second of the process.
///
/// This is added by default.
public PerformanceCounter ThreadingContentionRate
{
get
{
return FindCounter("Contention Rate / sec");
}
}
///
/// Gets the that monitors the memory utilization of the monitored process.
///
/// This is added by default.
public PerformanceCounter MemoryUsage
{
get
{
return FindCounter("Working Set");
}
}
///
/// Gets the that monitors the rate at which the monitored process is
/// issuing bytes to I/O operations that do not involve data such as control operations.
///
/// This is added by default.
public PerformanceCounter IOUsage
{
get
{
return FindCounter("IO Data Bytes/sec");
}
}
///
/// Gets the that monitors the rate at which the monitored process is
/// issuing read and write I/O operations.
///
/// This is added by default.
public PerformanceCounter IOActivity
{
get
{
return FindCounter("IO Data Operations/sec");
}
}
///
/// Gets the that monitors the total number of handles currently open by
/// the monitored process.
///
/// This is added by default.
public PerformanceCounter HandleCount
{
get
{
return FindCounter("Handle Count");
}
}
///
/// Gets the that monitors the number of threads currently active in the
/// monitored process.
///
/// This is added by default.
public PerformanceCounter ThreadCount
{
get
{
return FindCounter("Thread Count");
}
}
///
/// Gets the friendly name of the object.
///
public string Name
{
get
{
return string.Format("{0}.{1}", this.GetType().Name, m_processName);
}
}
///
/// Gets the current status of the object.
///
public 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.AppendFormat("{0}Statistics calculated using last {1} counter values sampled every {2}.{3}", Environment.NewLine, PerformanceCounter.DefaultSamplingWindow, samplingInterval, Environment.NewLine);
return status.ToString();
}
}
///
/// Gets the lifetime status statistics of the object.
///
public 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.AppendFormat("{0}Statistics calculated using {1} counter values sampled every {2}.{3}", Environment.NewLine, sampleCount, samplingInterval, Environment.NewLine);
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)
{
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 ((object)m_samplingTimer != null)
{
m_samplingTimer.Elapsed -= m_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.
public void AddCounter(string categoryName, string counterName, string instanceName, string aliasName, string valueUnit, float valueDivisor, bool readOnly = true)
{
try
{
AddCounter(new PerformanceCounter(categoryName, counterName, instanceName, aliasName, valueUnit, valueDivisor, readOnly));
}
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)
{
foreach (PerformanceCounter counter in m_counters)
{
if (string.Compare(counter.BaseCounter.CounterName, counterName, StringComparison.OrdinalIgnoreCase) == 0)
counters.Add(counter);
}
}
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)
{
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)
counters.Add(counter);
}
}
return counters.ToArray();
}
///
/// Sample all defined counters.
///
public void SampleCounters()
{
lock (m_counters)
{
#if !MONO
// Sample custom thread pool counters (these already exist in Mono)
PerformanceCounter workerThreadsCounter = FindCounter(ThreadPoolCountersCategoryName, "Worker Threads");
PerformanceCounter completionPortThreadsCounter = FindCounter(ThreadPoolCountersCategoryName, "Completion Port Threads");
if ((object)workerThreadsCounter != null && (object)completionPortThreadsCounter != null)
{
System.Diagnostics.PerformanceCounter workerThreads = workerThreadsCounter.BaseCounter;
System.Diagnostics.PerformanceCounter completionPortThreads = completionPortThreadsCounter.BaseCounter;
if ((object)workerThreads != null && (object)completionPortThreads != null)
{
int maximumWorkerThreads, maximumCompletionPortThreads, availableWorkerThreads, availableCompletionPortThreads;
ThreadPool.GetMaxThreads(out maximumWorkerThreads, out maximumCompletionPortThreads);
ThreadPool.GetAvailableThreads(out availableWorkerThreads, out availableCompletionPortThreads);
workerThreads.RawValue = maximumWorkerThreads - availableWorkerThreads;
completionPortThreads.RawValue = maximumCompletionPortThreads - availableCompletionPortThreads;
}
}
#endif
foreach (PerformanceCounter counter in m_counters)
{
counter.Sample();
}
}
}
private void m_samplingTimer_Elapsed(object sender, ElapsedEventArgs e)
{
SampleCounters();
}
#endregion
#region [ Static ]
#if !MONO
// Static Constructor
static PerformanceMonitor()
{
try
{
if (!PerformanceCounterCategory.Exists(ThreadPoolCountersCategoryName))
{
CounterCreationDataCollection customPerformanceCounters = new CounterCreationDataCollection();
// Create custom counter objects for thread pool monitoring
CounterCreationData workerThreadCounter = new CounterCreationData();
workerThreadCounter.CounterName = "Worker Threads";
workerThreadCounter.CounterHelp = "Active worker threads in the thread pool";
workerThreadCounter.CounterType = PerformanceCounterType.NumberOfItems32;
CounterCreationData completionPortThreadCounter = new CounterCreationData();
completionPortThreadCounter.CounterName = "Completion Port Threads";
completionPortThreadCounter.CounterHelp = "Active completion port threads in the thread pool";
completionPortThreadCounter.CounterType = PerformanceCounterType.NumberOfItems32;
// Add custom counter objects to CounterCreationDataCollection
customPerformanceCounters.Add(workerThreadCounter);
customPerformanceCounters.Add(completionPortThreadCounter);
// Bind the counters to the PerformanceCounterCategory
PerformanceCounterCategory.Create(ThreadPoolCountersCategoryName, "Application thread pool counters", PerformanceCounterCategoryType.MultiInstance, customPerformanceCounters);
}
}
catch
{
// Not failing if custom counters cannot be created
}
}
#endif
#endregion
}
}