File: services\monitoring\system\diagnosticts\PerformanceCounter.cs
Project: ndp\fx\src\System.csproj (System)
//------------------------------------------------------------------------------
// <copyright file="PerformanceCounter.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
 
namespace System.Diagnostics {
    using System.Runtime.Serialization.Formatters;
    using System.Runtime.InteropServices;
    using System.ComponentModel;
    using System.Diagnostics;
    using System;
    using System.Collections;
    using System.Globalization;
    using System.Security;
    using System.Security.Permissions;
    using System.Runtime.CompilerServices;
    using System.Runtime.ConstrainedExecution;
    using System.Threading;
 
 
    /// <devdoc>
    ///     Performance Counter component.
    ///     This class provides support for NT Performance counters.
    ///     It handles both the existing counters (accesible by Perf Registry Interface)
    ///     and user defined (extensible) counters.
    ///     This class is a part of a larger framework, that includes the perf dll object and
    ///     perf service.
    /// </devdoc>
    [
    InstallerType("System.Diagnostics.PerformanceCounterInstaller," + AssemblyRef.SystemConfigurationInstall),
    SRDescription(SR.PerformanceCounterDesc),
    HostProtection(Synchronization = true, SharedState = true)
    ]
    public sealed class PerformanceCounter : Component, ISupportInitialize {
        private string machineName;
        private string categoryName;
        private string counterName;
        private string instanceName;
        private PerformanceCounterInstanceLifetime instanceLifetime = PerformanceCounterInstanceLifetime.Global;
 
        private bool isReadOnly;
        private bool initialized = false;
        private string helpMsg = null;
        private int counterType = -1;
 
        // Cached old sample
        private CounterSample oldSample = CounterSample.Empty;
 
        // Cached IP Shared Performanco counter
        private SharedPerformanceCounter sharedCounter;
 
        [Obsolete("This field has been deprecated and is not used.  Use machine.config or an application configuration file to set the size of the PerformanceCounter file mapping.")]
        public static int DefaultFileMappingSize = 524288;
 
        private Object m_InstanceLockObject;
        private Object InstanceLockObject {
            get {
                if (m_InstanceLockObject == null) {
                    Object o = new Object();
                    Interlocked.CompareExchange(ref m_InstanceLockObject, o, null);
                }
                return m_InstanceLockObject;
            }
        }
 
        /// <devdoc>
        ///     The defaut constructor. Creates the perf counter object
        /// </devdoc>
        public PerformanceCounter() {
            machineName = ".";
            categoryName = String.Empty;
            counterName = String.Empty;
            instanceName = String.Empty;
            this.isReadOnly = true;
            GC.SuppressFinalize(this);
        }
 
        /// <devdoc>
        ///     Creates the Performance Counter Object
        /// </devdoc>
        public PerformanceCounter(string categoryName, string counterName, string instanceName, string machineName) {
            this.MachineName = machineName;
            this.CategoryName = categoryName;
            this.CounterName = counterName;
            this.InstanceName = instanceName;
            this.isReadOnly = true;
            Initialize();
            GC.SuppressFinalize(this);
        }
 
        internal PerformanceCounter(string categoryName, string counterName, string instanceName, string machineName, bool skipInit) {
            this.MachineName = machineName;
            this.CategoryName = categoryName;
            this.CounterName = counterName;
            this.InstanceName = instanceName;
            this.isReadOnly = true;
            this.initialized = true;
            GC.SuppressFinalize(this);
        }
 
        /// <devdoc>
        ///     Creates the Performance Counter Object on local machine.
        /// </devdoc>
        public PerformanceCounter(string categoryName, string counterName, string instanceName) :
        this(categoryName, counterName, instanceName, true) {
        }
 
        /// <devdoc>
        ///     Creates the Performance Counter Object on local machine.
        /// </devdoc>
        public PerformanceCounter(string categoryName, string counterName, string instanceName, bool readOnly) {
            if(!readOnly) {
                VerifyWriteableCounterAllowed();
            }
            this.MachineName = ".";
            this.CategoryName = categoryName;
            this.CounterName = counterName;
            this.InstanceName = instanceName;
            this.isReadOnly = readOnly;
            Initialize();
            GC.SuppressFinalize(this);
        }
 
