1 //------------------------------------------------------------------------------
2 // <copyright file="SharedPerformanceCounter.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
7 namespace System
.Diagnostics
{
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
{
48 if (procData
== null) {
49 new SecurityPermission(SecurityPermissionFlag
.UnmanagedCode
).Assert();
51 int pid
= NativeMethods
.GetCurrentProcessId();
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
) {
63 NativeMethods
.GetProcessTimes(procHandle
, out startTime
, out temp
, out temp
, out temp
);
66 procData
= new ProcessData(pid
, startTime
);
69 SecurityPermission
.RevertAssert();
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
));
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
);
118 this.counterEntryPointer
= GetCounter(counterName
, instanceName
, categoryData
.EnableReuse
, lifetime
);
122 private FileMapping FileView
{
123 [ResourceExposure(ResourceScope
.Machine
)]
125 return categoryData
.FileMapping
;
129 internal unsafe long Value
{
131 if (counterEntryPointer
== null)
134 return GetValue(this.counterEntryPointer
);
138 if (counterEntryPointer
== null)
141 SetValue(this.counterEntryPointer
, value);
145 private unsafe int CalculateAndAllocateMemory(int totalSize
, out int alignmentAdjustment
) {
148 alignmentAdjustment
= 0;
150 Debug
.Assert(!categoryData
.UseUniqueSharedMemory
, "We should never be calling CalculateAndAllocateMemory in the unique shared memory");
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
);
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
));
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
;
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
;
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;
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
;
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;
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
;
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
);
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
);
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);
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
;
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();
516 int r
= Interlocked
.CompareExchange(ref *spinLockPointer
, 1, 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)
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
)
551 for(uint i
=0; i
< wstr
.Length
; i
++)
552 hash
= ((hash
<< 5) + hash
) ^ wstr
[(int) i
];
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
);
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
);
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
])
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
];
605 lock(categoryDataTable
) {
606 data
= (CategoryData
) categoryDataTable
[categoryName
];
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;
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;
627 data
.FileMappingName
= DefaultFileMappingName
+ categoryName
;
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
;
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
) {
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());
664 data
.CounterNames
= names
;
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
;
683 if (categoryKey
!= null)
685 RegistryPermission
.RevertAssert();
690 baseAddress
= (long) data
.FileMapping
.FileViewAddress
;
692 if (data
.UseUniqueSharedMemory
)
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
);
718 instanceNameHashCode
= SingleInstanceHashCode
;
719 instanceName
= SingleInstanceName
;
723 CounterEntry
* counterPointer
= null;
724 InstanceEntry
* instancePointer
= null;
725 RuntimeHelpers
.PrepareConstrainedRegions();
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.
733 if (categoryData
.UseUniqueSharedMemory
)
734 sectionEntered
= true;
736 WaitAndEnterCriticalSection(&(categoryPointer
->SpinLock
), out sectionEntered
);
738 int newCategoryOffset
;
739 if (sectionEntered
) {
741 newCategoryOffset
= CreateCategory(categoryPointer
, instanceNameHashCode
, instanceName
, lifetime
);
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.
762 if (categoryData
.UseUniqueSharedMemory
)
763 sectionEntered
= true;
765 WaitAndEnterCriticalSection(&(lockInstancePointer
->SpinLock
), out sectionEntered
);
767 if (sectionEntered
) {
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.
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
;
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
;
799 while (!FindCounter(counterNameHashCode
, counterName
, instancePointer
, &counterPointer
)) {
801 WaitAndEnterCriticalSection(&(counterPointer
->SpinLock
), out sectionEntered
);
803 if (sectionEntered
) {
805 int newCounterOffset
= CreateCounter(counterPointer
, counterNameHashCode
, counterName
);
806 return (CounterEntry
*) (ResolveOffset(newCounterOffset
, CounterEntrySize
));
809 ExitCriticalSection(&(counterPointer
->SpinLock
));
814 return counterPointer
;
818 // cache this instance for reuse
820 if (counterPointer
!= null && instancePointer
!= null) {
821 this.thisInstanceOffset
= ResolveAddress((long)instancePointer
, InstanceEntrySize
);
824 catch (InvalidOperationException
) {
825 this.thisInstanceOffset
= -1;
829 mutex
.ReleaseMutex();
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
;
850 if (currentCategoryPointer
->IsConsistent
== 0)
851 Verify(currentCategoryPointer
);
853 if (currentCategoryPointer
->CategoryNameHashCode
== categoryNameHashCode
) {
854 if (StringEquals(categoryName
, currentCategoryPointer
->CategoryNameOffset
)) {
855 *returnCategoryPointerReference
= currentCategoryPointer
;
860 previousCategoryPointer
= currentCategoryPointer
;
861 if (currentCategoryPointer
->NextCategoryOffset
!= 0)
862 currentCategoryPointer
= (CategoryEntry
*)(ResolveOffset(currentCategoryPointer
->NextCategoryOffset
, CategoryEntrySize
));
864 *returnCategoryPointerReference
= previousCategoryPointer
;
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
;
874 if (currentCounterPointer
->CounterNameHashCode
== counterNameHashCode
) {
875 if (StringEquals(counterName
, currentCounterPointer
->CounterNameOffset
)) {
876 *returnCounterPointerReference
= currentCounterPointer
;
881 previousCounterPointer
= currentCounterPointer
;
882 if (currentCounterPointer
->NextCounterOffset
!= 0)
883 currentCounterPointer
= (CounterEntry
*)(ResolveOffset(currentCounterPointer
->NextCounterOffset
, CounterEntrySize
));
885 *returnCounterPointerReference
= previousCounterPointer
;
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
));
908 if (instanceName
== SingleInstanceName
)
909 throw new InvalidOperationException(SR
.GetString(SR
.MultiInstanceOnly
, categoryName
));
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();
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
);
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
));
979 if (lifetime
== PerformanceCounterInstanceLifetime
.Process
)
980 throw new InvalidOperationException(SR
.GetString(SR
.CantConvertGlobalToProcess
));
985 if (activateUnusedInstances
) {
987 RuntimeHelpers
.PrepareConstrainedRegions();
989 SharedUtils
.EnterMutexWithoutGlobal(categoryData
.MutexName
, ref mutex
);
990 ClearCounterValues(currentInstancePointer
);
991 if (lifetimeEntry
!= null)
992 PopulateLifetimeEntry(lifetimeEntry
, lifetime
);
994 currentInstancePointer
->RefCount
= 1;
999 mutex
.ReleaseMutex();
1009 if (currentInstancePointer
->RefCount
== 0) {
1010 foundFreeInstance
= true;
1013 previousInstancePointer
= currentInstancePointer
;
1014 if (currentInstancePointer
->NextInstanceOffset
!= 0)
1015 currentInstancePointer
= (InstanceEntry
*)(ResolveOffset(currentInstancePointer
->NextInstanceOffset
, InstanceEntrySize
));
1017 *returnInstancePointerReference
= previousInstancePointer
;
1023 SecurityPermission
.RevertAssert();
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
;
1040 if (currentInstancePointer
->RefCount
== 0) {
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");
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
1066 // don't bother locking again if we're using a separate shared memory.
1067 bool sectionEntered
;
1069 sectionEntered
= true;
1071 WaitAndEnterCriticalSection(&(currentInstancePointer
->SpinLock
), out sectionEntered
);
1073 if (sectionEntered
) {
1075 // Make copy with zero-term
1076 SafeMarshalCopy(instanceName
, (IntPtr
)instanceNamePtr
);
1077 currentInstancePointer
->InstanceNameHashCode
= instanceNameHashCode
;
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;
1095 ExitCriticalSection(&(currentInstancePointer
->SpinLock
));
1101 previousInstancePointer
= currentInstancePointer
;
1102 if (currentInstancePointer
->NextInstanceOffset
!= 0)
1103 currentInstancePointer
= (InstanceEntry
*)(ResolveOffset(currentInstancePointer
->NextInstanceOffset
, InstanceEntrySize
));
1106 *returnInstancePointerReference
= previousInstancePointer
;
1112 [ResourceExposure(ResourceScope
.None
)]
1113 [ResourceConsumption(ResourceScope
.Machine
, ResourceScope
.Machine
)]
1114 private unsafe void Verify(CategoryEntry
* currentCategoryPointer
) {
1115 if (!categoryData
.UseUniqueSharedMemory
)
1119 RuntimeHelpers
.PrepareConstrainedRegions();
1121 SharedUtils
.EnterMutexWithoutGlobal(categoryData
.MutexName
, ref mutex
);
1122 VerifyCategory(currentCategoryPointer
);
1125 if (mutex
!= null) {
1126 mutex
.ReleaseMutex();
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;
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
;
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;
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;
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) {
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;
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;
1257 [ReliabilityContract(Consistency
.WillNotCorruptState
, Cer
.MayFail
)]
1258 internal unsafe long IncrementBy(long value) {
1259 if (counterEntryPointer
== null)
1262 CounterEntry
* counterEntry
= this.counterEntryPointer
;
1264 return AddToValue(counterEntry
, value);
1267 internal unsafe long Increment() {
1268 if (counterEntryPointer
== null)
1271 return IncrementUnaligned(this.counterEntryPointer
);
1274 internal unsafe long Decrement() {
1275 if (counterEntryPointer
== null)
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
))
1294 InstanceEntry
* instancePointer
= (InstanceEntry
*)(ResolveOffset(categoryPointer
->FirstInstanceOffset
, InstanceEntrySize
));
1297 RuntimeHelpers
.PrepareConstrainedRegions();
1299 SharedUtils
.EnterMutexWithoutGlobal(categoryData
.MutexName
, ref mutex
);
1301 RemoveOneInstance(instancePointer
, true);
1303 if (instancePointer
->NextInstanceOffset
!= 0)
1304 instancePointer
= (InstanceEntry
*)(ResolveOffset(instancePointer
->NextInstanceOffset
, InstanceEntrySize
));
1311 if (mutex
!= null) {
1312 mutex
.ReleaseMutex();
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)
1325 int instanceNameHashCode
= GetWstrHashCode(instanceName
);
1327 CategoryEntry
* categoryPointer
;
1328 if (!FindCategory(&categoryPointer
))
1331 InstanceEntry
* instancePointer
= null;
1332 bool validatedCachedInstancePointer
= false;
1336 RuntimeHelpers
.PrepareConstrainedRegions();
1338 SharedUtils
.EnterMutexWithoutGlobal(categoryData
.MutexName
, ref mutex
);
1340 if (this.thisInstanceOffset
!= -1) {
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
);
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
))
1377 if (instancePointer
!= null)
1378 RemoveOneInstance(instancePointer
, false);
1381 if (mutex
!= null) {
1382 mutex
.ReleaseMutex();
1388 [ReliabilityContract(Consistency
.WillNotCorruptState
, Cer
.MayFail
)]
1389 private unsafe void RemoveOneInstance(InstanceEntry
* instancePointer
, bool clearValue
) {
1390 bool sectionEntered
= false;
1392 RuntimeHelpers
.PrepareConstrainedRegions();
1394 if (!categoryData
.UseUniqueSharedMemory
) {
1395 while (!sectionEntered
) {
1396 WaitAndEnterCriticalSection(&(instancePointer
->SpinLock
), out sectionEntered
);
1400 instancePointer
->RefCount
= 0;
1403 ClearCounterValues(instancePointer
);
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
));
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
)) {
1436 CounterEntryMisaligned
* entry
= (CounterEntryMisaligned
*) counterEntry
;
1437 newvalue
= (uint)entry
->Value_hi
;
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
;
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);
1457 return Interlocked
.Decrement(ref counterEntry
->Value
);
1460 private static unsafe long GetValue(CounterEntry
* counterEntry
) {
1461 if (IsMisaligned(counterEntry
)) {
1463 CounterEntryMisaligned
* entry
= (CounterEntryMisaligned
*) counterEntry
;
1464 value = (uint)entry
->Value_hi
;
1466 value |= (uint)entry
->Value_lo
;
1468 return (long) value;
1471 return counterEntry
->Value
;
1474 private static unsafe long IncrementUnaligned(CounterEntry
* counterEntry
) {
1475 if (IsMisaligned(counterEntry
))
1476 return AddToValue(counterEntry
, 1);
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);
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
;
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
));
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
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
{
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();
1554 // The sddl string consists of these parts:
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
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
) {
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
) {
1603 if (waitSleep
== 0) {
1607 System
.Threading
.Thread
.Sleep(waitSleep
);
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
;
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
);
1650 // The final tmpPadding field is needed to make the size of this structure 8-byte aligned. This is
1651 // necessary on IA64.
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
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
) {
1721 StartupTime
= startTime
;
1723 public int ProcessId
;
1724 public long StartupTime
;