File: System\Diagnostics\SharedPerformanceCounter.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;
using System.Collections;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using Microsoft.Win32;
using Microsoft.Win32.SafeHandles;

namespace System.Diagnostics
{
    internal sealed unsafe class SharedPerformanceCounter
    {
        private const int MaxSpinCount = 5000;
        internal const int DefaultCountersFileMappingSize = 524288;
        internal const int MaxCountersFileMappingSize = 33554432;
        internal const int MinCountersFileMappingSize = 32768;
        internal const int InstanceNameMaxLength = 127;
        internal const int InstanceNameSlotSize = 256;
        internal const string SingleInstanceName = "systemdiagnosticssharedsingleinstance";
        internal const string DefaultFileMappingName = "netfxcustomperfcounters.1.0";
        internal static readonly int s_singleInstanceHashCode = GetWstrHashCode(SingleInstanceName);
        private static readonly Hashtable s_categoryDataTable = new Hashtable(StringComparer.Ordinal);

        private static long s_lastInstanceLifetimeSweepTick;
        private const long InstanceLifetimeSweepWindow = 30 * 10000000; //ticks
        private static ProcessData s_procData;

        private static ProcessData ProcessData
        {
            get
            {
                if (s_procData == null)
                {
                    try
                    {
                        int pid = (int)Interop.Kernel32.GetCurrentProcessId();
                        long startTime = -1;

                        // Though we have asserted the required CAS permissions above, we may
                        // still fail to query the process information if the user does not
                        // have the necessary process access rights or privileges.
                        // This might be the case if the current process was started by a
                        // different user (primary token) than the current user
                        // (impersonation token) that has less privilege/ACL rights.
                        using (SafeProcessHandle procHandle = Interop.Kernel32.OpenProcess(Interop.Advapi32.ProcessOptions.PROCESS_QUERY_INFORMATION, false, pid))
                        {
                            if (!procHandle.IsInvalid)
                            {
                                long temp;
                                Interop.Kernel32.GetProcessTimes(procHandle, out startTime, out temp, out temp, out temp);
                            }
                        }
                        s_procData = new ProcessData(pid, startTime);
                    }
                    finally
                    {
                    }
                }
                return s_procData;
            }
        }

        // InitialOffset is the offset in our global shared memory where we put the first CategoryEntry.  It needs to be 4 because in
        // v1.0 and v1.1 we used IntPtr.Size.  That creates potential side-by-side issues on 64 bit machines using WOW64.
        // A v1.0 app running on WOW64 will assume the InitialOffset is 4.  A true 64 bit app on the same machine will assume
        // the initial offset is 8.
        // However, using an offset of 4 means that our CounterEntry.Value is potentially misaligned.  This is why we have SetValue
        // and other methods which split CounterEntry.Value into two ints.  With separate shared memory blocks per
        // category, we can fix this and always use an inital offset of 8.
        internal int _initialOffset = 4;

        private readonly CategoryData _categoryData;
        private byte* _baseAddress;
        private readonly unsafe CounterEntry* _counterEntryPointer;
        private readonly string _categoryName;
        private readonly int _categoryNameHashCode;
        private int _thisInstanceOffset = -1;

        internal SharedPerformanceCounter(string catName, string counterName, string instanceName) :
            this(catName, counterName, instanceName, PerformanceCounterInstanceLifetime.Global)
        { }

        internal unsafe SharedPerformanceCounter(string catName, string counterName, string instanceName, PerformanceCounterInstanceLifetime lifetime)
        {
            _categoryName = catName;
            _categoryNameHashCode = GetWstrHashCode(_categoryName);

            _categoryData = GetCategoryData();

            // Check that the instance name isn't too long if we're using the new shared memory.
            // We allocate InstanceNameSlotSize bytes in the shared memory
            if (_categoryData.UseUniqueSharedMemory)
            {
                if (instanceName != null && instanceName.Length > InstanceNameMaxLength)
                    throw new InvalidOperationException(SR.InstanceNameTooLong);
            }
            else
            {
                if (lifetime != PerformanceCounterInstanceLifetime.Global)
                    throw new InvalidOperationException(SR.ProcessLifetimeNotValidInGlobal);
            }

            if (counterName != null && instanceName != null)
            {
                if (!_categoryData.CounterNames.Contains(counterName))
                    Debug.Fail("Counter " + counterName + " does not exist in category " + catName);
                else
                    _counterEntryPointer = GetCounter(counterName, instanceName, _categoryData.EnableReuse, lifetime);
            }
        }

        private FileMapping FileView
        {
            get
            {
                return _categoryData.FileMapping;
            }
        }

        internal unsafe long Value
        {
            get
            {
                if (_counterEntryPointer == null)
                    return 0;

                return GetValue(_counterEntryPointer);
            }

            set
            {
                if (_counterEntryPointer == null)
                    return;

                SetValue(_counterEntryPointer, value);
            }
        }

        private unsafe int CalculateAndAllocateMemory(int totalSize, out int alignmentAdjustment)
        {
            int newOffset;
            int oldOffset;
            alignmentAdjustment = 0;

            Debug.Assert(!_categoryData.UseUniqueSharedMemory, "We should never be calling CalculateAndAllocateMemory in the unique shared memory");

            do
            {
                oldOffset = *((int*)_baseAddress);
                // we need to verify the oldOffset before we start using it.  Otherwise someone could change
                // it to something bogus and we would write outside of the shared memory.
                ResolveOffset(oldOffset, 0);

                newOffset = CalculateMemory(oldOffset, totalSize, out alignmentAdjustment);

                // In the default shared mem we need to make sure that the end address is also aligned.  This is because
                // in v1.1/v1.0 we just assumed that the next free offset was always properly aligned.
                int endAddressMod8 = (int)(_baseAddress + newOffset) & 0x7;
                int endAlignmentAdjustment = (8 - endAddressMod8) & 0x7;
                newOffset += endAlignmentAdjustment;

            } while (Interlocked.CompareExchange(ref *(int*)_baseAddress, newOffset, oldOffset) != oldOffset);

            return oldOffset;
        }

        private int CalculateMemory(int oldOffset, int totalSize, out int alignmentAdjustment)
        {
            int newOffset = CalculateMemoryNoBoundsCheck(oldOffset, totalSize, out alignmentAdjustment);

            if (newOffset > FileView._fileMappingSize || newOffset < 0)
            {
                throw new InvalidOperationException(SR.CountersOOM);
            }

            return newOffset;
        }

        private int CalculateMemoryNoBoundsCheck(int oldOffset, int totalSize, out int alignmentAdjustment)
        {
            int currentTotalSize = totalSize;

            Thread.MemoryBarrier();

            // make sure the start address is 8 byte aligned
            int startAddressMod8 = (int)(_baseAddress + oldOffset) & 0x7;
            alignmentAdjustment = (8 - startAddressMod8) & 0x7;
            currentTotalSize += alignmentAdjustment;

            int newOffset = oldOffset + currentTotalSize;

            return newOffset;
        }

