File: System\Diagnostics\PerformanceData\CounterSet.cs
Web Access
Project: src\src\runtime\src\libraries\System.Diagnostics.PerformanceCounter\src\System.Diagnostics.PerformanceCounter.csproj (System.Diagnostics.PerformanceCounter)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;

namespace System.Diagnostics.PerformanceData
{
    /// <summary>
    /// CounterSet is equivalent to "Counter Object" in native performance counter terminology,
    /// or "Counter Category" in previous framework releases. It defines a abstract grouping of
    /// counters, where each counter defines measurable matrix. In the new performance counter
    /// infrastructure, CounterSet is defined by GUID called CounterSetGuid, and is hosted inside
    /// provider application, which is also defined by another GUID called ProviderGuid.
    /// </summary>
    public class CounterSet : IDisposable
    {
        internal PerfProvider _provider;
        internal Guid _providerGuid;
        internal Guid _counterSet;
        internal CounterSetInstanceType _instType;
        private readonly object _lockObject;
        private bool _instanceCreated;
        internal Dictionary<string, int> _stringToId;
        internal Dictionary<int, CounterType> _idToCounter;

        /// <summary>
        /// CounterSet constructor.
        /// </summary>
        /// <param name="providerGuid">ProviderGuid identifies the provider application. A provider identified by ProviderGuid could publish several CounterSets defined by different CounterSetGuids</param>
        /// <param name="counterSetGuid">CounterSetGuid identifies the specific CounterSet. CounterSetGuid should be unique.</param>
        /// <param name="instanceType">One of defined CounterSetInstanceType values</param>
        public CounterSet(Guid providerGuid, Guid counterSetGuid, CounterSetInstanceType instanceType)
        {
            if (!PerfProviderCollection.ValidateCounterSetInstanceType(instanceType))
            {
                throw new ArgumentException(SR.Format(SR.Perflib_Argument_InvalidCounterSetInstanceType, instanceType), nameof(instanceType));
            }

            _providerGuid = providerGuid;
            _counterSet = counterSetGuid;
            _instType = instanceType;
            PerfProviderCollection.RegisterCounterSet(_counterSet);
            _provider = PerfProviderCollection.QueryProvider(_providerGuid);
            _lockObject = new object();
            _stringToId = new Dictionary<string, int>();
            _idToCounter = new Dictionary<int, CounterType>();
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        ~CounterSet()
        {
            Dispose(false);
        }

        protected virtual void Dispose(bool disposing)
        {
            lock (this)
            {
                PerfProviderCollection.UnregisterCounterSet(_counterSet);

                if (_instanceCreated && _provider != null)
                {
                    lock (_lockObject)
                    {
                        if (_provider != null)
                        {
                            Interlocked.Decrement(ref _provider._counterSet);
                            if (_provider._counterSet <= 0)
                            {
                                PerfProviderCollection.RemoveProvider(_providerGuid);
                            }
                            _provider = null;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Add non-displayable new counter to CounterSet; that is, perfmon would not display the counter.
        /// </summary>
        /// <param name="counterId">CounterId uniquely identifies the counter within CounterSet</param>
        /// <param name="counterType">One of defined CounterType values</param>
        public void AddCounter(int counterId, CounterType counterType)
        {
            if (_provider == null)
            {
                throw new InvalidOperationException(SR.Format(SR.Perflib_InvalidOperation_NoActiveProvider, _providerGuid));
            }
            if (!PerfProviderCollection.ValidateCounterType(counterType))
            {
                throw new ArgumentException(SR.Format(SR.Perflib_Argument_InvalidCounterType, counterType), nameof(counterType));
            }
            if (_instanceCreated)
            {
                throw new InvalidOperationException(SR.Format(SR.Perflib_InvalidOperation_AddCounterAfterInstance, _counterSet));
            }

            lock (_lockObject)
            {
                if (_instanceCreated)
                {
                    throw new InvalidOperationException(SR.Format(SR.Perflib_InvalidOperation_AddCounterAfterInstance, _counterSet));
                }
                if (_idToCounter.ContainsKey(counterId))
                {
                    throw new ArgumentException(SR.Format(SR.Perflib_Argument_CounterAlreadyExists, counterId, _counterSet), nameof(counterId));
                }

                _idToCounter.Add(counterId, counterType);
            }
        }

        /// <summary>
        /// Add named new counter to CounterSet.
        /// </summary>
        /// <param name="counterId">CounterId uniquely identifies the counter within CounterSet</param>
        /// <param name="counterType">One of defined CounterType values</param>
        /// <param name="counterName">This is friendly name to help provider developers as indexer. and it might not match what is displayed in counter consumption applications lie perfmon.</param>
        public void AddCounter(int counterId, CounterType counterType, string counterName)
        {
            ArgumentNullException.ThrowIfNull(counterName);

            if (counterName.Length == 0)
            {
                throw new ArgumentException(SR.Perflib_Argument_EmptyCounterName, nameof(counterName));
            }
            if (!PerfProviderCollection.ValidateCounterType(counterType))
            {
                throw new ArgumentException(SR.Format(SR.Perflib_Argument_InvalidCounterType, counterType), nameof(counterType));
            }
            if (_provider == null)
            {
                throw new InvalidOperationException(SR.Format(SR.Perflib_InvalidOperation_NoActiveProvider, _providerGuid));
            }
            if (_instanceCreated)
            {
                throw new InvalidOperationException(SR.Format(SR.Perflib_InvalidOperation_AddCounterAfterInstance, _counterSet));
            }

            lock (_lockObject)
            {
                if (_instanceCreated)
                {
                    throw new InvalidOperationException(SR.Format(SR.Perflib_InvalidOperation_AddCounterAfterInstance, _counterSet));
                }
                if (_stringToId.ContainsKey(counterName))
                {
                    throw new ArgumentException(SR.Format(SR.Perflib_Argument_CounterNameAlreadyExists, counterName, _counterSet), nameof(counterName));
                }
                if (_idToCounter.ContainsKey(counterId))
                {
                    throw new ArgumentException(SR.Format(SR.Perflib_Argument_CounterAlreadyExists, counterId, _counterSet), nameof(counterId));
                }

                _stringToId.Add(counterName, counterId);
                _idToCounter.Add(counterId, counterType);
            }
        }

        /// <summary>
        /// Create instances of the CounterSet. Created CounterSetInstance identifies active identity and tracks raw counter data for that identity.
        /// </summary>
        /// <param name="instanceName">Friendly name identifies the instance. InstanceName would be shown in counter consumption applications like perfmon.</param>
        /// <returns>CounterSetInstance object</returns>
        public CounterSetInstance CreateCounterSetInstance(string instanceName)
        {
            ArgumentNullException.ThrowIfNull(instanceName);

            if (instanceName.Length == 0)
            {
                throw new ArgumentException(SR.Perflib_Argument_EmptyInstanceName, nameof(instanceName));
            }
            if (_provider == null)
            {
                throw new InvalidOperationException(SR.Format(SR.Perflib_InvalidOperation_NoActiveProvider, _providerGuid));
            }
            if (!_instanceCreated)
            {
                lock (_lockObject)
                {
                    if (!_instanceCreated)
                    {
                        if (_provider == null)
                        {
                            throw new InvalidOperationException(SR.Format(SR.Perflib_InvalidOperation_NoActiveProvider, _providerGuid));
                        }
                        if (_provider._hProvider.IsInvalid)
                        {
                            throw new InvalidOperationException(SR.Format(SR.Perflib_InvalidOperation_NoActiveProvider, _providerGuid));
                        }
                        if (_idToCounter.Count == 0)
                        {
                            throw new InvalidOperationException(SR.Format(SR.Perflib_InvalidOperation_CounterSetContainsNoCounter, _counterSet));
                        }

                        uint Status = (uint)Interop.Errors.ERROR_SUCCESS;

                        unsafe
                        {
                            uint CounterSetInfoSize = checked((uint)sizeof(Interop.PerfCounter.PerfCounterSetInfoStruct)
                                            + (uint)_idToCounter.Count * (uint)sizeof(Interop.PerfCounter.PerfCounterInfoStruct));
                            uint CounterSetInfoUsed = 0;
                            Span<byte> CounterSetBuffer = CounterSetInfoSize > 512 ? new byte[(int)CounterSetInfoSize] : stackalloc byte[(int)CounterSetInfoSize];
                            fixed (byte* pCounterSetBuffer = CounterSetBuffer)
                            {
                                Debug.Assert(sizeof(Interop.PerfCounter.PerfCounterSetInfoStruct) == 40);
                                Debug.Assert(sizeof(Interop.PerfCounter.PerfCounterInfoStruct) == 32);

                                Interop.PerfCounter.PerfCounterSetInfoStruct* CounterSetInfo;
                                Interop.PerfCounter.PerfCounterInfoStruct* CounterInfo;

                                uint CurrentCounter = 0;
                                uint CurrentOffset = 0;

                                CounterSetInfo = (Interop.PerfCounter.PerfCounterSetInfoStruct*)pCounterSetBuffer;
                                CounterSetInfo->CounterSetGuid = _counterSet;
                                CounterSetInfo->ProviderGuid = _providerGuid;
                                CounterSetInfo->NumCounters = (uint)_idToCounter.Count;
                                CounterSetInfo->InstanceType = (uint)_instType;

                                foreach (KeyValuePair<int, CounterType> CounterDef in _idToCounter)
                                {
                                    CounterSetInfoUsed = (uint)sizeof(Interop.PerfCounter.PerfCounterSetInfoStruct)
                                                    + (uint)CurrentCounter * (uint)sizeof(Interop.PerfCounter.PerfCounterInfoStruct);
                                    if (CounterSetInfoUsed < CounterSetInfoSize)
                                    {
                                        CounterInfo = (Interop.PerfCounter.PerfCounterInfoStruct*)(pCounterSetBuffer + CounterSetInfoUsed);
                                        CounterInfo->CounterId = (uint)CounterDef.Key;
                                        CounterInfo->CounterType = (uint)CounterDef.Value;
                                        CounterInfo->Attrib = 0x0000000000000001;   // PERF_ATTRIB_BY_REFERENCE
                                        CounterInfo->Size = (uint)sizeof(void*); // always use pointer size
                                        CounterInfo->DetailLevel = 100;                  // PERF_DETAIL_NOVICE
                                        CounterInfo->Scale = 0;                    // Default scale
                                        CounterInfo->Offset = CurrentOffset;

                                        CurrentOffset += CounterInfo->Size;
                                    }
                                    CurrentCounter++;
                                }
                                Status = Interop.PerfCounter.PerfSetCounterSetInfo(_provider._hProvider, CounterSetInfo, CounterSetInfoSize);

                                // ERROR_INVALID_PARAMETER, ERROR_ALREADY_EXISTS, ERROR_NOT_ENOUGH_MEMORY, ERROR_OUTOFMEMORY
                                if (Status != (uint)Interop.Errors.ERROR_SUCCESS)
                                {
                                    throw Status switch
                                    {
                                        (uint)Interop.Errors.ERROR_ALREADY_EXISTS => new InvalidOperationException(SR.Format(SR.Perflib_Argument_CounterSetAlreadyRegister, _counterSet)),

                                        _ => new Win32Exception((int)Status),
                                    };
                                }

                                Interlocked.Increment(ref _provider._counterSet);
                            }
                        }

                        _instanceCreated = true;
                    }
                }
            }

            CounterSetInstance thisInst = new CounterSetInstance(this, instanceName);
            return thisInst;
        }
    }
}