1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System; 6 using System.Text; 7 using System.Threading; 8 using System.Collections; 9 using System.Runtime.CompilerServices; 10 using System.Runtime.InteropServices; 11 using Microsoft.Win32; 12 using Microsoft.Win32.SafeHandles; 13 14 namespace System.Diagnostics 15 { 16 internal sealed class SharedPerformanceCounter 17 { 18 private const int MaxSpinCount = 5000; 19 internal const int DefaultCountersFileMappingSize = 524288; 20 internal const int MaxCountersFileMappingSize = 33554432; 21 internal const int MinCountersFileMappingSize = 32768; 22 internal const int InstanceNameMaxLength = 127; 23 internal const int InstanceNameSlotSize = 256; 24 internal const string SingleInstanceName = "systemdiagnosticssharedsingleinstance"; 25 internal const string DefaultFileMappingName = "netfxcustomperfcounters.1.0"; 26 internal static readonly int s_singleInstanceHashCode = GetWstrHashCode(SingleInstanceName); 27 private static Hashtable s_categoryDataTable = new Hashtable(StringComparer.Ordinal); 28 private static readonly int s_categoryEntrySize = Marshal.SizeOf(typeof(CategoryEntry)); 29 private static readonly int s_instanceEntrySize = Marshal.SizeOf(typeof(InstanceEntry)); 30 private static readonly int s_counterEntrySize = Marshal.SizeOf(typeof(CounterEntry)); 31 private static readonly int s_processLifetimeEntrySize = Marshal.SizeOf(typeof(ProcessLifetimeEntry)); 32 33 private static long s_lastInstanceLifetimeSweepTick; 34 private const long InstanceLifetimeSweepWindow = 30 * 10000000; //ticks 35 private static volatile ProcessData s_procData; 36 37 private static ProcessData ProcessData 38 { 39 get 40 { 41 if (s_procData == null) 42 { 43 try 44 { 45 int pid = (int)Interop.Kernel32.GetCurrentProcessId(); 46 long startTime = -1; 47 48 // Though we have asserted the required CAS permissions above, we may 49 // still fail to query the process information if the user does not 50 // have the necessary process access rights or privileges. 51 // This might be the case if the current process was started by a 52 // different user (primary token) than the current user 53 // (impersonation token) that has less privilege/ACL rights. 54 using (SafeProcessHandle procHandle = Interop.Kernel32.OpenProcess(Interop.Advapi32.ProcessOptions.PROCESS_QUERY_INFORMATION, false, pid)) 55 { 56 if (!procHandle.IsInvalid) 57 { 58 long temp; 59 Interop.Kernel32.GetProcessTimes(procHandle, out startTime, out temp, out temp, out temp); 60 } 61 } 62 s_procData = new ProcessData(pid, startTime); 63 } 64 finally 65 { 66 } 67 } 68 return s_procData; 69 } 70 } 71 72 // InitialOffset is the offset in our global shared memory where we put the first CategoryEntry. It needs to be 4 because in 73 // v1.0 and v1.1 we used IntPtr.Size. That creates potential side-by-side issues on 64 bit machines using WOW64. 74 // A v1.0 app running on WOW64 will assume the InitialOffset is 4. A true 64 bit app on the same machine will assume 75 // the initial offset is 8. 76 // However, using an offset of 4 means that our CounterEntry.Value is potentially misaligned. This is why we have SetValue 77 // and other methods which split CounterEntry.Value into two ints. With separate shared memory blocks per 78 // category, we can fix this and always use an inital offset of 8. 79 internal int _initialOffset = 4; 80 81 private CategoryData _categoryData; 82 private long _baseAddress; 83 private unsafe CounterEntry* _counterEntryPointer; 84 private string _categoryName; 85 private int _categoryNameHashCode; 86 private int _thisInstanceOffset = -1; 87 SharedPerformanceCounter(string catName, string counterName, string instanceName)88 internal SharedPerformanceCounter(string catName, string counterName, string instanceName) : 89 this(catName, counterName, instanceName, PerformanceCounterInstanceLifetime.Global) 90 { } 91 SharedPerformanceCounter(string catName, string counterName, string instanceName, PerformanceCounterInstanceLifetime lifetime)92 internal unsafe SharedPerformanceCounter(string catName, string counterName, string instanceName, PerformanceCounterInstanceLifetime lifetime) 93 { 94 _categoryName = catName; 95 _categoryNameHashCode = GetWstrHashCode(_categoryName); 96 97 _categoryData = GetCategoryData(); 98 99 // Check that the instance name isn't too long if we're using the new shared memory. 100 // We allocate InstanceNameSlotSize bytes in the shared memory 101 if (_categoryData.UseUniqueSharedMemory) 102 { 103 if (instanceName != null && instanceName.Length > InstanceNameMaxLength) 104 throw new InvalidOperationException(SR.Format(SR.InstanceNameTooLong)); 105 } 106 else 107 { 108 if (lifetime != PerformanceCounterInstanceLifetime.Global) 109 throw new InvalidOperationException(SR.Format(SR.ProcessLifetimeNotValidInGlobal)); 110 } 111 112 if (counterName != null && instanceName != null) 113 { 114 if (!_categoryData.CounterNames.Contains(counterName)) 115 Debug.Assert(false, "Counter " + counterName + " does not exist in category " + catName); 116 else 117 _counterEntryPointer = GetCounter(counterName, instanceName, _categoryData.EnableReuse, lifetime); 118 } 119 } 120 121 private FileMapping FileView 122 { 123 get 124 { 125 return _categoryData.FileMapping; 126 } 127 } 128 129 internal unsafe long Value 130 { 131 get 132 { 133 if (_counterEntryPointer == null) 134 return 0; 135 136 return GetValue(_counterEntryPointer); 137 } 138 139 set 140 { 141 if (_counterEntryPointer == null) 142 return; 143 144 SetValue(_counterEntryPointer, value); 145 } 146 } 147 CalculateAndAllocateMemory(int totalSize, out int alignmentAdjustment)148 private unsafe int CalculateAndAllocateMemory(int totalSize, out int alignmentAdjustment) 149 { 150 int newOffset; 151 int oldOffset; 152 alignmentAdjustment = 0; 153 154 Debug.Assert(!_categoryData.UseUniqueSharedMemory, "We should never be calling CalculateAndAllocateMemory in the unique shared memory"); 155 156 do 157 { 158 oldOffset = *((int*)_baseAddress); 159 // we need to verify the oldOffset before we start using it. Otherwise someone could change 160 // it to something bogus and we would write outside of the shared memory. 161 ResolveOffset(oldOffset, 0); 162 163 newOffset = CalculateMemory(oldOffset, totalSize, out alignmentAdjustment); 164 165 // In the default shared mem we need to make sure that the end address is also aligned. This is because 166 // in v1.1/v1.0 we just assumed that the next free offset was always properly aligned. 167 int endAddressMod8 = (int)(_baseAddress + newOffset) & 0x7; 168 int endAlignmentAdjustment = (8 - endAddressMod8) & 0x7; 169 newOffset += endAlignmentAdjustment; 170 171 } while (Interlocked.CompareExchange(ref *(int*)((IntPtr)_baseAddress).ToPointer(), newOffset, oldOffset) != oldOffset); 172 173 return oldOffset; 174 } 175 CalculateMemory(int oldOffset, int totalSize, out int alignmentAdjustment)176 private int CalculateMemory(int oldOffset, int totalSize, out int alignmentAdjustment) 177 { 178 int newOffset = CalculateMemoryNoBoundsCheck(oldOffset, totalSize, out alignmentAdjustment); 179 180 if (newOffset > FileView._fileMappingSize || newOffset < 0) 181 { 182 throw new InvalidOperationException(SR.Format(SR.CountersOOM)); 183 } 184 185 return newOffset; 186 } 187 CalculateMemoryNoBoundsCheck(int oldOffset, int totalSize, out int alignmentAdjustment)188 private int CalculateMemoryNoBoundsCheck(int oldOffset, int totalSize, out int alignmentAdjustment) 189 { 190 int currentTotalSize = totalSize; 191 192 Thread.MemoryBarrier(); 193 194 // make sure the start address is 8 byte aligned 195 int startAddressMod8 = (int)(_baseAddress + oldOffset) & 0x7; 196 alignmentAdjustment = (8 - startAddressMod8) & 0x7; 197 currentTotalSize = currentTotalSize + alignmentAdjustment; 198 199 int newOffset = oldOffset + currentTotalSize; 200 201 return newOffset; 202 } 203 CreateCategory(CategoryEntry* lastCategoryPointer, int instanceNameHashCode, string instanceName, PerformanceCounterInstanceLifetime lifetime)204 private unsafe int CreateCategory(CategoryEntry* lastCategoryPointer, 205 int instanceNameHashCode, string instanceName, 206 PerformanceCounterInstanceLifetime lifetime) 207 { 208 int categoryNameLength; 209 int instanceNameLength; 210 int alignmentAdjustment; 211 int freeMemoryOffset; 212 int newOffset = 0; 213 int totalSize; 214 215 categoryNameLength = (_categoryName.Length + 1) * 2; 216 totalSize = s_categoryEntrySize + s_instanceEntrySize + (s_counterEntrySize * _categoryData.CounterNames.Count) + categoryNameLength; 217 for (int i = 0; i < _categoryData.CounterNames.Count; i++) 218 { 219 totalSize += (((string)_categoryData.CounterNames[i]).Length + 1) * 2; 220 } 221 222 if (_categoryData.UseUniqueSharedMemory) 223 { 224 instanceNameLength = InstanceNameSlotSize; 225 totalSize += s_processLifetimeEntrySize + instanceNameLength; 226 227 // If we're in a separate shared memory, we need to do a two stage update of the free memory pointer. 228 // First we calculate our alignment adjustment and where the new free offset is. Then we 229 // write the new structs and data. The last two operations are to link the new structs into the 230 // existing ones and update the next free offset. Our process could get killed in between those two, 231 // leaving the memory in an inconsistent state. We use the "IsConsistent" flag to help determine 232 // when that has happened. 233 freeMemoryOffset = *((int*)_baseAddress); 234 newOffset = CalculateMemory(freeMemoryOffset, totalSize, out alignmentAdjustment); 235 236 if (freeMemoryOffset == _initialOffset) 237 lastCategoryPointer->IsConsistent = 0; 238 } 239 else 240 { 241 instanceNameLength = (instanceName.Length + 1) * 2; 242 totalSize += instanceNameLength; 243 freeMemoryOffset = CalculateAndAllocateMemory(totalSize, out alignmentAdjustment); 244 } 245 246 long nextPtr = ResolveOffset(freeMemoryOffset, totalSize + alignmentAdjustment); 247 248 CategoryEntry* newCategoryEntryPointer; 249 InstanceEntry* newInstanceEntryPointer; 250 // We need to decide where to put the padding returned in alignmentAdjustment. There are several things that 251 // need to be aligned. First, we need to align each struct on a 4 byte boundary so we can use interlocked 252 // operations on the int Spinlock field. Second, we need to align the CounterEntry on an 8 byte boundary so that 253 // on 64 bit platforms we can use interlocked operations on the Value field. alignmentAdjustment guarantees 8 byte 254 // alignemnt, so we use that for both. If we're creating the very first category, however, we can't move that 255 // CategoryEntry. In this case we put the alignmentAdjustment before the InstanceEntry. 256 if (freeMemoryOffset == _initialOffset) 257 { 258 newCategoryEntryPointer = (CategoryEntry*)nextPtr; 259 nextPtr += s_categoryEntrySize + alignmentAdjustment; 260 newInstanceEntryPointer = (InstanceEntry*)nextPtr; 261 } 262 else 263 { 264 nextPtr += alignmentAdjustment; 265 newCategoryEntryPointer = (CategoryEntry*)nextPtr; 266 nextPtr += s_categoryEntrySize; 267 newInstanceEntryPointer = (InstanceEntry*)nextPtr; 268 } 269 nextPtr += s_instanceEntrySize; 270 271 // create the first CounterEntry and reserve space for all of the rest. We won't 272 // finish creating them until the end 273 CounterEntry* newCounterEntryPointer = (CounterEntry*)nextPtr; 274 nextPtr += s_counterEntrySize * _categoryData.CounterNames.Count; 275 276 if (_categoryData.UseUniqueSharedMemory) 277 { 278 ProcessLifetimeEntry* newLifetimeEntry = (ProcessLifetimeEntry*)nextPtr; 279 nextPtr += s_processLifetimeEntrySize; 280 281 newCounterEntryPointer->LifetimeOffset = (int)((long)newLifetimeEntry - _baseAddress); 282 PopulateLifetimeEntry(newLifetimeEntry, lifetime); 283 } 284 285 newCategoryEntryPointer->CategoryNameHashCode = _categoryNameHashCode; 286 newCategoryEntryPointer->NextCategoryOffset = 0; 287 newCategoryEntryPointer->FirstInstanceOffset = (int)((long)newInstanceEntryPointer - _baseAddress); 288 newCategoryEntryPointer->CategoryNameOffset = (int)(nextPtr - _baseAddress); 289 SafeMarshalCopy(_categoryName, (IntPtr)nextPtr); 290 nextPtr += categoryNameLength; 291 292 newInstanceEntryPointer->InstanceNameHashCode = instanceNameHashCode; 293 newInstanceEntryPointer->NextInstanceOffset = 0; 294 newInstanceEntryPointer->FirstCounterOffset = (int)((long)newCounterEntryPointer - _baseAddress); 295 newInstanceEntryPointer->RefCount = 1; 296 newInstanceEntryPointer->InstanceNameOffset = (int)(nextPtr - _baseAddress); 297 SafeMarshalCopy(instanceName, (IntPtr)nextPtr); 298 nextPtr += instanceNameLength; 299 300 string counterName = (string)_categoryData.CounterNames[0]; 301 newCounterEntryPointer->CounterNameHashCode = GetWstrHashCode(counterName); 302 SetValue(newCounterEntryPointer, 0); 303 newCounterEntryPointer->CounterNameOffset = (int)(nextPtr - _baseAddress); 304 SafeMarshalCopy(counterName, (IntPtr)nextPtr); 305 nextPtr += (counterName.Length + 1) * 2; 306 307 CounterEntry* previousCounterEntryPointer; 308 for (int i = 1; i < _categoryData.CounterNames.Count; i++) 309 { 310 previousCounterEntryPointer = newCounterEntryPointer; 311 counterName = (string)_categoryData.CounterNames[i]; 312 313 newCounterEntryPointer++; 314 newCounterEntryPointer->CounterNameHashCode = GetWstrHashCode(counterName); 315 SetValue(newCounterEntryPointer, 0); 316 newCounterEntryPointer->CounterNameOffset = (int)(nextPtr - _baseAddress); 317 SafeMarshalCopy(counterName, (IntPtr)nextPtr); 318 319 nextPtr += (counterName.Length + 1) * 2; 320 previousCounterEntryPointer->NextCounterOffset = (int)((long)newCounterEntryPointer - _baseAddress); 321 } 322 323 Debug.Assert(nextPtr - _baseAddress == freeMemoryOffset + totalSize + alignmentAdjustment, "We should have used all of the space we requested at this point"); 324 325 int offset = (int)((long)newCategoryEntryPointer - _baseAddress); 326 lastCategoryPointer->IsConsistent = 0; 327 // If not the first category node, link it. 328 if (offset != _initialOffset) 329 lastCategoryPointer->NextCategoryOffset = offset; 330 331 if (_categoryData.UseUniqueSharedMemory) 332 { 333 *((int*)_baseAddress) = newOffset; 334 lastCategoryPointer->IsConsistent = 1; 335 } 336 return offset; 337 } 338 CreateInstance(CategoryEntry* categoryPointer, int instanceNameHashCode, string instanceName, PerformanceCounterInstanceLifetime lifetime)339 private unsafe int CreateInstance(CategoryEntry* categoryPointer, 340 int instanceNameHashCode, string instanceName, 341 PerformanceCounterInstanceLifetime lifetime) 342 { 343 int instanceNameLength; 344 int totalSize = s_instanceEntrySize + (s_counterEntrySize * _categoryData.CounterNames.Count); 345 int alignmentAdjustment; 346 int freeMemoryOffset; 347 int newOffset = 0; 348 349 if (_categoryData.UseUniqueSharedMemory) 350 { 351 instanceNameLength = InstanceNameSlotSize; 352 totalSize += s_processLifetimeEntrySize + instanceNameLength; 353 354 // If we're in a separate shared memory, we need to do a two stage update of the free memory pointer. 355 // First we calculate our alignment adjustment and where the new free offset is. Then we 356 // write the new structs and data. The last two operations are to link the new structs into the 357 // existing ones and update the next free offset. Our process could get killed in between those two, 358 // leaving the memory in an inconsistent state. We use the "IsConsistent" flag to help determine 359 // when that has happened. 360 freeMemoryOffset = *((int*)_baseAddress); 361 newOffset = CalculateMemory(freeMemoryOffset, totalSize, out alignmentAdjustment); 362 } 363 else 364 { 365 instanceNameLength = (instanceName.Length + 1) * 2; 366 totalSize += instanceNameLength; 367 368 // add in the counter names for the global shared mem. 369 for (int i = 0; i < _categoryData.CounterNames.Count; i++) 370 { 371 totalSize += (((string)_categoryData.CounterNames[i]).Length + 1) * 2; 372 } 373 freeMemoryOffset = CalculateAndAllocateMemory(totalSize, out alignmentAdjustment); 374 } 375 376 freeMemoryOffset += alignmentAdjustment; 377 long nextPtr = ResolveOffset(freeMemoryOffset, totalSize); // don't add alignmentAdjustment since it's already 378 // been added to freeMemoryOffset 379 380 InstanceEntry* newInstanceEntryPointer = (InstanceEntry*)nextPtr; 381 nextPtr += s_instanceEntrySize; 382 383 // create the first CounterEntry and reserve space for all of the rest. We won't 384 // finish creating them until the end 385 CounterEntry* newCounterEntryPointer = (CounterEntry*)nextPtr; 386 nextPtr += s_counterEntrySize * _categoryData.CounterNames.Count; 387 388 if (_categoryData.UseUniqueSharedMemory) 389 { 390 ProcessLifetimeEntry* newLifetimeEntry = (ProcessLifetimeEntry*)nextPtr; 391 nextPtr += s_processLifetimeEntrySize; 392 393 newCounterEntryPointer->LifetimeOffset = (int)((long)newLifetimeEntry - _baseAddress); 394 PopulateLifetimeEntry(newLifetimeEntry, lifetime); 395 } 396 397 // set up the InstanceEntry 398 newInstanceEntryPointer->InstanceNameHashCode = instanceNameHashCode; 399 newInstanceEntryPointer->NextInstanceOffset = 0; 400 newInstanceEntryPointer->FirstCounterOffset = (int)((long)newCounterEntryPointer - _baseAddress); 401 newInstanceEntryPointer->RefCount = 1; 402 newInstanceEntryPointer->InstanceNameOffset = (int)(nextPtr - _baseAddress); 403 SafeMarshalCopy(instanceName, (IntPtr)nextPtr); 404 405 nextPtr += instanceNameLength; 406 407 if (_categoryData.UseUniqueSharedMemory) 408 { 409 // in the unique shared mem we'll assume that the CounterEntries of the first instance 410 // are all created. Then we can just refer to the old counter name rather than copying in a new one. 411 InstanceEntry* firstInstanceInCategoryPointer = (InstanceEntry*)ResolveOffset(categoryPointer->FirstInstanceOffset, s_instanceEntrySize); 412 CounterEntry* firstCounterInCategoryPointer = (CounterEntry*)ResolveOffset(firstInstanceInCategoryPointer->FirstCounterOffset, s_counterEntrySize); 413 newCounterEntryPointer->CounterNameHashCode = firstCounterInCategoryPointer->CounterNameHashCode; 414 SetValue(newCounterEntryPointer, 0); 415 newCounterEntryPointer->CounterNameOffset = firstCounterInCategoryPointer->CounterNameOffset; 416 417 // now create the rest of the CounterEntrys 418 CounterEntry* previousCounterEntryPointer; 419 for (int i = 1; i < _categoryData.CounterNames.Count; i++) 420 { 421 previousCounterEntryPointer = newCounterEntryPointer; 422 423 newCounterEntryPointer++; 424 Debug.Assert(firstCounterInCategoryPointer->NextCounterOffset != 0, "The unique shared memory should have all of its counters created by the time we hit CreateInstance"); 425 firstCounterInCategoryPointer = (CounterEntry*)ResolveOffset(firstCounterInCategoryPointer->NextCounterOffset, s_counterEntrySize); 426 newCounterEntryPointer->CounterNameHashCode = firstCounterInCategoryPointer->CounterNameHashCode; 427 SetValue(newCounterEntryPointer, 0); 428 newCounterEntryPointer->CounterNameOffset = firstCounterInCategoryPointer->CounterNameOffset; 429 430 previousCounterEntryPointer->NextCounterOffset = (int)((long)newCounterEntryPointer - _baseAddress); 431 } 432 } 433 else 434 { 435 // now create the rest of the CounterEntrys 436 CounterEntry* previousCounterEntryPointer = null; 437 for (int i = 0; i < _categoryData.CounterNames.Count; i++) 438 { 439 string counterName = (string)_categoryData.CounterNames[i]; 440 newCounterEntryPointer->CounterNameHashCode = GetWstrHashCode(counterName); 441 newCounterEntryPointer->CounterNameOffset = (int)(nextPtr - _baseAddress); 442 SafeMarshalCopy(counterName, (IntPtr)nextPtr); 443 nextPtr += (counterName.Length + 1) * 2; 444 445 SetValue(newCounterEntryPointer, 0); 446 447 if (i != 0) 448 previousCounterEntryPointer->NextCounterOffset = (int)((long)newCounterEntryPointer - _baseAddress); 449 450 previousCounterEntryPointer = newCounterEntryPointer; 451 newCounterEntryPointer++; 452 } 453 } 454 455 Debug.Assert(nextPtr - _baseAddress == freeMemoryOffset + totalSize, "We should have used all of the space we requested at this point"); 456 457 int offset = (int)((long)newInstanceEntryPointer - _baseAddress); 458 categoryPointer->IsConsistent = 0; 459 460 // prepend the new instance rather than append, helps with perf of hooking up subsequent counters 461 newInstanceEntryPointer->NextInstanceOffset = categoryPointer->FirstInstanceOffset; 462 categoryPointer->FirstInstanceOffset = offset; 463 464 if (_categoryData.UseUniqueSharedMemory) 465 { 466 *((int*)_baseAddress) = newOffset; 467 categoryPointer->IsConsistent = 1; 468 } 469 470 return freeMemoryOffset; 471 } 472 CreateCounter(CounterEntry* lastCounterPointer, int counterNameHashCode, string counterName)473 private unsafe int CreateCounter(CounterEntry* lastCounterPointer, 474 int counterNameHashCode, string counterName) 475 { 476 int counterNameLength = (counterName.Length + 1) * 2; 477 int totalSize = sizeof(CounterEntry) + counterNameLength; 478 int alignmentAdjustment; 479 int freeMemoryOffset; 480 481 Debug.Assert(!_categoryData.UseUniqueSharedMemory, "We should never be calling CreateCounter in the unique shared memory"); 482 freeMemoryOffset = CalculateAndAllocateMemory(totalSize, out alignmentAdjustment); 483 484 freeMemoryOffset += alignmentAdjustment; 485 486 long nextPtr = ResolveOffset(freeMemoryOffset, totalSize); 487 CounterEntry* newCounterEntryPointer = (CounterEntry*)nextPtr; 488 nextPtr += sizeof(CounterEntry); 489 490 newCounterEntryPointer->CounterNameOffset = (int)(nextPtr - _baseAddress); 491 newCounterEntryPointer->CounterNameHashCode = counterNameHashCode; 492 newCounterEntryPointer->NextCounterOffset = 0; 493 SetValue(newCounterEntryPointer, 0); 494 SafeMarshalCopy(counterName, (IntPtr)nextPtr); 495 496 Debug.Assert(nextPtr + counterNameLength - _baseAddress == freeMemoryOffset + totalSize, "We should have used all of the space we requested at this point"); 497 498 lastCounterPointer->NextCounterOffset = (int)((long)newCounterEntryPointer - _baseAddress); 499 return freeMemoryOffset; 500 } 501 PopulateLifetimeEntry(ProcessLifetimeEntry* lifetimeEntry, PerformanceCounterInstanceLifetime lifetime)502 private unsafe static void PopulateLifetimeEntry(ProcessLifetimeEntry* lifetimeEntry, PerformanceCounterInstanceLifetime lifetime) 503 { 504 505 if (lifetime == PerformanceCounterInstanceLifetime.Process) 506 { 507 508 lifetimeEntry->LifetimeType = (int)PerformanceCounterInstanceLifetime.Process; 509 lifetimeEntry->ProcessId = ProcessData.ProcessId; 510 lifetimeEntry->StartupTime = ProcessData.StartupTime; 511 } 512 else 513 { 514 lifetimeEntry->ProcessId = 0; 515 lifetimeEntry->StartupTime = 0; 516 } 517 } 518 WaitAndEnterCriticalSection(int* spinLockPointer, out bool taken)519 private static unsafe void WaitAndEnterCriticalSection(int* spinLockPointer, out bool taken) 520 { 521 WaitForCriticalSection(spinLockPointer); 522 523 // Note - we are taking a lock here, but it probably isn't 524 // worthwhile to use Thread.BeginCriticalRegion & EndCriticalRegion. 525 // These only really help the CLR escalate from a thread abort 526 // to an appdomain unload, under the assumption that you may be 527 // editing shared state within the appdomain. Here you are editing 528 // shared state, but it is shared across processes. Unloading the 529 // appdomain isn't exactly helping. The only thing that would help 530 // would be if the CLR tells the host to ensure all allocations 531 // have a higher chance of succeeding within this critical region, 532 // but of course that's only a probabilisitic statement. 533 534 // Must be able to assign to the out param. 535 try 536 { 537 } 538 finally 539 { 540 int r = Interlocked.CompareExchange(ref *spinLockPointer, 1, 0); 541 taken = (r == 0); 542 } 543 } 544 WaitForCriticalSection(int* spinLockPointer)545 private static unsafe void WaitForCriticalSection(int* spinLockPointer) 546 { 547 int spinCount = MaxSpinCount; 548 for (; spinCount > 0 && *spinLockPointer != 0; spinCount--) 549 { 550 // We suspect there are scenarios where the finalizer thread 551 // will call this method. The finalizer thread runs with 552 // a higher priority than the other code. Using SpinWait 553 // isn't sufficient, since it only spins, but doesn't yield 554 // to any lower-priority threads. Call Thread.Sleep(1). 555 if (*spinLockPointer != 0) 556 Thread.Sleep(1); 557 } 558 559 // if the lock still isn't free, most likely there's a deadlock caused by a process 560 // getting killed while it held the lock. We'll just free the lock 561 if (spinCount == 0 && *spinLockPointer != 0) 562 *spinLockPointer = 0; 563 } 564 ExitCriticalSection(int* spinLockPointer)565 private static unsafe void ExitCriticalSection(int* spinLockPointer) 566 { 567 *spinLockPointer = 0; 568 } 569 570 // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING 571 // This hashcode function is identical to the one in SharedPerformanceCounter.cpp. If 572 // you change one without changing the other, perfcounters will break. GetWstrHashCode(string wstr)573 internal static int GetWstrHashCode(string wstr) 574 { 575 uint hash = 5381; 576 for (uint i = 0; i < wstr.Length; i++) 577 hash = ((hash << 5) + hash) ^ wstr[(int)i]; 578 return (int)hash; 579 } 580 581 // Calculate the length of a string in the shared memory. If we reach the end of the shared memory 582 // before we see a null terminator, we throw. GetStringLength(char* startChar)583 private unsafe int GetStringLength(char* startChar) 584 { 585 char* currentChar = startChar; 586 ulong endAddress = (ulong)(_baseAddress + FileView._fileMappingSize); 587 588 while ((ulong)currentChar < (endAddress - 2)) 589 { 590 if (*currentChar == 0) 591 return (int)(currentChar - startChar); 592 593 currentChar++; 594 } 595 596 throw new InvalidOperationException(SR.Format(SR.MappingCorrupted)); 597 } 598 599 // Compare a managed string to a string located at a given offset. If we walk past the end of the 600 // shared memory, we throw. StringEquals(string stringA, int offset)601 private unsafe bool StringEquals(string stringA, int offset) 602 { 603 char* currentChar = (char*)ResolveOffset(offset, 0); 604 ulong endAddress = (ulong)(_baseAddress + FileView._fileMappingSize); 605 606 int i; 607 for (i = 0; i < stringA.Length; i++) 608 { 609 if ((ulong)(currentChar + i) > (endAddress - 2)) 610 throw new InvalidOperationException(SR.Format(SR.MappingCorrupted)); 611 612 if (stringA[i] != currentChar[i]) 613 return false; 614 } 615 616 // now check for the null termination. 617 if ((ulong)(currentChar + i) > (endAddress - 2)) 618 throw new InvalidOperationException(SR.Format(SR.MappingCorrupted)); 619 620 return (currentChar[i] == 0); 621 } 622 GetCategoryData()623 private unsafe CategoryData GetCategoryData() 624 { 625 CategoryData data = (CategoryData)s_categoryDataTable[_categoryName]; 626 627 if (data == null) 628 { 629 lock (s_categoryDataTable) 630 { 631 data = (CategoryData)s_categoryDataTable[_categoryName]; 632 if (data == null) 633 { 634 data = new CategoryData(); 635 data.FileMappingName = DefaultFileMappingName; 636 data.MutexName = _categoryName; 637 638 RegistryKey categoryKey = null; 639 try 640 { 641 categoryKey = Registry.LocalMachine.OpenSubKey(PerformanceCounterLib.ServicePath + "\\" + _categoryName + "\\Performance"); 642 643 // first read the options 644 Object optionsObject = categoryKey.GetValue("CategoryOptions"); 645 if (optionsObject != null) 646 { 647 int options = (int)optionsObject; 648 data.EnableReuse = (((PerformanceCounterCategoryOptions)options & PerformanceCounterCategoryOptions.EnableReuse) != 0); 649 650 if (((PerformanceCounterCategoryOptions)options & PerformanceCounterCategoryOptions.UseUniqueSharedMemory) != 0) 651 { 652 data.UseUniqueSharedMemory = true; 653 _initialOffset = 8; 654 data.FileMappingName = DefaultFileMappingName + _categoryName; 655 } 656 } 657 658 int fileMappingSize; 659 object fileMappingSizeObject = categoryKey.GetValue("FileMappingSize"); 660 if (fileMappingSizeObject != null && data.UseUniqueSharedMemory) 661 { 662 // we only use this reg value in the unique shared memory case. 663 fileMappingSize = (int)fileMappingSizeObject; 664 if (fileMappingSize < MinCountersFileMappingSize) 665 fileMappingSize = MinCountersFileMappingSize; 666 667 if (fileMappingSize > MaxCountersFileMappingSize) 668 fileMappingSize = MaxCountersFileMappingSize; 669 } 670 else 671 { 672 fileMappingSize = GetFileMappingSizeFromConfig(); 673 if (data.UseUniqueSharedMemory) 674 fileMappingSize = fileMappingSize >> 2; // if we have a custom filemapping, only make it 25% as large. 675 } 676 677 // now read the counter names 678 object counterNamesObject = categoryKey.GetValue("Counter Names"); 679 byte[] counterNamesBytes = counterNamesObject as byte[]; 680 681 if (counterNamesBytes != null) 682 { 683 ArrayList names = new ArrayList(); 684 fixed (byte* counterNamesPtr = counterNamesBytes) 685 { 686 int start = 0; 687 for (int i = 0; i < counterNamesBytes.Length - 1; i += 2) 688 { 689 if (counterNamesBytes[i] == 0 && counterNamesBytes[i + 1] == 0 && start != i) 690 { 691 string counter = new String((sbyte*)counterNamesPtr, start, i - start, Encoding.Unicode); 692 names.Add(counter.ToLowerInvariant()); 693 start = i + 2; 694 } 695 } 696 } 697 data.CounterNames = names; 698 } 699 else 700 { 701 string[] counterNames = (string[])counterNamesObject; 702 for (int i = 0; i < counterNames.Length; i++) 703 counterNames[i] = counterNames[i].ToLowerInvariant(); 704 data.CounterNames = new ArrayList(counterNames); 705 } 706 707 data.FileMappingName = "Global\\" + data.FileMappingName; 708 data.MutexName = "Global\\" + _categoryName; 709 data.FileMapping = new FileMapping(data.FileMappingName, fileMappingSize, _initialOffset); 710 s_categoryDataTable[_categoryName] = data; 711 } 712 finally 713 { 714 if (categoryKey != null) 715 categoryKey.Close(); 716 } 717 } 718 } 719 } 720 _baseAddress = (long)data.FileMapping.FileViewAddress; 721 722 if (data.UseUniqueSharedMemory) 723 _initialOffset = 8; 724 725 726 return data; 727 } 728 729 [MethodImpl(MethodImplOptions.NoInlining)] GetFileMappingSizeFromConfig()730 private static int GetFileMappingSizeFromConfig() 731 { 732 return DiagnosticsConfiguration.PerformanceCountersFileMappingSize; 733 } 734 RemoveCategoryData(string categoryName)735 private static void RemoveCategoryData(string categoryName) 736 { 737 lock (s_categoryDataTable) 738 { 739 s_categoryDataTable.Remove(categoryName); 740 } 741 } 742 GetCounter(string counterName, string instanceName, bool enableReuse, PerformanceCounterInstanceLifetime lifetime)743 private unsafe CounterEntry* GetCounter(string counterName, string instanceName, bool enableReuse, PerformanceCounterInstanceLifetime lifetime) 744 { 745 int counterNameHashCode = GetWstrHashCode(counterName); 746 int instanceNameHashCode; 747 if (instanceName != null && instanceName.Length != 0) 748 instanceNameHashCode = GetWstrHashCode(instanceName); 749 else 750 { 751 instanceNameHashCode = s_singleInstanceHashCode; 752 instanceName = SingleInstanceName; 753 } 754 755 Mutex mutex = null; 756 CounterEntry* counterPointer = null; 757 InstanceEntry* instancePointer = null; 758 try 759 { 760 SharedUtils.EnterMutexWithoutGlobal(_categoryData.MutexName, ref mutex); 761 CategoryEntry* categoryPointer; 762 bool counterFound = false; 763 while (!FindCategory(&categoryPointer)) 764 { 765 // don't bother locking again if we're using a separate shared memory. 766 bool sectionEntered; 767 if (_categoryData.UseUniqueSharedMemory) 768 sectionEntered = true; 769 else 770 WaitAndEnterCriticalSection(&(categoryPointer->SpinLock), out sectionEntered); 771 772 int newCategoryOffset; 773 if (sectionEntered) 774 { 775 try 776 { 777 newCategoryOffset = CreateCategory(categoryPointer, instanceNameHashCode, instanceName, lifetime); 778 } 779 finally 780 { 781 if (!_categoryData.UseUniqueSharedMemory) 782 ExitCriticalSection(&(categoryPointer->SpinLock)); 783 } 784 785 categoryPointer = (CategoryEntry*)(ResolveOffset(newCategoryOffset, s_categoryEntrySize)); 786 instancePointer = (InstanceEntry*)(ResolveOffset(categoryPointer->FirstInstanceOffset, s_instanceEntrySize)); 787 counterFound = FindCounter(counterNameHashCode, counterName, instancePointer, &counterPointer); 788 Debug.Assert(counterFound, "All counters should be created, so we should always find the counter"); 789 return counterPointer; 790 } 791 } 792 793 bool foundFreeInstance; 794 while (!FindInstance(instanceNameHashCode, instanceName, categoryPointer, &instancePointer, true, lifetime, out foundFreeInstance)) 795 { 796 InstanceEntry* lockInstancePointer = instancePointer; 797 798 // don't bother locking again if we're using a separate shared memory. 799 bool sectionEntered; 800 if (_categoryData.UseUniqueSharedMemory) 801 sectionEntered = true; 802 else 803 WaitAndEnterCriticalSection(&(lockInstancePointer->SpinLock), out sectionEntered); 804 805 if (sectionEntered) 806 { 807 try 808 { 809 bool reused = false; 810 811 if (enableReuse && foundFreeInstance) 812 { 813 reused = TryReuseInstance(instanceNameHashCode, instanceName, categoryPointer, &instancePointer, lifetime, lockInstancePointer); 814 // 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 815 // we're looking for. 816 } 817 818 if (!reused) 819 { 820 int newInstanceOffset = CreateInstance(categoryPointer, instanceNameHashCode, instanceName, lifetime); 821 instancePointer = (InstanceEntry*)(ResolveOffset(newInstanceOffset, s_instanceEntrySize)); 822 823 counterFound = FindCounter(counterNameHashCode, counterName, instancePointer, &counterPointer); 824 Debug.Assert(counterFound, "All counters should be created, so we should always find the counter"); 825 return counterPointer; 826 } 827 } 828 finally 829 { 830 if (!_categoryData.UseUniqueSharedMemory) 831 ExitCriticalSection(&(lockInstancePointer->SpinLock)); 832 } 833 } 834 } 835 836 if (_categoryData.UseUniqueSharedMemory) 837 { 838 counterFound = FindCounter(counterNameHashCode, counterName, instancePointer, &counterPointer); 839 Debug.Assert(counterFound, "All counters should be created, so we should always find the counter"); 840 return counterPointer; 841 } 842 else 843 { 844 while (!FindCounter(counterNameHashCode, counterName, instancePointer, &counterPointer)) 845 { 846 bool sectionEntered; 847 WaitAndEnterCriticalSection(&(counterPointer->SpinLock), out sectionEntered); 848 849 if (sectionEntered) 850 { 851 try 852 { 853 int newCounterOffset = CreateCounter(counterPointer, counterNameHashCode, counterName); 854 return (CounterEntry*)(ResolveOffset(newCounterOffset, s_counterEntrySize)); 855 } 856 finally 857 { 858 ExitCriticalSection(&(counterPointer->SpinLock)); 859 } 860 } 861 } 862 863 return counterPointer; 864 } 865 } 866 finally 867 { 868 // cache this instance for reuse 869 try 870 { 871 if (counterPointer != null && instancePointer != null) 872 { 873 _thisInstanceOffset = ResolveAddress((long)instancePointer, s_instanceEntrySize); 874 } 875 } 876 catch (InvalidOperationException) 877 { 878 _thisInstanceOffset = -1; 879 } 880 881 if (mutex != null) 882 { 883 mutex.ReleaseMutex(); 884 mutex.Close(); 885 } 886 } 887 } 888 889 // 890 // FindCategory - 891 // 892 // * when the function returns true the returnCategoryPointerReference is set to the CategoryEntry 893 // that matches 'categoryNameHashCode' and 'categoryName' 894 // 895 // * when the function returns false the returnCategoryPointerReference is set to the last CategoryEntry 896 // in the linked list 897 // FindCategory(CategoryEntry** returnCategoryPointerReference)898 private unsafe bool FindCategory(CategoryEntry** returnCategoryPointerReference) 899 { 900 CategoryEntry* firstCategoryPointer = (CategoryEntry*)(ResolveOffset(_initialOffset, s_categoryEntrySize)); 901 CategoryEntry* currentCategoryPointer = firstCategoryPointer; 902 CategoryEntry* previousCategoryPointer = firstCategoryPointer; 903 904 for (; ; ) 905 { 906 if (currentCategoryPointer->IsConsistent == 0) 907 Verify(currentCategoryPointer); 908 909 if (currentCategoryPointer->CategoryNameHashCode == _categoryNameHashCode) 910 { 911 if (StringEquals(_categoryName, currentCategoryPointer->CategoryNameOffset)) 912 { 913 *returnCategoryPointerReference = currentCategoryPointer; 914 return true; 915 } 916 } 917 918 previousCategoryPointer = currentCategoryPointer; 919 if (currentCategoryPointer->NextCategoryOffset != 0) 920 currentCategoryPointer = (CategoryEntry*)(ResolveOffset(currentCategoryPointer->NextCategoryOffset, s_categoryEntrySize)); 921 else 922 { 923 *returnCategoryPointerReference = previousCategoryPointer; 924 return false; 925 } 926 } 927 } 928 FindCounter(int counterNameHashCode, string counterName, InstanceEntry* instancePointer, CounterEntry** returnCounterPointerReference)929 private unsafe bool FindCounter(int counterNameHashCode, string counterName, InstanceEntry* instancePointer, CounterEntry** returnCounterPointerReference) 930 { 931 CounterEntry* currentCounterPointer = (CounterEntry*)(ResolveOffset(instancePointer->FirstCounterOffset, s_counterEntrySize)); 932 CounterEntry* previousCounterPointer = currentCounterPointer; 933 for (; ; ) 934 { 935 if (currentCounterPointer->CounterNameHashCode == counterNameHashCode) 936 { 937 if (StringEquals(counterName, currentCounterPointer->CounterNameOffset)) 938 { 939 *returnCounterPointerReference = currentCounterPointer; 940 return true; 941 } 942 } 943 944 previousCounterPointer = currentCounterPointer; 945 if (currentCounterPointer->NextCounterOffset != 0) 946 currentCounterPointer = (CounterEntry*)(ResolveOffset(currentCounterPointer->NextCounterOffset, s_counterEntrySize)); 947 else 948 { 949 *returnCounterPointerReference = previousCounterPointer; 950 return false; 951 } 952 } 953 } 954 FindInstance(int instanceNameHashCode, string instanceName, CategoryEntry* categoryPointer, InstanceEntry** returnInstancePointerReference, bool activateUnusedInstances, PerformanceCounterInstanceLifetime lifetime, out bool foundFreeInstance)955 private unsafe bool FindInstance(int instanceNameHashCode, string instanceName, 956 CategoryEntry* categoryPointer, InstanceEntry** returnInstancePointerReference, 957 bool activateUnusedInstances, PerformanceCounterInstanceLifetime lifetime, 958 out bool foundFreeInstance) 959 { 960 961 InstanceEntry* currentInstancePointer = (InstanceEntry*)(ResolveOffset(categoryPointer->FirstInstanceOffset, s_instanceEntrySize)); 962 InstanceEntry* previousInstancePointer = currentInstancePointer; 963 foundFreeInstance = false; 964 // Look at the first instance to determine if this is single or multi instance. 965 if (currentInstancePointer->InstanceNameHashCode == s_singleInstanceHashCode) 966 { 967 if (StringEquals(SingleInstanceName, currentInstancePointer->InstanceNameOffset)) 968 { 969 if (instanceName != SingleInstanceName) 970 throw new InvalidOperationException(SR.Format(SR.SingleInstanceOnly, _categoryName)); 971 } 972 else 973 { 974 if (instanceName == SingleInstanceName) 975 throw new InvalidOperationException(SR.Format(SR.MultiInstanceOnly, _categoryName)); 976 } 977 } 978 else 979 { 980 if (instanceName == SingleInstanceName) 981 throw new InvalidOperationException(SR.Format(SR.MultiInstanceOnly, _categoryName)); 982 } 983 984 // 985 // 1st pass find exact matching! 986 // 987 // We don't need to aggressively claim unused instances. For performance, we would proactively 988 // verify lifetime of instances if activateUnusedInstances is specified and certain time 989 // has elapsed since last sweep or we are running out of shared memory. 990 bool verifyLifeTime = activateUnusedInstances; 991 if (activateUnusedInstances) 992 { 993 994 int totalSize = s_instanceEntrySize + s_processLifetimeEntrySize + InstanceNameSlotSize + (s_counterEntrySize * _categoryData.CounterNames.Count); 995 int freeMemoryOffset = *((int*)_baseAddress); 996 int alignmentAdjustment; 997 int newOffset = CalculateMemoryNoBoundsCheck(freeMemoryOffset, totalSize, out alignmentAdjustment); 998 999 if (!(newOffset > FileView._fileMappingSize || newOffset < 0)) 1000 { 1001 long tickDelta = (DateTime.Now.Ticks - Volatile.Read(ref s_lastInstanceLifetimeSweepTick)); 1002 if (tickDelta < InstanceLifetimeSweepWindow) 1003 verifyLifeTime = false; 1004 } 1005 } 1006 1007 try 1008 { 1009 for (; ; ) 1010 { 1011 bool verifiedLifetimeOfThisInstance = false; 1012 if (verifyLifeTime && (currentInstancePointer->RefCount != 0)) 1013 { 1014 verifiedLifetimeOfThisInstance = true; 1015 VerifyLifetime(currentInstancePointer); 1016 } 1017 1018 if (currentInstancePointer->InstanceNameHashCode == instanceNameHashCode) 1019 { 1020 if (StringEquals(instanceName, currentInstancePointer->InstanceNameOffset)) 1021 { 1022 // we found a matching instance. 1023 *returnInstancePointerReference = currentInstancePointer; 1024 1025 CounterEntry* firstCounter = (CounterEntry*)ResolveOffset(currentInstancePointer->FirstCounterOffset, s_counterEntrySize); 1026 ProcessLifetimeEntry* lifetimeEntry; 1027 if (_categoryData.UseUniqueSharedMemory) 1028 lifetimeEntry = (ProcessLifetimeEntry*)ResolveOffset(firstCounter->LifetimeOffset, s_processLifetimeEntrySize); 1029 else 1030 lifetimeEntry = null; 1031 1032 // ensure that we have verified the lifetime of the matched instance 1033 if (!verifiedLifetimeOfThisInstance && currentInstancePointer->RefCount != 0) 1034 VerifyLifetime(currentInstancePointer); 1035 1036 if (currentInstancePointer->RefCount != 0) 1037 { 1038 if (lifetimeEntry != null && lifetimeEntry->ProcessId != 0) 1039 { 1040 if (lifetime != PerformanceCounterInstanceLifetime.Process) 1041 throw new InvalidOperationException(SR.Format(SR.CantConvertProcessToGlobal)); 1042 1043 // make sure only one process is using this instance. 1044 if (ProcessData.ProcessId != lifetimeEntry->ProcessId) 1045 throw new InvalidOperationException(SR.Format(SR.InstanceAlreadyExists, instanceName)); 1046 1047 // compare start time of the process, account for ACL issues in querying process information 1048 if ((lifetimeEntry->StartupTime != -1) && (ProcessData.StartupTime != -1)) 1049 { 1050 if (ProcessData.StartupTime != lifetimeEntry->StartupTime) 1051 throw new InvalidOperationException(SR.Format(SR.InstanceAlreadyExists, instanceName)); 1052 } 1053 } 1054 else 1055 { 1056 if (lifetime == PerformanceCounterInstanceLifetime.Process) 1057 throw new InvalidOperationException(SR.Format(SR.CantConvertGlobalToProcess)); 1058 } 1059 return true; 1060 } 1061 1062 if (activateUnusedInstances) 1063 { 1064 Mutex mutex = null; 1065 try 1066 { 1067 SharedUtils.EnterMutexWithoutGlobal(_categoryData.MutexName, ref mutex); 1068 ClearCounterValues(currentInstancePointer); 1069 if (lifetimeEntry != null) 1070 PopulateLifetimeEntry(lifetimeEntry, lifetime); 1071 1072 currentInstancePointer->RefCount = 1; 1073 return true; 1074 } 1075 finally 1076 { 1077 if (mutex != null) 1078 { 1079 mutex.ReleaseMutex(); 1080 mutex.Close(); 1081 } 1082 } 1083 } 1084 else 1085 return false; 1086 } 1087 } 1088 1089 if (currentInstancePointer->RefCount == 0) 1090 { 1091 foundFreeInstance = true; 1092 } 1093 1094 previousInstancePointer = currentInstancePointer; 1095 if (currentInstancePointer->NextInstanceOffset != 0) 1096 currentInstancePointer = (InstanceEntry*)(ResolveOffset(currentInstancePointer->NextInstanceOffset, s_instanceEntrySize)); 1097 else 1098 { 1099 *returnInstancePointerReference = previousInstancePointer; 1100 return false; 1101 } 1102 } 1103 } 1104 finally 1105 { 1106 if (verifyLifeTime) 1107 Volatile.Write(ref s_lastInstanceLifetimeSweepTick, DateTime.Now.Ticks); 1108 } 1109 } 1110 TryReuseInstance(int instanceNameHashCode, string instanceName, CategoryEntry* categoryPointer, InstanceEntry** returnInstancePointerReference, PerformanceCounterInstanceLifetime lifetime, InstanceEntry* lockInstancePointer)1111 private unsafe bool TryReuseInstance(int instanceNameHashCode, string instanceName, 1112 CategoryEntry* categoryPointer, InstanceEntry** returnInstancePointerReference, 1113 PerformanceCounterInstanceLifetime lifetime, 1114 InstanceEntry* lockInstancePointer) 1115 { 1116 // 2nd pass find a free instance slot 1117 InstanceEntry* currentInstancePointer = (InstanceEntry*)(ResolveOffset(categoryPointer->FirstInstanceOffset, s_instanceEntrySize)); 1118 InstanceEntry* previousInstancePointer = currentInstancePointer; 1119 for (; ; ) 1120 { 1121 if (currentInstancePointer->RefCount == 0) 1122 { 1123 1124 bool hasFit; 1125 long instanceNamePtr; // we need cache this to avoid race conditions. 1126 1127 if (_categoryData.UseUniqueSharedMemory) 1128 { 1129 instanceNamePtr = ResolveOffset(currentInstancePointer->InstanceNameOffset, InstanceNameSlotSize); 1130 // In the separate shared memory case we should always have enough space for instances. The 1131 // name slot size is fixed. 1132 Debug.Assert(((instanceName.Length + 1) * 2) <= InstanceNameSlotSize, "The instance name length should always fit in our slot size"); 1133 hasFit = true; 1134 } 1135 else 1136 { 1137 // we don't know the string length yet. 1138 instanceNamePtr = ResolveOffset(currentInstancePointer->InstanceNameOffset, 0); 1139 1140 // In the global shared memory, we require names to be exactly the same length in order 1141 // to reuse them. This way we don't end up leaking any space and we don't need to 1142 // depend on the layout of the memory to calculate the space we have. 1143 int length = GetStringLength((char*)instanceNamePtr); 1144 hasFit = (length == instanceName.Length); 1145 } 1146 1147 bool noSpinLock = (lockInstancePointer == currentInstancePointer) || _categoryData.UseUniqueSharedMemory; 1148 // Instance name fit 1149 if (hasFit) 1150 { 1151 // don't bother locking again if we're using a separate shared memory. 1152 bool sectionEntered; 1153 if (noSpinLock) 1154 sectionEntered = true; 1155 else 1156 WaitAndEnterCriticalSection(&(currentInstancePointer->SpinLock), out sectionEntered); 1157 1158 if (sectionEntered) 1159 { 1160 try 1161 { 1162 // Make copy with zero-term 1163 SafeMarshalCopy(instanceName, (IntPtr)instanceNamePtr); 1164 currentInstancePointer->InstanceNameHashCode = instanceNameHashCode; 1165 1166 // return 1167 *returnInstancePointerReference = currentInstancePointer; 1168 // clear the counter values. 1169 ClearCounterValues(*returnInstancePointerReference); 1170 1171 if (_categoryData.UseUniqueSharedMemory) 1172 { 1173 CounterEntry* counterPointer = (CounterEntry*)ResolveOffset(currentInstancePointer->FirstCounterOffset, s_counterEntrySize); 1174 ProcessLifetimeEntry* lifetimeEntry = (ProcessLifetimeEntry*)ResolveOffset(counterPointer->LifetimeOffset, s_processLifetimeEntrySize); 1175 PopulateLifetimeEntry(lifetimeEntry, lifetime); 1176 } 1177 1178 (*returnInstancePointerReference)->RefCount = 1; 1179 return true; 1180 } 1181 finally 1182 { 1183 if (!noSpinLock) 1184 ExitCriticalSection(&(currentInstancePointer->SpinLock)); 1185 } 1186 } 1187 } 1188 } 1189 1190 previousInstancePointer = currentInstancePointer; 1191 if (currentInstancePointer->NextInstanceOffset != 0) 1192 currentInstancePointer = (InstanceEntry*)(ResolveOffset(currentInstancePointer->NextInstanceOffset, s_instanceEntrySize)); 1193 else 1194 { 1195 *returnInstancePointerReference = previousInstancePointer; 1196 return false; 1197 } 1198 } 1199 } 1200 Verify(CategoryEntry* currentCategoryPointer)1201 private unsafe void Verify(CategoryEntry* currentCategoryPointer) 1202 { 1203 if (!_categoryData.UseUniqueSharedMemory) 1204 return; 1205 1206 Mutex mutex = null; 1207 try 1208 { 1209 SharedUtils.EnterMutexWithoutGlobal(_categoryData.MutexName, ref mutex); 1210 VerifyCategory(currentCategoryPointer); 1211 } 1212 finally 1213 { 1214 if (mutex != null) 1215 { 1216 mutex.ReleaseMutex(); 1217 mutex.Close(); 1218 } 1219 } 1220 } 1221 VerifyCategory(CategoryEntry* currentCategoryPointer)1222 private unsafe void VerifyCategory(CategoryEntry* currentCategoryPointer) 1223 { 1224 int freeOffset = *((int*)_baseAddress); 1225 ResolveOffset(freeOffset, 0); // verify next free offset 1226 1227 // begin by verifying the head node's offset 1228 int currentOffset = ResolveAddress((long)currentCategoryPointer, s_categoryEntrySize); 1229 if (currentOffset >= freeOffset) 1230 { 1231 // zero out the bad head node entry 1232 currentCategoryPointer->SpinLock = 0; 1233 currentCategoryPointer->CategoryNameHashCode = 0; 1234 currentCategoryPointer->CategoryNameOffset = 0; 1235 currentCategoryPointer->FirstInstanceOffset = 0; 1236 currentCategoryPointer->NextCategoryOffset = 0; 1237 currentCategoryPointer->IsConsistent = 0; 1238 return; 1239 } 1240 1241 if (currentCategoryPointer->NextCategoryOffset > freeOffset) 1242 currentCategoryPointer->NextCategoryOffset = 0; 1243 else if (currentCategoryPointer->NextCategoryOffset != 0) 1244 VerifyCategory((CategoryEntry*)ResolveOffset(currentCategoryPointer->NextCategoryOffset, s_categoryEntrySize)); 1245 1246 if (currentCategoryPointer->FirstInstanceOffset != 0) 1247 { 1248 // Check whether the recently added instance at the head of the list is committed. If not, rewire 1249 // the head of the list to point to the next instance 1250 if (currentCategoryPointer->FirstInstanceOffset > freeOffset) 1251 { 1252 InstanceEntry* currentInstancePointer = (InstanceEntry*)ResolveOffset(currentCategoryPointer->FirstInstanceOffset, s_instanceEntrySize); 1253 currentCategoryPointer->FirstInstanceOffset = currentInstancePointer->NextInstanceOffset; 1254 if (currentCategoryPointer->FirstInstanceOffset > freeOffset) 1255 currentCategoryPointer->FirstInstanceOffset = 0; 1256 } 1257 1258 if (currentCategoryPointer->FirstInstanceOffset != 0) 1259 { 1260 Debug.Assert(currentCategoryPointer->FirstInstanceOffset <= freeOffset, "The head of the list is inconsistent - possible mismatch of V2 & V3 instances?"); 1261 VerifyInstance((InstanceEntry*)ResolveOffset(currentCategoryPointer->FirstInstanceOffset, s_instanceEntrySize)); 1262 } 1263 } 1264 1265 currentCategoryPointer->IsConsistent = 1; 1266 } 1267 VerifyInstance(InstanceEntry* currentInstancePointer)1268 private unsafe void VerifyInstance(InstanceEntry* currentInstancePointer) 1269 { 1270 int freeOffset = *((int*)_baseAddress); 1271 ResolveOffset(freeOffset, 0); // verify next free offset 1272 1273 if (currentInstancePointer->NextInstanceOffset > freeOffset) 1274 currentInstancePointer->NextInstanceOffset = 0; 1275 else if (currentInstancePointer->NextInstanceOffset != 0) 1276 VerifyInstance((InstanceEntry*)ResolveOffset(currentInstancePointer->NextInstanceOffset, s_instanceEntrySize)); 1277 } 1278 VerifyLifetime(InstanceEntry* currentInstancePointer)1279 private unsafe void VerifyLifetime(InstanceEntry* currentInstancePointer) 1280 { 1281 Debug.Assert(currentInstancePointer->RefCount != 0, "RefCount must be 1 for instances passed to VerifyLifetime"); 1282 1283 CounterEntry* counter = (CounterEntry*)ResolveOffset(currentInstancePointer->FirstCounterOffset, s_counterEntrySize); 1284 if (counter->LifetimeOffset != 0) 1285 { 1286 ProcessLifetimeEntry* lifetime = (ProcessLifetimeEntry*)ResolveOffset(counter->LifetimeOffset, s_processLifetimeEntrySize); 1287 if (lifetime->LifetimeType == (int)PerformanceCounterInstanceLifetime.Process) 1288 { 1289 int pid = lifetime->ProcessId; 1290 long startTime = lifetime->StartupTime; 1291 1292 if (pid != 0) 1293 { 1294 1295 // Optimize for this process 1296 if (pid == ProcessData.ProcessId) 1297 { 1298 if ((ProcessData.StartupTime != -1) && (startTime != -1) && (ProcessData.StartupTime != startTime)) 1299 { 1300 // Process id got recycled. Reclaim this instance. 1301 currentInstancePointer->RefCount = 0; 1302 return; 1303 } 1304 } 1305 else 1306 { 1307 long processStartTime; 1308 using (SafeProcessHandle procHandle = Interop.Kernel32.OpenProcess(Interop.Advapi32.ProcessOptions.PROCESS_QUERY_INFORMATION, false, pid)) 1309 { 1310 int error = Marshal.GetLastWin32Error(); 1311 if ((error == Interop.Errors.ERROR_INVALID_PARAMETER) && procHandle.IsInvalid) 1312 { 1313 // The process is dead. Reclaim this instance. Note that we only clear the refcount here. 1314 // If we tried to clear the pid and startup time as well, we would have a race where 1315 // we could clear the pid/startup time but not the refcount. 1316 currentInstancePointer->RefCount = 0; 1317 return; 1318 } 1319 1320 // Defer cleaning the instance when we had previously encountered errors in 1321 // recording process start time (i.e, when startTime == -1) until after the 1322 // process id is not valid (which will be caught in the if check above) 1323 if (!procHandle.IsInvalid && startTime != -1) 1324 { 1325 long temp; 1326 if (Interop.Kernel32.GetProcessTimes(procHandle, out processStartTime, out temp, out temp, out temp)) 1327 { 1328 if (processStartTime != startTime) 1329 { 1330 // The process is dead but a new one is using the same pid. Reclaim this instance. 1331 currentInstancePointer->RefCount = 0; 1332 return; 1333 } 1334 } 1335 } 1336 } 1337 1338 // Check to see if the process handle has been signaled by the kernel. If this is the case then it's safe 1339 // to reclaim the instance as the process is in the process of exiting. 1340 using (SafeProcessHandle procHandle = Interop.Kernel32.OpenProcess(Interop.Advapi32.ProcessOptions.SYNCHRONIZE, false, pid)) 1341 { 1342 if (!procHandle.IsInvalid) 1343 { 1344 using (Interop.Kernel32.ProcessWaitHandle wh = new Interop.Kernel32.ProcessWaitHandle(procHandle)) 1345 { 1346 if (wh.WaitOne(0, false)) 1347 { 1348 // Process has exited 1349 currentInstancePointer->RefCount = 0; 1350 return; 1351 } 1352 } 1353 } 1354 } 1355 } 1356 } 1357 1358 } 1359 } 1360 } 1361 IncrementBy(long value)1362 internal unsafe long IncrementBy(long value) 1363 { 1364 if (_counterEntryPointer == null) 1365 return 0; 1366 1367 CounterEntry* counterEntry = _counterEntryPointer; 1368 1369 return AddToValue(counterEntry, value); 1370 } 1371 Increment()1372 internal unsafe long Increment() 1373 { 1374 if (_counterEntryPointer == null) 1375 return 0; 1376 1377 return IncrementUnaligned(_counterEntryPointer); 1378 } 1379 Decrement()1380 internal unsafe long Decrement() 1381 { 1382 if (_counterEntryPointer == null) 1383 return 0; 1384 1385 return DecrementUnaligned(_counterEntryPointer); 1386 } 1387 RemoveAllInstances(string categoryName)1388 internal unsafe static void RemoveAllInstances(string categoryName) 1389 { 1390 SharedPerformanceCounter spc = new SharedPerformanceCounter(categoryName, null, null); 1391 spc.RemoveAllInstances(); 1392 RemoveCategoryData(categoryName); 1393 } 1394 RemoveAllInstances()1395 private unsafe void RemoveAllInstances() 1396 { 1397 CategoryEntry* categoryPointer; 1398 if (!FindCategory(&categoryPointer)) 1399 return; 1400 1401 InstanceEntry* instancePointer = (InstanceEntry*)(ResolveOffset(categoryPointer->FirstInstanceOffset, s_instanceEntrySize)); 1402 1403 Mutex mutex = null; 1404 try 1405 { 1406 SharedUtils.EnterMutexWithoutGlobal(_categoryData.MutexName, ref mutex); 1407 for (; ; ) 1408 { 1409 RemoveOneInstance(instancePointer, true); 1410 1411 if (instancePointer->NextInstanceOffset != 0) 1412 instancePointer = (InstanceEntry*)(ResolveOffset(instancePointer->NextInstanceOffset, s_instanceEntrySize)); 1413 else 1414 { 1415 break; 1416 } 1417 } 1418 } 1419 finally 1420 { 1421 if (mutex != null) 1422 { 1423 mutex.ReleaseMutex(); 1424 mutex.Close(); 1425 } 1426 } 1427 } 1428 RemoveInstance(string instanceName, PerformanceCounterInstanceLifetime instanceLifetime)1429 internal unsafe void RemoveInstance(string instanceName, PerformanceCounterInstanceLifetime instanceLifetime) 1430 { 1431 if (instanceName == null || instanceName.Length == 0) 1432 return; 1433 1434 int instanceNameHashCode = GetWstrHashCode(instanceName); 1435 1436 CategoryEntry* categoryPointer; 1437 if (!FindCategory(&categoryPointer)) 1438 return; 1439 1440 InstanceEntry* instancePointer = null; 1441 bool validatedCachedInstancePointer = false; 1442 bool temp; 1443 1444 Mutex mutex = null; 1445 try 1446 { 1447 SharedUtils.EnterMutexWithoutGlobal(_categoryData.MutexName, ref mutex); 1448 1449 if (_thisInstanceOffset != -1) 1450 { 1451 try 1452 { 1453 // validate whether the cached instance pointer is pointing at the right instance 1454 instancePointer = (InstanceEntry*)(ResolveOffset(_thisInstanceOffset, s_instanceEntrySize)); 1455 if (instancePointer->InstanceNameHashCode == instanceNameHashCode) 1456 { 1457 if (StringEquals(instanceName, instancePointer->InstanceNameOffset)) 1458 { 1459 validatedCachedInstancePointer = true; 1460 1461 CounterEntry* firstCounter = (CounterEntry*)ResolveOffset(instancePointer->FirstCounterOffset, s_counterEntrySize); 1462 ProcessLifetimeEntry* lifetimeEntry; 1463 if (_categoryData.UseUniqueSharedMemory) 1464 { 1465 lifetimeEntry = (ProcessLifetimeEntry*)ResolveOffset(firstCounter->LifetimeOffset, s_processLifetimeEntrySize); 1466 if (lifetimeEntry != null 1467 && lifetimeEntry->LifetimeType == (int)PerformanceCounterInstanceLifetime.Process 1468 && lifetimeEntry->ProcessId != 0) 1469 { 1470 validatedCachedInstancePointer &= (instanceLifetime == PerformanceCounterInstanceLifetime.Process); 1471 validatedCachedInstancePointer &= (ProcessData.ProcessId == lifetimeEntry->ProcessId); 1472 if ((lifetimeEntry->StartupTime != -1) && (ProcessData.StartupTime != -1)) 1473 validatedCachedInstancePointer &= (ProcessData.StartupTime == lifetimeEntry->StartupTime); 1474 } 1475 else 1476 validatedCachedInstancePointer &= (instanceLifetime != PerformanceCounterInstanceLifetime.Process); 1477 } 1478 } 1479 } 1480 } 1481 catch (InvalidOperationException) 1482 { 1483 validatedCachedInstancePointer = false; 1484 } 1485 if (!validatedCachedInstancePointer) 1486 _thisInstanceOffset = -1; 1487 } 1488 1489 if (!validatedCachedInstancePointer && !FindInstance(instanceNameHashCode, instanceName, categoryPointer, &instancePointer, false, instanceLifetime, out temp)) 1490 return; 1491 1492 if (instancePointer != null) 1493 RemoveOneInstance(instancePointer, false); 1494 } 1495 finally 1496 { 1497 if (mutex != null) 1498 { 1499 mutex.ReleaseMutex(); 1500 mutex.Close(); 1501 } 1502 } 1503 } 1504 RemoveOneInstance(InstanceEntry* instancePointer, bool clearValue)1505 private unsafe void RemoveOneInstance(InstanceEntry* instancePointer, bool clearValue) 1506 { 1507 bool sectionEntered = false; 1508 1509 try 1510 { 1511 if (!_categoryData.UseUniqueSharedMemory) 1512 { 1513 while (!sectionEntered) 1514 { 1515 WaitAndEnterCriticalSection(&(instancePointer->SpinLock), out sectionEntered); 1516 } 1517 } 1518 1519 instancePointer->RefCount = 0; 1520 1521 if (clearValue) 1522 ClearCounterValues(instancePointer); 1523 } 1524 finally 1525 { 1526 if (sectionEntered) 1527 ExitCriticalSection(&(instancePointer->SpinLock)); 1528 } 1529 } 1530 ClearCounterValues(InstanceEntry* instancePointer)1531 private unsafe void ClearCounterValues(InstanceEntry* instancePointer) 1532 { 1533 //Clear counter instance values 1534 CounterEntry* currentCounterPointer = null; 1535 1536 if (instancePointer->FirstCounterOffset != 0) 1537 currentCounterPointer = (CounterEntry*)(ResolveOffset(instancePointer->FirstCounterOffset, s_counterEntrySize)); 1538 1539 while (currentCounterPointer != null) 1540 { 1541 SetValue(currentCounterPointer, 0); 1542 1543 if (currentCounterPointer->NextCounterOffset != 0) 1544 currentCounterPointer = (CounterEntry*)(ResolveOffset(currentCounterPointer->NextCounterOffset, s_counterEntrySize)); 1545 else 1546 currentCounterPointer = null; 1547 } 1548 1549 } 1550 AddToValue(CounterEntry* counterEntry, long addend)1551 private static unsafe long AddToValue(CounterEntry* counterEntry, long addend) 1552 { 1553 // Called while holding a lock - shouldn't have to worry about 1554 // reading misaligned data & getting old vs. new parts of an Int64. 1555 if (IsMisaligned(counterEntry)) 1556 { 1557 ulong newvalue; 1558 1559 CounterEntryMisaligned* entry = (CounterEntryMisaligned*)counterEntry; 1560 newvalue = (uint)entry->Value_hi; 1561 newvalue <<= 32; 1562 newvalue |= (uint)entry->Value_lo; 1563 1564 1565 newvalue = (ulong)((long)newvalue + addend); 1566 1567 entry->Value_hi = (int)(newvalue >> 32); 1568 entry->Value_lo = (int)(newvalue & 0xffffffff); 1569 1570 return (long)newvalue; 1571 } 1572 else 1573 return Interlocked.Add(ref counterEntry->Value, addend); 1574 } 1575 DecrementUnaligned(CounterEntry* counterEntry)1576 private static unsafe long DecrementUnaligned(CounterEntry* counterEntry) 1577 { 1578 if (IsMisaligned(counterEntry)) 1579 return AddToValue(counterEntry, -1); 1580 else 1581 return Interlocked.Decrement(ref counterEntry->Value); 1582 } 1583 GetValue(CounterEntry* counterEntry)1584 private static unsafe long GetValue(CounterEntry* counterEntry) 1585 { 1586 if (IsMisaligned(counterEntry)) 1587 { 1588 ulong value; 1589 CounterEntryMisaligned* entry = (CounterEntryMisaligned*)counterEntry; 1590 value = (uint)entry->Value_hi; 1591 value <<= 32; 1592 value |= (uint)entry->Value_lo; 1593 1594 return (long)value; 1595 } 1596 else 1597 return counterEntry->Value; 1598 } 1599 IncrementUnaligned(CounterEntry* counterEntry)1600 private static unsafe long IncrementUnaligned(CounterEntry* counterEntry) 1601 { 1602 if (IsMisaligned(counterEntry)) 1603 return AddToValue(counterEntry, 1); 1604 else 1605 return Interlocked.Increment(ref counterEntry->Value); 1606 } 1607 SetValue(CounterEntry* counterEntry, long value)1608 private static unsafe void SetValue(CounterEntry* counterEntry, long value) 1609 { 1610 if (IsMisaligned(counterEntry)) 1611 { 1612 CounterEntryMisaligned* entry = (CounterEntryMisaligned*)counterEntry; 1613 entry->Value_lo = (int)(value & 0xffffffff); 1614 entry->Value_hi = (int)(value >> 32); 1615 } 1616 else 1617 counterEntry->Value = value; 1618 } 1619 IsMisaligned(CounterEntry* counterEntry)1620 private static unsafe bool IsMisaligned(CounterEntry* counterEntry) 1621 { 1622 return (((Int64)counterEntry & 0x7) != 0); 1623 } 1624 ResolveOffset(int offset, int sizeToRead)1625 private long ResolveOffset(int offset, int sizeToRead) 1626 { 1627 //It is very important to check the integrity of the shared memory 1628 //everytime a new address is resolved. 1629 if (offset > (FileView._fileMappingSize - sizeToRead) || offset < 0) 1630 throw new InvalidOperationException(SR.Format(SR.MappingCorrupted)); 1631 1632 long address = _baseAddress + offset; 1633 1634 return address; 1635 } 1636 ResolveAddress(long address, int sizeToRead)1637 private int ResolveAddress(long address, int sizeToRead) 1638 { 1639 int offset = (int)(address - _baseAddress); 1640 1641 //It is very important to check the integrity of the shared memory 1642 //everytime a new address is resolved. 1643 if (offset > (FileView._fileMappingSize - sizeToRead) || offset < 0) 1644 throw new InvalidOperationException(SR.Format(SR.MappingCorrupted)); 1645 1646 return offset; 1647 } 1648 1649 private class FileMapping 1650 { 1651 internal int _fileMappingSize; 1652 private SafeMemoryMappedViewHandle _fileViewAddress = null; 1653 private SafeMemoryMappedFileHandle _fileMappingHandle = null; 1654 //The version of the file mapping name is independent from the 1655 //assembly version. 1656 FileMapping(string fileMappingName, int fileMappingSize, int initialOffset)1657 public FileMapping(string fileMappingName, int fileMappingSize, int initialOffset) 1658 { 1659 Initialize(fileMappingName, fileMappingSize, initialOffset); 1660 } 1661 1662 internal IntPtr FileViewAddress 1663 { 1664 get 1665 { 1666 if (_fileViewAddress.IsInvalid) 1667 throw new InvalidOperationException(SR.Format(SR.SharedMemoryGhosted)); 1668 1669 return _fileViewAddress.DangerousGetHandle(); 1670 } 1671 } 1672 Initialize(string fileMappingName, int fileMappingSize, int initialOffset)1673 private unsafe void Initialize(string fileMappingName, int fileMappingSize, int initialOffset) 1674 { 1675 string mappingName = fileMappingName; 1676 1677 SafeLocalMemHandle securityDescriptorPointer = null; 1678 try 1679 { 1680 // The sddl string consists of these parts: 1681 // D: it's a DACL 1682 // (A; this is an allow ACE 1683 // OICI; object inherit and container inherit 1684 // FRFWGRGW;;; allow file read, file write, generic read and generic write 1685 // AU) granted to Authenticated Users 1686 // ;S-1-5-33) the same permission granted to AU is also granted to restricted services 1687 string sddlString = "D:(A;OICI;FRFWGRGW;;;AU)(A;OICI;FRFWGRGW;;;S-1-5-33)"; 1688 1689 if (!Interop.Advapi32.ConvertStringSecurityDescriptorToSecurityDescriptor(sddlString, Interop.Kernel32.PerformanceCounterOptions.SDDL_REVISION_1, 1690 out securityDescriptorPointer, IntPtr.Zero)) 1691 throw new InvalidOperationException(SR.Format(SR.SetSecurityDescriptorFailed)); 1692 1693 Interop.Kernel32.SECURITY_ATTRIBUTES securityAttributes = new Interop.Kernel32.SECURITY_ATTRIBUTES(); 1694 securityAttributes.bInheritHandle = Interop.BOOL.FALSE; 1695 1696 // 1697 // 1698 // Here we call CreateFileMapping to create the memory mapped file. When CreateFileMapping fails 1699 // with ERROR_ACCESS_DENIED, we know the file mapping has been created and we then open it with OpenFileMapping. 1700 // 1701 // There is chance of a race condition between CreateFileMapping and OpenFileMapping; The memory mapped file 1702 // may actually be closed in between these two calls. When this happens, OpenFileMapping returns ERROR_FILE_NOT_FOUND. 1703 // In this case, we need to loop back and retry creating the memory mapped file. 1704 // 1705 // This loop will timeout in approximately 1.4 minutes. An InvalidOperationException is thrown in the timeout case. 1706 // 1707 // 1708 int waitRetries = 14; //((2^13)-1)*10ms == approximately 1.4mins 1709 int waitSleep = 0; 1710 bool created = false; 1711 while (!created && waitRetries > 0) 1712 { 1713 _fileMappingHandle = Interop.Kernel32.CreateFileMapping((IntPtr)(-1), ref securityAttributes, 1714 Interop.Kernel32.PageOptions.PAGE_READWRITE, 0, fileMappingSize, mappingName); 1715 1716 if ((Marshal.GetLastWin32Error() != Interop.Errors.ERROR_ACCESS_DENIED) || !_fileMappingHandle.IsInvalid) 1717 { 1718 created = true; 1719 } 1720 else 1721 { 1722 // Invalidate the old safehandle before we get rid of it. This prevents it from trying to finalize 1723 _fileMappingHandle.SetHandleAsInvalid(); 1724 _fileMappingHandle = Interop.Kernel32.OpenFileMapping(Interop.Kernel32.FileMapOptions.FILE_MAP_WRITE, false, mappingName); 1725 1726 if ((Marshal.GetLastWin32Error() != Interop.Errors.ERROR_FILE_NOT_FOUND) || !_fileMappingHandle.IsInvalid) 1727 { 1728 created = true; 1729 } 1730 else 1731 { 1732 --waitRetries; 1733 if (waitSleep == 0) 1734 { 1735 waitSleep = 10; 1736 } 1737 else 1738 { 1739 System.Threading.Thread.Sleep(waitSleep); 1740 waitSleep *= 2; 1741 } 1742 } 1743 } 1744 } 1745 if (_fileMappingHandle.IsInvalid) 1746 { 1747 throw new InvalidOperationException(SR.Format(SR.CantCreateFileMapping)); 1748 } 1749 1750 _fileViewAddress = Interop.Kernel32.MapViewOfFile(_fileMappingHandle, Interop.Kernel32.FileMapOptions.FILE_MAP_WRITE, 0, 0, UIntPtr.Zero); 1751 if (_fileViewAddress.IsInvalid) 1752 throw new InvalidOperationException(SR.Format(SR.CantMapFileView)); 1753 1754 // figure out what size the share memory really is. 1755 Interop.Kernel32.MEMORY_BASIC_INFORMATION meminfo = new Interop.Kernel32.MEMORY_BASIC_INFORMATION(); 1756 if (Interop.Kernel32.VirtualQuery(_fileViewAddress, ref meminfo, (UIntPtr)sizeof(Interop.Kernel32.MEMORY_BASIC_INFORMATION)) == UIntPtr.Zero) 1757 throw new InvalidOperationException(SR.Format(SR.CantGetMappingSize)); 1758 1759 _fileMappingSize = (int)meminfo.RegionSize; 1760 } 1761 finally 1762 { 1763 if (securityDescriptorPointer != null) 1764 securityDescriptorPointer.Close(); 1765 } 1766 1767 Interlocked.CompareExchange(ref *(int*)_fileViewAddress.DangerousGetHandle().ToPointer(), initialOffset, 0); 1768 } 1769 1770 } 1771 1772 // SafeMarshalCopy always null terminates the char array 1773 // before copying it to native memory 1774 // SafeMarshalCopy(string str, IntPtr nativePointer)1775 private static void SafeMarshalCopy(string str, IntPtr nativePointer) 1776 { 1777 // convert str to a char array and copy it to the unmanaged memory pointer 1778 char[] tmp = new char[str.Length + 1]; 1779 str.CopyTo(0, tmp, 0, str.Length); 1780 tmp[str.Length] = '\0'; // make sure the char[] is null terminated 1781 Marshal.Copy(tmp, 0, nativePointer, tmp.Length); 1782 } 1783 1784 // <WARNING> 1785 // The final tmpPadding field is needed to make the size of this structure 8-byte aligned. This is 1786 // necessary on IA64. 1787 // </WARNING> 1788 // Note that in V1.0 and v1.1 there was no explicit padding defined on any of these structs. That means that 1789 // sizeof(CategoryEntry) or Marshal.SizeOf(typeof(CategoryEntry)) returned 4 bytes less before Whidbey, 1790 // and the int we use as IsConsistent could actually overlap the InstanceEntry SpinLock. 1791 1792 [StructLayout(LayoutKind.Sequential)] 1793 private struct CategoryEntry 1794 { 1795 public int SpinLock; 1796 public int CategoryNameHashCode; 1797 public int CategoryNameOffset; 1798 public int FirstInstanceOffset; 1799 public int NextCategoryOffset; 1800 public int IsConsistent; // this was 4 bytes of padding in v1.0/v1.1 1801 } 1802 1803 [StructLayout(LayoutKind.Sequential)] 1804 private struct InstanceEntry 1805 { 1806 public int SpinLock; 1807 public int InstanceNameHashCode; 1808 public int InstanceNameOffset; 1809 public int RefCount; 1810 public int FirstCounterOffset; 1811 public int NextInstanceOffset; 1812 } 1813 1814 [StructLayout(LayoutKind.Sequential)] 1815 private struct CounterEntry 1816 { 1817 public int SpinLock; 1818 public int CounterNameHashCode; 1819 public int CounterNameOffset; 1820 public int LifetimeOffset; // this was 4 bytes of padding in v1.0/v1.1 1821 public long Value; 1822 public int NextCounterOffset; 1823 public int padding2; 1824 } 1825 1826 [StructLayout(LayoutKind.Sequential)] 1827 private struct CounterEntryMisaligned 1828 { 1829 public int SpinLock; 1830 public int CounterNameHashCode; 1831 public int CounterNameOffset; 1832 public int LifetimeOffset; // this was 4 bytes of padding in v1.0/v1.1 1833 public int Value_lo; 1834 public int Value_hi; 1835 public int NextCounterOffset; 1836 public int padding2; // The compiler adds this only if there is an int64 in the struct - 1837 // ie only for CounterEntry. It really needs to be here. 1838 } 1839 1840 [StructLayout(LayoutKind.Sequential)] 1841 private struct ProcessLifetimeEntry 1842 { 1843 public int LifetimeType; 1844 public int ProcessId; 1845 public Int64 StartupTime; 1846 } 1847 1848 private class CategoryData 1849 { 1850 public FileMapping FileMapping; 1851 public bool EnableReuse; 1852 public bool UseUniqueSharedMemory; 1853 public string FileMappingName; 1854 public string MutexName; 1855 public ArrayList CounterNames; 1856 } 1857 } 1858 1859 internal class ProcessData 1860 { ProcessData(int pid, long startTime)1861 public ProcessData(int pid, long startTime) 1862 { 1863 ProcessId = pid; 1864 StartupTime = startTime; 1865 } 1866 public int ProcessId; 1867 public long StartupTime; 1868 } 1869 1870 } 1871