        /// <devdoc>
        ///     Creates the Performance Counter Object, assumes that it's a single instance
        /// </devdoc>
        public PerformanceCounter(string categoryName, string counterName) :
        this(categoryName, counterName, true) {
        }
 
        /// <devdoc>
        ///     Creates the Performance Counter Object, assumes that it's a single instance
        /// </devdoc>
        public PerformanceCounter(string categoryName, string counterName, bool readOnly) :
        this(categoryName, counterName, "", readOnly) {
        }
 
        /// <devdoc>
        ///     Returns the performance category name for this performance counter
        /// </devdoc>
        [
        ReadOnly(true),
        DefaultValue(""),
        TypeConverter("System.Diagnostics.Design.CategoryValueConverter, " + AssemblyRef.SystemDesign),
        SRDescription(SR.PCCategoryName),
        SettingsBindable(true)
        ]
        public string CategoryName {
            get {
                return categoryName;
            }
            set {
                if (value == null)
                    throw new ArgumentNullException("value");
 
                if (categoryName == null || String.Compare(categoryName, value, StringComparison.OrdinalIgnoreCase) != 0) {
                    categoryName = value;
                    Close();
                }
            }
        }
 
        /// <devdoc>
        ///     Returns the description message for this performance counter
        /// </devdoc>
        [
        ReadOnly(true),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
        MonitoringDescription(SR.PC_CounterHelp)
        ]
        public string CounterHelp {
            get {
                string currentCategoryName = categoryName;
                string currentMachineName = machineName;
                
                PerformanceCounterPermission permission = new PerformanceCounterPermission(PerformanceCounterPermissionAccess.Read, currentMachineName, currentCategoryName);
                permission.Demand();
                Initialize();
 
                if (helpMsg == null)
                    helpMsg = PerformanceCounterLib.GetCounterHelp(currentMachineName, currentCategoryName, this.counterName);
 
                return helpMsg;
            }
        }
 
        /// <devdoc>
        ///     Sets/returns the performance counter name for this performance counter
        /// </devdoc>
        [
        ReadOnly(true),
        DefaultValue(""),
        TypeConverter("System.Diagnostics.Design.CounterNameConverter, " + AssemblyRef.SystemDesign),
        SRDescription(SR.PCCounterName),
        SettingsBindable(true)
        ]
        public string CounterName {
            get {
                return counterName;
            }
            set {
                if (value == null)
                    throw new ArgumentNullException("value");
 
                if (counterName == null || String.Compare(counterName, value, StringComparison.OrdinalIgnoreCase) != 0) {
                    counterName = value;
                    Close();
                }
            }
        }
 
        /// <devdoc>
        ///     Sets/Returns the counter type for this performance counter
        /// </devdoc>
        [
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
        MonitoringDescription(SR.PC_CounterType)
        ]
        public PerformanceCounterType CounterType {
            get {
                if (counterType == -1) {
                    string currentCategoryName = categoryName;
                    string currentMachineName = machineName;
                    
                    // This is the same thing that NextSample does, except that it doesn't try to get the actual counter
                    // value.  If we wanted the counter value, we would need to have an instance name. 
                    PerformanceCounterPermission permission = new PerformanceCounterPermission(PerformanceCounterPermissionAccess.Read, currentMachineName, currentCategoryName);
                    permission.Demand();
                    
                    Initialize();
                    CategorySample categorySample = PerformanceCounterLib.GetCategorySample(currentMachineName, currentCategoryName);
                    CounterDefinitionSample counterSample = categorySample.GetCounterDefinitionSample(this.counterName);
                    this.counterType = counterSample.CounterType;
                }
 
                return(PerformanceCounterType) counterType;
            }
        }
 