        private unsafe int CreateCategory(CategoryEntry* lastCategoryPointer,
                                            int instanceNameHashCode, string instanceName,
                                            PerformanceCounterInstanceLifetime lifetime)
        {
            int categoryNameLength;
            int instanceNameLength;
            int alignmentAdjustment;
            int freeMemoryOffset;
            int newOffset = 0;
            int totalSize;

            categoryNameLength = (_categoryName.Length + 1) * 2;
            totalSize = sizeof(CategoryEntry) + sizeof(InstanceEntry) + (sizeof(CounterEntry) * _categoryData.CounterNames.Count) + categoryNameLength;
            for (int i = 0; i < _categoryData.CounterNames.Count; i++)
            {
                totalSize += (((string)_categoryData.CounterNames[i]).Length + 1) * 2;
            }

            if (_categoryData.UseUniqueSharedMemory)
            {
                instanceNameLength = InstanceNameSlotSize;
                totalSize += sizeof(ProcessLifetimeEntry) + instanceNameLength;

                // If we're in a separate shared memory, we need to do a two stage update of the free memory pointer.
                // First we calculate our alignment adjustment and where the new free offset is.  Then we
                // write the new structs and data.  The last two operations are to link the new structs into the
                // existing ones and update the next free offset.  Our process could get killed in between those two,
                // leaving the memory in an inconsistent state.  We use the "IsConsistent" flag to help determine
                // when that has happened.
                freeMemoryOffset = *((int*)_baseAddress);
                newOffset = CalculateMemory(freeMemoryOffset, totalSize, out alignmentAdjustment);

                if (freeMemoryOffset == _initialOffset)
                    lastCategoryPointer->IsConsistent = 0;
            }
            else
            {
                instanceNameLength = (instanceName.Length + 1) * 2;
                totalSize += instanceNameLength;
                freeMemoryOffset = CalculateAndAllocateMemory(totalSize, out alignmentAdjustment);
            }

            byte* nextPtr = (byte*)ResolveOffset(freeMemoryOffset, totalSize + alignmentAdjustment);

            CategoryEntry* newCategoryEntryPointer;
            InstanceEntry* newInstanceEntryPointer;
            // We need to decide where to put the padding returned in alignmentAdjustment.  There are several things that
            // need to be aligned.  First, we need to align each struct on a 4 byte boundary so we can use interlocked
            // operations on the int Spinlock field.  Second, we need to align the CounterEntry on an 8 byte boundary so that
            // on 64 bit platforms we can use interlocked operations on the Value field.  alignmentAdjustment guarantees 8 byte
            // alignment, so we use that for both.  If we're creating the very first category, however, we can't move that
            // CategoryEntry.  In this case we put the alignmentAdjustment before the InstanceEntry.
            if (freeMemoryOffset == _initialOffset)
            {
                newCategoryEntryPointer = (CategoryEntry*)nextPtr;
                nextPtr += sizeof(CategoryEntry) + alignmentAdjustment;
                newInstanceEntryPointer = (InstanceEntry*)nextPtr;
            }
            else
            {
                nextPtr += alignmentAdjustment;
                newCategoryEntryPointer = (CategoryEntry*)nextPtr;
                nextPtr += sizeof(CategoryEntry);
                newInstanceEntryPointer = (InstanceEntry*)nextPtr;
            }
            nextPtr += sizeof(InstanceEntry);

            // create the first CounterEntry and reserve space for all of the rest.  We won't
            // finish creating them until the end
            CounterEntry* newCounterEntryPointer = (CounterEntry*)nextPtr;
            nextPtr += sizeof(CounterEntry) * _categoryData.CounterNames.Count;

            if (_categoryData.UseUniqueSharedMemory)
            {
                ProcessLifetimeEntry* newLifetimeEntry = (ProcessLifetimeEntry*)nextPtr;
                nextPtr += sizeof(ProcessLifetimeEntry);

                newCounterEntryPointer->LifetimeOffset = (int)((byte*)newLifetimeEntry - _baseAddress);
                PopulateLifetimeEntry(newLifetimeEntry, lifetime);
            }

            newCategoryEntryPointer->CategoryNameHashCode = _categoryNameHashCode;
            newCategoryEntryPointer->NextCategoryOffset = 0;
            newCategoryEntryPointer->FirstInstanceOffset = (int)((byte*)newInstanceEntryPointer - _baseAddress);
            newCategoryEntryPointer->CategoryNameOffset = (int)(nextPtr - _baseAddress);
            SafeMarshalCopy(_categoryName, nextPtr);
            nextPtr += categoryNameLength;

            newInstanceEntryPointer->InstanceNameHashCode = instanceNameHashCode;
            newInstanceEntryPointer->NextInstanceOffset = 0;
            newInstanceEntryPointer->FirstCounterOffset = (int)((byte*)newCounterEntryPointer - _baseAddress);
            newInstanceEntryPointer->RefCount = 1;
            newInstanceEntryPointer->InstanceNameOffset = (int)(nextPtr - _baseAddress);
            SafeMarshalCopy(instanceName, nextPtr);
            nextPtr += instanceNameLength;

            string counterName = (string)_categoryData.CounterNames[0];
            newCounterEntryPointer->CounterNameHashCode = GetWstrHashCode(counterName);
            SetValue(newCounterEntryPointer, 0);
            newCounterEntryPointer->CounterNameOffset = (int)(nextPtr - _baseAddress);
            SafeMarshalCopy(counterName, nextPtr);
            nextPtr += (counterName.Length + 1) * 2;

            CounterEntry* previousCounterEntryPointer;
            for (int i = 1; i < _categoryData.CounterNames.Count; i++)
            {
                previousCounterEntryPointer = newCounterEntryPointer;
                counterName = (string)_categoryData.CounterNames[i];

                newCounterEntryPointer++;
                newCounterEntryPointer->CounterNameHashCode = GetWstrHashCode(counterName);
                SetValue(newCounterEntryPointer, 0);
                newCounterEntryPointer->CounterNameOffset = (int)(nextPtr - _baseAddress);
                SafeMarshalCopy(counterName, nextPtr);

                nextPtr += (counterName.Length + 1) * 2;
                previousCounterEntryPointer->NextCounterOffset = (int)((byte*)newCounterEntryPointer - _baseAddress);
            }

            Debug.Assert(nextPtr - _baseAddress == freeMemoryOffset + totalSize + alignmentAdjustment, "We should have used all of the space we requested at this point");

            int offset = (int)((byte*)newCategoryEntryPointer - _baseAddress);
            lastCategoryPointer->IsConsistent = 0;
            // If not the first category node, link it.
            if (offset != _initialOffset)
                lastCategoryPointer->NextCategoryOffset = offset;

            if (_categoryData.UseUniqueSharedMemory)
            {
                *((int*)_baseAddress) = newOffset;
                lastCategoryPointer->IsConsistent = 1;
            }
            return offset;
        }

        private unsafe int CreateInstance(CategoryEntry* categoryPointer,
                                            int instanceNameHashCode, string instanceName,
                                            PerformanceCounterInstanceLifetime lifetime)
        {
            int instanceNameLength;
            int totalSize = sizeof(InstanceEntry) + (sizeof(CounterEntry) * _categoryData.CounterNames.Count);
            int alignmentAdjustment;
            int freeMemoryOffset;
            int newOffset = 0;

            if (_categoryData.UseUniqueSharedMemory)
            {
                instanceNameLength = InstanceNameSlotSize;
                totalSize += sizeof(ProcessLifetimeEntry) + instanceNameLength;

                // If we're in a separate shared memory, we need to do a two stage update of the free memory pointer.
                // First we calculate our alignment adjustment and where the new free offset is.  Then we
                // write the new structs and data.  The last two operations are to link the new structs into the
                // existing ones and update the next free offset.  Our process could get killed in between those two,
                // leaving the memory in an inconsistent state.  We use the "IsConsistent" flag to help determine
                // when that has happened.
                freeMemoryOffset = *((int*)_baseAddress);
                newOffset = CalculateMemory(freeMemoryOffset, totalSize, out alignmentAdjustment);
            }
            else
            {
                instanceNameLength = (instanceName.Length + 1) * 2;
                totalSize += instanceNameLength;

                // add in the counter names for the global shared mem.
                for (int i = 0; i < _categoryData.CounterNames.Count; i++)
                {
                    totalSize += (((string)_categoryData.CounterNames[i]).Length + 1) * 2;
                }
                freeMemoryOffset = CalculateAndAllocateMemory(totalSize, out alignmentAdjustment);
            }

            freeMemoryOffset += alignmentAdjustment;
            byte* nextPtr = (byte*)ResolveOffset(freeMemoryOffset, totalSize);    // don't add alignmentAdjustment since it's already
                                                                                  // been added to freeMemoryOffset

            InstanceEntry* newInstanceEntryPointer = (InstanceEntry*)nextPtr;
            nextPtr += sizeof(InstanceEntry);

            // create the first CounterEntry and reserve space for all of the rest.  We won't
            // finish creating them until the end
            CounterEntry* newCounterEntryPointer = (CounterEntry*)nextPtr;
            nextPtr += sizeof(CounterEntry) * _categoryData.CounterNames.Count;

            if (_categoryData.UseUniqueSharedMemory)
            {
                ProcessLifetimeEntry* newLifetimeEntry = (ProcessLifetimeEntry*)nextPtr;
                nextPtr += sizeof(ProcessLifetimeEntry);

                newCounterEntryPointer->LifetimeOffset = (int)((byte*)newLifetimeEntry - _baseAddress);
                PopulateLifetimeEntry(newLifetimeEntry, lifetime);
            }

            // set up the InstanceEntry
            newInstanceEntryPointer->InstanceNameHashCode = instanceNameHashCode;
            newInstanceEntryPointer->NextInstanceOffset = 0;
            newInstanceEntryPointer->FirstCounterOffset = (int)((byte*)newCounterEntryPointer - _baseAddress);
            newInstanceEntryPointer->RefCount = 1;
            newInstanceEntryPointer->InstanceNameOffset = (int)(nextPtr - _baseAddress);
            SafeMarshalCopy(instanceName, nextPtr);

            nextPtr += instanceNameLength;

            if (_categoryData.UseUniqueSharedMemory)
            {
                // in the unique shared mem we'll assume that the CounterEntries of the first instance
                // are all created.  Then we can just refer to the old counter name rather than copying in a new one.
                InstanceEntry* firstInstanceInCategoryPointer = (InstanceEntry*)ResolveOffset(categoryPointer->FirstInstanceOffset, sizeof(InstanceEntry));
                CounterEntry* firstCounterInCategoryPointer = (CounterEntry*)ResolveOffset(firstInstanceInCategoryPointer->FirstCounterOffset, sizeof(CounterEntry));
                newCounterEntryPointer->CounterNameHashCode = firstCounterInCategoryPointer->CounterNameHashCode;
                SetValue(newCounterEntryPointer, 0);
                newCounterEntryPointer->CounterNameOffset = firstCounterInCategoryPointer->CounterNameOffset;

                // now create the rest of the CounterEntrys
                CounterEntry* previousCounterEntryPointer;
                for (int i = 1; i < _categoryData.CounterNames.Count; i++)
                {
                    previousCounterEntryPointer = newCounterEntryPointer;

                    newCounterEntryPointer++;
                    Debug.Assert(firstCounterInCategoryPointer->NextCounterOffset != 0, "The unique shared memory should have all of its counters created by the time we hit CreateInstance");
                    firstCounterInCategoryPointer = (CounterEntry*)ResolveOffset(firstCounterInCategoryPointer->NextCounterOffset, sizeof(CounterEntry));
                    newCounterEntryPointer->CounterNameHashCode = firstCounterInCategoryPointer->CounterNameHashCode;
                    SetValue(newCounterEntryPointer, 0);
                    newCounterEntryPointer->CounterNameOffset = firstCounterInCategoryPointer->CounterNameOffset;

                    previousCounterEntryPointer->NextCounterOffset = (int)((byte*)newCounterEntryPointer - _baseAddress);
                }
            }
            else
            {
                // now create the rest of the CounterEntrys
                CounterEntry* previousCounterEntryPointer = null;
                for (int i = 0; i < _categoryData.CounterNames.Count; i++)
                {
                    string counterName = (string)_categoryData.CounterNames[i];
                    newCounterEntryPointer->CounterNameHashCode = GetWstrHashCode(counterName);
                    newCounterEntryPointer->CounterNameOffset = (int)(nextPtr - _baseAddress);
                    SafeMarshalCopy(counterName, nextPtr);
                    nextPtr += (counterName.Length + 1) * 2;

                    SetValue(newCounterEntryPointer, 0);

                    if (i != 0)
                        previousCounterEntryPointer->NextCounterOffset = (int)((byte*)newCounterEntryPointer - _baseAddress);

                    previousCounterEntryPointer = newCounterEntryPointer;
                    newCounterEntryPointer++;
                }
            }

            Debug.Assert(nextPtr - _baseAddress == freeMemoryOffset + totalSize, "We should have used all of the space we requested at this point");

            int offset = (int)((byte*)newInstanceEntryPointer - _baseAddress);
            categoryPointer->IsConsistent = 0;

            // prepend the new instance rather than append, helps with perf of hooking up subsequent counters
            newInstanceEntryPointer->NextInstanceOffset = categoryPointer->FirstInstanceOffset;
            categoryPointer->FirstInstanceOffset = offset;

            if (_categoryData.UseUniqueSharedMemory)
            {
                *((int*)_baseAddress) = newOffset;
                categoryPointer->IsConsistent = 1;
            }

            return freeMemoryOffset;
        }

