//------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ namespace System.ServiceModel.Diagnostics { using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.PerformanceData; using System.Globalization; using System.Runtime; using System.Threading; using System.Linq; abstract class PerformanceCountersBase : IDisposable { internal abstract string InstanceName { get; } internal abstract string[] CounterNames { get; } internal abstract int PerfCounterStart { get; } internal abstract int PerfCounterEnd { get; } private static string GetInstanceNameWithHash(string instanceName, string fullInstanceName) { return String.Format("{0}{1}", instanceName, StringUtil.GetNonRandomizedHashCode(fullInstanceName).ToString("X", CultureInfo.InvariantCulture)); } protected static string EnsureUniqueInstanceName(string categoryName, string instanceName, string fullInstanceName) { if (String.IsNullOrEmpty(categoryName)) throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNullOrEmptyString("categoryName"); if (String.IsNullOrEmpty(instanceName)) throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNullOrEmptyString("instanceName"); if (String.IsNullOrEmpty(fullInstanceName)) throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNullOrEmptyString("fullInstanceName"); try { // If the instance name is already used, append a hash of the full name to it. if (PerformanceCounterCategory.InstanceExists(instanceName, categoryName)) { return GetInstanceNameWithHash(instanceName, fullInstanceName); } } catch { // If an exception is thrown, return the instance name without modification. } return instanceName; } protected static string GetUniqueInstanceName(string categoryName, string instanceName, string fullInstanceName) { if (String.IsNullOrEmpty(categoryName)) throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNullOrEmptyString("categoryName"); if (String.IsNullOrEmpty(instanceName)) throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNullOrEmptyString("instanceName"); if (String.IsNullOrEmpty(fullInstanceName)) throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNullOrEmptyString("fullInstanceName"); try { // If the instance name with the hash appended exists, return it. string nameWithHash = GetInstanceNameWithHash(instanceName, fullInstanceName); if (PerformanceCounterCategory.InstanceExists(nameWithHash, categoryName)) { return nameWithHash; } } catch { // If an exception is thrown, return the instance name without modification. } return instanceName; } // remove count chars from string and add a 2 char hash code to beginning or end, as specified. protected static string GetHashedString(string str, int startIndex, int count, bool hashAtEnd) { string returnVal = str.Remove(startIndex, count); string hash = ((uint)StringUtil.GetNonRandomizedHashCode(str) % 99).ToString("00", CultureInfo.InvariantCulture); return hashAtEnd ? returnVal + hash : hash + returnVal; } internal abstract bool Initialized { get; } protected int disposed = 0; public void Dispose() { if (Interlocked.Exchange(ref disposed, 1) == 0) { Dispose(true); } } protected virtual void Dispose(bool disposing) { } // A CounterSetInstance is not disposed immediately when a service, endpoint or operation perf counter is disposed. Because messages // can be processed while a ServiceHost is being closed, and such messages can try to update perf counters data, resulting in AVs or // corruptions (see bug 249132 @ CSDMain). So instead of disposing a CounterSetInstance, we hold a WeakReference to it, until either // GC reclaims it or a new service/endpoint/operation perf counter is started with the same name (and re-uses the CounterSetInstance). // The CounterSetInstance finalizer will free up the perf counters memory, so we don't have a leak. protected class CounterSetInstanceCache { // instance name -> WeakReference of CounterSetInstance private readonly Dictionary cache = new Dictionary(); /// /// Returns and removes the CounterSetInstance with the specified name from the cache. Returns null if not found. /// internal CounterSetInstance Get(string instanceName) { Fx.Assert(instanceName != null, "Invalid argument."); lock (this.cache) { WeakReference wr; if (this.cache.TryGetValue(instanceName, out wr)) { Fx.Assert(wr != null, "The values in 'availableCounterSetInstances' should not be null."); this.cache.Remove(instanceName); return (CounterSetInstance)wr.Target; } else { return null; } } } /// /// Adds a CounterSetInstance to the cache, from where it will be garbage collected or re-used by another performance counter (whichever occurs first). /// internal void Add(string instanceName, CounterSetInstance instance) { Fx.Assert(instanceName != null, "Invalid argument."); Fx.Assert(instance != null, "Invalid argument."); lock (this.cache) { this.cache[instanceName] = new WeakReference(instance); } } /// /// Clear the entries for CounterSetInstances that were garbage collected. /// internal void Cleanup() { lock (this.cache) { foreach (var entry in this.cache.Where(pair => !pair.Value.IsAlive).ToList()) { this.cache.Remove(entry.Key); } } } } } }