1 //------------------------------------------------------------ 2 // Copyright (c) Microsoft Corporation. All rights reserved. 3 //------------------------------------------------------------ 4 namespace System.ServiceModel.Diagnostics 5 { 6 using System.Collections.Generic; 7 using System.Diagnostics; 8 using System.Diagnostics.PerformanceData; 9 using System.Globalization; 10 using System.Runtime; 11 using System.Threading; 12 using System.Linq; 13 14 abstract class PerformanceCountersBase : IDisposable 15 { 16 internal abstract string InstanceName 17 { 18 get; 19 } 20 21 internal abstract string[] CounterNames 22 { 23 get; 24 } 25 26 internal abstract int PerfCounterStart 27 { 28 get; 29 } 30 31 internal abstract int PerfCounterEnd 32 { 33 get; 34 } 35 GetInstanceNameWithHash(string instanceName, string fullInstanceName)36 private static string GetInstanceNameWithHash(string instanceName, string fullInstanceName) 37 { 38 return String.Format("{0}{1}", instanceName, StringUtil.GetNonRandomizedHashCode(fullInstanceName).ToString("X", CultureInfo.InvariantCulture)); 39 } 40 EnsureUniqueInstanceName(string categoryName, string instanceName, string fullInstanceName)41 protected static string EnsureUniqueInstanceName(string categoryName, string instanceName, string fullInstanceName) 42 { 43 if (String.IsNullOrEmpty(categoryName)) 44 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNullOrEmptyString("categoryName"); 45 if (String.IsNullOrEmpty(instanceName)) 46 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNullOrEmptyString("instanceName"); 47 if (String.IsNullOrEmpty(fullInstanceName)) 48 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNullOrEmptyString("fullInstanceName"); 49 50 try 51 { 52 // If the instance name is already used, append a hash of the full name to it. 53 if (PerformanceCounterCategory.InstanceExists(instanceName, categoryName)) 54 { 55 return GetInstanceNameWithHash(instanceName, fullInstanceName); 56 } 57 } 58 catch 59 { 60 // If an exception is thrown, return the instance name without modification. 61 } 62 63 return instanceName; 64 } 65 GetUniqueInstanceName(string categoryName, string instanceName, string fullInstanceName)66 protected static string GetUniqueInstanceName(string categoryName, string instanceName, string fullInstanceName) 67 { 68 if (String.IsNullOrEmpty(categoryName)) 69 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNullOrEmptyString("categoryName"); 70 if (String.IsNullOrEmpty(instanceName)) 71 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNullOrEmptyString("instanceName"); 72 if (String.IsNullOrEmpty(fullInstanceName)) 73 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNullOrEmptyString("fullInstanceName"); 74 75 try 76 { 77 // If the instance name with the hash appended exists, return it. 78 string nameWithHash = GetInstanceNameWithHash(instanceName, fullInstanceName); 79 if (PerformanceCounterCategory.InstanceExists(nameWithHash, categoryName)) 80 { 81 return nameWithHash; 82 } 83 } 84 catch 85 { 86 // If an exception is thrown, return the instance name without modification. 87 } 88 89 return instanceName; 90 } 91 92 // remove count chars from string and add a 2 char hash code to beginning or end, as specified. GetHashedString(string str, int startIndex, int count, bool hashAtEnd)93 protected static string GetHashedString(string str, int startIndex, int count, bool hashAtEnd) 94 { 95 string returnVal = str.Remove(startIndex, count); 96 string hash = ((uint)StringUtil.GetNonRandomizedHashCode(str) % 99).ToString("00", CultureInfo.InvariantCulture); 97 return hashAtEnd ? returnVal + hash : hash + returnVal; 98 } 99 100 internal abstract bool Initialized { get; } 101 102 protected int disposed = 0; 103 Dispose()104 public void Dispose() 105 { 106 if (Interlocked.Exchange(ref disposed, 1) == 0) 107 { 108 Dispose(true); 109 } 110 } 111 Dispose(bool disposing)112 protected virtual void Dispose(bool disposing) 113 { 114 } 115 116 // A CounterSetInstance is not disposed immediately when a service, endpoint or operation perf counter is disposed. Because messages 117 // can be processed while a ServiceHost is being closed, and such messages can try to update perf counters data, resulting in AVs or 118 // corruptions (see bug 249132 @ CSDMain). So instead of disposing a CounterSetInstance, we hold a WeakReference to it, until either 119 // GC reclaims it or a new service/endpoint/operation perf counter is started with the same name (and re-uses the CounterSetInstance). 120 // The CounterSetInstance finalizer will free up the perf counters memory, so we don't have a leak. 121 protected class CounterSetInstanceCache 122 { 123 // instance name -> WeakReference of CounterSetInstance 124 private readonly Dictionary<string, WeakReference> cache = new Dictionary<string, WeakReference>(); 125 126 /// <summary> 127 /// Returns and removes the CounterSetInstance with the specified name from the cache. Returns null if not found. 128 /// </summary> Get(string instanceName)129 internal CounterSetInstance Get(string instanceName) 130 { 131 Fx.Assert(instanceName != null, "Invalid argument."); 132 133 lock (this.cache) 134 { 135 WeakReference wr; 136 if (this.cache.TryGetValue(instanceName, out wr)) 137 { 138 Fx.Assert(wr != null, "The values in 'availableCounterSetInstances' should not be null."); 139 this.cache.Remove(instanceName); 140 return (CounterSetInstance)wr.Target; 141 } 142 else 143 { 144 return null; 145 } 146 } 147 } 148 149 /// <summary> 150 /// Adds a CounterSetInstance to the cache, from where it will be garbage collected or re-used by another performance counter (whichever occurs first). 151 /// </summary> Add(string instanceName, CounterSetInstance instance)152 internal void Add(string instanceName, CounterSetInstance instance) 153 { 154 Fx.Assert(instanceName != null, "Invalid argument."); 155 Fx.Assert(instance != null, "Invalid argument."); 156 157 lock (this.cache) 158 { 159 this.cache[instanceName] = new WeakReference(instance); 160 } 161 } 162 163 /// <summary> 164 /// Clear the entries for CounterSetInstances that were garbage collected. 165 /// </summary> Cleanup()166 internal void Cleanup() 167 { 168 lock (this.cache) 169 { 170 foreach (var entry in this.cache.Where(pair => !pair.Value.IsAlive).ToList()) 171 { 172 this.cache.Remove(entry.Key); 173 } 174 } 175 } 176 } 177 } 178 } 179