        private unsafe int CreateCounter(CounterEntry* lastCounterPointer,
                                           int counterNameHashCode, string counterName)
        {
            int counterNameLength = (counterName.Length + 1) * 2;
            int totalSize = sizeof(CounterEntry) + counterNameLength;
            int alignmentAdjustment;
            int freeMemoryOffset;

            Debug.Assert(!_categoryData.UseUniqueSharedMemory, "We should never be calling CreateCounter in the unique shared memory");
            freeMemoryOffset = CalculateAndAllocateMemory(totalSize, out alignmentAdjustment);

            freeMemoryOffset += alignmentAdjustment;

            byte* nextPtr = (byte*)ResolveOffset(freeMemoryOffset, totalSize);
            CounterEntry* newCounterEntryPointer = (CounterEntry*)nextPtr;
            nextPtr += sizeof(CounterEntry);

            newCounterEntryPointer->CounterNameOffset = (int)(nextPtr - _baseAddress);
            newCounterEntryPointer->CounterNameHashCode = counterNameHashCode;
            newCounterEntryPointer->NextCounterOffset = 0;
            SetValue(newCounterEntryPointer, 0);
            SafeMarshalCopy(counterName, nextPtr);

            Debug.Assert(nextPtr + counterNameLength - _baseAddress == freeMemoryOffset + totalSize, "We should have used all of the space we requested at this point");

            lastCounterPointer->NextCounterOffset = (int)((byte*)newCounterEntryPointer - _baseAddress);
            return freeMemoryOffset;
        }

        private static unsafe void PopulateLifetimeEntry(ProcessLifetimeEntry* lifetimeEntry, PerformanceCounterInstanceLifetime lifetime)
        {

            if (lifetime == PerformanceCounterInstanceLifetime.Process)
            {

                lifetimeEntry->LifetimeType = (int)PerformanceCounterInstanceLifetime.Process;
                lifetimeEntry->ProcessId = ProcessData.ProcessId;
                lifetimeEntry->StartupTime = ProcessData.StartupTime;
            }
            else
            {
                lifetimeEntry->ProcessId = 0;
                lifetimeEntry->StartupTime = 0;
            }
        }

        private static unsafe void WaitAndEnterCriticalSection(int* spinLockPointer, out bool taken)
        {
            WaitForCriticalSection(spinLockPointer);

            // Note - we are taking a lock here, but it probably isn't
            // worthwhile to use Thread.BeginCriticalRegion & EndCriticalRegion.
            // These only really help the CLR escalate from a thread abort
            // to an appdomain unload, under the assumption that you may be
            // editing shared state within the appdomain.  Here you are editing
            // shared state, but it is shared across processes.  Unloading the
            // appdomain isn't exactly helping.  The only thing that would help
            // would be if the CLR tells the host to ensure all allocations
            // have a higher chance of succeeding within this critical region,
            // but of course that's only a probabilisitic statement.

            // Must be able to assign to the out param.
            try
            {
            }
            finally
            {
                int r = Interlocked.CompareExchange(ref *spinLockPointer, 1, 0);
                taken = (r == 0);
            }
        }

        private static unsafe void WaitForCriticalSection(int* spinLockPointer)
        {
            int spinCount = MaxSpinCount;
            for (; spinCount > 0 && *spinLockPointer != 0; spinCount--)
            {
                // We suspect there are scenarios where the finalizer thread
                // will call this method.  The finalizer thread runs with
                // a higher priority than the other code.  Using SpinWait
                // isn't sufficient, since it only spins, but doesn't yield
                // to any lower-priority threads.  Call Thread.Sleep(1).
                if (*spinLockPointer != 0)
                    Thread.Sleep(1);
            }

            // if the lock still isn't free, most likely there's a deadlock caused by a process
            // getting killed while it held the lock.  We'll just free the lock
            if (spinCount == 0 && *spinLockPointer != 0)
                *spinLockPointer = 0;
        }

        private static unsafe void ExitCriticalSection(int* spinLockPointer)
        {
            *spinLockPointer = 0;
        }

        // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
        // This hashcode function is identical to the one in SharedPerformanceCounter.cpp.  If
        // you change one without changing the other, perfcounters will break.
        internal static int GetWstrHashCode(string wstr)
        {
            uint hash = 5381;
            for (uint i = 0; i < wstr.Length; i++)
                hash = ((hash << 5) + hash) ^ wstr[(int)i];
            return (int)hash;
        }

        // Calculate the length of a string in the shared memory.  If we reach the end of the shared memory
        // before we see a null terminator, we throw.
        private unsafe int GetStringLength(char* startChar)
        {
            char* currentChar = startChar;
            ulong endAddress = (ulong)(_baseAddress + FileView._fileMappingSize);

            while ((ulong)currentChar < (endAddress - 2))
            {
                if (*currentChar == 0)
                    return (int)(currentChar - startChar);

                currentChar++;
            }

            throw new InvalidOperationException(SR.MappingCorrupted);
        }

        // Compare a managed string to a string located at a given offset.  If we walk past the end of the
        // shared memory, we throw.
        private unsafe bool StringEquals(string stringA, int offset)
        {
            char* currentChar = (char*)ResolveOffset(offset, 0);
            ulong endAddress = (ulong)(_baseAddress + FileView._fileMappingSize);

            int i;
            for (i = 0; i < stringA.Length; i++)
            {
                if ((ulong)(currentChar + i) > (endAddress - 2))
                    throw new InvalidOperationException(SR.MappingCorrupted);

                if (stringA[i] != currentChar[i])
                    return false;
            }

            // now check for the null termination.
            if ((ulong)(currentChar + i) > (endAddress - 2))
                throw new InvalidOperationException(SR.MappingCorrupted);

            return (currentChar[i] == 0);
        }