        [
            DefaultValue(PerformanceCounterInstanceLifetime.Global),
            SRDescription(SR.PCInstanceLifetime),
        ]
        public PerformanceCounterInstanceLifetime InstanceLifetime {
            get { return instanceLifetime; }
            set { 
                if (value > PerformanceCounterInstanceLifetime.Process || value < PerformanceCounterInstanceLifetime.Global)
                    throw new ArgumentOutOfRangeException("value");
 
                if (initialized)
                    throw new InvalidOperationException(SR.GetString(SR.CantSetLifetimeAfterInitialized));
                
                instanceLifetime = value;
            }
        }
        
        /// <devdoc>
        ///     Sets/returns an instance name for this performance counter
        /// </devdoc>
        [
        ReadOnly(true),
        DefaultValue(""),
        TypeConverter("System.Diagnostics.Design.InstanceNameConverter, " + AssemblyRef.SystemDesign),
        SRDescription(SR.PCInstanceName),
        SettingsBindable(true)
        ]
        public string InstanceName {
            get {
                return instanceName;
            }
            set {
                if (value == null && instanceName == null)
                    return;
 
                if ((value == null && instanceName != null) ||
                      (value != null && instanceName == null) ||
                      String.Compare(instanceName, value, StringComparison.OrdinalIgnoreCase) != 0) {
                    instanceName = value;
                    Close();
                }
            }
        }
 
        /// <devdoc>
        ///     Returns true if counter is read only (system counter, foreign extensible counter or remote counter)
        /// </devdoc>
        [
        Browsable(false),
        DefaultValue(true),
        MonitoringDescription(SR.PC_ReadOnly)
        ]
        public bool ReadOnly {
            get {
                return isReadOnly;
            }
 
            set {
                if (value != this.isReadOnly) {
                    if(value == false) {
                        VerifyWriteableCounterAllowed();
                    }
                    this.isReadOnly = value;
                    Close();
                }
            }
        }
 
 
        /// <devdoc>
        ///     Set/returns the machine name for this performance counter
        /// </devdoc>
        [
        Browsable(false),
        DefaultValue("."),
        SRDescription(SR.PCMachineName),
        SettingsBindable(true)
        ]
        public string MachineName {
            get {
                return machineName;
            }
            set {
                if (!SyntaxCheck.CheckMachineName(value))
                    throw new ArgumentException(SR.GetString(SR.InvalidParameter, "machineName", value));
 
                if (machineName != value) {
                    machineName = value;
                    Close();
                }
            }
        }
 
        /// <devdoc>
        ///     Directly accesses the raw value of this counter.  If counter type is of a 32-bit size, it will truncate
        ///     the value given to 32 bits.  This can be significantly more performant for scenarios where
        ///     the raw value is sufficient.   Note that this only works for custom counters created using
        ///     this component,  non-custom counters will throw an exception if this property is accessed.
        /// </devdoc>
        [
        Browsable(false),
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
        MonitoringDescription(SR.PC_RawValue)
        ]
        public long RawValue {
            get {
                if (ReadOnly) {
                    //No need to initialize or Demand, since NextSample already does.
                    return NextSample().RawValue;
                }
                else {
                    Initialize();
 
                    return this.sharedCounter.Value;
                }
            }
            set {
                if (ReadOnly)
                    ThrowReadOnly();
 
                Initialize();
 
                this.sharedCounter.Value = value;
            }
        }
 
        /// <devdoc>
        /// </devdoc>
        public void BeginInit() {
            this.Close();
        }
 
        /// <devdoc>
        ///     Frees all the resources allocated by this counter
        /// </devdoc>
        public void Close() {
            this.helpMsg = null;
            this.oldSample = CounterSample.Empty;
            this.sharedCounter = null;
            this.initialized = false;
            this.counterType = -1;
        }
 
        /// <devdoc>
        ///     Frees all the resources allocated for all performance
        ///     counters, frees File Mapping used by extensible counters,
        ///     unloads dll's used to read counters.
        /// </devdoc>
        public static void CloseSharedResources() {
            PerformanceCounterPermission permission = new PerformanceCounterPermission(PerformanceCounterPermissionAccess.Read, ".", "*");
            permission.Demand();
            PerformanceCounterLib.CloseAllLibraries();
        }
 
