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