        private unsafe CategoryData GetCategoryData()
        {
            CategoryData data = (CategoryData)s_categoryDataTable[_categoryName];

            if (data == null)
            {
                lock (s_categoryDataTable)
                {
                    data = (CategoryData)s_categoryDataTable[_categoryName];
                    if (data == null)
                    {
                        data = new CategoryData();
                        data.FileMappingName = DefaultFileMappingName;
                        data.MutexName = _categoryName;

                        RegistryKey categoryKey = null;
                        try
                        {
                            categoryKey = Registry.LocalMachine.OpenSubKey(PerformanceCounterLib.ServicePath + "\\" + _categoryName + "\\Performance");

                            // first read the options
                            object optionsObject = categoryKey.GetValue("CategoryOptions");
                            if (optionsObject != null)
                            {
                                int options = (int)optionsObject;
                                data.EnableReuse = (((PerformanceCounterCategoryOptions)options & PerformanceCounterCategoryOptions.EnableReuse) != 0);

                                if (((PerformanceCounterCategoryOptions)options & PerformanceCounterCategoryOptions.UseUniqueSharedMemory) != 0)
                                {
                                    data.UseUniqueSharedMemory = true;
                                    _initialOffset = 8;
                                    data.FileMappingName = DefaultFileMappingName + _categoryName;
                                }
                            }

                            int fileMappingSize;
                            object fileMappingSizeObject = categoryKey.GetValue("FileMappingSize");
                            if (fileMappingSizeObject != null && data.UseUniqueSharedMemory)
                            {
                                // we only use this reg value in the unique shared memory case.
                                fileMappingSize = (int)fileMappingSizeObject;
                                if (fileMappingSize < MinCountersFileMappingSize)
                                    fileMappingSize = MinCountersFileMappingSize;

                                if (fileMappingSize > MaxCountersFileMappingSize)
                                    fileMappingSize = MaxCountersFileMappingSize;
                            }
                            else
                            {
                                fileMappingSize = GetFileMappingSizeFromConfig();
                                if (data.UseUniqueSharedMemory)
                                    fileMappingSize >>= 2;  // if we have a custom filemapping, only make it 25% as large.
                            }

                            // now read the counter names
                            object counterNamesObject = categoryKey.GetValue("Counter Names");
                            byte[] counterNamesBytes = counterNamesObject as byte[];

                            if (counterNamesBytes != null)
                            {
                                ArrayList names = new ArrayList();
                                fixed (byte* counterNamesPtr = counterNamesBytes)
                                {
                                    int start = 0;
                                    for (int i = 0; i < counterNamesBytes.Length - 1; i += 2)
                                    {
                                        if (counterNamesBytes[i] == 0 && counterNamesBytes[i + 1] == 0 && start != i)
                                        {
                                            string counter = new string((sbyte*)counterNamesPtr, start, i - start, Encoding.Unicode);
                                            names.Add(counter.ToLowerInvariant());
                                            start = i + 2;
                                        }
                                    }
                                }
                                data.CounterNames = names;
                            }
                            else
                            {
                                Debug.Assert(counterNamesObject is string[], $"Expected string[], got '{counterNamesObject}' of type '{counterNamesObject?.GetType()}' with kind '{categoryKey.GetValueKind("Counter Names")}' for category '{_categoryName}'");
                                string[] counterNames = (string[])counterNamesObject;
                                for (int i = 0; i < counterNames.Length; i++)
                                    counterNames[i] = counterNames[i].ToLowerInvariant();
                                data.CounterNames = new ArrayList(counterNames);
                            }

                            data.FileMappingName = "Global\\" + data.FileMappingName;
                            data.MutexName = "Global\\" + _categoryName;
                            data.FileMapping = new FileMapping(data.FileMappingName, fileMappingSize, _initialOffset);
                            s_categoryDataTable[_categoryName] = data;
                        }
                        finally
                        {
                            categoryKey?.Close();
                        }
                    }
                }
            }
            _baseAddress = (byte*)data.FileMapping.FileViewAddress;

            if (data.UseUniqueSharedMemory)
                _initialOffset = 8;


            return data;
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private static int GetFileMappingSizeFromConfig()
        {
            return DiagnosticsConfiguration.PerformanceCountersFileMappingSize;
        }

        private static void RemoveCategoryData(string categoryName)
        {
            lock (s_categoryDataTable)
            {
                s_categoryDataTable.Remove(categoryName);
            }
        }

        private unsafe CounterEntry* GetCounter(string counterName, string instanceName, bool enableReuse, PerformanceCounterInstanceLifetime lifetime)
        {
            int counterNameHashCode = GetWstrHashCode(counterName);
            int instanceNameHashCode;
            if (!string.IsNullOrEmpty(instanceName))
                instanceNameHashCode = GetWstrHashCode(instanceName);
            else
            {
                instanceNameHashCode = s_singleInstanceHashCode;
                instanceName = SingleInstanceName;
            }

            Mutex mutex = null;
            CounterEntry* counterPointer = null;
            InstanceEntry* instancePointer = null;
            try
            {
                NetFrameworkUtils.EnterMutexWithoutGlobal(_categoryData.MutexName, ref mutex);
                CategoryEntry* categoryPointer;
                bool counterFound = false;
                while (!FindCategory(&categoryPointer))
                {
                    // don't bother locking again if we're using a separate shared memory.
                    bool sectionEntered;
                    if (_categoryData.UseUniqueSharedMemory)
                        sectionEntered = true;
                    else
                        WaitAndEnterCriticalSection(&(categoryPointer->SpinLock), out sectionEntered);

                    int newCategoryOffset;
                    if (sectionEntered)
                    {
                        try
                        {
                            newCategoryOffset = CreateCategory(categoryPointer, instanceNameHashCode, instanceName, lifetime);
                        }
                        finally
                        {
                            if (!_categoryData.UseUniqueSharedMemory)
                                ExitCriticalSection(&(categoryPointer->SpinLock));
                        }

                        categoryPointer = (CategoryEntry*)(ResolveOffset(newCategoryOffset, sizeof(CategoryEntry)));
                        instancePointer = (InstanceEntry*)(ResolveOffset(categoryPointer->FirstInstanceOffset, sizeof(InstanceEntry)));
                        counterFound = FindCounter(counterNameHashCode, counterName, instancePointer, &counterPointer);
                        Debug.Assert(counterFound, "All counters should be created, so we should always find the counter");
                        return counterPointer;
                    }
                }

                bool foundFreeInstance;
                while (!FindInstance(instanceNameHashCode, instanceName, categoryPointer, &instancePointer, true, lifetime, out foundFreeInstance))
                {
                    InstanceEntry* lockInstancePointer = instancePointer;

                    // don't bother locking again if we're using a separate shared memory.
                    bool sectionEntered;
                    if (_categoryData.UseUniqueSharedMemory)
                        sectionEntered = true;
                    else
                        WaitAndEnterCriticalSection(&(lockInstancePointer->SpinLock), out sectionEntered);

                    if (sectionEntered)
                    {
                        try
                        {
                            bool reused = false;

                            if (enableReuse && foundFreeInstance)
                            {
                                reused = TryReuseInstance(instanceNameHashCode, instanceName, categoryPointer, &instancePointer, lifetime, lockInstancePointer);
                                // at this point we might have reused an instance that came from v1.1/v1.0.  We can't assume it will have the counter
                                // we're looking for.
                            }

                            if (!reused)
                            {
                                int newInstanceOffset = CreateInstance(categoryPointer, instanceNameHashCode, instanceName, lifetime);
                                instancePointer = (InstanceEntry*)(ResolveOffset(newInstanceOffset, sizeof(InstanceEntry)));

                                counterFound = FindCounter(counterNameHashCode, counterName, instancePointer, &counterPointer);
                                Debug.Assert(counterFound, "All counters should be created, so we should always find the counter");
                                return counterPointer;
                            }
                        }
                        finally
                        {
                            if (!_categoryData.UseUniqueSharedMemory)
                                ExitCriticalSection(&(lockInstancePointer->SpinLock));
                        }
                    }
                }

                if (_categoryData.UseUniqueSharedMemory)
                {
                    counterFound = FindCounter(counterNameHashCode, counterName, instancePointer, &counterPointer);
                    Debug.Assert(counterFound, "All counters should be created, so we should always find the counter");
                    return counterPointer;
                }
                else
                {
                    while (!FindCounter(counterNameHashCode, counterName, instancePointer, &counterPointer))
                    {
                        bool sectionEntered;
                        WaitAndEnterCriticalSection(&(counterPointer->SpinLock), out sectionEntered);

                        if (sectionEntered)
                        {
                            try
                            {
                                int newCounterOffset = CreateCounter(counterPointer, counterNameHashCode, counterName);
                                return (CounterEntry*)(ResolveOffset(newCounterOffset, sizeof(CounterEntry)));
                            }
                            finally
                            {
                                ExitCriticalSection(&(counterPointer->SpinLock));
                            }
                        }
                    }

                    return counterPointer;
                }
            }
            finally
            {
                // cache this instance for reuse
                try
                {
                    if (counterPointer != null && instancePointer != null)
                    {
                        _thisInstanceOffset = ResolveAddress((byte*)instancePointer, sizeof(InstanceEntry));
                    }
                }
                catch (InvalidOperationException)
                {
                    _thisInstanceOffset = -1;
                }

                if (mutex != null)
                {
                    mutex.ReleaseMutex();
                    mutex.Close();
                }
            }
        }

        //
        // FindCategory -
        //
        // * when the function returns true the returnCategoryPointerReference is set to the CategoryEntry
        //   that matches 'categoryNameHashCode' and 'categoryName'
        //
        // * when the function returns false the returnCategoryPointerReference is set to the last CategoryEntry
        //   in the linked list
        //
        private unsafe bool FindCategory(CategoryEntry** returnCategoryPointerReference)
        {
            CategoryEntry* firstCategoryPointer = (CategoryEntry*)(ResolveOffset(_initialOffset, sizeof(CategoryEntry)));
            CategoryEntry* currentCategoryPointer = firstCategoryPointer;
            CategoryEntry* previousCategoryPointer = firstCategoryPointer;

            while (true)
            {
                if (currentCategoryPointer->IsConsistent == 0)
                    Verify(currentCategoryPointer);

                if (currentCategoryPointer->CategoryNameHashCode == _categoryNameHashCode)
                {
                    if (StringEquals(_categoryName, currentCategoryPointer->CategoryNameOffset))
                    {
                        *returnCategoryPointerReference = currentCategoryPointer;
                        return true;
                    }
                }

                previousCategoryPointer = currentCategoryPointer;
                if (currentCategoryPointer->NextCategoryOffset != 0)
                    currentCategoryPointer = (CategoryEntry*)(ResolveOffset(currentCategoryPointer->NextCategoryOffset, sizeof(CategoryEntry)));
                else
                {
                    *returnCategoryPointerReference = previousCategoryPointer;
                    return false;
                }
            }
        }

        private unsafe bool FindCounter(int counterNameHashCode, string counterName, InstanceEntry* instancePointer, CounterEntry** returnCounterPointerReference)
        {
            CounterEntry* currentCounterPointer = (CounterEntry*)(ResolveOffset(instancePointer->FirstCounterOffset, sizeof(CounterEntry)));
            CounterEntry* previousCounterPointer = currentCounterPointer;
            while (true)
            {
                if (currentCounterPointer->CounterNameHashCode == counterNameHashCode)
                {
                    if (StringEquals(counterName, currentCounterPointer->CounterNameOffset))
                    {
                        *returnCounterPointerReference = currentCounterPointer;
                        return true;
                    }
                }

                previousCounterPointer = currentCounterPointer;
                if (currentCounterPointer->NextCounterOffset != 0)
                    currentCounterPointer = (CounterEntry*)(ResolveOffset(currentCounterPointer->NextCounterOffset, sizeof(CounterEntry)));
                else
                {
                    *returnCounterPointerReference = previousCounterPointer;
                    return false;
                }
            }
        }

        private unsafe bool FindInstance(int instanceNameHashCode, string instanceName,
                                           CategoryEntry* categoryPointer, InstanceEntry** returnInstancePointerReference,
                                           bool activateUnusedInstances, PerformanceCounterInstanceLifetime lifetime,
                                           out bool foundFreeInstance)
        {

            InstanceEntry* currentInstancePointer = (InstanceEntry*)(ResolveOffset(categoryPointer->FirstInstanceOffset, sizeof(InstanceEntry)));
            InstanceEntry* previousInstancePointer = currentInstancePointer;
            foundFreeInstance = false;
            // Look at the first instance to determine if this is single or multi instance.
            if (currentInstancePointer->InstanceNameHashCode == s_singleInstanceHashCode)
            {
                if (StringEquals(SingleInstanceName, currentInstancePointer->InstanceNameOffset))
                {
                    if (instanceName != SingleInstanceName)
                        throw new InvalidOperationException(SR.Format(SR.SingleInstanceOnly, _categoryName));
                }
                else
                {
                    if (instanceName == SingleInstanceName)
                        throw new InvalidOperationException(SR.Format(SR.MultiInstanceOnly, _categoryName));
                }
            }
            else
            {
                if (instanceName == SingleInstanceName)
                    throw new InvalidOperationException(SR.Format(SR.MultiInstanceOnly, _categoryName));
            }

            //
            // 1st pass find exact matching!
            //
            // We don't need to aggressively claim unused instances. For performance, we would proactively
            // verify lifetime of instances if activateUnusedInstances is specified and certain time
            // has elapsed since last sweep or we are running out of shared memory.
            bool verifyLifeTime = activateUnusedInstances;
            if (activateUnusedInstances)
            {

                int totalSize = sizeof(InstanceEntry) + sizeof(ProcessLifetimeEntry) + InstanceNameSlotSize + (sizeof(CounterEntry) * _categoryData.CounterNames.Count);
                int freeMemoryOffset = *((int*)_baseAddress);
                int alignmentAdjustment;
                int newOffset = CalculateMemoryNoBoundsCheck(freeMemoryOffset, totalSize, out alignmentAdjustment);

                if (!(newOffset > FileView._fileMappingSize || newOffset < 0))
                {
                    long tickDelta = (DateTime.Now.Ticks - Volatile.Read(ref s_lastInstanceLifetimeSweepTick));
                    if (tickDelta < InstanceLifetimeSweepWindow)
                        verifyLifeTime = false;
                }
            }

            try
            {
                while (true)
                {
                    bool verifiedLifetimeOfThisInstance = false;
                    if (verifyLifeTime && (currentInstancePointer->RefCount != 0))
                    {
                        verifiedLifetimeOfThisInstance = true;
                        VerifyLifetime(currentInstancePointer);
                    }

                    if (currentInstancePointer->InstanceNameHashCode == instanceNameHashCode)
                    {
                        if (StringEquals(instanceName, currentInstancePointer->InstanceNameOffset))
                        {
                            // we found a matching instance.
                            *returnInstancePointerReference = currentInstancePointer;

                            CounterEntry* firstCounter = (CounterEntry*)ResolveOffset(currentInstancePointer->FirstCounterOffset, sizeof(CounterEntry));
                            ProcessLifetimeEntry* lifetimeEntry;
                            if (_categoryData.UseUniqueSharedMemory)
                                lifetimeEntry = (ProcessLifetimeEntry*)ResolveOffset(firstCounter->LifetimeOffset, sizeof(ProcessLifetimeEntry));
                            else
                                lifetimeEntry = null;

                            // ensure that we have verified the lifetime of the matched instance
                            if (!verifiedLifetimeOfThisInstance && currentInstancePointer->RefCount != 0)
                                VerifyLifetime(currentInstancePointer);

                            if (currentInstancePointer->RefCount != 0)
                            {
                                if (lifetimeEntry != null && lifetimeEntry->ProcessId != 0)
                                {
                                    if (lifetime != PerformanceCounterInstanceLifetime.Process)
                                        throw new InvalidOperationException(SR.CantConvertProcessToGlobal);

                                    // make sure only one process is using this instance.
                                    if (ProcessData.ProcessId != lifetimeEntry->ProcessId)
                                        throw new InvalidOperationException(SR.Format(SR.InstanceAlreadyExists, instanceName));

                                    // compare start time of the process, account for ACL issues in querying process information
                                    if ((lifetimeEntry->StartupTime != -1) && (ProcessData.StartupTime != -1))
                                    {
                                        if (ProcessData.StartupTime != lifetimeEntry->StartupTime)
                                            throw new InvalidOperationException(SR.Format(SR.InstanceAlreadyExists, instanceName));
                                    }
                                }
                                else
                                {
                                    if (lifetime == PerformanceCounterInstanceLifetime.Process)
                                        throw new InvalidOperationException(SR.CantConvertGlobalToProcess);
                                }
                                return true;
                            }

                            if (activateUnusedInstances)
                            {
                                Mutex mutex = null;
                                try
                                {
                                    NetFrameworkUtils.EnterMutexWithoutGlobal(_categoryData.MutexName, ref mutex);
                                    ClearCounterValues(currentInstancePointer);
                                    if (lifetimeEntry != null)
                                        PopulateLifetimeEntry(lifetimeEntry, lifetime);

                                    currentInstancePointer->RefCount = 1;
                                    return true;
                                }
                                finally
                                {
                                    if (mutex != null)
                                    {
                                        mutex.ReleaseMutex();
                                        mutex.Close();
                                    }
                                }
                            }
                            else
                                return false;
                        }
                    }

                    if (currentInstancePointer->RefCount == 0)
                    {
                        foundFreeInstance = true;
                    }

                    previousInstancePointer = currentInstancePointer;
                    if (currentInstancePointer->NextInstanceOffset != 0)
                        currentInstancePointer = (InstanceEntry*)(ResolveOffset(currentInstancePointer->NextInstanceOffset, sizeof(InstanceEntry)));
                    else
                    {
                        *returnInstancePointerReference = previousInstancePointer;
                        return false;
                    }
                }
            }
            finally
            {
                if (verifyLifeTime)
                    Volatile.Write(ref s_lastInstanceLifetimeSweepTick, DateTime.Now.Ticks);
            }
        }

        private unsafe bool TryReuseInstance(int instanceNameHashCode, string instanceName,
                                               CategoryEntry* categoryPointer, InstanceEntry** returnInstancePointerReference,
                                               PerformanceCounterInstanceLifetime lifetime,
                                               InstanceEntry* lockInstancePointer)
        {
            // 2nd pass find a free instance slot
            InstanceEntry* currentInstancePointer = (InstanceEntry*)(ResolveOffset(categoryPointer->FirstInstanceOffset, sizeof(InstanceEntry)));
            InstanceEntry* previousInstancePointer = currentInstancePointer;
            while (true)
            {
                if (currentInstancePointer->RefCount == 0)
                {

                    bool hasFit;
                    char* instanceNamePtr;       // we need cache this to avoid race conditions.

                    if (_categoryData.UseUniqueSharedMemory)
                    {
                        instanceNamePtr = (char*)ResolveOffset(currentInstancePointer->InstanceNameOffset, InstanceNameSlotSize);
                        // In the separate shared memory case we should always have enough space for instances.  The
                        // name slot size is fixed.
                        Debug.Assert(((instanceName.Length + 1) * 2) <= InstanceNameSlotSize, "The instance name length should always fit in our slot size");
                        hasFit = true;
                    }
                    else
                    {
                        // we don't know the string length yet.
                        instanceNamePtr = (char*)ResolveOffset(currentInstancePointer->InstanceNameOffset, 0);

                        // In the global shared memory, we require names to be exactly the same length in order
                        // to reuse them.  This way we don't end up leaking any space and we don't need to
                        // depend on the layout of the memory to calculate the space we have.
                        int length = GetStringLength(instanceNamePtr);
                        hasFit = (length == instanceName.Length);
                    }

                    bool noSpinLock = (lockInstancePointer == currentInstancePointer) || _categoryData.UseUniqueSharedMemory;
                    // Instance name fit
                    if (hasFit)
                    {
                        // don't bother locking again if we're using a separate shared memory.
                        bool sectionEntered;
                        if (noSpinLock)
                            sectionEntered = true;
                        else
                            WaitAndEnterCriticalSection(&(currentInstancePointer->SpinLock), out sectionEntered);

                        if (sectionEntered)
                        {
                            try
                            {
                                // Make copy with zero-term
                                SafeMarshalCopy(instanceName, instanceNamePtr);
                                currentInstancePointer->InstanceNameHashCode = instanceNameHashCode;

                                // return
                                *returnInstancePointerReference = currentInstancePointer;
                                // clear the counter values.
                                ClearCounterValues(*returnInstancePointerReference);

                                if (_categoryData.UseUniqueSharedMemory)
                                {
                                    CounterEntry* counterPointer = (CounterEntry*)ResolveOffset(currentInstancePointer->FirstCounterOffset, sizeof(CounterEntry));
                                    ProcessLifetimeEntry* lifetimeEntry = (ProcessLifetimeEntry*)ResolveOffset(counterPointer->LifetimeOffset, sizeof(ProcessLifetimeEntry));
                                    PopulateLifetimeEntry(lifetimeEntry, lifetime);
                                }

                                (*returnInstancePointerReference)->RefCount = 1;
                                return true;
                            }
                            finally
                            {
                                if (!noSpinLock)
                                    ExitCriticalSection(&(currentInstancePointer->SpinLock));
                            }
                        }
                    }
                }

                previousInstancePointer = currentInstancePointer;
                if (currentInstancePointer->NextInstanceOffset != 0)
                    currentInstancePointer = (InstanceEntry*)(ResolveOffset(currentInstancePointer->NextInstanceOffset, sizeof(InstanceEntry)));
                else
                {
                    *returnInstancePointerReference = previousInstancePointer;
                    return false;
                }
            }
        }

        private unsafe void Verify(CategoryEntry* currentCategoryPointer)
        {
            if (!_categoryData.UseUniqueSharedMemory)
                return;

            Mutex mutex = null;
            try
            {
                NetFrameworkUtils.EnterMutexWithoutGlobal(_categoryData.MutexName, ref mutex);
                VerifyCategory(currentCategoryPointer);
            }
            finally
            {
                if (mutex != null)
                {
                    mutex.ReleaseMutex();
                    mutex.Close();
                }
            }
        }

        private unsafe void VerifyCategory(CategoryEntry* currentCategoryPointer)
        {
            int freeOffset = *((int*)_baseAddress);
            ResolveOffset(freeOffset, 0);        // verify next free offset

            // begin by verifying the head node's offset
            int currentOffset = ResolveAddress((byte*)currentCategoryPointer, sizeof(CategoryEntry));
            if (currentOffset >= freeOffset)
            {
                // zero out the bad head node entry
                currentCategoryPointer->SpinLock = 0;
                currentCategoryPointer->CategoryNameHashCode = 0;
                currentCategoryPointer->CategoryNameOffset = 0;
                currentCategoryPointer->FirstInstanceOffset = 0;
                currentCategoryPointer->NextCategoryOffset = 0;
                currentCategoryPointer->IsConsistent = 0;
                return;
            }

            if (currentCategoryPointer->NextCategoryOffset > freeOffset)
                currentCategoryPointer->NextCategoryOffset = 0;
            else if (currentCategoryPointer->NextCategoryOffset != 0)
                VerifyCategory((CategoryEntry*)ResolveOffset(currentCategoryPointer->NextCategoryOffset, sizeof(CategoryEntry)));

            if (currentCategoryPointer->FirstInstanceOffset != 0)
            {
                // Check whether the recently added instance at the head of the list is committed. If not, rewire
                // the head of the list to point to the next instance
                if (currentCategoryPointer->FirstInstanceOffset > freeOffset)
                {
                    InstanceEntry* currentInstancePointer = (InstanceEntry*)ResolveOffset(currentCategoryPointer->FirstInstanceOffset, sizeof(InstanceEntry));
                    currentCategoryPointer->FirstInstanceOffset = currentInstancePointer->NextInstanceOffset;
                    if (currentCategoryPointer->FirstInstanceOffset > freeOffset)
                        currentCategoryPointer->FirstInstanceOffset = 0;
                }

                if (currentCategoryPointer->FirstInstanceOffset != 0)
                {
                    Debug.Assert(currentCategoryPointer->FirstInstanceOffset <= freeOffset, "The head of the list is inconsistent - possible mismatch of V2 & V3 instances?");
                    VerifyInstance((InstanceEntry*)ResolveOffset(currentCategoryPointer->FirstInstanceOffset, sizeof(InstanceEntry)));
                }
            }

            currentCategoryPointer->IsConsistent = 1;
        }

        private unsafe void VerifyInstance(InstanceEntry* currentInstancePointer)
        {
            int freeOffset = *((int*)_baseAddress);
            ResolveOffset(freeOffset, 0);        // verify next free offset

            if (currentInstancePointer->NextInstanceOffset > freeOffset)
                currentInstancePointer->NextInstanceOffset = 0;
            else if (currentInstancePointer->NextInstanceOffset != 0)
                VerifyInstance((InstanceEntry*)ResolveOffset(currentInstancePointer->NextInstanceOffset, sizeof(InstanceEntry)));
        }

        private unsafe void VerifyLifetime(InstanceEntry* currentInstancePointer)
        {
            Debug.Assert(currentInstancePointer->RefCount != 0, "RefCount must be 1 for instances passed to VerifyLifetime");

            CounterEntry* counter = (CounterEntry*)ResolveOffset(currentInstancePointer->FirstCounterOffset, sizeof(CounterEntry));
            if (counter->LifetimeOffset != 0)
            {
                ProcessLifetimeEntry* lifetime = (ProcessLifetimeEntry*)ResolveOffset(counter->LifetimeOffset, sizeof(ProcessLifetimeEntry));
                if (lifetime->LifetimeType == (int)PerformanceCounterInstanceLifetime.Process)
                {
                    int pid = lifetime->ProcessId;
                    long startTime = lifetime->StartupTime;

                    if (pid != 0)
                    {

                        // Optimize for this process
                        if (pid == ProcessData.ProcessId)
                        {
                            if ((ProcessData.StartupTime != -1) && (startTime != -1) && (ProcessData.StartupTime != startTime))
                            {
                                // Process id got recycled.  Reclaim this instance.
                                currentInstancePointer->RefCount = 0;
                                return;
                            }
                        }
                        else
                        {
                            long processStartTime;
                            using (SafeProcessHandle procHandle = Interop.Kernel32.OpenProcess(Interop.Advapi32.ProcessOptions.PROCESS_QUERY_INFORMATION, false, pid))
                            {
                                int error = Marshal.GetLastWin32Error();
                                if ((error == Interop.Errors.ERROR_INVALID_PARAMETER) && procHandle.IsInvalid)
                                {
                                    // The process is dead.  Reclaim this instance.  Note that we only clear the refcount here.
                                    // If we tried to clear the pid and startup time as well, we would have a race where
                                    // we could clear the pid/startup time but not the refcount.
                                    currentInstancePointer->RefCount = 0;
                                    return;
                                }

                                // Defer cleaning the instance when we had previously encountered errors in
                                // recording process start time (i.e, when startTime == -1) until after the
                                // process id is not valid (which will be caught in the if check above)
                                if (!procHandle.IsInvalid && startTime != -1)
                                {
                                    long temp;
                                    if (Interop.Kernel32.GetProcessTimes(procHandle, out processStartTime, out temp, out temp, out temp))
                                    {
                                        if (processStartTime != startTime)
                                        {
                                            // The process is dead but a new one is using the same pid.  Reclaim this instance.
                                            currentInstancePointer->RefCount = 0;
                                            return;
                                        }
                                    }
                                }
                            }

                            // Check to see if the process handle has been signaled by the kernel.  If this is the case then it's safe
                            // to reclaim the instance as the process is in the process of exiting.
                            using (SafeProcessHandle procHandle = Interop.Kernel32.OpenProcess(Interop.Advapi32.ProcessOptions.SYNCHRONIZE, false, pid))
                            {
                                if (!procHandle.IsInvalid)
                                {
                                    using (Interop.Kernel32.ProcessWaitHandle wh = new Interop.Kernel32.ProcessWaitHandle(procHandle))
                                    {
                                        if (wh.WaitOne(0, false))
                                        {
                                            // Process has exited
                                            currentInstancePointer->RefCount = 0;
                                            return;
                                        }
                                    }
                                }
                            }
                        }
                    }

                }
            }
        }

        internal unsafe long IncrementBy(long value)
        {
            if (_counterEntryPointer == null)
                return 0;

            CounterEntry* counterEntry = _counterEntryPointer;

            return AddToValue(counterEntry, value);
        }

        internal unsafe long Increment()
        {
            if (_counterEntryPointer == null)
                return 0;

            return IncrementUnaligned(_counterEntryPointer);
        }

        internal unsafe long Decrement()
        {
            if (_counterEntryPointer == null)
                return 0;

            return DecrementUnaligned(_counterEntryPointer);
        }

        internal static unsafe void RemoveAllInstances(string categoryName)
        {
            SharedPerformanceCounter spc = new SharedPerformanceCounter(categoryName, null, null);
            spc.RemoveAllInstances();
            RemoveCategoryData(categoryName);
        }

        private unsafe void RemoveAllInstances()
        {
            CategoryEntry* categoryPointer;
            if (!FindCategory(&categoryPointer))
                return;

            InstanceEntry* instancePointer = (InstanceEntry*)(ResolveOffset(categoryPointer->FirstInstanceOffset, sizeof(InstanceEntry)));

            Mutex mutex = null;
            try
            {
                NetFrameworkUtils.EnterMutexWithoutGlobal(_categoryData.MutexName, ref mutex);
                while (true)
                {
                    RemoveOneInstance(instancePointer, true);

                    if (instancePointer->NextInstanceOffset != 0)
                        instancePointer = (InstanceEntry*)(ResolveOffset(instancePointer->NextInstanceOffset, sizeof(InstanceEntry)));
                    else
                    {
                        break;
                    }
                }
            }
            finally
            {
                if (mutex != null)
                {
                    mutex.ReleaseMutex();
                    mutex.Close();
                }
            }
        }

        internal unsafe void RemoveInstance(string instanceName, PerformanceCounterInstanceLifetime instanceLifetime)
        {
            if (string.IsNullOrEmpty(instanceName))
                return;

            int instanceNameHashCode = GetWstrHashCode(instanceName);

            CategoryEntry* categoryPointer;
            if (!FindCategory(&categoryPointer))
                return;

            InstanceEntry* instancePointer = null;
            bool validatedCachedInstancePointer = false;
            bool temp;

            Mutex mutex = null;
            try
            {
                NetFrameworkUtils.EnterMutexWithoutGlobal(_categoryData.MutexName, ref mutex);

                if (_thisInstanceOffset != -1)
                {
                    try
                    {
                        // validate whether the cached instance pointer is pointing at the right instance
                        instancePointer = (InstanceEntry*)(ResolveOffset(_thisInstanceOffset, sizeof(InstanceEntry)));
                        if (instancePointer->InstanceNameHashCode == instanceNameHashCode)
                        {
                            if (StringEquals(instanceName, instancePointer->InstanceNameOffset))
                            {
                                validatedCachedInstancePointer = true;

                                CounterEntry* firstCounter = (CounterEntry*)ResolveOffset(instancePointer->FirstCounterOffset, sizeof(CounterEntry));
                                ProcessLifetimeEntry* lifetimeEntry;
                                if (_categoryData.UseUniqueSharedMemory)
                                {
                                    lifetimeEntry = (ProcessLifetimeEntry*)ResolveOffset(firstCounter->LifetimeOffset, sizeof(ProcessLifetimeEntry));
                                    if (lifetimeEntry != null
                                        && lifetimeEntry->LifetimeType == (int)PerformanceCounterInstanceLifetime.Process
                                        && lifetimeEntry->ProcessId != 0)
                                    {
                                        validatedCachedInstancePointer &= (instanceLifetime == PerformanceCounterInstanceLifetime.Process);
                                        validatedCachedInstancePointer &= (ProcessData.ProcessId == lifetimeEntry->ProcessId);
                                        if ((lifetimeEntry->StartupTime != -1) && (ProcessData.StartupTime != -1))
                                            validatedCachedInstancePointer &= (ProcessData.StartupTime == lifetimeEntry->StartupTime);
                                    }
                                    else
                                        validatedCachedInstancePointer &= (instanceLifetime != PerformanceCounterInstanceLifetime.Process);
                                }
                            }
                        }
                    }
                    catch (InvalidOperationException)
                    {
                        validatedCachedInstancePointer = false;
                    }
                    if (!validatedCachedInstancePointer)
                        _thisInstanceOffset = -1;
                }

                if (!validatedCachedInstancePointer && !FindInstance(instanceNameHashCode, instanceName, categoryPointer, &instancePointer, false, instanceLifetime, out temp))
                    return;

                if (instancePointer != null)
                    RemoveOneInstance(instancePointer, false);
            }
            finally
            {
                if (mutex != null)
                {
                    mutex.ReleaseMutex();
                    mutex.Close();
                }
            }
        }

        private unsafe void RemoveOneInstance(InstanceEntry* instancePointer, bool clearValue)
        {
            bool sectionEntered = false;

            try
            {
                if (!_categoryData.UseUniqueSharedMemory)
                {
                    while (!sectionEntered)
                    {
                        WaitAndEnterCriticalSection(&(instancePointer->SpinLock), out sectionEntered);
                    }
                }

                instancePointer->RefCount = 0;

                if (clearValue)
                    ClearCounterValues(instancePointer);
            }
            finally
            {
                if (sectionEntered)
                    ExitCriticalSection(&(instancePointer->SpinLock));
            }
        }

        private unsafe void ClearCounterValues(InstanceEntry* instancePointer)
        {
            //Clear counter instance values
            CounterEntry* currentCounterPointer = null;

            if (instancePointer->FirstCounterOffset != 0)
                currentCounterPointer = (CounterEntry*)(ResolveOffset(instancePointer->FirstCounterOffset, sizeof(CounterEntry)));

            while (currentCounterPointer != null)
            {
                SetValue(currentCounterPointer, 0);

                if (currentCounterPointer->NextCounterOffset != 0)
                    currentCounterPointer = (CounterEntry*)(ResolveOffset(currentCounterPointer->NextCounterOffset, sizeof(CounterEntry)));
                else
                    currentCounterPointer = null;
            }

        }

        private static unsafe long AddToValue(CounterEntry* counterEntry, long addend)
        {
            // Called while holding a lock - shouldn't have to worry about
            // reading misaligned data & getting old vs. new parts of an Int64.
            if (IsMisaligned(counterEntry))
            {
                ulong newvalue;

                CounterEntryMisaligned* entry = (CounterEntryMisaligned*)counterEntry;
                newvalue = (uint)entry->Value_hi;
                newvalue <<= 32;
                newvalue |= (uint)entry->Value_lo;


                newvalue = (ulong)((long)newvalue + addend);

                entry->Value_hi = (int)(newvalue >> 32);
                entry->Value_lo = (int)(newvalue & 0xffffffff);

                return (long)newvalue;
            }
            else
                return Interlocked.Add(ref counterEntry->Value, addend);
        }

        private static unsafe long DecrementUnaligned(CounterEntry* counterEntry)
        {
            if (IsMisaligned(counterEntry))
                return AddToValue(counterEntry, -1);
            else
                return Interlocked.Decrement(ref counterEntry->Value);
        }

        private static unsafe long GetValue(CounterEntry* counterEntry)
        {
            if (IsMisaligned(counterEntry))
            {
                ulong value;
                CounterEntryMisaligned* entry = (CounterEntryMisaligned*)counterEntry;
                value = (uint)entry->Value_hi;
                value <<= 32;
                value |= (uint)entry->Value_lo;

                return (long)value;
            }
            else
                return counterEntry->Value;
        }

        private static unsafe long IncrementUnaligned(CounterEntry* counterEntry)
        {
            if (IsMisaligned(counterEntry))
                return AddToValue(counterEntry, 1);
            else
                return Interlocked.Increment(ref counterEntry->Value);
        }

        private static unsafe void SetValue(CounterEntry* counterEntry, long value)
        {
            if (IsMisaligned(counterEntry))
            {
                CounterEntryMisaligned* entry = (CounterEntryMisaligned*)counterEntry;
                entry->Value_lo = (int)(value & 0xffffffff);
                entry->Value_hi = (int)(value >> 32);
            }
            else
                counterEntry->Value = value;
        }

        private static unsafe bool IsMisaligned(CounterEntry* counterEntry)
        {
            return (((long)counterEntry & 0x7) != 0);
        }

        private unsafe void* ResolveOffset(int offset, int sizeToRead)
        {
            //It is very important to check the integrity of the shared memory
            //everytime a new address is resolved.
            if (offset > (FileView._fileMappingSize - sizeToRead) || offset < 0)
                throw new InvalidOperationException(SR.MappingCorrupted);

            void* address = (void*)(_baseAddress + offset);

            return address;
        }

        private int ResolveAddress(byte* address, int sizeToRead)
        {
            int offset = (int)(address - _baseAddress);

            //It is very important to check the integrity of the shared memory
            //everytime a new address is resolved.
            if (offset > (FileView._fileMappingSize - sizeToRead) || offset < 0)
                throw new InvalidOperationException(SR.MappingCorrupted);

            return offset;
        }

        private sealed class FileMapping
        {
            internal int _fileMappingSize;
            private SafeMemoryMappedViewHandle _fileViewAddress;
            private SafeMemoryMappedFileHandle _fileMappingHandle;
            //The version of the file mapping name is independent from the
            //assembly version.

            public FileMapping(string fileMappingName, int fileMappingSize, int initialOffset)
            {
                Initialize(fileMappingName, fileMappingSize, initialOffset);
            }

            internal IntPtr FileViewAddress
            {
                get
                {
                    if (_fileViewAddress.IsInvalid)
                        throw new InvalidOperationException(SR.SharedMemoryGhosted);

                    return _fileViewAddress.DangerousGetHandle();
                }
            }

            private unsafe void Initialize(string fileMappingName, int fileMappingSize, int initialOffset)
            {
                string mappingName = fileMappingName;

                void* pSecurityDescriptor = null;
                try
                {
                    // The sddl string consists of these parts:
                    // D:           it's a DACL
                    // (A;          this is an allow ACE
                    // OICI;        object inherit and container inherit
                    // FRFWGRGW;;;  allow file read, file write, generic read and generic write
                    // AU)          granted to Authenticated Users
                    // ;S-1-5-33)   the same permission granted to AU is also granted to restricted services
                    string sddlString = "D:(A;OICI;FRFWGRGW;;;AU)(A;OICI;FRFWGRGW;;;S-1-5-33)";

                    if (!Interop.Advapi32.ConvertStringSecurityDescriptorToSecurityDescriptor(
                        sddlString,
                        Interop.Kernel32.PerformanceCounterOptions.SDDL_REVISION_1,
                        out pSecurityDescriptor,
                        null))
                    {
                        throw new InvalidOperationException(SR.SetSecurityDescriptorFailed);
                    }

                    Interop.Kernel32.SECURITY_ATTRIBUTES securityAttributes = default;
                    securityAttributes.lpSecurityDescriptor = pSecurityDescriptor;
                    securityAttributes.bInheritHandle = Interop.BOOL.FALSE;

                    //
                    //
                    // Here we call CreateFileMapping to create the memory mapped file.  When CreateFileMapping fails
                    // with ERROR_ACCESS_DENIED, we know the file mapping has been created and we then open it with OpenFileMapping.
                    //
                    // There is chance of a race condition between CreateFileMapping and OpenFileMapping; The memory mapped file
                    // may actually be closed in between these two calls.  When this happens, OpenFileMapping returns ERROR_FILE_NOT_FOUND.
                    // In this case, we need to loop back and retry creating the memory mapped file.
                    //
                    // This loop will timeout in approximately 1.4 minutes.  An InvalidOperationException is thrown in the timeout case.
                    //
                    //
                    int waitRetries = 14;   //((2^13)-1)*10ms == approximately 1.4mins
                    int waitSleep = 0;
                    bool created = false;
                    while (!created && waitRetries > 0)
                    {
                        _fileMappingHandle = Interop.Kernel32.CreateFileMapping((IntPtr)(-1), ref securityAttributes,
                                                                  Interop.Kernel32.PageOptions.PAGE_READWRITE, 0, fileMappingSize, mappingName);

                        if ((Marshal.GetLastWin32Error() != Interop.Errors.ERROR_ACCESS_DENIED) || !_fileMappingHandle.IsInvalid)
                        {
                            created = true;
                        }
                        else
                        {
                            // Invalidate the old safehandle before we get rid of it.  This prevents it from trying to finalize
                            _fileMappingHandle.SetHandleAsInvalid();
                            _fileMappingHandle = Interop.Kernel32.OpenFileMapping(Interop.Kernel32.FileMapOptions.FILE_MAP_WRITE, false, mappingName);

                            if ((Marshal.GetLastWin32Error() != Interop.Errors.ERROR_FILE_NOT_FOUND) || !_fileMappingHandle.IsInvalid)
                            {
                                created = true;
                            }
                            else
                            {
                                --waitRetries;
                                if (waitSleep == 0)
                                {
                                    waitSleep = 10;
                                }
                                else
                                {
                                    System.Threading.Thread.Sleep(waitSleep);
                                    waitSleep *= 2;
                                }
                            }
                        }
                    }
                    if (_fileMappingHandle.IsInvalid)
                    {
                        throw new InvalidOperationException(SR.CantCreateFileMapping);
                    }

                    _fileViewAddress = Interop.Kernel32.MapViewOfFile(_fileMappingHandle, Interop.Kernel32.FileMapOptions.FILE_MAP_WRITE, 0, 0, UIntPtr.Zero);
                    if (_fileViewAddress.IsInvalid)
                        throw new InvalidOperationException(SR.CantMapFileView);

                    // figure out what size the share memory really is.
                    Interop.Kernel32.MEMORY_BASIC_INFORMATION meminfo = default;
                    if (Interop.Kernel32.VirtualQuery(_fileViewAddress, ref meminfo, (UIntPtr)sizeof(Interop.Kernel32.MEMORY_BASIC_INFORMATION)) == UIntPtr.Zero)
                        throw new InvalidOperationException(SR.CantGetMappingSize);

                    _fileMappingSize = (int)meminfo.RegionSize;
                }
                finally
                {
                    Marshal.FreeHGlobal((IntPtr)pSecurityDescriptor);
                }

                Interlocked.CompareExchange(ref *(int*)_fileViewAddress.DangerousGetHandle().ToPointer(), initialOffset, 0);
            }

        }

        // SafeMarshalCopy always null terminates the char array
        // before copying it to native memory
        //
        private static unsafe void SafeMarshalCopy(string str, void* nativePointer)
        {
            // convert str to a char array and copy it to the unmanaged memory pointer
            char[] tmp = new char[str.Length + 1];
            str.CopyTo(0, tmp, 0, str.Length);
            tmp[str.Length] = '\0';  // make sure the char[] is null terminated
            Marshal.Copy(tmp, 0, (nint)nativePointer, tmp.Length);
        }

        // <WARNING>
        // The final tmpPadding field is needed to make the size of this structure 8-byte aligned.  This is
        // necessary on IA64.
        // </WARNING>
        // Note that in V1.0 and v1.1 there was no explicit padding defined on any of these structs.  That means that
        // sizeof(CategoryEntry) or Marshal.SizeOf(typeof(CategoryEntry)) returned 4 bytes less before Whidbey,
        // and the int we use as IsConsistent could actually overlap the InstanceEntry SpinLock.

        [StructLayout(LayoutKind.Sequential)]
        private struct CategoryEntry
        {
            public int SpinLock;
            public int CategoryNameHashCode;
            public int CategoryNameOffset;
            public int FirstInstanceOffset;
            public int NextCategoryOffset;
            public int IsConsistent;         // this was 4 bytes of padding in v1.0/v1.1
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct InstanceEntry
        {
            public int SpinLock;
            public int InstanceNameHashCode;
            public int InstanceNameOffset;
            public int RefCount;
            public int FirstCounterOffset;
            public int NextInstanceOffset;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct CounterEntry
        {
            public int SpinLock;
            public int CounterNameHashCode;
            public int CounterNameOffset;
            public int LifetimeOffset;          // this was 4 bytes of padding in v1.0/v1.1
            public long Value;
            public int NextCounterOffset;
            public int padding2;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct CounterEntryMisaligned
        {
            public int SpinLock;
            public int CounterNameHashCode;
            public int CounterNameOffset;
            public int LifetimeOffset;         // this was 4 bytes of padding in v1.0/v1.1
            public int Value_lo;
            public int Value_hi;
            public int NextCounterOffset;
            public int padding2;        // The compiler adds this only if there is an int64 in the struct -
                                        // ie only for CounterEntry.  It really needs to be here.
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct ProcessLifetimeEntry
        {
            public int LifetimeType;
            public int ProcessId;
            public long StartupTime;
        }

        private sealed class CategoryData
        {
            public FileMapping FileMapping;
            public bool EnableReuse;
            public bool UseUniqueSharedMemory;
            public string FileMappingName;
            public string MutexName;
            public ArrayList CounterNames;
        }
    }

    internal sealed class ProcessData
    {
        public ProcessData(int pid, long startTime)
        {
            ProcessId = pid;
            StartupTime = startTime;
        }
        public int ProcessId;
        public long StartupTime;
    }

}