        /// <internalonly/>
        /// <devdoc>
        /// </devdoc>
        protected override void Dispose(bool disposing) {
            // safe to call while finalizing or disposing
            //
            if (disposing) {
                //Dispose managed and unmanaged resources
                Close();
            }
 
            base.Dispose(disposing);
        }
 
        /// <devdoc>
        ///     Decrements counter by one using an efficient atomic operation.
        /// </devdoc>
        public long Decrement() {
            if (ReadOnly)
                ThrowReadOnly();
 
            Initialize();
 
            return this.sharedCounter.Decrement();
        }
 
        /// <devdoc>
        /// </devdoc>
        public void EndInit() {
            Initialize();
        }
 
        /// <devdoc>
        ///     Increments the value of this counter.  If counter type is of a 32-bit size, it'll truncate
        ///     the value given to 32 bits. This method uses a mutex to guarantee correctness of
        ///     the operation in case of multiple writers. This method should be used with caution because of the negative
        ///     impact on performance due to creation of the mutex.
        /// </devdoc>
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        public long IncrementBy(long value) {
            if (isReadOnly)
                ThrowReadOnly();
 
            Initialize();
 
            return this.sharedCounter.IncrementBy(value);
        }
 
        /// <devdoc>
        ///     Increments counter by one using an efficient atomic operation.
        /// </devdoc>
        public long Increment() {
            if (isReadOnly)
                ThrowReadOnly();
 
            Initialize();
 
            return this.sharedCounter.Increment();
        }
 
        private void ThrowReadOnly() {
            throw new InvalidOperationException(SR.GetString(SR.ReadOnlyCounter));
        }
        
        private static void VerifyWriteableCounterAllowed() {
            if(EnvironmentHelpers.IsAppContainerProcess) {
                throw new NotSupportedException(SR.GetString(SR.PCNotSupportedUnderAppContainer));
            }
        }
 
        private void Initialize() {
            // Keep this method small so the JIT will inline it.
            if (!initialized && !DesignMode) {
                InitializeImpl();
            }
        }
 
        /// <devdoc>
        ///     Intializes required resources
        /// </devdoc>
        //[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        private void InitializeImpl() {
            bool tookLock = false;
            RuntimeHelpers.PrepareConstrainedRegions();
            try {
                Monitor.Enter(InstanceLockObject, ref tookLock);
 
                if (!initialized) {
                    string currentCategoryName = categoryName;
                    string currentMachineName = machineName;
 
                    if (currentCategoryName == String.Empty)
                        throw new InvalidOperationException(SR.GetString(SR.CategoryNameMissing));
                    if (this.counterName == String.Empty)
                        throw new InvalidOperationException(SR.GetString(SR.CounterNameMissing));
 
                    if (this.ReadOnly) {
                        PerformanceCounterPermission permission = new PerformanceCounterPermission(PerformanceCounterPermissionAccess.Read, currentMachineName, currentCategoryName);
 
                        permission.Demand();
 
                        if (!PerformanceCounterLib.CounterExists(currentMachineName, currentCategoryName, counterName))
                            throw new InvalidOperationException(SR.GetString(SR.CounterExists, currentCategoryName, counterName));
 
                        PerformanceCounterCategoryType categoryType = PerformanceCounterLib.GetCategoryType(currentMachineName, currentCategoryName);
                        if (categoryType == PerformanceCounterCategoryType.MultiInstance) {
                            if (String.IsNullOrEmpty(instanceName))
                                throw new InvalidOperationException(SR.GetString(SR.MultiInstanceOnly, currentCategoryName));
                        } else if (categoryType == PerformanceCounterCategoryType.SingleInstance) {
                            if (!String.IsNullOrEmpty(instanceName))
                                throw new InvalidOperationException(SR.GetString(SR.SingleInstanceOnly, currentCategoryName));
                        }
 
                        if (instanceLifetime != PerformanceCounterInstanceLifetime.Global)
                            throw new InvalidOperationException(SR.GetString(SR.InstanceLifetimeProcessonReadOnly));
 
                        this.initialized = true;
                    } else {
                        PerformanceCounterPermission permission = new PerformanceCounterPermission(PerformanceCounterPermissionAccess.Write, currentMachineName, currentCategoryName);
                        permission.Demand();
 
                        if (currentMachineName != "." && String.Compare(currentMachineName, PerformanceCounterLib.ComputerName, StringComparison.OrdinalIgnoreCase) != 0)
                            throw new InvalidOperationException(SR.GetString(SR.RemoteWriting));
 
                        SharedUtils.CheckNtEnvironment();
 
                        if (!PerformanceCounterLib.IsCustomCategory(currentMachineName, currentCategoryName))
                            throw new InvalidOperationException(SR.GetString(SR.NotCustomCounter));
 
                        // check category type
                        PerformanceCounterCategoryType categoryType = PerformanceCounterLib.GetCategoryType(currentMachineName, currentCategoryName);
                        if (categoryType == PerformanceCounterCategoryType.MultiInstance) {
                            if (String.IsNullOrEmpty(instanceName))
                                throw new InvalidOperationException(SR.GetString(SR.MultiInstanceOnly, currentCategoryName));
                        } else if (categoryType == PerformanceCounterCategoryType.SingleInstance) {
                            if (!String.IsNullOrEmpty(instanceName))
                                throw new InvalidOperationException(SR.GetString(SR.SingleInstanceOnly, currentCategoryName));
                        }
 
                        if (String.IsNullOrEmpty(instanceName) && InstanceLifetime == PerformanceCounterInstanceLifetime.Process)
                            throw new InvalidOperationException(SR.GetString(SR.InstanceLifetimeProcessforSingleInstance));
 
                        this.sharedCounter = new SharedPerformanceCounter(currentCategoryName.ToLower(CultureInfo.InvariantCulture), counterName.ToLower(CultureInfo.InvariantCulture), instanceName.ToLower(CultureInfo.InvariantCulture), instanceLifetime);
                        this.initialized = true;
                    }
                }
            } finally {
                if (tookLock)
                    Monitor.Exit(InstanceLockObject);
            }
 
        }
 
