Fix infrequent hangs in test-runner. (#16793)
[mono-project.git] / mcs / class / referencesource / System / services / monitoring / system / diagnosticts / SharedPerformanceCounter.cs
blob08e2e37527b1cb2300e921e3014b375413741957
1 //------------------------------------------------------------------------------
2 // <copyright file="SharedPerformanceCounter.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
7 namespace System.Diagnostics {
8 using System;
9 using System.Text;
10 using System.Threading;
11 using System.Collections;
12 using System.Runtime.ConstrainedExecution;
13 using System.Runtime.CompilerServices;
14 using System.Runtime.InteropServices;
15 using System.Security.Permissions;
16 using System.Security;
17 using Microsoft.Win32;
18 using Microsoft.Win32.SafeHandles;
19 using System.Globalization;
20 using System.Security.Principal;
21 using System.Security.AccessControl;
22 using System.Collections.Generic;
23 using System.Runtime.Versioning;
25 [HostProtection(Synchronization=true, SharedState=true)]
26 internal sealed class SharedPerformanceCounter {
27 private const int MaxSpinCount = 5000;
28 internal const int DefaultCountersFileMappingSize = 524288;
29 internal const int MaxCountersFileMappingSize = 33554432;
30 internal const int MinCountersFileMappingSize = 32768;
31 internal const int InstanceNameMaxLength = 127;
32 internal const int InstanceNameSlotSize = 256;
33 internal const string SingleInstanceName = "systemdiagnosticssharedsingleinstance";
34 internal const string DefaultFileMappingName = "netfxcustomperfcounters.1.0";
35 internal static readonly int SingleInstanceHashCode = GetWstrHashCode(SingleInstanceName);
36 private static Hashtable categoryDataTable = new Hashtable(StringComparer.Ordinal);
37 private static readonly int CategoryEntrySize = Marshal.SizeOf(typeof(CategoryEntry));
38 private static readonly int InstanceEntrySize = Marshal.SizeOf(typeof(InstanceEntry));
39 private static readonly int CounterEntrySize = Marshal.SizeOf(typeof(CounterEntry));
40 private static readonly int ProcessLifetimeEntrySize = Marshal.SizeOf(typeof(ProcessLifetimeEntry));
42 private static long LastInstanceLifetimeSweepTick;
43 private const long InstanceLifetimeSweepWindow = 30*10000000; //ticks
44 private static volatile ProcessData procData;
46 private static ProcessData ProcessData {
47 get {
48 if (procData == null) {
49 new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert();
50 try {
51 int pid = NativeMethods.GetCurrentProcessId();
52 long startTime = -1;
54 // Though we have asserted the required CAS permissions above, we may
55 // still fail to query the process information if the user does not
56 // have the necessary process access rights or privileges.
57 // This might be the case if the current process was started by a
58 // different user (primary token) than the current user
59 // (impersonation token) that has less privilege/ACL rights.
60 using (SafeProcessHandle procHandle = SafeProcessHandle.OpenProcess(NativeMethods.PROCESS_QUERY_INFORMATION, false, pid)) {
61 if (!procHandle.IsInvalid) {
62 long temp;
63 NativeMethods.GetProcessTimes(procHandle, out startTime, out temp, out temp, out temp);
66 procData = new ProcessData(pid, startTime);
68 finally {
69 SecurityPermission.RevertAssert();
72 return procData;
76 // InitialOffset is the offset in our global shared memory where we put the first CategoryEntry. It needs to be 4 because in
77 // v1.0 and v1.1 we used IntPtr.Size. That creates potential side-by-side issues on 64 bit machines using WOW64.
78 // A v1.0 app running on WOW64 will assume the InitialOffset is 4. A true 64 bit app on the same machine will assume
79 // the initial offset is 8.
80 // However, using an offset of 4 means that our CounterEntry.Value is potentially misaligned. This is why we have SetValue
81 // and other methods which split CounterEntry.Value into two ints. With separate shared memory blocks per
82 // category, we can fix this and always use an inital offset of 8.
83 internal int InitialOffset = 4;
85 private CategoryData categoryData;
86 private long baseAddress;
87 private unsafe CounterEntry* counterEntryPointer;
88 private string categoryName;
89 private int categoryNameHashCode;
90 private int thisInstanceOffset = -1;
92 internal SharedPerformanceCounter(string catName, string counterName, string instanceName) :
93 this (catName, counterName, instanceName, PerformanceCounterInstanceLifetime.Global) { }
95 [ResourceExposure(ResourceScope.None)]
96 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
97 internal unsafe SharedPerformanceCounter(string catName, string counterName, string instanceName, PerformanceCounterInstanceLifetime lifetime) {
98 this.categoryName = catName;
99 this.categoryNameHashCode = GetWstrHashCode(categoryName);
101 categoryData = GetCategoryData();
103 // Check that the instance name isn't too long if we're using the new shared memory.
104 // We allocate InstanceNameSlotSize bytes in the shared memory
105 if (categoryData.UseUniqueSharedMemory) {
106 if (instanceName != null && instanceName.Length > InstanceNameMaxLength)
107 throw new InvalidOperationException(SR.GetString(SR.InstanceNameTooLong));
109 else {
110 if (lifetime != PerformanceCounterInstanceLifetime.Global)
111 throw new InvalidOperationException(SR.GetString(SR.ProcessLifetimeNotValidInGlobal));
114 if (counterName != null && instanceName != null) {
115 if (!categoryData.CounterNames.Contains(counterName))
116 Debug.Assert(false, "Counter " + counterName + " does not exist in category " + catName);
117 else
118 this.counterEntryPointer = GetCounter(counterName, instanceName, categoryData.EnableReuse, lifetime);
122 private FileMapping FileView {
123 [ResourceExposure(ResourceScope.Machine)]
124 get {
125 return categoryData.FileMapping;
129 internal unsafe long Value {
130 get {
131 if (counterEntryPointer == null)
132 return 0;
134 return GetValue(this.counterEntryPointer);
137 set {
138 if (counterEntryPointer == null)
139 return;
141 SetValue(this.counterEntryPointer, value);
145 private unsafe int CalculateAndAllocateMemory(int totalSize, out int alignmentAdjustment) {
146 int newOffset;
147 int oldOffset;
148 alignmentAdjustment = 0;
150 Debug.Assert(!categoryData.UseUniqueSharedMemory, "We should never be calling CalculateAndAllocateMemory in the unique shared memory");
152 do {
153 oldOffset = *((int *) baseAddress);
154 // we need to verify the oldOffset before we start using it. Otherwise someone could change
155 // it to something bogus and we would write outside of the shared memory.
156 ResolveOffset(oldOffset, 0);
158 newOffset = CalculateMemory(oldOffset, totalSize, out alignmentAdjustment);
160 // In the default shared mem we need to make sure that the end address is also aligned. This is because
161 // in v1.1/v1.0 we just assumed that the next free offset was always properly aligned.
162 int endAddressMod8 = (int) (baseAddress + newOffset) & 0x7;
163 int endAlignmentAdjustment = (8 - endAddressMod8) & 0x7;
164 newOffset += endAlignmentAdjustment;
166 } while (SafeNativeMethods.InterlockedCompareExchange((IntPtr)baseAddress, newOffset, oldOffset) != oldOffset);
168 return oldOffset;
171 private int CalculateMemory(int oldOffset, int totalSize, out int alignmentAdjustment) {
172 int newOffset = CalculateMemoryNoBoundsCheck(oldOffset, totalSize, out alignmentAdjustment);
174 if (newOffset > FileView.FileMappingSize || newOffset < 0) {
175 throw new InvalidOperationException(SR.GetString(SR.CountersOOM));
178 return newOffset;
181 [ResourceExposure(ResourceScope.None)]
182 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
183 private int CalculateMemoryNoBoundsCheck(int oldOffset, int totalSize, out int alignmentAdjustment) {
184 int currentTotalSize = totalSize;
186 Thread.MemoryBarrier();
188 // make sure the start address is 8 byte aligned
189 int startAddressMod8 = (int) (baseAddress + oldOffset) & 0x7;
190 alignmentAdjustment = (8 - startAddressMod8) & 0x7;
191 currentTotalSize = currentTotalSize + alignmentAdjustment;
193 int newOffset = oldOffset + currentTotalSize;
195 return newOffset;
198 private unsafe int CreateCategory(CategoryEntry* lastCategoryPointer,
199 int instanceNameHashCode, string instanceName,
200 PerformanceCounterInstanceLifetime lifetime) {
201 int categoryNameLength;
202 int instanceNameLength;
203 int alignmentAdjustment;
204 int freeMemoryOffset;
205 int newOffset = 0;
206 int totalSize;
208 categoryNameLength = (categoryName.Length + 1) * 2;
209 totalSize = CategoryEntrySize + InstanceEntrySize + (CounterEntrySize * categoryData.CounterNames.Count) + categoryNameLength;
210 for (int i=0; i<categoryData.CounterNames.Count; i++) {
211 totalSize += (((string)categoryData.CounterNames[i]).Length + 1) * 2;
214 if (categoryData.UseUniqueSharedMemory) {
215 instanceNameLength = InstanceNameSlotSize;
216 totalSize += ProcessLifetimeEntrySize + instanceNameLength;
218 // If we're in a separate shared memory, we need to do a two stage update of the free memory pointer.
219 // First we calculate our alignment adjustment and where the new free offset is. Then we
220 // write the new structs and data. The last two operations are to link the new structs into the
221 // existing ones and update the next free offset. Our process could get killed in between those two,
222 // leaving the memory in an inconsistent state. We use the "IsConsistent" flag to help determine
223 // when that has happened.
224 freeMemoryOffset = *((int *) baseAddress);
225 newOffset = CalculateMemory(freeMemoryOffset, totalSize, out alignmentAdjustment);
227 if (freeMemoryOffset == InitialOffset)
228 lastCategoryPointer->IsConsistent = 0;
230 else {
231 instanceNameLength = (instanceName.Length +1) * 2;
232 totalSize += instanceNameLength;
233 freeMemoryOffset = CalculateAndAllocateMemory(totalSize, out alignmentAdjustment);
236 long nextPtr = ResolveOffset(freeMemoryOffset, totalSize + alignmentAdjustment);
238 CategoryEntry* newCategoryEntryPointer;
239 InstanceEntry* newInstanceEntryPointer;
240 // We need to decide where to put the padding returned in alignmentAdjustment. There are several things that
241 // need to be aligned. First, we need to align each struct on a 4 byte boundary so we can use interlocked
242 // operations on the int Spinlock field. Second, we need to align the CounterEntry on an 8 byte boundary so that
243 // on 64 bit platforms we can use interlocked operations on the Value field. alignmentAdjustment guarantees 8 byte
244 // alignemnt, so we use that for both. If we're creating the very first category, however, we can't move that
245 // CategoryEntry. In this case we put the alignmentAdjustment before the InstanceEntry.
246 if (freeMemoryOffset == InitialOffset) {
247 newCategoryEntryPointer = (CategoryEntry*) nextPtr;
248 nextPtr += CategoryEntrySize + alignmentAdjustment;
249 newInstanceEntryPointer = (InstanceEntry*) nextPtr;
251 else {
252 nextPtr += alignmentAdjustment;
253 newCategoryEntryPointer = (CategoryEntry*) nextPtr;
254 nextPtr += CategoryEntrySize;
255 newInstanceEntryPointer = (InstanceEntry*) nextPtr;
257 nextPtr += InstanceEntrySize;
259 // create the first CounterEntry and reserve space for all of the rest. We won't
260 // finish creating them until the end
261 CounterEntry* newCounterEntryPointer = (CounterEntry*) nextPtr;
262 nextPtr += CounterEntrySize * categoryData.CounterNames.Count;
264 if (categoryData.UseUniqueSharedMemory) {
265 ProcessLifetimeEntry* newLifetimeEntry = (ProcessLifetimeEntry*) nextPtr;
266 nextPtr += ProcessLifetimeEntrySize;
268 newCounterEntryPointer->LifetimeOffset = (int)((long)newLifetimeEntry - baseAddress);
269 PopulateLifetimeEntry(newLifetimeEntry, lifetime);
272 newCategoryEntryPointer->CategoryNameHashCode = categoryNameHashCode;
273 newCategoryEntryPointer->NextCategoryOffset = 0;
274 newCategoryEntryPointer->FirstInstanceOffset = (int)((long)newInstanceEntryPointer - baseAddress);
275 newCategoryEntryPointer->CategoryNameOffset = (int) (nextPtr - baseAddress);
276 SafeMarshalCopy(categoryName, (IntPtr)nextPtr);
277 nextPtr += categoryNameLength;
279 newInstanceEntryPointer->InstanceNameHashCode = instanceNameHashCode;
280 newInstanceEntryPointer->NextInstanceOffset = 0;
281 newInstanceEntryPointer->FirstCounterOffset = (int)((long)newCounterEntryPointer - baseAddress);
282 newInstanceEntryPointer->RefCount = 1;
283 newInstanceEntryPointer->InstanceNameOffset = (int) (nextPtr - baseAddress);
284 SafeMarshalCopy(instanceName, (IntPtr)nextPtr);
285 nextPtr += instanceNameLength;
287 string counterName = (string) categoryData.CounterNames[0];
288 newCounterEntryPointer->CounterNameHashCode = GetWstrHashCode(counterName);
289 SetValue(newCounterEntryPointer, 0);
290 newCounterEntryPointer->CounterNameOffset = (int) (nextPtr - baseAddress);
291 SafeMarshalCopy(counterName, (IntPtr)nextPtr);
292 nextPtr += (counterName.Length + 1) * 2;
294 CounterEntry* previousCounterEntryPointer;
295 for (int i=1; i<categoryData.CounterNames.Count; i++) {
296 previousCounterEntryPointer = newCounterEntryPointer;
297 counterName = (string) categoryData.CounterNames[i];
299 newCounterEntryPointer++;
300 newCounterEntryPointer->CounterNameHashCode = GetWstrHashCode(counterName);
301 SetValue(newCounterEntryPointer, 0);
302 newCounterEntryPointer->CounterNameOffset = (int) (nextPtr - baseAddress);
303 SafeMarshalCopy(counterName, (IntPtr)nextPtr);
305 nextPtr += (counterName.Length + 1) * 2;
306 previousCounterEntryPointer->NextCounterOffset = (int)((long)newCounterEntryPointer - baseAddress);
309 Debug.Assert(nextPtr - baseAddress == freeMemoryOffset + totalSize + alignmentAdjustment, "We should have used all of the space we requested at this point");
311 int offset = (int) ((long) newCategoryEntryPointer - baseAddress);
312 lastCategoryPointer->IsConsistent = 0;
313 // If not the first category node, link it.
314 if (offset != InitialOffset)
315 lastCategoryPointer->NextCategoryOffset = offset;
317 if (categoryData.UseUniqueSharedMemory) {
318 *((int*) baseAddress) = newOffset;
319 lastCategoryPointer->IsConsistent = 1;
321 return offset;
324 private unsafe int CreateInstance(CategoryEntry* categoryPointer,
325 int instanceNameHashCode, string instanceName,
326 PerformanceCounterInstanceLifetime lifetime) {
327 int instanceNameLength;
328 int totalSize = InstanceEntrySize + (CounterEntrySize * categoryData.CounterNames.Count);
329 int alignmentAdjustment;
330 int freeMemoryOffset;
331 int newOffset = 0;
334 if (categoryData.UseUniqueSharedMemory) {
335 instanceNameLength = InstanceNameSlotSize;
336 totalSize += ProcessLifetimeEntrySize + instanceNameLength;
338 // If we're in a separate shared memory, we need to do a two stage update of the free memory pointer.
339 // First we calculate our alignment adjustment and where the new free offset is. Then we
340 // write the new structs and data. The last two operations are to link the new structs into the
341 // existing ones and update the next free offset. Our process could get killed in between those two,
342 // leaving the memory in an inconsistent state. We use the "IsConsistent" flag to help determine
343 // when that has happened.
344 freeMemoryOffset = *((int *) baseAddress);
345 newOffset = CalculateMemory(freeMemoryOffset, totalSize, out alignmentAdjustment);
347 else {
348 instanceNameLength = (instanceName.Length +1) * 2;
349 totalSize += instanceNameLength;
351 // add in the counter names for the global shared mem.
352 for (int i=0; i<categoryData.CounterNames.Count; i++) {
353 totalSize += (((string)categoryData.CounterNames[i]).Length + 1) * 2;
355 freeMemoryOffset = CalculateAndAllocateMemory(totalSize, out alignmentAdjustment);
358 freeMemoryOffset += alignmentAdjustment;
359 long nextPtr = ResolveOffset(freeMemoryOffset, totalSize); // don't add alignmentAdjustment since it's already
360 // been added to freeMemoryOffset
362 InstanceEntry* newInstanceEntryPointer = (InstanceEntry*) nextPtr;
363 nextPtr += InstanceEntrySize;
365 // create the first CounterEntry and reserve space for all of the rest. We won't
366 // finish creating them until the end
367 CounterEntry* newCounterEntryPointer = (CounterEntry*) nextPtr;
368 nextPtr += CounterEntrySize * categoryData.CounterNames.Count;
370 if (categoryData.UseUniqueSharedMemory) {
371 ProcessLifetimeEntry* newLifetimeEntry = (ProcessLifetimeEntry*) nextPtr;
372 nextPtr += ProcessLifetimeEntrySize;
374 newCounterEntryPointer->LifetimeOffset = (int)((long)newLifetimeEntry - baseAddress);
375 PopulateLifetimeEntry(newLifetimeEntry, lifetime);
378 // set up the InstanceEntry
379 newInstanceEntryPointer->InstanceNameHashCode = instanceNameHashCode;
380 newInstanceEntryPointer->NextInstanceOffset = 0;
381 newInstanceEntryPointer->FirstCounterOffset = (int)((long)newCounterEntryPointer - baseAddress);
382 newInstanceEntryPointer->RefCount = 1;
383 newInstanceEntryPointer->InstanceNameOffset = (int) (nextPtr - baseAddress);
384 SafeMarshalCopy(instanceName, (IntPtr)nextPtr);
386 nextPtr += instanceNameLength;
389 if (categoryData.UseUniqueSharedMemory) {
390 // in the unique shared mem we'll assume that the CounterEntries of the first instance
391 // are all created. Then we can just refer to the old counter name rather than copying in a new one.
392 InstanceEntry* firstInstanceInCategoryPointer = (InstanceEntry*) ResolveOffset(categoryPointer->FirstInstanceOffset, InstanceEntrySize);
393 CounterEntry* firstCounterInCategoryPointer = (CounterEntry*) ResolveOffset(firstInstanceInCategoryPointer->FirstCounterOffset, CounterEntrySize);
394 newCounterEntryPointer->CounterNameHashCode = firstCounterInCategoryPointer->CounterNameHashCode;
395 SetValue(newCounterEntryPointer, 0);
396 newCounterEntryPointer->CounterNameOffset = firstCounterInCategoryPointer->CounterNameOffset;
398 // now create the rest of the CounterEntrys
399 CounterEntry* previousCounterEntryPointer;
400 for (int i=1; i<categoryData.CounterNames.Count; i++) {
401 previousCounterEntryPointer = newCounterEntryPointer;
403 newCounterEntryPointer++;
404 Debug.Assert(firstCounterInCategoryPointer->NextCounterOffset != 0, "The unique shared memory should have all of its counters created by the time we hit CreateInstance");
405 firstCounterInCategoryPointer = (CounterEntry*) ResolveOffset(firstCounterInCategoryPointer->NextCounterOffset, CounterEntrySize);
406 newCounterEntryPointer->CounterNameHashCode = firstCounterInCategoryPointer->CounterNameHashCode;
407 SetValue(newCounterEntryPointer, 0);
408 newCounterEntryPointer->CounterNameOffset = firstCounterInCategoryPointer->CounterNameOffset;
410 previousCounterEntryPointer->NextCounterOffset = (int)((long)newCounterEntryPointer - baseAddress);
413 else {
414 // now create the rest of the CounterEntrys
415 CounterEntry* previousCounterEntryPointer = null;
416 for (int i=0; i<categoryData.CounterNames.Count; i++) {
417 string counterName = (string) categoryData.CounterNames[i];
418 newCounterEntryPointer->CounterNameHashCode = GetWstrHashCode(counterName);
419 newCounterEntryPointer->CounterNameOffset = (int) (nextPtr - baseAddress);
420 SafeMarshalCopy(counterName, (IntPtr)nextPtr);
421 nextPtr += (counterName.Length + 1) * 2;
423 SetValue(newCounterEntryPointer, 0);
425 if (i != 0)
426 previousCounterEntryPointer->NextCounterOffset = (int)((long)newCounterEntryPointer - baseAddress);
428 previousCounterEntryPointer = newCounterEntryPointer;
429 newCounterEntryPointer++;
433 Debug.Assert(nextPtr - baseAddress == freeMemoryOffset + totalSize, "We should have used all of the space we requested at this point");
435 int offset = (int) ((long) newInstanceEntryPointer - baseAddress);
436 categoryPointer->IsConsistent = 0;
438 // prepend the new instance rather than append, helps with perf of hooking up subsequent counters
439 newInstanceEntryPointer->NextInstanceOffset = categoryPointer->FirstInstanceOffset;
440 categoryPointer->FirstInstanceOffset = offset;
442 if (categoryData.UseUniqueSharedMemory) {
443 *((int*) baseAddress) = newOffset;
444 categoryPointer->IsConsistent = 1;
447 return freeMemoryOffset;
450 private unsafe int CreateCounter(CounterEntry* lastCounterPointer,
451 int counterNameHashCode, string counterName) {
452 int counterNameLength = (counterName.Length + 1) * 2;
453 int totalSize = sizeof(CounterEntry) + counterNameLength;
454 int alignmentAdjustment;
455 int freeMemoryOffset;
457 Debug.Assert(!categoryData.UseUniqueSharedMemory, "We should never be calling CreateCounter in the unique shared memory");
458 freeMemoryOffset = CalculateAndAllocateMemory(totalSize, out alignmentAdjustment);
460 freeMemoryOffset += alignmentAdjustment;
462 long nextPtr = ResolveOffset(freeMemoryOffset, totalSize);
463 CounterEntry* newCounterEntryPointer = (CounterEntry*) nextPtr;
464 nextPtr += sizeof(CounterEntry);
466 newCounterEntryPointer->CounterNameOffset = (int) (nextPtr - baseAddress);
467 newCounterEntryPointer->CounterNameHashCode = counterNameHashCode;
468 newCounterEntryPointer->NextCounterOffset = 0;
469 SetValue(newCounterEntryPointer, 0);
470 SafeMarshalCopy(counterName, (IntPtr)nextPtr);
472 Debug.Assert(nextPtr + counterNameLength - baseAddress == freeMemoryOffset + totalSize, "We should have used all of the space we requested at this point");
474 lastCounterPointer->NextCounterOffset = (int) ((long) newCounterEntryPointer - baseAddress);
475 return freeMemoryOffset;
479 [ResourceExposure(ResourceScope.None)]
480 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
481 private unsafe static void PopulateLifetimeEntry(ProcessLifetimeEntry *lifetimeEntry, PerformanceCounterInstanceLifetime lifetime) {
483 if (lifetime == PerformanceCounterInstanceLifetime.Process) {
485 lifetimeEntry->LifetimeType = (int) PerformanceCounterInstanceLifetime.Process;
486 lifetimeEntry->ProcessId = ProcessData.ProcessId;
487 lifetimeEntry->StartupTime = ProcessData.StartupTime;
489 else {
490 lifetimeEntry->ProcessId = 0;
491 lifetimeEntry->StartupTime = 0;
496 [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
497 private static unsafe void WaitAndEnterCriticalSection(int* spinLockPointer, out bool taken) {
498 WaitForCriticalSection(spinLockPointer);
500 // Note - we are taking a lock here, but it probably isn't
501 // worthwhile to use Thread.BeginCriticalRegion & EndCriticalRegion.
502 // These only really help the CLR escalate from a thread abort
503 // to an appdomain unload, under the assumption that you may be
504 // editing shared state within the appdomain. Here you are editing
505 // shared state, but it is shared across processes. Unloading the
506 // appdomain isn't exactly helping. The only thing that would help
507 // would be if the CLR tells the host to ensure all allocations
508 // have a higher chance of succeeding within this critical region,
509 // but of course that's only a probabilisitic statement.
511 // Must be able to assign to the out param.
512 RuntimeHelpers.PrepareConstrainedRegions();
513 try {
515 finally {
516 int r = Interlocked.CompareExchange(ref *spinLockPointer, 1, 0);
517 taken = (r == 0);
521 [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
522 private static unsafe void WaitForCriticalSection(int* spinLockPointer) {
523 int spinCount = MaxSpinCount;
524 for (; spinCount > 0 && *spinLockPointer != 0; spinCount--) {
525 // We suspect there are scenarios where the finalizer thread
526 // will call this method. The finalizer thread runs with
527 // a higher priority than the other code. Using SpinWait
528 // isn't sufficient, since it only spins, but doesn't yield
529 // to any lower-priority threads. Call Thread.Sleep(1).
530 if (*spinLockPointer != 0)
531 Thread.Sleep(1);
534 // if the lock still isn't free, most likely there's a deadlock caused by a process
535 // getting killed while it held the lock. We'll just free the lock
536 if (spinCount == 0 && *spinLockPointer != 0)
537 *spinLockPointer = 0;
540 [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
541 private static unsafe void ExitCriticalSection(int* spinLockPointer) {
542 *spinLockPointer = 0;
545 // WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING
546 // This hashcode function is identical to the one in SharedPerformanceCounter.cpp. If
547 // you change one without changing the other, perfcounters will break.
548 internal static int GetWstrHashCode(string wstr)
550 uint hash = 5381;
551 for(uint i=0; i < wstr.Length; i++)
552 hash = ((hash << 5) + hash) ^ wstr[(int) i];
553 return (int)hash;
556 // Calculate the length of a string in the shared memory. If we reach the end of the shared memory
557 // before we see a null terminator, we throw.
558 [ResourceExposure(ResourceScope.None)]
559 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
560 private unsafe int GetStringLength(char* startChar) {
561 char* currentChar = startChar;
562 ulong endAddress = (ulong) (baseAddress + FileView.FileMappingSize);
564 while((ulong) currentChar < (endAddress - 2)) {
565 if (*currentChar == 0)
566 return (int) (currentChar - startChar);
568 currentChar++;
571 throw new InvalidOperationException(SR.GetString(SR.MappingCorrupted));
574 // Compare a managed string to a string located at a given offset. If we walk past the end of the
575 // shared memory, we throw.
576 [ResourceExposure(ResourceScope.None)]
577 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
578 private unsafe bool StringEquals(string stringA, int offset) {
579 char* currentChar = (char*) ResolveOffset(offset, 0);
580 ulong endAddress = (ulong) (baseAddress + FileView.FileMappingSize);
582 int i;
583 for (i=0; i<stringA.Length; i++) {
584 if ((ulong) (currentChar+i) > (endAddress - 2))
585 throw new InvalidOperationException(SR.GetString(SR.MappingCorrupted));
587 if (stringA[i] != currentChar[i])
588 return false;
591 // now check for the null termination.
592 if ((ulong) (currentChar+i) > (endAddress - 2))
593 throw new InvalidOperationException(SR.GetString(SR.MappingCorrupted));
595 return (currentChar[i] == 0);
599 [ResourceExposure(ResourceScope.None)] // Memory maps the perf counter data file
600 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
601 private unsafe CategoryData GetCategoryData() {
602 CategoryData data = (CategoryData) categoryDataTable[categoryName];
604 if (data == null) {
605 lock(categoryDataTable) {
606 data = (CategoryData) categoryDataTable[categoryName];
607 if (data == null) {
608 data = new CategoryData();
609 data.FileMappingName = DefaultFileMappingName;
610 data.MutexName = categoryName;
612 RegistryPermission registryPermission = new RegistryPermission(PermissionState.Unrestricted);
613 registryPermission.Assert();
614 RegistryKey categoryKey = null;
615 try {
616 categoryKey = Registry.LocalMachine.OpenSubKey(PerformanceCounterLib.ServicePath + "\\" + categoryName + "\\Performance");
618 // first read the options
619 Object optionsObject = categoryKey.GetValue("CategoryOptions");
620 if (optionsObject != null) {
621 int options = (int) optionsObject;
622 data.EnableReuse = (((PerformanceCounterCategoryOptions) options & PerformanceCounterCategoryOptions.EnableReuse) != 0);
624 if (((PerformanceCounterCategoryOptions) options & PerformanceCounterCategoryOptions.UseUniqueSharedMemory) != 0) {
625 data.UseUniqueSharedMemory = true;
626 InitialOffset = 8;
627 data.FileMappingName = DefaultFileMappingName + categoryName;
631 int fileMappingSize;
632 object fileMappingSizeObject = categoryKey.GetValue("FileMappingSize");
633 if (fileMappingSizeObject != null && data.UseUniqueSharedMemory) {
634 // we only use this reg value in the unique shared memory case.
635 fileMappingSize = (int) fileMappingSizeObject;
636 if (fileMappingSize < MinCountersFileMappingSize)
637 fileMappingSize = MinCountersFileMappingSize;
639 if (fileMappingSize > MaxCountersFileMappingSize)
640 fileMappingSize = MaxCountersFileMappingSize;
642 else {
643 fileMappingSize = GetFileMappingSizeFromConfig();
644 if (data.UseUniqueSharedMemory)
645 fileMappingSize = fileMappingSize >> 2; // if we have a custom filemapping, only make it 25% as large.
648 // now read the counter names
649 object counterNamesObject = categoryKey.GetValue("Counter Names");
650 byte[] counterNamesBytes = counterNamesObject as byte[];
652 if (counterNamesBytes != null) {
653 ArrayList names = new ArrayList();
654 fixed (byte* counterNamesPtr = counterNamesBytes) {
655 int start = 0;
656 for (int i=0; i<counterNamesBytes.Length-1; i+=2) {
657 if (counterNamesBytes[i] == 0 && counterNamesBytes[i+1] == 0 && start != i) {
658 string counter = new String((sbyte*)counterNamesPtr, start, i-start, Encoding.Unicode);
659 names.Add(counter.ToLowerInvariant());
660 start = i+2;
664 data.CounterNames = names;
666 else {
667 string[] counterNames = (string[]) counterNamesObject;
668 for (int i=0; i<counterNames.Length; i++)
669 counterNames[i] = counterNames[i].ToLowerInvariant();
670 data.CounterNames = new ArrayList(counterNames);
673 // figure out the shared memory name
674 if (SharedUtils.CurrentEnvironment == SharedUtils.W2kEnvironment) {
675 data.FileMappingName = "Global\\" + data.FileMappingName;
676 data.MutexName = "Global\\" + categoryName;
679 data.FileMapping = new FileMapping(data.FileMappingName, fileMappingSize, InitialOffset);
680 categoryDataTable[categoryName] = data;
682 finally {
683 if (categoryKey != null)
684 categoryKey.Close();
685 RegistryPermission.RevertAssert();
690 baseAddress = (long) data.FileMapping.FileViewAddress;
692 if (data.UseUniqueSharedMemory)
693 InitialOffset = 8;
696 return data;
699 [MethodImpl(MethodImplOptions.NoInlining)]
700 private static int GetFileMappingSizeFromConfig() {
701 return DiagnosticsConfiguration.PerfomanceCountersFileMappingSize;
704 private static void RemoveCategoryData(string categoryName) {
705 lock (categoryDataTable) {
706 categoryDataTable.Remove(categoryName);
710 [ResourceExposure(ResourceScope.None)]
711 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
712 private unsafe CounterEntry* GetCounter(string counterName, string instanceName, bool enableReuse, PerformanceCounterInstanceLifetime lifetime) {
713 int counterNameHashCode = GetWstrHashCode(counterName);
714 int instanceNameHashCode;
715 if (instanceName != null && instanceName.Length != 0)
716 instanceNameHashCode = GetWstrHashCode(instanceName);
717 else {
718 instanceNameHashCode = SingleInstanceHashCode;
719 instanceName = SingleInstanceName;
722 Mutex mutex = null;
723 CounterEntry* counterPointer = null;
724 InstanceEntry* instancePointer = null;
725 RuntimeHelpers.PrepareConstrainedRegions();
726 try {
727 SharedUtils.EnterMutexWithoutGlobal(categoryData.MutexName, ref mutex);
728 CategoryEntry* categoryPointer;
729 bool counterFound = false;
730 while (!FindCategory(&categoryPointer)) {
731 // don't bother locking again if we're using a separate shared memory.
732 bool sectionEntered;
733 if (categoryData.UseUniqueSharedMemory)
734 sectionEntered = true;
735 else
736 WaitAndEnterCriticalSection(&(categoryPointer->SpinLock), out sectionEntered);
738 int newCategoryOffset;
739 if (sectionEntered) {
740 try {
741 newCategoryOffset = CreateCategory(categoryPointer, instanceNameHashCode, instanceName, lifetime);
743 finally {
744 if (!categoryData.UseUniqueSharedMemory)
745 ExitCriticalSection(&(categoryPointer->SpinLock));
748 categoryPointer = (CategoryEntry*)(ResolveOffset(newCategoryOffset, CategoryEntrySize));
749 instancePointer = (InstanceEntry*)(ResolveOffset(categoryPointer->FirstInstanceOffset, InstanceEntrySize));
750 counterFound = FindCounter(counterNameHashCode, counterName, instancePointer, &counterPointer);
751 Debug.Assert(counterFound, "All counters should be created, so we should always find the counter");
752 return counterPointer;
756 bool foundFreeInstance;
757 while (!FindInstance(instanceNameHashCode, instanceName, categoryPointer, &instancePointer, true, lifetime, out foundFreeInstance)) {
758 InstanceEntry* lockInstancePointer = instancePointer;
760 // don't bother locking again if we're using a separate shared memory.
761 bool sectionEntered;
762 if (categoryData.UseUniqueSharedMemory)
763 sectionEntered = true;
764 else
765 WaitAndEnterCriticalSection(&(lockInstancePointer->SpinLock), out sectionEntered);
767 if (sectionEntered) {
768 try {
769 bool reused = false;
771 if (enableReuse && foundFreeInstance) {
772 reused = TryReuseInstance(instanceNameHashCode, instanceName, categoryPointer, &instancePointer, lifetime, lockInstancePointer);
773 // 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
774 // we're looking for.
777 if (!reused) {
778 int newInstanceOffset = CreateInstance(categoryPointer, instanceNameHashCode, instanceName, lifetime);
779 instancePointer = (InstanceEntry*)(ResolveOffset(newInstanceOffset, InstanceEntrySize));
781 counterFound = FindCounter(counterNameHashCode, counterName, instancePointer, &counterPointer);
782 Debug.Assert(counterFound, "All counters should be created, so we should always find the counter");
783 return counterPointer;
786 finally {
787 if (!categoryData.UseUniqueSharedMemory)
788 ExitCriticalSection(&(lockInstancePointer->SpinLock));
793 if (categoryData.UseUniqueSharedMemory) {
794 counterFound = FindCounter(counterNameHashCode, counterName, instancePointer, &counterPointer);
795 Debug.Assert(counterFound, "All counters should be created, so we should always find the counter");
796 return counterPointer;
798 else {
799 while (!FindCounter(counterNameHashCode, counterName, instancePointer, &counterPointer)) {
800 bool sectionEntered;
801 WaitAndEnterCriticalSection(&(counterPointer->SpinLock), out sectionEntered);
803 if (sectionEntered) {
804 try {
805 int newCounterOffset = CreateCounter(counterPointer, counterNameHashCode, counterName);
806 return (CounterEntry*) (ResolveOffset(newCounterOffset, CounterEntrySize));
808 finally {
809 ExitCriticalSection(&(counterPointer->SpinLock));
814 return counterPointer;
817 finally {
818 // cache this instance for reuse
819 try {
820 if (counterPointer != null && instancePointer != null) {
821 this.thisInstanceOffset = ResolveAddress((long)instancePointer, InstanceEntrySize);
824 catch (InvalidOperationException) {
825 this.thisInstanceOffset = -1;
828 if (mutex != null) {
829 mutex.ReleaseMutex();
830 mutex.Close();
836 // FindCategory -
838 // * when the function returns true the returnCategoryPointerReference is set to the CategoryEntry
839 // that matches 'categoryNameHashCode' and 'categoryName'
841 // * when the function returns false the returnCategoryPointerReference is set to the last CategoryEntry
842 // in the linked list
844 private unsafe bool FindCategory(CategoryEntry** returnCategoryPointerReference) {
845 CategoryEntry* firstCategoryPointer = (CategoryEntry*)(ResolveOffset(InitialOffset, CategoryEntrySize));
846 CategoryEntry* currentCategoryPointer = firstCategoryPointer;
847 CategoryEntry* previousCategoryPointer = firstCategoryPointer;
849 for(;;) {
850 if (currentCategoryPointer->IsConsistent == 0)
851 Verify(currentCategoryPointer);
853 if (currentCategoryPointer->CategoryNameHashCode == categoryNameHashCode) {
854 if (StringEquals(categoryName, currentCategoryPointer->CategoryNameOffset)) {
855 *returnCategoryPointerReference = currentCategoryPointer;
856 return true;
860 previousCategoryPointer = currentCategoryPointer;
861 if (currentCategoryPointer->NextCategoryOffset != 0)
862 currentCategoryPointer = (CategoryEntry*)(ResolveOffset(currentCategoryPointer->NextCategoryOffset, CategoryEntrySize));
863 else {
864 *returnCategoryPointerReference = previousCategoryPointer;
865 return false;
870 private unsafe bool FindCounter(int counterNameHashCode, string counterName, InstanceEntry* instancePointer, CounterEntry** returnCounterPointerReference) {
871 CounterEntry* currentCounterPointer = (CounterEntry*)(ResolveOffset(instancePointer->FirstCounterOffset, CounterEntrySize));
872 CounterEntry* previousCounterPointer = currentCounterPointer;
873 for(;;) {
874 if (currentCounterPointer->CounterNameHashCode == counterNameHashCode) {
875 if (StringEquals(counterName, currentCounterPointer->CounterNameOffset)) {
876 *returnCounterPointerReference = currentCounterPointer;
877 return true;
881 previousCounterPointer = currentCounterPointer;
882 if (currentCounterPointer->NextCounterOffset != 0)
883 currentCounterPointer = (CounterEntry*)(ResolveOffset(currentCounterPointer->NextCounterOffset, CounterEntrySize));
884 else {
885 *returnCounterPointerReference = previousCounterPointer;
886 return false;
891 [ResourceExposure(ResourceScope.None)]
892 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
893 private unsafe bool FindInstance(int instanceNameHashCode, string instanceName,
894 CategoryEntry* categoryPointer, InstanceEntry** returnInstancePointerReference,
895 bool activateUnusedInstances, PerformanceCounterInstanceLifetime lifetime,
896 out bool foundFreeInstance) {
898 InstanceEntry* currentInstancePointer = (InstanceEntry*)(ResolveOffset(categoryPointer->FirstInstanceOffset, InstanceEntrySize));
899 InstanceEntry* previousInstancePointer = currentInstancePointer;
900 foundFreeInstance = false;
901 // Look at the first instance to determine if this is single or multi instance.
902 if (currentInstancePointer->InstanceNameHashCode == SingleInstanceHashCode) {
903 if (StringEquals(SingleInstanceName, currentInstancePointer->InstanceNameOffset)){
904 if (instanceName != SingleInstanceName)
905 throw new InvalidOperationException(SR.GetString(SR.SingleInstanceOnly, categoryName));
907 else {
908 if (instanceName == SingleInstanceName)
909 throw new InvalidOperationException(SR.GetString(SR.MultiInstanceOnly, categoryName));
912 else {
913 if (instanceName == SingleInstanceName)
914 throw new InvalidOperationException(SR.GetString(SR.MultiInstanceOnly, categoryName));
918 // 1st pass find exact matching!
920 // We don't need to aggressively claim unused instances. For performance, we would proactively
921 // verify lifetime of instances if activateUnusedInstances is specified and certain time
922 // has elapsed since last sweep or we are running out of shared memory.
923 bool verifyLifeTime = activateUnusedInstances;
924 if (activateUnusedInstances) {
926 int totalSize = InstanceEntrySize + ProcessLifetimeEntrySize + InstanceNameSlotSize + (CounterEntrySize * categoryData.CounterNames.Count);
927 int freeMemoryOffset = *((int *) baseAddress);
928 int alignmentAdjustment;
929 int newOffset = CalculateMemoryNoBoundsCheck(freeMemoryOffset, totalSize, out alignmentAdjustment);
931 if (!(newOffset > FileView.FileMappingSize || newOffset < 0)) {
932 long tickDelta = (DateTime.Now.Ticks - Volatile.Read(ref LastInstanceLifetimeSweepTick));
933 if (tickDelta < InstanceLifetimeSweepWindow)
934 verifyLifeTime = false;
938 new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert();
939 try {
940 for(;;) {
941 bool verifiedLifetimeOfThisInstance = false;
942 if (verifyLifeTime && (currentInstancePointer->RefCount != 0)) {
943 verifiedLifetimeOfThisInstance = true;
944 VerifyLifetime(currentInstancePointer);
947 if (currentInstancePointer->InstanceNameHashCode == instanceNameHashCode) {
948 if (StringEquals(instanceName, currentInstancePointer->InstanceNameOffset)){
949 // we found a matching instance.
950 *returnInstancePointerReference = currentInstancePointer;
952 CounterEntry* firstCounter = (CounterEntry*) ResolveOffset(currentInstancePointer->FirstCounterOffset, CounterEntrySize);
953 ProcessLifetimeEntry* lifetimeEntry;
954 if (categoryData.UseUniqueSharedMemory)
955 lifetimeEntry = (ProcessLifetimeEntry*) ResolveOffset(firstCounter->LifetimeOffset, ProcessLifetimeEntrySize);
956 else
957 lifetimeEntry = null;
959 // ensure that we have verified the lifetime of the matched instance
960 if (!verifiedLifetimeOfThisInstance && currentInstancePointer->RefCount != 0)
961 VerifyLifetime(currentInstancePointer);
963 if (currentInstancePointer->RefCount != 0) {
964 if (lifetimeEntry != null && lifetimeEntry->ProcessId != 0) {
965 if (lifetime != PerformanceCounterInstanceLifetime.Process)
966 throw new InvalidOperationException(SR.GetString(SR.CantConvertProcessToGlobal));
968 // make sure only one process is using this instance.
969 if (ProcessData.ProcessId != lifetimeEntry->ProcessId)
970 throw new InvalidOperationException(SR.GetString(SR.InstanceAlreadyExists, instanceName));
972 // compare start time of the process, account for ACL issues in querying process information
973 if ((lifetimeEntry->StartupTime != -1) && (ProcessData.StartupTime != -1)) {
974 if (ProcessData.StartupTime != lifetimeEntry->StartupTime)
975 throw new InvalidOperationException(SR.GetString(SR.InstanceAlreadyExists, instanceName));
978 else {
979 if (lifetime == PerformanceCounterInstanceLifetime.Process)
980 throw new InvalidOperationException(SR.GetString(SR.CantConvertGlobalToProcess));
982 return true;
985 if (activateUnusedInstances) {
986 Mutex mutex = null;
987 RuntimeHelpers.PrepareConstrainedRegions();
988 try {
989 SharedUtils.EnterMutexWithoutGlobal(categoryData.MutexName, ref mutex);
990 ClearCounterValues(currentInstancePointer);
991 if (lifetimeEntry != null)
992 PopulateLifetimeEntry(lifetimeEntry, lifetime);
994 currentInstancePointer->RefCount = 1;
995 return true;
997 finally {
998 if (mutex != null) {
999 mutex.ReleaseMutex();
1000 mutex.Close();
1004 else
1005 return false;
1009 if (currentInstancePointer->RefCount == 0) {
1010 foundFreeInstance = true;
1013 previousInstancePointer = currentInstancePointer;
1014 if (currentInstancePointer->NextInstanceOffset != 0)
1015 currentInstancePointer = (InstanceEntry*)(ResolveOffset(currentInstancePointer->NextInstanceOffset, InstanceEntrySize));
1016 else {
1017 *returnInstancePointerReference = previousInstancePointer;
1018 return false;
1022 finally {
1023 SecurityPermission.RevertAssert();
1025 if (verifyLifeTime)
1026 Volatile.Write(ref LastInstanceLifetimeSweepTick, DateTime.Now.Ticks);
1030 private unsafe bool TryReuseInstance(int instanceNameHashCode, string instanceName,
1031 CategoryEntry* categoryPointer, InstanceEntry** returnInstancePointerReference,
1032 PerformanceCounterInstanceLifetime lifetime,
1033 InstanceEntry* lockInstancePointer) {
1035 // 2nd pass find a free instance slot
1037 InstanceEntry* currentInstancePointer = (InstanceEntry*)(ResolveOffset(categoryPointer->FirstInstanceOffset, InstanceEntrySize));
1038 InstanceEntry* previousInstancePointer = currentInstancePointer;
1039 for (;;) {
1040 if (currentInstancePointer->RefCount == 0) {
1042 bool hasFit;
1043 long instanceNamePtr; // we need cache this to avoid race conditions.
1045 if (categoryData.UseUniqueSharedMemory) {
1046 instanceNamePtr = ResolveOffset(currentInstancePointer->InstanceNameOffset, InstanceNameSlotSize);
1047 // In the separate shared memory case we should always have enough space for instances. The
1048 // name slot size is fixed.
1049 Debug.Assert(((instanceName.Length + 1) * 2) <= InstanceNameSlotSize, "The instance name length should always fit in our slot size");
1050 hasFit = true;
1052 else {
1053 // we don't know the string length yet.
1054 instanceNamePtr = ResolveOffset(currentInstancePointer->InstanceNameOffset, 0);
1056 // In the global shared memory, we require names to be exactly the same length in order
1057 // to reuse them. This way we don't end up leaking any space and we don't need to
1058 // depend on the layout of the memory to calculate the space we have.
1059 int length = GetStringLength((char*) instanceNamePtr);
1060 hasFit = (length == instanceName.Length);
1063 bool noSpinLock = (lockInstancePointer == currentInstancePointer) || categoryData.UseUniqueSharedMemory;
1064 // Instance name fit
1065 if (hasFit) {
1066 // don't bother locking again if we're using a separate shared memory.
1067 bool sectionEntered;
1068 if (noSpinLock)
1069 sectionEntered = true;
1070 else
1071 WaitAndEnterCriticalSection(&(currentInstancePointer->SpinLock), out sectionEntered);
1073 if (sectionEntered) {
1074 try {
1075 // Make copy with zero-term
1076 SafeMarshalCopy(instanceName, (IntPtr)instanceNamePtr);
1077 currentInstancePointer->InstanceNameHashCode = instanceNameHashCode;
1079 // return
1080 *returnInstancePointerReference = currentInstancePointer;
1081 // clear the counter values.
1082 ClearCounterValues(*returnInstancePointerReference);
1084 if (categoryData.UseUniqueSharedMemory) {
1085 CounterEntry* counterPointer = (CounterEntry*)ResolveOffset(currentInstancePointer->FirstCounterOffset, CounterEntrySize);
1086 ProcessLifetimeEntry* lifetimeEntry = (ProcessLifetimeEntry*) ResolveOffset(counterPointer->LifetimeOffset, ProcessLifetimeEntrySize);
1087 PopulateLifetimeEntry(lifetimeEntry, lifetime);
1090 (*returnInstancePointerReference)->RefCount = 1;
1091 return true;
1093 finally {
1094 if (!noSpinLock)
1095 ExitCriticalSection(&(currentInstancePointer->SpinLock));
1101 previousInstancePointer = currentInstancePointer;
1102 if (currentInstancePointer->NextInstanceOffset != 0)
1103 currentInstancePointer = (InstanceEntry*)(ResolveOffset(currentInstancePointer->NextInstanceOffset, InstanceEntrySize));
1104 else
1106 *returnInstancePointerReference = previousInstancePointer;
1107 return false;
1112 [ResourceExposure(ResourceScope.None)]
1113 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
1114 private unsafe void Verify(CategoryEntry* currentCategoryPointer) {
1115 if (!categoryData.UseUniqueSharedMemory)
1116 return;
1118 Mutex mutex = null;
1119 RuntimeHelpers.PrepareConstrainedRegions();
1120 try {
1121 SharedUtils.EnterMutexWithoutGlobal(categoryData.MutexName, ref mutex);
1122 VerifyCategory(currentCategoryPointer);
1124 finally {
1125 if (mutex != null) {
1126 mutex.ReleaseMutex();
1127 mutex.Close();
1132 private unsafe void VerifyCategory(CategoryEntry* currentCategoryPointer) {
1133 int freeOffset = *((int*)baseAddress);
1134 ResolveOffset(freeOffset, 0); // verify next free offset
1136 // begin by verifying the head node's offset
1137 int currentOffset = ResolveAddress((long)currentCategoryPointer, CategoryEntrySize);
1138 if (currentOffset >= freeOffset) {
1139 // zero out the bad head node entry
1140 currentCategoryPointer->SpinLock = 0;
1141 currentCategoryPointer->CategoryNameHashCode = 0;
1142 currentCategoryPointer->CategoryNameOffset = 0;
1143 currentCategoryPointer->FirstInstanceOffset = 0;
1144 currentCategoryPointer->NextCategoryOffset = 0;
1145 currentCategoryPointer->IsConsistent = 0;
1146 return;
1149 if (currentCategoryPointer->NextCategoryOffset > freeOffset)
1150 currentCategoryPointer->NextCategoryOffset = 0;
1151 else if (currentCategoryPointer->NextCategoryOffset != 0)
1152 VerifyCategory((CategoryEntry*) ResolveOffset(currentCategoryPointer->NextCategoryOffset, CategoryEntrySize));
1154 if (currentCategoryPointer->FirstInstanceOffset != 0) {
1155 // In V3, we started prepending the new instances rather than appending (as in V2) for performance.
1156 // Check whether the recently added instance at the head of the list is committed. If not, rewire
1157 // the head of the list to point to the next instance
1158 if (currentCategoryPointer->FirstInstanceOffset > freeOffset) {
1159 InstanceEntry* currentInstancePointer = (InstanceEntry*) ResolveOffset(currentCategoryPointer->FirstInstanceOffset, InstanceEntrySize);
1160 currentCategoryPointer->FirstInstanceOffset = currentInstancePointer->NextInstanceOffset;
1161 if (currentCategoryPointer->FirstInstanceOffset > freeOffset)
1162 currentCategoryPointer->FirstInstanceOffset = 0;
1169 if (currentCategoryPointer->FirstInstanceOffset != 0) {
1170 Debug.Assert(currentCategoryPointer->FirstInstanceOffset <= freeOffset, "The head of the list is inconsistent - possible mismatch of V2 & V3 instances?");
1171 VerifyInstance((InstanceEntry*) ResolveOffset(currentCategoryPointer->FirstInstanceOffset, InstanceEntrySize));
1175 currentCategoryPointer->IsConsistent = 1;
1178 private unsafe void VerifyInstance(InstanceEntry* currentInstancePointer) {
1179 int freeOffset = *((int*)baseAddress);
1180 ResolveOffset(freeOffset, 0); // verify next free offset
1182 if (currentInstancePointer->NextInstanceOffset > freeOffset)
1183 currentInstancePointer->NextInstanceOffset = 0;
1184 else if (currentInstancePointer->NextInstanceOffset != 0)
1185 VerifyInstance((InstanceEntry*) ResolveOffset(currentInstancePointer->NextInstanceOffset, InstanceEntrySize));
1188 [ResourceExposure(ResourceScope.None)]
1189 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
1190 private unsafe void VerifyLifetime(InstanceEntry* currentInstancePointer) {
1191 Debug.Assert(currentInstancePointer->RefCount != 0, "RefCount must be 1 for instances passed to VerifyLifetime");
1193 CounterEntry* counter = (CounterEntry*) ResolveOffset(currentInstancePointer->FirstCounterOffset, CounterEntrySize);
1194 if (counter->LifetimeOffset != 0) {
1195 ProcessLifetimeEntry* lifetime = (ProcessLifetimeEntry*) ResolveOffset(counter->LifetimeOffset, ProcessLifetimeEntrySize);
1196 if (lifetime->LifetimeType == (int) PerformanceCounterInstanceLifetime.Process) {
1197 int pid = lifetime->ProcessId;
1198 long startTime = lifetime->StartupTime;
1200 if (pid != 0) {
1202 // Optimize for this process
1203 if (pid == ProcessData.ProcessId) {
1204 if ((ProcessData.StartupTime != -1) && (startTime != -1) && (ProcessData.StartupTime != startTime)) {
1205 // Process id got recycled. Reclaim this instance.
1206 currentInstancePointer->RefCount = 0;
1207 return;
1210 else {
1211 long processStartTime;
1212 using (SafeProcessHandle procHandle = SafeProcessHandle.OpenProcess(NativeMethods.PROCESS_QUERY_INFORMATION, false, pid)) {
1213 int error = Marshal.GetLastWin32Error();
1214 if ((error == NativeMethods.ERROR_INVALID_PARAMETER) && procHandle.IsInvalid) {
1215 // The process is dead. Reclaim this instance. Note that we only clear the refcount here.
1216 // If we tried to clear the pid and startup time as well, we would have a ---- where
1217 // we could clear the pid/startup time but not the refcount.
1218 currentInstancePointer->RefCount = 0;
1219 return;
1222 // Defer cleaning the instance when we had previously encountered errors in
1223 // recording process start time (i.e, when startTime == -1) until after the
1224 // process id is not valid (which will be caught in the if check above)
1225 if (!procHandle.IsInvalid && startTime != -1) {
1226 long temp;
1227 if (NativeMethods.GetProcessTimes(procHandle, out processStartTime, out temp, out temp, out temp)) {
1228 if (processStartTime != startTime) {
1229 // The process is dead but a new one is using the same pid. Reclaim this instance.
1230 currentInstancePointer->RefCount = 0;
1231 return;
1237 // Check to see if the process handle has been signaled by the kernel. If this is the case then it's safe
1238 // to reclaim the instance as the process is in the process of exiting.
1239 using (SafeProcessHandle procHandle = SafeProcessHandle.OpenProcess(NativeMethods.SYNCHRONIZE, false, pid)) {
1240 if (!procHandle.IsInvalid) {
1241 using (ProcessWaitHandle wh = new ProcessWaitHandle(procHandle)) {
1242 if (wh.WaitOne(0, false)) {
1243 // Process has exited
1244 currentInstancePointer->RefCount = 0;
1245 return;
1257 [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
1258 internal unsafe long IncrementBy(long value) {
1259 if (counterEntryPointer == null)
1260 return 0;
1262 CounterEntry* counterEntry = this.counterEntryPointer;
1264 return AddToValue(counterEntry, value);
1267 internal unsafe long Increment() {
1268 if (counterEntryPointer == null)
1269 return 0;
1271 return IncrementUnaligned(this.counterEntryPointer);
1274 internal unsafe long Decrement() {
1275 if (counterEntryPointer == null)
1276 return 0;
1278 return DecrementUnaligned(this.counterEntryPointer);
1281 internal unsafe static void RemoveAllInstances(string categoryName) {
1282 SharedPerformanceCounter spc = new SharedPerformanceCounter(categoryName, null, null);
1283 spc.RemoveAllInstances();
1284 RemoveCategoryData(categoryName);
1287 [ResourceExposure(ResourceScope.None)]
1288 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
1289 private unsafe void RemoveAllInstances() {
1290 CategoryEntry* categoryPointer;
1291 if (!FindCategory(&categoryPointer))
1292 return;
1294 InstanceEntry* instancePointer = (InstanceEntry *)(ResolveOffset(categoryPointer->FirstInstanceOffset, InstanceEntrySize));
1296 Mutex mutex = null;
1297 RuntimeHelpers.PrepareConstrainedRegions();
1298 try {
1299 SharedUtils.EnterMutexWithoutGlobal(categoryData.MutexName, ref mutex);
1300 for(;;) {
1301 RemoveOneInstance(instancePointer, true);
1303 if (instancePointer->NextInstanceOffset != 0)
1304 instancePointer = (InstanceEntry*)(ResolveOffset(instancePointer->NextInstanceOffset, InstanceEntrySize));
1305 else {
1306 break;
1310 finally {
1311 if (mutex != null) {
1312 mutex.ReleaseMutex();
1313 mutex.Close();
1318 [ResourceExposure(ResourceScope.None)]
1319 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
1320 [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
1321 internal unsafe void RemoveInstance(string instanceName, PerformanceCounterInstanceLifetime instanceLifetime) {
1322 if (instanceName == null || instanceName.Length == 0)
1323 return;
1325 int instanceNameHashCode = GetWstrHashCode(instanceName);
1327 CategoryEntry* categoryPointer;
1328 if (!FindCategory(&categoryPointer))
1329 return;
1331 InstanceEntry* instancePointer = null;
1332 bool validatedCachedInstancePointer = false;
1333 bool temp;
1335 Mutex mutex = null;
1336 RuntimeHelpers.PrepareConstrainedRegions();
1337 try {
1338 SharedUtils.EnterMutexWithoutGlobal(categoryData.MutexName, ref mutex);
1340 if (this.thisInstanceOffset != -1) {
1341 try {
1342 // validate whether the cached instance pointer is pointing at the right instance
1343 instancePointer = (InstanceEntry*)(ResolveOffset(this.thisInstanceOffset, InstanceEntrySize));
1344 if (instancePointer->InstanceNameHashCode == instanceNameHashCode) {
1345 if (StringEquals(instanceName, instancePointer->InstanceNameOffset)){
1346 validatedCachedInstancePointer = true;
1348 // this is probably overkill
1349 CounterEntry* firstCounter = (CounterEntry*) ResolveOffset(instancePointer->FirstCounterOffset, CounterEntrySize);
1350 ProcessLifetimeEntry* lifetimeEntry;
1351 if (categoryData.UseUniqueSharedMemory) {
1352 lifetimeEntry = (ProcessLifetimeEntry*) ResolveOffset(firstCounter->LifetimeOffset, ProcessLifetimeEntrySize);
1353 if (lifetimeEntry != null
1354 && lifetimeEntry->LifetimeType == (int)PerformanceCounterInstanceLifetime.Process
1355 && lifetimeEntry->ProcessId != 0) {
1356 validatedCachedInstancePointer &= (instanceLifetime == PerformanceCounterInstanceLifetime.Process);
1357 validatedCachedInstancePointer &= (ProcessData.ProcessId == lifetimeEntry->ProcessId);
1358 if ((lifetimeEntry->StartupTime != -1) && (ProcessData.StartupTime != -1))
1359 validatedCachedInstancePointer &= (ProcessData.StartupTime == lifetimeEntry->StartupTime);
1361 else
1362 validatedCachedInstancePointer &= (instanceLifetime != PerformanceCounterInstanceLifetime.Process);
1367 catch (InvalidOperationException) {
1368 validatedCachedInstancePointer = false;
1370 if (!validatedCachedInstancePointer)
1371 this.thisInstanceOffset = -1;
1374 if (!validatedCachedInstancePointer && !FindInstance(instanceNameHashCode, instanceName, categoryPointer, &instancePointer, false, instanceLifetime, out temp))
1375 return ;
1377 if (instancePointer != null)
1378 RemoveOneInstance(instancePointer, false);
1380 finally {
1381 if (mutex != null) {
1382 mutex.ReleaseMutex();
1383 mutex.Close();
1388 [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
1389 private unsafe void RemoveOneInstance(InstanceEntry* instancePointer, bool clearValue) {
1390 bool sectionEntered = false;
1392 RuntimeHelpers.PrepareConstrainedRegions();
1393 try {
1394 if (!categoryData.UseUniqueSharedMemory) {
1395 while (!sectionEntered) {
1396 WaitAndEnterCriticalSection(&(instancePointer->SpinLock), out sectionEntered);
1400 instancePointer->RefCount = 0;
1402 if (clearValue)
1403 ClearCounterValues(instancePointer);
1405 finally {
1406 if (sectionEntered)
1407 ExitCriticalSection(&(instancePointer->SpinLock));
1411 private unsafe void ClearCounterValues(InstanceEntry* instancePointer) {
1412 //Clear counter instance values
1413 CounterEntry* currentCounterPointer = null;
1415 if (instancePointer->FirstCounterOffset != 0)
1416 currentCounterPointer = (CounterEntry*)(ResolveOffset(instancePointer->FirstCounterOffset, CounterEntrySize));
1418 while(currentCounterPointer != null) {
1419 SetValue(currentCounterPointer, 0);
1421 if (currentCounterPointer->NextCounterOffset != 0)
1422 currentCounterPointer = (CounterEntry*)(ResolveOffset(currentCounterPointer->NextCounterOffset, CounterEntrySize));
1423 else
1424 currentCounterPointer = null;
1429 [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
1430 private static unsafe long AddToValue(CounterEntry* counterEntry, long addend) {
1431 // Called while holding a lock - shouldn't have to worry about
1432 // reading misaligned data & getting old vs. new parts of an Int64.
1433 if (IsMisaligned(counterEntry)) {
1434 ulong newvalue;
1436 CounterEntryMisaligned* entry = (CounterEntryMisaligned*) counterEntry;
1437 newvalue = (uint)entry->Value_hi;
1438 newvalue <<= 32;
1439 newvalue |= (uint)entry->Value_lo;
1442 newvalue = (ulong) ((long) newvalue + addend);
1444 entry->Value_hi = (int) (newvalue >> 32);
1445 entry->Value_lo = (int) (newvalue & 0xffffffff);
1447 return (long) newvalue;
1449 else
1450 return Interlocked.Add(ref counterEntry->Value, addend);
1453 private static unsafe long DecrementUnaligned(CounterEntry* counterEntry) {
1454 if (IsMisaligned(counterEntry))
1455 return AddToValue(counterEntry, -1);
1456 else
1457 return Interlocked.Decrement(ref counterEntry->Value);
1460 private static unsafe long GetValue(CounterEntry* counterEntry) {
1461 if (IsMisaligned(counterEntry)) {
1462 ulong value;
1463 CounterEntryMisaligned* entry = (CounterEntryMisaligned*) counterEntry;
1464 value = (uint)entry->Value_hi;
1465 value <<= 32;
1466 value |= (uint)entry->Value_lo;
1468 return (long) value;
1470 else
1471 return counterEntry->Value;
1474 private static unsafe long IncrementUnaligned(CounterEntry* counterEntry) {
1475 if (IsMisaligned(counterEntry))
1476 return AddToValue(counterEntry, 1);
1477 else
1478 return Interlocked.Increment(ref counterEntry->Value);
1481 private static unsafe void SetValue(CounterEntry* counterEntry, long value) {
1482 if (IsMisaligned(counterEntry)) {
1483 CounterEntryMisaligned* entry = (CounterEntryMisaligned*) counterEntry;
1484 entry->Value_lo = (int) (value & 0xffffffff);
1485 entry->Value_hi = (int) (value >> 32);
1487 else
1488 counterEntry->Value = value;
1491 [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
1492 private static unsafe bool IsMisaligned(CounterEntry* counterEntry) {
1493 return (( (Int64)counterEntry & 0x7) != 0);
1496 [ResourceExposure(ResourceScope.None)]
1497 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
1498 private long ResolveOffset(int offset, int sizeToRead) {
1499 //It is very important to check the integrity of the shared memory
1500 //everytime a new address is resolved.
1501 if (offset > (FileView.FileMappingSize - sizeToRead) || offset < 0)
1502 throw new InvalidOperationException(SR.GetString(SR.MappingCorrupted));
1504 long address = baseAddress + offset;
1506 return address;
1509 [ResourceExposure(ResourceScope.None)]
1510 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
1511 private int ResolveAddress(long address, int sizeToRead) {
1512 int offset = (int)(address - baseAddress);
1514 //It is very important to check the integrity of the shared memory
1515 //everytime a new address is resolved.
1516 if (offset > (FileView.FileMappingSize - sizeToRead) || offset < 0)
1517 throw new InvalidOperationException(SR.GetString(SR.MappingCorrupted));
1519 return offset;
1523 private class FileMapping {
1524 internal int FileMappingSize;
1525 private SafeFileMapViewHandle fileViewAddress = null;
1526 private SafeFileMappingHandle fileMappingHandle = null;
1527 //The version of the file mapping name is independent from the
1528 //assembly version.
1530 [ResourceExposure(ResourceScope.Machine)]
1531 [ResourceConsumption(ResourceScope.Machine)]
1532 public FileMapping(string fileMappingName, int fileMappingSize, int initialOffset) {
1533 this.Initialize(fileMappingName, fileMappingSize, initialOffset);
1536 internal IntPtr FileViewAddress {
1537 get {
1538 if (fileViewAddress.IsInvalid)
1539 throw new InvalidOperationException(SR.GetString(SR.SharedMemoryGhosted));
1541 return fileViewAddress.DangerousGetHandle();
1545 [ResourceExposure(ResourceScope.Machine)]
1546 [ResourceConsumption(ResourceScope.Machine)]
1547 private unsafe void Initialize(string fileMappingName, int fileMappingSize, int initialOffset) {
1548 string mappingName = fileMappingName;
1549 SharedUtils.CheckEnvironment();
1551 SafeLocalMemHandle securityDescriptorPointer = null;
1552 new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert();
1553 try {
1554 // The sddl string consists of these parts:
1555 // D: it's a DACL
1556 // (A; this is an allow ACE
1557 // OICI; object inherit and container inherit
1558 // FRFWGRGW;;; allow file read, file write, generic read and generic write
1559 // AU) granted to Authenticated Users
1560 // ;S-1-5-33) the same permission granted to AU is also granted to restricted services
1561 string sddlString = "D:(A;OICI;FRFWGRGW;;;AU)(A;OICI;FRFWGRGW;;;S-1-5-33)";
1563 if (!SafeLocalMemHandle.ConvertStringSecurityDescriptorToSecurityDescriptor(sddlString, NativeMethods.SDDL_REVISION_1,
1564 out securityDescriptorPointer, IntPtr.Zero))
1565 throw new InvalidOperationException(SR.GetString(SR.SetSecurityDescriptorFailed));
1567 NativeMethods.SECURITY_ATTRIBUTES securityAttributes = new NativeMethods.SECURITY_ATTRIBUTES();
1568 securityAttributes.lpSecurityDescriptor = securityDescriptorPointer;
1569 securityAttributes.bInheritHandle = false;
1573 // Here we call CreateFileMapping to create the memory mapped file. When CreateFileMapping fails
1574 // with ERROR_ACCESS_DENIED, we know the file mapping has been created and we then open it with OpenFileMapping.
1576 // There is chance of a race condition between CreateFileMapping and OpenFileMapping; The memory mapped file
1577 // may actually be closed in between these two calls. When this happens, OpenFileMapping returns ERROR_FILE_NOT_FOUND.
1578 // In this case, we need to loop back and retry creating the memory mapped file.
1580 // This loop will timeout in approximately 1.4 minutes. An InvalidOperationException is thrown in the timeout case.
1583 int waitRetries = 14; //((2^13)-1)*10ms == approximately 1.4mins
1584 int waitSleep = 0;
1585 bool created = false;
1586 while (!created && waitRetries > 0) {
1587 fileMappingHandle = NativeMethods.CreateFileMapping((IntPtr)(-1), securityAttributes,
1588 NativeMethods.PAGE_READWRITE, 0, fileMappingSize, mappingName);
1590 if ((Marshal.GetLastWin32Error() != NativeMethods.ERROR_ACCESS_DENIED) || !fileMappingHandle.IsInvalid) {
1591 created = true;
1593 else {
1594 // Invalidate the old safehandle before we get rid of it. This prevents it from trying to finalize
1595 fileMappingHandle.SetHandleAsInvalid();
1596 fileMappingHandle = NativeMethods.OpenFileMapping(NativeMethods.FILE_MAP_WRITE, false, mappingName);
1598 if ((Marshal.GetLastWin32Error() != NativeMethods.ERROR_FILE_NOT_FOUND) || !fileMappingHandle.IsInvalid) {
1599 created = true;
1601 else {
1602 --waitRetries;
1603 if (waitSleep == 0) {
1604 waitSleep = 10;
1606 else {
1607 System.Threading.Thread.Sleep(waitSleep);
1608 waitSleep *= 2;
1613 if (fileMappingHandle.IsInvalid) {
1614 throw new InvalidOperationException(SR.GetString(SR.CantCreateFileMapping));
1617 fileViewAddress = SafeFileMapViewHandle.MapViewOfFile(fileMappingHandle, NativeMethods.FILE_MAP_WRITE, 0,0, UIntPtr.Zero);
1618 if (fileViewAddress.IsInvalid)
1619 throw new InvalidOperationException(SR.GetString(SR.CantMapFileView));
1621 // figure out what size the share memory really is.
1622 NativeMethods.MEMORY_BASIC_INFORMATION meminfo = new NativeMethods.MEMORY_BASIC_INFORMATION();
1623 if (NativeMethods.VirtualQuery(fileViewAddress, ref meminfo, (IntPtr) sizeof(NativeMethods.MEMORY_BASIC_INFORMATION)) == IntPtr.Zero)
1624 throw new InvalidOperationException(SR.GetString(SR.CantGetMappingSize));
1626 FileMappingSize = (int) meminfo.RegionSize;
1628 finally {
1629 if (securityDescriptorPointer != null) securityDescriptorPointer.Close();
1630 SecurityPermission.RevertAssert();
1633 SafeNativeMethods.InterlockedCompareExchange(fileViewAddress.DangerousGetHandle(), initialOffset, 0);
1638 // SafeMarshalCopy always null terminates the char array
1639 // before copying it to native memory
1641 private static void SafeMarshalCopy(string str, IntPtr nativePointer) {
1642 // convert str to a char array and copy it to the unmanaged memory pointer
1643 char[] tmp = new char[str.Length + 1];
1644 str.CopyTo(0, tmp, 0, str.Length);
1645 tmp[str.Length] = '\0'; // make sure the char[] is null terminated
1646 Marshal.Copy(tmp, 0, nativePointer, tmp.Length);
1649 // <WARNING>
1650 // The final tmpPadding field is needed to make the size of this structure 8-byte aligned. This is
1651 // necessary on IA64.
1652 // </WARNING>
1653 // Note that in V1.0 and v1.1 there was no explicit padding defined on any of these structs. That means that
1654 // sizeof(CategoryEntry) or Marshal.SizeOf(typeof(CategoryEntry)) returned 4 bytes less before Whidbey,
1655 // and the int we use as IsConsistent could actually overlap the InstanceEntry SpinLock.
1657 [StructLayout(LayoutKind.Sequential)]
1658 private struct CategoryEntry {
1659 public int SpinLock;
1660 public int CategoryNameHashCode;
1661 public int CategoryNameOffset;
1662 public int FirstInstanceOffset;
1663 public int NextCategoryOffset;
1664 public int IsConsistent; // this was 4 bytes of padding in v1.0/v1.1
1667 [StructLayout(LayoutKind.Sequential)]
1668 private struct InstanceEntry {
1669 public int SpinLock;
1670 public int InstanceNameHashCode;
1671 public int InstanceNameOffset;
1672 public int RefCount;
1673 public int FirstCounterOffset;
1674 public int NextInstanceOffset;
1677 [StructLayout(LayoutKind.Sequential)]
1678 private struct CounterEntry {
1679 public int SpinLock;
1680 public int CounterNameHashCode;
1681 public int CounterNameOffset;
1682 public int LifetimeOffset; // this was 4 bytes of padding in v1.0/v1.1
1683 public long Value;
1684 public int NextCounterOffset;
1685 public int padding2;
1688 [StructLayout(LayoutKind.Sequential)]
1689 private struct CounterEntryMisaligned {
1690 public int SpinLock;
1691 public int CounterNameHashCode;
1692 public int CounterNameOffset;
1693 public int LifetimeOffset; // this was 4 bytes of padding in v1.0/v1.1
1694 public int Value_lo;
1695 public int Value_hi;
1696 public int NextCounterOffset;
1697 public int padding2; // The compiler adds this only if there is an int64 in the struct -
1698 // ie only for CounterEntry. It really needs to be here.
1701 [StructLayout(LayoutKind.Sequential)]
1702 private struct ProcessLifetimeEntry {
1703 public int LifetimeType;
1704 public int ProcessId;
1705 public Int64 StartupTime;
1708 private class CategoryData {
1709 public FileMapping FileMapping;
1710 public bool EnableReuse;
1711 public bool UseUniqueSharedMemory;
1712 public string FileMappingName;
1713 public string MutexName;
1714 public ArrayList CounterNames;
1718 internal class ProcessData {
1719 public ProcessData(int pid, long startTime) {
1720 ProcessId = pid;
1721 StartupTime = startTime;
1723 public int ProcessId;
1724 public long StartupTime;