|
//------------------------------------------------------------------------------
// <copyright file="SharedPerformanceCounter.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Diagnostics {
using System;
using System.Text;
using System.Threading;
using System.Collections;
using System.Runtime.ConstrainedExecution;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Security;
using Microsoft.Win32;
using Microsoft.Win32.SafeHandles;
using System.Globalization;
using System.Security.Principal;
using System.Security.AccessControl;
using System.Collections.Generic;
using System.Runtime.Versioning;
[HostProtection(Synchronization=true, SharedState=true)]
internal sealed 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 SingleInstanceHashCode = GetWstrHashCode(SingleInstanceName);
private static Hashtable categoryDataTable = new Hashtable(StringComparer.Ordinal);
private static readonly int CategoryEntrySize = Marshal.SizeOf(typeof(CategoryEntry));
private static readonly int InstanceEntrySize = Marshal.SizeOf(typeof(InstanceEntry));
private static readonly int CounterEntrySize = Marshal.SizeOf(typeof(CounterEntry));
private static readonly int ProcessLifetimeEntrySize = Marshal.SizeOf(typeof(ProcessLifetimeEntry));
private static long LastInstanceLifetimeSweepTick;
private const long InstanceLifetimeSweepWindow = 30*10000000; //ticks
private static volatile ProcessData procData;
private static ProcessData ProcessData {
get {
if (procData == null) {
new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert();
try {
int pid = NativeMethods.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 = SafeProcessHandle.OpenProcess(NativeMethods.PROCESS_QUERY_INFORMATION, false, pid)) {
if (!procHandle.IsInvalid) {
long temp;
NativeMethods.GetProcessTimes(procHandle, out startTime, out temp, out temp, out temp);
}
}
procData = new ProcessData(pid, startTime);
}
finally {
SecurityPermission.RevertAssert();
}
}
return 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 CategoryData categoryData;
private long baseAddress;
private unsafe CounterEntry* counterEntryPointer;
private string categoryName;
private int categoryNameHashCode;
private int thisInstanceOffset = -1;
internal SharedPerformanceCounter(string catName, string counterName, string instanceName) :
this (catName, counterName, instanceName, PerformanceCounterInstanceLifetime.Global) { }
[ResourceExposure(ResourceScope.None)]
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
internal unsafe SharedPerformanceCounter(string catName, string counterName, string instanceName, PerformanceCounterInstanceLifetime lifetime) {
this.categoryName = catName;
this.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.GetString(SR.InstanceNameTooLong));
}
else {
if (lifetime != PerformanceCounterInstanceLifetime.Global)
throw new InvalidOperationException(SR.GetString(SR.ProcessLifetimeNotValidInGlobal));
}
if (counterName != null && instanceName != null) {
if (!categoryData.CounterNames.Contains(counterName))
Debug.Assert(false, "Counter " + counterName + " does not exist in category " + catName);
else
this.counterEntryPointer = GetCounter(counterName, instanceName, categoryData.EnableReuse, lifetime);
}
}
private FileMapping FileView {
[ResourceExposure(ResourceScope.Machine)]
get {
return categoryData.FileMapping;
}
}
internal unsafe long Value {
get {
if (counterEntryPointer == null)
return 0;
return GetValue(this.counterEntryPointer);
}
set {
if (counterEntryPointer == null)
return;
SetValue(this.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 (SafeNativeMethods.InterlockedCompareExchange((IntPtr)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.GetString(SR.CountersOOM));
}
return newOffset;
}
[ResourceExposure(ResourceScope.None)]
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
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 = 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 = CategoryEntrySize + InstanceEntrySize + (CounterEntrySize * 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 += ProcessLifetimeEntrySize + 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);
}
long nextPtr = 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
// alignemnt, 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 += CategoryEntrySize + alignmentAdjustment;
newInstanceEntryPointer = (InstanceEntry*) nextPtr;
}
else {
nextPtr += alignmentAdjustment;
newCategoryEntryPointer = (CategoryEntry*) nextPtr;
nextPtr += CategoryEntrySize;
newInstanceEntryPointer = (InstanceEntry*) nextPtr;
}
nextPtr += InstanceEntrySize;
// 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 += CounterEntrySize * categoryData.CounterNames.Count;
if (categoryData.UseUniqueSharedMemory) {
ProcessLifetimeEntry* newLifetimeEntry = (ProcessLifetimeEntry*) nextPtr;
nextPtr += ProcessLifetimeEntrySize;
newCounterEntryPointer->LifetimeOffset = (int)((long)newLifetimeEntry - baseAddress);
PopulateLifetimeEntry(newLifetimeEntry, lifetime);
}
newCategoryEntryPointer->CategoryNameHashCode = categoryNameHashCode;
newCategoryEntryPointer->NextCategoryOffset = 0;
newCategoryEntryPointer->FirstInstanceOffset = (int)((long)newInstanceEntryPointer - baseAddress);
newCategoryEntryPointer->CategoryNameOffset = (int) (nextPtr - baseAddress);
SafeMarshalCopy(categoryName, (IntPtr)nextPtr);
nextPtr += categoryNameLength;
newInstanceEntryPointer->InstanceNameHashCode = instanceNameHashCode;
newInstanceEntryPointer->NextInstanceOffset = 0;
newInstanceEntryPointer->FirstCounterOffset = (int)((long)newCounterEntryPointer - baseAddress);
newInstanceEntryPointer->RefCount = 1;
newInstanceEntryPointer->InstanceNameOffset = (int) (nextPtr - baseAddress);
SafeMarshalCopy(instanceName, (IntPtr)nextPtr);
nextPtr += instanceNameLength;
string counterName = (string) categoryData.CounterNames[0];
newCounterEntryPointer->CounterNameHashCode = GetWstrHashCode(counterName);
SetValue(newCounterEntryPointer, 0);
newCounterEntryPointer->CounterNameOffset = (int) (nextPtr - baseAddress);
SafeMarshalCopy(counterName, (IntPtr)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, (IntPtr)nextPtr);
nextPtr += (counterName.Length + 1) * 2;
previousCounterEntryPointer->NextCounterOffset = (int)((long)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) ((long) 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 = InstanceEntrySize + (CounterEntrySize * categoryData.CounterNames.Count);
int alignmentAdjustment;
int freeMemoryOffset;
int newOffset = 0;
if (categoryData.UseUniqueSharedMemory) {
instanceNameLength = InstanceNameSlotSize;
totalSize += ProcessLifetimeEntrySize + 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;
long nextPtr = ResolveOffset(freeMemoryOffset, totalSize); // don't add alignmentAdjustment since it's already
// been added to freeMemoryOffset
InstanceEntry* newInstanceEntryPointer = (InstanceEntry*) nextPtr;
nextPtr += InstanceEntrySize;
// 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 += CounterEntrySize * categoryData.CounterNames.Count;
if (categoryData.UseUniqueSharedMemory) {
ProcessLifetimeEntry* newLifetimeEntry = (ProcessLifetimeEntry*) nextPtr;
nextPtr += ProcessLifetimeEntrySize;
newCounterEntryPointer->LifetimeOffset = (int)((long)newLifetimeEntry - baseAddress);
PopulateLifetimeEntry(newLifetimeEntry, lifetime);
}
// set up the InstanceEntry
newInstanceEntryPointer->InstanceNameHashCode = instanceNameHashCode;
newInstanceEntryPointer->NextInstanceOffset = 0;
newInstanceEntryPointer->FirstCounterOffset = (int)((long)newCounterEntryPointer - baseAddress);
newInstanceEntryPointer->RefCount = 1;
newInstanceEntryPointer->InstanceNameOffset = (int) (nextPtr - baseAddress);
SafeMarshalCopy(instanceName, (IntPtr)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, InstanceEntrySize);
CounterEntry* firstCounterInCategoryPointer = (CounterEntry*) ResolveOffset(firstInstanceInCategoryPointer->FirstCounterOffset, CounterEntrySize);
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, CounterEntrySize);
newCounterEntryPointer->CounterNameHashCode = firstCounterInCategoryPointer->CounterNameHashCode;
SetValue(newCounterEntryPointer, 0);
newCounterEntryPointer->CounterNameOffset = firstCounterInCategoryPointer->CounterNameOffset;
previousCounterEntryPointer->NextCounterOffset = (int)((long)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, (IntPtr)nextPtr);
nextPtr += (counterName.Length + 1) * 2;
SetValue(newCounterEntryPointer, 0);
if (i != 0)
previousCounterEntryPointer->NextCounterOffset = (int)((long)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) ((long) 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;
long nextPtr = 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, (IntPtr)nextPtr);
Debug.Assert(nextPtr + counterNameLength - baseAddress == freeMemoryOffset + totalSize, "We should have used all of the space we requested at this point");
lastCounterPointer->NextCounterOffset = (int) ((long) newCounterEntryPointer - baseAddress);
return freeMemoryOffset;
}
[ResourceExposure(ResourceScope.None)]
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
private unsafe static 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;
}
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
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.
RuntimeHelpers.PrepareConstrainedRegions();
try {
}
finally {
int r = Interlocked.CompareExchange(ref *spinLockPointer, 1, 0);
taken = (r == 0);
}
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
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;
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
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.
[ResourceExposure(ResourceScope.None)]
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
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.GetString(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.
[ResourceExposure(ResourceScope.None)]
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
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.GetString(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.GetString(SR.MappingCorrupted));
return (currentChar[i] == 0);
}
[ResourceExposure(ResourceScope.None)] // Memory maps the perf counter data file
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
private unsafe CategoryData GetCategoryData() {
CategoryData data = (CategoryData) categoryDataTable[categoryName];
if (data == null) {
lock(categoryDataTable) {
data = (CategoryData) categoryDataTable[categoryName];
if (data == null) {
data = new CategoryData();
data.FileMappingName = DefaultFileMappingName;
data.MutexName = categoryName;
RegistryPermission registryPermission = new RegistryPermission(PermissionState.Unrestricted);
registryPermission.Assert();
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 = 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 {
string[] counterNames = (string[]) counterNamesObject;
for (int i=0; i<counterNames.Length; i++)
counterNames[i] = counterNames[i].ToLowerInvariant();
data.CounterNames = new ArrayList(counterNames);
}
// figure out the shared memory name
if (SharedUtils.CurrentEnvironment == SharedUtils.W2kEnvironment) {
data.FileMappingName = "Global\\" + data.FileMappingName;
data.MutexName = "Global\\" + categoryName;
}
data.FileMapping = new FileMapping(data.FileMappingName, fileMappingSize, InitialOffset);
categoryDataTable[categoryName] = data;
}
finally {
if (categoryKey != null)
categoryKey.Close();
RegistryPermission.RevertAssert();
}
}
}
}
baseAddress = (long) data.FileMapping.FileViewAddress;
if (data.UseUniqueSharedMemory)
InitialOffset = 8;
return data;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static int GetFileMappingSizeFromConfig() {
return DiagnosticsConfiguration.PerfomanceCountersFileMappingSize;
}
private static void RemoveCategoryData(string categoryName) {
lock (categoryDataTable) {
categoryDataTable.Remove(categoryName);
}
}
[ResourceExposure(ResourceScope.None)]
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
private unsafe CounterEntry* GetCounter(string counterName, string instanceName, bool enableReuse, PerformanceCounterInstanceLifetime lifetime) {
int counterNameHashCode = GetWstrHashCode(counterName);
int instanceNameHashCode;
if (instanceName != null && instanceName.Length != 0)
instanceNameHashCode = GetWstrHashCode(instanceName);
else {
instanceNameHashCode = SingleInstanceHashCode;
instanceName = SingleInstanceName;
}
Mutex mutex = null;
CounterEntry* counterPointer = null;
InstanceEntry* instancePointer = null;
RuntimeHelpers.PrepareConstrainedRegions();
try {
SharedUtils.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, CategoryEntrySize));
instancePointer = (InstanceEntry*)(ResolveOffset(categoryPointer->FirstInstanceOffset, InstanceEntrySize));
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, InstanceEntrySize));
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, CounterEntrySize));
}
finally {
ExitCriticalSection(&(counterPointer->SpinLock));
}
}
}
return counterPointer;
}
}
finally {
// cache this instance for reuse
try {
if (counterPointer != null && instancePointer != null) {
this.thisInstanceOffset = ResolveAddress((long)instancePointer, InstanceEntrySize);
}
}
catch (InvalidOperationException) {
this.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, CategoryEntrySize));
CategoryEntry* currentCategoryPointer = firstCategoryPointer;
CategoryEntry* previousCategoryPointer = firstCategoryPointer;
for(;;) {
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, CategoryEntrySize));
else {
*returnCategoryPointerReference = previousCategoryPointer;
return false;
}
}
}
private unsafe bool FindCounter(int counterNameHashCode, string counterName, InstanceEntry* instancePointer, CounterEntry** returnCounterPointerReference) {
CounterEntry* currentCounterPointer = (CounterEntry*)(ResolveOffset(instancePointer->FirstCounterOffset, CounterEntrySize));
CounterEntry* previousCounterPointer = currentCounterPointer;
for(;;) {
if (currentCounterPointer->CounterNameHashCode == counterNameHashCode) {
if (StringEquals(counterName, currentCounterPointer->CounterNameOffset)) {
*returnCounterPointerReference = currentCounterPointer;
return true;
}
}
previousCounterPointer = currentCounterPointer;
if (currentCounterPointer->NextCounterOffset != 0)
currentCounterPointer = (CounterEntry*)(ResolveOffset(currentCounterPointer->NextCounterOffset, CounterEntrySize));
else {
*returnCounterPointerReference = previousCounterPointer;
return false;
}
}
}
[ResourceExposure(ResourceScope.None)]
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
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, InstanceEntrySize));
InstanceEntry* previousInstancePointer = currentInstancePointer;
foundFreeInstance = false;
// Look at the first instance to determine if this is single or multi instance.
if (currentInstancePointer->InstanceNameHashCode == SingleInstanceHashCode) {
if (StringEquals(SingleInstanceName, currentInstancePointer->InstanceNameOffset)){
if (instanceName != SingleInstanceName)
throw new InvalidOperationException(SR.GetString(SR.SingleInstanceOnly, categoryName));
}
else {
if (instanceName == SingleInstanceName)
throw new InvalidOperationException(SR.GetString(SR.MultiInstanceOnly, categoryName));
}
}
else {
if (instanceName == SingleInstanceName)
throw new InvalidOperationException(SR.GetString(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 = InstanceEntrySize + ProcessLifetimeEntrySize + InstanceNameSlotSize + (CounterEntrySize * 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 LastInstanceLifetimeSweepTick));
if (tickDelta < InstanceLifetimeSweepWindow)
verifyLifeTime = false;
}
}
new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert();
try {
for(;;) {
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, CounterEntrySize);
ProcessLifetimeEntry* lifetimeEntry;
if (categoryData.UseUniqueSharedMemory)
lifetimeEntry = (ProcessLifetimeEntry*) ResolveOffset(firstCounter->LifetimeOffset, ProcessLifetimeEntrySize);
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.GetString(SR.CantConvertProcessToGlobal));
// make sure only one process is using this instance.
if (ProcessData.ProcessId != lifetimeEntry->ProcessId)
throw new InvalidOperationException(SR.GetString(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.GetString(SR.InstanceAlreadyExists, instanceName));
}
}
else {
if (lifetime == PerformanceCounterInstanceLifetime.Process)
throw new InvalidOperationException(SR.GetString(SR.CantConvertGlobalToProcess));
}
return true;
}
if (activateUnusedInstances) {
Mutex mutex = null;
RuntimeHelpers.PrepareConstrainedRegions();
try {
SharedUtils.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, InstanceEntrySize));
else {
*returnInstancePointerReference = previousInstancePointer;
return false;
}
}
}
finally {
SecurityPermission.RevertAssert();
if (verifyLifeTime)
Volatile.Write(ref 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, InstanceEntrySize));
InstanceEntry* previousInstancePointer = currentInstancePointer;
for (;;) {
if (currentInstancePointer->RefCount == 0) {
bool hasFit;
long instanceNamePtr; // we need cache this to avoid race conditions.
if (categoryData.UseUniqueSharedMemory) {
instanceNamePtr = 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 = 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((char*) 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, (IntPtr)instanceNamePtr);
currentInstancePointer->InstanceNameHashCode = instanceNameHashCode;
// return
*returnInstancePointerReference = currentInstancePointer;
// clear the counter values.
ClearCounterValues(*returnInstancePointerReference);
if (categoryData.UseUniqueSharedMemory) {
CounterEntry* counterPointer = (CounterEntry*)ResolveOffset(currentInstancePointer->FirstCounterOffset, CounterEntrySize);
ProcessLifetimeEntry* lifetimeEntry = (ProcessLifetimeEntry*) ResolveOffset(counterPointer->LifetimeOffset, ProcessLifetimeEntrySize);
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, InstanceEntrySize));
else
{
*returnInstancePointerReference = previousInstancePointer;
return false;
}
}
}
[ResourceExposure(ResourceScope.None)]
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
private unsafe void Verify(CategoryEntry* currentCategoryPointer) {
if (!categoryData.UseUniqueSharedMemory)
return;
Mutex mutex = null;
RuntimeHelpers.PrepareConstrainedRegions();
try {
SharedUtils.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((long)currentCategoryPointer, CategoryEntrySize);
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, CategoryEntrySize));
if (currentCategoryPointer->FirstInstanceOffset != 0) {
// In V3, we started prepending the new instances rather than appending (as in V2) for performance.
// 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, InstanceEntrySize);
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, InstanceEntrySize));
}
}
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, InstanceEntrySize));
}
[ResourceExposure(ResourceScope.None)]
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
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, CounterEntrySize);
if (counter->LifetimeOffset != 0) {
ProcessLifetimeEntry* lifetime = (ProcessLifetimeEntry*) ResolveOffset(counter->LifetimeOffset, ProcessLifetimeEntrySize);
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 = SafeProcessHandle.OpenProcess(NativeMethods.PROCESS_QUERY_INFORMATION, false, pid)) {
int error = Marshal.GetLastWin32Error();
if ((error == NativeMethods.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 ---- 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 (NativeMethods.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 = SafeProcessHandle.OpenProcess(NativeMethods.SYNCHRONIZE, false, pid)) {
if (!procHandle.IsInvalid) {
using (ProcessWaitHandle wh = new ProcessWaitHandle(procHandle)) {
if (wh.WaitOne(0, false)) {
// Process has exited
currentInstancePointer->RefCount = 0;
return;
}
}
}
}
}
}
}
}
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
internal unsafe long IncrementBy(long value) {
if (counterEntryPointer == null)
return 0;
CounterEntry* counterEntry = this.counterEntryPointer;
return AddToValue(counterEntry, value);
}
internal unsafe long Increment() {
if (counterEntryPointer == null)
return 0;
return IncrementUnaligned(this.counterEntryPointer);
}
internal unsafe long Decrement() {
if (counterEntryPointer == null)
return 0;
return DecrementUnaligned(this.counterEntryPointer);
}
internal unsafe static void RemoveAllInstances(string categoryName) {
SharedPerformanceCounter spc = new SharedPerformanceCounter(categoryName, null, null);
spc.RemoveAllInstances();
RemoveCategoryData(categoryName);
}
[ResourceExposure(ResourceScope.None)]
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
private unsafe void RemoveAllInstances() {
CategoryEntry* categoryPointer;
if (!FindCategory(&categoryPointer))
return;
InstanceEntry* instancePointer = (InstanceEntry *)(ResolveOffset(categoryPointer->FirstInstanceOffset, InstanceEntrySize));
Mutex mutex = null;
RuntimeHelpers.PrepareConstrainedRegions();
try {
SharedUtils.EnterMutexWithoutGlobal(categoryData.MutexName, ref mutex);
for(;;) {
RemoveOneInstance(instancePointer, true);
if (instancePointer->NextInstanceOffset != 0)
instancePointer = (InstanceEntry*)(ResolveOffset(instancePointer->NextInstanceOffset, InstanceEntrySize));
else {
break;
}
}
}
finally {
if (mutex != null) {
mutex.ReleaseMutex();
mutex.Close();
}
}
}
[ResourceExposure(ResourceScope.None)]
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
internal unsafe void RemoveInstance(string instanceName, PerformanceCounterInstanceLifetime instanceLifetime) {
if (instanceName == null || instanceName.Length == 0)
return;
int instanceNameHashCode = GetWstrHashCode(instanceName);
CategoryEntry* categoryPointer;
if (!FindCategory(&categoryPointer))
return;
InstanceEntry* instancePointer = null;
bool validatedCachedInstancePointer = false;
bool temp;
Mutex mutex = null;
RuntimeHelpers.PrepareConstrainedRegions();
try {
SharedUtils.EnterMutexWithoutGlobal(categoryData.MutexName, ref mutex);
if (this.thisInstanceOffset != -1) {
try {
// validate whether the cached instance pointer is pointing at the right instance
instancePointer = (InstanceEntry*)(ResolveOffset(this.thisInstanceOffset, InstanceEntrySize));
if (instancePointer->InstanceNameHashCode == instanceNameHashCode) {
if (StringEquals(instanceName, instancePointer->InstanceNameOffset)){
validatedCachedInstancePointer = true;
// this is probably overkill
CounterEntry* firstCounter = (CounterEntry*) ResolveOffset(instancePointer->FirstCounterOffset, CounterEntrySize);
ProcessLifetimeEntry* lifetimeEntry;
if (categoryData.UseUniqueSharedMemory) {
lifetimeEntry = (ProcessLifetimeEntry*) ResolveOffset(firstCounter->LifetimeOffset, ProcessLifetimeEntrySize);
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)
this.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();
}
}
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
private unsafe void RemoveOneInstance(InstanceEntry* instancePointer, bool clearValue) {
bool sectionEntered = false;
RuntimeHelpers.PrepareConstrainedRegions();
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, CounterEntrySize));
while(currentCounterPointer != null) {
SetValue(currentCounterPointer, 0);
if (currentCounterPointer->NextCounterOffset != 0)
currentCounterPointer = (CounterEntry*)(ResolveOffset(currentCounterPointer->NextCounterOffset, CounterEntrySize));
else
currentCounterPointer = null;
}
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
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;
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
private static unsafe bool IsMisaligned(CounterEntry* counterEntry) {
return (( (Int64)counterEntry & 0x7) != 0);
}
[ResourceExposure(ResourceScope.None)]
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
private long 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.GetString(SR.MappingCorrupted));
long address = baseAddress + offset;
return address;
}
[ResourceExposure(ResourceScope.None)]
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
private int ResolveAddress(long 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.GetString(SR.MappingCorrupted));
return offset;
}
private class FileMapping {
internal int FileMappingSize;
private SafeFileMapViewHandle fileViewAddress = null;
private SafeFileMappingHandle fileMappingHandle = null;
//The version of the file mapping name is independent from the
//assembly version.
[ResourceExposure(ResourceScope.Machine)]
[ResourceConsumption(ResourceScope.Machine)]
public FileMapping(string fileMappingName, int fileMappingSize, int initialOffset) {
this.Initialize(fileMappingName, fileMappingSize, initialOffset);
}
internal IntPtr FileViewAddress {
get {
if (fileViewAddress.IsInvalid)
throw new InvalidOperationException(SR.GetString(SR.SharedMemoryGhosted));
return fileViewAddress.DangerousGetHandle();
}
}
[ResourceExposure(ResourceScope.Machine)]
[ResourceConsumption(ResourceScope.Machine)]
private unsafe void Initialize(string fileMappingName, int fileMappingSize, int initialOffset) {
string mappingName = fileMappingName;
SharedUtils.CheckEnvironment();
SafeLocalMemHandle securityDescriptorPointer = null;
new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert();
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 (!SafeLocalMemHandle.ConvertStringSecurityDescriptorToSecurityDescriptor(sddlString, NativeMethods.SDDL_REVISION_1,
out securityDescriptorPointer, IntPtr.Zero))
throw new InvalidOperationException(SR.GetString(SR.SetSecurityDescriptorFailed));
NativeMethods.SECURITY_ATTRIBUTES securityAttributes = new NativeMethods.SECURITY_ATTRIBUTES();
securityAttributes.lpSecurityDescriptor = securityDescriptorPointer;
securityAttributes.bInheritHandle = 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 = NativeMethods.CreateFileMapping((IntPtr)(-1), securityAttributes,
NativeMethods.PAGE_READWRITE, 0, fileMappingSize, mappingName);
if ((Marshal.GetLastWin32Error() != NativeMethods.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 = NativeMethods.OpenFileMapping(NativeMethods.FILE_MAP_WRITE, false, mappingName);
if ((Marshal.GetLastWin32Error() != NativeMethods.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.GetString(SR.CantCreateFileMapping));
}
fileViewAddress = SafeFileMapViewHandle.MapViewOfFile(fileMappingHandle, NativeMethods.FILE_MAP_WRITE, 0,0, UIntPtr.Zero);
if (fileViewAddress.IsInvalid)
throw new InvalidOperationException(SR.GetString(SR.CantMapFileView));
// figure out what size the share memory really is.
NativeMethods.MEMORY_BASIC_INFORMATION meminfo = new NativeMethods.MEMORY_BASIC_INFORMATION();
if (NativeMethods.VirtualQuery(fileViewAddress, ref meminfo, (IntPtr) sizeof(NativeMethods.MEMORY_BASIC_INFORMATION)) == IntPtr.Zero)
throw new InvalidOperationException(SR.GetString(SR.CantGetMappingSize));
FileMappingSize = (int) meminfo.RegionSize;
}
finally {
if (securityDescriptorPointer != null) securityDescriptorPointer.Close();
SecurityPermission.RevertAssert();
}
SafeNativeMethods.InterlockedCompareExchange(fileViewAddress.DangerousGetHandle(), initialOffset, 0);
}
}
// SafeMarshalCopy always null terminates the char array
// before copying it to native memory
//
private static void SafeMarshalCopy(string str, IntPtr 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, 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 Int64 StartupTime;
}
private class CategoryData {
public FileMapping FileMapping;
public bool EnableReuse;
public bool UseUniqueSharedMemory;
public string FileMappingName;
public string MutexName;
public ArrayList CounterNames;
}
}
internal class ProcessData {
public ProcessData(int pid, long startTime) {
ProcessId = pid;
StartupTime = startTime;
}
public int ProcessId;
public long StartupTime;
}
}
|