         // Will cause an update, raw value
        /// <devdoc>
        ///     Obtains a counter sample and returns the raw value for it.
        /// </devdoc>
        public CounterSample NextSample() {
            string currentCategoryName = categoryName;
            string currentMachineName = machineName;
             
            PerformanceCounterPermission permission = new PerformanceCounterPermission(PerformanceCounterPermissionAccess.Read, currentMachineName, currentCategoryName);
            permission.Demand();
 
            Initialize();
            CategorySample categorySample = PerformanceCounterLib.GetCategorySample(currentMachineName, currentCategoryName);
            CounterDefinitionSample counterSample = categorySample.GetCounterDefinitionSample(this.counterName);
            this.counterType = counterSample.CounterType;
            if (!categorySample.IsMultiInstance) {
                if (instanceName != null && instanceName.Length != 0)
                    throw new InvalidOperationException(SR.GetString(SR.InstanceNameProhibited, this.instanceName));
 
                return counterSample.GetSingleValue();
            }
            else {
                if (instanceName == null || instanceName.Length == 0)
                    throw new InvalidOperationException(SR.GetString(SR.InstanceNameRequired));
 
                return counterSample.GetInstanceValue(this.instanceName);
            }
        }
 
        /// <devdoc>
        ///     Obtains a counter sample and returns the calculated value for it.
        ///     NOTE: For counters whose calculated value depend upon 2 counter reads,
        ///           the very first read will return 0.0.
        /// </devdoc>
        public float NextValue() {
            //No need to initialize or Demand, since NextSample already does.
            CounterSample newSample = NextSample();
            float retVal = 0.0f;
 
            retVal = CounterSample.Calculate(oldSample, newSample);
            oldSample = newSample;
 
            return retVal;
        }
 
        /// <devdoc>
        ///     Removes this counter instance from the shared memory
        /// </devdoc>
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        public void RemoveInstance() {
            if (isReadOnly)
                throw new InvalidOperationException(SR.GetString(SR.ReadOnlyRemoveInstance));
 
            Initialize();
            sharedCounter.RemoveInstance(this.instanceName.ToLower(CultureInfo.InvariantCulture), instanceLifetime);
        }
    }
}